quick-sh 1.0.0 → 1.0.3

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/lib/help.js CHANGED
@@ -1,59 +1,41 @@
1
+ const { t } = require('./i18n');
2
+
1
3
  // 显示帮助信息
2
4
  function showHelp() {
3
5
  console.log(`
4
- 🚀 Quick Shell - Local Script Management Tool
5
-
6
- DESCRIPTION:
7
- Quick Shell (q) is a command-line tool that helps you manage and execute
8
- local scripts quickly. It supports JavaScript (.js, .mjs), Shell (.sh)
9
- scripts, and directory-based scripts.
10
-
11
- USAGE:
12
- q [command] [options]
13
- q <script> [args...]
14
-
15
- COMMANDS:
16
- -path <directory> Set the script directory path
17
- -list, -l Show current configuration and available scripts
18
- -help, -h Show this help message
19
- --version, -V Show version number
20
-
21
- SCRIPT EXECUTION:
22
- q <script> Execute a script by name
23
- q <script> [args] Execute a script with arguments
24
-
25
- EXAMPLES:
26
- # Setup
27
- q -path /path/to/scripts Set script directory
28
- q -list View current configuration
29
-
30
- # Execute scripts
31
- q hello Run hello.js or hello.sh
32
- q backup /src /dest Run backup script with arguments
33
- q deploy production --verbose Run deploy script with multiple args
34
- q my-folder Run index.js/index.sh from my-folder/
35
-
36
- SUPPORTED FILE TYPES:
37
- .js, .mjs JavaScript files (executed with node)
38
- .sh Shell scripts (executed with sh)
39
- directories Looks for index.js, index.sh, or index.mjs
40
-
41
- ALIAS CONFIGURATION:
42
- Create a config.json file in your script directory to define aliases:
43
-
44
- {
45
- "chat": {"bin": "./chat/src/main.js"}, // Relative path
46
- "python3": "/usr/bin/python3", // Absolute path
47
- "w": "which" // System command
48
- }
49
-
50
- Execution priority: Alias config > Script files > System commands
51
-
52
- CONFIGURATION:
53
- Global config: ~/.quick-sh/config.json
54
- Alias config: <script-path>/config.json
6
+ 🚀 ${t('app.name')}
7
+
8
+ ${t('help.usage')}
9
+ q <script> [args...] ${t('help.executeScript')}
10
+ q -l ${t('help.listScripts')}
11
+ q -path <dir> ${t('help.setDirectory')}
12
+ q -lang [code] ${t('help.setLanguage')}
13
+ q -ai [-config/-use] ${t('help.aiChat')}
14
+
15
+ ${t('help.localScripts')}
16
+ q hello ${t('help.runScript', { script: 'hello.js or hello.sh' })}
17
+ q backup /src /dest ${t('help.runWithArgs')}
55
18
 
56
- For more information, visit: https://github.com/your-repo/quick-sh
19
+ ${t('help.remoteScripts')}
20
+ q --sources ${t('help.listSources')}
21
+ q --add-source <name> github <url> ${t('help.addGithubSource')}
22
+ q --download <source> <script> ${t('help.downloadScript')}
23
+ q --remote-list ${t('help.listDownloaded')}
24
+
25
+ ${t('help.examples')}
26
+ q -path ~/scripts ${t('help.setDirectory')}
27
+ q -lang zh ${t('help.setChineseExample')}
28
+ q -lang ${t('help.showLanguageExample')}
29
+ q -ai -config ${t('help.configureAI')}
30
+ q -ai -use deepseek-v3 ${t('help.useAIModel')}
31
+ q -l ${t('help.listScripts')}
32
+ q --add-source utils github https://github.com/user/utils
33
+ q --download utils backup.js
34
+ q backup ${t('help.executeScript')}
35
+
36
+ ${t('help.configFiles')}
37
+ ~/.quick-sh/config.json ${t('help.globalConfig')}
38
+ <script-path>/config.json ${t('help.aliasConfig')}
57
39
  `);
58
40
  }
59
41
 
package/lib/i18n.js ADDED
@@ -0,0 +1,153 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const os = require('os');
4
+
5
+ // 语言配置文件路径
6
+ const LANG_DIR = path.join(__dirname, '..', 'locales');
7
+
8
+ // 默认语言
9
+ const DEFAULT_LANG = 'en';
10
+
11
+ // 支持的语言
12
+ const SUPPORTED_LANGUAGES = {
13
+ 'en': 'English',
14
+ 'zh': '中文',
15
+ 'ja': '日本語'
16
+ };
17
+
18
+ // 当前语言
19
+ let currentLanguage = DEFAULT_LANG;
20
+ let translations = {};
21
+
22
+ // 检测系统语言
23
+ function detectSystemLanguage() {
24
+ try {
25
+ // 获取系统语言环境
26
+ const locale = process.env.LC_ALL || process.env.LC_MESSAGES || process.env.LANG || process.env.LANGUAGE || 'en_US';
27
+
28
+ // 提取语言代码
29
+ const langCode = locale.split(/[_.-]/)[0].toLowerCase();
30
+
31
+ // 语言映射
32
+ const langMap = {
33
+ 'en': 'en',
34
+ 'zh': 'zh',
35
+ 'ja': 'ja',
36
+ 'chinese': 'zh',
37
+ 'japanese': 'ja',
38
+ 'english': 'en'
39
+ };
40
+
41
+ return langMap[langCode] || DEFAULT_LANG;
42
+ } catch (error) {
43
+ return DEFAULT_LANG;
44
+ }
45
+ }
46
+
47
+ // 加载语言文件
48
+ async function loadLanguage(lang = null) {
49
+ try {
50
+ let targetLang = lang;
51
+
52
+ // 如果没有指定语言,先尝试获取用户设置的语言
53
+ if (!targetLang) {
54
+ try {
55
+ const { getUserLanguage } = require('./config');
56
+ const userLang = await getUserLanguage();
57
+ targetLang = userLang || detectSystemLanguage();
58
+ } catch (error) {
59
+ // 如果获取用户语言失败,回退到系统检测
60
+ targetLang = detectSystemLanguage();
61
+ }
62
+ }
63
+
64
+ // 确保语言被支持
65
+ if (!SUPPORTED_LANGUAGES[targetLang]) {
66
+ currentLanguage = DEFAULT_LANG;
67
+ } else {
68
+ currentLanguage = targetLang;
69
+ }
70
+
71
+ // 加载翻译文件
72
+ const langFile = path.join(LANG_DIR, `${currentLanguage}.json`);
73
+
74
+ if (await fs.pathExists(langFile)) {
75
+ translations = await fs.readJson(langFile);
76
+ } else {
77
+ // 如果文件不存在,回退到英文
78
+ currentLanguage = DEFAULT_LANG;
79
+ const fallbackFile = path.join(LANG_DIR, `${DEFAULT_LANG}.json`);
80
+ if (await fs.pathExists(fallbackFile)) {
81
+ translations = await fs.readJson(fallbackFile);
82
+ } else {
83
+ translations = {};
84
+ }
85
+ }
86
+ } catch (error) {
87
+ // 发生错误时使用默认语言
88
+ currentLanguage = DEFAULT_LANG;
89
+ translations = {};
90
+ }
91
+ }
92
+
93
+ // 获取翻译文本
94
+ function t(key, params = {}) {
95
+ try {
96
+ // 支持嵌套键值,如 'errors.notFound'
97
+ const keys = key.split('.');
98
+ let value = translations;
99
+
100
+ for (const k of keys) {
101
+ if (value && typeof value === 'object' && k in value) {
102
+ value = value[k];
103
+ } else {
104
+ // 如果找不到翻译,返回键值本身
105
+ return key;
106
+ }
107
+ }
108
+
109
+ // 如果找到的不是字符串,返回键值
110
+ if (typeof value !== 'string') {
111
+ return key;
112
+ }
113
+
114
+ // 替换参数
115
+ let result = value;
116
+ for (const [paramKey, paramValue] of Object.entries(params)) {
117
+ result = result.replace(new RegExp(`{{${paramKey}}}`, 'g'), paramValue);
118
+ }
119
+
120
+ return result;
121
+ } catch (error) {
122
+ return key;
123
+ }
124
+ }
125
+
126
+ // 获取当前语言
127
+ function getCurrentLanguage() {
128
+ return currentLanguage;
129
+ }
130
+
131
+ // 获取支持的语言列表
132
+ function getSupportedLanguages() {
133
+ return SUPPORTED_LANGUAGES;
134
+ }
135
+
136
+ // 设置语言
137
+ async function setLanguage(lang) {
138
+ await loadLanguage(lang);
139
+ }
140
+
141
+ // 初始化国际化系统
142
+ async function initI18n() {
143
+ await loadLanguage();
144
+ }
145
+
146
+ module.exports = {
147
+ t,
148
+ getCurrentLanguage,
149
+ getSupportedLanguages,
150
+ setLanguage,
151
+ initI18n,
152
+ detectSystemLanguage
153
+ };
@@ -0,0 +1,317 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const https = require('https');
4
+ const http = require('http');
5
+ const { URL } = require('url');
6
+ const os = require('os');
7
+ const { readConfig, writeConfig } = require('./config');
8
+ const { t } = require('./i18n');
9
+
10
+ // 远程脚本存储目录
11
+ const REMOTE_SCRIPTS_DIR = path.join(os.homedir(), '.quick-sh', 'remote-scripts');
12
+
13
+ // 支持的源类型
14
+ const SOURCE_TYPES = {
15
+ GITHUB: 'github',
16
+ RAW_URL: 'raw_url',
17
+ GIT: 'git'
18
+ };
19
+
20
+ // 确保远程脚本目录存在
21
+ async function ensureRemoteScriptsDir() {
22
+ await fs.ensureDir(REMOTE_SCRIPTS_DIR);
23
+ }
24
+
25
+ // 获取所有配置的源
26
+ async function getSources() {
27
+ const config = await readConfig();
28
+ return config.sources || {};
29
+ }
30
+
31
+ // 添加新源
32
+ async function addSource(name, type, url, options = {}) {
33
+ if (!name || !type || !url) {
34
+ throw new Error(t('remote.sourceNameRequired'));
35
+ }
36
+
37
+ if (!Object.values(SOURCE_TYPES).includes(type)) {
38
+ throw new Error(t('remote.invalidSourceType', {
39
+ type,
40
+ types: Object.values(SOURCE_TYPES).join(', ')
41
+ }));
42
+ }
43
+
44
+ const config = await readConfig();
45
+ if (!config.sources) {
46
+ config.sources = {};
47
+ }
48
+
49
+ config.sources[name] = {
50
+ type,
51
+ url,
52
+ ...options,
53
+ addedAt: new Date().toISOString()
54
+ };
55
+
56
+ await writeConfig(config);
57
+ console.log(t('remote.sourceAdded', { name }));
58
+ }
59
+
60
+ // 删除源
61
+ async function removeSource(name) {
62
+ const config = await readConfig();
63
+ if (!config.sources || !config.sources[name]) {
64
+ throw new Error(t('remote.sourceNotFound', { name }));
65
+ }
66
+
67
+ delete config.sources[name];
68
+ await writeConfig(config);
69
+
70
+ // 删除该源下载的脚本
71
+ const sourceDir = path.join(REMOTE_SCRIPTS_DIR, name);
72
+ if (await fs.pathExists(sourceDir)) {
73
+ await fs.remove(sourceDir);
74
+ console.log(t('remote.scriptsDeleted', { name }));
75
+ }
76
+
77
+ console.log(t('remote.sourceRemoved', { name }));
78
+ }
79
+
80
+ // 列出所有源
81
+ async function listSources() {
82
+ const sources = await getSources();
83
+
84
+ if (Object.keys(sources).length === 0) {
85
+ console.log(t('remote.noSources'));
86
+ return;
87
+ }
88
+
89
+ console.log('\n' + t('remote.sourcesTitle'));
90
+ console.log('=' .repeat(50));
91
+
92
+ for (const [name, source] of Object.entries(sources)) {
93
+ console.log('\n' + t('remote.sourceInfo', { name }));
94
+ console.log(t('remote.sourceType', { type: source.type }));
95
+ console.log(t('remote.sourceUrl', { url: source.url }));
96
+ console.log(t('remote.sourceAdded2', { date: new Date(source.addedAt).toLocaleString() }));
97
+
98
+ // 显示该源下载的脚本数量
99
+ const sourceDir = path.join(REMOTE_SCRIPTS_DIR, name);
100
+ if (await fs.pathExists(sourceDir)) {
101
+ const scripts = await fs.readdir(sourceDir);
102
+ const scriptCount = scripts.filter(f => !f.startsWith('.')).length;
103
+ console.log(t('remote.scriptCount', { count: scriptCount }));
104
+ } else {
105
+ console.log(t('remote.scriptCount', { count: 0 }));
106
+ }
107
+ }
108
+ }
109
+
110
+ // HTTP/HTTPS请求下载文件
111
+ function downloadFile(url) {
112
+ return new Promise((resolve, reject) => {
113
+ const client = url.startsWith('https:') ? https : http;
114
+ const request = client.get(url, (response) => {
115
+ if (response.statusCode === 200) {
116
+ let data = '';
117
+ response.on('data', chunk => data += chunk);
118
+ response.on('end', () => resolve(data));
119
+ } else if (response.statusCode === 302 || response.statusCode === 301) {
120
+ // 处理重定向
121
+ downloadFile(response.headers.location).then(resolve).catch(reject);
122
+ } else {
123
+ reject(new Error(t('errors.httpError', {
124
+ code: response.statusCode,
125
+ message: response.statusMessage
126
+ })));
127
+ }
128
+ });
129
+
130
+ request.on('error', (error) => {
131
+ reject(new Error(t('errors.networkError', { error: error.message })));
132
+ });
133
+ request.setTimeout(30000, () => {
134
+ request.destroy();
135
+ reject(new Error(t('errors.downloadTimeout')));
136
+ });
137
+ });
138
+ }
139
+
140
+ // 从GitHub下载脚本
141
+ async function downloadFromGithub(source, scriptPath) {
142
+ const { url, branch = 'main' } = source;
143
+
144
+ // 解析GitHub URL
145
+ let repoMatch = url.match(/github\.com\/([^\/]+)\/([^\/]+)/);
146
+ if (!repoMatch) {
147
+ throw new Error(t('remote.invalidGithubUrl'));
148
+ }
149
+
150
+ const [, owner, repo] = repoMatch;
151
+ const cleanRepo = repo.replace(/\.git$/, '');
152
+
153
+ // 构建raw.githubusercontent.com URL
154
+ const rawUrl = `https://raw.githubusercontent.com/${owner}/${cleanRepo}/${branch}/${scriptPath}`;
155
+
156
+ console.log(t('remote.downloadingFromGithub', { url: rawUrl }));
157
+ return await downloadFile(rawUrl);
158
+ }
159
+
160
+ // 从原始URL下载脚本
161
+ async function downloadFromRawUrl(source, scriptPath) {
162
+ const { url } = source;
163
+ const fullUrl = `${url.replace(/\/$/, '')}/${scriptPath}`;
164
+
165
+ console.log(t('remote.downloadingFromUrl', { url: fullUrl }));
166
+ return await downloadFile(fullUrl);
167
+ }
168
+
169
+ // 下载指定脚本
170
+ async function downloadScript(sourceName, scriptPath, localName = null) {
171
+ await ensureRemoteScriptsDir();
172
+
173
+ const sources = await getSources();
174
+ const source = sources[sourceName];
175
+
176
+ if (!source) {
177
+ throw new Error(t('remote.sourceNotFound', { name: sourceName }));
178
+ }
179
+
180
+ let scriptContent;
181
+
182
+ try {
183
+ switch (source.type) {
184
+ case SOURCE_TYPES.GITHUB:
185
+ scriptContent = await downloadFromGithub(source, scriptPath);
186
+ break;
187
+ case SOURCE_TYPES.RAW_URL:
188
+ scriptContent = await downloadFromRawUrl(source, scriptPath);
189
+ break;
190
+ default:
191
+ throw new Error(`暂不支持源类型: ${source.type}`);
192
+ }
193
+ } catch (error) {
194
+ throw new Error(t('remote.downloadFailed', { error: error.message }));
195
+ }
196
+
197
+ // 确定本地文件名
198
+ const fileName = localName || path.basename(scriptPath);
199
+ const sourceDir = path.join(REMOTE_SCRIPTS_DIR, sourceName);
200
+ await fs.ensureDir(sourceDir);
201
+
202
+ const localPath = path.join(sourceDir, fileName);
203
+
204
+ // 写入文件
205
+ await fs.writeFile(localPath, scriptContent);
206
+
207
+ // 如果是脚本文件,设置执行权限
208
+ const ext = path.extname(fileName);
209
+ if (['.js', '.sh', '.mjs'].includes(ext)) {
210
+ await fs.chmod(localPath, 0o755);
211
+ }
212
+
213
+ console.log(t('remote.downloadSuccess', { path: localPath }));
214
+
215
+ // 返回本地路径用于进一步处理
216
+ return localPath;
217
+ }
218
+
219
+ // 列出已下载的远程脚本
220
+ async function listRemoteScripts() {
221
+ await ensureRemoteScriptsDir();
222
+
223
+ const sources = await getSources();
224
+ let totalScripts = 0;
225
+
226
+ console.log('\n' + t('remote.remoteScriptsTitle'));
227
+ console.log('=' .repeat(50));
228
+
229
+ for (const [sourceName, source] of Object.entries(sources)) {
230
+ const sourceDir = path.join(REMOTE_SCRIPTS_DIR, sourceName);
231
+ if (await fs.pathExists(sourceDir)) {
232
+ const files = await fs.readdir(sourceDir);
233
+ const scripts = files.filter(f => !f.startsWith('.'));
234
+
235
+ if (scripts.length > 0) {
236
+ console.log('\n' + t('remote.sourcePrefix', { name: sourceName, type: source.type }));
237
+ scripts.sort().forEach(script => {
238
+ console.log(t('remote.scriptFile', { script }));
239
+ totalScripts++;
240
+ });
241
+ }
242
+ }
243
+ }
244
+
245
+ if (totalScripts === 0) {
246
+ console.log(t('remote.noRemoteDownloaded'));
247
+ } else {
248
+ console.log('\n' + t('remote.totalScripts', { count: totalScripts }));
249
+ }
250
+ }
251
+
252
+ // 删除已下载的脚本
253
+ async function removeRemoteScript(sourceName, scriptName) {
254
+ const sourceDir = path.join(REMOTE_SCRIPTS_DIR, sourceName);
255
+ const scriptPath = path.join(sourceDir, scriptName);
256
+
257
+ if (!await fs.pathExists(scriptPath)) {
258
+ throw new Error(t('remote.scriptNotFound', { script: `${sourceName}/${scriptName}` }));
259
+ }
260
+
261
+ await fs.remove(scriptPath);
262
+ console.log(t('remote.scriptRemoved', { script: `${sourceName}/${scriptName}` }));
263
+
264
+ // 如果源目录为空,也删除目录
265
+ const remainingFiles = await fs.readdir(sourceDir);
266
+ if (remainingFiles.length === 0) {
267
+ await fs.remove(sourceDir);
268
+ }
269
+ }
270
+
271
+ // 获取远程脚本目录路径(供script-manager使用)
272
+ function getRemoteScriptsDir() {
273
+ return REMOTE_SCRIPTS_DIR;
274
+ }
275
+
276
+ // 搜索远程脚本中是否包含指定名称的脚本
277
+ async function findRemoteScript(scriptName) {
278
+ await ensureRemoteScriptsDir();
279
+
280
+ const sources = await getSources();
281
+
282
+ for (const [sourceName, source] of Object.entries(sources)) {
283
+ const sourceDir = path.join(REMOTE_SCRIPTS_DIR, sourceName);
284
+ if (await fs.pathExists(sourceDir)) {
285
+ const scriptPath = path.join(sourceDir, scriptName);
286
+
287
+ // 检查完全匹配
288
+ if (await fs.pathExists(scriptPath)) {
289
+ return { sourceName, scriptPath };
290
+ }
291
+
292
+ // 检查带扩展名的匹配
293
+ const extensions = ['.js', '.sh', '.mjs'];
294
+ for (const ext of extensions) {
295
+ const scriptPathWithExt = `${scriptPath}${ext}`;
296
+ if (await fs.pathExists(scriptPathWithExt)) {
297
+ return { sourceName, scriptPath: scriptPathWithExt };
298
+ }
299
+ }
300
+ }
301
+ }
302
+
303
+ return null;
304
+ }
305
+
306
+ module.exports = {
307
+ SOURCE_TYPES,
308
+ addSource,
309
+ removeSource,
310
+ listSources,
311
+ downloadScript,
312
+ listRemoteScripts,
313
+ removeRemoteScript,
314
+ getRemoteScriptsDir,
315
+ findRemoteScript,
316
+ getSources
317
+ };