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.
@@ -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.0.0"
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.0.0",
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
  },
@@ -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. Shell completions
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
+ }