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.
- package/README.md +7 -0
- package/oclif.manifest.json +41 -1
- 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 +143 -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 +95 -21
- 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 +127 -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 +318 -0
- package/src/services/ai-env-deployer.js +444 -0
- package/src/services/ai-env-scanner.js +242 -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 +85 -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 +1006 -0
- package/src/utils/tui/modal.js +15 -14
- package/src/utils/tui/navigable-table.js +25 -17
- package/src/utils/tui/tab-tui.js +800 -0
- package/src/utils/typewriter.js +3 -3
- package/src/utils/welcome.js +18 -21
- package/src/validators/repo-name.js +2 -2
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {Command, Flags, Args} from '@oclif/core'
|
|
2
2
|
import ora from 'ora'
|
|
3
3
|
import chalk from 'chalk'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import {checkbox, confirm, input} from '@inquirer/prompts'
|
|
5
|
+
import {detectPlatform} from '../../services/platform.js'
|
|
6
6
|
import {
|
|
7
7
|
isChezmoiInstalled,
|
|
8
8
|
getManagedFiles,
|
|
@@ -11,13 +11,13 @@ import {
|
|
|
11
11
|
isPathSensitive,
|
|
12
12
|
isWSLWindowsPath,
|
|
13
13
|
} from '../../services/dotfiles.js'
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
14
|
+
import {loadConfig} from '../../services/config.js'
|
|
15
|
+
import {execOrThrow} from '../../services/shell.js'
|
|
16
|
+
import {formatDotfilesAdd} from '../../formatters/dotfiles.js'
|
|
17
|
+
import {DvmiError} from '../../utils/errors.js'
|
|
18
|
+
import {homedir} from 'node:os'
|
|
19
|
+
import {join} from 'node:path'
|
|
20
|
+
import {existsSync} from 'node:fs'
|
|
21
21
|
|
|
22
22
|
/** @import { DotfilesAddResult } from '../../types.js' */
|
|
23
23
|
|
|
@@ -47,13 +47,13 @@ export default class DotfilesAdd extends Command {
|
|
|
47
47
|
static enableJsonFlag = true
|
|
48
48
|
|
|
49
49
|
static flags = {
|
|
50
|
-
help: Flags.help({
|
|
51
|
-
encrypt: Flags.boolean({
|
|
52
|
-
'no-encrypt': Flags.boolean({
|
|
50
|
+
help: Flags.help({char: 'h'}),
|
|
51
|
+
encrypt: Flags.boolean({char: 'e', description: 'Force encryption for all files being added', default: false}),
|
|
52
|
+
'no-encrypt': Flags.boolean({description: 'Disable auto-encryption (add all as plaintext)', default: false}),
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
static args = {
|
|
56
|
-
files: Args.string({
|
|
56
|
+
files: Args.string({description: 'File paths to add', required: false}),
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
// oclif does not support variadic args natively via Args.string for multiple values;
|
|
@@ -61,7 +61,7 @@ export default class DotfilesAdd extends Command {
|
|
|
61
61
|
static strict = false
|
|
62
62
|
|
|
63
63
|
async run() {
|
|
64
|
-
const {
|
|
64
|
+
const {flags} = await this.parse(DotfilesAdd)
|
|
65
65
|
const isJson = flags.json
|
|
66
66
|
const forceEncrypt = flags.encrypt
|
|
67
67
|
const forceNoEncrypt = flags['no-encrypt']
|
|
@@ -73,23 +73,21 @@ export default class DotfilesAdd extends Command {
|
|
|
73
73
|
// Pre-checks
|
|
74
74
|
const config = await loadConfig()
|
|
75
75
|
if (!config.dotfiles?.enabled) {
|
|
76
|
-
throw new DvmiError(
|
|
77
|
-
'Chezmoi dotfiles management is not configured',
|
|
78
|
-
'Run `dvmi dotfiles setup` first',
|
|
79
|
-
)
|
|
76
|
+
throw new DvmiError('Chezmoi dotfiles management is not configured', 'Run `dvmi dotfiles setup` first')
|
|
80
77
|
}
|
|
81
78
|
|
|
82
79
|
const chezmoiInstalled = await isChezmoiInstalled()
|
|
83
80
|
if (!chezmoiInstalled) {
|
|
84
81
|
const platformInfo = await detectPlatform()
|
|
85
|
-
const hint =
|
|
86
|
-
|
|
87
|
-
|
|
82
|
+
const hint =
|
|
83
|
+
platformInfo.platform === 'macos'
|
|
84
|
+
? 'Run `brew install chezmoi` or visit https://chezmoi.io/install'
|
|
85
|
+
: 'Run `sh -c "$(curl -fsLS get.chezmoi.io)"` or visit https://chezmoi.io/install'
|
|
88
86
|
throw new DvmiError('chezmoi is not installed', hint)
|
|
89
87
|
}
|
|
90
88
|
|
|
91
89
|
const platformInfo = await detectPlatform()
|
|
92
|
-
const {
|
|
90
|
+
const {platform} = platformInfo
|
|
93
91
|
const sensitivePatterns = getSensitivePatterns(config)
|
|
94
92
|
|
|
95
93
|
// Get already-managed files for V-007 check
|
|
@@ -97,7 +95,7 @@ export default class DotfilesAdd extends Command {
|
|
|
97
95
|
const managedPaths = new Set(managedFiles.map((f) => f.path))
|
|
98
96
|
|
|
99
97
|
/** @type {DotfilesAddResult} */
|
|
100
|
-
const result = {
|
|
98
|
+
const result = {added: [], skipped: [], rejected: []}
|
|
101
99
|
|
|
102
100
|
if (fileArgs.length > 0) {
|
|
103
101
|
// Direct mode — files provided as arguments
|
|
@@ -107,19 +105,22 @@ export default class DotfilesAdd extends Command {
|
|
|
107
105
|
|
|
108
106
|
// V-002: WSL2 Windows path rejection
|
|
109
107
|
if (platform === 'wsl2' && isWSLWindowsPath(absPath)) {
|
|
110
|
-
result.rejected.push({
|
|
108
|
+
result.rejected.push({
|
|
109
|
+
path: displayPath,
|
|
110
|
+
reason: 'Windows filesystem paths not supported on WSL2. Use Linux-native paths (~/) instead.',
|
|
111
|
+
})
|
|
111
112
|
continue
|
|
112
113
|
}
|
|
113
114
|
|
|
114
115
|
// V-001: file must exist
|
|
115
116
|
if (!existsSync(absPath)) {
|
|
116
|
-
result.skipped.push({
|
|
117
|
+
result.skipped.push({path: displayPath, reason: 'File not found'})
|
|
117
118
|
continue
|
|
118
119
|
}
|
|
119
120
|
|
|
120
121
|
// V-007: not already managed
|
|
121
122
|
if (managedPaths.has(absPath)) {
|
|
122
|
-
result.skipped.push({
|
|
123
|
+
result.skipped.push({path: displayPath, reason: 'Already managed by chezmoi'})
|
|
123
124
|
continue
|
|
124
125
|
}
|
|
125
126
|
|
|
@@ -138,9 +139,12 @@ export default class DotfilesAdd extends Command {
|
|
|
138
139
|
if (encrypt) args.push('--encrypt')
|
|
139
140
|
args.push(absPath)
|
|
140
141
|
await execOrThrow('chezmoi', args)
|
|
141
|
-
result.added.push({
|
|
142
|
+
result.added.push({path: displayPath, encrypted: encrypt})
|
|
142
143
|
} catch {
|
|
143
|
-
result.skipped.push({
|
|
144
|
+
result.skipped.push({
|
|
145
|
+
path: displayPath,
|
|
146
|
+
reason: `Failed to add to chezmoi. Run \`chezmoi doctor\` to verify your setup.`,
|
|
147
|
+
})
|
|
144
148
|
}
|
|
145
149
|
}
|
|
146
150
|
|
|
@@ -161,11 +165,15 @@ export default class DotfilesAdd extends Command {
|
|
|
161
165
|
if (isCI || isNonInteractive) {
|
|
162
166
|
this.error(
|
|
163
167
|
'This command requires an interactive terminal (TTY) when no files are specified. Provide file paths as arguments or run with --json.',
|
|
164
|
-
{
|
|
168
|
+
{exit: 1},
|
|
165
169
|
)
|
|
166
170
|
}
|
|
167
171
|
|
|
168
|
-
const spinner = ora({
|
|
172
|
+
const spinner = ora({
|
|
173
|
+
spinner: 'arc',
|
|
174
|
+
color: false,
|
|
175
|
+
text: chalk.hex('#FF6B2B')('Loading recommended files...'),
|
|
176
|
+
}).start()
|
|
169
177
|
const recommended = getDefaultFileList(platform)
|
|
170
178
|
spinner.stop()
|
|
171
179
|
|
|
@@ -191,9 +199,9 @@ export default class DotfilesAdd extends Command {
|
|
|
191
199
|
})
|
|
192
200
|
|
|
193
201
|
// Offer custom file
|
|
194
|
-
const addCustom = await confirm({
|
|
202
|
+
const addCustom = await confirm({message: 'Add a custom file path?', default: false})
|
|
195
203
|
if (addCustom) {
|
|
196
|
-
const customPath = await input({
|
|
204
|
+
const customPath = await input({message: 'Enter file path:'})
|
|
197
205
|
if (customPath.trim()) selected.push(customPath.trim())
|
|
198
206
|
}
|
|
199
207
|
|
|
@@ -202,24 +210,28 @@ export default class DotfilesAdd extends Command {
|
|
|
202
210
|
return result
|
|
203
211
|
}
|
|
204
212
|
|
|
205
|
-
const addSpinner = ora({
|
|
213
|
+
const addSpinner = ora({
|
|
214
|
+
spinner: 'arc',
|
|
215
|
+
color: false,
|
|
216
|
+
text: chalk.hex('#FF6B2B')('Adding files to chezmoi...'),
|
|
217
|
+
}).start()
|
|
206
218
|
addSpinner.stop()
|
|
207
219
|
|
|
208
220
|
for (const rawPath of selected) {
|
|
209
221
|
const absPath = expandTilde(rawPath)
|
|
210
222
|
|
|
211
223
|
if (platform === 'wsl2' && isWSLWindowsPath(absPath)) {
|
|
212
|
-
result.rejected.push({
|
|
224
|
+
result.rejected.push({path: rawPath, reason: 'Windows filesystem paths not supported on WSL2'})
|
|
213
225
|
continue
|
|
214
226
|
}
|
|
215
227
|
|
|
216
228
|
if (!existsSync(absPath)) {
|
|
217
|
-
result.skipped.push({
|
|
229
|
+
result.skipped.push({path: rawPath, reason: 'File not found'})
|
|
218
230
|
continue
|
|
219
231
|
}
|
|
220
232
|
|
|
221
233
|
if (managedPaths.has(absPath)) {
|
|
222
|
-
result.skipped.push({
|
|
234
|
+
result.skipped.push({path: rawPath, reason: 'Already managed by chezmoi'})
|
|
223
235
|
continue
|
|
224
236
|
}
|
|
225
237
|
|
|
@@ -237,9 +249,9 @@ export default class DotfilesAdd extends Command {
|
|
|
237
249
|
if (encrypt) args.push('--encrypt')
|
|
238
250
|
args.push(absPath)
|
|
239
251
|
await execOrThrow('chezmoi', args)
|
|
240
|
-
result.added.push({
|
|
252
|
+
result.added.push({path: rawPath, encrypted: encrypt})
|
|
241
253
|
} catch {
|
|
242
|
-
result.skipped.push({
|
|
254
|
+
result.skipped.push({path: rawPath, reason: `Failed to add. Run \`chezmoi doctor\` to verify your setup.`})
|
|
243
255
|
}
|
|
244
256
|
}
|
|
245
257
|
|
|
@@ -1,30 +1,27 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {Command, Flags} from '@oclif/core'
|
|
2
2
|
import ora from 'ora'
|
|
3
3
|
import chalk from 'chalk'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
4
|
+
import {confirm} from '@inquirer/prompts'
|
|
5
|
+
import {detectPlatform} from '../../services/platform.js'
|
|
6
|
+
import {isChezmoiInstalled, getChezmoiConfig, buildSetupSteps} from '../../services/dotfiles.js'
|
|
7
|
+
import {formatDotfilesSetup} from '../../formatters/dotfiles.js'
|
|
8
|
+
import {DvmiError} from '../../utils/errors.js'
|
|
9
9
|
|
|
10
10
|
/** @import { DotfilesSetupResult, SetupStep, StepResult } from '../../types.js' */
|
|
11
11
|
|
|
12
12
|
export default class DotfilesSetup extends Command {
|
|
13
13
|
static description = 'Interactive wizard to configure chezmoi with age encryption for dotfile management'
|
|
14
14
|
|
|
15
|
-
static examples = [
|
|
16
|
-
'<%= config.bin %> dotfiles setup',
|
|
17
|
-
'<%= config.bin %> dotfiles setup --json',
|
|
18
|
-
]
|
|
15
|
+
static examples = ['<%= config.bin %> dotfiles setup', '<%= config.bin %> dotfiles setup --json']
|
|
19
16
|
|
|
20
17
|
static enableJsonFlag = true
|
|
21
18
|
|
|
22
19
|
static flags = {
|
|
23
|
-
help: Flags.help({
|
|
20
|
+
help: Flags.help({char: 'h'}),
|
|
24
21
|
}
|
|
25
22
|
|
|
26
23
|
async run() {
|
|
27
|
-
const {
|
|
24
|
+
const {flags} = await this.parse(DotfilesSetup)
|
|
28
25
|
const isJson = flags.json
|
|
29
26
|
|
|
30
27
|
// Non-interactive guard
|
|
@@ -33,12 +30,12 @@ export default class DotfilesSetup extends Command {
|
|
|
33
30
|
if ((isCI || isNonInteractive) && !isJson) {
|
|
34
31
|
this.error(
|
|
35
32
|
'This command requires an interactive terminal (TTY). Run with --json for a non-interactive status check.',
|
|
36
|
-
{
|
|
33
|
+
{exit: 1},
|
|
37
34
|
)
|
|
38
35
|
}
|
|
39
36
|
|
|
40
37
|
const platformInfo = await detectPlatform()
|
|
41
|
-
const {
|
|
38
|
+
const {platform} = platformInfo
|
|
42
39
|
|
|
43
40
|
// --json branch: non-interactive setup attempt
|
|
44
41
|
if (isJson) {
|
|
@@ -52,9 +49,10 @@ export default class DotfilesSetup extends Command {
|
|
|
52
49
|
sourceDir: null,
|
|
53
50
|
publicKey: null,
|
|
54
51
|
status: 'failed',
|
|
55
|
-
message:
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
message:
|
|
53
|
+
platform === 'macos'
|
|
54
|
+
? 'chezmoi is not installed. Run `brew install chezmoi` or visit https://chezmoi.io/install'
|
|
55
|
+
: 'chezmoi is not installed. Run `sh -c "$(curl -fsLS get.chezmoi.io)"` or visit https://chezmoi.io/install',
|
|
58
56
|
}
|
|
59
57
|
}
|
|
60
58
|
|
|
@@ -76,15 +74,20 @@ export default class DotfilesSetup extends Command {
|
|
|
76
74
|
// ---------------------------------------------------------------------------
|
|
77
75
|
// Interactive mode
|
|
78
76
|
// ---------------------------------------------------------------------------
|
|
79
|
-
const preSpinner = ora({
|
|
77
|
+
const preSpinner = ora({
|
|
78
|
+
spinner: 'arc',
|
|
79
|
+
color: false,
|
|
80
|
+
text: chalk.hex('#FF6B2B')('Checking chezmoi status...'),
|
|
81
|
+
}).start()
|
|
80
82
|
const chezmoiInstalled = await isChezmoiInstalled()
|
|
81
83
|
const existingConfig = await getChezmoiConfig()
|
|
82
84
|
preSpinner.stop()
|
|
83
85
|
|
|
84
86
|
if (!chezmoiInstalled) {
|
|
85
|
-
const hint =
|
|
86
|
-
|
|
87
|
-
|
|
87
|
+
const hint =
|
|
88
|
+
platform === 'macos'
|
|
89
|
+
? 'Run `brew install chezmoi` or visit https://chezmoi.io/install'
|
|
90
|
+
: 'Run `sh -c "$(curl -fsLS get.chezmoi.io)"` or visit https://chezmoi.io/install'
|
|
88
91
|
throw new DvmiError('chezmoi is not installed', hint)
|
|
89
92
|
}
|
|
90
93
|
|
|
@@ -92,18 +95,26 @@ export default class DotfilesSetup extends Command {
|
|
|
92
95
|
const hasEncryption = existingConfig?.encryption?.tool === 'age' || !!existingConfig?.age?.identity
|
|
93
96
|
if (existingConfig && hasEncryption) {
|
|
94
97
|
this.log(chalk.green(' ✔ chezmoi is already configured with age encryption'))
|
|
95
|
-
const reconfigure = await confirm({
|
|
98
|
+
const reconfigure = await confirm({message: 'Reconfigure encryption (regenerate age key)?', default: false})
|
|
96
99
|
if (!reconfigure) {
|
|
97
100
|
const sourceDir = existingConfig?.sourceDir ?? existingConfig?.sourcePath ?? null
|
|
98
101
|
this.log(chalk.dim(' Skipped. Existing encryption configuration kept.'))
|
|
99
|
-
return {
|
|
102
|
+
return {
|
|
103
|
+
platform,
|
|
104
|
+
chezmoiInstalled: true,
|
|
105
|
+
encryptionConfigured: true,
|
|
106
|
+
sourceDir,
|
|
107
|
+
publicKey: null,
|
|
108
|
+
status: 'skipped',
|
|
109
|
+
message: 'Existing encryption configuration kept',
|
|
110
|
+
}
|
|
100
111
|
}
|
|
101
112
|
} else if (existingConfig) {
|
|
102
113
|
this.log(chalk.yellow(' chezmoi is initialised but encryption is not configured — adding age encryption'))
|
|
103
114
|
}
|
|
104
115
|
|
|
105
116
|
// Build and run steps
|
|
106
|
-
const steps = buildSetupSteps(platform, {
|
|
117
|
+
const steps = buildSetupSteps(platform, {existingConfig})
|
|
107
118
|
|
|
108
119
|
this.log('')
|
|
109
120
|
|
|
@@ -111,24 +122,24 @@ export default class DotfilesSetup extends Command {
|
|
|
111
122
|
let sourceDir = null
|
|
112
123
|
|
|
113
124
|
for (const step of steps) {
|
|
114
|
-
const typeColor = {
|
|
125
|
+
const typeColor = {check: chalk.blue, install: chalk.yellow, configure: chalk.cyan, verify: chalk.green}
|
|
115
126
|
const colorFn = typeColor[step.type] ?? chalk.white
|
|
116
127
|
this.log(` ${colorFn(`[${step.type}]`)} ${step.label}`)
|
|
117
128
|
|
|
118
129
|
if (step.requiresConfirmation) {
|
|
119
|
-
const proceed = await confirm({
|
|
130
|
+
const proceed = await confirm({message: `Proceed with: ${step.label}?`, default: true})
|
|
120
131
|
if (!proceed) {
|
|
121
132
|
this.log(chalk.dim(' Skipped.'))
|
|
122
133
|
continue
|
|
123
134
|
}
|
|
124
135
|
}
|
|
125
136
|
|
|
126
|
-
const stepSpinner = ora({
|
|
137
|
+
const stepSpinner = ora({spinner: 'arc', color: false, text: chalk.dim(step.label)}).start()
|
|
127
138
|
let result
|
|
128
139
|
try {
|
|
129
140
|
result = await step.run()
|
|
130
141
|
} catch (err) {
|
|
131
|
-
result = {
|
|
142
|
+
result = {status: /** @type {'failed'} */ ('failed'), hint: err instanceof Error ? err.message : String(err)}
|
|
132
143
|
}
|
|
133
144
|
|
|
134
145
|
if (result.status === 'success') {
|
|
@@ -147,8 +158,26 @@ export default class DotfilesSetup extends Command {
|
|
|
147
158
|
} else {
|
|
148
159
|
stepSpinner.fail(chalk.red(`${step.label} — failed`))
|
|
149
160
|
if (result.hint) this.log(chalk.dim(` → ${result.hint}`))
|
|
150
|
-
this.log(
|
|
151
|
-
|
|
161
|
+
this.log(
|
|
162
|
+
formatDotfilesSetup({
|
|
163
|
+
platform,
|
|
164
|
+
chezmoiInstalled: true,
|
|
165
|
+
encryptionConfigured: false,
|
|
166
|
+
sourceDir: null,
|
|
167
|
+
publicKey: null,
|
|
168
|
+
status: 'failed',
|
|
169
|
+
message: result.hint,
|
|
170
|
+
}),
|
|
171
|
+
)
|
|
172
|
+
return {
|
|
173
|
+
platform,
|
|
174
|
+
chezmoiInstalled: true,
|
|
175
|
+
encryptionConfigured: false,
|
|
176
|
+
sourceDir: null,
|
|
177
|
+
publicKey: null,
|
|
178
|
+
status: 'failed',
|
|
179
|
+
message: result.hint,
|
|
180
|
+
}
|
|
152
181
|
}
|
|
153
182
|
}
|
|
154
183
|
|
|
@@ -161,9 +190,9 @@ export default class DotfilesSetup extends Command {
|
|
|
161
190
|
// Try to get public key from key file
|
|
162
191
|
if (!publicKey) {
|
|
163
192
|
try {
|
|
164
|
-
const {
|
|
165
|
-
const {
|
|
166
|
-
const {
|
|
193
|
+
const {homedir} = await import('node:os')
|
|
194
|
+
const {join} = await import('node:path')
|
|
195
|
+
const {readFile} = await import('node:fs/promises')
|
|
167
196
|
const keyPath = join(homedir(), '.config', 'chezmoi', 'key.txt')
|
|
168
197
|
const keyContent = await readFile(keyPath, 'utf8').catch(() => '')
|
|
169
198
|
const match = keyContent.match(/# public key: (age1[a-z0-9]+)/i)
|
|
@@ -1,34 +1,31 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {Command, Flags} from '@oclif/core'
|
|
2
2
|
import ora from 'ora'
|
|
3
3
|
import chalk from 'chalk'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
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({
|
|
20
|
+
help: Flags.help({char: 'h'}),
|
|
24
21
|
}
|
|
25
22
|
|
|
26
23
|
async run() {
|
|
27
|
-
const {
|
|
24
|
+
const {flags} = await this.parse(DotfilesStatus)
|
|
28
25
|
const isJson = flags.json
|
|
29
26
|
|
|
30
27
|
const platformInfo = await detectPlatform()
|
|
31
|
-
const {
|
|
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: {
|
|
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 =
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
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(),
|