lumina-ai-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.
@@ -0,0 +1,42 @@
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
+
4
+ const { createElement: h } = React;
5
+
6
+ function StatusBar({ model, persona, tokensUsed, tokenBudget, cost, isStreaming }) {
7
+ const budgetStr = tokenBudget === 'unlimited' ? '\u221E' : String(tokenBudget);
8
+ const usedPct = tokenBudget === 'unlimited'
9
+ ? '--'
10
+ : Math.min(100, Math.round((tokensUsed / tokenBudget) * 100)) + '%';
11
+ const costStr = cost ? '$' + cost.toFixed(4) : '$0.0000';
12
+
13
+ const children = [
14
+ h(Box, { key: 'left', flexGrow: 1 },
15
+ h(Text, null,
16
+ h(Text, { color: 'cyan' }, 'Model:'), ' ' + model + ' ',
17
+ h(Text, { color: 'cyan' }, 'Persona:'), ' ' + persona
18
+ )
19
+ ),
20
+ h(Box, { key: 'center' },
21
+ h(Text, null,
22
+ h(Text, { color: 'yellow' }, 'Tokens:'), ' ' + tokensUsed + '/' + budgetStr + ' ',
23
+ h(Text, { color: 'yellow' }, 'Used:'), ' ' + usedPct
24
+ )
25
+ ),
26
+ h(Box, { key: 'cost', marginLeft: 2 },
27
+ h(Text, { color: 'green' }, costStr)
28
+ ),
29
+ isStreaming ? h(Box, { key: 'streaming', marginLeft: 2 },
30
+ h(Text, { color: 'green' }, '\u25CF Streaming...')
31
+ ) : null
32
+ ].filter(Boolean);
33
+
34
+ return h(Box, {
35
+ borderStyle: 'single',
36
+ borderColor: 'gray',
37
+ paddingX: 1,
38
+ paddingY: 0
39
+ }, ...children);
40
+ }
41
+
42
+ export default StatusBar;
@@ -0,0 +1,37 @@
1
+ import hljs from 'highlight.js';
2
+ import chalk from 'chalk';
3
+
4
+ export function highlightCode(code, language) {
5
+ try {
6
+ const lang = language || detectLanguage(code);
7
+ if (lang && hljs.getLanguage(lang)) {
8
+ const result = hljs.highlight(code, { language: lang });
9
+ return applyTerminalColors(result.value);
10
+ }
11
+ } catch {}
12
+ return code;
13
+ }
14
+
15
+ function detectLanguage(code) {
16
+ if (/^import\s+|^export\s+|^const\s+\w+\s*=|^function\s|^class\s|=>/.test(code)) return 'javascript';
17
+ if (/^from\s+|^def\s+|^import\s+\w+$/.test(code)) return 'python';
18
+ if (/^#include|^using\s+namespace|^int\s+main/.test(code)) return 'cpp';
19
+ if (/^package\s+|^import\s+java/.test(code)) return 'java';
20
+ if (/^fn\s+|^let\s+|^mut\s+|^impl\s+/.test(code)) return 'rust';
21
+ if (/^<[^>]+>/.test(code)) return 'html';
22
+ return null;
23
+ }
24
+
25
+ function applyTerminalColors(html) {
26
+ return html
27
+ .replace(/<span class="hljs-keyword">([^<]+)<\/span>/g, (_, m) => chalk.magenta(m))
28
+ .replace(/<span class="hljs-string">([^<]+)<\/span>/g, (_, m) => chalk.green(m))
29
+ .replace(/<span class="hljs-number">([^<]+)<\/span>/g, (_, m) => chalk.yellow(m))
30
+ .replace(/<span class="hljs-comment">([^<]+)<\/span>/g, (_, m) => chalk.gray(m))
31
+ .replace(/<span class="hljs-title">([^<]+)<\/span>/g, (_, m) => chalk.blue(m))
32
+ .replace(/<span class="hljs-built_in">([^<]+)<\/span>/g, (_, m) => chalk.cyan(m))
33
+ .replace(/<span class="hljs-attr">([^<]+)<\/span>/g, (_, m) => chalk.red(m))
34
+ .replace(/<span class="hljs-selector-tag">([^<]+)<\/span>/g, (_, m) => chalk.magenta(m))
35
+ .replace(/<span class="hljs-selector-class">([^<]+)<\/span>/g, (_, m) => chalk.yellow(m))
36
+ .replace(/<span class="hljs-([^"]+)">([^<]+)<\/span>/g, (_, __, m) => chalk.white(m));
37
+ }
@@ -0,0 +1,33 @@
1
+ const PRICING = {
2
+ 'gpt-4o': { input: 2.50, output: 10.00 },
3
+ 'gpt-4o-mini': { input: 0.15, output: 0.60 },
4
+ 'gpt-3.5-turbo': { input: 0.50, output: 1.50 },
5
+ 'claude-3-5-sonnet':{ input: 3.00, output: 15.00 },
6
+ 'claude-3-opus': { input: 15.00, output: 75.00 },
7
+ 'claude-3-haiku': { input: 0.25, output: 1.25 },
8
+ 'gemini-1.5-pro': { input: 1.25, output: 5.00 },
9
+ 'gemini-1.5-flash': { input: 0.075, output: 0.30 },
10
+ 'gemini-2.0-flash': { input: 0.10, output: 0.40 },
11
+ 'mixtral-8x7b': { input: 0.24, output: 0.24 },
12
+ 'llama2-70b': { input: 0.59, output: 0.79 },
13
+ 'llama3-70b': { input: 0.59, output: 0.79 },
14
+ 'llama3-8b': { input: 0.06, output: 0.06 },
15
+ 'llama-3.3-70b': { input: 0.59, output: 0.79 },
16
+ 'deepseek-r1': { input: 0.55, output: 0.55 },
17
+ };
18
+
19
+ export function getPricing(model) {
20
+ for (const [key, price] of Object.entries(PRICING)) {
21
+ if (model.toLowerCase().includes(key)) {
22
+ return price;
23
+ }
24
+ }
25
+ return { input: 1.00, output: 2.00 };
26
+ }
27
+
28
+ export function estimateCost(inputTokens, outputTokens, model) {
29
+ const p = getPricing(model);
30
+ const inputCost = (inputTokens / 1000000) * p.input;
31
+ const outputCost = (outputTokens / 1000000) * p.output;
32
+ return inputCost + outputCost;
33
+ }
@@ -0,0 +1,46 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+
5
+ export function saveConversation(messages, filename) {
6
+ const dir = path.join(os.homedir(), '.lumina', 'conversations');
7
+ if (!fs.existsSync(dir)) {
8
+ fs.mkdirSync(dir, { recursive: true });
9
+ }
10
+ const filePath = path.join(dir, filename.endsWith('.json') ? filename : `${filename}.json`);
11
+ fs.writeFileSync(filePath, JSON.stringify(messages, null, 2), 'utf-8');
12
+ return filePath;
13
+ }
14
+
15
+ export function loadConversation(filepath) {
16
+ try {
17
+ const raw = fs.readFileSync(filepath, 'utf-8');
18
+ return JSON.parse(raw);
19
+ } catch {
20
+ return null;
21
+ }
22
+ }
23
+
24
+ export function detectFileExtension(language) {
25
+ const extMap = {
26
+ javascript: 'js', js: 'js', jsx: 'jsx', typescript: 'ts', ts: 'ts',
27
+ tsx: 'tsx', python: 'py', py: 'py', ruby: 'rb', rb: 'rb',
28
+ go: 'go', rust: 'rs', rs: 'rs', c: 'c', cpp: 'cpp',
29
+ java: 'java', kotlin: 'kt', scala: 'scala', swift: 'swift',
30
+ php: 'php', html: 'html', css: 'css', scss: 'scss', less: 'less',
31
+ sql: 'sql', sh: 'sh', bash: 'sh', yaml: 'yml', yml: 'yml',
32
+ json: 'json', xml: 'xml', markdown: 'md', md: 'md',
33
+ };
34
+ return extMap[language?.toLowerCase()] || 'txt';
35
+ }
36
+
37
+ export function writeCodeFile(content, filename) {
38
+ const cwd = process.cwd();
39
+ const filePath = path.join(cwd, filename);
40
+ const dir = path.dirname(filePath);
41
+ if (!fs.existsSync(dir)) {
42
+ fs.mkdirSync(dir, { recursive: true });
43
+ }
44
+ fs.writeFileSync(filePath, content, 'utf-8');
45
+ return filePath;
46
+ }
@@ -0,0 +1,28 @@
1
+ import { encode } from 'gpt-tokenizer';
2
+
3
+ export function countTokens(text, model = 'gpt-4o-mini') {
4
+ if (!text) return 0;
5
+ try {
6
+ const tokens = encode(text);
7
+ return tokens.length;
8
+ } catch {
9
+ return approximateTokens(text);
10
+ }
11
+ }
12
+
13
+ export function approximateTokens(text) {
14
+ if (!text) return 0;
15
+ let count = 0;
16
+ for (const char of text) {
17
+ if (/[\x00-\x7F]/.test(char)) {
18
+ count += 1;
19
+ } else if (/[\u0080-\u07FF]/.test(char)) {
20
+ count += 2;
21
+ } else if (/[\u0800-\uFFFF]/.test(char)) {
22
+ count += 3;
23
+ } else {
24
+ count += 4;
25
+ }
26
+ }
27
+ return Math.ceil(count / 4);
28
+ }