taiwan-invoice-skill 2.2.0 → 2.4.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
@@ -119,6 +119,23 @@ taiwan-invoice init --global # 安裝到全域目錄
119
119
 
120
120
  ---
121
121
 
122
+ ## 智能工具
123
+
124
+ 安裝後包含以下 Python 工具(純 Python,無需外部依賴):
125
+
126
+ ```bash
127
+ # BM25 搜索引擎 - 搜索錯誤碼、欄位映射、稅務規則
128
+ python scripts/search.py "10000016" --domain error
129
+
130
+ # 加值中心推薦系統 - 根據需求推薦服務商
131
+ python scripts/recommend.py "電商 高交易量 穩定"
132
+
133
+ # 代碼生成器 - 生成 TypeScript/Python 服務模組
134
+ python scripts/generate-invoice-service.py ECPay --output ts
135
+ ```
136
+
137
+ ---
138
+
122
139
  ## 授權
123
140
 
124
141
  [MIT License](https://github.com/Moksa1123/taiwan-invoice/blob/main/LICENSE)
@@ -21,7 +21,8 @@ user-invocable: true
21
21
  - `scripts/search.py` - BM25 搜索引擎(查詢 API、錯誤碼、欄位映射)
22
22
  - `scripts/recommend.py` - 加值中心推薦系統
23
23
  - `scripts/generate-invoice-service.py` - 服務代碼生成器
24
- - `data/` - CSV 數據檔(providers, operations, error-codes, field-mappings, tax-rules, troubleshooting)
24
+ - `scripts/persist.py` - 持久化配置工具(MASTER.md 生成)
25
+ - `data/` - CSV 數據檔(providers, operations, error-codes, field-mappings, tax-rules, troubleshooting, reasoning)
25
26
 
26
27
  ### 何時使用此技能
27
28
  - 開發電子發票開立功能
@@ -66,6 +67,7 @@ python scripts/search.py "折讓" --format json
66
67
  | `field` | 欄位映射 | field-mappings.csv |
67
68
  | `tax` | 稅務計算規則 | tax-rules.csv |
68
69
  | `troubleshoot` | 疑難排解 | troubleshooting.csv |
70
+ | `reasoning` | 推薦決策規則 | reasoning.csv |
69
71
 
70
72
  ### 推薦系統 (recommend.py)
71
73
 
@@ -108,6 +110,37 @@ python scripts/generate-invoice-service.py SmilePay --output py
108
110
  python scripts/generate-invoice-service.py Amego --output ts > amego-service.ts
109
111
  ```
110
112
 
113
+ ### 持久化配置 (persist.py)
114
+
115
+ 將發票配置保存為 MASTER.md,供 AI 助手持續參考:
116
+
117
+ ```bash
118
+ # 初始化配置
119
+ python scripts/persist.py init ECPay
120
+ python scripts/persist.py init SmilePay -p "MyProject"
121
+
122
+ # 顯示當前配置
123
+ python scripts/persist.py show
124
+
125
+ # 列出可用服務商
126
+ python scripts/persist.py list
127
+
128
+ # 強制覆蓋
129
+ python scripts/persist.py init Amego --force
130
+ ```
131
+
132
+ **生成結構:**
133
+ ```
134
+ invoice-config/
135
+ └── MASTER.md # 專案發票配置
136
+ ├── 基本資訊
137
+ ├── 服務商配置
138
+ ├── API 端點
139
+ ├── 發票類型設定
140
+ ├── 環境變數建議
141
+ └── 開發檢查清單
142
+ ```
143
+
111
144
  ---
112
145
 
113
146
  ## 發票類型
@@ -0,0 +1,32 @@
1
+ scenario,recommended_provider,confidence,reason,decision_rules,anti_patterns,use_cases
2
+ 高交易量電商,ECPay,HIGH,市佔率最高 系統穩定性佳,volume>1000/day AND priority=stability,避免使用測試環境長期運行,大型電商平台 購物網站
3
+ 穩定性優先,ECPay,HIGH,台灣電子發票市佔率領先,priority=stability OR risk_tolerance=low,避免頻繁切換服務商,金融相關 醫療系統
4
+ 完整文檔需求,ECPay,HIGH,提供完整 API 文檔與 SDK,priority=documentation OR team_size>3,避免依賴社群資源,企業級開發 團隊協作
5
+ 多語言SDK需求,ECPay,MEDIUM,官方SDK支援多種語言,language IN (java php dotnet) AND need_sdk=true,避免自行實作加密,Java/.NET專案
6
+ 簡單整合需求,SmilePay,HIGH,整合流程最簡單 無需複雜加密,priority=speed OR deadline<7days,避免過度設計,MVP開發 快速上線
7
+ 小型專案,SmilePay,HIGH,適合小型專案和個人開發者,team_size<=2 AND budget=low,避免過度工程化,個人專案 小型網站
8
+ 快速上線,SmilePay,HIGH,最快速完成整合,deadline<14days AND complexity=low,避免完美主義,緊急專案 POC驗證
9
+ 測試環境友善,SmilePay,MEDIUM,測試環境設定簡單,phase=development OR phase=testing,避免正式環境測試,開發階段 測試階段
10
+ 低預算專案,SmilePay,MEDIUM,費用相對較低,budget=low AND volume<500/day,避免過度投資,新創公司 個人開發
11
+ API設計優先,Amego,HIGH,MIG 4.0 最新 API 標準,priority=api_design OR tech_stack=modern,避免使用舊版API,技術導向團隊
12
+ 現代技術棧,Amego,HIGH,採用最新技術標準,framework IN (nextjs nuxt sveltekit),避免傳統架構,現代前端專案
13
+ RESTful需求,Amego,MEDIUM,API設計符合REST規範,priority=clean_code AND experience>3years,避免過度抽象,資深開發團隊
14
+ MIG標準合規,Amego,HIGH,完整支援MIG 4.0規範,compliance=mig40 OR government=true,避免自定義協議,政府專案 標準合規
15
+ B2B發票為主,ECPay,MEDIUM,B2B發票功能完整 經驗豐富,invoice_type=b2b AND volume>100/day,避免B2C邏輯套用B2B,企業對企業交易
16
+ B2C發票為主,ECPay,MEDIUM,B2C市佔最高 問題解決資源多,invoice_type=b2c AND support_need=high,避免忽視客服需求,一般消費者交易
17
+ B2C簡單整合,SmilePay,MEDIUM,B2C整合最簡單,invoice_type=b2c AND priority=speed,避免過度複雜化,簡單B2C場景
18
+ 混合發票需求,ECPay,MEDIUM,同時支援B2B/B2C 切換彈性,invoice_type=mixed,避免硬編碼發票類型,B2B+B2C混合
19
+ 載具功能需求,ECPay,MEDIUM,載具支援最完整,feature=carrier AND carrier_type IN (mobile barcode),避免忽略載具驗證,手機條碼整合
20
+ 捐贈功能需求,ECPay,MEDIUM,捐贈功能完整,feature=donation,避免捐贈與載具混用,公益捐贈整合
21
+ 列印功能需求,ECPay,MEDIUM,列印功能完整 多種格式,feature=print AND print_format IN (a4 thermal),避免忽略隨機碼,實體發票列印
22
+ 作廢功能需求,ECPay,LOW,作廢流程標準化,feature=void,避免作廢後重開相同號碼,發票作廢處理
23
+ 折讓功能需求,ECPay,LOW,折讓功能完整,feature=allowance,避免折讓超過原發票金額,退款折讓處理
24
+ 無技術資源,SmilePay,HIGH,無需複雜加密 整合門檻最低,tech_resource=limited OR developer_count=1,避免選擇高複雜度方案,一人團隊 非技術背景
25
+ AES加密經驗,ECPay,MEDIUM,需要AES-128-CBC加密經驗,encryption_experience=aes,避免明文傳輸,有加密經驗團隊
26
+ MD5簽章經驗,Amego,MEDIUM,使用MD5簽章驗證,encryption_experience=md5,避免簽章順序錯誤,有MD5經驗團隊
27
+ Node.js專案,ECPay,MEDIUM,官方有Node.js範例,language=nodejs AND need_example=true,避免從零實作加密,Node.js開發
28
+ Python專案,ECPay,MEDIUM,社群Python範例豐富,language=python,避免依賴過時範例,Python開發
29
+ PHP專案,ECPay,HIGH,官方PHP SDK最完整,language=php AND need_sdk=true,避免版本不相容,PHP開發
30
+ 新創公司,SmilePay,MEDIUM,快速上線 成本控制,company_type=startup AND phase=early,避免過度投資基礎建設,早期新創
31
+ 企業級應用,ECPay,HIGH,穩定性和支援優先,company_type=enterprise,避免使用非主流方案,大型企業
32
+ 政府專案,Amego,MEDIUM,MIG標準合規,project_type=government,避免非標準協議,政府標案
@@ -47,6 +47,11 @@ CSV_CONFIG = {
47
47
  'file': 'troubleshooting.csv',
48
48
  'search_cols': ['issue', 'symptom', 'cause', 'solution', 'provider', 'category'],
49
49
  'output_cols': ['issue', 'symptom', 'cause', 'solution', 'provider', 'severity']
50
+ },
51
+ 'reasoning': {
52
+ 'file': 'reasoning.csv',
53
+ 'search_cols': ['scenario', 'recommended_provider', 'reason', 'decision_rules', 'use_cases'],
54
+ 'output_cols': ['scenario', 'recommended_provider', 'confidence', 'reason', 'anti_patterns', 'use_cases']
50
55
  }
51
56
  }
52
57
 
@@ -57,7 +62,8 @@ DOMAIN_KEYWORDS = {
57
62
  'error': ['error', 'code', '錯誤', '代碼', '失敗', 'fail', '-', '10000', '1001', '2001'],
58
63
  'field': ['field', 'param', '欄位', '參數', 'mapping', '映射', 'merchantid', 'orderid', 'buyername'],
59
64
  'tax': ['tax', 'b2c', 'b2b', '稅', '應稅', '免稅', '零稅率', 'salesamount', 'taxamount', '計算'],
60
- 'troubleshoot': ['問題', 'issue', 'error', 'fix', '解決', '失敗', '空白', 'troubleshoot', '踩坑']
65
+ 'troubleshoot': ['問題', 'issue', 'error', 'fix', '解決', '失敗', '空白', 'troubleshoot', '踩坑'],
66
+ 'reasoning': ['推薦', 'recommend', '選擇', 'choose', '適合', 'suitable', '場景', 'scenario', '決策', 'decision']
61
67
  }
62
68
 
63
69
 
@@ -0,0 +1,330 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Taiwan Invoice Skill - 持久化模式
4
+ 將發票配置保存為 MASTER.md,供 AI 助手持續參考
5
+
6
+ 用法:
7
+ python persist.py init ECPay # 初始化 ECPay 配置
8
+ python persist.py init SmilePay # 初始化 SmilePay 配置
9
+ python persist.py show # 顯示當前配置
10
+ python persist.py update --key xxx # 更新配置
11
+ """
12
+
13
+ import os
14
+ import sys
15
+ import argparse
16
+ from datetime import datetime
17
+ from typing import Dict, Any, Optional
18
+
19
+ # 取得 data 目錄路徑
20
+ SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
21
+ DATA_DIR = os.path.join(os.path.dirname(SCRIPT_DIR), 'data')
22
+
23
+ # 預設配置目錄
24
+ DEFAULT_CONFIG_DIR = 'invoice-config'
25
+ MASTER_FILENAME = 'MASTER.md'
26
+
27
+ # 服務商配置模板
28
+ PROVIDER_CONFIGS = {
29
+ 'ECPay': {
30
+ 'display_name': '綠界科技',
31
+ 'auth_method': 'AES-128-CBC',
32
+ 'test_url': 'https://einvoice-stage.ecpay.com.tw',
33
+ 'prod_url': 'https://einvoice.ecpay.com.tw',
34
+ 'test_merchant_id': '2000132',
35
+ 'test_hash_key': 'ejCk326UnaZWKisg',
36
+ 'test_hash_iv': 'q9jcZX8Ib9LM8wYk',
37
+ 'endpoints': {
38
+ 'issue_b2c': '/B2CInvoice/Issue',
39
+ 'issue_b2b': '/B2BInvoice/Issue',
40
+ 'void': '/Invoice/IssueInvalid',
41
+ 'allowance': '/Invoice/AllowanceByCollegiate',
42
+ 'print': '/Invoice/Print',
43
+ },
44
+ 'encryption': 'URL Encode → AES-128-CBC → Base64',
45
+ },
46
+ 'SmilePay': {
47
+ 'display_name': '速買配',
48
+ 'auth_method': 'Verify_key',
49
+ 'test_url': 'https://ssl.smse.com.tw/api_test',
50
+ 'prod_url': 'https://ssl.smse.com.tw/api',
51
+ 'test_merchant_id': 'SEI1000034',
52
+ 'test_hash_key': '9D73935693EE0237FABA6AB744E48661',
53
+ 'test_hash_iv': '',
54
+ 'endpoints': {
55
+ 'issue': '/SPEinvoice_Storage.asp',
56
+ 'void': '/SPEinvoice_Invalid.asp',
57
+ 'allowance': '/SPEinvoice_AllowanceByCollegiate.asp',
58
+ 'print': '/SPEinvoice_Print_Single.asp',
59
+ },
60
+ 'encryption': 'URL Parameters + Verify_key',
61
+ },
62
+ 'Amego': {
63
+ 'display_name': '光貿科技',
64
+ 'auth_method': 'MD5 Signature',
65
+ 'test_url': 'https://invoice-api.amego.tw',
66
+ 'prod_url': 'https://invoice-api.amego.tw',
67
+ 'test_merchant_id': '12345678',
68
+ 'test_hash_key': 'sHeq7t8G1wiQvhAuIM27',
69
+ 'test_hash_iv': '',
70
+ 'endpoints': {
71
+ 'issue': '/api/invoice/issue',
72
+ 'void': '/api/invoice/void',
73
+ 'allowance': '/api/invoice/allowance',
74
+ 'print': '/api/invoice/print',
75
+ },
76
+ 'encryption': 'JSON + MD5(data + time + appKey)',
77
+ },
78
+ }
79
+
80
+
81
+ def generate_master_md(provider: str, project_name: str = '', custom_config: Optional[Dict] = None) -> str:
82
+ """
83
+ 生成 MASTER.md 內容
84
+ """
85
+ if provider not in PROVIDER_CONFIGS:
86
+ raise ValueError(f"Unknown provider: {provider}")
87
+
88
+ config = PROVIDER_CONFIGS[provider].copy()
89
+ if custom_config:
90
+ config.update(custom_config)
91
+
92
+ now = datetime.now().strftime('%Y-%m-%d %H:%M')
93
+
94
+ content = f"""# Taiwan Invoice - 專案配置
95
+
96
+ > 此檔案為 AI 助手的持久化配置,請勿手動刪除
97
+
98
+ ## 基本資訊
99
+
100
+ | 項目 | 值 |
101
+ |------|-----|
102
+ | **專案名稱** | {project_name or '未命名專案'} |
103
+ | **加值中心** | {config['display_name']} ({provider}) |
104
+ | **建立時間** | {now} |
105
+ | **環境** | 測試環境 |
106
+
107
+ ---
108
+
109
+ ## 服務商配置
110
+
111
+ ### {config['display_name']} ({provider})
112
+
113
+ **認證方式**: {config['auth_method']}
114
+
115
+ **API 端點**:
116
+ - 測試: `{config['test_url']}`
117
+ - 正式: `{config['prod_url']}`
118
+
119
+ **測試憑證**:
120
+ ```
121
+ MerchantID: {config['test_merchant_id']}
122
+ HashKey: {config['test_hash_key']}
123
+ HashIV: {config['test_hash_iv'] or 'N/A'}
124
+ ```
125
+
126
+ **加密流程**:
127
+ ```
128
+ {config['encryption']}
129
+ ```
130
+
131
+ ---
132
+
133
+ ## API 端點
134
+
135
+ | 操作 | 端點 |
136
+ |------|------|
137
+ """
138
+
139
+ for op, endpoint in config['endpoints'].items():
140
+ content += f"| {op} | `{endpoint}` |\n"
141
+
142
+ content += f"""
143
+ ---
144
+
145
+ ## 發票類型設定
146
+
147
+ ### B2C (二聯式)
148
+
149
+ - 金額: **含稅價**
150
+ - BuyerIdentifier: `0000000000`
151
+ - TaxAmount: `0`
152
+ - 可使用載具/捐贈
153
+
154
+ ### B2B (三聯式)
155
+
156
+ - 金額: **未稅價**
157
+ - 需填寫統編 (8碼)
158
+ - 需計算稅額: `TaxAmount = round(Total - Total/1.05)`
159
+ - **不可**使用載具/捐贈
160
+
161
+ ---
162
+
163
+ ## 環境變數建議
164
+
165
+ ```env
166
+ # {config['display_name']} 配置
167
+ INVOICE_PROVIDER={provider}
168
+ INVOICE_MERCHANT_ID=
169
+ INVOICE_HASH_KEY=
170
+ INVOICE_HASH_IV=
171
+ INVOICE_ENV=test
172
+ ```
173
+
174
+ ---
175
+
176
+ ## 開發檢查清單
177
+
178
+ - [ ] 設定環境變數
179
+ - [ ] 實作加密/解密函數
180
+ - [ ] 建立 InvoiceService 介面
181
+ - [ ] 實作 {provider}InvoiceService
182
+ - [ ] 處理 B2C/B2B 金額計算差異
183
+ - [ ] 儲存 invoiceProvider 和 randomNumber
184
+ - [ ] 實作錯誤處理與 logger
185
+ - [ ] 測試環境驗證
186
+
187
+ ---
188
+
189
+ ## 注意事項
190
+
191
+ 1. **randomNumber**: 開立成功後務必儲存,列印時需要
192
+ 2. **invoiceProvider**: 開立時儲存使用的服務商,列印時使用
193
+ 3. **時間戳記**: {"ECPay 需在 10 分鐘內" if provider == 'ECPay' else "Amego 需在 60 秒內" if provider == 'Amego' else "注意伺服器時間"}
194
+ 4. **載具與捐贈**: 互斥,不可同時設定
195
+
196
+ ---
197
+
198
+ *Generated by Taiwan Invoice Skill v2.3.0*
199
+ """
200
+
201
+ return content
202
+
203
+
204
+ def init_config(provider: str, target_dir: str, project_name: str = '', force: bool = False) -> str:
205
+ """
206
+ 初始化配置
207
+ """
208
+ config_dir = os.path.join(target_dir, DEFAULT_CONFIG_DIR)
209
+ master_path = os.path.join(config_dir, MASTER_FILENAME)
210
+
211
+ # 檢查是否已存在
212
+ if os.path.exists(master_path) and not force:
213
+ raise FileExistsError(f"Config already exists: {master_path}. Use --force to overwrite.")
214
+
215
+ # 建立目錄
216
+ os.makedirs(config_dir, exist_ok=True)
217
+
218
+ # 生成內容
219
+ content = generate_master_md(provider, project_name)
220
+
221
+ # 寫入檔案
222
+ with open(master_path, 'w', encoding='utf-8') as f:
223
+ f.write(content)
224
+
225
+ return master_path
226
+
227
+
228
+ def show_config(target_dir: str) -> Optional[str]:
229
+ """
230
+ 顯示當前配置
231
+ """
232
+ master_path = os.path.join(target_dir, DEFAULT_CONFIG_DIR, MASTER_FILENAME)
233
+
234
+ if not os.path.exists(master_path):
235
+ return None
236
+
237
+ with open(master_path, 'r', encoding='utf-8') as f:
238
+ return f.read()
239
+
240
+
241
+ def format_ascii_box(title: str, content: str, width: int = 70) -> str:
242
+ """
243
+ 格式化 ASCII Box
244
+ """
245
+ lines = []
246
+ lines.append('╔' + '═' * (width - 2) + '╗')
247
+ lines.append('║' + f' {title}'.center(width - 2) + '║')
248
+ lines.append('╠' + '═' * (width - 2) + '╣')
249
+
250
+ for line in content.split('\n'):
251
+ if len(line) > width - 4:
252
+ line = line[:width - 7] + '...'
253
+ lines.append('║' + ' ' + line.ljust(width - 4) + ' ' + '║')
254
+
255
+ lines.append('╚' + '═' * (width - 2) + '╝')
256
+ return '\n'.join(lines)
257
+
258
+
259
+ def main():
260
+ parser = argparse.ArgumentParser(
261
+ description='Taiwan Invoice - 持久化配置工具',
262
+ formatter_class=argparse.RawDescriptionHelpFormatter,
263
+ epilog="""
264
+ Examples:
265
+ python persist.py init ECPay # 初始化 ECPay 配置
266
+ python persist.py init SmilePay -p "MyProject" # 指定專案名稱
267
+ python persist.py show # 顯示當前配置
268
+ python persist.py init Amego --force # 強制覆蓋
269
+ """
270
+ )
271
+
272
+ subparsers = parser.add_subparsers(dest='command', help='Commands')
273
+
274
+ # init 命令
275
+ init_parser = subparsers.add_parser('init', help='Initialize configuration')
276
+ init_parser.add_argument('provider', choices=['ECPay', 'SmilePay', 'Amego'],
277
+ help='Invoice provider')
278
+ init_parser.add_argument('-p', '--project', default='',
279
+ help='Project name')
280
+ init_parser.add_argument('-d', '--dir', default='.',
281
+ help='Target directory (default: current)')
282
+ init_parser.add_argument('-f', '--force', action='store_true',
283
+ help='Force overwrite existing config')
284
+
285
+ # show 命令
286
+ show_parser = subparsers.add_parser('show', help='Show current configuration')
287
+ show_parser.add_argument('-d', '--dir', default='.',
288
+ help='Target directory (default: current)')
289
+
290
+ # list 命令
291
+ list_parser = subparsers.add_parser('list', help='List available providers')
292
+
293
+ args = parser.parse_args()
294
+
295
+ if args.command == 'init':
296
+ try:
297
+ path = init_config(args.provider, args.dir, args.project, args.force)
298
+ print(format_ascii_box(
299
+ '✓ Configuration Initialized',
300
+ f"Provider: {args.provider}\nPath: {path}\n\nNext: Set your credentials in the MASTER.md file"
301
+ ))
302
+ except FileExistsError as e:
303
+ print(f"Error: {e}")
304
+ sys.exit(1)
305
+ except ValueError as e:
306
+ print(f"Error: {e}")
307
+ sys.exit(1)
308
+
309
+ elif args.command == 'show':
310
+ content = show_config(args.dir)
311
+ if content:
312
+ print(content)
313
+ else:
314
+ print("No configuration found. Run 'python persist.py init <provider>' to create one.")
315
+
316
+ elif args.command == 'list':
317
+ print("\nAvailable Providers:")
318
+ print("=" * 50)
319
+ for provider, config in PROVIDER_CONFIGS.items():
320
+ print(f"\n {provider}")
321
+ print(f" {config['display_name']}")
322
+ print(f" Auth: {config['auth_method']}")
323
+ print()
324
+
325
+ else:
326
+ parser.print_help()
327
+
328
+
329
+ if __name__ == '__main__':
330
+ main()
@@ -82,6 +82,17 @@ def load_providers() -> List[Dict[str, str]]:
82
82
  return list(reader)
83
83
 
84
84
 
85
+ def load_reasoning_rules() -> List[Dict[str, str]]:
86
+ """載入推理規則"""
87
+ filepath = os.path.join(DATA_DIR, 'reasoning.csv')
88
+ if not os.path.exists(filepath):
89
+ return []
90
+
91
+ with open(filepath, 'r', encoding='utf-8') as f:
92
+ reader = csv.DictReader(f)
93
+ return list(reader)
94
+
95
+
85
96
  def analyze_requirements(query: str) -> Dict[str, Tuple[int, List[str]]]:
86
97
  """
87
98
  分析使用者需求,計算各加值中心分數
@@ -97,7 +108,29 @@ def analyze_requirements(query: str) -> Dict[str, Tuple[int, List[str]]]:
97
108
  'Amego': (0, []),
98
109
  }
99
110
 
100
- # 根據關鍵字累計分數
111
+ # 從 reasoning.csv 載入規則
112
+ reasoning_rules = load_reasoning_rules()
113
+ confidence_weights = {'HIGH': 3, 'MEDIUM': 2, 'LOW': 1}
114
+
115
+ for rule in reasoning_rules:
116
+ scenario = rule.get('scenario', '').lower()
117
+ use_cases = rule.get('use_cases', '').lower()
118
+
119
+ # 檢查場景或使用案例是否匹配查詢
120
+ scenario_words = scenario.replace(' ', '')
121
+ if any(word in query_lower for word in scenario.split()) or \
122
+ any(word in query_lower for word in use_cases.split()):
123
+ provider = rule.get('recommended_provider', '')
124
+ confidence = rule.get('confidence', 'LOW')
125
+ reason = rule.get('reason', '')
126
+
127
+ if provider in scores:
128
+ weight = confidence_weights.get(confidence, 1)
129
+ current_score, reasons = scores[provider]
130
+ if reason and reason not in reasons:
131
+ scores[provider] = (current_score + weight, reasons + [reason])
132
+
133
+ # 根據關鍵字累計分數 (fallback)
101
134
  for keyword, rules in RECOMMENDATION_RULES.items():
102
135
  if keyword.lower() in query_lower:
103
136
  for provider, weight, reason in rules:
@@ -23,22 +23,29 @@ from core import (
23
23
  )
24
24
 
25
25
 
26
- def format_ascii_box(title: str, content: List[str], width: int = 80) -> str:
26
+ def format_ascii_box(title: str, content: List[str], width: int = 80, style: str = 'double') -> str:
27
27
  """
28
28
  格式化 ASCII Box 輸出
29
+
30
+ style: 'single' (─│) or 'double' (═║)
29
31
  """
32
+ if style == 'double':
33
+ h, v, tl, tr, bl, br, lm, rm = '═', '║', '╔', '╗', '╚', '╝', '╠', '╣'
34
+ else:
35
+ h, v, tl, tr, bl, br, lm, rm = '─', '│', '┌', '┐', '└', '┘', '├', '┤'
36
+
30
37
  lines = []
31
- lines.append('┌' + '─' * (width - 2) + '┐')
32
- lines.append('│' + f' {title}'.ljust(width - 2) + '│')
33
- lines.append('├' + '─' * (width - 2) + '┤')
38
+ lines.append(tl + h * (width - 2) + tr)
39
+ lines.append(v + f' {title}'.ljust(width - 2) + v)
40
+ lines.append(lm + h * (width - 2) + rm)
34
41
 
35
42
  for line in content:
36
43
  # 處理過長的行
37
44
  if len(line) > width - 4:
38
45
  line = line[:width - 7] + '...'
39
- lines.append(' ' + line.ljust(width - 4) + ' ')
46
+ lines.append(v + ' ' + line.ljust(width - 4) + ' ' + v)
40
47
 
41
- lines.append('└' + '─' * (width - 2) + '┘')
48
+ lines.append(bl + h * (width - 2) + br)
42
49
  return '\n'.join(lines)
43
50
 
44
51
 
@@ -104,6 +111,66 @@ def format_all_results(results: Dict[str, List[Dict[str, Any]]], query: str) ->
104
111
  return '\n'.join(output)
105
112
 
106
113
 
114
+ def format_markdown_result(result: Dict[str, Any], index: int) -> str:
115
+ """
116
+ 格式化單個結果為 Markdown
117
+ """
118
+ lines = []
119
+ score = result.get('_score', 0)
120
+ lines.append(f"### Result {index} (Score: {score})")
121
+ lines.append("")
122
+
123
+ for key, value in result.items():
124
+ if key == '_score' or not value:
125
+ continue
126
+ lines.append(f"- **{key}**: {value}")
127
+
128
+ lines.append("")
129
+ return '\n'.join(lines)
130
+
131
+
132
+ def format_markdown_domain(results: List[Dict[str, Any]], domain: str, query: str) -> str:
133
+ """
134
+ 格式化域結果為 Markdown
135
+ """
136
+ if not results:
137
+ return f"No results found in '{domain}' for query: {query}\n"
138
+
139
+ lines = []
140
+ lines.append(f"## {domain.upper()}")
141
+ lines.append(f"")
142
+ lines.append(f"> Query: `{query}` | Results: {len(results)}")
143
+ lines.append("")
144
+
145
+ for i, result in enumerate(results, 1):
146
+ lines.append(format_markdown_result(result, i))
147
+
148
+ return '\n'.join(lines)
149
+
150
+
151
+ def format_markdown_all(results: Dict[str, List[Dict[str, Any]]], query: str) -> str:
152
+ """
153
+ 格式化所有域結果為 Markdown
154
+ """
155
+ if not results:
156
+ return f"# No results found for query: {query}\n"
157
+
158
+ lines = []
159
+ lines.append(f"# Taiwan Invoice Search Results")
160
+ lines.append(f"")
161
+ lines.append(f"**Query**: `{query}`")
162
+ lines.append(f"")
163
+ lines.append("---")
164
+ lines.append("")
165
+
166
+ for domain, domain_results in results.items():
167
+ lines.append(format_markdown_domain(domain_results, domain, query))
168
+ lines.append("---")
169
+ lines.append("")
170
+
171
+ return '\n'.join(lines)
172
+
173
+
107
174
  def list_domains():
108
175
  """
109
176
  列出所有可用的搜索域
@@ -148,7 +215,7 @@ Examples:
148
215
  help='Search all domains')
149
216
  parser.add_argument('-l', '--list', action='store_true',
150
217
  help='List available domains')
151
- parser.add_argument('-f', '--format', choices=['ascii', 'simple', 'json'],
218
+ parser.add_argument('-f', '--format', choices=['ascii', 'simple', 'json', 'markdown', 'md'],
152
219
  default='ascii', help='Output format (default: ascii)')
153
220
 
154
221
  args = parser.parse_args()
@@ -172,6 +239,8 @@ Examples:
172
239
  if args.format == 'json':
173
240
  import json
174
241
  print(json.dumps(results, ensure_ascii=False, indent=2))
242
+ elif args.format in ('markdown', 'md'):
243
+ print(format_markdown_all(results, query))
175
244
  else:
176
245
  print(format_all_results(results, query))
177
246
  return
@@ -180,13 +249,16 @@ Examples:
180
249
  domain = args.domain
181
250
  if not domain:
182
251
  domain = detect_domain(query)
183
- print(f"[Auto-detected domain: {domain}]")
252
+ if args.format not in ('json', 'markdown', 'md'):
253
+ print(f"[Auto-detected domain: {domain}]")
184
254
 
185
255
  results = search(query, domain, args.max_results)
186
256
 
187
257
  if args.format == 'json':
188
258
  import json
189
259
  print(json.dumps(results, ensure_ascii=False, indent=2))
260
+ elif args.format in ('markdown', 'md'):
261
+ print(format_markdown_domain(results, domain, query))
190
262
  elif args.format == 'simple':
191
263
  for i, result in enumerate(results, 1):
192
264
  print(f"\n[{i}] Score: {result.get('_score', 0)}")
@@ -1,16 +1,18 @@
1
1
  {
2
2
  "platform": "antigravity",
3
3
  "displayName": "Antigravity / Generic Agent",
4
- "installType": "full",
4
+ "installType": "workflow",
5
5
  "folderStructure": {
6
6
  "root": ".agent",
7
- "skillPath": "skills/taiwan-invoice",
8
- "filename": "SKILL.md"
7
+ "skillPath": "workflows",
8
+ "filename": "taiwan-invoice.md",
9
+ "sharedRoot": ".shared",
10
+ "sharedPath": "taiwan-invoice"
9
11
  },
10
- "scriptPath": "skills/taiwan-invoice/scripts",
12
+ "scriptPath": ".shared/taiwan-invoice/scripts",
11
13
  "frontmatter": {
12
- "name": "taiwan-invoice",
13
- "description": "Taiwan E-Invoice API integration specialist for ECPay, SmilePay, and Amego. Provides B2C/B2B invoice issuance, void, allowance, query, and print functionality."
14
+ "description": "Taiwan E-Invoice API integration for ECPay, SmilePay, and Amego",
15
+ "auto_execution_mode": "3"
14
16
  },
15
17
  "sections": {
16
18
  "examples": true,
@@ -18,7 +20,7 @@
18
20
  "scripts": true,
19
21
  "quickReference": false
20
22
  },
21
- "title": "Taiwan E-Invoice Skill",
22
- "description": "Comprehensive guide for Taiwan E-Invoice API integration. Supports ECPay, SmilePay, and Amego providers with B2C/B2B invoice operations.",
23
- "skillOrWorkflow": "Skill"
23
+ "title": "taiwan-invoice",
24
+ "description": "台灣電子發票 API 整合工具。支援 ECPaySmilePayAmego 三家加值中心的 B2C/B2B 發票開立、作廢、折讓、查詢、列印功能。",
25
+ "skillOrWorkflow": "Workflow"
24
26
  }
package/dist/index.js CHANGED
@@ -14990,7 +14990,7 @@ var AI_FOLDERS = {
14990
14990
  claude: [".claude"],
14991
14991
  cursor: [".cursor"],
14992
14992
  windsurf: [".windsurf"],
14993
- antigravity: [".agent"],
14993
+ antigravity: [".agent", ".shared"],
14994
14994
  copilot: [".github"],
14995
14995
  kiro: [".kiro"],
14996
14996
  codex: [".codex"],
@@ -15087,10 +15087,150 @@ async function copyTaiwanInvoiceAssets(targetSkillDir, sections) {
15087
15087
  await (0, import_promises.mkdir)(scriptsTarget, { recursive: true });
15088
15088
  await (0, import_promises.cp)((0, import_node_path.join)(sourceDir, "scripts"), scriptsTarget, { recursive: true });
15089
15089
  }
15090
+ if (sections.scripts && await exists((0, import_node_path.join)(sourceDir, "data"))) {
15091
+ const dataTarget = (0, import_node_path.join)(targetSkillDir, "data");
15092
+ await (0, import_promises.mkdir)(dataTarget, { recursive: true });
15093
+ await (0, import_promises.cp)((0, import_node_path.join)(sourceDir, "data"), dataTarget, { recursive: true });
15094
+ }
15095
+ }
15096
+ async function renderWorkflowFile(config) {
15097
+ const frontmatter = renderFrontmatter(config.frontmatter);
15098
+ const sharedPath = config.folderStructure.sharedPath || "taiwan-invoice";
15099
+ const content = `# ${config.title}
15100
+
15101
+ ${config.description}
15102
+
15103
+ ## Prerequisites
15104
+
15105
+ Check if Python is installed:
15106
+
15107
+ \`\`\`bash
15108
+ python3 --version || python --version
15109
+ \`\`\`
15110
+
15111
+ ---
15112
+
15113
+ ## How to Use This Workflow
15114
+
15115
+ When user requests Taiwan E-Invoice work (issue, void, allowance, print, query), follow this workflow:
15116
+
15117
+ ### Step 1: Analyze User Requirements
15118
+
15119
+ Extract key information:
15120
+ - **Provider**: ECPay, SmilePay, or Amego
15121
+ - **Invoice type**: B2C (\u4E8C\u806F\u5F0F) or B2B (\u4E09\u806F\u5F0F)
15122
+ - **Operation**: issue, void, allowance, print, query
15123
+
15124
+ ### Step 2: Recommend Provider (Optional)
15125
+
15126
+ \`\`\`bash
15127
+ python3 .shared/${sharedPath}/scripts/recommend.py "<keywords>"
15128
+ \`\`\`
15129
+
15130
+ Example:
15131
+ \`\`\`bash
15132
+ python3 .shared/${sharedPath}/scripts/recommend.py "\u96FB\u5546 \u9AD8\u4EA4\u6613\u91CF \u7A69\u5B9A"
15133
+ \`\`\`
15134
+
15135
+ ### Step 3: Search for Information
15136
+
15137
+ \`\`\`bash
15138
+ python3 .shared/${sharedPath}/scripts/search.py "<keyword>" --domain <domain>
15139
+ \`\`\`
15140
+
15141
+ Available domains:
15142
+ | Domain | Description |
15143
+ |--------|-------------|
15144
+ | provider | Provider comparison |
15145
+ | operation | API endpoints |
15146
+ | error | Error codes |
15147
+ | field | Field mappings |
15148
+ | tax | Tax rules |
15149
+ | troubleshoot | Troubleshooting |
15150
+
15151
+ ### Step 4: Generate Service Code
15152
+
15153
+ \`\`\`bash
15154
+ python3 .shared/${sharedPath}/scripts/generate-invoice-service.py <Provider> --output ts
15155
+ \`\`\`
15156
+
15157
+ ### Step 5: Initialize Project Config (Optional)
15158
+
15159
+ \`\`\`bash
15160
+ python3 .shared/${sharedPath}/scripts/persist.py init <Provider>
15161
+ \`\`\`
15162
+
15163
+ ---
15164
+
15165
+ ## Quick Reference
15166
+
15167
+ ### B2C Invoice (\u4E8C\u806F\u5F0F)
15168
+ - Amount: **Tax-inclusive**
15169
+ - BuyerIdentifier: \`0000000000\`
15170
+ - TaxAmount: \`0\`
15171
+ - Can use carrier/donation
15172
+
15173
+ ### B2B Invoice (\u4E09\u806F\u5F0F)
15174
+ - Amount: **Tax-exclusive**
15175
+ - Must fill 8-digit tax ID
15176
+ - Calculate tax: \`TaxAmount = round(Total - Total/1.05)\`
15177
+ - **Cannot** use carrier/donation
15178
+
15179
+ ---
15180
+
15181
+ ## Test Credentials
15182
+
15183
+ ### ECPay
15184
+ \`\`\`
15185
+ MerchantID: 2000132
15186
+ HashKey: ejCk326UnaZWKisg
15187
+ HashIV: q9jcZX8Ib9LM8wYk
15188
+ \`\`\`
15189
+
15190
+ ### SmilePay
15191
+ \`\`\`
15192
+ Grvc: SEI1000034
15193
+ Verify_key: 9D73935693EE0237FABA6AB744E48661
15194
+ \`\`\`
15195
+
15196
+ ### Amego
15197
+ \`\`\`
15198
+ Invoice: 12345678
15199
+ App Key: sHeq7t8G1wiQvhAuIM27
15200
+ \`\`\`
15201
+
15202
+ ---
15203
+
15204
+ *Generated by Taiwan Invoice Skill*
15205
+ `;
15206
+ return frontmatter + content;
15090
15207
  }
15091
15208
  async function generatePlatformFiles(targetDir, aiType) {
15092
15209
  const config = await loadPlatformConfig(aiType);
15093
15210
  const createdFolders = [];
15211
+ if (config.installType === "workflow") {
15212
+ const workflowDir = (0, import_node_path.join)(
15213
+ targetDir,
15214
+ config.folderStructure.root,
15215
+ config.folderStructure.skillPath
15216
+ );
15217
+ await (0, import_promises.mkdir)(workflowDir, { recursive: true });
15218
+ const workflowContent = await renderWorkflowFile(config);
15219
+ const workflowFilePath = (0, import_node_path.join)(workflowDir, config.folderStructure.filename);
15220
+ await (0, import_promises.writeFile)(workflowFilePath, workflowContent, "utf-8");
15221
+ createdFolders.push(config.folderStructure.root);
15222
+ if (config.folderStructure.sharedRoot && config.folderStructure.sharedPath) {
15223
+ const sharedDir = (0, import_node_path.join)(
15224
+ targetDir,
15225
+ config.folderStructure.sharedRoot,
15226
+ config.folderStructure.sharedPath
15227
+ );
15228
+ await (0, import_promises.mkdir)(sharedDir, { recursive: true });
15229
+ await copyTaiwanInvoiceAssets(sharedDir, config.sections);
15230
+ createdFolders.push(config.folderStructure.sharedRoot);
15231
+ }
15232
+ return createdFolders;
15233
+ }
15094
15234
  const skillDir = (0, import_node_path.join)(
15095
15235
  targetDir,
15096
15236
  config.folderStructure.root,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taiwan-invoice-skill",
3
- "version": "2.2.0",
3
+ "version": "2.4.0",
4
4
  "description": "CLI to install Taiwan E-Invoice skill for AI coding assistants",
5
5
  "bin": {
6
6
  "taiwan-invoice": "./dist/index.js"