galaxy-opc-plugin 0.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 +166 -0
- package/index.ts +145 -0
- package/openclaw.plugin.json +17 -0
- package/package.json +47 -0
- package/skills/basic-crm/SKILL.md +81 -0
- package/skills/basic-finance/SKILL.md +100 -0
- package/skills/business-monitoring/SKILL.md +120 -0
- package/skills/company-lifecycle/SKILL.md +99 -0
- package/skills/company-registration/SKILL.md +80 -0
- package/skills/finance-tax/SKILL.md +150 -0
- package/skills/hr-assistant/SKILL.md +127 -0
- package/skills/investment-management/SKILL.md +101 -0
- package/skills/legal-assistant/SKILL.md +113 -0
- package/skills/media-ops/SKILL.md +101 -0
- package/skills/procurement-management/SKILL.md +91 -0
- package/skills/project-management/SKILL.md +125 -0
- package/src/api/companies.ts +193 -0
- package/src/api/dashboard.ts +25 -0
- package/src/api/routes.ts +14 -0
- package/src/db/index.ts +63 -0
- package/src/db/migrations.ts +67 -0
- package/src/db/schema.ts +518 -0
- package/src/db/sqlite-adapter.ts +366 -0
- package/src/opc/company-manager.ts +82 -0
- package/src/opc/context-injector.ts +186 -0
- package/src/opc/reminder-service.ts +289 -0
- package/src/opc/types.ts +330 -0
- package/src/opc/workspace-factory.ts +189 -0
- package/src/tools/acquisition-tool.ts +150 -0
- package/src/tools/asset-package-tool.ts +283 -0
- package/src/tools/finance-tool.ts +244 -0
- package/src/tools/hr-tool.ts +211 -0
- package/src/tools/investment-tool.ts +201 -0
- package/src/tools/legal-tool.ts +191 -0
- package/src/tools/lifecycle-tool.ts +251 -0
- package/src/tools/media-tool.ts +174 -0
- package/src/tools/monitoring-tool.ts +207 -0
- package/src/tools/opb-tool.ts +193 -0
- package/src/tools/opc-tool.ts +206 -0
- package/src/tools/procurement-tool.ts +191 -0
- package/src/tools/project-tool.ts +203 -0
- package/src/tools/schemas.ts +163 -0
- package/src/tools/staff-tool.ts +211 -0
- package/src/utils/tool-helper.ts +16 -0
- package/src/web/config-ui.ts +3501 -0
- package/src/web/landing-page.ts +269 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 星环OPC中心 — opc_legal 法务助手工具
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Type, type Static } from "@sinclair/typebox";
|
|
6
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
7
|
+
import type { OpcDatabase } from "../db/index.js";
|
|
8
|
+
import { json } from "../utils/tool-helper.js";
|
|
9
|
+
|
|
10
|
+
const LegalSchema = Type.Union([
|
|
11
|
+
Type.Object({
|
|
12
|
+
action: Type.Literal("create_contract"),
|
|
13
|
+
company_id: Type.String({ description: "公司 ID" }),
|
|
14
|
+
title: Type.String({ description: "合同标题" }),
|
|
15
|
+
counterparty: Type.String({ description: "签约对方" }),
|
|
16
|
+
contract_type: Type.String({ description: "合同类型: 服务合同/采购合同/劳动合同/租赁合同/合作协议/NDA/其他" }),
|
|
17
|
+
amount: Type.Optional(Type.Number({ description: "合同金额(元)" })),
|
|
18
|
+
start_date: Type.Optional(Type.String({ description: "起始日期 (YYYY-MM-DD)" })),
|
|
19
|
+
end_date: Type.Optional(Type.String({ description: "结束日期 (YYYY-MM-DD)" })),
|
|
20
|
+
key_terms: Type.Optional(Type.String({ description: "核心条款摘要" })),
|
|
21
|
+
risk_notes: Type.Optional(Type.String({ description: "风险提示" })),
|
|
22
|
+
reminder_date: Type.Optional(Type.String({ description: "到期提醒日期" })),
|
|
23
|
+
}),
|
|
24
|
+
Type.Object({
|
|
25
|
+
action: Type.Literal("list_contracts"),
|
|
26
|
+
company_id: Type.String({ description: "公司 ID" }),
|
|
27
|
+
status: Type.Optional(Type.String({ description: "按状态筛选: draft/active/expired/terminated/disputed" })),
|
|
28
|
+
}),
|
|
29
|
+
Type.Object({
|
|
30
|
+
action: Type.Literal("get_contract"),
|
|
31
|
+
contract_id: Type.String({ description: "合同 ID" }),
|
|
32
|
+
}),
|
|
33
|
+
Type.Object({
|
|
34
|
+
action: Type.Literal("update_contract"),
|
|
35
|
+
contract_id: Type.String({ description: "合同 ID" }),
|
|
36
|
+
status: Type.Optional(Type.String({ description: "新状态" })),
|
|
37
|
+
key_terms: Type.Optional(Type.String({ description: "更新核心条款" })),
|
|
38
|
+
risk_notes: Type.Optional(Type.String({ description: "更新风险提示" })),
|
|
39
|
+
reminder_date: Type.Optional(Type.String({ description: "更新提醒日期" })),
|
|
40
|
+
}),
|
|
41
|
+
Type.Object({
|
|
42
|
+
action: Type.Literal("contract_risk_check"),
|
|
43
|
+
contract_type: Type.String({ description: "合同类型" }),
|
|
44
|
+
key_terms: Type.Optional(Type.String({ description: "条款内容(用于分析)" })),
|
|
45
|
+
}),
|
|
46
|
+
Type.Object({
|
|
47
|
+
action: Type.Literal("compliance_checklist"),
|
|
48
|
+
company_id: Type.String({ description: "公司 ID" }),
|
|
49
|
+
}),
|
|
50
|
+
Type.Object({
|
|
51
|
+
action: Type.Literal("contract_template"),
|
|
52
|
+
contract_type: Type.String({ description: "合同类型: 服务合同/NDA/劳动合同/租赁合同" }),
|
|
53
|
+
}),
|
|
54
|
+
Type.Object({
|
|
55
|
+
action: Type.Literal("delete_contract"),
|
|
56
|
+
contract_id: Type.String({ description: "合同 ID" }),
|
|
57
|
+
}),
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
type LegalParams = Static<typeof LegalSchema>;
|
|
61
|
+
|
|
62
|
+
const CONTRACT_TEMPLATES: Record<string, { title: string; sections: string[] }> = {
|
|
63
|
+
"服务合同": {
|
|
64
|
+
title: "技术服务合同模板",
|
|
65
|
+
sections: ["合同编号/日期", "甲乙双方信息", "服务内容与范围", "服务期限", "服务费用与支付方式", "验收标准", "保密条款", "知识产权归属", "违约责任", "争议解决", "附件"],
|
|
66
|
+
},
|
|
67
|
+
NDA: {
|
|
68
|
+
title: "保密协议(NDA)模板",
|
|
69
|
+
sections: ["定义与范围", "保密义务", "保密期限", "例外情形", "违约责任", "法律适用与争议解决"],
|
|
70
|
+
},
|
|
71
|
+
"劳动合同": {
|
|
72
|
+
title: "劳动合同模板",
|
|
73
|
+
sections: ["用人单位信息", "劳动者信息", "合同期限", "工作内容与地点", "工作时间与休假", "劳动报酬", "社会保险", "劳动保护", "合同解除/终止", "争议解决"],
|
|
74
|
+
},
|
|
75
|
+
"租赁合同": {
|
|
76
|
+
title: "房屋租赁合同模板",
|
|
77
|
+
sections: ["出租方/承租方信息", "房屋坐落与面积", "租赁用途", "租赁期限", "租金与支付", "押金", "维修责任", "转租限制", "合同解除", "争议解决"],
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const RISK_CHECKLIST: Record<string, string[]> = {
|
|
82
|
+
"服务合同": ["是否明确验收标准", "付款节点是否合理", "知识产权归属是否清晰", "违约金是否过高或过低", "是否有保密条款", "争议解决方式是否约定"],
|
|
83
|
+
"采购合同": ["质量标准是否明确", "交货时间是否合理", "退换货条款", "付款条件", "运输风险承担"],
|
|
84
|
+
"劳动合同": ["试用期是否合规", "竞业限制是否有补偿", "加班约定是否合法", "社保公积金是否覆盖"],
|
|
85
|
+
"租赁合同": ["租金递增条款", "提前退租条件", "装修归属", "押金退还条件"],
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export function registerLegalTool(api: OpenClawPluginApi, db: OpcDatabase): void {
|
|
89
|
+
api.registerTool(
|
|
90
|
+
{
|
|
91
|
+
name: "opc_legal",
|
|
92
|
+
label: "OPC 法务助手",
|
|
93
|
+
description:
|
|
94
|
+
"法务助手工具。操作: create_contract(创建合同), list_contracts(合同列表), " +
|
|
95
|
+
"get_contract(合同详情), update_contract(更新合同), contract_risk_check(合同风险检查), " +
|
|
96
|
+
"compliance_checklist(合规清单), contract_template(合同模板), delete_contract(删除合同)",
|
|
97
|
+
parameters: LegalSchema,
|
|
98
|
+
async execute(_toolCallId, params) {
|
|
99
|
+
const p = params as LegalParams;
|
|
100
|
+
try {
|
|
101
|
+
switch (p.action) {
|
|
102
|
+
case "create_contract": {
|
|
103
|
+
const id = db.genId();
|
|
104
|
+
const now = new Date().toISOString();
|
|
105
|
+
db.execute(
|
|
106
|
+
`INSERT INTO opc_contracts (id, company_id, title, counterparty, contract_type, amount, start_date, end_date, status, key_terms, risk_notes, reminder_date, created_at, updated_at)
|
|
107
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'draft', ?, ?, ?, ?, ?)`,
|
|
108
|
+
id, p.company_id, p.title, p.counterparty, p.contract_type,
|
|
109
|
+
p.amount ?? 0, p.start_date ?? "", p.end_date ?? "",
|
|
110
|
+
p.key_terms ?? "", p.risk_notes ?? "", p.reminder_date ?? "", now, now,
|
|
111
|
+
);
|
|
112
|
+
return json(db.queryOne("SELECT * FROM opc_contracts WHERE id = ?", id));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
case "list_contracts": {
|
|
116
|
+
let sql = "SELECT * FROM opc_contracts WHERE company_id = ?";
|
|
117
|
+
const params2: unknown[] = [p.company_id];
|
|
118
|
+
if (p.status) { sql += " AND status = ?"; params2.push(p.status); }
|
|
119
|
+
sql += " ORDER BY created_at DESC";
|
|
120
|
+
return json(db.query(sql, ...params2));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
case "get_contract":
|
|
124
|
+
return json(db.queryOne("SELECT * FROM opc_contracts WHERE id = ?", p.contract_id) ?? { error: "合同不存在" });
|
|
125
|
+
|
|
126
|
+
case "update_contract": {
|
|
127
|
+
const fields: string[] = [];
|
|
128
|
+
const values: unknown[] = [];
|
|
129
|
+
if (p.status) { fields.push("status = ?"); values.push(p.status); }
|
|
130
|
+
if (p.key_terms) { fields.push("key_terms = ?"); values.push(p.key_terms); }
|
|
131
|
+
if (p.risk_notes) { fields.push("risk_notes = ?"); values.push(p.risk_notes); }
|
|
132
|
+
if (p.reminder_date) { fields.push("reminder_date = ?"); values.push(p.reminder_date); }
|
|
133
|
+
fields.push("updated_at = ?"); values.push(new Date().toISOString());
|
|
134
|
+
values.push(p.contract_id);
|
|
135
|
+
db.execute(`UPDATE opc_contracts SET ${fields.join(", ")} WHERE id = ?`, ...values);
|
|
136
|
+
return json(db.queryOne("SELECT * FROM opc_contracts WHERE id = ?", p.contract_id) ?? { error: "合同不存在" });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
case "contract_risk_check": {
|
|
140
|
+
const risks = RISK_CHECKLIST[p.contract_type] ?? RISK_CHECKLIST["服务合同"] ?? [];
|
|
141
|
+
return json({
|
|
142
|
+
contract_type: p.contract_type,
|
|
143
|
+
risk_checklist: risks,
|
|
144
|
+
general_risks: [
|
|
145
|
+
"确认对方主体资格(营业执照、法人身份)",
|
|
146
|
+
"确认签约代表人的授权",
|
|
147
|
+
"合同金额大写小写一致",
|
|
148
|
+
"约定管辖法院或仲裁机构",
|
|
149
|
+
"保留合同原件",
|
|
150
|
+
],
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
case "compliance_checklist": {
|
|
155
|
+
const contracts = db.query(
|
|
156
|
+
"SELECT * FROM opc_contracts WHERE company_id = ? AND status = 'active' AND reminder_date != '' AND reminder_date <= date('now', '+30 days')",
|
|
157
|
+
p.company_id,
|
|
158
|
+
);
|
|
159
|
+
return json({
|
|
160
|
+
annual: ["工商年报(6月30日前)", "税务年报(5月31日前)", "社保年审", "劳动用工备案"],
|
|
161
|
+
monthly: ["纳税申报", "社保公积金缴纳", "银行对账"],
|
|
162
|
+
expiring_contracts: contracts,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
case "contract_template": {
|
|
167
|
+
const tpl = CONTRACT_TEMPLATES[p.contract_type];
|
|
168
|
+
if (!tpl) {
|
|
169
|
+
return json({ error: `无此模板,可用: ${Object.keys(CONTRACT_TEMPLATES).join(", ")}` });
|
|
170
|
+
}
|
|
171
|
+
return json(tpl);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
case "delete_contract": {
|
|
175
|
+
db.execute("DELETE FROM opc_contracts WHERE id = ?", p.contract_id);
|
|
176
|
+
return json({ ok: true });
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
default:
|
|
180
|
+
return json({ error: `未知操作: ${(p as { action: string }).action}` });
|
|
181
|
+
}
|
|
182
|
+
} catch (err) {
|
|
183
|
+
return json({ error: err instanceof Error ? err.message : String(err) });
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
{ name: "opc_legal" },
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
api.logger.info("opc: 已注册 opc_legal 工具");
|
|
191
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 星环OPC中心 — opc_lifecycle 公司生命周期工具
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Type, type Static } from "@sinclair/typebox";
|
|
6
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
7
|
+
import type { OpcDatabase } from "../db/index.js";
|
|
8
|
+
import { json } from "../utils/tool-helper.js";
|
|
9
|
+
|
|
10
|
+
const LifecycleSchema = Type.Union([
|
|
11
|
+
Type.Object({
|
|
12
|
+
action: Type.Literal("add_milestone"),
|
|
13
|
+
company_id: Type.String({ description: "公司 ID" }),
|
|
14
|
+
title: Type.String({ description: "里程碑标题" }),
|
|
15
|
+
category: Type.Optional(Type.String({ description: "类别: business/product/finance/legal/team/other" })),
|
|
16
|
+
target_date: Type.Optional(Type.String({ description: "目标日期 (YYYY-MM-DD)" })),
|
|
17
|
+
description: Type.Optional(Type.String({ description: "描述" })),
|
|
18
|
+
}),
|
|
19
|
+
Type.Object({
|
|
20
|
+
action: Type.Literal("list_milestones"),
|
|
21
|
+
company_id: Type.String({ description: "公司 ID" }),
|
|
22
|
+
status: Type.Optional(Type.String({ description: "按状态筛选: pending/in_progress/completed/cancelled" })),
|
|
23
|
+
category: Type.Optional(Type.String({ description: "按类别筛选" })),
|
|
24
|
+
}),
|
|
25
|
+
Type.Object({
|
|
26
|
+
action: Type.Literal("create_event"),
|
|
27
|
+
company_id: Type.String({ description: "公司 ID" }),
|
|
28
|
+
title: Type.String({ description: "事件标题" }),
|
|
29
|
+
event_type: Type.Optional(Type.String({ description: "事件类型: registration/funding/product_launch/partnership/pivot/expansion/other" })),
|
|
30
|
+
event_date: Type.Optional(Type.String({ description: "事件日期 (YYYY-MM-DD)" })),
|
|
31
|
+
impact: Type.Optional(Type.String({ description: "影响说明" })),
|
|
32
|
+
description: Type.Optional(Type.String({ description: "详细描述" })),
|
|
33
|
+
}),
|
|
34
|
+
Type.Object({
|
|
35
|
+
action: Type.Literal("list_events"),
|
|
36
|
+
company_id: Type.String({ description: "公司 ID" }),
|
|
37
|
+
event_type: Type.Optional(Type.String({ description: "按事件类型筛选" })),
|
|
38
|
+
}),
|
|
39
|
+
Type.Object({
|
|
40
|
+
action: Type.Literal("timeline"),
|
|
41
|
+
company_id: Type.String({ description: "公司 ID" }),
|
|
42
|
+
}),
|
|
43
|
+
Type.Object({
|
|
44
|
+
action: Type.Literal("update_milestone"),
|
|
45
|
+
milestone_id: Type.String({ description: "里程碑 ID" }),
|
|
46
|
+
status: Type.Optional(Type.String({ description: "新状态: pending/in_progress/completed/cancelled" })),
|
|
47
|
+
completed_date: Type.Optional(Type.String({ description: "实际完成日期 (YYYY-MM-DD)" })),
|
|
48
|
+
description: Type.Optional(Type.String({ description: "更新描述" })),
|
|
49
|
+
}),
|
|
50
|
+
Type.Object({
|
|
51
|
+
action: Type.Literal("generate_report"),
|
|
52
|
+
company_id: Type.String({ description: "公司 ID" }),
|
|
53
|
+
}),
|
|
54
|
+
Type.Object({
|
|
55
|
+
action: Type.Literal("delete_milestone"),
|
|
56
|
+
milestone_id: Type.String({ description: "里程碑 ID" }),
|
|
57
|
+
}),
|
|
58
|
+
Type.Object({
|
|
59
|
+
action: Type.Literal("delete_event"),
|
|
60
|
+
event_id: Type.String({ description: "事件 ID" }),
|
|
61
|
+
}),
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
type LifecycleParams = Static<typeof LifecycleSchema>;
|
|
65
|
+
|
|
66
|
+
export function registerLifecycleTool(api: OpenClawPluginApi, db: OpcDatabase): void {
|
|
67
|
+
api.registerTool(
|
|
68
|
+
{
|
|
69
|
+
name: "opc_lifecycle",
|
|
70
|
+
label: "OPC 公司生命周期",
|
|
71
|
+
description:
|
|
72
|
+
"公司生命周期管理工具。操作: add_milestone(添加里程碑), update_milestone(更新里程碑状态/完成), list_milestones(里程碑列表), " +
|
|
73
|
+
"create_event(记录公司事件), list_events(事件列表), " +
|
|
74
|
+
"timeline(统一时间线), generate_report(公司综合报告), delete_milestone(删除里程碑), delete_event(删除事件)",
|
|
75
|
+
parameters: LifecycleSchema,
|
|
76
|
+
async execute(_toolCallId, params) {
|
|
77
|
+
const p = params as LifecycleParams;
|
|
78
|
+
try {
|
|
79
|
+
switch (p.action) {
|
|
80
|
+
case "add_milestone": {
|
|
81
|
+
const id = db.genId();
|
|
82
|
+
const now = new Date().toISOString();
|
|
83
|
+
db.execute(
|
|
84
|
+
`INSERT INTO opc_milestones (id, company_id, title, category, target_date, completed_date, status, description, created_at)
|
|
85
|
+
VALUES (?, ?, ?, ?, ?, '', 'pending', ?, ?)`,
|
|
86
|
+
id, p.company_id, p.title,
|
|
87
|
+
p.category ?? "business", p.target_date ?? "", p.description ?? "", now,
|
|
88
|
+
);
|
|
89
|
+
return json(db.queryOne("SELECT * FROM opc_milestones WHERE id = ?", id));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
case "list_milestones": {
|
|
93
|
+
let sql = "SELECT * FROM opc_milestones WHERE company_id = ?";
|
|
94
|
+
const params2: unknown[] = [p.company_id];
|
|
95
|
+
if (p.status) { sql += " AND status = ?"; params2.push(p.status); }
|
|
96
|
+
if (p.category) { sql += " AND category = ?"; params2.push(p.category); }
|
|
97
|
+
sql += " ORDER BY target_date ASC";
|
|
98
|
+
return json(db.query(sql, ...params2));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
case "create_event": {
|
|
102
|
+
const id = db.genId();
|
|
103
|
+
const now = new Date().toISOString();
|
|
104
|
+
db.execute(
|
|
105
|
+
`INSERT INTO opc_lifecycle_events (id, company_id, event_type, title, event_date, impact, description, created_at)
|
|
106
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
107
|
+
id, p.company_id, p.event_type ?? "other", p.title,
|
|
108
|
+
p.event_date ?? now.slice(0, 10), p.impact ?? "", p.description ?? "", now,
|
|
109
|
+
);
|
|
110
|
+
return json(db.queryOne("SELECT * FROM opc_lifecycle_events WHERE id = ?", id));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
case "list_events": {
|
|
114
|
+
let sql = "SELECT * FROM opc_lifecycle_events WHERE company_id = ?";
|
|
115
|
+
const params2: unknown[] = [p.company_id];
|
|
116
|
+
if (p.event_type) { sql += " AND event_type = ?"; params2.push(p.event_type); }
|
|
117
|
+
sql += " ORDER BY event_date DESC";
|
|
118
|
+
return json(db.query(sql, ...params2));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
case "timeline": {
|
|
122
|
+
const milestones = db.query(
|
|
123
|
+
`SELECT 'milestone' as item_type, title, category as sub_type,
|
|
124
|
+
COALESCE(NULLIF(completed_date, ''), target_date) as date, status, description
|
|
125
|
+
FROM opc_milestones WHERE company_id = ?`,
|
|
126
|
+
p.company_id,
|
|
127
|
+
);
|
|
128
|
+
const events = db.query(
|
|
129
|
+
`SELECT 'event' as item_type, title, event_type as sub_type,
|
|
130
|
+
event_date as date, 'recorded' as status, description
|
|
131
|
+
FROM opc_lifecycle_events WHERE company_id = ?`,
|
|
132
|
+
p.company_id,
|
|
133
|
+
);
|
|
134
|
+
const combined = [...(milestones as Record<string, unknown>[]), ...(events as Record<string, unknown>[])];
|
|
135
|
+
combined.sort((a, b) => {
|
|
136
|
+
const da = (a.date as string) || "9999";
|
|
137
|
+
const db2 = (b.date as string) || "9999";
|
|
138
|
+
return da.localeCompare(db2);
|
|
139
|
+
});
|
|
140
|
+
return json({ timeline: combined, total: combined.length });
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
case "update_milestone": {
|
|
144
|
+
const sets: string[] = [];
|
|
145
|
+
const vals: unknown[] = [];
|
|
146
|
+
if (p.status !== undefined) { sets.push("status = ?"); vals.push(p.status); }
|
|
147
|
+
if (p.completed_date !== undefined) { sets.push("completed_date = ?"); vals.push(p.completed_date); }
|
|
148
|
+
if (p.description !== undefined) { sets.push("description = ?"); vals.push(p.description); }
|
|
149
|
+
// 标记 completed 时自动填充今日日期
|
|
150
|
+
if (p.status === "completed" && p.completed_date === undefined) {
|
|
151
|
+
sets.push("completed_date = ?");
|
|
152
|
+
vals.push(new Date().toISOString().slice(0, 10));
|
|
153
|
+
}
|
|
154
|
+
if (sets.length === 0) return json({ error: "未提供任何更新字段" });
|
|
155
|
+
vals.push(p.milestone_id);
|
|
156
|
+
db.execute(`UPDATE opc_milestones SET ${sets.join(", ")} WHERE id = ?`, ...vals);
|
|
157
|
+
return json(db.queryOne("SELECT * FROM opc_milestones WHERE id = ?", p.milestone_id));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
case "generate_report": {
|
|
161
|
+
const company = db.queryOne("SELECT * FROM opc_companies WHERE id = ?", p.company_id);
|
|
162
|
+
if (!company) return json({ error: "公司不存在" });
|
|
163
|
+
|
|
164
|
+
const employees = db.query(
|
|
165
|
+
"SELECT COUNT(*) as count FROM opc_hr_records WHERE company_id = ? AND status = 'active'", p.company_id,
|
|
166
|
+
) as { count: number }[];
|
|
167
|
+
|
|
168
|
+
const finance = db.queryOne(
|
|
169
|
+
`SELECT
|
|
170
|
+
COALESCE(SUM(CASE WHEN type='income' THEN amount ELSE 0 END), 0) as total_income,
|
|
171
|
+
COALESCE(SUM(CASE WHEN type='expense' THEN amount ELSE 0 END), 0) as total_expense,
|
|
172
|
+
COUNT(*) as tx_count
|
|
173
|
+
FROM opc_transactions WHERE company_id = ?`,
|
|
174
|
+
p.company_id,
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
const contacts = db.queryOne(
|
|
178
|
+
"SELECT COUNT(*) as count FROM opc_contacts WHERE company_id = ?", p.company_id,
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
const contracts = db.queryOne(
|
|
182
|
+
`SELECT COUNT(*) as total,
|
|
183
|
+
SUM(CASE WHEN status='active' THEN 1 ELSE 0 END) as active
|
|
184
|
+
FROM opc_contracts WHERE company_id = ?`,
|
|
185
|
+
p.company_id,
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
const projects = db.queryOne(
|
|
189
|
+
`SELECT COUNT(*) as total,
|
|
190
|
+
SUM(CASE WHEN status='active' THEN 1 ELSE 0 END) as active
|
|
191
|
+
FROM opc_projects WHERE company_id = ?`,
|
|
192
|
+
p.company_id,
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
const milestones = db.queryOne(
|
|
196
|
+
`SELECT COUNT(*) as total,
|
|
197
|
+
SUM(CASE WHEN status='completed' THEN 1 ELSE 0 END) as completed
|
|
198
|
+
FROM opc_milestones WHERE company_id = ?`,
|
|
199
|
+
p.company_id,
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
const rounds = db.queryOne(
|
|
203
|
+
`SELECT COUNT(*) as total, COALESCE(SUM(amount), 0) as total_raised
|
|
204
|
+
FROM opc_investment_rounds WHERE company_id = ? AND status = 'closed'`,
|
|
205
|
+
p.company_id,
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
const alerts = db.queryOne(
|
|
209
|
+
`SELECT COUNT(*) as active_alerts
|
|
210
|
+
FROM opc_alerts WHERE company_id = ? AND status = 'active'`,
|
|
211
|
+
p.company_id,
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
return json({
|
|
215
|
+
company,
|
|
216
|
+
summary: {
|
|
217
|
+
employees: employees[0]?.count ?? 0,
|
|
218
|
+
finance,
|
|
219
|
+
contacts,
|
|
220
|
+
contracts,
|
|
221
|
+
projects,
|
|
222
|
+
milestones,
|
|
223
|
+
investment_rounds: rounds,
|
|
224
|
+
active_alerts: alerts,
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
case "delete_milestone": {
|
|
230
|
+
db.execute("DELETE FROM opc_milestones WHERE id = ?", p.milestone_id);
|
|
231
|
+
return json({ ok: true });
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
case "delete_event": {
|
|
235
|
+
db.execute("DELETE FROM opc_lifecycle_events WHERE id = ?", p.event_id);
|
|
236
|
+
return json({ ok: true });
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
default:
|
|
240
|
+
return json({ error: `未知操作: ${(p as { action: string }).action}` });
|
|
241
|
+
}
|
|
242
|
+
} catch (err) {
|
|
243
|
+
return json({ error: err instanceof Error ? err.message : String(err) });
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
{ name: "opc_lifecycle" },
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
api.logger.info("opc: 已注册 opc_lifecycle 工具");
|
|
251
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 星环OPC中心 — opc_media 新媒体运营工具
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Type, type Static } from "@sinclair/typebox";
|
|
6
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
7
|
+
import type { OpcDatabase } from "../db/index.js";
|
|
8
|
+
import { json } from "../utils/tool-helper.js";
|
|
9
|
+
|
|
10
|
+
const MediaSchema = Type.Union([
|
|
11
|
+
Type.Object({
|
|
12
|
+
action: Type.Literal("create_content"),
|
|
13
|
+
company_id: Type.String({ description: "公司 ID" }),
|
|
14
|
+
title: Type.String({ description: "内容标题" }),
|
|
15
|
+
platform: Type.String({ description: "平台: 微信公众号/小红书/抖音/微博/知乎/B站/其他" }),
|
|
16
|
+
content_type: Type.Optional(Type.String({ description: "类型: article/short_video/image/live/other" })),
|
|
17
|
+
content: Type.Optional(Type.String({ description: "内容正文/脚本" })),
|
|
18
|
+
scheduled_date: Type.Optional(Type.String({ description: "计划发布日期 (YYYY-MM-DD)" })),
|
|
19
|
+
tags: Type.Optional(Type.String({ description: "标签,JSON 数组" })),
|
|
20
|
+
}),
|
|
21
|
+
Type.Object({
|
|
22
|
+
action: Type.Literal("list_content"),
|
|
23
|
+
company_id: Type.String({ description: "公司 ID" }),
|
|
24
|
+
platform: Type.Optional(Type.String({ description: "按平台筛选" })),
|
|
25
|
+
status: Type.Optional(Type.String({ description: "按状态筛选: draft/scheduled/published/archived" })),
|
|
26
|
+
}),
|
|
27
|
+
Type.Object({
|
|
28
|
+
action: Type.Literal("update_content"),
|
|
29
|
+
content_id: Type.String({ description: "内容 ID" }),
|
|
30
|
+
title: Type.Optional(Type.String({ description: "新标题" })),
|
|
31
|
+
content: Type.Optional(Type.String({ description: "新内容" })),
|
|
32
|
+
status: Type.Optional(Type.String({ description: "新状态" })),
|
|
33
|
+
published_date: Type.Optional(Type.String({ description: "实际发布日期" })),
|
|
34
|
+
metrics: Type.Optional(Type.String({ description: "数据指标 JSON,如 {\"views\":1000,\"likes\":50}" })),
|
|
35
|
+
}),
|
|
36
|
+
Type.Object({
|
|
37
|
+
action: Type.Literal("content_calendar"),
|
|
38
|
+
company_id: Type.String({ description: "公司 ID" }),
|
|
39
|
+
month: Type.Optional(Type.String({ description: "月份 (YYYY-MM),默认当月" })),
|
|
40
|
+
}),
|
|
41
|
+
Type.Object({
|
|
42
|
+
action: Type.Literal("platform_guide"),
|
|
43
|
+
platform: Type.String({ description: "平台名称" }),
|
|
44
|
+
}),
|
|
45
|
+
Type.Object({
|
|
46
|
+
action: Type.Literal("delete_content"),
|
|
47
|
+
content_id: Type.String({ description: "内容 ID" }),
|
|
48
|
+
}),
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
type MediaParams = Static<typeof MediaSchema>;
|
|
52
|
+
|
|
53
|
+
const PLATFORM_GUIDES: Record<string, { format: string; best_time: string; tips: string[] }> = {
|
|
54
|
+
"微信公众号": {
|
|
55
|
+
format: "图文文章 800-2000字,配图 3-6 张",
|
|
56
|
+
best_time: "早 7-9 点,中午 12-13 点,晚 20-22 点",
|
|
57
|
+
tips: ["标题控制在 20 字以内", "开头 3 行决定打开率", "文末引导关注/转发", "排版简洁,段落短"],
|
|
58
|
+
},
|
|
59
|
+
"小红书": {
|
|
60
|
+
format: "图文笔记 300-800字,封面图决定点击率",
|
|
61
|
+
best_time: "中午 12-14 点,晚 19-22 点",
|
|
62
|
+
tips: ["标题加 emoji 提升点击", "首图要有冲击力", "内容要有干货/教程价值", "标签 10-15 个"],
|
|
63
|
+
},
|
|
64
|
+
"抖音": {
|
|
65
|
+
format: "短视频 15-60秒,竖屏 9:16",
|
|
66
|
+
best_time: "中午 12-13 点,晚 18-22 点",
|
|
67
|
+
tips: ["前 3 秒要抓注意力", "字幕必须加", "选热门 BGM", "每条视频一个核心信息"],
|
|
68
|
+
},
|
|
69
|
+
"微博": {
|
|
70
|
+
format: "文字 140字内 + 配图/视频",
|
|
71
|
+
best_time: "工作日 10-12 点,20-23 点",
|
|
72
|
+
tips: ["蹭热点要快", "话题标签要用", "互动性内容效果好", "长文用头条文章"],
|
|
73
|
+
},
|
|
74
|
+
"知乎": {
|
|
75
|
+
format: "长文回答 1000-3000字,专业深度内容",
|
|
76
|
+
best_time: "工作日 10-12 点,20-22 点",
|
|
77
|
+
tips: ["选高关注问题回答", "开头要有结论", "引用数据增加可信度", "专栏文章沉淀内容"],
|
|
78
|
+
},
|
|
79
|
+
"B站": {
|
|
80
|
+
format: "中长视频 3-15分钟,横屏 16:9",
|
|
81
|
+
best_time: "周末全天,工作日 18-23 点",
|
|
82
|
+
tips: ["封面标题要有信息量", "内容节奏要快", "弹幕互动很重要", "系列化内容涨粉快"],
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export function registerMediaTool(api: OpenClawPluginApi, db: OpcDatabase): void {
|
|
87
|
+
api.registerTool(
|
|
88
|
+
{
|
|
89
|
+
name: "opc_media",
|
|
90
|
+
label: "OPC 新媒体运营",
|
|
91
|
+
description:
|
|
92
|
+
"新媒体运营工具。操作: create_content(创建内容), list_content(内容列表), " +
|
|
93
|
+
"update_content(更新内容), content_calendar(发布日历), platform_guide(平台指南), delete_content(删除内容)",
|
|
94
|
+
parameters: MediaSchema,
|
|
95
|
+
async execute(_toolCallId, params) {
|
|
96
|
+
const p = params as MediaParams;
|
|
97
|
+
try {
|
|
98
|
+
switch (p.action) {
|
|
99
|
+
case "create_content": {
|
|
100
|
+
const id = db.genId();
|
|
101
|
+
const now = new Date().toISOString();
|
|
102
|
+
db.execute(
|
|
103
|
+
`INSERT INTO opc_media_content (id, company_id, title, platform, content_type, content, status, scheduled_date, tags, created_at, updated_at)
|
|
104
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
105
|
+
id, p.company_id, p.title, p.platform,
|
|
106
|
+
p.content_type ?? "article", p.content ?? "",
|
|
107
|
+
p.scheduled_date ? "scheduled" : "draft",
|
|
108
|
+
p.scheduled_date ?? "", p.tags ?? "[]", now, now,
|
|
109
|
+
);
|
|
110
|
+
return json(db.queryOne("SELECT * FROM opc_media_content WHERE id = ?", id));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
case "list_content": {
|
|
114
|
+
let sql = "SELECT * FROM opc_media_content WHERE company_id = ?";
|
|
115
|
+
const params2: unknown[] = [p.company_id];
|
|
116
|
+
if (p.platform) { sql += " AND platform = ?"; params2.push(p.platform); }
|
|
117
|
+
if (p.status) { sql += " AND status = ?"; params2.push(p.status); }
|
|
118
|
+
sql += " ORDER BY created_at DESC";
|
|
119
|
+
return json(db.query(sql, ...params2));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
case "update_content": {
|
|
123
|
+
const fields: string[] = [];
|
|
124
|
+
const values: unknown[] = [];
|
|
125
|
+
if (p.title) { fields.push("title = ?"); values.push(p.title); }
|
|
126
|
+
if (p.content) { fields.push("content = ?"); values.push(p.content); }
|
|
127
|
+
if (p.status) { fields.push("status = ?"); values.push(p.status); }
|
|
128
|
+
if (p.published_date) { fields.push("published_date = ?"); values.push(p.published_date); }
|
|
129
|
+
if (p.metrics) { fields.push("metrics = ?"); values.push(p.metrics); }
|
|
130
|
+
fields.push("updated_at = ?"); values.push(new Date().toISOString());
|
|
131
|
+
values.push(p.content_id);
|
|
132
|
+
db.execute(`UPDATE opc_media_content SET ${fields.join(", ")} WHERE id = ?`, ...values);
|
|
133
|
+
return json(db.queryOne("SELECT * FROM opc_media_content WHERE id = ?", p.content_id) ?? { error: "内容不存在" });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
case "content_calendar": {
|
|
137
|
+
const month = p.month ?? new Date().toISOString().slice(0, 7);
|
|
138
|
+
const scheduled = db.query(
|
|
139
|
+
"SELECT * FROM opc_media_content WHERE company_id = ? AND scheduled_date LIKE ? ORDER BY scheduled_date",
|
|
140
|
+
p.company_id, month + "%",
|
|
141
|
+
);
|
|
142
|
+
const published = db.query(
|
|
143
|
+
"SELECT * FROM opc_media_content WHERE company_id = ? AND published_date LIKE ? ORDER BY published_date",
|
|
144
|
+
p.company_id, month + "%",
|
|
145
|
+
);
|
|
146
|
+
return json({ month, scheduled, published });
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
case "platform_guide": {
|
|
150
|
+
const guide = PLATFORM_GUIDES[p.platform];
|
|
151
|
+
if (!guide) {
|
|
152
|
+
return json({ error: `无此平台指南,可用: ${Object.keys(PLATFORM_GUIDES).join(", ")}` });
|
|
153
|
+
}
|
|
154
|
+
return json({ platform: p.platform, ...guide });
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
case "delete_content": {
|
|
158
|
+
db.execute("DELETE FROM opc_media_content WHERE id = ?", p.content_id);
|
|
159
|
+
return json({ ok: true });
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
default:
|
|
163
|
+
return json({ error: `未知操作: ${(p as { action: string }).action}` });
|
|
164
|
+
}
|
|
165
|
+
} catch (err) {
|
|
166
|
+
return json({ error: err instanceof Error ? err.message : String(err) });
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
{ name: "opc_media" },
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
api.logger.info("opc: 已注册 opc_media 工具");
|
|
174
|
+
}
|