sophhub 0.4.45 → 0.4.47

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.
Files changed (37) hide show
  1. package/package.json +1 -1
  2. package/skills/sophnet-age-appearance/skill.json +9 -2
  3. package/skills/sophnet-age-appearance/src/scripts/age_appearance.py +8 -10
  4. package/skills/sophnet-id-photo/skill.json +9 -2
  5. package/skills/sophnet-id-photo/src/scripts/id_photo.py +5 -7
  6. package/skills/sophnet-image-edit/skill.json +9 -2
  7. package/skills/sophnet-image-edit/src/scripts/edit_image.py +5 -7
  8. package/skills/sophnet-image-generate/skill.json +9 -2
  9. package/skills/sophnet-image-generate/src/scripts/generate_image.py +51 -1
  10. package/skills/sophnet-infinite-talk/skill.json +9 -2
  11. package/skills/sophnet-infinite-talk/src/scripts/gen.py +39 -0
  12. package/skills/sophnet-invoice-assistant/skill.json +21 -0
  13. package/skills/sophnet-invoice-assistant/src/SKILL.md +186 -0
  14. package/skills/sophnet-invoice-assistant/src/pyproject.toml +8 -0
  15. package/skills/sophnet-invoice-assistant/src/references/api-contract.md +73 -0
  16. package/skills/sophnet-invoice-assistant/src/references/conversation-examples.md +16 -0
  17. package/skills/sophnet-invoice-assistant/src/references/error-cases.md +16 -0
  18. package/skills/sophnet-invoice-assistant/src/scripts/__init__.py +1 -0
  19. package/skills/sophnet-invoice-assistant/src/scripts/auth_context.py +74 -0
  20. package/skills/sophnet-invoice-assistant/src/scripts/cli.py +136 -0
  21. package/skills/sophnet-invoice-assistant/src/scripts/download_flow.py +89 -0
  22. package/skills/sophnet-invoice-assistant/src/scripts/dto.py +46 -0
  23. package/skills/sophnet-invoice-assistant/src/scripts/invoice_client.py +164 -0
  24. package/skills/sophnet-invoice-assistant/src/scripts/output.py +63 -0
  25. package/skills/sophnet-invoice-assistant/src/scripts/prepare_flow.py +58 -0
  26. package/skills/sophnet-invoice-assistant/src/scripts/risk_rules.py +95 -0
  27. package/skills/sophnet-invoice-assistant/src/scripts/status_flow.py +69 -0
  28. package/skills/sophnet-invoice-assistant/src/scripts/submit_flow.py +472 -0
  29. package/skills/sophnet-invoice-assistant/src/scripts/template_flow.py +196 -0
  30. package/skills/sophnet-invoice-assistant/src/scripts/update_flow.py +223 -0
  31. package/skills/sophnet-sticker-edit/skill.json +9 -2
  32. package/skills/sophnet-sticker-edit/src/scripts/edit_sticker_image.py +5 -7
  33. package/skills/sophnet-video-generate/skill.json +9 -2
  34. package/skills/sophnet-video-generate/src/SKILL.md +21 -12
  35. package/skills/sophnet-video-generate/src/scripts/gen_video.py +10 -15
  36. package/skills/store-marketing/skill.json +9 -2
  37. package/skills/store-marketing/src/scripts/generate_poster.py +39 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sophhub",
3
- "version": "0.4.45",
3
+ "version": "0.4.47",
4
4
  "description": "SophHub CLI - Manage and download AI Agent skills and agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,12 +1,19 @@
1
1
  {
2
2
  "name": "sophnet-age-appearance",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "types": [
5
5
  "store"
6
6
  ],
7
7
  "displayName": "年龄相貌模拟",
8
8
  "description": "仅支持单张人物图:用 AI 模拟指定年龄的长相(幼年、青少年或成年以后等推演)。用户问「10 岁 / 20 岁长什么样」「变老变年轻」「年龄推演」「小时候或以后样子」并希望出图时使用;多图需分次调用。",
9
9
  "changelog": [
10
+ {
11
+ "version": "1.0.2",
12
+ "date": "2026-06-24",
13
+ "changes": [
14
+ "修复 upload_oss 失败时临时文件泄漏;移除 PREVIEW_PATH 回退"
15
+ ]
16
+ },
10
17
  {
11
18
  "version": "1.0.1",
12
19
  "date": "2026-06-17",
@@ -23,5 +30,5 @@
23
30
  }
24
31
  ],
25
32
  "createdAt": "2026-05-13",
26
- "updatedAt": "2026-06-17"
33
+ "updatedAt": "2026-06-24"
27
34
  }
@@ -216,8 +216,9 @@ def reupload_for_signed_url(api_key, raw_url):
216
216
 
217
217
  signed_url = sophnet_tools.upload_oss(tmp_path)
218
218
  if not signed_url:
219
- print("Warning: upload_oss returned no signed URL, keeping local file", file=sys.stderr)
220
- return None, tmp_path
219
+ print("Warning: upload_oss returned no signed URL", file=sys.stderr)
220
+ os.unlink(tmp_path)
221
+ return None, None
221
222
 
222
223
  return signed_url, tmp_path
223
224
 
@@ -246,14 +247,11 @@ def emit_one_age_image(api_key, args, ages_list, idx, edit_refs):
246
247
  print(f"TARGET_AGE={age}", flush=True)
247
248
  signed_url, local_path = reupload_for_signed_url(api_key, raw_url)
248
249
  print(f"IMAGE_URL={signed_url or raw_url}", flush=True)
249
- if local_path:
250
- if signed_url:
251
- try:
252
- os.unlink(local_path)
253
- except OSError:
254
- pass
255
- else:
256
- print(f"PREVIEW_PATH={local_path}", flush=True)
250
+ if signed_url and local_path:
251
+ try:
252
+ os.unlink(local_path)
253
+ except OSError:
254
+ pass
257
255
  if idx + 1 < len(ages_list):
258
256
  time.sleep(max(0, args.pause_between_calls))
259
257
 
@@ -1,12 +1,19 @@
1
1
  {
2
2
  "name": "sophnet-id-photo",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "types": [
5
5
  "store"
6
6
  ],
7
7
  "displayName": "一键证件照",
8
8
  "description": "Sophnet 证件照:人脸检测后在多张达标脸中取面积最大者裁切头肩,VL 预审后换纯色底(白/蓝/红)并微调体态与角度。用户要证件照、换证件照底色、一寸/二寸/报名或电子证照底色时使用。",
9
9
  "changelog": [
10
+ {
11
+ "version": "1.0.2",
12
+ "date": "2026-06-24",
13
+ "changes": [
14
+ "修复 upload_oss 失败时临时文件泄漏;移除 PREVIEW_PATH 回退"
15
+ ]
16
+ },
10
17
  {
11
18
  "version": "1.0.1",
12
19
  "date": "2026-06-17",
@@ -23,5 +30,5 @@
23
30
  }
24
31
  ],
25
32
  "createdAt": "2026-05-13",
26
- "updatedAt": "2026-06-17"
33
+ "updatedAt": "2026-06-24"
27
34
  }
@@ -323,8 +323,9 @@ def reupload_for_signed_url(api_key, raw_url):
323
323
 
324
324
  signed_url = sophnet_tools.upload_oss(tmp_path)
325
325
  if not signed_url:
326
- print("Warning: upload_oss returned no signed URL, keeping local file", file=sys.stderr)
327
- return None, tmp_path
326
+ print("Warning: upload_oss returned no signed URL", file=sys.stderr)
327
+ os.unlink(tmp_path)
328
+ return None, None
328
329
 
329
330
  return signed_url, tmp_path
330
331
 
@@ -527,11 +528,8 @@ def main():
527
528
  for i, raw_url in enumerate(raw_urls, 1):
528
529
  signed_url, local_path = reupload_for_signed_url(api_key, raw_url)
529
530
  print(f"IMAGE_URL={signed_url or raw_url}")
530
- if local_path:
531
- if signed_url:
532
- os.unlink(local_path)
533
- else:
534
- print(f"PREVIEW_PATH={local_path}")
531
+ if signed_url and local_path:
532
+ os.unlink(local_path)
535
533
 
536
534
  print(f"ALL_BG_TIP={ALL_BG_TIP}", flush=True)
537
535
 
@@ -1,12 +1,19 @@
1
1
  {
2
2
  "name": "sophnet-image-edit",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "types": [
5
5
  "builtin"
6
6
  ],
7
7
  "displayName": "",
8
8
  "description": "",
9
9
  "changelog": [
10
+ {
11
+ "version": "1.0.3",
12
+ "date": "2026-06-24",
13
+ "changes": [
14
+ "修复 upload_oss 失败时临时文件泄漏;移除 PREVIEW_PATH 回退"
15
+ ]
16
+ },
10
17
  {
11
18
  "version": "1.0.2",
12
19
  "date": "2026-06-17",
@@ -30,5 +37,5 @@
30
37
  }
31
38
  ],
32
39
  "createdAt": "2026-04-09",
33
- "updatedAt": "2026-06-17"
40
+ "updatedAt": "2026-06-24"
34
41
  }
@@ -194,8 +194,9 @@ def reupload_for_signed_url(api_key, raw_url):
194
194
 
195
195
  signed_url = sophnet_tools.upload_oss(tmp_path)
196
196
  if not signed_url:
197
- print("Warning: upload_oss returned no signed URL, keeping local file", file=sys.stderr)
198
- return None, tmp_path
197
+ print("Warning: upload_oss returned no signed URL", file=sys.stderr)
198
+ os.unlink(tmp_path)
199
+ return None, None
199
200
 
200
201
  return signed_url, tmp_path
201
202
 
@@ -268,11 +269,8 @@ def main():
268
269
  for i, raw_url in enumerate(raw_urls, 1):
269
270
  signed_url, local_path = reupload_for_signed_url(api_key, raw_url)
270
271
  print(f"IMAGE_URL={signed_url or raw_url}")
271
- if local_path:
272
- if signed_url:
273
- os.unlink(local_path)
274
- else:
275
- print(f"PREVIEW_PATH={local_path}")
272
+ if signed_url and local_path:
273
+ os.unlink(local_path)
276
274
 
277
275
 
278
276
  if __name__ == "__main__":
@@ -1,12 +1,19 @@
1
1
  {
2
2
  "name": "sophnet-image-generate",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "types": [
5
5
  "builtin"
6
6
  ],
7
7
  "displayName": "",
8
8
  "description": "",
9
9
  "changelog": [
10
+ {
11
+ "version": "1.0.3",
12
+ "date": "2026-06-24",
13
+ "changes": [
14
+ "生成图片后通过 sophnet_tools.upload_oss 重新上传,输出短链接避免被 Openclaw 截断"
15
+ ]
16
+ },
10
17
  {
11
18
  "version": "1.0.2",
12
19
  "date": "2026-06-17",
@@ -30,5 +37,5 @@
30
37
  }
31
38
  ],
32
39
  "createdAt": "2026-04-09",
33
- "updatedAt": "2026-06-17"
40
+ "updatedAt": "2026-06-24"
34
41
  }
@@ -6,7 +6,9 @@ Outputs machine-friendly TASK_ID, STATUS, and IMAGE_URL lines.
6
6
 
7
7
  import argparse
8
8
  import json
9
+ import os
9
10
  import sys
11
+ import tempfile
10
12
  import time
11
13
 
12
14
  import requests
@@ -111,6 +113,49 @@ def extract_urls(data):
111
113
  return urls
112
114
 
113
115
 
116
+ def reupload_images(api_key, urls):
117
+ """Download images from URLs and re-upload to OSS for short signed URLs."""
118
+ headers = {"Authorization": f"Bearer {api_key}"}
119
+ new_urls = []
120
+ temp_files = []
121
+
122
+ for url in urls:
123
+ try:
124
+ resp = requests.get(url, headers=headers, timeout=120, stream=True)
125
+ resp.raise_for_status()
126
+ except requests.RequestException as e:
127
+ print(f"Warning: failed to download image: {e}", file=sys.stderr)
128
+ continue
129
+
130
+ ext = os.path.splitext(url.split("?")[0])[-1] or ".png"
131
+ fd, tmp_path = tempfile.mkstemp(suffix=ext, prefix="img_gen_")
132
+ try:
133
+ with os.fdopen(fd, "wb") as f:
134
+ for chunk in resp.iter_content(8192):
135
+ f.write(chunk)
136
+ except IOError as e:
137
+ print(f"Warning: failed to write temp file: {e}", file=sys.stderr)
138
+ os.unlink(tmp_path)
139
+ continue
140
+
141
+ signed_url = sophnet_tools.upload_oss(tmp_path)
142
+ if signed_url:
143
+ new_urls.append(signed_url)
144
+ else:
145
+ print("Warning: upload_oss failed, using original URL", file=sys.stderr)
146
+ new_urls.append(url)
147
+
148
+ temp_files.append(tmp_path)
149
+
150
+ for f in temp_files:
151
+ try:
152
+ os.unlink(f)
153
+ except OSError:
154
+ pass
155
+
156
+ return new_urls
157
+
158
+
114
159
  def main():
115
160
  parser = argparse.ArgumentParser(description="SophNet image generation")
116
161
  parser.add_argument("--prompt", required=True, help="Image prompt")
@@ -148,7 +193,12 @@ def main():
148
193
  print(json.dumps(result, ensure_ascii=False), file=sys.stderr)
149
194
  sys.exit(1)
150
195
 
151
- for u in urls:
196
+ new_urls = reupload_images(api_key, urls)
197
+ if not new_urls:
198
+ print("Error: all image re-uploads failed.", file=sys.stderr)
199
+ sys.exit(1)
200
+
201
+ for u in new_urls:
152
202
  print(f"IMAGE_URL={u}")
153
203
 
154
204
 
@@ -1,12 +1,19 @@
1
1
  {
2
2
  "name": "sophnet-infinite-talk",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "types": [
5
5
  "builtin"
6
6
  ],
7
7
  "displayName": "",
8
8
  "description": "",
9
9
  "changelog": [
10
+ {
11
+ "version": "1.0.2",
12
+ "date": "2026-06-24",
13
+ "changes": [
14
+ "query 命令输出 video_url 前通过 sophnet_tools.upload_oss 重新上传,避免长链接被截断"
15
+ ]
16
+ },
10
17
  {
11
18
  "version": "1.0.1",
12
19
  "date": "2026-06-17",
@@ -23,5 +30,5 @@
23
30
  }
24
31
  ],
25
32
  "createdAt": "2026-04-09",
26
- "updatedAt": "2026-06-17"
33
+ "updatedAt": "2026-06-24"
27
34
  }
@@ -14,6 +14,7 @@ import os
14
14
  import sys
15
15
  import argparse
16
16
  import json
17
+ import tempfile
17
18
  import requests
18
19
  from typing import Optional, Any, Dict
19
20
  import sophnet_tools
@@ -120,6 +121,40 @@ def get_task(api_key: str, task_id: str) -> Dict[str, Any]:
120
121
  return {"status": status, "video_url": video_url}
121
122
 
122
123
 
124
+ def reupload_video(api_key, video_url):
125
+ """Download video from API URL and re-upload to OSS for short signed URL."""
126
+ headers = _headers(api_key)
127
+ try:
128
+ resp = requests.get(video_url, headers=headers, timeout=300, stream=True)
129
+ resp.raise_for_status()
130
+ except requests.RequestException as e:
131
+ print(f"Warning: failed to download video: {e}", file=sys.stderr)
132
+ return None
133
+
134
+ ext = os.path.splitext(video_url.split("?")[0])[-1] or ".mp4"
135
+ fd, tmp_path = tempfile.mkstemp(suffix=ext, prefix="inf_talk_")
136
+ try:
137
+ with os.fdopen(fd, "wb") as f:
138
+ for chunk in resp.iter_content(8192):
139
+ f.write(chunk)
140
+ except IOError as e:
141
+ print(f"Warning: failed to write temp file: {e}", file=sys.stderr)
142
+ os.unlink(tmp_path)
143
+ return None
144
+
145
+ signed_url = sophnet_tools.upload_oss(tmp_path)
146
+ try:
147
+ os.unlink(tmp_path)
148
+ except OSError:
149
+ pass
150
+
151
+ if not signed_url:
152
+ print("Warning: upload_oss returned no signed URL", file=sys.stderr)
153
+ return None
154
+
155
+ return signed_url
156
+
157
+
123
158
  def _json_out(obj: Dict[str, Any]) -> None:
124
159
  print(json.dumps(obj, ensure_ascii=False))
125
160
 
@@ -167,6 +202,10 @@ if __name__ == "__main__":
167
202
 
168
203
  elif args.command == "query":
169
204
  result = get_task(api_key, args.task_id)
205
+ if result.get("status") == "succeeded" and result.get("video_url"):
206
+ signed_url = reupload_video(api_key, result["video_url"])
207
+ if signed_url:
208
+ result["video_url"] = signed_url
170
209
  _json_out(result)
171
210
  if "error" in result:
172
211
  sys.exit(1)
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "sophnet-invoice-assistant",
3
+ "version": "1.0.0",
4
+ "types": [
5
+ "store"
6
+ ],
7
+ "displayName": "Sophnet发票助手",
8
+ "description": "帮助已登录 Sophnet 的个人用户在 OpenClaw 中收集开票信息、提交发票申请、查询发票状态和下载发票文件。",
9
+ "changelog": [
10
+ {
11
+ "version": "1.0.0",
12
+ "date": "2026-06-26",
13
+ "changes": [
14
+ "初始化个人发票助手 skill 骨架",
15
+ "补充设计文档、脚本入口与参考文档"
16
+ ]
17
+ }
18
+ ],
19
+ "createdAt": "2026-06-26",
20
+ "updatedAt": "2026-06-26"
21
+ }
@@ -0,0 +1,186 @@
1
+ ---
2
+ name: sophnet-invoice-assistant
3
+ description: Use when an OpenClaw user wants to manage personal Sophnet invoice templates, apply for an invoice, update an invoice application, check invoice status, or download an issued invoice file while already logged in to Sophnet.
4
+ ---
5
+
6
+ # Sophnet Invoice Assistant
7
+
8
+ 用于帮助已登录 Sophnet 的用户完成发票中心相关操作,包括:
9
+
10
+ - 准备发票上下文
11
+ - 提交发票申请
12
+ - 修改发票申请
13
+ - 查询发票申请状态
14
+ - 下载发票文件
15
+ - 管理发票模板
16
+
17
+ ## Supported actions
18
+
19
+ - `prepare`
20
+ - `submit`
21
+ - `update`
22
+ - `status`
23
+ - `download`
24
+ - `template_list`
25
+ - `template_save`
26
+ - `template_delete`
27
+
28
+ ## Auth requirements
29
+
30
+ 脚本会按以下优先级自动读取 Sophnet 登录态:
31
+
32
+ - `SOPHNET_ACCESS_TOKEN`
33
+ - `SOPHNET_COOKIE`
34
+ - `OPENCLAW_JWT_PATH` 指向的 `jwt.json` 中的 `web_jwt`
35
+ - 默认位置的 `jwt.json`
36
+
37
+ 支持的 API Base URL 环境变量:
38
+
39
+ - `SOPHNET_API_BASE_URL`
40
+ - `SOPHNET_BASE_URL`
41
+
42
+ ## CLI usage
43
+
44
+ ```bash
45
+ cd "<this-skill-directory>"
46
+ uv run {baseDir}/scripts/cli.py --action prepare --user-request "帮我准备发票信息"
47
+ ```
48
+
49
+ ```bash
50
+ cd "<this-skill-directory>"
51
+ uv run {baseDir}/scripts/cli.py \
52
+ --action submit \
53
+ --user-request "帮我给这两笔充值开票并聚合开一张" \
54
+ --buyer-type ENTERPRISE \
55
+ --invoice-type SPECIAL \
56
+ --title "杭州某某科技有限公司" \
57
+ --tax-no "9133XXXX" \
58
+ --address-phone "杭州xx路 0571-xxxx" \
59
+ --bank-account "招商银行杭州分行 6222xxxx" \
60
+ --recharge-trans-nos "RCHG001,RCHG002" \
61
+ --issuing-mode AGGREGATED
62
+ ```
63
+
64
+ ```bash
65
+ cd "<this-skill-directory>"
66
+ uv run {baseDir}/scripts/cli.py \
67
+ --action update \
68
+ --application-no INV202606250001 \
69
+ --recharge-trans-nos "RCHG001,RCHG002" \
70
+ --issuing-mode SEPARATE \
71
+ --user-request "把这张发票申请改成分开开发票"
72
+ ```
73
+
74
+ ## Parameters
75
+
76
+ - `--action`: `prepare|submit|update|status|download|template_list|template_save|template_delete`
77
+ - `--user-request`: 用户原始请求
78
+ - `--org-id`: 可选,已知个人组织 ID
79
+ - `--application-no`: 可选,查询/下载/修改指定申请时使用
80
+ - `--template-id`: 可选,删除模板时使用
81
+ - `--confirm`: `true|false`
82
+ - `--buyer-type`: `INDIVIDUAL|ENTERPRISE`
83
+ - `--title`: 发票抬头
84
+ - `--tax-no`: 企业税号
85
+ - `--address-phone`: 企业地址电话
86
+ - `--bank-account`: 企业开户行及账号
87
+ - `--invoice-type`: `NORMAL|SPECIAL`
88
+ - `--recharge-trans-nos`: 逗号分隔的充值流水号
89
+ - `--issuing-mode`: `AGGREGATED|SEPARATE`
90
+ - `--save-as-template`: `true|false`
91
+ - `--template-name`: 模板名称
92
+ - `--use-latest-template`: `true|false`
93
+
94
+ ## Current business rules
95
+
96
+ ### Recharge eligibility
97
+
98
+ - `2026-06-24` 之前的充值记录不支持自助开发票
99
+ - skill 会在 `prepare` 中显式返回:
100
+ - `AVAILABLE_RECHARGE_RECORDS`
101
+ - `INELIGIBLE_RECHARGE_RECORDS`
102
+ - `INVOICE_ELIGIBLE_START_DATE=2026-06-24`
103
+ - 如果用户选择了不可开票记录,`submit` / `update` 会直接失败并提示重新选择
104
+
105
+ ### Multiple recharge records
106
+
107
+ - 当一次申请关联多条 `rechargeTransNos` 时,必须指定 `issuingMode`
108
+ - 可选值:
109
+ - `AGGREGATED`: 多条充值记录聚合开票
110
+ - `SEPARATE`: 每条充值记录分别开票
111
+ - 如果用户选了多条充值记录但没有给出 `issuingMode`,skill 会返回 `STATUS=need_confirmation`
112
+
113
+ ## Current behavior
114
+
115
+ ### `prepare`
116
+
117
+ - 自动定位当前个人组织
118
+ - 拉取发票模板
119
+ - 拉取可开票充值记录
120
+ - 拉取最近发票申请
121
+ - 额外返回不可开票记录数量与起始日期限制
122
+
123
+ ### `submit`
124
+
125
+ - 调用真实 Sophnet 发票申请接口
126
+ - 自动解析用户选择的模板、开票信息和充值流水
127
+ - 多条充值流水时要求 `issuingMode`
128
+ - 自动过滤 `2026-06-24` 之前的充值记录
129
+ - 当前默认会将本次开票信息保存为模板
130
+
131
+ ### `update`
132
+
133
+ - 调用真实 Sophnet 发票申请修改接口
134
+ - 支持修改抬头、税号、地址电话、银行账号、发票类型、充值流水、开票方式
135
+ - 多条充值流水时要求 `issuingMode`
136
+ - 默认先返回确认摘要,再由 OpenClaw 做二次确认
137
+
138
+ ### `status`
139
+
140
+ - 查询最近申请或指定申请状态
141
+
142
+ ### `download`
143
+
144
+ - 下载已开具发票的文件信息
145
+
146
+ ### `template_list`
147
+
148
+ - 查询模板列表,可按模板名称过滤
149
+
150
+ ### `template_save`
151
+
152
+ - 新增模板或按同名覆盖模板
153
+
154
+ ### `template_delete`
155
+
156
+ - 按 `templateId` 或 `templateName` 删除模板
157
+
158
+ ## Output format
159
+
160
+ ```text
161
+ STATUS=succeeded|failed|need_confirmation|not_implemented
162
+ ACTION=prepare|submit|update|status|download|template_list|template_save|template_delete
163
+ MESSAGE=...
164
+ ```
165
+
166
+ 常见附加字段:
167
+
168
+ - `ORG_ID`
169
+ - `APPLICATION_NO`
170
+ - `APPLICATION_STATUS`
171
+ - `TITLE`
172
+ - `AMOUNT`
173
+ - `ISSUING_MODE`
174
+ - `TEMPLATE_ID`
175
+ - `TEMPLATE_NAME`
176
+ - `INVOICE_ELIGIBLE_START_DATE`
177
+ - `AVAILABLE_ISSUING_MODES`
178
+ - `INELIGIBLE_RECHARGE_RECORD_COUNT`
179
+ - `ERROR_CODE`
180
+ - `ERROR_MESSAGE`
181
+
182
+ ## Notes
183
+
184
+ - 当前 skill 面向个人组织发票中心流程
185
+ - 所有操作依赖真实 Sophnet API
186
+ - 不要在输出中打印完整 token、cookie 或其他敏感凭证
@@ -0,0 +1,8 @@
1
+ [project]
2
+ name = "sophnet-invoice-assistant"
3
+ version = "1.0.0"
4
+ description = "Skill-local runtime dependencies for sophnet-invoice-assistant"
5
+ requires-python = ">=3.8"
6
+ dependencies = [
7
+ "requests>=2.31.0",
8
+ ]
@@ -0,0 +1,73 @@
1
+ # API Contract Notes
2
+
3
+ 本文档记录 `sophnet-invoice-assistant` 当前对接的 Sophnet 发票中心接口与关键约束。
4
+
5
+ ## Current endpoints
6
+
7
+ - `GET /orgs/joined-org`
8
+ - `GET /orgs/{orgId}/invoices/templates`
9
+ - `POST /orgs/{orgId}/invoices/templates`
10
+ - `DELETE /orgs/{orgId}/invoices/templates/{templateId}`
11
+ - `GET /orgs/{orgId}/invoices/recharge-records`
12
+ - `POST /orgs/{orgId}/invoices/applications`
13
+ - `PUT /orgs/{orgId}/invoices/applications/{applicationNo}`
14
+ - `GET /orgs/{orgId}/invoices/applications`
15
+ - `GET /orgs/{orgId}/invoices/applications/{applicationNo}/download`
16
+
17
+ ## Request payload notes
18
+
19
+ 创建和修改发票申请都使用以下核心字段:
20
+
21
+ - `buyerType`
22
+ - `title`
23
+ - `taxNo`
24
+ - `addressPhone`
25
+ - `bankAccount`
26
+ - `invoiceType`
27
+ - `rechargeTransNos`
28
+ - `issuingMode`
29
+
30
+ 模板保存额外使用:
31
+
32
+ - `saveAsTemplate`
33
+ - `templateName`
34
+
35
+ ## Important business rules
36
+
37
+ ### Multiple recharge records
38
+
39
+ 当 `rechargeTransNos` 数量大于 1 时,后端要求必须传 `issuingMode`:
40
+
41
+ - `AGGREGATED`
42
+ - `SEPARATE`
43
+
44
+ 后端校验位置:
45
+
46
+ - `InvoiceApplicationValidator.validateIssuingMode(...)`
47
+
48
+ ### Recharge eligibility start date
49
+
50
+ `2026-06-24` 之前的充值记录不支持自助开发票。
51
+
52
+ 后端规则常量位置:
53
+
54
+ - `InvoiceEligibilityRules.ELIGIBLE_RECHARGE_START_DATE`
55
+ - `InvoiceEligibilityRules.ELIGIBLE_RECHARGE_START_DATE_TEXT`
56
+
57
+ 前端同步常量位置:
58
+
59
+ - `sophnet/web/src/views/organization/invoices/utils.ts`
60
+ - `INVOICE_ELIGIBLE_START_DATE = "2026-06-24"`
61
+
62
+ ## Suggested client wrappers
63
+
64
+ - `get_current_personal_org()`
65
+ - `list_invoice_templates(org_id)`
66
+ - `save_invoice_template(org_id, payload)`
67
+ - `delete_invoice_template(org_id, template_id)`
68
+ - `list_invoiceable_recharge_records(org_id, params)`
69
+ - `create_invoice_application(org_id, payload)`
70
+ - `update_invoice_application(org_id, application_no, payload)`
71
+ - `list_invoice_applications(org_id, params)`
72
+ - `get_invoice_application(org_id, application_no)`
73
+ - `download_invoice_file(org_id, application_no)`
@@ -0,0 +1,16 @@
1
+ # Conversation Examples
2
+
3
+ ## 申请发票
4
+
5
+ - 用户:帮我开发票
6
+ - Skill:先确认一下,你是要复用上次抬头,还是这次要填新的开票信息?
7
+
8
+ ## 查询状态
9
+
10
+ - 用户:查一下我的发票进度
11
+ - Skill:我先帮你查最近的发票申请状态。
12
+
13
+ ## 下载发票
14
+
15
+ - 用户:把已经开的发票发我
16
+ - Skill:我先定位最近已开具的发票记录,如果有多条会让你确认是哪一张。