devvami 1.4.2 → 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 +129 -89
- 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 +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 +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 +16 -16
- 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
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import {Command, Flags} from '@oclif/core'
|
|
2
|
+
import ora from 'ora'
|
|
3
|
+
|
|
4
|
+
import {scanEnvironments, computeCategoryCounts} from '../../services/ai-env-scanner.js'
|
|
5
|
+
import {
|
|
6
|
+
loadAIConfig,
|
|
7
|
+
addEntry,
|
|
8
|
+
updateEntry,
|
|
9
|
+
deactivateEntry,
|
|
10
|
+
activateEntry,
|
|
11
|
+
deleteEntry,
|
|
12
|
+
} from '../../services/ai-config-store.js'
|
|
13
|
+
import {deployEntry, undeployEntry, reconcileOnScan} from '../../services/ai-env-deployer.js'
|
|
14
|
+
import {loadConfig} from '../../services/config.js'
|
|
15
|
+
import {formatEnvironmentsTable, formatCategoriesTable} from '../../formatters/ai-config.js'
|
|
16
|
+
import {startTabTUI} from '../../utils/tui/tab-tui.js'
|
|
17
|
+
import {DvmiError} from '../../utils/errors.js'
|
|
18
|
+
|
|
19
|
+
/** @import { DetectedEnvironment, CategoryEntry } from '../../types.js' */
|
|
20
|
+
|
|
21
|
+
export default class SyncConfigAi extends Command {
|
|
22
|
+
static description = 'Manage AI coding tool configurations across environments via TUI'
|
|
23
|
+
|
|
24
|
+
static examples = ['<%= config.bin %> sync-config-ai', '<%= config.bin %> sync-config-ai --json']
|
|
25
|
+
|
|
26
|
+
static enableJsonFlag = true
|
|
27
|
+
|
|
28
|
+
static flags = {
|
|
29
|
+
help: Flags.help({char: 'h'}),
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async run() {
|
|
33
|
+
const {flags} = await this.parse(SyncConfigAi)
|
|
34
|
+
const isJson = flags.json
|
|
35
|
+
|
|
36
|
+
// ── Scan environments ────────────────────────────────────────────────────
|
|
37
|
+
const spinner = isJson ? null : ora('Scanning AI coding environments…').start()
|
|
38
|
+
let detectedEnvs
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
detectedEnvs = scanEnvironments(process.cwd())
|
|
42
|
+
} catch (err) {
|
|
43
|
+
spinner?.fail('Scan failed')
|
|
44
|
+
throw new DvmiError(
|
|
45
|
+
'Failed to scan AI coding environments',
|
|
46
|
+
err instanceof Error ? err.message : 'Check filesystem permissions',
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── Load AI config store ─────────────────────────────────────────────────
|
|
51
|
+
let store
|
|
52
|
+
try {
|
|
53
|
+
store = await loadAIConfig()
|
|
54
|
+
} catch {
|
|
55
|
+
spinner?.fail('Failed to load AI config')
|
|
56
|
+
throw new DvmiError(
|
|
57
|
+
'AI config file is corrupted',
|
|
58
|
+
'Delete `~/.config/dvmi/ai-config.json` to reset, or fix the JSON manually',
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ── Reconcile: re-deploy/undeploy based on current environment detection ─
|
|
63
|
+
if (detectedEnvs.length > 0 && store.entries.length > 0) {
|
|
64
|
+
try {
|
|
65
|
+
await reconcileOnScan(store.entries, detectedEnvs, process.cwd())
|
|
66
|
+
// Reload store after reconciliation in case it mutated entries
|
|
67
|
+
store = await loadAIConfig()
|
|
68
|
+
} catch {
|
|
69
|
+
// Reconciliation errors are non-fatal — continue with current state
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ── Compute per-environment category counts ──────────────────────────────
|
|
74
|
+
for (const env of detectedEnvs) {
|
|
75
|
+
env.counts = computeCategoryCounts(env.id, store.entries)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
spinner?.stop()
|
|
79
|
+
|
|
80
|
+
// ── JSON mode ────────────────────────────────────────────────────────────
|
|
81
|
+
if (isJson) {
|
|
82
|
+
const categories = {
|
|
83
|
+
mcp: store.entries.filter((e) => e.type === 'mcp'),
|
|
84
|
+
command: store.entries.filter((e) => e.type === 'command'),
|
|
85
|
+
skill: store.entries.filter((e) => e.type === 'skill'),
|
|
86
|
+
agent: store.entries.filter((e) => e.type === 'agent'),
|
|
87
|
+
}
|
|
88
|
+
return {environments: detectedEnvs, categories}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ── Check chezmoi config ─────────────────────────────────────────────────
|
|
92
|
+
let chezmoiEnabled = false
|
|
93
|
+
try {
|
|
94
|
+
const cliConfig = await loadConfig()
|
|
95
|
+
chezmoiEnabled = cliConfig.dotfiles?.enabled === true
|
|
96
|
+
} catch {
|
|
97
|
+
// Non-fatal — chezmoi tip will show
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ── Launch TUI ───────────────────────────────────────────────────────────
|
|
101
|
+
await startTabTUI({
|
|
102
|
+
envs: detectedEnvs,
|
|
103
|
+
entries: store.entries,
|
|
104
|
+
chezmoiEnabled,
|
|
105
|
+
formatEnvs: formatEnvironmentsTable,
|
|
106
|
+
formatCats: formatCategoriesTable,
|
|
107
|
+
refreshEntries: async () => {
|
|
108
|
+
const s = await loadAIConfig()
|
|
109
|
+
return s.entries
|
|
110
|
+
},
|
|
111
|
+
onAction: async (action) => {
|
|
112
|
+
// Reload current entries for each action to avoid stale data
|
|
113
|
+
const currentStore = await loadAIConfig()
|
|
114
|
+
|
|
115
|
+
if (action.type === 'create') {
|
|
116
|
+
const created = await addEntry({
|
|
117
|
+
name: action.values.name,
|
|
118
|
+
type: action.tabKey || 'mcp',
|
|
119
|
+
environments: action.values.environments || [],
|
|
120
|
+
params: action.values,
|
|
121
|
+
})
|
|
122
|
+
await deployEntry(created, detectedEnvs, process.cwd())
|
|
123
|
+
} else if (action.type === 'edit') {
|
|
124
|
+
const updated = await updateEntry(action.id, {params: action.values})
|
|
125
|
+
await deployEntry(updated, detectedEnvs, process.cwd())
|
|
126
|
+
} else if (action.type === 'delete') {
|
|
127
|
+
await deleteEntry(action.id)
|
|
128
|
+
await undeployEntry(
|
|
129
|
+
currentStore.entries.find((e) => e.id === action.id),
|
|
130
|
+
detectedEnvs,
|
|
131
|
+
process.cwd(),
|
|
132
|
+
)
|
|
133
|
+
} else if (action.type === 'deactivate') {
|
|
134
|
+
const entry = await deactivateEntry(action.id)
|
|
135
|
+
await undeployEntry(entry, detectedEnvs, process.cwd())
|
|
136
|
+
} else if (action.type === 'activate') {
|
|
137
|
+
const entry = await activateEntry(action.id)
|
|
138
|
+
await deployEntry(entry, detectedEnvs, process.cwd())
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {Command, Flags} from '@oclif/core'
|
|
2
2
|
import chalk from 'chalk'
|
|
3
3
|
import ora from 'ora'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import {getTasks, getTasksByList, isAuthenticated} from '../../services/clickup.js'
|
|
5
|
+
import {loadConfig} from '../../services/config.js'
|
|
6
|
+
import {renderTable} from '../../formatters/table.js'
|
|
7
7
|
|
|
8
8
|
export default class TasksAssigned extends Command {
|
|
9
9
|
static description = 'Task ClickUp assegnati a te (alias di tasks list)'
|
|
@@ -19,7 +19,7 @@ export default class TasksAssigned extends Command {
|
|
|
19
19
|
static enableJsonFlag = true
|
|
20
20
|
|
|
21
21
|
static flags = {
|
|
22
|
-
status: Flags.string({
|
|
22
|
+
status: Flags.string({description: 'Filtra per status (open, in_progress, done)'}),
|
|
23
23
|
search: Flags.string({
|
|
24
24
|
char: 's',
|
|
25
25
|
description: 'Cerca nel titolo del task (case-insensitive)',
|
|
@@ -30,7 +30,7 @@ export default class TasksAssigned extends Command {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
async run() {
|
|
33
|
-
const {
|
|
33
|
+
const {flags} = await this.parse(TasksAssigned)
|
|
34
34
|
const isJson = flags.json
|
|
35
35
|
const config = await loadConfig()
|
|
36
36
|
|
|
@@ -45,7 +45,9 @@ export default class TasksAssigned extends Command {
|
|
|
45
45
|
this.error('ClickUp team ID not configured. Run `dvmi init` to configure ClickUp.')
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
const spinner = isJson
|
|
48
|
+
const spinner = isJson
|
|
49
|
+
? null
|
|
50
|
+
: ora({spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Fetching tasks...')}).start()
|
|
49
51
|
|
|
50
52
|
/** @param {number} count */
|
|
51
53
|
const onProgress = (count) => {
|
|
@@ -54,40 +56,38 @@ export default class TasksAssigned extends Command {
|
|
|
54
56
|
|
|
55
57
|
let tasks
|
|
56
58
|
if (flags['list-id']) {
|
|
57
|
-
tasks = await getTasksByList(flags['list-id'], {
|
|
59
|
+
tasks = await getTasksByList(flags['list-id'], {status: flags.status}, onProgress).catch((err) => {
|
|
58
60
|
spinner?.stop()
|
|
59
61
|
this.error(err.message)
|
|
60
62
|
})
|
|
61
63
|
} else {
|
|
62
|
-
tasks = await getTasks(/** @type {string} */ (teamId), {
|
|
64
|
+
tasks = await getTasks(/** @type {string} */ (teamId), {status: flags.status}, onProgress)
|
|
63
65
|
}
|
|
64
66
|
spinner?.stop()
|
|
65
67
|
|
|
66
68
|
// Apply search filter
|
|
67
69
|
const searchQuery = flags.search?.toLowerCase()
|
|
68
|
-
const filtered = searchQuery
|
|
69
|
-
? tasks.filter((t) => t.name.toLowerCase().includes(searchQuery))
|
|
70
|
-
: tasks
|
|
70
|
+
const filtered = searchQuery ? tasks.filter((t) => t.name.toLowerCase().includes(searchQuery)) : tasks
|
|
71
71
|
|
|
72
|
-
if (isJson) return {
|
|
72
|
+
if (isJson) return {tasks: filtered}
|
|
73
73
|
|
|
74
74
|
if (tasks.length === 0) {
|
|
75
75
|
this.log(chalk.dim('No tasks assigned to you.'))
|
|
76
|
-
return {
|
|
76
|
+
return {tasks: []}
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
if (filtered.length === 0) {
|
|
80
80
|
this.log(chalk.dim('No tasks matching filters.'))
|
|
81
|
-
return {
|
|
81
|
+
return {tasks: []}
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
// Priority label + color
|
|
85
85
|
const priorityLabel = (p) => ['', 'URGENT', 'HIGH', 'NORMAL', 'LOW'][p] ?? String(p)
|
|
86
86
|
const priorityColor = (label) => {
|
|
87
87
|
if (label === 'URGENT') return chalk.red.bold(label)
|
|
88
|
-
if (label === 'HIGH')
|
|
88
|
+
if (label === 'HIGH') return chalk.yellow(label)
|
|
89
89
|
if (label === 'NORMAL') return chalk.white(label)
|
|
90
|
-
if (label === 'LOW')
|
|
90
|
+
if (label === 'LOW') return chalk.dim(label)
|
|
91
91
|
return label
|
|
92
92
|
}
|
|
93
93
|
|
|
@@ -96,7 +96,7 @@ export default class TasksAssigned extends Command {
|
|
|
96
96
|
const s = status.toLowerCase()
|
|
97
97
|
if (s.includes('done') || s.includes('complet') || s.includes('closed')) return chalk.green(status)
|
|
98
98
|
if (s.includes('progress') || s.includes('active') || s.includes('open')) return chalk.cyan(status)
|
|
99
|
-
if (s.includes('block') || s.includes('review') || s.includes('wait'))
|
|
99
|
+
if (s.includes('block') || s.includes('review') || s.includes('wait')) return chalk.yellow(status)
|
|
100
100
|
return chalk.dim(status)
|
|
101
101
|
}
|
|
102
102
|
|
|
@@ -105,27 +105,37 @@ export default class TasksAssigned extends Command {
|
|
|
105
105
|
flags.status && chalk.dim(`status: ${chalk.white(flags.status)}`),
|
|
106
106
|
flags.search && chalk.dim(`search: ${chalk.white(`"${flags.search}"`)}`),
|
|
107
107
|
flags['list-id'] && chalk.dim(`list-id: ${chalk.white(flags['list-id'])}`),
|
|
108
|
-
]
|
|
108
|
+
]
|
|
109
|
+
.filter(Boolean)
|
|
110
|
+
.join(chalk.dim(' · '))
|
|
109
111
|
|
|
110
112
|
this.log(
|
|
111
113
|
chalk.bold('\nYour assigned tasks') +
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
114
|
+
(filterInfo ? chalk.dim(' — ') + filterInfo : '') +
|
|
115
|
+
chalk.dim(` (${filtered.length}${filtered.length < tasks.length ? `/${tasks.length}` : ''})`) +
|
|
116
|
+
'\n',
|
|
115
117
|
)
|
|
116
118
|
|
|
117
|
-
this.log(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
119
|
+
this.log(
|
|
120
|
+
renderTable(filtered, [
|
|
121
|
+
{header: 'ID', key: 'id', width: 10},
|
|
122
|
+
{header: 'Link', key: 'url', width: 42, format: (v) => v ?? '—'},
|
|
123
|
+
{
|
|
124
|
+
header: 'Priority',
|
|
125
|
+
key: 'priority',
|
|
126
|
+
width: 8,
|
|
127
|
+
format: (v) => priorityLabel(Number(v)),
|
|
128
|
+
colorize: priorityColor,
|
|
129
|
+
},
|
|
130
|
+
{header: 'Status', key: 'status', width: 15, colorize: statusColor},
|
|
131
|
+
{header: 'Due', key: 'dueDate', width: 12, format: (v) => v ?? '—'},
|
|
132
|
+
{header: 'Lista', key: 'listName', width: 20, format: (v) => v ?? '—'},
|
|
133
|
+
{header: 'Cartella', key: 'folderName', width: 20, format: (v) => v ?? '—'},
|
|
134
|
+
{header: 'Description', key: 'name', width: 55},
|
|
135
|
+
]),
|
|
136
|
+
)
|
|
127
137
|
|
|
128
138
|
this.log('')
|
|
129
|
-
return {
|
|
139
|
+
return {tasks: filtered}
|
|
130
140
|
}
|
|
131
141
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {Command, Flags} from '@oclif/core'
|
|
2
2
|
import chalk from 'chalk'
|
|
3
3
|
import ora from 'ora'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import {getTasks, getTasksByList, isAuthenticated} from '../../services/clickup.js'
|
|
5
|
+
import {loadConfig} from '../../services/config.js'
|
|
6
|
+
import {renderTable} from '../../formatters/table.js'
|
|
7
7
|
|
|
8
8
|
export default class TasksList extends Command {
|
|
9
9
|
static description = 'Task ClickUp assegnati a te'
|
|
@@ -21,7 +21,7 @@ export default class TasksList extends Command {
|
|
|
21
21
|
static enableJsonFlag = true
|
|
22
22
|
|
|
23
23
|
static flags = {
|
|
24
|
-
status: Flags.string({
|
|
24
|
+
status: Flags.string({description: 'Filtra per status (open, in_progress, done)'}),
|
|
25
25
|
search: Flags.string({
|
|
26
26
|
char: 's',
|
|
27
27
|
description: 'Cerca nel titolo del task (case-insensitive)',
|
|
@@ -32,7 +32,7 @@ export default class TasksList extends Command {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
async run() {
|
|
35
|
-
const {
|
|
35
|
+
const {flags} = await this.parse(TasksList)
|
|
36
36
|
const isJson = flags.json
|
|
37
37
|
const config = await loadConfig()
|
|
38
38
|
|
|
@@ -47,7 +47,9 @@ export default class TasksList extends Command {
|
|
|
47
47
|
this.error('ClickUp team ID not configured. Run `dvmi init` to configure ClickUp.')
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
const spinner = isJson
|
|
50
|
+
const spinner = isJson
|
|
51
|
+
? null
|
|
52
|
+
: ora({spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Fetching tasks...')}).start()
|
|
51
53
|
|
|
52
54
|
/** @param {number} count */
|
|
53
55
|
const onProgress = (count) => {
|
|
@@ -56,40 +58,38 @@ export default class TasksList extends Command {
|
|
|
56
58
|
|
|
57
59
|
let tasks
|
|
58
60
|
if (flags['list-id']) {
|
|
59
|
-
tasks = await getTasksByList(flags['list-id'], {
|
|
61
|
+
tasks = await getTasksByList(flags['list-id'], {status: flags.status}, onProgress).catch((err) => {
|
|
60
62
|
spinner?.stop()
|
|
61
63
|
this.error(err.message)
|
|
62
64
|
})
|
|
63
65
|
} else {
|
|
64
|
-
tasks = await getTasks(/** @type {string} */ (teamId), {
|
|
66
|
+
tasks = await getTasks(/** @type {string} */ (teamId), {status: flags.status}, onProgress)
|
|
65
67
|
}
|
|
66
68
|
spinner?.stop()
|
|
67
69
|
|
|
68
70
|
// Apply search filter
|
|
69
71
|
const searchQuery = flags.search?.toLowerCase()
|
|
70
|
-
const filtered = searchQuery
|
|
71
|
-
? tasks.filter((t) => t.name.toLowerCase().includes(searchQuery))
|
|
72
|
-
: tasks
|
|
72
|
+
const filtered = searchQuery ? tasks.filter((t) => t.name.toLowerCase().includes(searchQuery)) : tasks
|
|
73
73
|
|
|
74
|
-
if (isJson) return {
|
|
74
|
+
if (isJson) return {tasks: filtered}
|
|
75
75
|
|
|
76
76
|
if (tasks.length === 0) {
|
|
77
77
|
this.log(chalk.dim('No tasks assigned to you.'))
|
|
78
|
-
return {
|
|
78
|
+
return {tasks: []}
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
if (filtered.length === 0) {
|
|
82
82
|
this.log(chalk.dim(`No tasks matching filters.`))
|
|
83
|
-
return {
|
|
83
|
+
return {tasks: []}
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
// Priority label + color
|
|
87
87
|
const priorityLabel = (p) => ['', 'URGENT', 'HIGH', 'NORMAL', 'LOW'][p] ?? String(p)
|
|
88
88
|
const priorityColor = (label) => {
|
|
89
89
|
if (label === 'URGENT') return chalk.red.bold(label)
|
|
90
|
-
if (label === 'HIGH')
|
|
90
|
+
if (label === 'HIGH') return chalk.yellow(label)
|
|
91
91
|
if (label === 'NORMAL') return chalk.white(label)
|
|
92
|
-
if (label === 'LOW')
|
|
92
|
+
if (label === 'LOW') return chalk.dim(label)
|
|
93
93
|
return label
|
|
94
94
|
}
|
|
95
95
|
|
|
@@ -98,7 +98,7 @@ export default class TasksList extends Command {
|
|
|
98
98
|
const s = status.toLowerCase()
|
|
99
99
|
if (s.includes('done') || s.includes('complet') || s.includes('closed')) return chalk.green(status)
|
|
100
100
|
if (s.includes('progress') || s.includes('active') || s.includes('open')) return chalk.cyan(status)
|
|
101
|
-
if (s.includes('block') || s.includes('review') || s.includes('wait'))
|
|
101
|
+
if (s.includes('block') || s.includes('review') || s.includes('wait')) return chalk.yellow(status)
|
|
102
102
|
return chalk.dim(status)
|
|
103
103
|
}
|
|
104
104
|
|
|
@@ -107,27 +107,37 @@ export default class TasksList extends Command {
|
|
|
107
107
|
flags.status && chalk.dim(`status: ${chalk.white(flags.status)}`),
|
|
108
108
|
flags.search && chalk.dim(`search: ${chalk.white(`"${flags.search}"`)}`),
|
|
109
109
|
flags['list-id'] && chalk.dim(`list-id: ${chalk.white(flags['list-id'])}`),
|
|
110
|
-
]
|
|
110
|
+
]
|
|
111
|
+
.filter(Boolean)
|
|
112
|
+
.join(chalk.dim(' · '))
|
|
111
113
|
|
|
112
114
|
this.log(
|
|
113
115
|
chalk.bold('\nYour tasks') +
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
116
|
+
(filterInfo ? chalk.dim(' — ') + filterInfo : '') +
|
|
117
|
+
chalk.dim(` (${filtered.length}${filtered.length < tasks.length ? `/${tasks.length}` : ''})`) +
|
|
118
|
+
'\n',
|
|
117
119
|
)
|
|
118
120
|
|
|
119
|
-
this.log(
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
121
|
+
this.log(
|
|
122
|
+
renderTable(filtered, [
|
|
123
|
+
{header: 'ID', key: 'id', width: 10},
|
|
124
|
+
{header: 'Link', key: 'url', width: 42, format: (v) => v ?? '—'},
|
|
125
|
+
{
|
|
126
|
+
header: 'Priority',
|
|
127
|
+
key: 'priority',
|
|
128
|
+
width: 8,
|
|
129
|
+
format: (v) => priorityLabel(Number(v)),
|
|
130
|
+
colorize: priorityColor,
|
|
131
|
+
},
|
|
132
|
+
{header: 'Status', key: 'status', width: 15, colorize: statusColor},
|
|
133
|
+
{header: 'Due', key: 'dueDate', width: 12, format: (v) => v ?? '—'},
|
|
134
|
+
{header: 'Lista', key: 'listName', width: 20, format: (v) => v ?? '—'},
|
|
135
|
+
{header: 'Cartella', key: 'folderName', width: 20, format: (v) => v ?? '—'},
|
|
136
|
+
{header: 'Description', key: 'name', width: 55},
|
|
137
|
+
]),
|
|
138
|
+
)
|
|
129
139
|
|
|
130
140
|
this.log('')
|
|
131
|
-
return {
|
|
141
|
+
return {tasks: filtered}
|
|
132
142
|
}
|
|
133
143
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {Command} from '@oclif/core'
|
|
2
2
|
import chalk from 'chalk'
|
|
3
3
|
import ora from 'ora'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import {getTasksToday, isAuthenticated} from '../../services/clickup.js'
|
|
5
|
+
import {loadConfig} from '../../services/config.js'
|
|
6
|
+
import {renderTable} from '../../formatters/table.js'
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Return today's date as a local YYYY-MM-DD string.
|
|
@@ -15,17 +15,15 @@ function localTodayString() {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export default class TasksToday extends Command {
|
|
18
|
-
static description =
|
|
18
|
+
static description =
|
|
19
|
+
'Task in lavorazione oggi: data odierna nel range [startDate, dueDate]. Include task scaduti non conclusi.'
|
|
19
20
|
|
|
20
|
-
static examples = [
|
|
21
|
-
'<%= config.bin %> tasks today',
|
|
22
|
-
'<%= config.bin %> tasks today --json',
|
|
23
|
-
]
|
|
21
|
+
static examples = ['<%= config.bin %> tasks today', '<%= config.bin %> tasks today --json']
|
|
24
22
|
|
|
25
23
|
static enableJsonFlag = true
|
|
26
24
|
|
|
27
25
|
async run() {
|
|
28
|
-
const {
|
|
26
|
+
const {flags} = await this.parse(TasksToday)
|
|
29
27
|
const isJson = flags.json
|
|
30
28
|
const config = await loadConfig()
|
|
31
29
|
|
|
@@ -36,38 +34,42 @@ export default class TasksToday extends Command {
|
|
|
36
34
|
const teamId = config.clickup?.teamId
|
|
37
35
|
if (!teamId) this.error('ClickUp team ID not configured. Run `dvmi init` to configure ClickUp.')
|
|
38
36
|
|
|
39
|
-
const spinner = isJson
|
|
37
|
+
const spinner = isJson
|
|
38
|
+
? null
|
|
39
|
+
: ora({spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')("Fetching today's tasks...")}).start()
|
|
40
40
|
const tasks = await getTasksToday(teamId)
|
|
41
41
|
spinner?.stop()
|
|
42
42
|
|
|
43
|
-
if (isJson) return {
|
|
43
|
+
if (isJson) return {tasks}
|
|
44
44
|
|
|
45
45
|
if (tasks.length === 0) {
|
|
46
46
|
this.log(chalk.dim('No tasks for today.'))
|
|
47
47
|
this.log(chalk.dim('Check `dvmi tasks list` for all assigned tasks.'))
|
|
48
|
-
return {
|
|
48
|
+
return {tasks: []}
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
const today = localTodayString()
|
|
52
52
|
|
|
53
|
-
this.log(chalk.bold(
|
|
54
|
-
this.log(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
53
|
+
this.log(chalk.bold("\nToday's tasks:\n"))
|
|
54
|
+
this.log(
|
|
55
|
+
renderTable(tasks, [
|
|
56
|
+
{header: 'Title', key: 'name', width: 45},
|
|
57
|
+
{header: 'Status', key: 'status', width: 15},
|
|
58
|
+
{
|
|
59
|
+
header: 'Due',
|
|
60
|
+
key: 'dueDate',
|
|
61
|
+
width: 12,
|
|
62
|
+
format: (v) => v ?? '—',
|
|
63
|
+
colorize: (v) => {
|
|
64
|
+
if (!v) return chalk.dim('—')
|
|
65
|
+
if (v < today) return chalk.red.bold(v)
|
|
66
|
+
return v
|
|
67
|
+
},
|
|
66
68
|
},
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
{header: 'Link', key: 'url', format: (v) => v ?? '—'},
|
|
70
|
+
]),
|
|
71
|
+
)
|
|
70
72
|
|
|
71
|
-
return {
|
|
73
|
+
return {tasks}
|
|
72
74
|
}
|
|
73
75
|
}
|
package/src/commands/upgrade.js
CHANGED
|
@@ -1,25 +1,24 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {Command} from '@oclif/core'
|
|
2
2
|
import chalk from 'chalk'
|
|
3
3
|
import ora from 'ora'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import {checkForUpdate} from '../services/version-check.js'
|
|
5
|
+
import {exec} from '../services/shell.js'
|
|
6
6
|
|
|
7
7
|
export default class Upgrade extends Command {
|
|
8
|
-
static description =
|
|
8
|
+
static description = "Aggiorna la CLI all'ultima versione disponibile"
|
|
9
9
|
|
|
10
|
-
static examples = [
|
|
11
|
-
'<%= config.bin %> upgrade',
|
|
12
|
-
'<%= config.bin %> upgrade --json',
|
|
13
|
-
]
|
|
10
|
+
static examples = ['<%= config.bin %> upgrade', '<%= config.bin %> upgrade --json']
|
|
14
11
|
|
|
15
12
|
static enableJsonFlag = true
|
|
16
13
|
|
|
17
14
|
async run() {
|
|
18
|
-
const {
|
|
15
|
+
const {flags} = await this.parse(Upgrade)
|
|
19
16
|
const isJson = flags.json
|
|
20
17
|
|
|
21
|
-
const spinner = isJson
|
|
22
|
-
|
|
18
|
+
const spinner = isJson
|
|
19
|
+
? null
|
|
20
|
+
: ora({spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Checking for updates...')}).start()
|
|
21
|
+
const {hasUpdate, current, latest} = await checkForUpdate({force: true})
|
|
23
22
|
spinner?.stop()
|
|
24
23
|
|
|
25
24
|
// Guard against malformed version strings from the GitHub Releases API
|
|
@@ -29,7 +28,7 @@ export default class Upgrade extends Command {
|
|
|
29
28
|
|
|
30
29
|
if (!hasUpdate) {
|
|
31
30
|
const msg = `You're already on the latest version (${current})`
|
|
32
|
-
if (isJson) return {
|
|
31
|
+
if (isJson) return {currentVersion: current, latestVersion: latest, updated: false}
|
|
33
32
|
this.log(chalk.green('✓') + ' ' + msg)
|
|
34
33
|
return
|
|
35
34
|
}
|
|
@@ -38,17 +37,19 @@ export default class Upgrade extends Command {
|
|
|
38
37
|
this.log(`Updating from ${chalk.yellow(current)} to ${chalk.green(latest)}`)
|
|
39
38
|
}
|
|
40
39
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
const updateSpinner = isJson
|
|
41
|
+
? null
|
|
42
|
+
: ora({spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Installing update...')}).start()
|
|
43
|
+
// Non passare --registry globale: verrebbe usato anche per le dipendenze.
|
|
44
|
+
// ~/.npmrc ha già devvami:registry per il solo scope corretto.
|
|
45
|
+
const result = await exec('npm', ['install', '-g', `devvami@${latest}`])
|
|
45
46
|
if (result.exitCode !== 0) {
|
|
46
47
|
updateSpinner?.fail('Update failed')
|
|
47
48
|
this.error(`Update failed: ${result.stderr}`)
|
|
48
49
|
}
|
|
49
50
|
updateSpinner?.succeed(`Updated to ${latest}`)
|
|
50
51
|
|
|
51
|
-
const response = {
|
|
52
|
+
const response = {currentVersion: current, latestVersion: latest, updated: true}
|
|
52
53
|
if (isJson) return response
|
|
53
54
|
|
|
54
55
|
this.log(chalk.green('✓') + ` Successfully updated to ${latest}`)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {Command, Args, Flags} from '@oclif/core'
|
|
2
2
|
import ora from 'ora'
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
3
|
+
import {getCveDetail} from '../../services/nvd.js'
|
|
4
|
+
import {formatCveDetail} from '../../formatters/vuln.js'
|
|
5
|
+
import {openBrowser} from '../../utils/open-browser.js'
|
|
6
|
+
import {ValidationError} from '../../utils/errors.js'
|
|
7
7
|
|
|
8
8
|
export default class VulnDetail extends Command {
|
|
9
9
|
static description = 'View full details for a specific CVE'
|
|
@@ -17,7 +17,7 @@ export default class VulnDetail extends Command {
|
|
|
17
17
|
static enableJsonFlag = true
|
|
18
18
|
|
|
19
19
|
static args = {
|
|
20
|
-
cveId: Args.string({
|
|
20
|
+
cveId: Args.string({description: 'CVE identifier (e.g. CVE-2021-44228)', required: true}),
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
static flags = {
|
|
@@ -29,9 +29,9 @@ export default class VulnDetail extends Command {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
async run() {
|
|
32
|
-
const {
|
|
32
|
+
const {args, flags} = await this.parse(VulnDetail)
|
|
33
33
|
const isJson = flags.json
|
|
34
|
-
const {
|
|
34
|
+
const {cveId} = args
|
|
35
35
|
|
|
36
36
|
if (!cveId || !/^CVE-\d{4}-\d{4,}$/i.test(cveId)) {
|
|
37
37
|
throw new ValidationError(
|