@verbatims/cli 0.1.1 → 0.1.3
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/bin/verbatims.js +0 -0
- package/package.json +1 -1
- package/src/commands/index.ts +2 -0
- package/src/commands/themes.ts +292 -0
package/bin/verbatims.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
package/src/commands/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { registerTagsCommand } from './tags.js'
|
|
|
6
6
|
import { registerCollectionsCommand } from './collections.js'
|
|
7
7
|
import { registerSearchCommand } from './search.js'
|
|
8
8
|
import { registerConfigCommand } from './config.js'
|
|
9
|
+
import { registerThemesCommand } from './themes.js'
|
|
9
10
|
|
|
10
11
|
export function registerCommands(program: Command) {
|
|
11
12
|
registerQuotesCommand(program)
|
|
@@ -15,4 +16,5 @@ export function registerCommands(program: Command) {
|
|
|
15
16
|
registerCollectionsCommand(program)
|
|
16
17
|
registerSearchCommand(program)
|
|
17
18
|
registerConfigCommand(program)
|
|
19
|
+
registerThemesCommand(program)
|
|
18
20
|
}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import type { Command } from 'commander'
|
|
2
|
+
import { text, isCancel, cancel, confirm } from '@clack/prompts'
|
|
3
|
+
import chalk from 'chalk'
|
|
4
|
+
import { getClient } from '../utils/client.js'
|
|
5
|
+
import { output, type Format } from '../utils/format.js'
|
|
6
|
+
import { withSpinner } from '../utils/spinner.js'
|
|
7
|
+
|
|
8
|
+
function getFormat(program: Command): Format {
|
|
9
|
+
return (program.opts().format ?? 'table') as Format
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function registerThemesCommand(program: Command) {
|
|
13
|
+
const themes = program.command('themes').description('Manage themes')
|
|
14
|
+
|
|
15
|
+
themes
|
|
16
|
+
.command('list')
|
|
17
|
+
.description('List themes')
|
|
18
|
+
.option('--search <q>', 'Search themes')
|
|
19
|
+
.option('--sort-by <field>', 'Sort field (priority|name|slug)')
|
|
20
|
+
.option('--sort-order <order>', 'Sort order (asc|desc)')
|
|
21
|
+
.option('--limit <n>', 'Number of results', '20')
|
|
22
|
+
.action(async (options: Record<string, string>) => {
|
|
23
|
+
const client = await getClient()
|
|
24
|
+
const format = getFormat(program)
|
|
25
|
+
const { data, pagination } = await withSpinner('Fetching themes', () =>
|
|
26
|
+
client.themes.list({
|
|
27
|
+
search: options.search,
|
|
28
|
+
sort_by: options.sortBy as 'priority' | 'name' | 'slug' | undefined,
|
|
29
|
+
sort_order: options.sortOrder as 'asc' | 'desc' | undefined,
|
|
30
|
+
limit: Number(options.limit),
|
|
31
|
+
}),
|
|
32
|
+
format,
|
|
33
|
+
)
|
|
34
|
+
output(data, format)
|
|
35
|
+
if (pagination && format !== 'json') {
|
|
36
|
+
console.log(chalk.dim(`\nPage ${pagination.page}/${pagination.totalPages} — ${pagination.total} total`))
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
themes
|
|
41
|
+
.command('get <id>')
|
|
42
|
+
.description('Get a theme by ID')
|
|
43
|
+
.action(async (id: string) => {
|
|
44
|
+
const client = await getClient()
|
|
45
|
+
const format = getFormat(program)
|
|
46
|
+
const { data } = await withSpinner('Fetching theme', () => client.themes.get(Number(id)), format)
|
|
47
|
+
output(data, format)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
themes
|
|
51
|
+
.command('create')
|
|
52
|
+
.description('Create a theme')
|
|
53
|
+
.option('--slug <slug>', 'URL-friendly slug')
|
|
54
|
+
.option('--name <name>', 'Theme name')
|
|
55
|
+
.option('--description <text>', 'Description')
|
|
56
|
+
.option('--language <lang>', 'Language')
|
|
57
|
+
.option('--active', 'Set as active')
|
|
58
|
+
.option('--default', 'Set as default')
|
|
59
|
+
.option('--priority <n>', 'Priority', '0')
|
|
60
|
+
.action(async (options: Record<string, string>) => {
|
|
61
|
+
const client = await getClient()
|
|
62
|
+
const format = getFormat(program)
|
|
63
|
+
|
|
64
|
+
const slug = options.slug ?? await text({ message: 'Theme slug', validate: (v) => !v ? 'Required' : undefined })
|
|
65
|
+
if (isCancel(slug)) cancel('Cancelled')
|
|
66
|
+
|
|
67
|
+
const name = options.name ?? await text({ message: 'Theme name', validate: (v) => !v ? 'Required' : undefined })
|
|
68
|
+
if (isCancel(name)) cancel('Cancelled')
|
|
69
|
+
|
|
70
|
+
const description = options.description ?? await text({ message: 'Description (optional)', initialValue: '' })
|
|
71
|
+
if (isCancel(description)) cancel('Cancelled')
|
|
72
|
+
|
|
73
|
+
const { data } = await withSpinner('Creating theme', () =>
|
|
74
|
+
client.themes.create({
|
|
75
|
+
slug: slug as string,
|
|
76
|
+
name: name as string,
|
|
77
|
+
description: (description as string) || null,
|
|
78
|
+
language: options.language || null,
|
|
79
|
+
is_active: options.active !== undefined ? true : undefined,
|
|
80
|
+
is_default: options.default !== undefined ? true : undefined,
|
|
81
|
+
priority: options.priority ? Number(options.priority) : undefined,
|
|
82
|
+
}),
|
|
83
|
+
format,
|
|
84
|
+
)
|
|
85
|
+
console.log(chalk.green(' Theme created'))
|
|
86
|
+
output(data, format)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
themes
|
|
90
|
+
.command('update <id>')
|
|
91
|
+
.description('Update a theme')
|
|
92
|
+
.option('--slug <slug>', 'URL-friendly slug')
|
|
93
|
+
.option('--name <name>', 'Theme name')
|
|
94
|
+
.option('--description <text>', 'Description')
|
|
95
|
+
.option('--language <lang>', 'Language')
|
|
96
|
+
.option('--priority <n>', 'Priority')
|
|
97
|
+
.action(async (id: string, options: Record<string, string>) => {
|
|
98
|
+
const client = await getClient()
|
|
99
|
+
const format = getFormat(program)
|
|
100
|
+
|
|
101
|
+
const data_: Record<string, unknown> = {}
|
|
102
|
+
if (options.slug) data_.slug = options.slug
|
|
103
|
+
if (options.name) data_.name = options.name
|
|
104
|
+
if (options.description !== undefined) data_.description = options.description || null
|
|
105
|
+
if (options.language !== undefined) data_.language = options.language || null
|
|
106
|
+
if (options.priority !== undefined) data_.priority = Number(options.priority)
|
|
107
|
+
|
|
108
|
+
const { data } = await withSpinner('Updating theme', () => client.themes.update(Number(id), data_ as any), format)
|
|
109
|
+
console.log(chalk.green(' Theme updated'))
|
|
110
|
+
output(data, format)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
themes
|
|
114
|
+
.command('delete <id>')
|
|
115
|
+
.description('Delete a theme')
|
|
116
|
+
.option('--yes', 'Skip confirmation')
|
|
117
|
+
.action(async (id: string, options: Record<string, unknown>) => {
|
|
118
|
+
if (!options.yes) {
|
|
119
|
+
const confirmed = await confirm({ message: `Delete theme #${id}?` })
|
|
120
|
+
if (isCancel(confirmed) || !confirmed) cancel('Cancelled')
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const client = await getClient()
|
|
124
|
+
await withSpinner('Deleting theme', () => client.themes.delete(Number(id)))
|
|
125
|
+
console.log(chalk.green(` Theme #${id} deleted`))
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
themes
|
|
129
|
+
.command('activate <id>')
|
|
130
|
+
.description('Activate or deactivate a theme')
|
|
131
|
+
.option('--active', 'Activate')
|
|
132
|
+
.option('--inactive', 'Deactivate')
|
|
133
|
+
.action(async (id: string, options: Record<string, unknown>) => {
|
|
134
|
+
const active = options.active ?? !options.inactive
|
|
135
|
+
const client = await getClient()
|
|
136
|
+
const format = getFormat(program)
|
|
137
|
+
const { data } = await withSpinner('Updating theme', () => client.themes.activate(Number(id), !!active), format)
|
|
138
|
+
console.log(chalk.green(` Theme ${active ? 'activated' : 'deactivated'}`))
|
|
139
|
+
output(data, format)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
themes
|
|
143
|
+
.command('default <id>')
|
|
144
|
+
.description('Set or unset a theme as default')
|
|
145
|
+
.option('--set', 'Set as default')
|
|
146
|
+
.option('--unset', 'Unset as default')
|
|
147
|
+
.action(async (id: string, options: Record<string, unknown>) => {
|
|
148
|
+
const isDefault = options.set ?? !options.unset
|
|
149
|
+
const client = await getClient()
|
|
150
|
+
const format = getFormat(program)
|
|
151
|
+
const { data } = await withSpinner('Updating theme', () => client.themes.setDefault(Number(id), !!isDefault), format)
|
|
152
|
+
console.log(chalk.green(` Theme ${isDefault ? 'set as default' : 'unset as default'}`))
|
|
153
|
+
output(data, format)
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
themes
|
|
157
|
+
.command('active')
|
|
158
|
+
.description('Get the currently active theme')
|
|
159
|
+
.option('--language <lang>', 'Language')
|
|
160
|
+
.action(async (options: Record<string, string>) => {
|
|
161
|
+
const client = await getClient()
|
|
162
|
+
const format = getFormat(program)
|
|
163
|
+
const { data } = await withSpinner('Fetching active theme', () =>
|
|
164
|
+
client.themes.getActive({ language: options.language }),
|
|
165
|
+
format,
|
|
166
|
+
)
|
|
167
|
+
if (!data) {
|
|
168
|
+
console.log(chalk.dim('No active theme'))
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
output(data, format)
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
themes
|
|
175
|
+
.command('feed <slug>')
|
|
176
|
+
.description('Get the curated feed for a theme')
|
|
177
|
+
.option('--language <lang>', 'Language')
|
|
178
|
+
.action(async (slug: string, options: Record<string, string>) => {
|
|
179
|
+
const client = await getClient()
|
|
180
|
+
const format = getFormat(program)
|
|
181
|
+
const { data } = await withSpinner('Fetching theme feed', () =>
|
|
182
|
+
client.themes.getFeed(slug, { language: options.language }),
|
|
183
|
+
format,
|
|
184
|
+
)
|
|
185
|
+
if (!data) {
|
|
186
|
+
console.log(chalk.dim('No feed data'))
|
|
187
|
+
return
|
|
188
|
+
}
|
|
189
|
+
output(data, format)
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
themes
|
|
193
|
+
.command('add-filter <id>')
|
|
194
|
+
.description('Add a content filter to a theme')
|
|
195
|
+
.option('--type <type>', 'Filter type (keyword|tag_name|author_name|reference_name|author_id|reference_id)')
|
|
196
|
+
.option('--value <value>', 'Filter value')
|
|
197
|
+
.option('--match-mode <mode>', 'Match mode (any|all)', 'any')
|
|
198
|
+
.action(async (id: string, options: Record<string, string>) => {
|
|
199
|
+
const client = await getClient()
|
|
200
|
+
const format = getFormat(program)
|
|
201
|
+
|
|
202
|
+
const type = options.type ?? await text({ message: 'Filter type', validate: (v) => !v ? 'Required' : undefined })
|
|
203
|
+
if (isCancel(type)) cancel('Cancelled')
|
|
204
|
+
|
|
205
|
+
const value = options.value ?? await text({ message: 'Filter value', validate: (v) => !v ? 'Required' : undefined })
|
|
206
|
+
if (isCancel(value)) cancel('Cancelled')
|
|
207
|
+
|
|
208
|
+
const { data } = await withSpinner('Adding filter', () =>
|
|
209
|
+
client.themes.addFilter(Number(id), {
|
|
210
|
+
type: type as string,
|
|
211
|
+
value: value as string,
|
|
212
|
+
match_mode: options.matchMode as 'any' | 'all' | undefined,
|
|
213
|
+
}),
|
|
214
|
+
format,
|
|
215
|
+
)
|
|
216
|
+
console.log(chalk.green(' Filter added'))
|
|
217
|
+
output(data, format)
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
themes
|
|
221
|
+
.command('remove-filter <id> <filterId>')
|
|
222
|
+
.description('Remove a filter from a theme')
|
|
223
|
+
.action(async (id: string, filterId: string) => {
|
|
224
|
+
const confirmed = await confirm({ message: `Remove filter #${filterId} from theme #${id}?` })
|
|
225
|
+
if (isCancel(confirmed) || !confirmed) cancel('Cancelled')
|
|
226
|
+
|
|
227
|
+
const client = await getClient()
|
|
228
|
+
await withSpinner('Removing filter', () => client.themes.removeFilter(Number(id), Number(filterId)))
|
|
229
|
+
console.log(chalk.green(` Filter #${filterId} removed`))
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
themes
|
|
233
|
+
.command('suggestions')
|
|
234
|
+
.description('Generate theme suggestions')
|
|
235
|
+
.option('--ai', 'Use AI generation')
|
|
236
|
+
.option('--tags <tags>', 'Seed tags (comma-separated)')
|
|
237
|
+
.option('--language <lang>', 'Language')
|
|
238
|
+
.action(async (options: Record<string, string>) => {
|
|
239
|
+
const client = await getClient()
|
|
240
|
+
const format = getFormat(program)
|
|
241
|
+
const { data } = await withSpinner('Generating suggestions', () =>
|
|
242
|
+
client.themes.suggestions({
|
|
243
|
+
ai: options.ai ? true : undefined,
|
|
244
|
+
tags: options.tags,
|
|
245
|
+
language: options.language,
|
|
246
|
+
}),
|
|
247
|
+
format,
|
|
248
|
+
)
|
|
249
|
+
output(data, format)
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
themes
|
|
253
|
+
.command('filter-suggestions <q>')
|
|
254
|
+
.description('Search filter values')
|
|
255
|
+
.requiredOption('--type <type>', 'Filter type (tag_name|author_name|reference_name|keyword|language)')
|
|
256
|
+
.action(async (q: string, options: Record<string, string>) => {
|
|
257
|
+
const client = await getClient()
|
|
258
|
+
const format = getFormat(program)
|
|
259
|
+
const { data } = await withSpinner('Fetching suggestions', () =>
|
|
260
|
+
client.themes.filterSuggestions({ q, type: options.type! }),
|
|
261
|
+
format,
|
|
262
|
+
)
|
|
263
|
+
output(data, format)
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
themes
|
|
267
|
+
.command('filter-recommendations')
|
|
268
|
+
.description('Get filter recommendations based on existing filters')
|
|
269
|
+
.option('--name <name>', 'Theme name')
|
|
270
|
+
.action(async (options: Record<string, string>) => {
|
|
271
|
+
const client = await getClient()
|
|
272
|
+
const format = getFormat(program)
|
|
273
|
+
const { data } = await withSpinner('Getting recommendations', () =>
|
|
274
|
+
client.themes.filterRecommendations({ name: options.name }),
|
|
275
|
+
format,
|
|
276
|
+
)
|
|
277
|
+
output(data, format)
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
themes
|
|
281
|
+
.command('suggest-name <name>')
|
|
282
|
+
.description('Generate an AI-powered theme name suggestion')
|
|
283
|
+
.action(async (name: string) => {
|
|
284
|
+
const client = await getClient()
|
|
285
|
+
const format = getFormat(program)
|
|
286
|
+
const { data } = await withSpinner('Generating name suggestion', () =>
|
|
287
|
+
client.themes.suggestName(name),
|
|
288
|
+
format,
|
|
289
|
+
)
|
|
290
|
+
output(data, format)
|
|
291
|
+
})
|
|
292
|
+
}
|