devvami 1.1.2 → 1.3.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 +36 -1
- package/oclif.manifest.json +233 -7
- package/package.json +2 -1
- package/src/commands/costs/get.js +112 -18
- package/src/commands/costs/trend.js +165 -0
- package/src/commands/init.js +8 -3
- package/src/commands/logs/index.js +190 -0
- package/src/commands/prompts/run.js +19 -1
- package/src/commands/security/setup.js +249 -0
- package/src/commands/welcome.js +17 -0
- package/src/formatters/charts.js +205 -0
- package/src/formatters/cost.js +18 -5
- package/src/formatters/security.js +119 -0
- package/src/help.js +44 -24
- package/src/services/aws-costs.js +130 -6
- package/src/services/clickup.js +9 -3
- package/src/services/cloudwatch-logs.js +92 -0
- package/src/services/config.js +17 -1
- package/src/services/docs.js +5 -1
- package/src/services/prompts.js +2 -2
- package/src/services/security.js +634 -0
- package/src/types.js +132 -4
- package/src/utils/aws-vault.js +144 -0
- package/src/utils/welcome.js +173 -0
package/src/types.js
CHANGED
|
@@ -189,10 +189,72 @@
|
|
|
189
189
|
|
|
190
190
|
/**
|
|
191
191
|
* @typedef {Object} AWSCostEntry
|
|
192
|
-
* @property {string} serviceName
|
|
193
|
-
* @property {
|
|
194
|
-
* @property {
|
|
195
|
-
* @property {
|
|
192
|
+
* @property {string} serviceName - AWS service name (e.g. "Amazon EC2"), or tag value label for tag grouping
|
|
193
|
+
* @property {string} [tagValue] - Tag value when grouping by TAG or BOTH (e.g. "prod"); undefined for service-only grouping
|
|
194
|
+
* @property {number} amount - Cost amount (USD)
|
|
195
|
+
* @property {string} unit - Currency unit (always "USD")
|
|
196
|
+
* @property {{ start: string, end: string }} period - ISO date range (YYYY-MM-DD)
|
|
197
|
+
*/
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* @typedef {'service' | 'tag' | 'both'} CostGroupMode
|
|
201
|
+
* The dimension used to group cost entries.
|
|
202
|
+
* - 'service': group by AWS service name (default, backward-compatible)
|
|
203
|
+
* - 'tag': group by a tag key's values (requires tagKey)
|
|
204
|
+
* - 'both': group by AWS service + tag value simultaneously
|
|
205
|
+
*/
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* @typedef {Object} CostTrendPoint
|
|
209
|
+
* @property {string} date - ISO date (YYYY-MM-DD) for this data point
|
|
210
|
+
* @property {number} amount - Total cost for this day (USD)
|
|
211
|
+
* @property {string} [label] - Display label: serviceName for service/both grouping,
|
|
212
|
+
* tag value for tag grouping; omitted when not multi-series
|
|
213
|
+
*/
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* @typedef {Object} CostTrendSeries
|
|
217
|
+
* @property {string} name - Series label (service name or tag value)
|
|
218
|
+
* @property {CostTrendPoint[]} points - Ordered daily data points (ascending by date)
|
|
219
|
+
*/
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* @typedef {Object} LogGroup
|
|
223
|
+
* @property {string} name - Full log group name (e.g. "/aws/lambda/my-fn")
|
|
224
|
+
* @property {number} [storedBytes] - Total stored bytes (may be absent for empty groups)
|
|
225
|
+
* @property {number} [retentionDays] - Retention policy in days; undefined = never expire
|
|
226
|
+
* @property {string} [creationTime] - ISO8601 creation timestamp
|
|
227
|
+
*/
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* @typedef {Object} LogEvent
|
|
231
|
+
* @property {string} eventId - Unique event ID assigned by CloudWatch
|
|
232
|
+
* @property {string} logStreamName - Stream within the log group (e.g. "2026/03/26/[$LATEST]abc")
|
|
233
|
+
* @property {number} timestamp - Event time as epoch milliseconds
|
|
234
|
+
* @property {string} message - Raw log message text
|
|
235
|
+
*/
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* @typedef {Object} LogFilterResult
|
|
239
|
+
* @property {LogEvent[]} events - Matched log events (up to --limit)
|
|
240
|
+
* @property {boolean} truncated - True when the result was capped by --limit or AWS pagination
|
|
241
|
+
* @property {string} logGroupName - The log group that was queried
|
|
242
|
+
* @property {number} startTime - Query start as epoch milliseconds
|
|
243
|
+
* @property {number} endTime - Query end as epoch milliseconds
|
|
244
|
+
* @property {string} filterPattern - The pattern used ('' = no filter)
|
|
245
|
+
*/
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* @typedef {Object} ChartBarData
|
|
249
|
+
* @property {string} name - Row label (service name, tag value, or "service / tag")
|
|
250
|
+
* @property {number} value - Cost amount (USD)
|
|
251
|
+
*/
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* @typedef {Object} ChartSeries
|
|
255
|
+
* @property {string} name - Series label displayed in legend
|
|
256
|
+
* @property {number[]} values - Ordered numeric values (one per day, ~60 for 2 months)
|
|
257
|
+
* @property {string[]} labels - Date labels matching values array (YYYY-MM-DD)
|
|
196
258
|
*/
|
|
197
259
|
|
|
198
260
|
/**
|
|
@@ -260,3 +322,69 @@
|
|
|
260
322
|
* @property {string} owner - GitHub owner (org or user)
|
|
261
323
|
* @property {string} repo - Repository name
|
|
262
324
|
*/
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* @typedef {Object} SecurityTool
|
|
328
|
+
* @property {string} id - Unique identifier (e.g., "aws-vault", "pass", "gpg", "gcm", "osxkeychain")
|
|
329
|
+
* @property {string} displayName - Human-readable name for UI output
|
|
330
|
+
* @property {'aws'|'git'|'dependency'} role - What the tool protects; 'dependency' = required by another tool
|
|
331
|
+
* @property {'not-installed'|'installed'|'misconfigured'|'skipped'|'n/a'} status - Status after check phase
|
|
332
|
+
* @property {Platform[]} platforms - Platforms where this tool applies
|
|
333
|
+
* @property {string|null} version - Detected version string, if available
|
|
334
|
+
* @property {string|null} hint - Actionable message when status is 'misconfigured' or 'not-installed'
|
|
335
|
+
*/
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* @typedef {Object} SetupStep
|
|
339
|
+
* @property {string} id - Unique step identifier (e.g., "install-aws-vault", "init-pass")
|
|
340
|
+
* @property {string} label - Human-readable description shown to the developer
|
|
341
|
+
* @property {string} toolId - The SecurityTool this step belongs to
|
|
342
|
+
* @property {'check'|'install'|'configure'|'verify'} type - Step category
|
|
343
|
+
* @property {() => Promise<StepResult>} run - Async function that executes the step
|
|
344
|
+
* @property {boolean} requiresConfirmation - True for 'install' and 'configure' steps
|
|
345
|
+
* @property {boolean} [skippable] - True if the developer can skip this step without breaking subsequent steps
|
|
346
|
+
* @property {boolean} [gpgInteractive] - True if the step spawns GPG interactively (requires stdio:inherit)
|
|
347
|
+
*/
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* @typedef {Object} StepResult
|
|
351
|
+
* @property {'success'|'skipped'|'failed'} status
|
|
352
|
+
* @property {string} [message] - Human-readable outcome message
|
|
353
|
+
* @property {string} [hint] - Actionable recovery suggestion shown only when status is 'failed'
|
|
354
|
+
* @property {string} [hintUrl] - Documentation URL to include with the hint
|
|
355
|
+
*/
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* @typedef {Object} SetupSession
|
|
359
|
+
* @property {Platform} platform - Detected platform for this run
|
|
360
|
+
* @property {'aws'|'git'|'both'} selection - What the developer chose to set up
|
|
361
|
+
* @property {SetupStep[]} steps - Ordered list of steps for this session
|
|
362
|
+
* @property {Map<string, StepResult>} results - Map of stepId → StepResult
|
|
363
|
+
* @property {'in-progress'|'completed'|'failed'|'cancelled'} overallStatus - Aggregate status
|
|
364
|
+
*/
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* @typedef {Object} GpgKey
|
|
368
|
+
* @property {string} id - Long key ID (16-character hex)
|
|
369
|
+
* @property {string} fingerprint - Full 40-character fingerprint
|
|
370
|
+
* @property {string} name - Associated name from the key's UID
|
|
371
|
+
* @property {string} email - Associated email from the key's UID
|
|
372
|
+
* @property {string|null} expiry - Expiry date as ISO8601 string, or null if no expiry
|
|
373
|
+
*/
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* @typedef {Object} SecurityToolStatus
|
|
377
|
+
* @property {string} id - Tool id
|
|
378
|
+
* @property {string} displayName - Human-readable name
|
|
379
|
+
* @property {'not-installed'|'installed'|'misconfigured'|'n/a'} status - Current status
|
|
380
|
+
* @property {string|null} version - Detected version, if any
|
|
381
|
+
* @property {string|null} hint - Recovery hint if misconfigured
|
|
382
|
+
*/
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* @typedef {Object} SecuritySetupJsonResult
|
|
386
|
+
* @property {Platform} platform - Detected platform
|
|
387
|
+
* @property {'aws'|'git'|'both'|null} selection - Selection made (null for --json health check)
|
|
388
|
+
* @property {SecurityToolStatus[]} tools - Status of each applicable tool
|
|
389
|
+
* @property {'success'|'partial'|'not-configured'} overallStatus - Aggregate status
|
|
390
|
+
*/
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { loadConfigSync } from '../services/config.js'
|
|
2
|
+
import { execa } from 'execa'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns the aws-vault exec prefix to prepend to AWS CLI commands.
|
|
6
|
+
*
|
|
7
|
+
* Detection order:
|
|
8
|
+
* 1. process.env.AWS_VAULT — set by `aws-vault exec` at runtime in the child process
|
|
9
|
+
* 2. config.awsProfile — if an already-loaded config object is passed (avoids sync I/O)
|
|
10
|
+
* 3. loadConfigSync() — synchronous fallback for static getters where async is unavailable
|
|
11
|
+
*
|
|
12
|
+
* @param {{ awsProfile?: string } | null} [config] - Already-loaded config (optional).
|
|
13
|
+
* Pass this when the caller has already loaded config asynchronously to avoid a redundant sync read.
|
|
14
|
+
* @returns {string} e.g. `"aws-vault exec myprofile -- "` or `""`
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* // Inside an async run() method where config is already loaded:
|
|
18
|
+
* const prefix = awsVaultPrefix(config)
|
|
19
|
+
* this.error(`No credentials. Use: ${prefix}dvmi costs get`)
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* // Inside a static getter (no async available):
|
|
23
|
+
* static get examples() {
|
|
24
|
+
* const prefix = awsVaultPrefix()
|
|
25
|
+
* return [`${prefix}<%= config.bin %> costs get my-service`]
|
|
26
|
+
* }
|
|
27
|
+
*/
|
|
28
|
+
export function awsVaultPrefix(config = null) {
|
|
29
|
+
// 1. Runtime env var — set by aws-vault exec in the subprocess environment
|
|
30
|
+
if (process.env.AWS_VAULT) return `aws-vault exec ${process.env.AWS_VAULT} -- `
|
|
31
|
+
|
|
32
|
+
// 2. Already-loaded config passed by the caller
|
|
33
|
+
if (config?.awsProfile) return `aws-vault exec ${config.awsProfile} -- `
|
|
34
|
+
|
|
35
|
+
// 3. Synchronous config read — fallback for static getters
|
|
36
|
+
const synced = loadConfigSync()
|
|
37
|
+
if (synced.awsProfile) return `aws-vault exec ${synced.awsProfile} -- `
|
|
38
|
+
|
|
39
|
+
return ''
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Returns true when the current process already has AWS credentials in env.
|
|
44
|
+
* @returns {boolean}
|
|
45
|
+
*/
|
|
46
|
+
export function hasAwsCredentialEnv() {
|
|
47
|
+
return Boolean(
|
|
48
|
+
process.env.AWS_ACCESS_KEY_ID ||
|
|
49
|
+
process.env.AWS_SESSION_TOKEN,
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Returns true when this process is already running inside aws-vault exec.
|
|
55
|
+
* @returns {boolean}
|
|
56
|
+
*/
|
|
57
|
+
export function isAwsVaultSession() {
|
|
58
|
+
return Boolean(process.env.AWS_VAULT)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Re-execute the current dvmi command under aws-vault and mirror stdio.
|
|
63
|
+
* Returns null when re-exec should not run.
|
|
64
|
+
*
|
|
65
|
+
* Guard conditions:
|
|
66
|
+
* - awsProfile must be configured
|
|
67
|
+
* - command must not already be inside aws-vault
|
|
68
|
+
* - process must not already have AWS credentials in env
|
|
69
|
+
* - re-exec must not have already happened in this process chain
|
|
70
|
+
*
|
|
71
|
+
* @param {{ awsProfile?: string } | null} [config]
|
|
72
|
+
* @returns {Promise<number | null>} child exit code or null when skipped
|
|
73
|
+
*/
|
|
74
|
+
export async function reexecCurrentCommandWithAwsVault(config = null) {
|
|
75
|
+
const profile = config?.awsProfile ?? loadConfigSync().awsProfile
|
|
76
|
+
if (!profile) return null
|
|
77
|
+
if (isAwsVaultSession()) return null
|
|
78
|
+
if (hasAwsCredentialEnv()) return null
|
|
79
|
+
if (process.env.DVMI_AWS_VAULT_REEXEC === '1') return null
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
const child = await execa(
|
|
83
|
+
'aws-vault',
|
|
84
|
+
[
|
|
85
|
+
'exec',
|
|
86
|
+
profile,
|
|
87
|
+
'--',
|
|
88
|
+
process.execPath,
|
|
89
|
+
...process.argv.slice(1),
|
|
90
|
+
],
|
|
91
|
+
{
|
|
92
|
+
reject: false,
|
|
93
|
+
stdio: 'inherit',
|
|
94
|
+
env: {
|
|
95
|
+
...process.env,
|
|
96
|
+
DVMI_AWS_VAULT_REEXEC: '1',
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
return child.exitCode ?? 1
|
|
102
|
+
} catch {
|
|
103
|
+
// aws-vault missing or failed to spawn; fallback to normal execution path
|
|
104
|
+
return null
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Re-execute the current dvmi command under aws-vault using an explicit profile.
|
|
110
|
+
* This bypasses auto-detection guards and is intended for interactive recovery flows.
|
|
111
|
+
*
|
|
112
|
+
* @param {string} profile
|
|
113
|
+
* @param {Record<string, string>} [extraEnv]
|
|
114
|
+
* @returns {Promise<number | null>} child exit code or null when skipped/failed to spawn
|
|
115
|
+
*/
|
|
116
|
+
export async function reexecCurrentCommandWithAwsVaultProfile(profile, extraEnv = {}) {
|
|
117
|
+
if (!profile) return null
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
const child = await execa(
|
|
121
|
+
'aws-vault',
|
|
122
|
+
[
|
|
123
|
+
'exec',
|
|
124
|
+
profile,
|
|
125
|
+
'--',
|
|
126
|
+
process.execPath,
|
|
127
|
+
...process.argv.slice(1),
|
|
128
|
+
],
|
|
129
|
+
{
|
|
130
|
+
reject: false,
|
|
131
|
+
stdio: 'inherit',
|
|
132
|
+
env: {
|
|
133
|
+
...process.env,
|
|
134
|
+
DVMI_AWS_VAULT_REEXEC: '1',
|
|
135
|
+
...extraEnv,
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
return child.exitCode ?? 1
|
|
141
|
+
} catch {
|
|
142
|
+
return null
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import { printBanner } from './banner.js'
|
|
3
|
+
import { isColorEnabled } from './gradient.js'
|
|
4
|
+
import { typewriterLine } from './typewriter.js'
|
|
5
|
+
|
|
6
|
+
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
const STAGGER = 150
|
|
9
|
+
const delay = (ms) => new Promise((r) => setTimeout(r, ms))
|
|
10
|
+
const out = (line) => process.stdout.write(line + '\n')
|
|
11
|
+
const nl = () => process.stdout.write('\n')
|
|
12
|
+
|
|
13
|
+
// ─── Color palette ────────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
const p = isColorEnabled
|
|
16
|
+
? {
|
|
17
|
+
sep: (t) => chalk.hex('#4A9EFF').dim(t),
|
|
18
|
+
cyan: (t) => chalk.hex('#00D4FF').bold(t),
|
|
19
|
+
green: (t) => chalk.hex('#00FF88').bold(t),
|
|
20
|
+
pink: (t) => chalk.hex('#FF3399').bold(t),
|
|
21
|
+
gold: (t) => chalk.hex('#FFD700').bold(t),
|
|
22
|
+
orange: (t) => chalk.hex('#FF6B2B').bold(t),
|
|
23
|
+
blue: (t) => chalk.hex('#4A9EFF')(t),
|
|
24
|
+
white: (t) => chalk.white(t),
|
|
25
|
+
dim: (t) => chalk.dim(t),
|
|
26
|
+
}
|
|
27
|
+
: Object.fromEntries(
|
|
28
|
+
['sep', 'cyan', 'green', 'pink', 'gold', 'orange', 'blue', 'white', 'dim'].map((k) => [
|
|
29
|
+
k,
|
|
30
|
+
(t) => t,
|
|
31
|
+
]),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Build a ruler-style section header.
|
|
38
|
+
* Example: " 🔐 SUPPLY CHAIN SECURITY ──────────────────────────────"
|
|
39
|
+
* No right-side border: dashes trail right, no alignment required.
|
|
40
|
+
*
|
|
41
|
+
* @param {string} icon
|
|
42
|
+
* @param {string} label
|
|
43
|
+
* @param {(t: string) => string} colorFn
|
|
44
|
+
* @returns {string}
|
|
45
|
+
*/
|
|
46
|
+
function ruler(icon, label, colorFn) {
|
|
47
|
+
return colorFn(` ${icon} ${label} `) + p.sep('─'.repeat(40))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ─── Welcome screen ───────────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Print the full cyberpunk dvmi welcome screen.
|
|
54
|
+
* Shows the animated DVMI logo followed by a styled mission dashboard.
|
|
55
|
+
* Falls back to plain text in non-TTY / NO_COLOR / CI environments.
|
|
56
|
+
*
|
|
57
|
+
* @param {string} [version=''] - CLI version string (e.g. '2.1.0')
|
|
58
|
+
* @returns {Promise<void>}
|
|
59
|
+
*/
|
|
60
|
+
export async function printWelcomeScreen(version = '') {
|
|
61
|
+
// ── 1. Animated DVMI logo ──────────────────────────────────────────────────
|
|
62
|
+
await printBanner()
|
|
63
|
+
|
|
64
|
+
// ── 2. Badge line (typewriter with brand gradient) ─────────────────────────
|
|
65
|
+
const versionTag = version ? `v${version} · ` : ''
|
|
66
|
+
await typewriterLine(` ◆ Developer Mission Interface · ${versionTag}Node >= 24`)
|
|
67
|
+
|
|
68
|
+
// ── 3. Connection established ──────────────────────────────────────────────
|
|
69
|
+
await delay(STAGGER)
|
|
70
|
+
nl()
|
|
71
|
+
out(p.sep(' ' + '─'.repeat(72)))
|
|
72
|
+
nl()
|
|
73
|
+
out(p.cyan(' LINK ESTABLISHED'))
|
|
74
|
+
nl()
|
|
75
|
+
out(p.white(' dvmi consolidates the operational surface of modern software delivery into'))
|
|
76
|
+
out(p.white(' one deterministic CLI experience. Instead of context switching across browser'))
|
|
77
|
+
out(p.white(' tabs, dashboards, and disconnected tools, you run critical workflows from a'))
|
|
78
|
+
out(p.white(' single terminal interface: consistent output, predictable behavior, full control.'))
|
|
79
|
+
|
|
80
|
+
// ── 4. Mission profile ─────────────────────────────────────────────────────
|
|
81
|
+
await delay(STAGGER)
|
|
82
|
+
nl()
|
|
83
|
+
out(ruler('⚙️ ', 'MISSION PROFILE :: WHAT THIS CLI DOES', p.cyan))
|
|
84
|
+
nl()
|
|
85
|
+
const mission = [
|
|
86
|
+
'discover and manage repositories across your GitHub organization',
|
|
87
|
+
'handle pull requests with faster review and decision flow',
|
|
88
|
+
'monitor CI/CD pipelines, inspect failures, rerun with intent',
|
|
89
|
+
'query and read technical documentation without leaving the shell',
|
|
90
|
+
'track execution priorities through task-oriented commands',
|
|
91
|
+
'inspect cloud costs early, before budget drift becomes an incident',
|
|
92
|
+
]
|
|
93
|
+
for (const line of mission) {
|
|
94
|
+
out(p.dim(' - ') + p.white(line))
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ── 5. Supply chain security ───────────────────────────────────────────────
|
|
98
|
+
await delay(STAGGER)
|
|
99
|
+
nl()
|
|
100
|
+
out(ruler('🔐', "SUPPLY CHAIN SECURITY :: VERIFY, DON'T GUESS", p.green))
|
|
101
|
+
nl()
|
|
102
|
+
out(p.dim(' dvmi takes a security-first but pragmatic approach to software delivery:'))
|
|
103
|
+
nl()
|
|
104
|
+
const security = [
|
|
105
|
+
'artifact integrity and provenance-aware delivery workflow',
|
|
106
|
+
'dependency visibility with an SBOM mindset (SPDX / CycloneDX)',
|
|
107
|
+
'continuous hygiene on dependency risk and secret exposure',
|
|
108
|
+
'credential management via OS-native secure storage (keychain)',
|
|
109
|
+
'less improvised shell procedures, more repeatable safe operations',
|
|
110
|
+
]
|
|
111
|
+
for (const line of security) {
|
|
112
|
+
out(p.green(' ▸ ') + p.white(line))
|
|
113
|
+
}
|
|
114
|
+
nl()
|
|
115
|
+
out(p.dim(' Objective: reduce the risk surface without slowing down delivery.'))
|
|
116
|
+
|
|
117
|
+
// ── 6. DevEx high-velocity ─────────────────────────────────────────────────
|
|
118
|
+
await delay(STAGGER)
|
|
119
|
+
nl()
|
|
120
|
+
out(ruler('⚡', 'DEVEX HIGH-VELOCITY :: STAY IN FLOW', p.pink))
|
|
121
|
+
nl()
|
|
122
|
+
out(p.dim(' dvmi is designed to lower cognitive cost and keep you in flow:'))
|
|
123
|
+
nl()
|
|
124
|
+
const devex = [
|
|
125
|
+
'less context switching between tools and dashboards',
|
|
126
|
+
'less time spent hunting down "where did this break"',
|
|
127
|
+
'faster "what is blocked / what is next" decision loops',
|
|
128
|
+
'scriptable, composable output for automation and team workflows',
|
|
129
|
+
]
|
|
130
|
+
for (const line of devex) {
|
|
131
|
+
out(p.pink(' ▸ ') + p.white(line))
|
|
132
|
+
}
|
|
133
|
+
nl()
|
|
134
|
+
out(p.dim(' No noise added. Operational signal only.'))
|
|
135
|
+
|
|
136
|
+
// ── 7. Delivery reliability ────────────────────────────────────────────────
|
|
137
|
+
await delay(STAGGER)
|
|
138
|
+
nl()
|
|
139
|
+
out(ruler('📡', 'DELIVERY RELIABILITY :: SHIP WITH CONTROL', p.orange))
|
|
140
|
+
nl()
|
|
141
|
+
out(p.white(' From PR readiness to pipeline health to release confidence,'))
|
|
142
|
+
out(p.white(' dvmi moves teams from reactive debugging to proactive control.'))
|
|
143
|
+
nl()
|
|
144
|
+
out(p.white(' Reliability is treated as a habit, not a phase.'))
|
|
145
|
+
|
|
146
|
+
// ── 8. Boot sequence ───────────────────────────────────────────────────────
|
|
147
|
+
await delay(STAGGER)
|
|
148
|
+
nl()
|
|
149
|
+
out(ruler('🚀', 'BOOT SEQUENCE', p.gold))
|
|
150
|
+
nl()
|
|
151
|
+
|
|
152
|
+
/** @type {Array<[string, string]>} */
|
|
153
|
+
const commands = [
|
|
154
|
+
['dvmi init', 'configure your workspace'],
|
|
155
|
+
['dvmi auth login', 'connect GitHub & ClickUp'],
|
|
156
|
+
['dvmi pr status', 'open pull requests'],
|
|
157
|
+
['dvmi pipeline status', 'CI/CD health check'],
|
|
158
|
+
['dvmi tasks today', 'focus mode: what to ship today'],
|
|
159
|
+
['dvmi costs get', 'AWS bill reality check'],
|
|
160
|
+
['dvmi doctor', 'diagnose config issues'],
|
|
161
|
+
]
|
|
162
|
+
for (const [cmd, comment] of commands) {
|
|
163
|
+
out(' ' + p.blue('$ ' + cmd.padEnd(24)) + p.dim('# ' + comment))
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ── 9. Closing ─────────────────────────────────────────────────────────────
|
|
167
|
+
await delay(STAGGER)
|
|
168
|
+
nl()
|
|
169
|
+
out(p.sep(' ' + '─'.repeat(72)))
|
|
170
|
+
nl()
|
|
171
|
+
out(p.dim(' DVMI PROTOCOL: ') + p.cyan('Ship fast. Verify everything.'))
|
|
172
|
+
nl()
|
|
173
|
+
}
|