@nuvin/nuvin-cli 1.4.0 → 1.5.1-prerelease

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,152 @@
1
+ import { exec } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ import { platform, tmpdir } from 'node:os';
4
+ import { readFileSync, unlinkSync } from 'node:fs';
5
+ import { join } from 'node:path';
6
+
7
+ const execAsync = promisify(exec);
8
+ async function hasClipboardFiles() {
9
+ try {
10
+ const os = platform();
11
+ if (os === 'darwin') {
12
+ // Check if clipboard contains image data - expanded format support
13
+ const { stdout } = await execAsync('osascript -e "clipboard info"');
14
+ return (stdout.includes('«class PNGf»') ||
15
+ stdout.includes('«class TIFF»') ||
16
+ stdout.includes('«class JPEG»') ||
17
+ stdout.includes('«class GIFf»') ||
18
+ stdout.includes('«class BMPf»') ||
19
+ stdout.includes('«class HEIF»') ||
20
+ stdout.includes('«class WEBP»'));
21
+ }
22
+ else if (os === 'linux') {
23
+ const { stdout } = await execAsync('xclip -selection clipboard -t TARGETS -o 2>/dev/null || echo ""');
24
+ return (stdout.includes('image/png') ||
25
+ stdout.includes('image/jpeg') ||
26
+ stdout.includes('image/gif') ||
27
+ stdout.includes('image/bmp') ||
28
+ stdout.includes('image/tiff') ||
29
+ stdout.includes('image/webp'));
30
+ }
31
+ else if (os === 'win32') {
32
+ try {
33
+ // Check if clipboard contains image data
34
+ const { stdout } = await execAsync("powershell -command \"if ([System.Windows.Forms.Clipboard]::ContainsImage()) { 'true' } else { 'false' }\" 2>$null");
35
+ return stdout.trim() === 'true';
36
+ }
37
+ catch {
38
+ return false;
39
+ }
40
+ }
41
+ return false;
42
+ }
43
+ catch {
44
+ return false;
45
+ }
46
+ }
47
+ async function getClipboardFiles() {
48
+ try {
49
+ const os = platform();
50
+ if (os === 'darwin') {
51
+ return await getMacOSClipboardImage();
52
+ }
53
+ else if (os === 'linux') {
54
+ return await getLinuxClipboardImage();
55
+ }
56
+ else if (os === 'win32') {
57
+ return await getWindowsClipboardImage();
58
+ }
59
+ return [];
60
+ }
61
+ catch (error) {
62
+ console.warn('Failed to get clipboard files:', error);
63
+ return [];
64
+ }
65
+ }
66
+ async function getMacOSClipboardImage() {
67
+ const formats = [
68
+ { format: '«class PNGf»', extension: 'png', mimeType: 'image/png' },
69
+ { format: '«class JPEG»', extension: 'jpg', mimeType: 'image/jpeg' },
70
+ { format: '«class TIFF»', extension: 'tiff', mimeType: 'image/tiff' },
71
+ { format: '«class GIFf»', extension: 'gif', mimeType: 'image/gif' },
72
+ { format: '«class BMPf»', extension: 'bmp', mimeType: 'image/bmp' },
73
+ ];
74
+ for (const { format, extension, mimeType } of formats) {
75
+ try {
76
+ const tempPath = join(tmpdir(), `clipboard-${Date.now()}.${extension}`);
77
+ await execAsync(`osascript -e "
78
+ set imageData to the clipboard as ${format}
79
+ set fileRef to open for access POSIX file \\"${tempPath}\\" with write permission
80
+ write imageData to fileRef
81
+ close access fileRef
82
+ "`);
83
+ const buffer = readFileSync(tempPath);
84
+ unlinkSync(tempPath);
85
+ return [{ data: buffer, type: mimeType, name: `clipboard-image.${extension}` }];
86
+ }
87
+ catch { }
88
+ }
89
+ // Fallback to pngpaste if available
90
+ try {
91
+ const tempPath = join(tmpdir(), `clipboard-${Date.now()}.png`);
92
+ await execAsync(`pngpaste "${tempPath}"`);
93
+ const buffer = readFileSync(tempPath);
94
+ unlinkSync(tempPath);
95
+ return [{ data: buffer, type: 'image/png', name: 'clipboard-image.png' }];
96
+ }
97
+ catch {
98
+ // No fallback worked
99
+ }
100
+ throw new Error('Unable to extract clipboard image on macOS');
101
+ }
102
+ async function getLinuxClipboardImage() {
103
+ const { stdout: targets } = await execAsync('xclip -selection clipboard -t TARGETS -o 2>/dev/null || echo ""');
104
+ const formats = [
105
+ { target: 'image/png', extension: 'png' },
106
+ { target: 'image/jpeg', extension: 'jpg' },
107
+ { target: 'image/gif', extension: 'gif' },
108
+ { target: 'image/bmp', extension: 'bmp' },
109
+ { target: 'image/tiff', extension: 'tiff' },
110
+ { target: 'image/webp', extension: 'webp' },
111
+ ];
112
+ for (const { target, extension } of formats) {
113
+ if (targets.includes(target)) {
114
+ try {
115
+ const { stdout: imageData } = await execAsync(`xclip -selection clipboard -t ${target} -o | base64`);
116
+ const buffer = Buffer.from(imageData.trim(), 'base64');
117
+ return [{ data: buffer, type: target, name: `clipboard-image.${extension}` }];
118
+ }
119
+ catch { }
120
+ }
121
+ }
122
+ throw new Error('No supported image format found in clipboard');
123
+ }
124
+ async function getWindowsClipboardImage() {
125
+ const tempPath = join(tmpdir(), `clipboard-${Date.now()}.png`);
126
+ try {
127
+ await execAsync(`powershell -command "
128
+ Add-Type -AssemblyName System.Windows.Forms
129
+ Add-Type -AssemblyName System.Drawing
130
+
131
+ if ([System.Windows.Forms.Clipboard]::ContainsImage()) {
132
+ $img = [System.Windows.Forms.Clipboard]::GetImage()
133
+ if ($img -ne $null) {
134
+ $img.Save('${tempPath.replace(/\\/g, '\\\\')}', [System.Drawing.Imaging.ImageFormat]::Png)
135
+ $img.Dispose()
136
+ }
137
+ }
138
+ "`);
139
+ const buffer = readFileSync(tempPath);
140
+ unlinkSync(tempPath);
141
+ return [{ data: buffer, type: 'image/png', name: 'clipboard-image.png' }];
142
+ }
143
+ catch (error) {
144
+ try {
145
+ unlinkSync(tempPath);
146
+ }
147
+ catch { }
148
+ throw error;
149
+ }
150
+ }
151
+
152
+ export { getClipboardFiles, hasClipboardFiles };
@@ -0,0 +1,112 @@
1
+ import { A as AppContext, p as processMessageToUILines, r as render, j as jsxRuntimeExports, T as ThemeProvider, C as ConfigProvider, N as NotificationProvider, a as ToolApprovalProvider, b as CommandProvider, B as Box, c as ChatDisplay, I as InteractionArea, F as Footer, d as Text } from './cli-C9AKAZ8P.js';
2
+ import { useContext, useEffect } from 'react';
3
+ import * as fsp from 'node:fs/promises';
4
+ import * as path from 'node:path';
5
+ import 'meow';
6
+ import 'node:fs';
7
+ import 'node:os';
8
+ import 'ansi-escapes';
9
+ import 'node:crypto';
10
+ import 'yaml';
11
+ import 'ink-gradient';
12
+ import 'node:child_process';
13
+ import 'node:url';
14
+ import 'open';
15
+ import '@nuvin/nuvin-core';
16
+ import 'node:buffer';
17
+ import 'node:process';
18
+ import 'react-reconciler';
19
+ import 'yoga-layout';
20
+ import 'ansi-regex';
21
+ import 'node:events';
22
+ import 'chalk';
23
+ import 'marked';
24
+ import 'supports-hyperlinks';
25
+ import 'node-emoji';
26
+ import 'cli-table3';
27
+ import 'cli-highlight';
28
+ import 'ink-spinner';
29
+ import 'node:stream';
30
+ import './_commonjsHelpers-BFTU3MAI.js';
31
+ import 'events';
32
+ import 'assert';
33
+ import 'module';
34
+
35
+ /**
36
+ `useApp` is a React hook that exposes a method to manually exit the app (unmount).
37
+ */
38
+ const useApp = () => useContext(AppContext);
39
+
40
+ function DemoDisplayContent({ messages, metadata, messageCount }) {
41
+ const { exit } = useApp();
42
+ useEffect(() => {
43
+ const timer = setTimeout(() => {
44
+ exit();
45
+ }, 300);
46
+ return () => clearTimeout(timer);
47
+ }, [exit]);
48
+ return (jsxRuntimeExports.jsxs(Box, { flexDirection: "column", height: "100%", width: "100%", children: [jsxRuntimeExports.jsx(ChatDisplay, { messages: messages }, "demo"), jsxRuntimeExports.jsx(InteractionArea, { busy: false, vimModeEnabled: false, hasActiveCommand: false }), jsxRuntimeExports.jsx(Footer, { status: "Demo Mode", lastMetadata: metadata, toolApprovalMode: false, vimModeEnabled: false, vimMode: "insert", workingDirectory: process.cwd() }), jsxRuntimeExports.jsx(Box, { paddingX: 1, borderStyle: "single", borderColor: "yellow", marginTop: -1, children: jsxRuntimeExports.jsxs(Text, { color: "yellow", children: ["\uD83C\uDFA8 Demo Mode - Loaded ", messageCount, " messages - Auto-exiting in 3s..."] }) })] }));
49
+ }
50
+ // Mock config for demo
51
+ const mockConfig = {
52
+ providers: {},
53
+ session: { memPersist: false },
54
+ requireToolApproval: false,
55
+ };
56
+ function DemoDisplay({ messages, metadata, messageCount }) {
57
+ return (jsxRuntimeExports.jsx(ThemeProvider, { children: jsxRuntimeExports.jsx(ConfigProvider, { initialConfig: mockConfig, children: jsxRuntimeExports.jsx(NotificationProvider, { children: jsxRuntimeExports.jsx(ToolApprovalProvider, { requireToolApproval: false, onError: (msg) => console.error(msg), children: jsxRuntimeExports.jsx(CommandProvider, { children: jsxRuntimeExports.jsx(DemoDisplayContent, { messages: messages, metadata: metadata, messageCount: messageCount }) }) }) }) }) }));
58
+ }
59
+ class DemoMode {
60
+ constructor(historyPath) {
61
+ this.historyPath = historyPath;
62
+ }
63
+ async run() {
64
+ try {
65
+ const resolvedPath = path.resolve(this.historyPath);
66
+ const fileContent = await fsp.readFile(resolvedPath, 'utf-8');
67
+ const historyData = JSON.parse(fileContent);
68
+ if (!historyData.cli || historyData.cli.length === 0) {
69
+ console.error('Error: No messages found in history file');
70
+ process.exit(1);
71
+ }
72
+ const cliMessages = historyData.cli;
73
+ const uiMessages = [];
74
+ for (const msg of cliMessages) {
75
+ uiMessages.push(...processMessageToUILines(msg));
76
+ }
77
+ let metadata = null;
78
+ for (let i = cliMessages.length - 1; i >= 0; i--) {
79
+ const msg = cliMessages[i];
80
+ if (msg.role === 'assistant') {
81
+ metadata = {
82
+ totalTokens: 0,
83
+ };
84
+ break;
85
+ }
86
+ }
87
+ console.clear();
88
+ console.log('\x1Bc');
89
+ const { waitUntilExit } = render(jsxRuntimeExports.jsx(DemoDisplay, { messages: uiMessages, metadata: metadata, messageCount: cliMessages.length }), {
90
+ exitOnCtrlC: true,
91
+ patchConsole: true,
92
+ maxFps: 30,
93
+ });
94
+ await waitUntilExit();
95
+ }
96
+ catch (error) {
97
+ if (error.code === 'ENOENT') {
98
+ console.error(`Error: History file not found: ${this.historyPath}`);
99
+ }
100
+ else if (error instanceof SyntaxError) {
101
+ console.error(`Error: Invalid JSON in history file: ${this.historyPath}`);
102
+ }
103
+ else {
104
+ const message = error instanceof Error ? error.message : String(error);
105
+ console.error(`Error loading demo: ${message}`);
106
+ }
107
+ process.exit(1);
108
+ }
109
+ }
110
+ }
111
+
112
+ export { DemoMode };