Compare commits

...

19 Commits

Author SHA1 Message Date
469b9c8bc7 chore: auto release commit (2026-03-31 15:24:21)
All checks were successful
技能自动化发布 / release (push) Successful in 15s
2026-03-31 15:24:22 +08:00
9fcf0aaf78 chore: auto release commit (2026-03-31 14:18:51)
All checks were successful
技能自动化发布 / release (push) Successful in 50s
2026-03-31 14:18:51 +08:00
01d9c82f80 chore: auto release commit (2026-03-31 11:50:55)
All checks were successful
技能自动化发布 / release (push) Successful in 14s
2026-03-31 11:50:56 +08:00
66d3801e43 chore: auto release commit (2026-03-30 19:25:21)
All checks were successful
技能自动化发布 / release (push) Successful in 17s
2026-03-30 19:25:21 +08:00
973547b6fb chore: auto release commit (2026-03-30 19:10:46)
Some checks failed
技能自动化发布 / release (push) Failing after 0s
2026-03-30 19:10:46 +08:00
edaac8aa45 chore: auto release commit (2026-03-24 18:22:46)
All checks were successful
技能自动化发布 (测试版-无鉴权) / build-and-deploy (push) Successful in 9s
2026-03-24 18:22:46 +08:00
157f51c765 chore: auto release commit (2026-03-24 18:07:44)
All checks were successful
技能自动化发布 (测试版-无鉴权) / build-and-deploy (push) Successful in 9s
2026-03-24 18:07:44 +08:00
d54a69cba8 chore: auto release commit (2026-03-24 18:06:10) 2026-03-24 18:06:10 +08:00
3b6f15ecbb chore: auto release commit (2026-03-24 18:05:25) 2026-03-24 18:05:25 +08:00
cd131697d5 修改版本
All checks were successful
技能自动化发布 (测试版-无鉴权) / build-and-deploy (push) Successful in 14s
2026-03-24 16:42:34 +08:00
2e3ac2cfe7 feat: separate skill display name from slug
All checks were successful
技能自动化发布 (测试版-无鉴权) / build-and-deploy (push) Successful in 11s
Set SKILL name to Chinese display text and add openclaw.slug for stable machine identifier, so database records can keep readable names while preserving stable install path keys.

Made-with: Cursor
2026-03-24 16:24:09 +08:00
bea22d5e9e ci: enforce tag-based version and strict sync checks
Use Git tag as release version, validate SKILL.md version consistency, and fail workflow when API business code is not success.
2026-03-24 16:14:06 +08:00
7759e0b376 fix: add required openclaw metadata for skill sync
All checks were successful
技能自动化发布 (测试版-无鉴权) / build-and-deploy (push) Successful in 12s
Include category and pricing-related openclaw metadata in SKILL frontmatter so /api/skill/update validation can pass during CI sync.

Made-with: Cursor
2026-03-24 15:55:39 +08:00
92a9d61007 ci: use mirrored checkout action
All checks were successful
技能自动化发布 (测试版-无鉴权) / build-and-deploy (push) Successful in 11s
Switch workflow checkout step to internal Gitea mirrored actions-checkout to reduce external GitHub dependency and speed up setup.

Made-with: Cursor
2026-03-24 15:09:02 +08:00
16b3f50dac ci: set artifact_type=skill for release upload
All checks were successful
技能自动化发布 (测试版-无鉴权) / build-and-deploy (push) Successful in 14s
2026-03-24 14:20:34 +08:00
48c6bec254 chore: 使用清华源加速 pip 安装
All checks were successful
技能自动化发布 (测试版-无鉴权) / build-and-deploy (push) Successful in 11s
2026-03-24 11:47:12 +08:00
9e7369a2af fix: 使用环境变量传递数据以修复 Python 解析错误
Some checks failed
技能自动化发布 (测试版-无鉴权) / build-and-deploy (push) Failing after 1m31s
2026-03-24 11:38:20 +08:00
bad0846cc3 fix: 修复 pip 安装限制并升级变量输出语法
Some checks failed
技能自动化发布 (测试版-无鉴权) / build-and-deploy (push) Failing after 15s
2026-03-24 11:35:33 +08:00
721442315f fix: 修复 YAML 文件编码为 UTF-8
Some checks failed
技能自动化发布 (测试版-无鉴权) / build-and-deploy (push) Failing after 1m31s
2026-03-24 11:30:41 +08:00
5 changed files with 243 additions and 211 deletions

View File

@@ -1,70 +1,11 @@
name: 技能自动化发布 (测试版-无鉴权)
name: 技能自动化发布
on:
push:
tags: ["v*"]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
# 1. 下载源码
- uses: actions/checkout@v3
# 2. 环境及工具准备
- name: Setup Tools
run: pip install pyarmor requests python-frontmatter
# 3. 【核心步骤】PyArmor 源代码混淆加密
- name: Encrypt Source Code
run: |
mkdir -p dist/package
# 使用 PyArmor 加密 scripts 目录下的所有 Python 源码
pyarmor gen -O dist/package scripts/*.py
# 拷贝说明文档到发包目录
cp SKILL.md dist/package/
# 4. 提取元数据并物理打包
- name: Parse Metadata and Pack
id: build_task
run: |
python -c "
import frontmatter, os, json, shutil
post = frontmatter.load('SKILL.md')
metadata = post.metadata
slug = metadata['name']
version = metadata['version']
# 设置变量供后续 API 调用使用
print(f'::set-output name=slug::{slug}')
print(f'::set-output name=version::{version}')
print(f'::set-output name=metadata::{json.dumps(metadata)}')
# 制作最终的发布 ZIP 包
shutil.make_archive(slug, 'zip', 'dist/package')
"
# 5. 同步数据库记录 (对应接口: /api/skill/update)
- name: Sync Database (V2.0)
run: |
python -c "
import requests, json
metadata = json.loads('${{ steps.build_task.outputs.metadata }}')
# 严谨修正:接口路径为 /api/skill/update
url = 'https://jc2009.com/api/skill/update'
res = requests.post(url, json=metadata)
print(f'DB Sync Result: {res.text}')
if res.status_code != 200: exit(1)
"
# 6. 上传物理包 (对应接口: /api/upload)
- name: Upload Encrypted ZIP
run: |
SLUG=${{ steps.build_task.outputs.slug }}
VERSION=${{ steps.build_task.outputs.version }}
python -c "
import requests
url = 'https://jc2009.com/api/upload'
payload = {'plugin_name': '$SLUG', 'version': '$VERSION'}
with open(f'$SLUG.zip', 'rb') as f:
res = requests.post(url, data=payload, files={'file': f})
print(f'Upload Result: {res.text}')
if res.status_code != 200: exit(1)
"
release:
uses: admin/jiangchang-platform-kit/.github/workflows/reusable-release-skill.yaml@main
with:
artifact_platform: windows
pyarmor_platform: windows.x86_64

View File

@@ -1,16 +1,18 @@
---
name: account-manager
name: 账号管理
description: 多平台多账号管理。管理各平台账号与Chrome Profile的对应关系供publisher类Skill调用获取账号信息。
version: 1.0.1
author: 深圳匠厂科技有限公司
metadata:
openclaw:
slug: account-manager
emoji: "👤"
category: "通用"
allowed-tools:
- bash
---
# 账号管理
# 账号管理
## 使用时机
@@ -23,32 +25,4 @@ allowed-tools:
### 列出某平台所有账号
```bash
python3 {baseDir}/scripts/account.py list <platform>
```
### 获取某个账号详情
```bash
python3 {baseDir}/scripts/account.py get <account_id>
```
### 初始化账号登录(首次使用,打开浏览器手动登录)
```bash
python3 {baseDir}/scripts/account.py login <account_id>
```
## 输出格式
- list逐行返回账号ID和名称
- get返回账号完整信息
- login打开浏览器后提示用户手动完成登录
## 错误处理
- 账号不存在:提示"未找到该账号请检查账号ID是否正确"
- 平台无账号:提示"该平台暂无账号请先在accounts.json中添加"
## 注意事项
- 账号档案存储在 `accounts.json`,新增账号需手动编辑该文件
- Chrome Profile目录首次登录后自动创建登录态永久保存
- 每个账号只需初始化登录一次
python3 {baseDir}/scripts/account.py list <platform>

View File

@@ -1,81 +0,0 @@
{
"sohu": [
{
"id": "sohu_account1",
"name": "搜狐账号1",
"phone": "13800000000",
"platform": "sohu",
"profile_dir": "D:/OpenClaw/chrome-profiles/sohu_account1"
},
{
"id": "sohu_account2",
"name": "搜狐账号2",
"phone": "13900000000",
"platform": "sohu",
"profile_dir": "D:/OpenClaw/chrome-profiles/sohu_account2"
}
],
"zhihu": [
{
"id": "zhihu_account1",
"name": "知乎账号1",
"phone": "",
"platform": "zhihu",
"profile_dir": "D:/OpenClaw/chrome-profiles/zhihu_account1"
}
],
"kimi": [
{
"id": "kimi_account1",
"name": "Kimi账号1",
"phone": "13800000000",
"platform": "kimi",
"profile_dir": "D:/OpenClaw/chrome-profiles/kimi_account1"
}
],
"deepseek": [
{
"id": "deepseek_account1",
"name": "DeepSeek账号1",
"phone": "",
"platform": "deepseek",
"profile_dir": "D:/OpenClaw/chrome-profiles/deepseek_account1"
}
],
"doubao": [
{
"id": "doubao_account1",
"name": "豆包账号1",
"phone": "",
"platform": "doubao",
"profile_dir": "D:/OpenClaw/chrome-profiles/doubao_account1"
}
],
"qianwen": [
{
"id": "qianwen_account1",
"name": "千问账号1",
"phone": "",
"platform": "qianwen",
"profile_dir": "D:/OpenClaw/chrome-profiles/qianwen_account1"
}
],
"yiyan": [
{
"id": "yiyan_account1",
"name": "一言账号1",
"phone": "",
"platform": "yiyan",
"profile_dir": "D:/OpenClaw/chrome-profiles/yiyan_account1"
}
],
"yuanbao": [
{
"id": "yuanbao_account1",
"name": "元宝账号1",
"phone": "",
"platform": "yuanbao",
"profile_dir": "D:/OpenClaw/chrome-profiles/yuanbao_account1"
}
]
}

23
release.ps1 Normal file
View File

@@ -0,0 +1,23 @@
[CmdletBinding()]
param(
[string]$Prefix = "v",
[string]$Message = "正式发布",
[switch]$AutoCommit,
[switch]$RequireClean,
[string]$CommitMessage,
[switch]$DryRun
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$sharedScript = Join-Path $scriptDir "..\jiangchang-platform-kit\tools\release.ps1"
$sharedScript = [System.IO.Path]::GetFullPath($sharedScript)
if (-not (Test-Path $sharedScript)) {
throw "Shared release script not found: $sharedScript"
}
& $sharedScript @PSBoundParameters
exit $LASTEXITCODE

View File

@@ -1,7 +1,8 @@
import sys
import json
import os
import subprocess
import sqlite3
from datetime import datetime
# Windows GBK 编码兼容修复
if sys.platform == "win32":
@@ -9,7 +10,7 @@ if sys.platform == "win32":
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
ACCOUNTS_FILE = os.path.join(BASE_DIR, "accounts.json")
SKILL_SLUG = "account-manager"
PLATFORM_URLS = {
# 自媒体/图文平台
@@ -26,50 +27,192 @@ PLATFORM_URLS = {
"yuanbao": "https://yuanbao.tencent.com"
}
def load_accounts():
with open(ACCOUNTS_FILE, "r", encoding="utf-8") as f:
return json.load(f)
def get_data_root():
env = (os.getenv("JIANGCHANG_DATA_ROOT") or "").strip()
if env:
return env
if sys.platform == "win32":
return r"D:\jiangchang-data"
return os.path.join(os.path.expanduser("~"), ".jiangchang-data")
def get_user_id():
uid = (os.getenv("JIANGCHANG_USER_ID") or "").strip()
return uid or "_anon"
def get_skill_data_dir():
path = os.path.join(get_data_root(), get_user_id(), SKILL_SLUG)
os.makedirs(path, exist_ok=True)
return path
def get_db_path():
return os.path.join(get_skill_data_dir(), "account-manager.db")
def get_default_profile_dir(account_id):
path = os.path.join(get_skill_data_dir(), "profiles", account_id)
os.makedirs(path, exist_ok=True)
return path
def get_conn():
return sqlite3.connect(get_db_path())
def init_db():
conn = get_conn()
try:
cur = conn.cursor()
cur.execute(
"""
CREATE TABLE IF NOT EXISTS accounts (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
platform TEXT NOT NULL,
phone TEXT,
profile_dir TEXT,
login_status INTEGER DEFAULT 0,
last_login_at TEXT,
extra_json TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
)
"""
)
_ensure_column(cur, "accounts", "login_status", "INTEGER DEFAULT 0")
_ensure_column(cur, "accounts", "last_login_at", "TEXT")
conn.commit()
finally:
conn.close()
def _ensure_column(cur, table_name, col_name, ddl):
cur.execute(f"PRAGMA table_info({table_name})")
cols = [row[1] for row in cur.fetchall()]
if col_name not in cols:
cur.execute(f"ALTER TABLE {table_name} ADD COLUMN {col_name} {ddl}")
def get_account_by_id(account_id):
init_db()
conn = get_conn()
try:
cur = conn.cursor()
cur.execute(
"SELECT id, name, platform, phone, profile_dir, login_status, last_login_at, extra_json FROM accounts WHERE id = ?",
(account_id,),
)
row = cur.fetchone()
if not row:
return None
acc = {
"id": row[0],
"name": row[1],
"platform": row[2],
"phone": row[3] or "",
"profile_dir": row[4] or get_default_profile_dir(row[0]),
"login_status": int(row[5] or 0),
"last_login_at": row[6],
}
if row[7]:
try:
extra = json.loads(row[7])
if isinstance(extra, dict):
acc.update(extra)
except Exception:
pass
return acc
finally:
conn.close()
def cmd_list(platform="all"):
"""
修改后的 list 功能能直观打出带着【手机号】的详细台账供AI查询匹配
支持查全部(all),或指定(sohu)
"""
accounts = load_accounts()
if platform == "all":
platforms_to_check = accounts.keys()
else:
platforms_to_check = [platform]
init_db()
conn = get_conn()
found = False
for plat in platforms_to_check:
platform_accounts = accounts.get(plat, [])
for acc in platform_accounts:
phone = acc.get('phone', '未绑定手机')
print(f"账号ID:{acc['id']} | 名称:{acc['name']} | 手机号:{phone} | 平台:{acc['platform']}")
found = True
try:
cur = conn.cursor()
if platform == "all":
cur.execute(
"SELECT id, name, phone, platform, login_status FROM accounts ORDER BY platform, id"
)
else:
cur.execute(
"SELECT id, name, phone, platform, login_status FROM accounts WHERE platform = ? ORDER BY id",
(platform,),
)
rows = cur.fetchall()
finally:
conn.close()
found = False
for row in rows:
phone = row[2] or "未绑定手机"
login_text = "已登录" if int(row[4] or 0) == 1 else "未登录"
print(f"账号ID:{row[0]} | 名称:{row[1]} | 手机号:{phone} | 平台:{row[3]} | 状态:{login_text}")
found = True
if not found:
print(f"ERROR:NO_ACCOUNTS_FOUND")
def cmd_get(account_id):
accounts = load_accounts()
for platform_accounts in accounts.values():
for acc in platform_accounts:
if acc["id"] == account_id:
print(json.dumps(acc, ensure_ascii=False))
return
acc = get_account_by_id(account_id)
if acc:
print(json.dumps(acc, ensure_ascii=False))
return
print("ERROR:ACCOUNT_NOT_FOUND")
def cmd_add(account_id, name, platform, phone="", profile_dir=""):
init_db()
platform = (platform or "").strip().lower()
if platform not in PLATFORM_URLS:
print(f"ERROR:INVALID_PLATFORM (支持: {', '.join(sorted(PLATFORM_URLS.keys()))})")
return
account_id = (account_id or "").strip()
name = (name or "").strip() or account_id
if not account_id:
print("ERROR:INVALID_ACCOUNT_ID")
return
profile_dir = (profile_dir or "").strip() or get_default_profile_dir(account_id)
os.makedirs(profile_dir, exist_ok=True)
now = datetime.now().isoformat(timespec="seconds")
conn = get_conn()
try:
cur = conn.cursor()
cur.execute(
"""
INSERT INTO accounts (id, name, platform, phone, profile_dir, login_status, last_login_at, extra_json, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, 0, NULL, NULL, ?, ?)
ON CONFLICT(id) DO UPDATE SET
name = excluded.name,
platform = excluded.platform,
phone = excluded.phone,
profile_dir = excluded.profile_dir,
updated_at = excluded.updated_at
""",
(account_id, name, platform, phone, profile_dir, now, now),
)
conn.commit()
finally:
conn.close()
print(f"✅ 已保存账号:{account_id}{platform}")
print(" 请继续执行登录python account.py login <账号ID>")
def cmd_login(account_id):
"""首次登录打开浏览器用户手动登录登录态自动保存到Profile目录"""
accounts = load_accounts()
target = None
for platform_accounts in accounts.values():
for acc in platform_accounts:
if acc["id"] == account_id:
target = acc
break
target = get_account_by_id(account_id)
if not target:
print("ERROR:ACCOUNT_NOT_FOUND")
@@ -110,16 +253,48 @@ asyncio.run(main())
f.write(login_script)
tmp_path = f.name
os.system(f'python "{tmp_path}"')
exit_code = os.system(f'python "{tmp_path}"')
os.unlink(tmp_path)
if exit_code == 0:
_mark_login_success(account_id)
print("✅ 登录状态已标记为已登录")
else:
print("⚠️ 浏览器登录流程异常退出,登录状态未更新")
def _mark_login_success(account_id):
now = datetime.now().isoformat(timespec="seconds")
conn = get_conn()
try:
cur = conn.cursor()
cur.execute(
"UPDATE accounts SET login_status = 1, last_login_at = ?, updated_at = ? WHERE id = ?",
(now, now, account_id),
)
conn.commit()
finally:
conn.close()
if __name__ == "__main__":
if len(sys.argv) < 2:
print("用法python account.py list [platform] | get <id> | login <id>")
print("用法python account.py add <id> <name> <platform> [phone] [profile_dir] | list [platform] | get <id> | login <id>")
sys.exit(1)
cmd = sys.argv[1]
if cmd == "list":
# 兼容写反顺序python account.py sohu list
if len(sys.argv) >= 3 and sys.argv[2] == "list" and cmd in PLATFORM_URLS:
cmd_list(cmd)
elif cmd in PLATFORM_URLS and len(sys.argv) == 2:
cmd_list(cmd)
elif cmd == "add" and len(sys.argv) >= 5:
cmd_add(
sys.argv[2],
sys.argv[3],
sys.argv[4],
sys.argv[5] if len(sys.argv) >= 6 else "",
sys.argv[6] if len(sys.argv) >= 7 else "",
)
elif cmd == "list":
# 默认不传 platform 时打出 all
cmd_list(sys.argv[2] if len(sys.argv) >= 3 else "all")
elif cmd == "get" and len(sys.argv) >= 3: