xshat-lite 1.0.0 → 1.1.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 CHANGED
@@ -36,6 +36,12 @@ npm install
36
36
  npm install -g xshat-lite
37
37
  ```
38
38
 
39
+ 首次全局启动时,程序会自动进入一次轻量向导,帮助你:
40
+
41
+ - 选择更适合首用的默认 provider
42
+ - 打开可见浏览器支持
43
+ - 预热浏览器环境并准备首次聊天
44
+
39
45
  本地调试全局命令:
40
46
 
41
47
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xshat-lite",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "CLI chat and agent tool powered by free AI web providers",
5
5
  "type": "module",
6
6
  "main": "src/index.mjs",
package/src/index.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import readline from 'node:readline/promises';
2
+ import fs from 'node:fs';
2
3
  import { stdin as input, stdout as output } from 'process';
3
4
  import { resolve } from 'node:path';
4
5
  import { confirm, input as promptInput, select } from '@inquirer/prompts';
@@ -50,8 +51,11 @@ import {
50
51
  import { runRuntimeMaintenance } from './modules/runtime-maintenance.mjs';
51
52
  import { waitForKeypress } from './utils/key-input.mjs';
52
53
  import config, {
54
+ getRuntimeRootDir,
53
55
  getProvider,
54
- listProviders
56
+ getUserConfigPath,
57
+ listProviders,
58
+ updateConfigValue
55
59
  } from './utils/config.mjs';
56
60
 
57
61
  let chatManager = null;
@@ -67,6 +71,117 @@ let lastSubmittedQuestion = '';
67
71
  let lastAgentOrReplySummary = '';
68
72
  const inputHistory = [];
69
73
 
74
+ function getRecommendedFirstRunProviderId() {
75
+ const candidates = ['duckai', 'qwen', 'deepai', 'huggingchat', 'you'];
76
+ const available = new Set(listProviders().map((provider) => provider.id));
77
+ return candidates.find((id) => available.has(id)) || getProvider().id;
78
+ }
79
+
80
+ function isFirstRunGuideNeeded() {
81
+ if (config.meta?.firstRunCompleted) {
82
+ return false;
83
+ }
84
+
85
+ return !fs.existsSync(getUserConfigPath());
86
+ }
87
+
88
+ function buildFirstRunGuideRows() {
89
+ const defaultProvider = getProvider();
90
+ const recommendedProvider = getProvider(getRecommendedFirstRunProviderId());
91
+ const userDataDirExists = fs.existsSync(config.browser?.userDataDir || '');
92
+
93
+ return [
94
+ { label: '运行目录', value: getRuntimeRootDir() },
95
+ { label: '默认 Provider', value: defaultProvider.name },
96
+ { label: '推荐首选', value: recommendedProvider.name },
97
+ { label: '浏览器模式', value: config.browser?.headless ? 'Headless' : 'Visible' },
98
+ { label: '允许可见浏览器', value: String(Boolean(config.browser?.allowVisibleBrowser)) },
99
+ { label: '资料目录', value: config.browser?.userDataDir || '(未设置)' },
100
+ { label: '资料目录状态', value: userDataDirExists ? '已存在' : '首次启动时创建' }
101
+ ];
102
+ }
103
+
104
+ async function warmupBrowserEnvironment() {
105
+ ui.printInfo('正在预热浏览器环境,首次启动可能需要下载浏览器内核...');
106
+ try {
107
+ await browserManager.init({
108
+ headless: true
109
+ });
110
+ await browserManager.close();
111
+ ui.printSuccess('浏览器环境已准备好');
112
+ return true;
113
+ } catch (error) {
114
+ ui.printWarning(`浏览器预热未完成: ${error.message}`);
115
+ return false;
116
+ }
117
+ }
118
+
119
+ async function applyRecommendedFirstRunProfile() {
120
+ const recommendedProviderId = getRecommendedFirstRunProviderId();
121
+ await Promise.all([
122
+ config.defaultProvider !== recommendedProviderId
123
+ ? updateConfigValue('defaultProvider', recommendedProviderId)
124
+ : Promise.resolve(),
125
+ config.browser?.headless
126
+ ? updateConfigValue('browser.headless', false)
127
+ : Promise.resolve(),
128
+ config.browser?.allowVisibleBrowser !== true
129
+ ? updateConfigValue('browser.allowVisibleBrowser', true)
130
+ : Promise.resolve(),
131
+ config.browser?.autoRecover !== true
132
+ ? updateConfigValue('browser.autoRecover', true)
133
+ : Promise.resolve()
134
+ ]);
135
+ }
136
+
137
+ async function markFirstRunGuideCompleted() {
138
+ await updateConfigValue('meta.firstRunCompleted', true);
139
+ }
140
+
141
+ async function maybeRunFirstLaunchGuide() {
142
+ if (!isFirstRunGuideNeeded()) {
143
+ return;
144
+ }
145
+
146
+ ui.clear();
147
+ ui.printTitle('首次启动向导');
148
+ ui.printNewline();
149
+ ui.printInfo('检测到这是一个全新的 XShat 运行环境。先做一轮轻量初始化,后面进聊天会顺很多。');
150
+ ui.printNewline();
151
+ ui.printKeyValueTable('环境概览', buildFirstRunGuideRows());
152
+ ui.printNewline();
153
+ ui.printInfo('建议第一次先使用免登录或更稳定的站点,再按需要切换到 Gemini / ChatGPT / Copilot。');
154
+ ui.printNewline();
155
+
156
+ const choice = await select({
157
+ message: '选择首次启动动作',
158
+ choices: [
159
+ { name: '一键推荐设置(Duck.ai/Qwen + 可见浏览器)', value: 'recommended' },
160
+ { name: '只预热浏览器环境', value: 'warmup' },
161
+ { name: '打开系统设置自己调整', value: 'settings' },
162
+ { name: '暂时跳过', value: 'skip' }
163
+ ],
164
+ default: 'recommended'
165
+ });
166
+
167
+ if (choice === 'recommended') {
168
+ await applyRecommendedFirstRunProfile();
169
+ ui.printSuccess(`已应用首次推荐设置,默认提供方切到 ${getProvider().name}`);
170
+ await warmupBrowserEnvironment();
171
+ } else if (choice === 'warmup') {
172
+ await warmupBrowserEnvironment();
173
+ } else if (choice === 'settings') {
174
+ await showSettingsMenu({ ui, config });
175
+ } else {
176
+ ui.printMuted('已跳过首次向导,后续仍可在 /config 里调整。');
177
+ }
178
+
179
+ await markFirstRunGuideCompleted();
180
+ ui.printNewline();
181
+ ui.printInfo('首次向导完成。按回车进入主菜单...');
182
+ await askInput('', true);
183
+ }
184
+
70
185
  function getSessionCompressionOptions() {
71
186
  return {
72
187
  compressThreshold: Number(config.session?.compressThreshold) || 12,
@@ -2743,11 +2858,12 @@ async function main() {
2743
2858
  await logger.init();
2744
2859
  await sessionManager.init();
2745
2860
  await taskStore.init();
2861
+ setupGracefulShutdown();
2862
+ await maybeRunFirstLaunchGuide();
2863
+
2746
2864
  const maintenance = await runRuntimeMaintenance();
2747
2865
  ensureTaskRunner().startLoop();
2748
2866
 
2749
- setupGracefulShutdown();
2750
-
2751
2867
  if (maintenance.tempRemoved > 0) {
2752
2868
  ui.printMuted(`启动清理: 已移除 ${maintenance.tempRemoved} 个临时文件`);
2753
2869
  }
@@ -219,6 +219,10 @@ export function getUserConfigPath() {
219
219
  return userConfigPath;
220
220
  }
221
221
 
222
+ export function getRuntimeRootDir() {
223
+ return runtimeRootDir;
224
+ }
225
+
222
226
  export function getUserConfigOverrides() {
223
227
  return JSON.parse(JSON.stringify(userConfigOverrides));
224
228
  }