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
package/oclif.manifest.json
CHANGED
|
@@ -1024,6 +1024,270 @@
|
|
|
1024
1024
|
"status.js"
|
|
1025
1025
|
]
|
|
1026
1026
|
},
|
|
1027
|
+
"prompts:browse": {
|
|
1028
|
+
"aliases": [],
|
|
1029
|
+
"args": {
|
|
1030
|
+
"source": {
|
|
1031
|
+
"description": "Source to browse: skills | awesome",
|
|
1032
|
+
"name": "source",
|
|
1033
|
+
"options": [
|
|
1034
|
+
"skills",
|
|
1035
|
+
"awesome"
|
|
1036
|
+
],
|
|
1037
|
+
"required": true
|
|
1038
|
+
}
|
|
1039
|
+
},
|
|
1040
|
+
"description": "Browse prompts and skills from external sources (skills.sh, awesome-copilot)",
|
|
1041
|
+
"examples": [
|
|
1042
|
+
"<%= config.bin %> prompts browse skills --query refactor",
|
|
1043
|
+
"<%= config.bin %> prompts browse skills --query testing --json",
|
|
1044
|
+
"<%= config.bin %> prompts browse awesome",
|
|
1045
|
+
"<%= config.bin %> prompts browse awesome --category agents",
|
|
1046
|
+
"<%= config.bin %> prompts browse awesome --category instructions --json"
|
|
1047
|
+
],
|
|
1048
|
+
"flags": {
|
|
1049
|
+
"json": {
|
|
1050
|
+
"description": "Format output as json.",
|
|
1051
|
+
"helpGroup": "GLOBAL",
|
|
1052
|
+
"name": "json",
|
|
1053
|
+
"allowNo": false,
|
|
1054
|
+
"type": "boolean"
|
|
1055
|
+
},
|
|
1056
|
+
"query": {
|
|
1057
|
+
"char": "q",
|
|
1058
|
+
"description": "Search query (only applies to skills source)",
|
|
1059
|
+
"name": "query",
|
|
1060
|
+
"hasDynamicHelp": false,
|
|
1061
|
+
"multiple": false,
|
|
1062
|
+
"type": "option"
|
|
1063
|
+
},
|
|
1064
|
+
"category": {
|
|
1065
|
+
"char": "c",
|
|
1066
|
+
"description": "awesome-copilot category (agents, instructions, skills, plugins, hooks, workflows)",
|
|
1067
|
+
"name": "category",
|
|
1068
|
+
"default": "instructions",
|
|
1069
|
+
"hasDynamicHelp": false,
|
|
1070
|
+
"multiple": false,
|
|
1071
|
+
"type": "option"
|
|
1072
|
+
}
|
|
1073
|
+
},
|
|
1074
|
+
"hasDynamicHelp": false,
|
|
1075
|
+
"hiddenAliases": [],
|
|
1076
|
+
"id": "prompts:browse",
|
|
1077
|
+
"pluginAlias": "devvami",
|
|
1078
|
+
"pluginName": "devvami",
|
|
1079
|
+
"pluginType": "core",
|
|
1080
|
+
"strict": true,
|
|
1081
|
+
"enableJsonFlag": true,
|
|
1082
|
+
"isESM": true,
|
|
1083
|
+
"relativePath": [
|
|
1084
|
+
"src",
|
|
1085
|
+
"commands",
|
|
1086
|
+
"prompts",
|
|
1087
|
+
"browse.js"
|
|
1088
|
+
]
|
|
1089
|
+
},
|
|
1090
|
+
"prompts:download": {
|
|
1091
|
+
"aliases": [],
|
|
1092
|
+
"args": {
|
|
1093
|
+
"path": {
|
|
1094
|
+
"description": "Relative path of the prompt in the repository (e.g. coding/refactor-prompt.md)",
|
|
1095
|
+
"name": "path",
|
|
1096
|
+
"required": false
|
|
1097
|
+
}
|
|
1098
|
+
},
|
|
1099
|
+
"description": "Download a prompt from your personal repository to .prompts/",
|
|
1100
|
+
"examples": [
|
|
1101
|
+
"<%= config.bin %> prompts download",
|
|
1102
|
+
"<%= config.bin %> prompts download coding/refactor-prompt.md",
|
|
1103
|
+
"<%= config.bin %> prompts download coding/refactor-prompt.md --overwrite",
|
|
1104
|
+
"<%= config.bin %> prompts download --json"
|
|
1105
|
+
],
|
|
1106
|
+
"flags": {
|
|
1107
|
+
"json": {
|
|
1108
|
+
"description": "Format output as json.",
|
|
1109
|
+
"helpGroup": "GLOBAL",
|
|
1110
|
+
"name": "json",
|
|
1111
|
+
"allowNo": false,
|
|
1112
|
+
"type": "boolean"
|
|
1113
|
+
},
|
|
1114
|
+
"overwrite": {
|
|
1115
|
+
"description": "Overwrite existing local file without prompting",
|
|
1116
|
+
"name": "overwrite",
|
|
1117
|
+
"allowNo": false,
|
|
1118
|
+
"type": "boolean"
|
|
1119
|
+
}
|
|
1120
|
+
},
|
|
1121
|
+
"hasDynamicHelp": false,
|
|
1122
|
+
"hiddenAliases": [],
|
|
1123
|
+
"id": "prompts:download",
|
|
1124
|
+
"pluginAlias": "devvami",
|
|
1125
|
+
"pluginName": "devvami",
|
|
1126
|
+
"pluginType": "core",
|
|
1127
|
+
"strict": true,
|
|
1128
|
+
"enableJsonFlag": true,
|
|
1129
|
+
"isESM": true,
|
|
1130
|
+
"relativePath": [
|
|
1131
|
+
"src",
|
|
1132
|
+
"commands",
|
|
1133
|
+
"prompts",
|
|
1134
|
+
"download.js"
|
|
1135
|
+
]
|
|
1136
|
+
},
|
|
1137
|
+
"prompts:install-speckit": {
|
|
1138
|
+
"aliases": [],
|
|
1139
|
+
"args": {},
|
|
1140
|
+
"description": "Install spec-kit (specify-cli) and run `specify init` to set up Spec-Driven Development",
|
|
1141
|
+
"examples": [
|
|
1142
|
+
"<%= config.bin %> prompts install-speckit",
|
|
1143
|
+
"<%= config.bin %> prompts install-speckit --ai opencode",
|
|
1144
|
+
"<%= config.bin %> prompts install-speckit --force",
|
|
1145
|
+
"<%= config.bin %> prompts install-speckit --reinstall"
|
|
1146
|
+
],
|
|
1147
|
+
"flags": {
|
|
1148
|
+
"ai": {
|
|
1149
|
+
"description": "AI agent to pass to `specify init --ai` (defaults to the aiTool set in `dvmi init`)",
|
|
1150
|
+
"name": "ai",
|
|
1151
|
+
"hasDynamicHelp": false,
|
|
1152
|
+
"multiple": false,
|
|
1153
|
+
"options": [
|
|
1154
|
+
"opencode",
|
|
1155
|
+
"copilot",
|
|
1156
|
+
"claude",
|
|
1157
|
+
"gemini",
|
|
1158
|
+
"cursor-agent",
|
|
1159
|
+
"codex",
|
|
1160
|
+
"windsurf",
|
|
1161
|
+
"kiro-cli",
|
|
1162
|
+
"amp"
|
|
1163
|
+
],
|
|
1164
|
+
"type": "option"
|
|
1165
|
+
},
|
|
1166
|
+
"force": {
|
|
1167
|
+
"char": "f",
|
|
1168
|
+
"description": "Pass --force to `specify init` (safe to run in a non-empty directory)",
|
|
1169
|
+
"name": "force",
|
|
1170
|
+
"allowNo": false,
|
|
1171
|
+
"type": "boolean"
|
|
1172
|
+
},
|
|
1173
|
+
"reinstall": {
|
|
1174
|
+
"description": "Reinstall specify-cli even if it is already installed",
|
|
1175
|
+
"name": "reinstall",
|
|
1176
|
+
"allowNo": false,
|
|
1177
|
+
"type": "boolean"
|
|
1178
|
+
}
|
|
1179
|
+
},
|
|
1180
|
+
"hasDynamicHelp": false,
|
|
1181
|
+
"hiddenAliases": [],
|
|
1182
|
+
"id": "prompts:install-speckit",
|
|
1183
|
+
"pluginAlias": "devvami",
|
|
1184
|
+
"pluginName": "devvami",
|
|
1185
|
+
"pluginType": "core",
|
|
1186
|
+
"strict": true,
|
|
1187
|
+
"enableJsonFlag": false,
|
|
1188
|
+
"isESM": true,
|
|
1189
|
+
"relativePath": [
|
|
1190
|
+
"src",
|
|
1191
|
+
"commands",
|
|
1192
|
+
"prompts",
|
|
1193
|
+
"install-speckit.js"
|
|
1194
|
+
]
|
|
1195
|
+
},
|
|
1196
|
+
"prompts:list": {
|
|
1197
|
+
"aliases": [],
|
|
1198
|
+
"args": {},
|
|
1199
|
+
"description": "List prompts from your personal prompt repository",
|
|
1200
|
+
"examples": [
|
|
1201
|
+
"<%= config.bin %> prompts list",
|
|
1202
|
+
"<%= config.bin %> prompts list --filter refactor",
|
|
1203
|
+
"<%= config.bin %> prompts list --json"
|
|
1204
|
+
],
|
|
1205
|
+
"flags": {
|
|
1206
|
+
"json": {
|
|
1207
|
+
"description": "Format output as json.",
|
|
1208
|
+
"helpGroup": "GLOBAL",
|
|
1209
|
+
"name": "json",
|
|
1210
|
+
"allowNo": false,
|
|
1211
|
+
"type": "boolean"
|
|
1212
|
+
},
|
|
1213
|
+
"filter": {
|
|
1214
|
+
"char": "f",
|
|
1215
|
+
"description": "Filter prompts by title, category, description, or tag (case-insensitive)",
|
|
1216
|
+
"name": "filter",
|
|
1217
|
+
"hasDynamicHelp": false,
|
|
1218
|
+
"multiple": false,
|
|
1219
|
+
"type": "option"
|
|
1220
|
+
}
|
|
1221
|
+
},
|
|
1222
|
+
"hasDynamicHelp": false,
|
|
1223
|
+
"hiddenAliases": [],
|
|
1224
|
+
"id": "prompts:list",
|
|
1225
|
+
"pluginAlias": "devvami",
|
|
1226
|
+
"pluginName": "devvami",
|
|
1227
|
+
"pluginType": "core",
|
|
1228
|
+
"strict": true,
|
|
1229
|
+
"enableJsonFlag": true,
|
|
1230
|
+
"isESM": true,
|
|
1231
|
+
"relativePath": [
|
|
1232
|
+
"src",
|
|
1233
|
+
"commands",
|
|
1234
|
+
"prompts",
|
|
1235
|
+
"list.js"
|
|
1236
|
+
]
|
|
1237
|
+
},
|
|
1238
|
+
"prompts:run": {
|
|
1239
|
+
"aliases": [],
|
|
1240
|
+
"args": {
|
|
1241
|
+
"path": {
|
|
1242
|
+
"description": "Relative path of the local prompt (e.g. coding/refactor-prompt.md)",
|
|
1243
|
+
"name": "path",
|
|
1244
|
+
"required": false
|
|
1245
|
+
}
|
|
1246
|
+
},
|
|
1247
|
+
"description": "Execute a local prompt with a configured AI tool",
|
|
1248
|
+
"examples": [
|
|
1249
|
+
"<%= config.bin %> prompts run",
|
|
1250
|
+
"<%= config.bin %> prompts run coding/refactor-prompt.md",
|
|
1251
|
+
"<%= config.bin %> prompts run coding/refactor-prompt.md --tool opencode",
|
|
1252
|
+
"<%= config.bin %> prompts run coding/refactor-prompt.md --json"
|
|
1253
|
+
],
|
|
1254
|
+
"flags": {
|
|
1255
|
+
"json": {
|
|
1256
|
+
"description": "Format output as json.",
|
|
1257
|
+
"helpGroup": "GLOBAL",
|
|
1258
|
+
"name": "json",
|
|
1259
|
+
"allowNo": false,
|
|
1260
|
+
"type": "boolean"
|
|
1261
|
+
},
|
|
1262
|
+
"tool": {
|
|
1263
|
+
"char": "t",
|
|
1264
|
+
"description": "AI tool to use (opencode, copilot)",
|
|
1265
|
+
"name": "tool",
|
|
1266
|
+
"hasDynamicHelp": false,
|
|
1267
|
+
"multiple": false,
|
|
1268
|
+
"options": [
|
|
1269
|
+
"opencode",
|
|
1270
|
+
"copilot"
|
|
1271
|
+
],
|
|
1272
|
+
"type": "option"
|
|
1273
|
+
}
|
|
1274
|
+
},
|
|
1275
|
+
"hasDynamicHelp": false,
|
|
1276
|
+
"hiddenAliases": [],
|
|
1277
|
+
"id": "prompts:run",
|
|
1278
|
+
"pluginAlias": "devvami",
|
|
1279
|
+
"pluginName": "devvami",
|
|
1280
|
+
"pluginType": "core",
|
|
1281
|
+
"strict": true,
|
|
1282
|
+
"enableJsonFlag": true,
|
|
1283
|
+
"isESM": true,
|
|
1284
|
+
"relativePath": [
|
|
1285
|
+
"src",
|
|
1286
|
+
"commands",
|
|
1287
|
+
"prompts",
|
|
1288
|
+
"run.js"
|
|
1289
|
+
]
|
|
1290
|
+
},
|
|
1027
1291
|
"repo:list": {
|
|
1028
1292
|
"aliases": [],
|
|
1029
1293
|
"args": {},
|
|
@@ -1234,5 +1498,5 @@
|
|
|
1234
1498
|
]
|
|
1235
1499
|
}
|
|
1236
1500
|
},
|
|
1237
|
-
"version": "1.
|
|
1501
|
+
"version": "1.1.0"
|
|
1238
1502
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "devvami",
|
|
3
3
|
"description": "DevEx CLI for developers and teams — manage repos, PRs, pipelines, tasks, and costs from the terminal",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.1.0",
|
|
5
5
|
"author": "",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -76,6 +76,9 @@
|
|
|
76
76
|
},
|
|
77
77
|
"docs": {
|
|
78
78
|
"description": "📖 Repository documentation"
|
|
79
|
+
},
|
|
80
|
+
"prompts": {
|
|
81
|
+
"description": "🔮 AI prompt hub — browse, download, and run prompts"
|
|
79
82
|
}
|
|
80
83
|
}
|
|
81
84
|
},
|
package/src/commands/init.js
CHANGED
|
@@ -9,6 +9,8 @@ import { exec, which } from '../services/shell.js'
|
|
|
9
9
|
import { configExists, loadConfig, saveConfig, CONFIG_PATH } from '../services/config.js'
|
|
10
10
|
import { oauthFlow, storeToken, validateToken, getTeams } from '../services/clickup.js'
|
|
11
11
|
|
|
12
|
+
import { SUPPORTED_TOOLS } from '../services/prompts.js'
|
|
13
|
+
|
|
12
14
|
export default class Init extends Command {
|
|
13
15
|
static description = 'Setup completo ambiente di sviluppo locale'
|
|
14
16
|
|
|
@@ -207,7 +209,34 @@ export default class Init extends Command {
|
|
|
207
209
|
}
|
|
208
210
|
}
|
|
209
211
|
|
|
210
|
-
// 6.
|
|
212
|
+
// 6. AI tool selection
|
|
213
|
+
if (isDryRun) {
|
|
214
|
+
steps.push({ name: 'ai-tool', status: 'would configure' })
|
|
215
|
+
} else if (isJson) {
|
|
216
|
+
config = await loadConfig()
|
|
217
|
+
steps.push({
|
|
218
|
+
name: 'ai-tool',
|
|
219
|
+
status: config.aiTool ? 'configured' : 'not_configured',
|
|
220
|
+
aiTool: config.aiTool,
|
|
221
|
+
})
|
|
222
|
+
} else {
|
|
223
|
+
const aiToolChoices = Object.keys(SUPPORTED_TOOLS).map((t) => ({ name: t, value: t }))
|
|
224
|
+
aiToolChoices.push({ name: 'none / skip', value: '' })
|
|
225
|
+
const aiTool = await select({
|
|
226
|
+
message: 'Select your preferred AI tool for `dvmi prompts run`:',
|
|
227
|
+
choices: aiToolChoices,
|
|
228
|
+
})
|
|
229
|
+
if (aiTool) {
|
|
230
|
+
config = { ...config, aiTool }
|
|
231
|
+
await saveConfig(config)
|
|
232
|
+
this.log(chalk.green(`✓ AI tool set to: ${aiTool}`))
|
|
233
|
+
steps.push({ name: 'ai-tool', status: 'configured', aiTool })
|
|
234
|
+
} else {
|
|
235
|
+
steps.push({ name: 'ai-tool', status: 'skipped' })
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// 7. Shell completions
|
|
211
240
|
steps.push({ name: 'shell-completions', status: 'ok', action: 'install via: dvmi autocomplete' })
|
|
212
241
|
|
|
213
242
|
const result = { steps, configPath: CONFIG_PATH }
|
|
@@ -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
|
+
}
|