snow-ai 0.4.9 → 0.4.11
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/dist/app.js +32 -14
- package/dist/cli.js +6 -0
- package/dist/ui/pages/WelcomeScreen.js +50 -32
- package/dist/utils/configManager.js +11 -3
- package/dist/utils/logger.d.ts +9 -3
- package/dist/utils/logger.js +28 -3
- package/dist/utils/mcpToolsManager.d.ts +1 -1
- package/dist/utils/mcpToolsManager.js +13 -9
- package/package.json +6 -3
- package/scripts/postinstall.cjs +106 -0
package/dist/app.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react';
|
|
1
|
+
import React, { useState, useEffect, Suspense } from 'react';
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
3
|
import { Alert } from '@inkjs/ui';
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
import Spinner from 'ink-spinner';
|
|
5
|
+
// Lazy load all page components to improve startup time
|
|
6
|
+
// Only load components when they are actually needed
|
|
7
|
+
const WelcomeScreen = React.lazy(() => import('./ui/pages/WelcomeScreen.js'));
|
|
8
|
+
const ChatScreen = React.lazy(() => import('./ui/pages/ChatScreen.js'));
|
|
9
|
+
const HeadlessModeScreen = React.lazy(() => import('./ui/pages/HeadlessModeScreen.js'));
|
|
10
|
+
const MCPConfigScreen = React.lazy(() => import('./ui/pages/MCPConfigScreen.js'));
|
|
11
|
+
const SystemPromptConfigScreen = React.lazy(() => import('./ui/pages/SystemPromptConfigScreen.js'));
|
|
12
|
+
const CustomHeadersScreen = React.lazy(() => import('./ui/pages/CustomHeadersScreen.js'));
|
|
10
13
|
import { useGlobalExit, } from './hooks/useGlobalExit.js';
|
|
11
14
|
import { onNavigate } from './hooks/useGlobalNavigation.js';
|
|
12
15
|
import { useTerminalSize } from './hooks/useTerminalSize.js';
|
|
@@ -56,23 +59,33 @@ function AppContent({ version, skipWelcome, }) {
|
|
|
56
59
|
}
|
|
57
60
|
};
|
|
58
61
|
const renderView = () => {
|
|
62
|
+
const loadingFallback = (React.createElement(Box, null,
|
|
63
|
+
React.createElement(Text, { color: "cyan" },
|
|
64
|
+
React.createElement(Spinner, { type: "dots" })),
|
|
65
|
+
React.createElement(Text, null, " Loading...")));
|
|
59
66
|
switch (currentView) {
|
|
60
67
|
case 'welcome':
|
|
61
|
-
return (React.createElement(
|
|
68
|
+
return (React.createElement(Suspense, { fallback: loadingFallback },
|
|
69
|
+
React.createElement(WelcomeScreen, { version: version, onMenuSelect: handleMenuSelect })));
|
|
62
70
|
case 'chat':
|
|
63
|
-
return React.createElement(
|
|
71
|
+
return (React.createElement(Suspense, { fallback: loadingFallback },
|
|
72
|
+
React.createElement(ChatScreen, { key: chatScreenKey, skipWelcome: skipWelcome })));
|
|
64
73
|
case 'settings':
|
|
65
74
|
return (React.createElement(Box, { flexDirection: "column" },
|
|
66
75
|
React.createElement(Text, { color: "blue" }, "Settings"),
|
|
67
76
|
React.createElement(Text, { color: "gray" }, "Settings interface would be implemented here")));
|
|
68
77
|
case 'mcp':
|
|
69
|
-
return (React.createElement(
|
|
78
|
+
return (React.createElement(Suspense, { fallback: loadingFallback },
|
|
79
|
+
React.createElement(MCPConfigScreen, { onBack: () => setCurrentView('welcome'), onSave: () => setCurrentView('welcome') })));
|
|
70
80
|
case 'systemprompt':
|
|
71
|
-
return (React.createElement(
|
|
81
|
+
return (React.createElement(Suspense, { fallback: loadingFallback },
|
|
82
|
+
React.createElement(SystemPromptConfigScreen, { onBack: () => setCurrentView('welcome') })));
|
|
72
83
|
case 'customheaders':
|
|
73
|
-
return React.createElement(
|
|
84
|
+
return (React.createElement(Suspense, { fallback: loadingFallback },
|
|
85
|
+
React.createElement(CustomHeadersScreen, { onBack: () => setCurrentView('welcome') })));
|
|
74
86
|
default:
|
|
75
|
-
return (React.createElement(
|
|
87
|
+
return (React.createElement(Suspense, { fallback: loadingFallback },
|
|
88
|
+
React.createElement(WelcomeScreen, { version: version, onMenuSelect: handleMenuSelect })));
|
|
76
89
|
}
|
|
77
90
|
};
|
|
78
91
|
return (React.createElement(Box, { flexDirection: "column", width: terminalWidth },
|
|
@@ -84,9 +97,14 @@ export default function App({ version, skipWelcome, headlessPrompt }) {
|
|
|
84
97
|
// If headless prompt is provided, use headless mode
|
|
85
98
|
// Wrap in I18nProvider since HeadlessModeScreen might use hooks that depend on it
|
|
86
99
|
if (headlessPrompt) {
|
|
100
|
+
const loadingFallback = (React.createElement(Box, null,
|
|
101
|
+
React.createElement(Text, { color: "cyan" },
|
|
102
|
+
React.createElement(Spinner, { type: "dots" })),
|
|
103
|
+
React.createElement(Text, null, " Loading...")));
|
|
87
104
|
return (React.createElement(I18nProvider, null,
|
|
88
105
|
React.createElement(ThemeProvider, null,
|
|
89
|
-
React.createElement(
|
|
106
|
+
React.createElement(Suspense, { fallback: loadingFallback },
|
|
107
|
+
React.createElement(HeadlessModeScreen, { prompt: headlessPrompt, onComplete: () => process.exit(0) })))));
|
|
90
108
|
}
|
|
91
109
|
return (React.createElement(I18nProvider, null,
|
|
92
110
|
React.createElement(ThemeProvider, null,
|
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
// Show loading indicator immediately before any imports
|
|
3
|
+
process.stdout.write('\x1b[?25l'); // Hide cursor
|
|
4
|
+
process.stdout.write('⠋ Loading...\r');
|
|
2
5
|
import React from 'react';
|
|
3
6
|
import { render, Text, Box } from 'ink';
|
|
4
7
|
import Spinner from 'ink-spinner';
|
|
@@ -137,6 +140,9 @@ const Startup = ({ version, skipWelcome, headlessPrompt, }) => {
|
|
|
137
140
|
};
|
|
138
141
|
// Disable bracketed paste mode on startup
|
|
139
142
|
process.stdout.write('\x1b[?2004l');
|
|
143
|
+
// Clear the early loading indicator and show cursor
|
|
144
|
+
process.stdout.write('\x1b[2K\r'); // Clear line
|
|
145
|
+
process.stdout.write('\x1b[?25h'); // Show cursor
|
|
140
146
|
// Re-enable on exit to avoid polluting parent shell
|
|
141
147
|
const cleanup = () => {
|
|
142
148
|
process.stdout.write('\x1b[?2004l');
|
|
@@ -1,21 +1,23 @@
|
|
|
1
|
-
import React, { useState, useMemo, useCallback, useEffect, useRef } from 'react';
|
|
1
|
+
import React, { useState, useMemo, useCallback, useEffect, useRef, Suspense } from 'react';
|
|
2
2
|
import { Box, Text, useStdout, Static } from 'ink';
|
|
3
3
|
import { Alert } from '@inkjs/ui';
|
|
4
4
|
import Gradient from 'ink-gradient';
|
|
5
5
|
import ansiEscapes from 'ansi-escapes';
|
|
6
|
+
import Spinner from 'ink-spinner';
|
|
6
7
|
import Menu from '../components/Menu.js';
|
|
7
8
|
import { useTerminalSize } from '../../hooks/useTerminalSize.js';
|
|
8
|
-
import ConfigScreen from './ConfigScreen.js';
|
|
9
|
-
import ProxyConfigScreen from './ProxyConfigScreen.js';
|
|
10
|
-
import SubAgentConfigScreen from './SubAgentConfigScreen.js';
|
|
11
|
-
import SubAgentListScreen from './SubAgentListScreen.js';
|
|
12
|
-
import SensitiveCommandConfigScreen from './SensitiveCommandConfigScreen.js';
|
|
13
|
-
import CodeBaseConfigScreen from './CodeBaseConfigScreen.js';
|
|
14
|
-
import SystemPromptConfigScreen from './SystemPromptConfigScreen.js';
|
|
15
|
-
import CustomHeadersScreen from './CustomHeadersScreen.js';
|
|
16
|
-
import LanguageSettingsScreen from './LanguageSettingsScreen.js';
|
|
17
|
-
import ThemeSettingsScreen from './ThemeSettingsScreen.js';
|
|
18
9
|
import { useI18n } from '../../i18n/index.js';
|
|
10
|
+
// Lazy load all configuration screens for better startup performance
|
|
11
|
+
const ConfigScreen = React.lazy(() => import('./ConfigScreen.js'));
|
|
12
|
+
const ProxyConfigScreen = React.lazy(() => import('./ProxyConfigScreen.js'));
|
|
13
|
+
const SubAgentConfigScreen = React.lazy(() => import('./SubAgentConfigScreen.js'));
|
|
14
|
+
const SubAgentListScreen = React.lazy(() => import('./SubAgentListScreen.js'));
|
|
15
|
+
const SensitiveCommandConfigScreen = React.lazy(() => import('./SensitiveCommandConfigScreen.js'));
|
|
16
|
+
const CodeBaseConfigScreen = React.lazy(() => import('./CodeBaseConfigScreen.js'));
|
|
17
|
+
const SystemPromptConfigScreen = React.lazy(() => import('./SystemPromptConfigScreen.js'));
|
|
18
|
+
const CustomHeadersScreen = React.lazy(() => import('./CustomHeadersScreen.js'));
|
|
19
|
+
const LanguageSettingsScreen = React.lazy(() => import('./LanguageSettingsScreen.js'));
|
|
20
|
+
const ThemeSettingsScreen = React.lazy(() => import('./ThemeSettingsScreen.js'));
|
|
19
21
|
export default function WelcomeScreen({ version = '1.0.0', onMenuSelect, }) {
|
|
20
22
|
const { t } = useI18n();
|
|
21
23
|
const [infoText, setInfoText] = useState(t.welcome.startChatInfo);
|
|
@@ -158,6 +160,11 @@ export default function WelcomeScreen({ version = '1.0.0', onMenuSelect, }) {
|
|
|
158
160
|
clearTimeout(handler);
|
|
159
161
|
};
|
|
160
162
|
}, [terminalWidth]); // Remove stdout from dependencies to avoid loops
|
|
163
|
+
// Loading fallback component for lazy-loaded screens
|
|
164
|
+
const loadingFallback = (React.createElement(Box, { paddingX: 1 },
|
|
165
|
+
React.createElement(Text, { color: "cyan" },
|
|
166
|
+
React.createElement(Spinner, { type: "dots" })),
|
|
167
|
+
React.createElement(Text, null, " Loading...")));
|
|
161
168
|
return (React.createElement(Box, { flexDirection: "column", width: terminalWidth },
|
|
162
169
|
React.createElement(Static, { key: remountKey, items: [
|
|
163
170
|
React.createElement(Box, { key: "welcome-header", flexDirection: "row", paddingLeft: 2, paddingTop: 1, paddingBottom: 0, width: terminalWidth },
|
|
@@ -175,25 +182,36 @@ export default function WelcomeScreen({ version = '1.0.0', onMenuSelect, }) {
|
|
|
175
182
|
React.createElement(Menu, { options: menuOptions, onSelect: handleInlineMenuSelect, onSelectionChange: handleSelectionChange })))),
|
|
176
183
|
inlineView === 'menu' && (React.createElement(Box, { paddingX: 1 },
|
|
177
184
|
React.createElement(Alert, { variant: "info" }, infoText))),
|
|
178
|
-
inlineView === 'config' && (React.createElement(
|
|
179
|
-
React.createElement(
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
inlineView === '
|
|
185
|
-
React.createElement(
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
inlineView === '
|
|
191
|
-
React.createElement(
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
inlineView === '
|
|
197
|
-
React.createElement(
|
|
198
|
-
|
|
185
|
+
inlineView === 'config' && (React.createElement(Suspense, { fallback: loadingFallback },
|
|
186
|
+
React.createElement(Box, { paddingX: 1 },
|
|
187
|
+
React.createElement(ConfigScreen, { onBack: handleBackToMenu, onSave: handleConfigSave, inlineMode: true })))),
|
|
188
|
+
inlineView === 'proxy-config' && (React.createElement(Suspense, { fallback: loadingFallback },
|
|
189
|
+
React.createElement(Box, { paddingX: 1 },
|
|
190
|
+
React.createElement(ProxyConfigScreen, { onBack: handleBackToMenu, onSave: handleConfigSave, inlineMode: true })))),
|
|
191
|
+
inlineView === 'codebase-config' && (React.createElement(Suspense, { fallback: loadingFallback },
|
|
192
|
+
React.createElement(Box, { paddingX: 1 },
|
|
193
|
+
React.createElement(CodeBaseConfigScreen, { onBack: handleBackToMenu, onSave: handleConfigSave, inlineMode: true })))),
|
|
194
|
+
inlineView === 'subagent-list' && (React.createElement(Suspense, { fallback: loadingFallback },
|
|
195
|
+
React.createElement(Box, { paddingX: 1 },
|
|
196
|
+
React.createElement(SubAgentListScreen, { onBack: handleBackToMenu, onAdd: handleSubAgentAdd, onEdit: handleSubAgentEdit, inlineMode: true })))),
|
|
197
|
+
inlineView === 'subagent-add' && (React.createElement(Suspense, { fallback: loadingFallback },
|
|
198
|
+
React.createElement(Box, { paddingX: 1 },
|
|
199
|
+
React.createElement(SubAgentConfigScreen, { onBack: () => setInlineView('subagent-list'), onSave: handleSubAgentSave, inlineMode: true })))),
|
|
200
|
+
inlineView === 'subagent-edit' && (React.createElement(Suspense, { fallback: loadingFallback },
|
|
201
|
+
React.createElement(Box, { paddingX: 1 },
|
|
202
|
+
React.createElement(SubAgentConfigScreen, { onBack: () => setInlineView('subagent-list'), onSave: handleSubAgentSave, agentId: editingAgentId, inlineMode: true })))),
|
|
203
|
+
inlineView === 'sensitive-commands' && (React.createElement(Suspense, { fallback: loadingFallback },
|
|
204
|
+
React.createElement(Box, { paddingX: 1 },
|
|
205
|
+
React.createElement(SensitiveCommandConfigScreen, { onBack: handleBackToMenu, inlineMode: true })))),
|
|
206
|
+
inlineView === 'systemprompt' && (React.createElement(Suspense, { fallback: loadingFallback },
|
|
207
|
+
React.createElement(Box, { paddingX: 1 },
|
|
208
|
+
React.createElement(SystemPromptConfigScreen, { onBack: handleBackToMenu })))),
|
|
209
|
+
inlineView === 'customheaders' && (React.createElement(Suspense, { fallback: loadingFallback },
|
|
210
|
+
React.createElement(Box, { paddingX: 1 },
|
|
211
|
+
React.createElement(CustomHeadersScreen, { onBack: handleBackToMenu })))),
|
|
212
|
+
inlineView === 'language-settings' && (React.createElement(Suspense, { fallback: loadingFallback },
|
|
213
|
+
React.createElement(Box, { paddingX: 1 },
|
|
214
|
+
React.createElement(LanguageSettingsScreen, { onBack: handleBackToMenu, inlineMode: true })))),
|
|
215
|
+
inlineView === 'theme-settings' && (React.createElement(Suspense, { fallback: loadingFallback },
|
|
216
|
+
React.createElement(ThemeSettingsScreen, { onBack: handleBackToMenu, inlineMode: true })))));
|
|
199
217
|
}
|
|
@@ -283,13 +283,21 @@ export function initializeProfiles() {
|
|
|
283
283
|
migrateLegacyConfig();
|
|
284
284
|
// Ensure the active profile exists and is loaded to config.json
|
|
285
285
|
const activeProfile = getActiveProfileName();
|
|
286
|
-
|
|
286
|
+
let profileConfig = loadProfile(activeProfile);
|
|
287
287
|
if (profileConfig) {
|
|
288
288
|
// Sync the active profile to config.json
|
|
289
289
|
saveConfig(profileConfig);
|
|
290
290
|
}
|
|
291
291
|
else {
|
|
292
|
-
// If active profile doesn't exist,
|
|
293
|
-
|
|
292
|
+
// If active profile doesn't exist, create it first
|
|
293
|
+
// This is especially important for first-time installations
|
|
294
|
+
const defaultConfig = loadConfig();
|
|
295
|
+
saveProfile(activeProfile, defaultConfig);
|
|
296
|
+
setActiveProfileName(activeProfile);
|
|
297
|
+
// Now load and sync the newly created profile
|
|
298
|
+
profileConfig = loadProfile(activeProfile);
|
|
299
|
+
if (profileConfig) {
|
|
300
|
+
saveConfig(profileConfig);
|
|
301
|
+
}
|
|
294
302
|
}
|
|
295
303
|
}
|
package/dist/utils/logger.d.ts
CHANGED
|
@@ -26,6 +26,12 @@ export declare class Logger {
|
|
|
26
26
|
debug(message: string, meta?: any): void;
|
|
27
27
|
log(level: LogLevel, message: string, meta?: any): void;
|
|
28
28
|
}
|
|
29
|
-
declare const
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
declare const logger: {
|
|
30
|
+
error(message: string, meta?: any): void;
|
|
31
|
+
warn(message: string, meta?: any): void;
|
|
32
|
+
info(message: string, meta?: any): void;
|
|
33
|
+
debug(message: string, meta?: any): void;
|
|
34
|
+
log(level: LogLevel, message: string, meta?: any): void;
|
|
35
|
+
};
|
|
36
|
+
export default logger;
|
|
37
|
+
export { logger };
|
package/dist/utils/logger.js
CHANGED
|
@@ -92,6 +92,31 @@ export class Logger {
|
|
|
92
92
|
this.writeLog(level, message, meta);
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
// Lazy initialization to avoid blocking startup
|
|
96
|
+
let _defaultLogger = null;
|
|
97
|
+
function getDefaultLogger() {
|
|
98
|
+
if (!_defaultLogger) {
|
|
99
|
+
_defaultLogger = new Logger();
|
|
100
|
+
}
|
|
101
|
+
return _defaultLogger;
|
|
102
|
+
}
|
|
103
|
+
// Create a proxy object that lazily initializes the logger
|
|
104
|
+
const logger = {
|
|
105
|
+
error(message, meta) {
|
|
106
|
+
getDefaultLogger().error(message, meta);
|
|
107
|
+
},
|
|
108
|
+
warn(message, meta) {
|
|
109
|
+
getDefaultLogger().warn(message, meta);
|
|
110
|
+
},
|
|
111
|
+
info(message, meta) {
|
|
112
|
+
getDefaultLogger().info(message, meta);
|
|
113
|
+
},
|
|
114
|
+
debug(message, meta) {
|
|
115
|
+
getDefaultLogger().debug(message, meta);
|
|
116
|
+
},
|
|
117
|
+
log(level, message, meta) {
|
|
118
|
+
getDefaultLogger().log(level, message, meta);
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
export default logger;
|
|
122
|
+
export { logger };
|
|
@@ -19,15 +19,18 @@ import os from 'os';
|
|
|
19
19
|
import path from 'path';
|
|
20
20
|
let toolsCache = null;
|
|
21
21
|
const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
const session = sessionManager.getCurrentSession();
|
|
25
|
-
return session ? session.id : null;
|
|
26
|
-
});
|
|
22
|
+
// Lazy initialization of TODO service to avoid circular dependencies
|
|
23
|
+
let todoService = null;
|
|
27
24
|
/**
|
|
28
|
-
* Get the TODO service instance
|
|
25
|
+
* Get the TODO service instance (lazy initialization)
|
|
29
26
|
*/
|
|
30
27
|
export function getTodoService() {
|
|
28
|
+
if (!todoService) {
|
|
29
|
+
todoService = new TodoService(path.join(os.homedir(), '.snow'), () => {
|
|
30
|
+
const session = sessionManager.getCurrentSession();
|
|
31
|
+
return session ? session.id : null;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
31
34
|
return todoService;
|
|
32
35
|
}
|
|
33
36
|
/**
|
|
@@ -123,8 +126,9 @@ async function refreshToolsCache() {
|
|
|
123
126
|
});
|
|
124
127
|
}
|
|
125
128
|
// Add built-in TODO tools (always available)
|
|
126
|
-
|
|
127
|
-
|
|
129
|
+
const todoSvc = getTodoService(); // This will never return null after lazy init
|
|
130
|
+
await todoSvc.initialize();
|
|
131
|
+
const todoTools = todoSvc.getTools();
|
|
128
132
|
const todoServiceTools = todoTools.map(tool => ({
|
|
129
133
|
name: tool.name.replace('todo-', ''),
|
|
130
134
|
description: tool.description || '',
|
|
@@ -689,7 +693,7 @@ export async function executeMCPTool(toolName, args, abortSignal, onTokenUpdate)
|
|
|
689
693
|
}
|
|
690
694
|
if (serviceName === 'todo') {
|
|
691
695
|
// Handle built-in TODO tools (no connection needed)
|
|
692
|
-
return await
|
|
696
|
+
return await getTodoService().executeTool(actualToolName, args);
|
|
693
697
|
}
|
|
694
698
|
else if (serviceName === 'notebook') {
|
|
695
699
|
// Handle built-in Notebook tools (no connection needed)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "snow-ai",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.11",
|
|
4
4
|
"description": "Intelligent Command Line Assistant powered by AI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -24,19 +24,22 @@
|
|
|
24
24
|
},
|
|
25
25
|
"homepage": "https://github.com/MayDay-wpf/snow-cli#readme",
|
|
26
26
|
"engines": {
|
|
27
|
-
"node": ">=16"
|
|
27
|
+
"node": ">=16",
|
|
28
|
+
"npm": ">=8.3.0"
|
|
28
29
|
},
|
|
29
30
|
"scripts": {
|
|
30
31
|
"build": "tsc",
|
|
31
32
|
"dev": "tsc --watch",
|
|
32
33
|
"start": "node dist/cli.js",
|
|
33
34
|
"prepublishOnly": "npm run build",
|
|
35
|
+
"postinstall": "node scripts/postinstall.cjs",
|
|
34
36
|
"test": "prettier --check . && xo && ava",
|
|
35
37
|
"lint": "xo",
|
|
36
38
|
"format": "prettier --write ."
|
|
37
39
|
},
|
|
38
40
|
"files": [
|
|
39
|
-
"dist"
|
|
41
|
+
"dist",
|
|
42
|
+
"scripts"
|
|
40
43
|
],
|
|
41
44
|
"dependencies": {
|
|
42
45
|
"@inkjs/ui": "^2.0.0",
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Post-install script to provide installation optimization tips for users
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const https = require('https');
|
|
8
|
+
const { execSync } = require('child_process');
|
|
9
|
+
|
|
10
|
+
// ANSI color codes
|
|
11
|
+
const colors = {
|
|
12
|
+
reset: '\x1b[0m',
|
|
13
|
+
bright: '\x1b[1m',
|
|
14
|
+
cyan: '\x1b[36m',
|
|
15
|
+
yellow: '\x1b[33m',
|
|
16
|
+
green: '\x1b[32m',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Detect if user is in China based on IP geolocation
|
|
21
|
+
*/
|
|
22
|
+
function detectRegion() {
|
|
23
|
+
return new Promise((resolve) => {
|
|
24
|
+
const timeout = setTimeout(() => resolve('unknown'), 3000);
|
|
25
|
+
|
|
26
|
+
https.get('https://ipapi.co/json/', (res) => {
|
|
27
|
+
let data = '';
|
|
28
|
+
res.on('data', (chunk) => data += chunk);
|
|
29
|
+
res.on('end', () => {
|
|
30
|
+
clearTimeout(timeout);
|
|
31
|
+
try {
|
|
32
|
+
const info = JSON.parse(data);
|
|
33
|
+
resolve(info.country_code === 'CN' ? 'china' : 'other');
|
|
34
|
+
} catch {
|
|
35
|
+
resolve('unknown');
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}).on('error', () => {
|
|
39
|
+
clearTimeout(timeout);
|
|
40
|
+
resolve('unknown');
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Check current npm registry
|
|
47
|
+
*/
|
|
48
|
+
function getCurrentRegistry() {
|
|
49
|
+
try {
|
|
50
|
+
const registry = execSync('npm config get registry', { encoding: 'utf8' }).trim();
|
|
51
|
+
return registry;
|
|
52
|
+
} catch {
|
|
53
|
+
return 'https://registry.npmjs.org';
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Main function
|
|
59
|
+
*/
|
|
60
|
+
async function main() {
|
|
61
|
+
// Skip if running in CI environment
|
|
62
|
+
if (process.env.CI || process.env.CONTINUOUS_INTEGRATION) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const currentRegistry = getCurrentRegistry();
|
|
67
|
+
const isUsingMirror = currentRegistry.includes('npmmirror.com') ||
|
|
68
|
+
currentRegistry.includes('taobao.org');
|
|
69
|
+
|
|
70
|
+
// If already using a mirror, skip the tips
|
|
71
|
+
if (isUsingMirror) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
console.log(`\n${colors.cyan}${colors.bright}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${colors.reset}`);
|
|
76
|
+
console.log(`${colors.cyan}${colors.bright} Snow AI - Installation Optimization Tips${colors.reset}`);
|
|
77
|
+
console.log(`${colors.cyan}${colors.bright}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${colors.reset}\n`);
|
|
78
|
+
|
|
79
|
+
const region = await detectRegion();
|
|
80
|
+
|
|
81
|
+
if (region === 'china') {
|
|
82
|
+
console.log(`${colors.yellow}检测到您在中国大陆地区,建议配置 npm 镜像源以加速安装:${colors.reset}\n`);
|
|
83
|
+
console.log(`${colors.green}# 方案 1: 使用淘宝镜像 (推荐)${colors.reset}`);
|
|
84
|
+
console.log(` npm config set registry https://registry.npmmirror.com\n`);
|
|
85
|
+
console.log(`${colors.green}# 方案 2: 临时使用镜像安装${colors.reset}`);
|
|
86
|
+
console.log(` npm install -g snow-ai --registry=https://registry.npmmirror.com\n`);
|
|
87
|
+
console.log(`${colors.green}# 恢复官方源${colors.reset}`);
|
|
88
|
+
console.log(` npm config set registry https://registry.npmjs.org\n`);
|
|
89
|
+
} else {
|
|
90
|
+
console.log(`${colors.yellow}To speed up npm installation, you can:${colors.reset}\n`);
|
|
91
|
+
console.log(`${colors.green}# Enable parallel downloads${colors.reset}`);
|
|
92
|
+
console.log(` npm config set maxsockets 10\n`);
|
|
93
|
+
console.log(`${colors.green}# Use offline cache when possible${colors.reset}`);
|
|
94
|
+
console.log(` npm config set prefer-offline true\n`);
|
|
95
|
+
console.log(`${colors.green}# Skip unnecessary checks${colors.reset}`);
|
|
96
|
+
console.log(` npm config set audit false\n`);
|
|
97
|
+
console.log(` npm config set fund false\n`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
console.log(`${colors.cyan}Current registry: ${currentRegistry}${colors.reset}`);
|
|
101
|
+
console.log(`${colors.cyan}${colors.bright}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${colors.reset}\n`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
main().catch(() => {
|
|
105
|
+
// Silently fail - don't break installation
|
|
106
|
+
});
|