devvami 1.0.0 → 1.1.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/oclif.manifest.json +265 -1
- package/package.json +4 -1
- package/src/commands/init.js +30 -1
- package/src/commands/prompts/browse.js +164 -0
- package/src/commands/prompts/download.js +154 -0
- package/src/commands/prompts/install-speckit.js +97 -0
- package/src/commands/prompts/list.js +107 -0
- package/src/commands/prompts/run.js +189 -0
- package/src/formatters/prompts.js +159 -0
- package/src/help.js +23 -8
- package/src/services/awesome-copilot.js +123 -0
- package/src/services/github.js +2 -1
- package/src/services/prompts.js +307 -0
- package/src/services/skills-sh.js +81 -0
- package/src/services/speckit.js +73 -0
- package/src/types.js +34 -0
- package/src/utils/frontmatter.js +52 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core'
|
|
2
|
+
import ora from 'ora'
|
|
3
|
+
import chalk from 'chalk'
|
|
4
|
+
import { loadConfig } from '../../services/config.js'
|
|
5
|
+
import { isUvInstalled, isSpecifyInstalled, installSpecifyCli, runSpecifyInit } from '../../services/speckit.js'
|
|
6
|
+
import { DvmiError } from '../../utils/errors.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Map from dvmi's `aiTool` config values to spec-kit's `--ai` flag values.
|
|
10
|
+
* @type {Record<string, string>}
|
|
11
|
+
*/
|
|
12
|
+
const AI_TOOL_MAP = {
|
|
13
|
+
opencode: 'opencode',
|
|
14
|
+
copilot: 'copilot',
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default class PromptsInstallSpeckit extends Command {
|
|
18
|
+
static description = 'Install spec-kit (specify-cli) and run `specify init` to set up Spec-Driven Development'
|
|
19
|
+
|
|
20
|
+
static examples = [
|
|
21
|
+
'<%= config.bin %> prompts install-speckit',
|
|
22
|
+
'<%= config.bin %> prompts install-speckit --ai opencode',
|
|
23
|
+
'<%= config.bin %> prompts install-speckit --force',
|
|
24
|
+
'<%= config.bin %> prompts install-speckit --reinstall',
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
static flags = {
|
|
28
|
+
ai: Flags.string({
|
|
29
|
+
description:
|
|
30
|
+
'AI agent to pass to `specify init --ai` (defaults to the aiTool set in `dvmi init`)',
|
|
31
|
+
options: ['opencode', 'copilot', 'claude', 'gemini', 'cursor-agent', 'codex', 'windsurf', 'kiro-cli', 'amp'],
|
|
32
|
+
}),
|
|
33
|
+
force: Flags.boolean({
|
|
34
|
+
char: 'f',
|
|
35
|
+
description: 'Pass --force to `specify init` (safe to run in a non-empty directory)',
|
|
36
|
+
default: false,
|
|
37
|
+
}),
|
|
38
|
+
reinstall: Flags.boolean({
|
|
39
|
+
description: 'Reinstall specify-cli even if it is already installed',
|
|
40
|
+
default: false,
|
|
41
|
+
}),
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async run() {
|
|
45
|
+
const { flags } = await this.parse(PromptsInstallSpeckit)
|
|
46
|
+
|
|
47
|
+
// ── 1. Require uv ────────────────────────────────────────────────────────
|
|
48
|
+
if (!(await isUvInstalled())) {
|
|
49
|
+
this.error('uv is not installed — spec-kit requires the uv Python package manager', {
|
|
50
|
+
exit: 1,
|
|
51
|
+
suggestions: ['Install uv: https://docs.astral.sh/uv/getting-started/installation/'],
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ── 2. Install specify-cli (skip if already present unless --reinstall) ──
|
|
56
|
+
const alreadyInstalled = await isSpecifyInstalled()
|
|
57
|
+
|
|
58
|
+
if (!alreadyInstalled || flags.reinstall) {
|
|
59
|
+
const label = alreadyInstalled ? 'Reinstalling specify-cli...' : 'Installing specify-cli...'
|
|
60
|
+
const spinner = ora({ spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')(label) }).start()
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
await installSpecifyCli({ force: flags.reinstall })
|
|
64
|
+
spinner.succeed(chalk.green('specify-cli installed'))
|
|
65
|
+
} catch (err) {
|
|
66
|
+
spinner.fail()
|
|
67
|
+
if (err instanceof DvmiError) {
|
|
68
|
+
this.error(err.message, { exit: 1, suggestions: [err.hint] })
|
|
69
|
+
}
|
|
70
|
+
throw err
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
this.log(chalk.dim('specify-cli already installed'))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ── 3. Resolve --ai flag (flag > config > let specify prompt) ────────────
|
|
77
|
+
let aiFlag = flags.ai
|
|
78
|
+
if (!aiFlag) {
|
|
79
|
+
const config = await loadConfig()
|
|
80
|
+
aiFlag = config.aiTool ? (AI_TOOL_MAP[config.aiTool] ?? undefined) : undefined
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ── 4. Run `specify init --here` (interactive — inherits stdio) ──────────
|
|
84
|
+
this.log('')
|
|
85
|
+
this.log(chalk.bold('Running specify init — follow the prompts to set up your project:'))
|
|
86
|
+
this.log('')
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
await runSpecifyInit(process.cwd(), { ai: aiFlag, force: flags.force })
|
|
90
|
+
} catch (err) {
|
|
91
|
+
if (err instanceof DvmiError) {
|
|
92
|
+
this.error(err.message, { exit: 1, suggestions: [err.hint] })
|
|
93
|
+
}
|
|
94
|
+
throw err
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core'
|
|
2
|
+
import ora from 'ora'
|
|
3
|
+
import chalk from 'chalk'
|
|
4
|
+
import { select } from '@inquirer/prompts'
|
|
5
|
+
import { listPrompts } from '../../services/prompts.js'
|
|
6
|
+
import { formatPromptTable, formatPromptBody } from '../../formatters/prompts.js'
|
|
7
|
+
import { DvmiError } from '../../utils/errors.js'
|
|
8
|
+
|
|
9
|
+
/** @import { Prompt } from '../../types.js' */
|
|
10
|
+
|
|
11
|
+
export default class PromptsList extends Command {
|
|
12
|
+
static description = 'List prompts from your personal prompt repository'
|
|
13
|
+
|
|
14
|
+
static examples = [
|
|
15
|
+
'<%= config.bin %> prompts list',
|
|
16
|
+
'<%= config.bin %> prompts list --filter refactor',
|
|
17
|
+
'<%= config.bin %> prompts list --json',
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
static enableJsonFlag = true
|
|
21
|
+
|
|
22
|
+
static flags = {
|
|
23
|
+
filter: Flags.string({
|
|
24
|
+
char: 'f',
|
|
25
|
+
description: 'Filter prompts by title, category, description, or tag (case-insensitive)',
|
|
26
|
+
}),
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async run() {
|
|
30
|
+
const { flags } = await this.parse(PromptsList)
|
|
31
|
+
const isJson = flags.json
|
|
32
|
+
|
|
33
|
+
const spinner = isJson
|
|
34
|
+
? null
|
|
35
|
+
: ora({
|
|
36
|
+
spinner: 'arc',
|
|
37
|
+
color: false,
|
|
38
|
+
text: chalk.hex('#FF6B2B')('Fetching prompts...'),
|
|
39
|
+
}).start()
|
|
40
|
+
|
|
41
|
+
/** @type {Prompt[]} */
|
|
42
|
+
let prompts
|
|
43
|
+
try {
|
|
44
|
+
prompts = await listPrompts()
|
|
45
|
+
} catch (err) {
|
|
46
|
+
spinner?.fail()
|
|
47
|
+
if (err instanceof DvmiError) {
|
|
48
|
+
this.error(err.message, { exit: err.exitCode, suggestions: [err.hint] })
|
|
49
|
+
}
|
|
50
|
+
throw err
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
spinner?.stop()
|
|
54
|
+
|
|
55
|
+
// Apply filter
|
|
56
|
+
const query = flags.filter?.toLowerCase()
|
|
57
|
+
const filtered = query
|
|
58
|
+
? prompts.filter(
|
|
59
|
+
(p) =>
|
|
60
|
+
p.title.toLowerCase().includes(query) ||
|
|
61
|
+
p.category?.toLowerCase().includes(query) ||
|
|
62
|
+
p.description?.toLowerCase().includes(query) ||
|
|
63
|
+
p.tags?.some((t) => t.toLowerCase().includes(query)),
|
|
64
|
+
)
|
|
65
|
+
: prompts
|
|
66
|
+
|
|
67
|
+
if (isJson) {
|
|
68
|
+
return { prompts: filtered, total: filtered.length }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (filtered.length === 0) {
|
|
72
|
+
const msg = query
|
|
73
|
+
? chalk.dim(`No prompts matching "${flags.filter}".`)
|
|
74
|
+
: chalk.yellow('No prompts found in the repository.')
|
|
75
|
+
this.log(msg)
|
|
76
|
+
return { prompts: [], total: 0 }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const filterInfo = query ? chalk.dim(` — filter: ${chalk.white(`"${flags.filter}"`)}`) : ''
|
|
80
|
+
this.log(
|
|
81
|
+
chalk.bold(`\nPrompts`) +
|
|
82
|
+
filterInfo +
|
|
83
|
+
chalk.dim(` (${filtered.length}${filtered.length < prompts.length ? `/${prompts.length}` : ''})\n`),
|
|
84
|
+
)
|
|
85
|
+
this.log(formatPromptTable(filtered))
|
|
86
|
+
this.log('')
|
|
87
|
+
|
|
88
|
+
// Interactive selection to view full prompt content
|
|
89
|
+
try {
|
|
90
|
+
const choices = filtered.map((p) => ({ name: p.title, value: p }))
|
|
91
|
+
choices.push({ name: chalk.dim('← Exit'), value: /** @type {Prompt} */ (null) })
|
|
92
|
+
|
|
93
|
+
const selected = await select({
|
|
94
|
+
message: 'Select a prompt to view its content (or Exit):',
|
|
95
|
+
choices,
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
if (selected) {
|
|
99
|
+
this.log('\n' + formatPromptBody(selected) + '\n')
|
|
100
|
+
}
|
|
101
|
+
} catch {
|
|
102
|
+
// User pressed Ctrl+C — exit gracefully
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return { prompts: filtered, total: filtered.length }
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { Command, Args, Flags } from '@oclif/core'
|
|
2
|
+
import ora from 'ora'
|
|
3
|
+
import chalk from 'chalk'
|
|
4
|
+
import { select } from '@inquirer/prompts'
|
|
5
|
+
import { join } from 'node:path'
|
|
6
|
+
import { readdir } from 'node:fs/promises'
|
|
7
|
+
import { resolveLocalPrompt, invokeTool, SUPPORTED_TOOLS } from '../../services/prompts.js'
|
|
8
|
+
import { loadConfig } from '../../services/config.js'
|
|
9
|
+
import { DvmiError } from '../../utils/errors.js'
|
|
10
|
+
|
|
11
|
+
/** @import { AITool } from '../../types.js' */
|
|
12
|
+
|
|
13
|
+
const DEFAULT_PROMPTS_DIR = '.prompts'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Walk a directory recursively and collect `.md` file paths relative to `base`.
|
|
17
|
+
* @param {string} dir
|
|
18
|
+
* @param {string} base
|
|
19
|
+
* @returns {Promise<string[]>}
|
|
20
|
+
*/
|
|
21
|
+
async function walkPrompts(dir, base) {
|
|
22
|
+
/** @type {string[]} */
|
|
23
|
+
const results = []
|
|
24
|
+
let entries
|
|
25
|
+
try {
|
|
26
|
+
entries = await readdir(dir, { withFileTypes: true })
|
|
27
|
+
} catch {
|
|
28
|
+
return results
|
|
29
|
+
}
|
|
30
|
+
for (const entry of entries) {
|
|
31
|
+
const full = join(dir, entry.name)
|
|
32
|
+
if (entry.isDirectory()) {
|
|
33
|
+
const sub = await walkPrompts(full, base)
|
|
34
|
+
results.push(...sub)
|
|
35
|
+
} else if (entry.name.endsWith('.md')) {
|
|
36
|
+
results.push(full.replace(base + '/', ''))
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return results
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export default class PromptsRun extends Command {
|
|
43
|
+
static description = 'Execute a local prompt with a configured AI tool'
|
|
44
|
+
|
|
45
|
+
static examples = [
|
|
46
|
+
'<%= config.bin %> prompts run',
|
|
47
|
+
'<%= config.bin %> prompts run coding/refactor-prompt.md',
|
|
48
|
+
'<%= config.bin %> prompts run coding/refactor-prompt.md --tool opencode',
|
|
49
|
+
'<%= config.bin %> prompts run coding/refactor-prompt.md --json',
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
static enableJsonFlag = true
|
|
53
|
+
|
|
54
|
+
static args = {
|
|
55
|
+
path: Args.string({
|
|
56
|
+
description: 'Relative path of the local prompt (e.g. coding/refactor-prompt.md)',
|
|
57
|
+
required: false,
|
|
58
|
+
}),
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
static flags = {
|
|
62
|
+
tool: Flags.string({
|
|
63
|
+
char: 't',
|
|
64
|
+
description: `AI tool to use (${Object.keys(SUPPORTED_TOOLS).join(', ')})`,
|
|
65
|
+
options: Object.keys(SUPPORTED_TOOLS),
|
|
66
|
+
}),
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async run() {
|
|
70
|
+
const { args, flags } = await this.parse(PromptsRun)
|
|
71
|
+
const isJson = flags.json
|
|
72
|
+
|
|
73
|
+
// Load config
|
|
74
|
+
let config = {}
|
|
75
|
+
try {
|
|
76
|
+
config = await loadConfig()
|
|
77
|
+
} catch {
|
|
78
|
+
/* use defaults */
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const localDir =
|
|
82
|
+
process.env.DVMI_PROMPTS_DIR ?? config.promptsDir ?? join(process.cwd(), DEFAULT_PROMPTS_DIR)
|
|
83
|
+
|
|
84
|
+
// Resolve tool: --tool flag > config.aiTool
|
|
85
|
+
const toolName = /** @type {AITool | undefined} */ (flags.tool ?? config.aiTool)
|
|
86
|
+
|
|
87
|
+
// In --json mode, output the invocation plan without spawning
|
|
88
|
+
if (isJson) {
|
|
89
|
+
if (!args.path) {
|
|
90
|
+
this.error('Prompt path is required in --json mode', {
|
|
91
|
+
exit: 1,
|
|
92
|
+
suggestions: ['Run `dvmi prompts run <path> --json`'],
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!toolName) {
|
|
97
|
+
this.error('No AI tool configured', {
|
|
98
|
+
exit: 1,
|
|
99
|
+
suggestions: ['Run `dvmi init` to configure your preferred AI tool, or pass --tool <name>'],
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (!SUPPORTED_TOOLS[toolName]) {
|
|
104
|
+
this.error(`Unknown tool: "${toolName}". Supported: ${Object.keys(SUPPORTED_TOOLS).join(', ')}`, {
|
|
105
|
+
exit: 1,
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let prompt
|
|
110
|
+
try {
|
|
111
|
+
prompt = await resolveLocalPrompt(args.path, localDir)
|
|
112
|
+
} catch (err) {
|
|
113
|
+
if (err instanceof DvmiError) {
|
|
114
|
+
this.error(err.message, { exit: err.exitCode, suggestions: [err.hint] })
|
|
115
|
+
}
|
|
116
|
+
throw err
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const tool = SUPPORTED_TOOLS[toolName]
|
|
120
|
+
const invocation = [tool.bin.join(' '), tool.promptFlag, '<prompt content>'].join(' ')
|
|
121
|
+
return {
|
|
122
|
+
tool: toolName,
|
|
123
|
+
promptPath: args.path,
|
|
124
|
+
invocation,
|
|
125
|
+
preview: prompt.body.slice(0, 200),
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// --- Interactive mode ---
|
|
130
|
+
|
|
131
|
+
// Resolve path interactively if not provided
|
|
132
|
+
let relativePath = args.path
|
|
133
|
+
if (!relativePath) {
|
|
134
|
+
const localPaths = await walkPrompts(localDir, localDir)
|
|
135
|
+
|
|
136
|
+
if (localPaths.length === 0) {
|
|
137
|
+
this.error('No local prompts found', {
|
|
138
|
+
exit: 1,
|
|
139
|
+
suggestions: [`Run \`dvmi prompts download\` to download prompts to ${localDir}`],
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
relativePath = await select({
|
|
144
|
+
message: 'Select a local prompt to run:',
|
|
145
|
+
choices: localPaths.map((p) => ({ name: p, value: p })),
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Verify tool is configured
|
|
150
|
+
if (!toolName) {
|
|
151
|
+
this.error('No AI tool configured', {
|
|
152
|
+
exit: 1,
|
|
153
|
+
suggestions: ['Run `dvmi init` to configure your preferred AI tool, or pass --tool <name>'],
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Load prompt
|
|
158
|
+
const spinner = ora({
|
|
159
|
+
spinner: 'arc',
|
|
160
|
+
color: false,
|
|
161
|
+
text: chalk.hex('#FF6B2B')('Loading prompt...'),
|
|
162
|
+
}).start()
|
|
163
|
+
|
|
164
|
+
let prompt
|
|
165
|
+
try {
|
|
166
|
+
prompt = await resolveLocalPrompt(relativePath, localDir)
|
|
167
|
+
spinner.stop()
|
|
168
|
+
} catch (err) {
|
|
169
|
+
spinner.fail()
|
|
170
|
+
if (err instanceof DvmiError) {
|
|
171
|
+
this.error(err.message, { exit: err.exitCode, suggestions: [err.hint] })
|
|
172
|
+
}
|
|
173
|
+
throw err
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
this.log(chalk.bold(`\nRunning: ${chalk.hex('#FF9A5C')(prompt.title)}`))
|
|
177
|
+
this.log(chalk.dim(` Tool: ${toolName}`) + '\n')
|
|
178
|
+
|
|
179
|
+
// Invoke tool
|
|
180
|
+
try {
|
|
181
|
+
await invokeTool(toolName, prompt.body)
|
|
182
|
+
} catch (err) {
|
|
183
|
+
if (err instanceof DvmiError) {
|
|
184
|
+
this.error(err.message, { exit: err.exitCode, suggestions: [err.hint] })
|
|
185
|
+
}
|
|
186
|
+
throw err
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import { marked } from 'marked'
|
|
3
|
+
import { renderTable } from './table.js'
|
|
4
|
+
|
|
5
|
+
/** @import { Prompt, Skill, AwesomeEntry } from '../types.js' */
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Format a list of prompts as a terminal table.
|
|
9
|
+
* Columns: title, category, description.
|
|
10
|
+
*
|
|
11
|
+
* @param {Prompt[]} prompts
|
|
12
|
+
* @returns {string}
|
|
13
|
+
*/
|
|
14
|
+
export function formatPromptTable(prompts) {
|
|
15
|
+
if (prompts.length === 0) {
|
|
16
|
+
return chalk.dim('No prompts found.')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return renderTable(
|
|
20
|
+
/** @type {Record<string, unknown>[]} */ (prompts),
|
|
21
|
+
[
|
|
22
|
+
{
|
|
23
|
+
header: 'Title',
|
|
24
|
+
key: 'title',
|
|
25
|
+
width: 36,
|
|
26
|
+
colorize: (v) => chalk.hex('#FF9A5C')(v),
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
header: 'Category',
|
|
30
|
+
key: 'category',
|
|
31
|
+
width: 16,
|
|
32
|
+
format: (v) => v ?? '—',
|
|
33
|
+
colorize: (v) => chalk.hex('#4A9EFF')(v),
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
header: 'Description',
|
|
37
|
+
key: 'description',
|
|
38
|
+
width: 60,
|
|
39
|
+
format: (v) => v ?? '—',
|
|
40
|
+
colorize: (v) => chalk.white(v),
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Format a single prompt's full content for display in the terminal.
|
|
48
|
+
* Renders the title as a header and the body as plain text (markdown stripped).
|
|
49
|
+
*
|
|
50
|
+
* @param {Prompt} prompt
|
|
51
|
+
* @returns {string}
|
|
52
|
+
*/
|
|
53
|
+
export function formatPromptBody(prompt) {
|
|
54
|
+
const titleLine = chalk.bold.hex('#FF6B2B')(prompt.title)
|
|
55
|
+
const divider = chalk.dim('─'.repeat(60))
|
|
56
|
+
|
|
57
|
+
const meta = [
|
|
58
|
+
prompt.category ? chalk.dim(`Category: `) + chalk.hex('#4A9EFF')(prompt.category) : null,
|
|
59
|
+
prompt.description ? chalk.dim(`Description: `) + chalk.white(prompt.description) : null,
|
|
60
|
+
prompt.tags?.length ? chalk.dim(`Tags: `) + chalk.hex('#4A9EFF')(prompt.tags.join(', ')) : null,
|
|
61
|
+
]
|
|
62
|
+
.filter(Boolean)
|
|
63
|
+
.join('\n')
|
|
64
|
+
|
|
65
|
+
// Render markdown to plain terminal text by stripping HTML tags from marked output
|
|
66
|
+
const rendered = marked(prompt.body, { async: false })
|
|
67
|
+
const plain = String(rendered)
|
|
68
|
+
.replace(/<[^>]+>/g, '')
|
|
69
|
+
.replace(/&/g, '&')
|
|
70
|
+
.replace(/</g, '<')
|
|
71
|
+
.replace(/>/g, '>')
|
|
72
|
+
.replace(/"/g, '"')
|
|
73
|
+
.trim()
|
|
74
|
+
|
|
75
|
+
const parts = [titleLine, divider]
|
|
76
|
+
if (meta) parts.push(meta, divider)
|
|
77
|
+
parts.push(plain)
|
|
78
|
+
|
|
79
|
+
return parts.join('\n')
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Format a list of skills.sh skills as a terminal table.
|
|
84
|
+
* Columns: name, installs, description.
|
|
85
|
+
*
|
|
86
|
+
* @param {Skill[]} skills
|
|
87
|
+
* @returns {string}
|
|
88
|
+
*/
|
|
89
|
+
export function formatSkillTable(skills) {
|
|
90
|
+
if (skills.length === 0) {
|
|
91
|
+
return chalk.dim('No skills found.')
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return renderTable(
|
|
95
|
+
/** @type {Record<string, unknown>[]} */ (skills),
|
|
96
|
+
[
|
|
97
|
+
{
|
|
98
|
+
header: 'Name',
|
|
99
|
+
key: 'name',
|
|
100
|
+
width: 36,
|
|
101
|
+
colorize: (v) => chalk.hex('#FF9A5C')(v),
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
header: 'Installs',
|
|
105
|
+
key: 'installs',
|
|
106
|
+
width: 10,
|
|
107
|
+
format: (v) => (v != null ? String(v) : '—'),
|
|
108
|
+
colorize: (v) => chalk.hex('#4A9EFF')(v),
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
header: 'Description',
|
|
112
|
+
key: 'description',
|
|
113
|
+
width: 60,
|
|
114
|
+
format: (v) => v ?? '—',
|
|
115
|
+
colorize: (v) => chalk.white(v),
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Format a list of awesome-copilot entries as a terminal table.
|
|
123
|
+
* Columns: name, category, description.
|
|
124
|
+
*
|
|
125
|
+
* @param {AwesomeEntry[]} entries
|
|
126
|
+
* @param {string} [category] - Active category label shown in the header
|
|
127
|
+
* @returns {string}
|
|
128
|
+
*/
|
|
129
|
+
export function formatAwesomeTable(entries, category) {
|
|
130
|
+
if (entries.length === 0) {
|
|
131
|
+
return chalk.dim(category ? `No entries found for category "${category}".` : 'No entries found.')
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return renderTable(
|
|
135
|
+
/** @type {Record<string, unknown>[]} */ (entries),
|
|
136
|
+
[
|
|
137
|
+
{
|
|
138
|
+
header: 'Name',
|
|
139
|
+
key: 'name',
|
|
140
|
+
width: 36,
|
|
141
|
+
colorize: (v) => chalk.hex('#FF9A5C')(v),
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
header: 'Category',
|
|
145
|
+
key: 'category',
|
|
146
|
+
width: 14,
|
|
147
|
+
format: (v) => v ?? '—',
|
|
148
|
+
colorize: (v) => chalk.hex('#4A9EFF')(v),
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
header: 'Description',
|
|
152
|
+
key: 'description',
|
|
153
|
+
width: 58,
|
|
154
|
+
format: (v) => v ?? '—',
|
|
155
|
+
colorize: (v) => chalk.white(v),
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
)
|
|
159
|
+
}
|
package/src/help.js
CHANGED
|
@@ -68,6 +68,16 @@ const CATEGORIES = [
|
|
|
68
68
|
{ id: 'costs:get', hint: '[--period] [--profile]' },
|
|
69
69
|
],
|
|
70
70
|
},
|
|
71
|
+
{
|
|
72
|
+
title: 'AI Prompts',
|
|
73
|
+
cmds: [
|
|
74
|
+
{ id: 'prompts:list', hint: '[--filter]' },
|
|
75
|
+
{ id: 'prompts:download', hint: '<PATH> [--overwrite]' },
|
|
76
|
+
{ id: 'prompts:browse', hint: '[--source] [--query] [--category]' },
|
|
77
|
+
{ id: 'prompts:install-speckit', hint: '[--force]' },
|
|
78
|
+
{ id: 'prompts:run', hint: '[PATH] [--tool]' },
|
|
79
|
+
],
|
|
80
|
+
},
|
|
71
81
|
{
|
|
72
82
|
title: 'Setup & Ambiente',
|
|
73
83
|
cmds: [
|
|
@@ -82,14 +92,19 @@ const CATEGORIES = [
|
|
|
82
92
|
|
|
83
93
|
// ─── Example commands shown at bottom of root help ──────────────────────────
|
|
84
94
|
const EXAMPLES = [
|
|
85
|
-
{ cmd: 'dvmi
|
|
86
|
-
{ cmd: 'dvmi
|
|
87
|
-
{ cmd: 'dvmi
|
|
88
|
-
{ cmd: 'dvmi
|
|
89
|
-
{ cmd: 'dvmi
|
|
90
|
-
{ cmd: 'dvmi
|
|
91
|
-
{ cmd: 'dvmi
|
|
92
|
-
{ cmd: 'dvmi
|
|
95
|
+
{ cmd: 'dvmi prompts list', note: 'Sfoglia prompt AI dal tuo repository' },
|
|
96
|
+
{ cmd: 'dvmi prompts list --filter refactor', note: 'Filtra prompt per parola chiave' },
|
|
97
|
+
{ cmd: 'dvmi prompts download coding/refactor-prompt.md', note: 'Scarica un prompt localmente' },
|
|
98
|
+
{ cmd: 'dvmi prompts browse skills --query refactor', note: 'Cerca skill su skills.sh' },
|
|
99
|
+
{ cmd: 'dvmi prompts browse awesome --category agents', note: 'Sfoglia awesome-copilot agents' },
|
|
100
|
+
{ cmd: 'dvmi prompts run coding/refactor-prompt.md --tool opencode', note: 'Esegui un prompt con opencode' },
|
|
101
|
+
{ cmd: 'dvmi docs read', note: 'Leggi il README del repo corrente' },
|
|
102
|
+
{ cmd: 'dvmi docs search "authentication"', note: 'Cerca nei docs del repo corrente' },
|
|
103
|
+
{ cmd: 'dvmi repo list --search "api"', note: 'Filtra repository per nome' },
|
|
104
|
+
{ cmd: 'dvmi pr status', note: 'PR aperte e review in attesa' },
|
|
105
|
+
{ cmd: 'dvmi pipeline status', note: 'Ultimi workflow CI/CD' },
|
|
106
|
+
{ cmd: 'dvmi tasks list --search "bug"', note: 'Cerca task ClickUp' },
|
|
107
|
+
{ cmd: 'dvmi costs get --json', note: 'Costi AWS in formato JSON' },
|
|
93
108
|
]
|
|
94
109
|
|
|
95
110
|
// ─── Help class ─────────────────────────────────────────────────────────────
|