ai-git-tools 2.0.80 → 2.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
@@ -51,6 +51,27 @@ npm install --save-dev ai-git-tools
51
51
  }
52
52
  ```
53
53
 
54
+ ### 本機直接使用(尚未發布到 npm)
55
+
56
+ 如果你想在套件正式發布到 npm 前,直接從本機原始碼執行:
57
+
58
+ ```bash
59
+ # 方式一:使用 npm link 將本機套件連結到全域
60
+ npm link
61
+ npx ai-git-tools model-info
62
+
63
+ # 方式二:直接使用 node 執行 bin/cli.js
64
+ node bin/cli.js model-info
65
+ ```
66
+
67
+ 其他命令也適用相同方式,例如:
68
+
69
+ ```bash
70
+ npx ai-git-tools init
71
+ npx ai-git-tools commit
72
+ npx ai-git-tools pr
73
+ ```
74
+
54
75
  ## 🚀 快速開始
55
76
 
56
77
  ### 1. 初始化配置
@@ -100,7 +121,7 @@ npx ai-git-tools wf
100
121
 
101
122
  ## 📖 命令說明
102
123
 
103
- ### \`gitai init\`
124
+ ### \`ai-git-tools init\`
104
125
 
105
126
  初始化配置檔案
106
127
 
@@ -108,7 +129,7 @@ npx ai-git-tools wf
108
129
  npx ai-git-tools init
109
130
  \`\`\`
110
131
 
111
- ### \`gitai commit\`
132
+ ### \`ai-git-tools commit\`
112
133
 
113
134
  為已 staged 的變更生成並執行 commit
114
135
 
@@ -130,7 +151,7 @@ npx ai-git-tools commit --model claude-haiku-4.5
130
151
  npx ai-git-tools commit --verbose
131
152
  \`\`\`
132
153
 
133
- ### \`gitai commit-all\` (別名: \`ca\`)
154
+ ### \`ai-git-tools commit-all\` (別名: \`ca\`)
134
155
 
135
156
  智能分析所有變更並自動分組提交
136
157
 
@@ -151,7 +172,7 @@ npx ai-git-tools commit-all
151
172
  npx ai-git-tools ca --verbose
152
173
  \`\`\`
153
174
 
154
- ### \`gitai pr\`
175
+ ### \`ai-git-tools pr\`
155
176
 
156
177
  生成 PR 並發送到 GitHub
157
178
 
@@ -177,7 +198,7 @@ npx ai-git-tools pr --preview
177
198
  npx ai-git-tools pr --no-confirm
178
199
  \`\`\`
179
200
 
180
- ### \`gitai workflow\` (別名: \`wf\`)
201
+ ### \`ai-git-tools workflow\` (別名: \`wf\`)
181
202
 
182
203
  完整工作流程:commit-all + pr
183
204
 
@@ -199,6 +220,31 @@ npx ai-git-tools workflow
199
220
  npx ai-git-tools wf --preview
200
221
  \`\`\`
201
222
 
223
+ ### \`ai-git-tools model-info\`
224
+
225
+ 查詢目前 GitHub Copilot SDK 支援的 AI 模型清單與詳細資訊
226
+
227
+ \`\`\`bash
228
+ npx ai-git-tools model-info [選項]
229
+
230
+ 選項:
231
+ --json 以 JSON 格式輸出完整模型資料
232
+ --filter <keyword> 依模型名稱、供應商或描述關鍵字過濾
233
+ --model <model> 查詢單一模型的詳細資訊
234
+ --no-cache 忽略快取,強制重新連線 Copilot 取得最新資料
235
+ \`\`\`
236
+
237
+ **範例:**
238
+
239
+ \`\`\`bash
240
+ npx ai-git-tools model-info
241
+ npx ai-git-tools model-info --model gpt-4.1
242
+ npx ai-git-tools model-info --filter claude
243
+ npx ai-git-tools model-info --json
244
+ \`\`\`
245
+
246
+ > 第一次執行會連線到 Copilot CLI 取得模型清單,可能需要數秒鐘;後續預設使用 24 小時快取。
247
+
202
248
  ## ⚙️ 配置
203
249
 
204
250
  配置檔案範例(\`.ai-git-config.js\`):
@@ -352,6 +398,49 @@ feat(auth): 新增使用者登入功能
352
398
  - 整合 JWT 認證機制
353
399
  \`\`\`
354
400
 
401
+ ## 🚀 發布到 npm
402
+
403
+ 若你是本專案維護者,發布新版本到 npm 的步驟如下:
404
+
405
+ ### 1. 確認已登入 npm
406
+
407
+ ```bash
408
+ npm whoami
409
+ ```
410
+
411
+ 若未登入,請執行:
412
+
413
+ ```bash
414
+ npm login
415
+ ```
416
+
417
+ ### 2. 確認版本號
418
+
419
+ 修改 [package.json](package.json) 中的 `version` 欄位(例如 `2.0.80`)。
420
+
421
+ > 每次發布版本號必須遞增,否則 npm 會拒絕。
422
+
423
+ ### 3. 執行發布
424
+
425
+ ```bash
426
+ npm run lint
427
+ npm publish --access public
428
+ ```
429
+
430
+ `prepublishOnly` 會自動執行 `npm run lint`,`prepare` 會自動設定 `bin/cli.js` 為可執行檔。
431
+
432
+ ### 4. 驗證發布
433
+
434
+ ```bash
435
+ npm view ai-git-tools version
436
+ ```
437
+
438
+ ### 注意事項
439
+
440
+ - 發布前請確認工作目錄乾淨(`git status` 無未提交變更)
441
+ - npm 會自動修正 `package.json` 格式問題(例如 `repository.url` 會正規化為 `git+https://...`)
442
+ - 公開套件請使用 `--access public`,私有套件可省略或改為 `--access restricted`
443
+
355
444
  ## 🤔 常見問題
356
445
 
357
446
  ### Q: 需要安裝 GitHub Copilot 嗎?
@@ -374,18 +463,3 @@ A: 目前遵循 Conventional Commits 規範。未來版本會支援自訂格式
374
463
 
375
464
  A: 支援。工具會分析整個倉庫的變更。
376
465
 
377
- ## 📝 授權
378
-
379
- MIT License
380
-
381
- ## 🤝 貢獻
382
-
383
- 歡迎提交 Issue 和 Pull Request!
384
-
385
- ## 📧 聯絡
386
-
387
- 有問題或建議?歡迎[提交 Issue](https://github.com/yourusername/ai-git-tools/issues)
388
-
389
- ---
390
-
391
- Made with ❤️ by AI Git Tools Team
package/bin/cli.js CHANGED
@@ -15,6 +15,7 @@ import { commitAllCommand } from '../src/commands/commit-all.js';
15
15
  import { prCommand } from '../src/commands/pr.js';
16
16
  import { initCommand } from '../src/commands/init.js';
17
17
  import { usageCommand } from '../src/commands/usage.js';
18
+ import { modelInfoCommand } from '../src/commands/model-info.js';
18
19
  import { registerCommand } from '../src/utils/cli-helpers.js';
19
20
 
20
21
  // 讀取 package.json 獲取版本號
@@ -75,4 +76,12 @@ registerCommand(program, 'usage', '查看組織 GitHub Copilot 使用狀態與
75
76
  { flags: '--json', description: '以 JSON 格式輸出完整資料' },
76
77
  ], usageCommand);
77
78
 
79
+ // Model Info 命令
80
+ registerCommand(program, 'model-info', '查看目前可用的 AI 模型資訊', [
81
+ { flags: '--json', description: '以 JSON 格式輸出完整模型資料' },
82
+ { flags: '--filter <keyword>', description: '依模型名稱或描述關鍵字過濾' },
83
+ { flags: '--model <model>', description: '查詢單一模型的詳細資訊' },
84
+ { flags: '--no-cache', description: '忽略快取,強制重新連線 Copilot 取得最新模型清單' },
85
+ ], modelInfoCommand);
86
+
78
87
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-git-tools",
3
- "version": "2.0.80",
3
+ "version": "2.1.0",
4
4
  "description": "AI-powered Git automation tools for commit messages and PR generation",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -39,11 +39,11 @@
39
39
  "CHANGELOG.md"
40
40
  ],
41
41
  "dependencies": {
42
- "@github/copilot-sdk": "^0.2.1",
43
- "commander": "^12.0.0",
42
+ "@github/copilot-sdk": "^1.0.11",
44
43
  "chalk": "^5.3.0",
45
- "ora": "^8.0.1",
46
- "inquirer": "^9.2.0"
44
+ "commander": "^12.0.0",
45
+ "inquirer": "^9.2.0",
46
+ "ora": "^8.0.1"
47
47
  },
48
48
  "devDependencies": {
49
49
  "eslint": "^8.57.0",
@@ -51,7 +51,7 @@
51
51
  },
52
52
  "repository": {
53
53
  "type": "git",
54
- "url": "https://github.com/YisoTsao/ai-git-tools.git"
54
+ "url": "git+https://github.com/YisoTsao/ai-git-tools.git"
55
55
  },
56
56
  "bugs": {
57
57
  "url": "https://github.com/YisoTsao/ai-git-tools/issues"
@@ -0,0 +1,195 @@
1
+ /**
2
+ * model-info 命令
3
+ * 顯示 GitHub Copilot SDK 可用模型資訊
4
+ */
5
+
6
+ import ora from 'ora';
7
+ import { Logger } from '../utils/logger.js';
8
+ import { fetchCopilotModels } from '../core/model-client.js';
9
+ import {
10
+ enrichModel,
11
+ getModelById,
12
+ filterModels,
13
+ formatPrice,
14
+ formatTokenCount,
15
+ } from '../data/copilot-models.js';
16
+
17
+ const logger = new Logger();
18
+
19
+ /**
20
+ * 格式化建議用途陣列為可讀字串
21
+ * @param {string[]} items
22
+ * @returns {string}
23
+ */
24
+ function formatRecommendedFor(items) {
25
+ if (!items || items.length === 0) return '—';
26
+ return items.join('、');
27
+ }
28
+
29
+ /**
30
+ * 格式化支援能力陣列
31
+ * @param {object} supports
32
+ * @returns {string}
33
+ */
34
+ function formatCapabilities(supports = {}) {
35
+ const caps = [];
36
+ if (supports.vision) caps.push('vision');
37
+ if (supports.tool_calls) caps.push('tools');
38
+ if (supports.reasoningEffort || supports.reasoning_effort) caps.push('reasoning');
39
+ if (supports.streaming) caps.push('streaming');
40
+ if (supports.parallel_tool_calls) caps.push('parallel-tools');
41
+ return caps.length > 0 ? caps.join('、') : '—';
42
+ }
43
+
44
+ /**
45
+ * 取得模型狀態圖示
46
+ * @param {string} state
47
+ * @returns {string}
48
+ */
49
+ function policyStateLabel(state) {
50
+ switch (state) {
51
+ case 'enabled':
52
+ return '啟用';
53
+ case 'disabled':
54
+ return '停用';
55
+ case 'unconfigured':
56
+ return '未配置';
57
+ default:
58
+ return state || '—';
59
+ }
60
+ }
61
+
62
+ /**
63
+ * 印出單一模型詳細資訊
64
+ * @param {object} model
65
+ */
66
+ function printModel(model) {
67
+ const billing = model.billing?.token_prices;
68
+
69
+ console.log(`\n ${logger.colors?.cyan ?? ''}${model.id}${logger.colors?.reset ?? ''}`);
70
+ console.log(' ─────────────────────────────────────────────────────────────');
71
+ console.log(` 名稱: ${model.name}`);
72
+ console.log(` 供應商: ${model.provider}`);
73
+ console.log(` 描述: ${model.description}`);
74
+ console.log(` 上下文: ${model.contextWindow}`);
75
+ console.log(` 最大輸出: ${model.maxOutputTokens}`);
76
+ console.log(` 建議用途: ${formatRecommendedFor(model.recommendedFor)}`);
77
+ console.log(` 速度: ${model.speed}`);
78
+ console.log(` 狀態: ${policyStateLabel(model.policy?.state)}`);
79
+ console.log(` 能力: ${formatCapabilities(model.capabilities?.supports)}`);
80
+ if (billing && billing.batch_size > 0) {
81
+ const hasAnyPrice =
82
+ billing.input_price > 0 || billing.output_price > 0 || billing.cache_price > 0;
83
+ if (hasAnyPrice) {
84
+ console.log(` 價格(預估):`);
85
+ console.log(
86
+ ` 輸入: ${formatPrice(billing.input_price, billing.batch_size)} / ${formatTokenCount(billing.batch_size)} tokens`
87
+ );
88
+ console.log(
89
+ ` 輸出: ${formatPrice(billing.output_price, billing.batch_size)} / ${formatTokenCount(billing.batch_size)} tokens`
90
+ );
91
+ if (billing.cache_price !== undefined) {
92
+ console.log(
93
+ ` 快取: ${formatPrice(billing.cache_price, billing.batch_size)} / ${formatTokenCount(billing.batch_size)} tokens`
94
+ );
95
+ }
96
+ }
97
+ }
98
+ if (model.supportedReasoningEfforts && model.supportedReasoningEfforts.length > 0) {
99
+ console.log(
100
+ ` Reasoning: ${model.supportedReasoningEfforts.join('、')}(預設:${model.defaultReasoningEffort || '—'})`
101
+ );
102
+ }
103
+ if (model.notes) {
104
+ console.log(` 備註: ${model.notes}`);
105
+ }
106
+ }
107
+
108
+ /**
109
+ * 印出模型列表(精簡版)
110
+ * @param {object[]} models
111
+ */
112
+ function printModelList(models) {
113
+ console.log();
114
+ for (const model of models) {
115
+ const idPart = `${logger.colors?.cyan ?? ''}${model.id.padEnd(18)}${logger.colors?.reset ?? ''}`;
116
+ console.log(` ${idPart} ${model.description}`);
117
+ }
118
+ }
119
+
120
+ /**
121
+ * model-info 命令入口
122
+ * @param {object} options - commander 解析後的選項
123
+ */
124
+ export async function modelInfoCommand(options) {
125
+ const { json, filter, model: modelId, cache } = options;
126
+ const noCache = cache === false;
127
+
128
+ let sdkModels = [];
129
+ let usedCache = false;
130
+
131
+ const spinner = ora('正在連線到 Copilot 取得模型清單...').start();
132
+
133
+ try {
134
+ sdkModels = await fetchCopilotModels({ noCache });
135
+ spinner.stop();
136
+ } catch (error) {
137
+ spinner.stop();
138
+ if (error.cachedModels) {
139
+ sdkModels = error.cachedModels;
140
+ usedCache = true;
141
+ } else {
142
+ logger.error(`無法取得模型清單:${error.message}`);
143
+ logger.info('請確認已安裝 GitHub CLI 並完成 Copilot 授權(gh auth login)');
144
+ process.exit(1);
145
+ }
146
+ }
147
+
148
+ const models = sdkModels.map(enrichModel);
149
+
150
+ if (json) {
151
+ let output = models;
152
+ if (filter) output = filterModels(filter, output);
153
+ if (modelId) {
154
+ const model = getModelById(modelId, output);
155
+ output = model ? [model] : [];
156
+ }
157
+ console.log(JSON.stringify(output, null, 2));
158
+ return;
159
+ }
160
+
161
+ logger.section('🤖 Copilot 可用模型');
162
+
163
+ if (usedCache) {
164
+ logger.warning('即時取得失敗,顯示快取資料');
165
+ }
166
+
167
+ if (modelId) {
168
+ const model = getModelById(modelId, models);
169
+ if (!model) {
170
+ logger.error(`找不到模型「${modelId}」`);
171
+ logger.info(`可用模型:${models.map(m => m.id).join('、')}`);
172
+ process.exit(1);
173
+ }
174
+ printModel(model);
175
+ console.log();
176
+ return;
177
+ }
178
+
179
+ const filteredModels = filterModels(filter, models);
180
+ if (filteredModels.length === 0) {
181
+ logger.warning('沒有符合條件的模型');
182
+ return;
183
+ }
184
+
185
+ if (filter) {
186
+ printModelList(filteredModels);
187
+ } else {
188
+ for (const model of filteredModels) {
189
+ printModel(model);
190
+ }
191
+ }
192
+
193
+ console.log();
194
+ logger.info(`共 ${filteredModels.length} 個模型,使用 --model <id> 查看詳細資訊`);
195
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Copilot 模型列表客戶端
3
+ * 透過 @github/copilot-sdk 動態取得可用模型清單
4
+ */
5
+
6
+ import { writeFileSync, readFileSync, existsSync, mkdirSync } from 'fs';
7
+ import { tmpdir } from 'os';
8
+ import { join } from 'path';
9
+ import { CopilotClient } from '@github/copilot-sdk';
10
+
11
+ // 暫存快取檔案路徑
12
+ const CACHE_DIR = join(tmpdir(), 'ai-git-tools');
13
+ const CACHE_FILE = join(CACHE_DIR, 'copilot-models-cache.json');
14
+ const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 小時
15
+
16
+ /**
17
+ * 確保快取目錄存在
18
+ */
19
+ function ensureCacheDir() {
20
+ if (!existsSync(CACHE_DIR)) {
21
+ mkdirSync(CACHE_DIR, { recursive: true });
22
+ }
23
+ }
24
+
25
+ /**
26
+ * 讀取快取的模型清單
27
+ * @returns {object | null}
28
+ */
29
+ function readCache() {
30
+ try {
31
+ if (!existsSync(CACHE_FILE)) return null;
32
+ const raw = readFileSync(CACHE_FILE, 'utf-8');
33
+ const parsed = JSON.parse(raw);
34
+ if (!parsed.fetchedAt || !Array.isArray(parsed.models)) return null;
35
+
36
+ const age = Date.now() - new Date(parsed.fetchedAt).getTime();
37
+ if (age > CACHE_TTL_MS) return null;
38
+
39
+ return parsed;
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+
45
+ /**
46
+ * 寫入快取
47
+ * @param {Array<object>} models
48
+ */
49
+ function writeCache(models) {
50
+ try {
51
+ ensureCacheDir();
52
+ writeFileSync(
53
+ CACHE_FILE,
54
+ JSON.stringify({ fetchedAt: new Date().toISOString(), models }, null, 2),
55
+ 'utf-8'
56
+ );
57
+ } catch {
58
+ // 快取寫入失敗不影響主要功能
59
+ }
60
+ }
61
+
62
+ /**
63
+ * 清除模型快取
64
+ */
65
+ export function clearModelCache() {
66
+ try {
67
+ if (existsSync(CACHE_FILE)) {
68
+ writeFileSync(CACHE_FILE, '', 'utf-8');
69
+ }
70
+ } catch {
71
+ // 忽略清除失敗
72
+ }
73
+ }
74
+
75
+ /**
76
+ * 從 Copilot SDK 取得可用模型清單
77
+ * @param {object} options
78
+ * @param {boolean} [options.noCache=false] - 是否忽略快取強制重新抓取
79
+ * @param {boolean} [options.fallbackToCache=true] - SDK 失敗時是否回退使用快取
80
+ * @returns {Promise<Array<object>>}
81
+ */
82
+ export async function fetchCopilotModels({ noCache = false, fallbackToCache = true } = {}) {
83
+ if (!noCache) {
84
+ const cached = readCache();
85
+ if (cached) {
86
+ return cached.models;
87
+ }
88
+ }
89
+
90
+ // 抑制 Copilot CLI 子程序的 SQLite ExperimentalWarning
91
+ process.env.NODE_NO_WARNINGS = '1';
92
+
93
+ const client = new CopilotClient();
94
+ try {
95
+ await client.start();
96
+ const models = await client.listModels();
97
+
98
+ writeCache(models);
99
+ return models;
100
+ } catch (error) {
101
+ if (fallbackToCache) {
102
+ const cached = readCache();
103
+ if (cached) {
104
+ error.fallbackToCache = true;
105
+ error.cachedModels = cached.models;
106
+ throw error;
107
+ }
108
+ }
109
+ throw error;
110
+ } finally {
111
+ try {
112
+ await client.stop();
113
+ } catch {
114
+ // 忽略關閉錯誤
115
+ }
116
+ }
117
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Copilot 模型本機增強資料表
3
+ * 用來補充 Copilot SDK listModels() 回傳的動態資料
4
+ */
5
+
6
+ const MODEL_ENHANCEMENTS = {};
7
+
8
+ /**
9
+ * 推斷模型供應商
10
+ * @param {string} modelId
11
+ * @returns {string}
12
+ */
13
+ export function inferProvider(modelId) {
14
+ if (modelId.startsWith('claude')) return 'Anthropic';
15
+ if (modelId.startsWith('gpt')) return 'OpenAI';
16
+ return 'Unknown';
17
+ }
18
+
19
+ /**
20
+ * 將 token 數格式化為可讀字串(例如 264000 → 264K)
21
+ * @param {number} tokens
22
+ * @returns {string}
23
+ */
24
+ export function formatTokenCount(tokens) {
25
+ if (!tokens && tokens !== 0) return '—';
26
+ if (tokens >= 1_000_000) return `${(tokens / 1_000_000).toFixed(1).replace(/\.0$/, '')}M`;
27
+ if (tokens >= 1_000) return `${Math.round(tokens / 1_000)}K`;
28
+ return `${tokens}`;
29
+ }
30
+
31
+ /**
32
+ * 取得模型的本機增強資料
33
+ * @param {string} modelId
34
+ * @returns {object}
35
+ */
36
+ export function getModelEnhancement(modelId) {
37
+ return (
38
+ MODEL_ENHANCEMENTS[modelId] || {
39
+ description: 'Copilot SDK 回報的可用模型',
40
+ recommendedFor: [],
41
+ speed: 'medium',
42
+ notes: '',
43
+ }
44
+ );
45
+ }
46
+
47
+ /**
48
+ * 合併 SDK 模型資料與本機增強資料
49
+ * @param {object} sdkModel
50
+ * @returns {object}
51
+ */
52
+ export function enrichModel(sdkModel) {
53
+ const enhancement = getModelEnhancement(sdkModel.id);
54
+ const limits = sdkModel.capabilities?.limits || {};
55
+
56
+ return {
57
+ ...sdkModel,
58
+ provider: inferProvider(sdkModel.id),
59
+ description: enhancement.description,
60
+ recommendedFor: enhancement.recommendedFor,
61
+ speed: enhancement.speed,
62
+ notes: enhancement.notes,
63
+ contextWindow: formatTokenCount(limits.max_context_window_tokens),
64
+ maxOutputTokens: formatTokenCount(limits.max_output_tokens),
65
+ };
66
+ }
67
+
68
+ /**
69
+ * 格式化價格(將 SDK 內部單位轉換為美元 / 1M tokens)
70
+ * @param {number} price
71
+ * @param {number} batchSize
72
+ * @returns {string}
73
+ */
74
+ export function formatPrice(price, batchSize) {
75
+ if (price === undefined || price === null) return '—';
76
+ if (!batchSize || batchSize === 0 || price === 0) return '—';
77
+ // SDK 內部單位為 10^-11 美元 / batch_size tokens
78
+ const dollars = price / 100_000_000_000;
79
+ return `$${dollars.toFixed(2)}`;
80
+ }
81
+
82
+ /**
83
+ * 依模型 ID 取得增強後資訊(從 SDK 模型清單中)
84
+ * @param {string} id
85
+ * @param {Array<object>} models
86
+ * @returns {object | undefined}
87
+ */
88
+ export function getModelById(id, models = []) {
89
+ return models.find(m => m.id === id);
90
+ }
91
+
92
+ /**
93
+ * 依關鍵字過濾模型
94
+ * @param {string} keyword
95
+ * @param {Array<object>} models
96
+ * @returns {Array<object>}
97
+ */
98
+ export function filterModels(keyword, models = []) {
99
+ if (!keyword) return models;
100
+ const lower = keyword.toLowerCase();
101
+ return models.filter(
102
+ m =>
103
+ m.id.toLowerCase().includes(lower) ||
104
+ m.name.toLowerCase().includes(lower) ||
105
+ (m.description && m.description.toLowerCase().includes(lower)) ||
106
+ (m.provider && m.provider.toLowerCase().includes(lower))
107
+ );
108
+ }