snow-ai 0.4.9 → 0.4.10

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 CHANGED
@@ -1,12 +1,14 @@
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 Spinner from 'ink-spinner';
4
5
  import WelcomeScreen from './ui/pages/WelcomeScreen.js';
5
- import MCPConfigScreen from './ui/pages/MCPConfigScreen.js';
6
- import SystemPromptConfigScreen from './ui/pages/SystemPromptConfigScreen.js';
7
- import CustomHeadersScreen from './ui/pages/CustomHeadersScreen.js';
8
- import ChatScreen from './ui/pages/ChatScreen.js';
9
- import HeadlessModeScreen from './ui/pages/HeadlessModeScreen.js';
6
+ // Lazy load heavy components to improve startup time
7
+ const ChatScreen = React.lazy(() => import('./ui/pages/ChatScreen.js'));
8
+ const HeadlessModeScreen = React.lazy(() => import('./ui/pages/HeadlessModeScreen.js'));
9
+ const MCPConfigScreen = React.lazy(() => import('./ui/pages/MCPConfigScreen.js'));
10
+ const SystemPromptConfigScreen = React.lazy(() => import('./ui/pages/SystemPromptConfigScreen.js'));
11
+ const CustomHeadersScreen = React.lazy(() => import('./ui/pages/CustomHeadersScreen.js'));
10
12
  import { useGlobalExit, } from './hooks/useGlobalExit.js';
11
13
  import { onNavigate } from './hooks/useGlobalNavigation.js';
12
14
  import { useTerminalSize } from './hooks/useTerminalSize.js';
@@ -56,21 +58,29 @@ function AppContent({ version, skipWelcome, }) {
56
58
  }
57
59
  };
58
60
  const renderView = () => {
61
+ const loadingFallback = (React.createElement(Box, null,
62
+ React.createElement(Text, { color: "cyan" },
63
+ React.createElement(Spinner, { type: "dots" })),
64
+ React.createElement(Text, null, " Loading...")));
59
65
  switch (currentView) {
60
66
  case 'welcome':
61
67
  return (React.createElement(WelcomeScreen, { version: version, onMenuSelect: handleMenuSelect }));
62
68
  case 'chat':
63
- return React.createElement(ChatScreen, { key: chatScreenKey, skipWelcome: skipWelcome });
69
+ return (React.createElement(Suspense, { fallback: loadingFallback },
70
+ React.createElement(ChatScreen, { key: chatScreenKey, skipWelcome: skipWelcome })));
64
71
  case 'settings':
65
72
  return (React.createElement(Box, { flexDirection: "column" },
66
73
  React.createElement(Text, { color: "blue" }, "Settings"),
67
74
  React.createElement(Text, { color: "gray" }, "Settings interface would be implemented here")));
68
75
  case 'mcp':
69
- return (React.createElement(MCPConfigScreen, { onBack: () => setCurrentView('welcome'), onSave: () => setCurrentView('welcome') }));
76
+ return (React.createElement(Suspense, { fallback: loadingFallback },
77
+ React.createElement(MCPConfigScreen, { onBack: () => setCurrentView('welcome'), onSave: () => setCurrentView('welcome') })));
70
78
  case 'systemprompt':
71
- return (React.createElement(SystemPromptConfigScreen, { onBack: () => setCurrentView('welcome') }));
79
+ return (React.createElement(Suspense, { fallback: loadingFallback },
80
+ React.createElement(SystemPromptConfigScreen, { onBack: () => setCurrentView('welcome') })));
72
81
  case 'customheaders':
73
- return React.createElement(CustomHeadersScreen, { onBack: () => setCurrentView('welcome') });
82
+ return (React.createElement(Suspense, { fallback: loadingFallback },
83
+ React.createElement(CustomHeadersScreen, { onBack: () => setCurrentView('welcome') })));
74
84
  default:
75
85
  return (React.createElement(WelcomeScreen, { version: version, onMenuSelect: handleMenuSelect }));
76
86
  }
@@ -84,9 +94,14 @@ export default function App({ version, skipWelcome, headlessPrompt }) {
84
94
  // If headless prompt is provided, use headless mode
85
95
  // Wrap in I18nProvider since HeadlessModeScreen might use hooks that depend on it
86
96
  if (headlessPrompt) {
97
+ const loadingFallback = (React.createElement(Box, null,
98
+ React.createElement(Text, { color: "cyan" },
99
+ React.createElement(Spinner, { type: "dots" })),
100
+ React.createElement(Text, null, " Loading...")));
87
101
  return (React.createElement(I18nProvider, null,
88
102
  React.createElement(ThemeProvider, null,
89
- React.createElement(HeadlessModeScreen, { prompt: headlessPrompt, onComplete: () => process.exit(0) }))));
103
+ React.createElement(Suspense, { fallback: loadingFallback },
104
+ React.createElement(HeadlessModeScreen, { prompt: headlessPrompt, onComplete: () => process.exit(0) })))));
90
105
  }
91
106
  return (React.createElement(I18nProvider, null,
92
107
  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');
@@ -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
- const profileConfig = loadProfile(activeProfile);
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, switch to default
293
- switchProfile('default');
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
  }
@@ -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 defaultLogger: Logger;
30
- export default defaultLogger;
31
- export { defaultLogger as logger };
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 };
@@ -92,6 +92,31 @@ export class Logger {
92
92
  this.writeLog(level, message, meta);
93
93
  }
94
94
  }
95
- const defaultLogger = new Logger();
96
- export default defaultLogger;
97
- export { defaultLogger as logger };
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,7 +19,7 @@ export interface MCPServiceTools {
19
19
  error?: string;
20
20
  }
21
21
  /**
22
- * Get the TODO service instance
22
+ * Get the TODO service instance (lazy initialization)
23
23
  */
24
24
  export declare function getTodoService(): TodoService;
25
25
  /**
@@ -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
- // Initialize TODO service with sessionManager accessor
23
- const todoService = new TodoService(path.join(os.homedir(), '.snow'), () => {
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
- await todoService.initialize();
127
- const todoTools = todoService.getTools();
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 todoService.executeTool(actualToolName, args);
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.9",
3
+ "version": "0.4.10",
4
4
  "description": "Intelligent Command Line Assistant powered by AI",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -24,7 +24,8 @@
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",