cli-snip-tool 1.0.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.
@@ -0,0 +1,440 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.registerAdd = registerAdd;
40
+ exports.registerFind = registerFind;
41
+ exports.registerList = registerList;
42
+ exports.registerCopy = registerCopy;
43
+ exports.registerDelete = registerDelete;
44
+ exports.registerExport = registerExport;
45
+ const inquirer_1 = __importDefault(require("inquirer"));
46
+ const chalk_1 = __importDefault(require("chalk"));
47
+ const clipboardy_1 = __importDefault(require("clipboardy"));
48
+ const utils_1 = require("./utils");
49
+ const cli_table3_1 = __importDefault(require("cli-table3"));
50
+ const cli_highlight_1 = require("cli-highlight");
51
+ const ora_1 = __importDefault(require("ora"));
52
+ const db_1 = require("./db");
53
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
54
+ const LANGUAGES = [
55
+ 'bash', 'typescript', 'javascript', 'python', 'sql',
56
+ 'css', 'html', 'json', 'yaml', 'dockerfile',
57
+ 'go', 'rust', 'plaintext',
58
+ ];
59
+ /** Render a single snippet in a pretty box. */
60
+ function printSnippet(s, { showCode = true } = {}) {
61
+ const header = chalk_1.default.bold.cyan(`#${s.id}`) + ' ' + chalk_1.default.bold(s.title);
62
+ const meta = [
63
+ chalk_1.default.dim('lang:') + ' ' + chalk_1.default.yellow(s.language),
64
+ s.tags.length ? chalk_1.default.dim('tags:') + ' ' + s.tags.map(t => chalk_1.default.green(`#${t}`)).join(' ') : '',
65
+ chalk_1.default.dim('copies:') + ' ' + chalk_1.default.magenta(String(s.use_count)),
66
+ ].filter(Boolean).join(' ');
67
+ console.log();
68
+ console.log(header);
69
+ console.log(chalk_1.default.dim(meta));
70
+ if (showCode) {
71
+ const highlighted = (0, cli_highlight_1.highlight)(s.code, { language: s.language });
72
+ const lines = highlighted.split('\n');
73
+ const preview = lines.slice(0, 6).map(l => ' ' + l).join('\n');
74
+ const truncated = lines.length > 6 ? chalk_1.default.dim(` … ${lines.length - 6} more lines`) : '';
75
+ console.log(preview);
76
+ if (truncated)
77
+ console.log(truncated);
78
+ }
79
+ if (s.description) {
80
+ console.log(chalk_1.default.dim(' ' + s.description));
81
+ }
82
+ }
83
+ /** Render a list of snippets in a clean, professional cli-table3 table */
84
+ function renderSnippetsTable(snippets) {
85
+ const table = new cli_table3_1.default({
86
+ head: [
87
+ chalk_1.default.bold.cyan('ID'),
88
+ chalk_1.default.bold.cyan('Title'),
89
+ chalk_1.default.bold.cyan('Language'),
90
+ chalk_1.default.bold.cyan('Tags'),
91
+ chalk_1.default.bold.cyan('Copies')
92
+ ],
93
+ colWidths: [6, 40, 15, 25, 10],
94
+ wordWrap: true,
95
+ });
96
+ for (const s of snippets) {
97
+ table.push([
98
+ chalk_1.default.bold(String(s.id)),
99
+ s.title,
100
+ chalk_1.default.yellow(s.language),
101
+ s.tags.map(t => chalk_1.default.green(`#${t}`)).join(', '),
102
+ chalk_1.default.magenta(String(s.use_count)),
103
+ ]);
104
+ }
105
+ console.log(table.toString());
106
+ }
107
+ // ─── snip add ────────────────────────────────────────────────────────────────
108
+ function registerAdd(program) {
109
+ program
110
+ .command('add [code...]')
111
+ .description('Save a new code snippet')
112
+ .option('-t, --title <title>', 'Snippet title (skip prompt)')
113
+ .option('-l, --language <language>', 'Programming language (skip prompt)')
114
+ .option('--tags <tags>', 'Comma-separated tags (skip prompt)')
115
+ .option('--stdin', 'Read code from stdin instead of opening editor')
116
+ .action(async (codeArgs, opts) => {
117
+ const codeFromArgs = codeArgs.join(' ').trim();
118
+ if (!codeFromArgs && !opts.stdin) {
119
+ console.log(chalk_1.default.cyan('💡 Tip: You can save short commands inline directly without opening the editor:'));
120
+ console.log(chalk_1.default.dim(' snip add -t "My Title" --tags "tag1,tag2" your command here\n'));
121
+ console.log(chalk_1.default.cyan('💡 Tip: If the command is too long, you can save it as a snippet:'));
122
+ console.log(chalk_1.default.dim(' snip add -title "My Title" --language "Language" --tags "tag1,tag2"\n'));
123
+ }
124
+ // ── Step 1: collect title ──────────────────────────────────────────────
125
+ let { title, language, tags: rawTags } = opts;
126
+ if (!title) {
127
+ const ans = await inquirer_1.default.prompt([{
128
+ type: 'input',
129
+ name: 'title',
130
+ message: 'Snippet title:',
131
+ validate: (v) => v.trim().length > 0 || 'Title cannot be empty',
132
+ }]);
133
+ title = ans.title.trim();
134
+ }
135
+ // ── Step 2: pick language ──────────────────────────────────────────────
136
+ const existingLangs = await (0, db_1.getAllLanguages)();
137
+ const langChoices = [
138
+ ...new Set([...LANGUAGES, ...existingLangs]),
139
+ ].sort();
140
+ if (!language) {
141
+ const ans = await inquirer_1.default.prompt([{
142
+ type: 'list',
143
+ name: 'language',
144
+ message: 'Language:',
145
+ choices: langChoices,
146
+ default: 'bash',
147
+ loop: false,
148
+ }]);
149
+ language = ans.language;
150
+ }
151
+ // ── Step 3: optional description ──────────────────────────────────────
152
+ const { description } = await inquirer_1.default.prompt([{
153
+ type: 'input',
154
+ name: 'description',
155
+ message: 'Description (optional, press Enter to skip):',
156
+ }]);
157
+ // ── Step 4: tags ──────────────────────────────────────────────────────
158
+ let tags = [];
159
+ if (rawTags) {
160
+ tags = rawTags.split(',').map((t) => t.trim()).filter(Boolean);
161
+ }
162
+ else {
163
+ const existingTags = await (0, db_1.getAllTags)();
164
+ if (existingTags.length > 0) {
165
+ // Let the user pick from existing tags AND type new ones
166
+ const { selectedTags } = await inquirer_1.default.prompt([{
167
+ type: 'checkbox',
168
+ name: 'selectedTags',
169
+ message: 'Select existing tags (Space to toggle):',
170
+ choices: existingTags,
171
+ pageSize: 10,
172
+ }]);
173
+ tags.push(...selectedTags);
174
+ }
175
+ const { newTags } = await inquirer_1.default.prompt([{
176
+ type: 'input',
177
+ name: 'newTags',
178
+ message: 'Add new tags (comma-separated, or Enter to skip):',
179
+ }]);
180
+ if (newTags.trim()) {
181
+ tags.push(...newTags.split(',').map((t) => t.trim()).filter(Boolean));
182
+ }
183
+ }
184
+ // ── Step 5: get the code ──────────────────────────────────────────────
185
+ let code;
186
+ if (codeFromArgs) {
187
+ code = codeFromArgs;
188
+ }
189
+ else if (opts.stdin) {
190
+ // Read from piped stdin: cat file.sh | snip add --stdin
191
+ code = '';
192
+ process.stdin.setEncoding('utf8');
193
+ for await (const chunk of process.stdin)
194
+ code += chunk;
195
+ code = code.trim();
196
+ }
197
+ else {
198
+ console.log(chalk_1.default.dim('\nOpening your editor — save and close when done…'));
199
+ const placeholder = `# Paste your ${language} snippet here\n`;
200
+ code = (0, utils_1.openInEditor)(placeholder);
201
+ }
202
+ if (!code) {
203
+ console.log(chalk_1.default.red('No code provided. Snippet not saved.'));
204
+ process.exit(1);
205
+ }
206
+ // ── Step 6: confirm & save ────────────────────────────────────────────
207
+ const { confirm } = await inquirer_1.default.prompt([{
208
+ type: 'confirm',
209
+ name: 'confirm',
210
+ message: `Save snippet "${chalk_1.default.bold(title)}"?`,
211
+ default: true,
212
+ }]);
213
+ if (!confirm) {
214
+ console.log(chalk_1.default.yellow('Cancelled.'));
215
+ return;
216
+ }
217
+ const snippet = (0, db_1.createSnippet)({
218
+ title,
219
+ code,
220
+ language,
221
+ description: description.trim() || undefined,
222
+ tags,
223
+ });
224
+ console.log();
225
+ console.log(chalk_1.default.green('✔ Snippet saved!'));
226
+ printSnippet(snippet);
227
+ console.log(chalk_1.default.dim(`\n Run ${chalk_1.default.cyan(`snip copy ${snippet.id}`)} to copy it.\n`));
228
+ });
229
+ }
230
+ // ─── snip find ───────────────────────────────────────────────────────────────
231
+ function registerFind(program) {
232
+ program
233
+ .command('find [query]')
234
+ .description('Search snippets by text, tag, or language')
235
+ .option('--tag <tag>', 'Filter by tag')
236
+ .option('--language <language>', 'Filter by language')
237
+ .option('--limit <n>', 'Max results to show', '10')
238
+ .option('--copy', 'Copy the top result straight to clipboard')
239
+ .action(async (query, opts) => {
240
+ const results = (0, db_1.searchSnippets)({
241
+ query: query?.trim(),
242
+ tag: opts.tag,
243
+ language: opts.language,
244
+ limit: parseInt(opts.limit, 10),
245
+ });
246
+ // ── No results ────────────────────────────────────────────────────────
247
+ if (results.length === 0) {
248
+ console.log();
249
+ console.log(chalk_1.default.yellow(' No snippets found.'));
250
+ const filters = [
251
+ query && `query "${query}"`,
252
+ opts.tag && `tag #${opts.tag}`,
253
+ opts.language && `language ${opts.language}`,
254
+ ].filter(Boolean).join(', ');
255
+ if (filters)
256
+ console.log(chalk_1.default.dim(` Searched for: ${filters}`));
257
+ console.log(chalk_1.default.dim(` Try: ${chalk_1.default.cyan('snip list')} to see all snippets.\n`));
258
+ return;
259
+ }
260
+ // ── --copy flag: skip the picker, just copy result #1 ─────────────────
261
+ if (opts.copy) {
262
+ const top = results[0];
263
+ const spinner = (0, ora_1.default)('Copying to clipboard...').start();
264
+ try {
265
+ clipboardy_1.default.writeSync(top.code);
266
+ (0, db_1.incrementUseCount)(top.id);
267
+ spinner.succeed(chalk_1.default.green(`Copied "${top.title}" to clipboard.`));
268
+ }
269
+ catch (err) {
270
+ spinner.fail(chalk_1.default.red(`Failed to copy: ${err.message}`));
271
+ }
272
+ return;
273
+ }
274
+ // ── Show results list ─────────────────────────────────────────────────
275
+ console.log();
276
+ console.log(chalk_1.default.bold(` ${results.length} result${results.length === 1 ? '' : 's'}`) +
277
+ (query ? chalk_1.default.dim(` for "${query}"`) : ''));
278
+ results.forEach(s => printSnippet(s, { showCode: false }));
279
+ // ── Interactive picker ────────────────────────────────────────────────
280
+ console.log();
281
+ const { chosenId } = await inquirer_1.default.prompt([{
282
+ type: 'list',
283
+ name: 'chosenId',
284
+ message: 'Select a snippet:',
285
+ choices: [
286
+ ...results.map(s => ({
287
+ name: `#${s.id} ${s.title} ${chalk_1.default.dim(`[${s.language}]`)}`,
288
+ value: s.id,
289
+ })),
290
+ new inquirer_1.default.Separator(),
291
+ { name: chalk_1.default.dim('Cancel'), value: null },
292
+ ],
293
+ loop: false,
294
+ pageSize: 12,
295
+ }]);
296
+ if (!chosenId)
297
+ return;
298
+ const chosen = results.find(s => s.id === chosenId);
299
+ // ── Action menu ───────────────────────────────────────────────────────
300
+ const { action } = await inquirer_1.default.prompt([{
301
+ type: 'list',
302
+ name: 'action',
303
+ message: `What do you want to do with "${chalk_1.default.bold(chosen.title)}"?`,
304
+ choices: [
305
+ { name: '📋 Copy to clipboard', value: 'copy' },
306
+ { name: '👁 Preview full code', value: 'preview' },
307
+ { name: '✏️ Edit snippet', value: 'edit' },
308
+ { name: chalk_1.default.dim('Cancel'), value: null },
309
+ ],
310
+ }]);
311
+ if (!action)
312
+ return;
313
+ // ── Handle chosen action ──────────────────────────────────────────────
314
+ if (action === 'copy') {
315
+ const spinner = (0, ora_1.default)('Copying to clipboard...').start();
316
+ try {
317
+ clipboardy_1.default.writeSync(chosen.code);
318
+ (0, db_1.incrementUseCount)(chosen.id);
319
+ spinner.succeed(chalk_1.default.green('Copied to clipboard!\n'));
320
+ }
321
+ catch (err) {
322
+ spinner.fail(chalk_1.default.red(`Failed to copy: ${err.message}\n`));
323
+ }
324
+ }
325
+ if (action === 'preview') {
326
+ console.log();
327
+ console.log(chalk_1.default.bold.underline(chosen.title));
328
+ console.log(chalk_1.default.dim(`${chosen.language} ${chosen.tags.map(t => '#' + t).join(' ')}`));
329
+ console.log();
330
+ const highlighted = (0, cli_highlight_1.highlight)(chosen.code, { language: chosen.language });
331
+ highlighted.split('\n').forEach(line => console.log(' ' + line));
332
+ console.log();
333
+ }
334
+ if (action === 'edit') {
335
+ // Delegate to `snip edit <id>` — keeps concerns separate
336
+ const { spawnSync: spawn } = await Promise.resolve().then(() => __importStar(require('child_process')));
337
+ spawn(process.argv[0], [process.argv[1], 'edit', String(chosen.id)], {
338
+ stdio: 'inherit',
339
+ });
340
+ }
341
+ });
342
+ }
343
+ // ─── snip list ───────────────────────────────────────────────────────────────
344
+ function registerList(program) {
345
+ program
346
+ .command('list')
347
+ .description('List all saved snippets')
348
+ .option('--tag <tag>', 'Filter by tag')
349
+ .option('--language <language>', 'Filter by language')
350
+ .action((opts) => {
351
+ const results = (0, db_1.searchSnippets)({
352
+ tag: opts.tag,
353
+ language: opts.language,
354
+ limit: 1000, // Show a reasonable bulk limit
355
+ });
356
+ if (results.length === 0) {
357
+ console.log();
358
+ console.log(chalk_1.default.yellow(' No snippets found.'));
359
+ return;
360
+ }
361
+ console.log();
362
+ renderSnippetsTable(results);
363
+ console.log(chalk_1.default.dim(` Total: ${results.length} snippet(s)\n`));
364
+ });
365
+ }
366
+ // ─── snip copy ───────────────────────────────────────────────────────────────
367
+ function registerCopy(program) {
368
+ program
369
+ .command('copy <id>')
370
+ .description('Copy a snippet by ID to clipboard')
371
+ .action(async (idStr) => {
372
+ const id = parseInt(idStr, 10);
373
+ if (isNaN(id)) {
374
+ console.error(chalk_1.default.red('Error: ID must be a number'));
375
+ process.exit(1);
376
+ }
377
+ const snippet = (0, db_1.getSnippetById)(id);
378
+ if (!snippet) {
379
+ console.error(chalk_1.default.red(`Error: Snippet #${id} not found`));
380
+ process.exit(1);
381
+ }
382
+ const spinner = (0, ora_1.default)('Copying to clipboard...').start();
383
+ try {
384
+ clipboardy_1.default.writeSync(snippet.code);
385
+ (0, db_1.incrementUseCount)(snippet.id);
386
+ spinner.succeed(chalk_1.default.green(`Copied "${snippet.title}" to clipboard!`));
387
+ }
388
+ catch (err) {
389
+ spinner.fail(chalk_1.default.red(`Failed to copy: ${err.message}`));
390
+ }
391
+ });
392
+ }
393
+ // ─── snip delete ─────────────────────────────────────────────────────────────
394
+ function registerDelete(program) {
395
+ program
396
+ .command('delete <id>')
397
+ .description('Delete a snippet by ID')
398
+ .action((idStr) => {
399
+ const id = parseInt(idStr, 10);
400
+ if (isNaN(id)) {
401
+ console.error(chalk_1.default.red('Error: ID must be a number'));
402
+ process.exit(1);
403
+ }
404
+ const deleted = (0, db_1.deleteSnippet)(id);
405
+ if (deleted) {
406
+ console.log(chalk_1.default.green(`✔ Snippet #${id} deleted successfully.`));
407
+ }
408
+ else {
409
+ console.log(chalk_1.default.yellow(`⚠ Snippet #${id} not found.`));
410
+ }
411
+ });
412
+ }
413
+ // ─── snip export ─────────────────────────────────────────────────────────────
414
+ function registerExport(program) {
415
+ program
416
+ .command('export')
417
+ .description('Export all snippets')
418
+ .option('-f, --format <format>', 'Export format: json or markdown', 'json')
419
+ .action((opts) => {
420
+ const snippets = (0, db_1.exportAll)();
421
+ const format = opts.format.toLowerCase();
422
+ if (format === 'markdown' || format === 'md') {
423
+ let output = '# Snippets Export\n\n';
424
+ for (const s of snippets) {
425
+ output += `## ${s.title}\n`;
426
+ if (s.description)
427
+ output += `*Description: ${s.description}*\n`;
428
+ output += `*Language: ${s.language}*\n`;
429
+ if (s.tags.length)
430
+ output += `*Tags: ${s.tags.map(t => '#' + t).join(' ')}*\n`;
431
+ output += `\n\`\`\`${s.language}\n${s.code}\n\`\`\`\n\n`;
432
+ }
433
+ console.log(output);
434
+ }
435
+ else {
436
+ // Default to JSON
437
+ console.log(JSON.stringify(snippets, null, 2));
438
+ }
439
+ });
440
+ }