lumbung-cli 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.
package/README.md ADDED
@@ -0,0 +1,255 @@
1
+ # 🖥️ lumbung CLI
2
+
3
+ Command Line Interface untuk **lumbung - Modern Snippet Manager**.
4
+
5
+ Upload, cari, dan kelola snippet kode langsung dari terminal Anda.
6
+
7
+ ---
8
+
9
+ ## 📦 Instalasi
10
+
11
+ ### Prasyarat
12
+ - Node.js versi 18 atau lebih baru
13
+ - Akun lumbung (daftar di https://lumbungkode.netlify.app)
14
+
15
+ ### Install dari Source
16
+
17
+ ```bash
18
+ # Clone repository
19
+ cd snippet-manager/cli
20
+
21
+ # Install dependencies
22
+ npm install
23
+
24
+ # Link secara global
25
+ npm link
26
+
27
+ # Sekarang bisa digunakan dari mana saja
28
+ lumbung --help
29
+ ```
30
+
31
+ ---
32
+
33
+ ## 🚀 Panduan Penggunaan
34
+
35
+ ### 1. Login ke Akun Anda
36
+
37
+ Sebelum menggunakan CLI, login terlebih dahulu:
38
+
39
+ ```bash
40
+ lumbung login
41
+ ```
42
+
43
+ Masukkan email dan password akun lumbung Anda. Kredensial akan disimpan secara lokal.
44
+
45
+ Cek status login:
46
+ ```bash
47
+ lumbung whoami
48
+ ```
49
+
50
+ ### 2. Upload Snippet dari File
51
+
52
+ ```bash
53
+ # Upload file dengan auto-detect title dan language
54
+ lumbung push myfile.js
55
+
56
+ # Upload dengan title custom
57
+ lumbung push script.py --title "Python Script"
58
+
59
+ # Upload sebagai public snippet
60
+ lumbung push helper.ts --public
61
+
62
+ # Upload dengan metadata lengkap
63
+ lumbung push api.go \
64
+ --title "Go REST API" \
65
+ --description "Simple REST endpoint" \
66
+ --tags "go,rest,api" \
67
+ --public
68
+ ```
69
+
70
+ ### 3. Lihat Daftar Snippet Anda
71
+
72
+ ```bash
73
+ # List semua snippet
74
+ lumbung list
75
+
76
+ # Filter by language
77
+ lumbung list --language python
78
+
79
+ # Limit hasil
80
+ lumbung list --limit 5
81
+
82
+ # Output sebagai JSON
83
+ lumbung list --json
84
+ ```
85
+
86
+ ### 4. Ambil Snippet by ID
87
+
88
+ ```bash
89
+ # Tampilkan snippet di terminal
90
+ lumbung get abc123
91
+
92
+ # Simpan ke file
93
+ lumbung get abc123 --output ./download/snippet.js
94
+
95
+ # Copy ke clipboard
96
+ lumbung get abc123 --copy
97
+ ```
98
+
99
+ ### 5. Cari Snippet
100
+
101
+ ```bash
102
+ # Search dengan kata kunci
103
+ lumbung search "react hooks"
104
+
105
+ # Search hanya snippet milik sendiri
106
+ lumbung search "useEffect" --mine
107
+
108
+ # Search snippet public
109
+ lumbung search "authentication" --public
110
+
111
+ # Filter by language
112
+ lumbung search "api" --language python
113
+ ```
114
+
115
+ ### 6. Logout
116
+
117
+ ```bash
118
+ lumbung logout
119
+ ```
120
+
121
+ ---
122
+
123
+ ## 📋 Referensi Command
124
+
125
+ | Command | Deskripsi |
126
+ |---------|-----------|
127
+ | `lumbung login` | Login ke akun lumbung |
128
+ | `lumbung logout` | Logout dari akun |
129
+ | `lumbung whoami` | Tampilkan user yang sedang login |
130
+ | `lumbung push <file>` | Upload file sebagai snippet |
131
+ | `lumbung get <id>` | Ambil snippet berdasarkan ID |
132
+ | `lumbung list` | Daftar snippet milik Anda |
133
+ | `lumbung search <query>` | Cari snippet dengan full-text search |
134
+
135
+ ---
136
+
137
+ ## 🎯 Options
138
+
139
+ ### `push` Options
140
+ | Option | Deskripsi |
141
+ |--------|-----------|
142
+ | `-t, --title <title>` | Judul snippet |
143
+ | `-l, --language <lang>` | Bahasa pemrograman |
144
+ | `-d, --description <desc>` | Deskripsi snippet |
145
+ | `--tags <tags>` | Tags (pisahkan dengan koma) |
146
+ | `--public` | Jadikan snippet public |
147
+
148
+ ### `get` Options
149
+ | Option | Deskripsi |
150
+ |--------|-----------|
151
+ | `-o, --output <file>` | Simpan ke file |
152
+ | `-c, --copy` | Copy ke clipboard |
153
+
154
+ ### `list` Options
155
+ | Option | Deskripsi |
156
+ |--------|-----------|
157
+ | `-l, --language <lang>` | Filter by language |
158
+ | `-n, --limit <num>` | Limit hasil (default: 10) |
159
+ | `--public` | Hanya snippet public |
160
+ | `--json` | Output sebagai JSON |
161
+
162
+ ### `search` Options
163
+ | Option | Deskripsi |
164
+ |--------|-----------|
165
+ | `-l, --language <lang>` | Filter by language |
166
+ | `-n, --limit <num>` | Limit hasil (default: 10) |
167
+ | `--mine` | Hanya snippet milik sendiri |
168
+ | `--public` | Hanya snippet public |
169
+
170
+ ---
171
+
172
+ ## 🌐 Bahasa yang Didukung
173
+
174
+ CLI mendukung **semua bahasa pemrograman**. Auto-detect berdasarkan extension:
175
+
176
+ | Extension | Language |
177
+ |-----------|----------|
178
+ | `.js`, `.jsx` | JavaScript |
179
+ | `.ts`, `.tsx` | TypeScript |
180
+ | `.py` | Python |
181
+ | `.java` | Java |
182
+ | `.cpp`, `.c` | C/C++ |
183
+ | `.go` | Go |
184
+ | `.rs` | Rust |
185
+ | `.rb` | Ruby |
186
+ | `.php` | PHP |
187
+ | `.html` | HTML |
188
+ | `.css`, `.scss` | CSS |
189
+ | `.sql` | SQL |
190
+ | `.md` | Markdown |
191
+ | `.json` | JSON |
192
+ | `.yaml`, `.yml` | YAML |
193
+ | Dan lainnya... | Auto-detect |
194
+
195
+ ---
196
+
197
+ ## 💡 Contoh Penggunaan
198
+
199
+ ### Workflow Harian
200
+
201
+ ```bash
202
+ # Pagi: Login
203
+ lumbung login
204
+
205
+ # Simpan function baru yang dibuat
206
+ lumbung push ./src/utils/debounce.js --tags "utility,hooks"
207
+
208
+ # Cari snippet lama
209
+ lumbung search "validation"
210
+
211
+ # Ambil snippet dan gunakan
212
+ lumbung get xyz789 --output ./temp/snippet.js
213
+
214
+ # Sore: Check semua snippet hari ini
215
+ lumbung list --limit 20
216
+ ```
217
+
218
+ ### Scripting & Automation
219
+
220
+ ```bash
221
+ # Export semua snippet sebagai JSON
222
+ lumbung list --json > my-snippets.json
223
+
224
+ # Batch upload
225
+ for file in ./src/**/*.js; do
226
+ lumbung push "$file" --public
227
+ done
228
+ ```
229
+
230
+ ---
231
+
232
+ ## ❓ Troubleshooting
233
+
234
+ ### "Not logged in"
235
+ Jalankan `lumbung login` terlebih dahulu.
236
+
237
+ ### "Snippet not found"
238
+ Pastikan ID snippet benar. Gunakan `lumbung list` untuk melihat ID snippet Anda.
239
+
240
+ ### "Search failed"
241
+ Query minimal 3 karakter. Pastikan koneksi internet aktif.
242
+
243
+ ### Credential issues
244
+ Hapus credential dan login ulang:
245
+ ```bash
246
+ lumbung logout
247
+ lumbung login
248
+ ```
249
+
250
+ ---
251
+
252
+ ## 📄 License
253
+
254
+ MIT License - lumbung Team
255
+
package/bin/lumbung.js ADDED
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Lumbung CLI - Command Line Interface for Lumbung Kode
5
+ *
6
+ * Usage:
7
+ * lumbung login Login to your account
8
+ * lumbung push <file> Upload a snippet from file
9
+ * lumbung get <id> Fetch a snippet by ID
10
+ * lumbung list List your snippets
11
+ * lumbung search <query> Search snippets
12
+ * lumbung logout Logout from your account
13
+ *
14
+ * Run 'lumbung --help' for more information.
15
+ */
16
+
17
+ import { program } from 'commander'
18
+ import chalk from 'chalk'
19
+ import { login, logout } from '../src/commands/auth.js'
20
+ import { push } from '../src/commands/push.js'
21
+ import { get } from '../src/commands/get.js'
22
+ import { list } from '../src/commands/list.js'
23
+ import { search } from '../src/commands/search.js'
24
+ import { getConfig } from '../src/lib/config.js'
25
+
26
+ // ASCII Art Banner
27
+ const banner = `
28
+ ╔════════════════════════════════════════════════════════════════════════════════╗
29
+ ║ ║
30
+ ║ ██╗ ██╗ ██╗███╗ ███╗██████╗ ██╗ ██╗███╗ ██╗ ██████╗ ║
31
+ ║ ██║ ██║ ██║████╗ ████║██╔══██╗██║ ██║████╗ ██║██╔════╝ ║
32
+ ║ ██║ ██║ ██║██╔████╔██║██████╔╝██║ ██║██╔██╗ ██║██║ ███╗ ║
33
+ ║ ██║ ██║ ██║██║╚██╔╝██║██╔══██╗██║ ██║██║╚██╗██║██║ ██║ ║
34
+ ║ ███████╗╚██████╔╝██║ ╚═╝ ██║██████╔╝╚██████╔╝██║ ╚████║╚██████╔╝ ║
35
+ ║ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═════╝ ╚═════╝ ╚═╝ ╚═══╝ ╚══════╝ ║
36
+ ║ ║
37
+ ║ Gudang Snippet Kode Modern - CLI Tool ║
38
+ ║ ║
39
+ ╚════════════════════════════════════════════════════════════════════════════════╝
40
+ `
41
+
42
+ program
43
+ .name('lumbung')
44
+ .description('CLI tool for Lumbung Kode - Gudang Snippet Kode Modern')
45
+ .version('1.0.0')
46
+ .hook('preAction', (thisCommand) => {
47
+ // Only show banner for main help
48
+ if (thisCommand.args.length === 0 && !process.argv.slice(2).length) {
49
+ console.log(chalk.cyan(banner))
50
+ }
51
+ })
52
+
53
+ // Login command
54
+ program
55
+ .command('login')
56
+ .description('Login to your Lumbung Kode account')
57
+ .action(login)
58
+
59
+ // Logout command
60
+ program
61
+ .command('logout')
62
+ .description('Logout from your account')
63
+ .action(logout)
64
+
65
+ // Push command
66
+ program
67
+ .command('push <file>')
68
+ .description('Upload a snippet from a file')
69
+ .option('-t, --title <title>', 'Snippet title (auto-detected if not provided)')
70
+ .option('-l, --language <language>', 'Programming language (auto-detected from extension)')
71
+ .option('-d, --description <description>', 'Snippet description')
72
+ .option('--tags <tags>', 'Comma-separated tags')
73
+ .option('--public', 'Make snippet public', false)
74
+ .action(push)
75
+
76
+ // Get command
77
+ program
78
+ .command('get <id>')
79
+ .description('Fetch a snippet by ID')
80
+ .option('-o, --output <file>', 'Save to file')
81
+ .option('-c, --copy', 'Copy to clipboard')
82
+ .action(get)
83
+
84
+ // List command
85
+ program
86
+ .command('list')
87
+ .description('List your snippets')
88
+ .option('-l, --language <language>', 'Filter by language')
89
+ .option('-n, --limit <number>', 'Limit results', '10')
90
+ .option('--public', 'Show only public snippets')
91
+ .option('--json', 'Output as JSON')
92
+ .action(list)
93
+
94
+ // Search command
95
+ program
96
+ .command('search <query>')
97
+ .description('Search snippets using full-text search')
98
+ .option('-l, --language <language>', 'Filter by language')
99
+ .option('-n, --limit <number>', 'Limit results', '10')
100
+ .option('--mine', 'Search only my snippets')
101
+ .option('--public', 'Search only public snippets')
102
+ .action(search)
103
+
104
+ // Whoami command (check login status)
105
+ program
106
+ .command('whoami')
107
+ .description('Show current logged in user')
108
+ .action(() => {
109
+ const config = getConfig()
110
+ if (config.email) {
111
+ console.log(chalk.green(`✓ Logged in as: ${chalk.bold(config.email)}`))
112
+ } else {
113
+ console.log(chalk.yellow('Not logged in. Run: lumbung login'))
114
+ }
115
+ })
116
+
117
+ // Parse and execute
118
+ program.parse()
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "lumbung-cli",
3
+ "version": "1.0.0",
4
+ "description": "CLI tool for Lumbung Kode - Gudang Snippet Kode Modern",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "bin": {
8
+ "lumbung": "./bin/lumbung.js"
9
+ },
10
+ "scripts": {
11
+ "start": "node bin/lumbung.js",
12
+ "link": "npm link"
13
+ },
14
+ "keywords": [
15
+ "cli",
16
+ "snippet",
17
+ "code",
18
+ "manager",
19
+ "lumbung",
20
+ "lumbung-kode",
21
+ "snippet-manager"
22
+ ],
23
+ "author": "Sofyan (sofyan2108)",
24
+ "license": "MIT",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "https://github.com/sofyan2108/lumbung-kode-ta.git"
28
+ },
29
+ "homepage": "https://github.com/sofyan2108/lumbung-kode-ta#readme",
30
+ "dependencies": {
31
+ "@supabase/supabase-js": "^2.86.0",
32
+ "chalk": "^5.3.0",
33
+ "commander": "^12.1.0",
34
+ "conf": "^13.0.1",
35
+ "inquirer": "^9.3.7",
36
+ "ora": "^8.1.1"
37
+ }
38
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Auth Commands - Login & Logout
3
+ */
4
+
5
+ import inquirer from 'inquirer'
6
+ import chalk from 'chalk'
7
+ import ora from 'ora'
8
+ import { loginWithEmail } from '../lib/api.js'
9
+ import { saveCredentials, clearCredentials, isLoggedIn, getConfig } from '../lib/config.js'
10
+
11
+ export async function login() {
12
+ // Check if already logged in
13
+ if (isLoggedIn()) {
14
+ const { email } = getConfig()
15
+ console.log(chalk.yellow(`Already logged in as ${chalk.bold(email)}`))
16
+
17
+ const { confirm } = await inquirer.prompt([{
18
+ type: 'confirm',
19
+ name: 'confirm',
20
+ message: 'Do you want to login with a different account?',
21
+ default: false
22
+ }])
23
+
24
+ if (!confirm) return
25
+ }
26
+
27
+ console.log(chalk.cyan('\n📧 Login to Lumbung Kode\n'))
28
+
29
+ // Prompt for credentials
30
+ const answers = await inquirer.prompt([
31
+ {
32
+ type: 'input',
33
+ name: 'email',
34
+ message: 'Email:',
35
+ validate: (input) => {
36
+ if (!input.includes('@')) return 'Please enter a valid email'
37
+ return true
38
+ }
39
+ },
40
+ {
41
+ type: 'password',
42
+ name: 'password',
43
+ message: 'Password:',
44
+ mask: '*',
45
+ validate: (input) => {
46
+ if (input.length < 6) return 'Password must be at least 6 characters'
47
+ return true
48
+ }
49
+ }
50
+ ])
51
+
52
+ const spinner = ora('Logging in...').start()
53
+
54
+ try {
55
+ const { accessToken, refreshToken, user } = await loginWithEmail(
56
+ answers.email,
57
+ answers.password
58
+ )
59
+
60
+ // Save credentials locally
61
+ saveCredentials(accessToken, refreshToken, user.email, user.id)
62
+
63
+ spinner.succeed(chalk.green(`Logged in as ${chalk.bold(user.email)}`))
64
+ console.log(chalk.gray(`\nUser ID: ${user.id}`))
65
+ console.log(chalk.gray('Credentials saved to local config.\n'))
66
+
67
+ } catch (error) {
68
+ spinner.fail(chalk.red('Login failed'))
69
+ console.log(chalk.red(`Error: ${error.message}`))
70
+ console.log(chalk.gray('\nMake sure your email and password are correct.'))
71
+ console.log(chalk.gray('If you don\'t have an account, register at https://Lumbung Kode.app'))
72
+ }
73
+ }
74
+
75
+ export async function logout() {
76
+ if (!isLoggedIn()) {
77
+ console.log(chalk.yellow('Not logged in.'))
78
+ return
79
+ }
80
+
81
+ const { email } = getConfig()
82
+
83
+ const { confirm } = await inquirer.prompt([{
84
+ type: 'confirm',
85
+ name: 'confirm',
86
+ message: `Logout from ${email}?`,
87
+ default: true
88
+ }])
89
+
90
+ if (confirm) {
91
+ clearCredentials()
92
+ console.log(chalk.green('✓ Logged out successfully.'))
93
+ }
94
+ }
95
+
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Get Command - Fetch snippet by ID
3
+ */
4
+
5
+ import fs from 'fs'
6
+ import chalk from 'chalk'
7
+ import ora from 'ora'
8
+ import { fetchSnippetById } from '../lib/api.js'
9
+
10
+ export async function get(id, options) {
11
+ const spinner = ora('Fetching snippet...').start()
12
+
13
+ try {
14
+ const snippet = await fetchSnippetById(id)
15
+
16
+ if (!snippet) {
17
+ spinner.fail(chalk.red('Snippet not found'))
18
+ return
19
+ }
20
+
21
+ spinner.succeed(chalk.green('Snippet fetched!'))
22
+
23
+ // Save to file if --output specified
24
+ if (options.output) {
25
+ fs.writeFileSync(options.output, snippet.code)
26
+ console.log(chalk.green(`\n✓ Saved to: ${options.output}`))
27
+ return
28
+ }
29
+
30
+ // Copy to clipboard if --copy specified
31
+ if (options.copy) {
32
+ try {
33
+ const { execSync } = await import('child_process')
34
+
35
+ // Cross-platform clipboard
36
+ if (process.platform === 'win32') {
37
+ execSync(`echo ${snippet.code.replace(/"/g, '\\"')} | clip`, { stdio: 'ignore' })
38
+ } else if (process.platform === 'darwin') {
39
+ execSync(`echo "${snippet.code}" | pbcopy`, { stdio: 'ignore' })
40
+ } else {
41
+ execSync(`echo "${snippet.code}" | xclip -selection clipboard`, { stdio: 'ignore' })
42
+ }
43
+
44
+ console.log(chalk.green('\n✓ Copied to clipboard!'))
45
+ } catch (e) {
46
+ console.log(chalk.yellow('\n⚠ Could not copy to clipboard'))
47
+ }
48
+ }
49
+
50
+ // Display snippet info and code
51
+ console.log('')
52
+ console.log(chalk.gray('═'.repeat(60)))
53
+ console.log(chalk.bold.cyan(`📝 ${snippet.title}`))
54
+ console.log(chalk.gray('═'.repeat(60)))
55
+ console.log(`${chalk.gray('Language:')} ${chalk.yellow(snippet.language)}`)
56
+ console.log(`${chalk.gray('Public:')} ${snippet.is_public ? '🌍 Yes' : '🔒 No'}`)
57
+ if (snippet.description) {
58
+ console.log(`${chalk.gray('Desc:')} ${snippet.description}`)
59
+ }
60
+ if (snippet.tags && snippet.tags.length > 0) {
61
+ console.log(`${chalk.gray('Tags:')} ${snippet.tags.map(t => chalk.blue(`#${t}`)).join(' ')}`)
62
+ }
63
+ console.log(chalk.gray('─'.repeat(60)))
64
+ console.log('')
65
+ console.log(snippet.code)
66
+ console.log('')
67
+ console.log(chalk.gray('─'.repeat(60)))
68
+ console.log(chalk.gray(`Stats: ❤️ ${snippet.like_count || 0} likes | 📋 ${snippet.copy_count || 0} copies`))
69
+
70
+ } catch (error) {
71
+ spinner.fail(chalk.red('Failed to fetch snippet'))
72
+ console.log(chalk.red(`Error: ${error.message}`))
73
+ }
74
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * List Command - List user snippets
3
+ */
4
+
5
+ import chalk from 'chalk'
6
+ import ora from 'ora'
7
+ import { fetchMySnippets } from '../lib/api.js'
8
+ import { isLoggedIn } from '../lib/config.js'
9
+
10
+ export async function list(options) {
11
+ // Check login
12
+ if (!isLoggedIn()) {
13
+ console.log(chalk.red('✗ Not logged in. Run: codehaven login'))
14
+ return
15
+ }
16
+
17
+ const spinner = ora('Fetching your snippets...').start()
18
+
19
+ try {
20
+ const snippets = await fetchMySnippets({
21
+ language: options.language,
22
+ limit: options.limit,
23
+ isPublic: options.public ? true : undefined
24
+ })
25
+
26
+ spinner.stop()
27
+
28
+ if (snippets.length === 0) {
29
+ console.log(chalk.yellow('\n📭 No snippets found.'))
30
+ console.log(chalk.gray('Create your first snippet: codehaven push <file>'))
31
+ return
32
+ }
33
+
34
+ // JSON output
35
+ if (options.json) {
36
+ console.log(JSON.stringify(snippets, null, 2))
37
+ return
38
+ }
39
+
40
+ // Table output
41
+ console.log('')
42
+ console.log(chalk.bold.cyan(`📚 Your Snippets (${snippets.length})`))
43
+ console.log(chalk.gray('─'.repeat(80)))
44
+ console.log(
45
+ chalk.gray('ID'.padEnd(38)) +
46
+ chalk.gray('Title'.padEnd(25)) +
47
+ chalk.gray('Lang'.padEnd(12)) +
48
+ chalk.gray('Vis')
49
+ )
50
+ console.log(chalk.gray('─'.repeat(80)))
51
+
52
+ snippets.forEach(s => {
53
+ const visibility = s.is_public ? chalk.green('🌍') : chalk.gray('🔒')
54
+ const truncatedTitle = s.title.length > 22 ? s.title.slice(0, 22) + '...' : s.title
55
+
56
+ console.log(
57
+ chalk.gray(s.id.padEnd(38)) +
58
+ chalk.white(truncatedTitle.padEnd(25)) +
59
+ chalk.yellow(s.language.padEnd(12)) +
60
+ visibility
61
+ )
62
+ })
63
+
64
+ console.log(chalk.gray('─'.repeat(80)))
65
+ console.log(chalk.gray(`\nShowing ${snippets.length} snippets. Use --limit to see more.`))
66
+ console.log(chalk.gray('View snippet: codehaven get <id>'))
67
+
68
+ } catch (error) {
69
+ spinner.fail(chalk.red('Failed to fetch snippets'))
70
+ console.log(chalk.red(`Error: ${error.message}`))
71
+ }
72
+ }
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Push Command - Upload snippet from file
3
+ */
4
+
5
+ import fs from 'fs'
6
+ import path from 'path'
7
+ import chalk from 'chalk'
8
+ import ora from 'ora'
9
+ import { createSnippet } from '../lib/api.js'
10
+ import { isLoggedIn } from '../lib/config.js'
11
+
12
+ // Extension to language mapping
13
+ const EXTENSION_MAP = {
14
+ '.js': 'javascript',
15
+ '.jsx': 'javascript',
16
+ '.ts': 'typescript',
17
+ '.tsx': 'typescript',
18
+ '.py': 'python',
19
+ '.java': 'java',
20
+ '.cpp': 'cpp',
21
+ '.c': 'c',
22
+ '.cs': 'csharp',
23
+ '.go': 'go',
24
+ '.rs': 'rust',
25
+ '.rb': 'ruby',
26
+ '.php': 'php',
27
+ '.html': 'html',
28
+ '.css': 'css',
29
+ '.scss': 'scss',
30
+ '.sql': 'sql',
31
+ '.md': 'markdown',
32
+ '.json': 'json',
33
+ '.yaml': 'yaml',
34
+ '.yml': 'yaml',
35
+ '.xml': 'xml',
36
+ '.sh': 'bash',
37
+ '.bat': 'batch',
38
+ '.ps1': 'powershell'
39
+ }
40
+
41
+ export async function push(file, options) {
42
+ // Check login
43
+ if (!isLoggedIn()) {
44
+ console.log(chalk.red('✗ Not logged in. Run: codehaven login'))
45
+ return
46
+ }
47
+
48
+ // Check file exists
49
+ if (!fs.existsSync(file)) {
50
+ console.log(chalk.red(`✗ File not found: ${file}`))
51
+ return
52
+ }
53
+
54
+ const spinner = ora('Reading file...').start()
55
+
56
+ try {
57
+ // Read file content
58
+ const code = fs.readFileSync(file, 'utf-8')
59
+
60
+ if (!code.trim()) {
61
+ spinner.fail(chalk.red('File is empty'))
62
+ return
63
+ }
64
+
65
+ // Detect language from extension
66
+ const ext = path.extname(file).toLowerCase()
67
+ const detectedLanguage = EXTENSION_MAP[ext] || 'plaintext'
68
+
69
+ // Use provided options or defaults
70
+ const title = options.title || path.basename(file, ext)
71
+ const language = options.language || detectedLanguage
72
+ const description = options.description || ''
73
+ const tags = options.tags ? options.tags.split(',').map(t => t.trim()) : []
74
+ const isPublic = options.public || false
75
+
76
+ spinner.text = 'Uploading snippet...'
77
+
78
+ // Create snippet
79
+ const snippet = await createSnippet({
80
+ title,
81
+ language,
82
+ code,
83
+ description,
84
+ tags,
85
+ is_public: isPublic
86
+ })
87
+
88
+ spinner.succeed(chalk.green('Snippet uploaded successfully!'))
89
+
90
+ // Display result
91
+ console.log('')
92
+ console.log(chalk.gray('─'.repeat(50)))
93
+ console.log(chalk.bold('📝 Snippet Details:'))
94
+ console.log(chalk.gray('─'.repeat(50)))
95
+ console.log(` ${chalk.cyan('ID:')} ${snippet.id}`)
96
+ console.log(` ${chalk.cyan('Title:')} ${snippet.title}`)
97
+ console.log(` ${chalk.cyan('Language:')} ${snippet.language}`)
98
+ console.log(` ${chalk.cyan('Public:')} ${snippet.is_public ? '🌍 Yes' : '🔒 No'}`)
99
+ if (tags.length > 0) {
100
+ console.log(` ${chalk.cyan('Tags:')} ${tags.map(t => `#${t}`).join(' ')}`)
101
+ }
102
+ console.log(chalk.gray('─'.repeat(50)))
103
+ console.log('')
104
+ console.log(chalk.gray(`View at: https://codehaven.app/snippet/${snippet.id}`))
105
+
106
+ } catch (error) {
107
+ spinner.fail(chalk.red('Failed to upload snippet'))
108
+ console.log(chalk.red(`Error: ${error.message}`))
109
+ }
110
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Search Command - Full-text search snippets
3
+ */
4
+
5
+ import chalk from 'chalk'
6
+ import ora from 'ora'
7
+ import { searchSnippets } from '../lib/api.js'
8
+
9
+ export async function search(query, options) {
10
+ if (query.length < 3) {
11
+ console.log(chalk.yellow('⚠ Search query must be at least 3 characters'))
12
+ return
13
+ }
14
+
15
+ const spinner = ora(`Searching for "${query}"...`).start()
16
+
17
+ try {
18
+ const snippets = await searchSnippets(query, {
19
+ language: options.language,
20
+ limit: options.limit,
21
+ mine: options.mine,
22
+ publicOnly: options.public
23
+ })
24
+
25
+ spinner.stop()
26
+
27
+ if (snippets.length === 0) {
28
+ console.log(chalk.yellow(`\n🔍 No results for "${query}"`))
29
+ console.log(chalk.gray('Try different keywords or check spelling.'))
30
+ return
31
+ }
32
+
33
+ // Display results
34
+ console.log('')
35
+ console.log(chalk.bold.cyan(`🔍 Search Results: "${query}" (${snippets.length} found)`))
36
+ console.log(chalk.gray('─'.repeat(80)))
37
+
38
+ snippets.forEach((s, i) => {
39
+ const visibility = s.is_public ? chalk.green('🌍 Public') : chalk.gray('🔒 Private')
40
+
41
+ console.log('')
42
+ console.log(`${chalk.bold.white(`${i + 1}. ${s.title}`)}`)
43
+ console.log(chalk.gray(` ID: ${s.id}`))
44
+ console.log(` ${chalk.yellow(s.language)} | ${visibility} | ❤️ ${s.like_count || 0}`)
45
+
46
+ if (s.description) {
47
+ const desc = s.description.length > 60 ? s.description.slice(0, 60) + '...' : s.description
48
+ console.log(chalk.gray(` ${desc}`))
49
+ }
50
+
51
+ if (s.tags && s.tags.length > 0) {
52
+ console.log(` ${s.tags.map(t => chalk.blue(`#${t}`)).join(' ')}`)
53
+ }
54
+ })
55
+
56
+ console.log('')
57
+ console.log(chalk.gray('─'.repeat(80)))
58
+ console.log(chalk.gray('Fetch snippet: codehaven get <id>'))
59
+
60
+ } catch (error) {
61
+ spinner.fail(chalk.red('Search failed'))
62
+ console.log(chalk.red(`Error: ${error.message}`))
63
+ }
64
+ }
package/src/lib/api.js ADDED
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Supabase API Client for CLI
3
+ */
4
+
5
+ import { createClient } from '@supabase/supabase-js'
6
+ import { getConfig } from './config.js'
7
+
8
+ // Supabase credentials (same as web app)
9
+ const SUPABASE_URL = 'https://pjzfmcsypilbhmzcxlbt.supabase.co'
10
+ const SUPABASE_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBqemZtY3N5cGlsYmhtemN4bGJ0Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzQwNTI0NjksImV4cCI6MjA0OTYyODQ2OX0.nE-Oi9xYm6NlI6R4MokXJcG75Wf4P_lEIaEiQzpPfGk'
11
+
12
+ // Create Supabase client
13
+ export function createSupabaseClient() {
14
+ const { accessToken } = getConfig()
15
+
16
+ const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
17
+ auth: {
18
+ autoRefreshToken: false,
19
+ persistSession: false
20
+ },
21
+ global: {
22
+ headers: accessToken ? {
23
+ Authorization: `Bearer ${accessToken}`
24
+ } : {}
25
+ }
26
+ })
27
+
28
+ return supabase
29
+ }
30
+
31
+ // Login with email/password
32
+ export async function loginWithEmail(email, password) {
33
+ const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
34
+
35
+ const { data, error } = await supabase.auth.signInWithPassword({
36
+ email,
37
+ password
38
+ })
39
+
40
+ if (error) throw error
41
+
42
+ return {
43
+ accessToken: data.session.access_token,
44
+ refreshToken: data.session.refresh_token,
45
+ user: data.user
46
+ }
47
+ }
48
+
49
+ // Fetch user snippets
50
+ export async function fetchMySnippets(options = {}) {
51
+ const supabase = createSupabaseClient()
52
+ const { userId } = getConfig()
53
+
54
+ let query = supabase
55
+ .from('snippets')
56
+ .select('id, title, language, description, tags, is_public, created_at, like_count, copy_count')
57
+ .eq('user_id', userId)
58
+ .order('created_at', { ascending: false })
59
+
60
+ if (options.language) {
61
+ query = query.ilike('language', options.language)
62
+ }
63
+
64
+ if (options.isPublic !== undefined) {
65
+ query = query.eq('is_public', options.isPublic)
66
+ }
67
+
68
+ if (options.limit) {
69
+ query = query.limit(parseInt(options.limit))
70
+ }
71
+
72
+ const { data, error } = await query
73
+
74
+ if (error) throw error
75
+ return data
76
+ }
77
+
78
+ // Fetch snippet by ID
79
+ export async function fetchSnippetById(id) {
80
+ const supabase = createSupabaseClient()
81
+
82
+ const { data, error } = await supabase
83
+ .from('snippets')
84
+ .select('*')
85
+ .eq('id', id)
86
+ .single()
87
+
88
+ if (error) throw error
89
+ return data
90
+ }
91
+
92
+ // Create new snippet
93
+ export async function createSnippet(snippetData) {
94
+ const supabase = createSupabaseClient()
95
+ const { userId } = getConfig()
96
+
97
+ const { data, error } = await supabase
98
+ .from('snippets')
99
+ .insert({
100
+ ...snippetData,
101
+ user_id: userId
102
+ })
103
+ .select()
104
+ .single()
105
+
106
+ if (error) throw error
107
+ return data
108
+ }
109
+
110
+ // Search snippets with full-text search
111
+ export async function searchSnippets(query, options = {}) {
112
+ const supabase = createSupabaseClient()
113
+ const { userId } = getConfig()
114
+
115
+ let dbQuery = supabase
116
+ .from('snippets')
117
+ .select('id, title, language, description, tags, is_public, created_at, like_count')
118
+ .textSearch('search_vector', query)
119
+ .order('created_at', { ascending: false })
120
+
121
+ // Filter by ownership
122
+ if (options.mine) {
123
+ dbQuery = dbQuery.eq('user_id', userId)
124
+ } else if (options.publicOnly) {
125
+ dbQuery = dbQuery.eq('is_public', true)
126
+ }
127
+
128
+ // Filter by language
129
+ if (options.language) {
130
+ dbQuery = dbQuery.ilike('language', options.language)
131
+ }
132
+
133
+ // Limit results
134
+ if (options.limit) {
135
+ dbQuery = dbQuery.limit(parseInt(options.limit))
136
+ }
137
+
138
+ const { data, error } = await dbQuery
139
+
140
+ if (error) throw error
141
+ return data
142
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Config Storage - Stores API credentials locally
3
+ */
4
+
5
+ import Conf from 'conf'
6
+
7
+ const config = new Conf({
8
+ projectName: 'codehaven-cli',
9
+ schema: {
10
+ accessToken: { type: 'string', default: '' },
11
+ refreshToken: { type: 'string', default: '' },
12
+ email: { type: 'string', default: '' },
13
+ userId: { type: 'string', default: '' }
14
+ }
15
+ })
16
+
17
+ export function saveCredentials(accessToken, refreshToken, email, userId) {
18
+ config.set('accessToken', accessToken)
19
+ config.set('refreshToken', refreshToken)
20
+ config.set('email', email)
21
+ config.set('userId', userId)
22
+ }
23
+
24
+ export function getConfig() {
25
+ return {
26
+ accessToken: config.get('accessToken'),
27
+ refreshToken: config.get('refreshToken'),
28
+ email: config.get('email'),
29
+ userId: config.get('userId')
30
+ }
31
+ }
32
+
33
+ export function clearCredentials() {
34
+ config.clear()
35
+ }
36
+
37
+ export function isLoggedIn() {
38
+ return !!config.get('accessToken')
39
+ }