devvami 1.0.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/oclif.manifest.json +331 -67
- package/package.json +5 -1
- package/src/commands/init.js +30 -1
- package/src/commands/open.js +1 -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/commands/upgrade.js +5 -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/clickup.js +15 -1
- package/src/services/config.js +2 -1
- package/src/services/github.js +2 -1
- package/src/services/prompts.js +326 -0
- package/src/services/skills-sh.js +81 -0
- package/src/services/speckit.js +76 -0
- package/src/types.js +34 -0
- package/src/utils/frontmatter.js +52 -0
|
@@ -0,0 +1,164 @@
|
|
|
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 { searchSkills } from '../../services/skills-sh.js'
|
|
6
|
+
import { fetchAwesomeEntries, AWESOME_CATEGORIES } from '../../services/awesome-copilot.js'
|
|
7
|
+
import { formatSkillTable, formatAwesomeTable } from '../../formatters/prompts.js'
|
|
8
|
+
import { DvmiError } from '../../utils/errors.js'
|
|
9
|
+
|
|
10
|
+
/** @import { Skill, AwesomeEntry } from '../../types.js' */
|
|
11
|
+
|
|
12
|
+
const VALID_SOURCES = ['skills', 'awesome']
|
|
13
|
+
|
|
14
|
+
export default class PromptsBrowse extends Command {
|
|
15
|
+
static description = 'Browse prompts and skills from external sources (skills.sh, awesome-copilot)'
|
|
16
|
+
|
|
17
|
+
static examples = [
|
|
18
|
+
'<%= config.bin %> prompts browse skills --query refactor',
|
|
19
|
+
'<%= config.bin %> prompts browse skills --query testing --json',
|
|
20
|
+
'<%= config.bin %> prompts browse awesome',
|
|
21
|
+
'<%= config.bin %> prompts browse awesome --category agents',
|
|
22
|
+
'<%= config.bin %> prompts browse awesome --category instructions --json',
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
static enableJsonFlag = true
|
|
26
|
+
|
|
27
|
+
static args = {
|
|
28
|
+
source: Args.string({
|
|
29
|
+
description: `Source to browse: ${VALID_SOURCES.join(' | ')}`,
|
|
30
|
+
required: true,
|
|
31
|
+
options: VALID_SOURCES,
|
|
32
|
+
}),
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static flags = {
|
|
36
|
+
query: Flags.string({
|
|
37
|
+
char: 'q',
|
|
38
|
+
description: 'Search query (only applies to skills source)',
|
|
39
|
+
}),
|
|
40
|
+
category: Flags.string({
|
|
41
|
+
char: 'c',
|
|
42
|
+
description: `awesome-copilot category (${AWESOME_CATEGORIES.join(', ')})`,
|
|
43
|
+
default: 'instructions',
|
|
44
|
+
}),
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async run() {
|
|
48
|
+
const { args, flags } = await this.parse(PromptsBrowse)
|
|
49
|
+
const isJson = flags.json
|
|
50
|
+
const source = args.source
|
|
51
|
+
|
|
52
|
+
if (!VALID_SOURCES.includes(source)) {
|
|
53
|
+
this.error(`Invalid source: "${source}". Must be one of: ${VALID_SOURCES.join(', ')}`, {
|
|
54
|
+
exit: 1,
|
|
55
|
+
suggestions: VALID_SOURCES.map((s) => `dvmi prompts browse ${s}`),
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const spinner = isJson
|
|
60
|
+
? null
|
|
61
|
+
: ora({
|
|
62
|
+
spinner: 'arc',
|
|
63
|
+
color: false,
|
|
64
|
+
text: chalk.hex('#FF6B2B')(
|
|
65
|
+
source === 'skills' ? 'Searching skills.sh...' : `Fetching awesome-copilot (${flags.category})...`,
|
|
66
|
+
),
|
|
67
|
+
}).start()
|
|
68
|
+
|
|
69
|
+
if (source === 'skills') {
|
|
70
|
+
if (!flags.query || flags.query.length < 2) {
|
|
71
|
+
this.error('skills.sh requires a search query (min 2 characters)', {
|
|
72
|
+
exit: 1,
|
|
73
|
+
suggestions: ['dvmi prompts browse skills --query refactor'],
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** @type {Skill[]} */
|
|
78
|
+
let skills
|
|
79
|
+
try {
|
|
80
|
+
skills = await searchSkills(flags.query ?? '', 50)
|
|
81
|
+
} catch (err) {
|
|
82
|
+
spinner?.fail()
|
|
83
|
+
if (err instanceof DvmiError) {
|
|
84
|
+
this.error(err.message, { exit: err.exitCode, suggestions: [err.hint] })
|
|
85
|
+
}
|
|
86
|
+
throw err
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
spinner?.stop()
|
|
90
|
+
|
|
91
|
+
if (isJson) {
|
|
92
|
+
return { skills, total: skills.length }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
this.log(
|
|
96
|
+
chalk.bold('\nSkills') +
|
|
97
|
+
(flags.query ? chalk.dim(` — query: "${flags.query}"`) : '') +
|
|
98
|
+
chalk.dim(` (${skills.length})\n`),
|
|
99
|
+
)
|
|
100
|
+
this.log(formatSkillTable(skills))
|
|
101
|
+
|
|
102
|
+
return { skills, total: skills.length }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// source === 'awesome'
|
|
106
|
+
const category = flags.category
|
|
107
|
+
|
|
108
|
+
if (!AWESOME_CATEGORIES.includes(category)) {
|
|
109
|
+
this.error(`Invalid category: "${category}". Valid: ${AWESOME_CATEGORIES.join(', ')}`, {
|
|
110
|
+
exit: 1,
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** @type {AwesomeEntry[]} */
|
|
115
|
+
let entries
|
|
116
|
+
try {
|
|
117
|
+
entries = await fetchAwesomeEntries(category)
|
|
118
|
+
} catch (err) {
|
|
119
|
+
spinner?.fail()
|
|
120
|
+
if (err instanceof DvmiError) {
|
|
121
|
+
this.error(err.message, { exit: err.exitCode, suggestions: [err.hint] })
|
|
122
|
+
}
|
|
123
|
+
throw err
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
spinner?.stop()
|
|
127
|
+
|
|
128
|
+
if (isJson) {
|
|
129
|
+
return { entries, total: entries.length, category }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
this.log(
|
|
133
|
+
chalk.bold('\nAwesome Copilot') +
|
|
134
|
+
chalk.dim(` — category: `) +
|
|
135
|
+
chalk.hex('#4A9EFF')(category) +
|
|
136
|
+
chalk.dim(` (${entries.length})\n`),
|
|
137
|
+
)
|
|
138
|
+
this.log(formatAwesomeTable(entries, category))
|
|
139
|
+
this.log('')
|
|
140
|
+
|
|
141
|
+
if (entries.length > 0) {
|
|
142
|
+
try {
|
|
143
|
+
const choices = entries.map((e) => ({ name: `${e.name} ${chalk.dim(e.url)}`, value: e }))
|
|
144
|
+
choices.push({ name: chalk.dim('← Exit'), value: /** @type {AwesomeEntry} */ (null) })
|
|
145
|
+
|
|
146
|
+
const selected = await select({
|
|
147
|
+
message: 'Select an entry to view its URL (or Exit):',
|
|
148
|
+
choices,
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
if (selected) {
|
|
152
|
+
this.log(`\n${chalk.bold(selected.name)}\n${chalk.hex('#4A9EFF')(selected.url)}\n`)
|
|
153
|
+
if (selected.description) {
|
|
154
|
+
this.log(chalk.white(selected.description) + '\n')
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
} catch {
|
|
158
|
+
// User pressed Ctrl+C — exit gracefully
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return { entries, total: entries.length, category }
|
|
163
|
+
}
|
|
164
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { Command, Args, Flags } from '@oclif/core'
|
|
2
|
+
import ora from 'ora'
|
|
3
|
+
import chalk from 'chalk'
|
|
4
|
+
import { select, confirm } from '@inquirer/prompts'
|
|
5
|
+
import { join } from 'node:path'
|
|
6
|
+
import { listPrompts, downloadPrompt } from '../../services/prompts.js'
|
|
7
|
+
import { loadConfig } from '../../services/config.js'
|
|
8
|
+
import { DvmiError } from '../../utils/errors.js'
|
|
9
|
+
|
|
10
|
+
/** @import { Prompt } from '../../types.js' */
|
|
11
|
+
|
|
12
|
+
const DEFAULT_PROMPTS_DIR = '.prompts'
|
|
13
|
+
|
|
14
|
+
export default class PromptsDownload extends Command {
|
|
15
|
+
static description = 'Download a prompt from your personal repository to .prompts/'
|
|
16
|
+
|
|
17
|
+
static examples = [
|
|
18
|
+
'<%= config.bin %> prompts download',
|
|
19
|
+
'<%= config.bin %> prompts download coding/refactor-prompt.md',
|
|
20
|
+
'<%= config.bin %> prompts download coding/refactor-prompt.md --overwrite',
|
|
21
|
+
'<%= config.bin %> prompts download --json',
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
static enableJsonFlag = true
|
|
25
|
+
|
|
26
|
+
static args = {
|
|
27
|
+
path: Args.string({
|
|
28
|
+
description: 'Relative path of the prompt in the repository (e.g. coding/refactor-prompt.md)',
|
|
29
|
+
required: false,
|
|
30
|
+
}),
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
static flags = {
|
|
34
|
+
overwrite: Flags.boolean({
|
|
35
|
+
description: 'Overwrite existing local file without prompting',
|
|
36
|
+
default: false,
|
|
37
|
+
}),
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async run() {
|
|
41
|
+
const { args, flags } = await this.parse(PromptsDownload)
|
|
42
|
+
const isJson = flags.json
|
|
43
|
+
|
|
44
|
+
// Determine local prompts directory from config or default to cwd/.prompts
|
|
45
|
+
let config = {}
|
|
46
|
+
try {
|
|
47
|
+
config = await loadConfig()
|
|
48
|
+
} catch {
|
|
49
|
+
/* use defaults */
|
|
50
|
+
}
|
|
51
|
+
const localDir =
|
|
52
|
+
process.env.DVMI_PROMPTS_DIR ?? config.promptsDir ?? join(process.cwd(), DEFAULT_PROMPTS_DIR)
|
|
53
|
+
|
|
54
|
+
// Resolve path interactively if not provided (only in interactive mode)
|
|
55
|
+
let relativePath = args.path
|
|
56
|
+
if (!relativePath) {
|
|
57
|
+
if (isJson) {
|
|
58
|
+
this.error('Prompt path is required in --json mode', {
|
|
59
|
+
exit: 1,
|
|
60
|
+
suggestions: ['Run `dvmi prompts download <path> --json`'],
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const spinner = ora({
|
|
65
|
+
spinner: 'arc',
|
|
66
|
+
color: false,
|
|
67
|
+
text: chalk.hex('#FF6B2B')('Fetching prompts...'),
|
|
68
|
+
}).start()
|
|
69
|
+
|
|
70
|
+
/** @type {Prompt[]} */
|
|
71
|
+
let prompts
|
|
72
|
+
try {
|
|
73
|
+
prompts = await listPrompts()
|
|
74
|
+
} catch (err) {
|
|
75
|
+
spinner.fail()
|
|
76
|
+
if (err instanceof DvmiError) {
|
|
77
|
+
this.error(err.message, { exit: err.exitCode, suggestions: [err.hint] })
|
|
78
|
+
}
|
|
79
|
+
throw err
|
|
80
|
+
}
|
|
81
|
+
spinner.stop()
|
|
82
|
+
|
|
83
|
+
if (prompts.length === 0) {
|
|
84
|
+
this.log(chalk.yellow('No prompts found in the repository.'))
|
|
85
|
+
return { downloaded: [], skipped: [] }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const choices = prompts.map((p) => ({
|
|
89
|
+
name: `${p.path} ${chalk.dim(p.title)}`,
|
|
90
|
+
value: p.path,
|
|
91
|
+
}))
|
|
92
|
+
relativePath = await select({ message: 'Select a prompt to download:', choices })
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Attempt download (skips automatically if file exists and --overwrite not set)
|
|
96
|
+
const spinner = isJson
|
|
97
|
+
? null
|
|
98
|
+
: ora({
|
|
99
|
+
spinner: 'arc',
|
|
100
|
+
color: false,
|
|
101
|
+
text: chalk.hex('#FF6B2B')(`Downloading ${relativePath}...`),
|
|
102
|
+
}).start()
|
|
103
|
+
|
|
104
|
+
let result
|
|
105
|
+
try {
|
|
106
|
+
result = await downloadPrompt(relativePath, localDir, { overwrite: flags.overwrite })
|
|
107
|
+
} catch (err) {
|
|
108
|
+
spinner?.fail()
|
|
109
|
+
if (err instanceof DvmiError) {
|
|
110
|
+
this.error(err.message, { exit: err.exitCode, suggestions: [err.hint] })
|
|
111
|
+
}
|
|
112
|
+
throw err
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
spinner?.stop()
|
|
116
|
+
|
|
117
|
+
// Conflict: file exists and user didn't pass --overwrite → ask interactively
|
|
118
|
+
if (result.skipped && !flags.overwrite && !isJson) {
|
|
119
|
+
const shouldOverwrite = await confirm({
|
|
120
|
+
message: chalk.yellow(`File already exists at ${result.path}. Overwrite?`),
|
|
121
|
+
default: false,
|
|
122
|
+
})
|
|
123
|
+
if (shouldOverwrite) {
|
|
124
|
+
try {
|
|
125
|
+
result = await downloadPrompt(relativePath, localDir, { overwrite: true })
|
|
126
|
+
} catch (err) {
|
|
127
|
+
if (err instanceof DvmiError) {
|
|
128
|
+
this.error(err.message, { exit: err.exitCode, suggestions: [err.hint] })
|
|
129
|
+
}
|
|
130
|
+
throw err
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (isJson) {
|
|
136
|
+
return {
|
|
137
|
+
downloaded: result.skipped ? [] : [result.path],
|
|
138
|
+
skipped: result.skipped ? [result.path] : [],
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (result.skipped) {
|
|
143
|
+
this.log(chalk.dim(`Skipped (already exists): ${result.path}`))
|
|
144
|
+
this.log(chalk.dim(' Run with --overwrite to replace it.'))
|
|
145
|
+
} else {
|
|
146
|
+
this.log(chalk.green(`✓ Downloaded: ${result.path}`))
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
downloaded: result.skipped ? [] : [result.path],
|
|
151
|
+
skipped: result.skipped ? [result.path] : [],
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -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
|
+
}
|