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,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 星环OPC中心 — opc_monitoring 运营监控工具
|
|
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 MonitoringSchema = Type.Union([
|
|
11
|
+
Type.Object({
|
|
12
|
+
action: Type.Literal("record_metric"),
|
|
13
|
+
company_id: Type.String({ description: "公司 ID" }),
|
|
14
|
+
name: Type.String({ description: "指标名称(如: 月收入/用户数/转化率)" }),
|
|
15
|
+
value: Type.Number({ description: "指标值" }),
|
|
16
|
+
unit: Type.Optional(Type.String({ description: "单位(如: 元/人/%)" })),
|
|
17
|
+
category: Type.Optional(Type.String({ description: "分类: revenue/user/conversion/cost/other" })),
|
|
18
|
+
recorded_at: Type.Optional(Type.String({ description: "记录时间 (YYYY-MM-DD 或 ISO datetime)" })),
|
|
19
|
+
notes: Type.Optional(Type.String({ description: "备注" })),
|
|
20
|
+
}),
|
|
21
|
+
Type.Object({
|
|
22
|
+
action: Type.Literal("get_metrics"),
|
|
23
|
+
company_id: Type.String({ description: "公司 ID" }),
|
|
24
|
+
name: Type.Optional(Type.String({ description: "按指标名称筛选" })),
|
|
25
|
+
category: Type.Optional(Type.String({ description: "按分类筛选" })),
|
|
26
|
+
start_date: Type.Optional(Type.String({ description: "开始日期" })),
|
|
27
|
+
end_date: Type.Optional(Type.String({ description: "结束日期" })),
|
|
28
|
+
}),
|
|
29
|
+
Type.Object({
|
|
30
|
+
action: Type.Literal("create_alert"),
|
|
31
|
+
company_id: Type.String({ description: "公司 ID" }),
|
|
32
|
+
title: Type.String({ description: "告警标题" }),
|
|
33
|
+
severity: Type.Optional(Type.String({ description: "严重度: info/warning/critical" })),
|
|
34
|
+
category: Type.Optional(Type.String({ description: "分类" })),
|
|
35
|
+
message: Type.Optional(Type.String({ description: "告警详情" })),
|
|
36
|
+
}),
|
|
37
|
+
Type.Object({
|
|
38
|
+
action: Type.Literal("list_alerts"),
|
|
39
|
+
company_id: Type.String({ description: "公司 ID" }),
|
|
40
|
+
severity: Type.Optional(Type.String({ description: "按严重度筛选: info/warning/critical" })),
|
|
41
|
+
status: Type.Optional(Type.String({ description: "按状态筛选: active/acknowledged/resolved" })),
|
|
42
|
+
}),
|
|
43
|
+
Type.Object({
|
|
44
|
+
action: Type.Literal("dismiss_alert"),
|
|
45
|
+
alert_id: Type.String({ description: "告警 ID" }),
|
|
46
|
+
}),
|
|
47
|
+
Type.Object({
|
|
48
|
+
action: Type.Literal("kpi_summary"),
|
|
49
|
+
company_id: Type.String({ description: "公司 ID" }),
|
|
50
|
+
}),
|
|
51
|
+
Type.Object({
|
|
52
|
+
action: Type.Literal("delete_metric"),
|
|
53
|
+
metric_id: Type.String({ description: "指标记录 ID" }),
|
|
54
|
+
}),
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
type MonitoringParams = Static<typeof MonitoringSchema>;
|
|
58
|
+
|
|
59
|
+
export function registerMonitoringTool(api: OpenClawPluginApi, db: OpcDatabase): void {
|
|
60
|
+
api.registerTool(
|
|
61
|
+
{
|
|
62
|
+
name: "opc_monitoring",
|
|
63
|
+
label: "OPC 运营监控",
|
|
64
|
+
description:
|
|
65
|
+
"运营监控工具。操作: record_metric(记录指标), get_metrics(查询指标), " +
|
|
66
|
+
"create_alert(创建告警), list_alerts(告警列表), " +
|
|
67
|
+
"dismiss_alert(消除告警), kpi_summary(KPI 汇总), delete_metric(删除指标记录)",
|
|
68
|
+
parameters: MonitoringSchema,
|
|
69
|
+
async execute(_toolCallId, params) {
|
|
70
|
+
const p = params as MonitoringParams;
|
|
71
|
+
try {
|
|
72
|
+
switch (p.action) {
|
|
73
|
+
case "record_metric": {
|
|
74
|
+
const id = db.genId();
|
|
75
|
+
const now = new Date().toISOString();
|
|
76
|
+
db.execute(
|
|
77
|
+
`INSERT INTO opc_metrics (id, company_id, name, value, unit, category, recorded_at, notes, created_at)
|
|
78
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
79
|
+
id, p.company_id, p.name, p.value,
|
|
80
|
+
p.unit ?? "", p.category ?? "", p.recorded_at ?? now, p.notes ?? "", now,
|
|
81
|
+
);
|
|
82
|
+
return json(db.queryOne("SELECT * FROM opc_metrics WHERE id = ?", id));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
case "get_metrics": {
|
|
86
|
+
let sql = "SELECT * FROM opc_metrics WHERE company_id = ?";
|
|
87
|
+
const params2: unknown[] = [p.company_id];
|
|
88
|
+
if (p.name) { sql += " AND name = ?"; params2.push(p.name); }
|
|
89
|
+
if (p.category) { sql += " AND category = ?"; params2.push(p.category); }
|
|
90
|
+
if (p.start_date) { sql += " AND recorded_at >= ?"; params2.push(p.start_date); }
|
|
91
|
+
if (p.end_date) { sql += " AND recorded_at <= ?"; params2.push(p.end_date); }
|
|
92
|
+
sql += " ORDER BY recorded_at DESC";
|
|
93
|
+
return json(db.query(sql, ...params2));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
case "create_alert": {
|
|
97
|
+
const id = db.genId();
|
|
98
|
+
const now = new Date().toISOString();
|
|
99
|
+
db.execute(
|
|
100
|
+
`INSERT INTO opc_alerts (id, company_id, title, severity, category, status, message, resolved_at, created_at)
|
|
101
|
+
VALUES (?, ?, ?, ?, ?, 'active', ?, '', ?)`,
|
|
102
|
+
id, p.company_id, p.title,
|
|
103
|
+
p.severity ?? "info", p.category ?? "", p.message ?? "", now,
|
|
104
|
+
);
|
|
105
|
+
return json(db.queryOne("SELECT * FROM opc_alerts WHERE id = ?", id));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
case "list_alerts": {
|
|
109
|
+
let sql = "SELECT * FROM opc_alerts WHERE company_id = ?";
|
|
110
|
+
const params2: unknown[] = [p.company_id];
|
|
111
|
+
if (p.severity) { sql += " AND severity = ?"; params2.push(p.severity); }
|
|
112
|
+
if (p.status) { sql += " AND status = ?"; params2.push(p.status); }
|
|
113
|
+
sql += " ORDER BY created_at DESC";
|
|
114
|
+
return json(db.query(sql, ...params2));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
case "dismiss_alert": {
|
|
118
|
+
const now = new Date().toISOString();
|
|
119
|
+
db.execute(
|
|
120
|
+
"UPDATE opc_alerts SET status = 'resolved', resolved_at = ? WHERE id = ?",
|
|
121
|
+
now, p.alert_id,
|
|
122
|
+
);
|
|
123
|
+
return json(db.queryOne("SELECT * FROM opc_alerts WHERE id = ?", p.alert_id) ?? { error: "告警不存在" });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
case "kpi_summary": {
|
|
127
|
+
// 跨表聚合 KPI
|
|
128
|
+
const revenue = db.queryOne(
|
|
129
|
+
`SELECT COALESCE(SUM(CASE WHEN type='income' THEN amount ELSE 0 END), 0) as total_income,
|
|
130
|
+
COALESCE(SUM(CASE WHEN type='expense' THEN amount ELSE 0 END), 0) as total_expense
|
|
131
|
+
FROM opc_transactions WHERE company_id = ?`,
|
|
132
|
+
p.company_id,
|
|
133
|
+
) as { total_income: number; total_expense: number };
|
|
134
|
+
|
|
135
|
+
const employees = db.queryOne(
|
|
136
|
+
"SELECT COUNT(*) as count FROM opc_employees WHERE company_id = ? AND status = 'active'",
|
|
137
|
+
p.company_id,
|
|
138
|
+
) as { count: number };
|
|
139
|
+
|
|
140
|
+
const projects = db.queryOne(
|
|
141
|
+
`SELECT COUNT(*) as total,
|
|
142
|
+
SUM(CASE WHEN status='active' THEN 1 ELSE 0 END) as active,
|
|
143
|
+
SUM(CASE WHEN status='completed' THEN 1 ELSE 0 END) as completed
|
|
144
|
+
FROM opc_projects WHERE company_id = ?`,
|
|
145
|
+
p.company_id,
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
const contracts = db.queryOne(
|
|
149
|
+
`SELECT COUNT(*) as total,
|
|
150
|
+
SUM(CASE WHEN status='active' THEN 1 ELSE 0 END) as active,
|
|
151
|
+
COALESCE(SUM(CASE WHEN status='active' THEN amount ELSE 0 END), 0) as active_value
|
|
152
|
+
FROM opc_contracts WHERE company_id = ?`,
|
|
153
|
+
p.company_id,
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
const alerts = db.query(
|
|
157
|
+
`SELECT severity, COUNT(*) as count
|
|
158
|
+
FROM opc_alerts WHERE company_id = ? AND status = 'active'
|
|
159
|
+
GROUP BY severity`,
|
|
160
|
+
p.company_id,
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
const recentMetrics = db.query(
|
|
164
|
+
`SELECT name, value, unit, category, recorded_at
|
|
165
|
+
FROM opc_metrics WHERE company_id = ?
|
|
166
|
+
ORDER BY recorded_at DESC LIMIT 20`,
|
|
167
|
+
p.company_id,
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
const contacts = db.queryOne(
|
|
171
|
+
"SELECT COUNT(*) as count FROM opc_contacts WHERE company_id = ?",
|
|
172
|
+
p.company_id,
|
|
173
|
+
) as { count: number };
|
|
174
|
+
|
|
175
|
+
return json({
|
|
176
|
+
financial: {
|
|
177
|
+
total_income: revenue.total_income,
|
|
178
|
+
total_expense: revenue.total_expense,
|
|
179
|
+
net_profit: revenue.total_income - revenue.total_expense,
|
|
180
|
+
},
|
|
181
|
+
team: { active_employees: employees.count },
|
|
182
|
+
projects,
|
|
183
|
+
contracts,
|
|
184
|
+
customers: { total_contacts: contacts.count },
|
|
185
|
+
alerts: { active: alerts },
|
|
186
|
+
recent_metrics: recentMetrics,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
case "delete_metric": {
|
|
191
|
+
db.execute("DELETE FROM opc_metrics WHERE id = ?", p.metric_id);
|
|
192
|
+
return json({ ok: true });
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
default:
|
|
196
|
+
return json({ error: `未知操作: ${(p as { action: string }).action}` });
|
|
197
|
+
}
|
|
198
|
+
} catch (err) {
|
|
199
|
+
return json({ error: err instanceof Error ? err.message : String(err) });
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
{ name: "opc_monitoring" },
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
api.logger.info("opc: 已注册 opc_monitoring 工具");
|
|
207
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 星环OPC中心 — opc_opb 一人企业画布工具
|
|
3
|
+
*
|
|
4
|
+
* 基于《一人企业方法论2.0》的 OPB Canvas(16模块),
|
|
5
|
+
* 帮助创始人系统化设计与记录其一人公司战略蓝图。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Type, type Static } from "@sinclair/typebox";
|
|
9
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
10
|
+
import type { OpcDatabase } from "../db/index.js";
|
|
11
|
+
import { json } from "../utils/tool-helper.js";
|
|
12
|
+
|
|
13
|
+
const OPB_FIELDS = [
|
|
14
|
+
"track", // 赛道(所处行业/细分市场)
|
|
15
|
+
"target_customer", // 目标客户
|
|
16
|
+
"pain_point", // 核心痛点
|
|
17
|
+
"solution", // 解决方案
|
|
18
|
+
"unique_value", // 独特价值主张(USP)
|
|
19
|
+
"channels", // 获客渠道
|
|
20
|
+
"revenue_model", // 收入模式
|
|
21
|
+
"cost_structure", // 成本结构
|
|
22
|
+
"key_resources", // 关键资源
|
|
23
|
+
"key_activities", // 关键活动
|
|
24
|
+
"key_partners", // 关键合作伙伴
|
|
25
|
+
"unfair_advantage", // 不公平优势
|
|
26
|
+
"metrics", // 关键指标
|
|
27
|
+
"non_compete", // 非竞争策略
|
|
28
|
+
"scaling_strategy", // 规模化路径
|
|
29
|
+
"notes", // 备注
|
|
30
|
+
] as const;
|
|
31
|
+
|
|
32
|
+
const OpbSchema = Type.Union([
|
|
33
|
+
Type.Object({
|
|
34
|
+
action: Type.Literal("canvas_init"),
|
|
35
|
+
company_id: Type.String({ description: "公司 ID" }),
|
|
36
|
+
track: Type.Optional(Type.String({ description: "赛道(所处行业/细分市场)" })),
|
|
37
|
+
target_customer: Type.Optional(Type.String({ description: "目标客户画像" })),
|
|
38
|
+
pain_point: Type.Optional(Type.String({ description: "核心痛点" })),
|
|
39
|
+
solution: Type.Optional(Type.String({ description: "解决方案" })),
|
|
40
|
+
unique_value: Type.Optional(Type.String({ description: "独特价值主张(USP)" })),
|
|
41
|
+
channels: Type.Optional(Type.String({ description: "获客渠道" })),
|
|
42
|
+
revenue_model: Type.Optional(Type.String({ description: "收入模式" })),
|
|
43
|
+
cost_structure: Type.Optional(Type.String({ description: "成本结构" })),
|
|
44
|
+
key_resources: Type.Optional(Type.String({ description: "关键资源(三个池子:内容/产品/客户)" })),
|
|
45
|
+
key_activities: Type.Optional(Type.String({ description: "关键活动" })),
|
|
46
|
+
key_partners: Type.Optional(Type.String({ description: "关键合作伙伴" })),
|
|
47
|
+
unfair_advantage: Type.Optional(Type.String({ description: "不公平优势" })),
|
|
48
|
+
metrics: Type.Optional(Type.String({ description: "关键指标(KPI)" })),
|
|
49
|
+
non_compete: Type.Optional(Type.String({ description: "非竞争策略" })),
|
|
50
|
+
scaling_strategy: Type.Optional(Type.String({ description: "规模化路径" })),
|
|
51
|
+
notes: Type.Optional(Type.String({ description: "其他备注" })),
|
|
52
|
+
}),
|
|
53
|
+
Type.Object({
|
|
54
|
+
action: Type.Literal("canvas_get"),
|
|
55
|
+
company_id: Type.String({ description: "公司 ID" }),
|
|
56
|
+
}),
|
|
57
|
+
Type.Object({
|
|
58
|
+
action: Type.Literal("canvas_update"),
|
|
59
|
+
company_id: Type.String({ description: "公司 ID" }),
|
|
60
|
+
track: Type.Optional(Type.String({ description: "赛道(所处行业/细分市场)" })),
|
|
61
|
+
target_customer: Type.Optional(Type.String({ description: "目标客户画像" })),
|
|
62
|
+
pain_point: Type.Optional(Type.String({ description: "核心痛点" })),
|
|
63
|
+
solution: Type.Optional(Type.String({ description: "解决方案" })),
|
|
64
|
+
unique_value: Type.Optional(Type.String({ description: "独特价值主张(USP)" })),
|
|
65
|
+
channels: Type.Optional(Type.String({ description: "获客渠道" })),
|
|
66
|
+
revenue_model: Type.Optional(Type.String({ description: "收入模式" })),
|
|
67
|
+
cost_structure: Type.Optional(Type.String({ description: "成本结构" })),
|
|
68
|
+
key_resources: Type.Optional(Type.String({ description: "关键资源(三个池子:内容/产品/客户)" })),
|
|
69
|
+
key_activities: Type.Optional(Type.String({ description: "关键活动" })),
|
|
70
|
+
key_partners: Type.Optional(Type.String({ description: "关键合作伙伴" })),
|
|
71
|
+
unfair_advantage: Type.Optional(Type.String({ description: "不公平优势" })),
|
|
72
|
+
metrics: Type.Optional(Type.String({ description: "关键指标(KPI)" })),
|
|
73
|
+
non_compete: Type.Optional(Type.String({ description: "非竞争策略" })),
|
|
74
|
+
scaling_strategy: Type.Optional(Type.String({ description: "规模化路径" })),
|
|
75
|
+
notes: Type.Optional(Type.String({ description: "其他备注" })),
|
|
76
|
+
}),
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
type OpbParams = Static<typeof OpbSchema>;
|
|
80
|
+
|
|
81
|
+
interface CanvasRow {
|
|
82
|
+
id: string; company_id: string;
|
|
83
|
+
track: string; target_customer: string; pain_point: string; solution: string;
|
|
84
|
+
unique_value: string; channels: string; revenue_model: string; cost_structure: string;
|
|
85
|
+
key_resources: string; key_activities: string; key_partners: string;
|
|
86
|
+
unfair_advantage: string; metrics: string; non_compete: string;
|
|
87
|
+
scaling_strategy: string; notes: string;
|
|
88
|
+
created_at: string; updated_at: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function registerOpbTool(api: OpenClawPluginApi, db: OpcDatabase): void {
|
|
92
|
+
api.registerTool(
|
|
93
|
+
{
|
|
94
|
+
name: "opc_opb",
|
|
95
|
+
label: "OPB 一人企业画布",
|
|
96
|
+
description:
|
|
97
|
+
"一人企业方法论(OPB)画布工具。" +
|
|
98
|
+
"操作: canvas_init(初始化/填写画布), canvas_get(查看画布), canvas_update(更新画布字段)。" +
|
|
99
|
+
"画布涵盖 16 个模块:赛道、目标客户、痛点、解决方案、独特价值、获客渠道、" +
|
|
100
|
+
"收入模式、成本结构、关键资源、关键活动、关键合作伙伴、不公平优势、" +
|
|
101
|
+
"关键指标、非竞争策略、规模化路径、备注。",
|
|
102
|
+
parameters: OpbSchema,
|
|
103
|
+
async execute(_toolCallId, params) {
|
|
104
|
+
const p = params as OpbParams;
|
|
105
|
+
try {
|
|
106
|
+
switch (p.action) {
|
|
107
|
+
case "canvas_init": {
|
|
108
|
+
// Check if canvas already exists
|
|
109
|
+
const existing = db.queryOne(
|
|
110
|
+
"SELECT id FROM opc_opb_canvas WHERE company_id = ?", p.company_id,
|
|
111
|
+
) as { id: string } | null;
|
|
112
|
+
if (existing) {
|
|
113
|
+
return json({ ok: false, error: "该公司画布已存在,请使用 canvas_update 更新" });
|
|
114
|
+
}
|
|
115
|
+
const id = db.genId();
|
|
116
|
+
const now = new Date().toISOString();
|
|
117
|
+
db.execute(
|
|
118
|
+
`INSERT INTO opc_opb_canvas (
|
|
119
|
+
id, company_id, track, target_customer, pain_point, solution,
|
|
120
|
+
unique_value, channels, revenue_model, cost_structure,
|
|
121
|
+
key_resources, key_activities, key_partners, unfair_advantage,
|
|
122
|
+
metrics, non_compete, scaling_strategy, notes, created_at, updated_at
|
|
123
|
+
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
|
|
124
|
+
id, p.company_id,
|
|
125
|
+
p.track ?? "", p.target_customer ?? "", p.pain_point ?? "", p.solution ?? "",
|
|
126
|
+
p.unique_value ?? "", p.channels ?? "", p.revenue_model ?? "", p.cost_structure ?? "",
|
|
127
|
+
p.key_resources ?? "", p.key_activities ?? "", p.key_partners ?? "",
|
|
128
|
+
p.unfair_advantage ?? "", p.metrics ?? "", p.non_compete ?? "",
|
|
129
|
+
p.scaling_strategy ?? "", p.notes ?? "", now, now,
|
|
130
|
+
);
|
|
131
|
+
const canvas = db.queryOne(
|
|
132
|
+
"SELECT * FROM opc_opb_canvas WHERE id = ?", id,
|
|
133
|
+
) as CanvasRow;
|
|
134
|
+
return json({ ok: true, canvas, message: "OPB 画布已初始化。建议逐步填写各模块内容。" });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
case "canvas_get": {
|
|
138
|
+
const canvas = db.queryOne(
|
|
139
|
+
"SELECT * FROM opc_opb_canvas WHERE company_id = ?", p.company_id,
|
|
140
|
+
) as CanvasRow | null;
|
|
141
|
+
if (!canvas) {
|
|
142
|
+
return json({ ok: false, error: "该公司暂无 OPB 画布,请先使用 canvas_init 创建" });
|
|
143
|
+
}
|
|
144
|
+
// Calculate completion percentage
|
|
145
|
+
const filled = OPB_FIELDS.filter(f => canvas[f as keyof CanvasRow] && String(canvas[f as keyof CanvasRow]).trim() !== "").length;
|
|
146
|
+
const completion = Math.round((filled / (OPB_FIELDS.length - 1)) * 100); // exclude notes
|
|
147
|
+
return json({ canvas, completion, total_fields: OPB_FIELDS.length - 1, filled });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
case "canvas_update": {
|
|
151
|
+
const existing = db.queryOne(
|
|
152
|
+
"SELECT id FROM opc_opb_canvas WHERE company_id = ?", p.company_id,
|
|
153
|
+
) as { id: string } | null;
|
|
154
|
+
if (!existing) {
|
|
155
|
+
return json({ ok: false, error: "该公司暂无 OPB 画布,请先使用 canvas_init 创建" });
|
|
156
|
+
}
|
|
157
|
+
const now = new Date().toISOString();
|
|
158
|
+
const updates: string[] = [];
|
|
159
|
+
const vals: unknown[] = [];
|
|
160
|
+
for (const field of OPB_FIELDS) {
|
|
161
|
+
const val = (p as Record<string, unknown>)[field];
|
|
162
|
+
if (val !== undefined) {
|
|
163
|
+
updates.push(`${field} = ?`);
|
|
164
|
+
vals.push(val);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (updates.length === 0) {
|
|
168
|
+
return json({ ok: false, error: "未提供任何更新字段" });
|
|
169
|
+
}
|
|
170
|
+
updates.push("updated_at = ?");
|
|
171
|
+
vals.push(now, existing.id);
|
|
172
|
+
db.execute(
|
|
173
|
+
`UPDATE opc_opb_canvas SET ${updates.join(", ")} WHERE id = ?`,
|
|
174
|
+
...vals,
|
|
175
|
+
);
|
|
176
|
+
const canvas = db.queryOne(
|
|
177
|
+
"SELECT * FROM opc_opb_canvas WHERE id = ?", existing.id,
|
|
178
|
+
) as CanvasRow;
|
|
179
|
+
return json({ ok: true, canvas, updated_fields: updates.length - 1 });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
default:
|
|
183
|
+
return json({ error: "未知 action" });
|
|
184
|
+
}
|
|
185
|
+
} catch (err) {
|
|
186
|
+
return json({ error: err instanceof Error ? err.message : String(err) });
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
{ name: "opc_opb" },
|
|
191
|
+
);
|
|
192
|
+
api.logger.info("opc: 已注册 opc_opb 工具");
|
|
193
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 星环OPC中心 — opc_manage 统一工具
|
|
3
|
+
*
|
|
4
|
+
* 单一工具 + action 字段模式,参照 feishu_doc 实现。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
8
|
+
import type { OpcDatabase } from "../db/index.js";
|
|
9
|
+
import { CompanyManager } from "../opc/company-manager.js";
|
|
10
|
+
import type { OpcCompanyStatus, OpcTransactionCategory, OpcTransactionType } from "../opc/types.js";
|
|
11
|
+
import { ensureCompanyWorkspace } from "../opc/workspace-factory.js";
|
|
12
|
+
import { OpcManageSchema, type OpcManageParams } from "./schemas.js";
|
|
13
|
+
import { json } from "../utils/tool-helper.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 将 company_id(可能是 DB ID 或公司名称)解析为实际数据库 ID。
|
|
17
|
+
* AI 有时会传公司名称而非 ID,此函数做兜底匹配。
|
|
18
|
+
*/
|
|
19
|
+
function resolveCompanyId(db: OpcDatabase, input: string): string {
|
|
20
|
+
// 先精确匹配 ID
|
|
21
|
+
const byId = db.queryOne("SELECT id FROM opc_companies WHERE id = ?", input) as { id: string } | null;
|
|
22
|
+
if (byId) return byId.id;
|
|
23
|
+
// 再精确匹配名称
|
|
24
|
+
const byName = db.queryOne("SELECT id FROM opc_companies WHERE name = ?", input) as { id: string } | null;
|
|
25
|
+
if (byName) return byName.id;
|
|
26
|
+
// 最后模糊匹配名称
|
|
27
|
+
const byLike = db.queryOne("SELECT id FROM opc_companies WHERE name LIKE ?", `%${input}%`) as { id: string } | null;
|
|
28
|
+
if (byLike) return byLike.id;
|
|
29
|
+
// 找不到就原样返回,由上层报错
|
|
30
|
+
return input;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function registerOpcTool(api: OpenClawPluginApi, db: OpcDatabase): void {
|
|
34
|
+
const manager = new CompanyManager(db);
|
|
35
|
+
|
|
36
|
+
api.registerTool(
|
|
37
|
+
{
|
|
38
|
+
name: "opc_manage",
|
|
39
|
+
label: "OPC 一人公司管理",
|
|
40
|
+
description: [
|
|
41
|
+
"一人公司(OPC)全生命周期管理工具。",
|
|
42
|
+
"操作: register_company(注册公司), get_company(查询公司), list_companies(公司列表),",
|
|
43
|
+
"update_company(更新公司), activate_company(激活公司), change_company_status(变更状态),",
|
|
44
|
+
"add_transaction(记账), list_transactions(交易列表), finance_summary(财务摘要),",
|
|
45
|
+
"add_contact(添加客户), list_contacts(客户列表), update_contact(更新客户),",
|
|
46
|
+
"delete_contact(删除客户), dashboard(看板统计),",
|
|
47
|
+
"set_company_skills(设置公司Agent skills), get_company_skills(查询公司Agent skills)",
|
|
48
|
+
].join(" "),
|
|
49
|
+
parameters: OpcManageSchema,
|
|
50
|
+
async execute(_toolCallId, params) {
|
|
51
|
+
const p = params as OpcManageParams;
|
|
52
|
+
try {
|
|
53
|
+
switch (p.action) {
|
|
54
|
+
// ── 公司管理 ──
|
|
55
|
+
case "register_company": {
|
|
56
|
+
const company = manager.registerCompany({
|
|
57
|
+
name: p.name,
|
|
58
|
+
industry: p.industry,
|
|
59
|
+
owner_name: p.owner_name,
|
|
60
|
+
owner_contact: p.owner_contact,
|
|
61
|
+
registered_capital: p.registered_capital,
|
|
62
|
+
description: p.description,
|
|
63
|
+
});
|
|
64
|
+
// 自动创建公司专属 Agent 工作区
|
|
65
|
+
const skillsRow = db.queryOne("SELECT value FROM opc_tool_config WHERE key = ?", `company_skills_${company.id}`) as { value: string } | null;
|
|
66
|
+
const companySkills: string[] = skillsRow ? (JSON.parse(skillsRow.value) as string[]) : [];
|
|
67
|
+
ensureCompanyWorkspace({
|
|
68
|
+
companyId: company.id,
|
|
69
|
+
companyName: company.name,
|
|
70
|
+
cfg: api.config,
|
|
71
|
+
runtime: api.runtime,
|
|
72
|
+
log: (msg) => api.logger.info(msg),
|
|
73
|
+
skills: companySkills,
|
|
74
|
+
}).catch((err) => api.logger.warn(`opc: 创建工作区失败: ${err}`));
|
|
75
|
+
return json(company);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
case "get_company":
|
|
79
|
+
return json(manager.getCompany(p.company_id) ?? { error: "公司不存在" });
|
|
80
|
+
|
|
81
|
+
case "list_companies":
|
|
82
|
+
return json(
|
|
83
|
+
manager.listCompanies(p.status as OpcCompanyStatus | undefined),
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
case "update_company":
|
|
87
|
+
return json(
|
|
88
|
+
manager.updateCompany(p.company_id, {
|
|
89
|
+
name: p.name,
|
|
90
|
+
industry: p.industry,
|
|
91
|
+
description: p.description,
|
|
92
|
+
owner_contact: p.owner_contact,
|
|
93
|
+
}) ?? { error: "公司不存在" },
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
case "activate_company":
|
|
97
|
+
return json(manager.activateCompany(p.company_id) ?? { error: "公司不存在" });
|
|
98
|
+
|
|
99
|
+
case "change_company_status":
|
|
100
|
+
return json(
|
|
101
|
+
manager.transitionStatus(
|
|
102
|
+
p.company_id,
|
|
103
|
+
p.new_status as OpcCompanyStatus,
|
|
104
|
+
) ?? { error: "公司不存在" },
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
// ── 交易记录 ──
|
|
108
|
+
case "add_transaction":
|
|
109
|
+
return json(
|
|
110
|
+
db.createTransaction({
|
|
111
|
+
company_id: p.company_id,
|
|
112
|
+
type: p.type as OpcTransactionType,
|
|
113
|
+
category: (p.category ?? "other") as OpcTransactionCategory,
|
|
114
|
+
amount: p.amount,
|
|
115
|
+
description: p.description ?? "",
|
|
116
|
+
counterparty: p.counterparty ?? "",
|
|
117
|
+
transaction_date: p.transaction_date ?? new Date().toISOString().slice(0, 10),
|
|
118
|
+
}),
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
case "list_transactions":
|
|
122
|
+
return json(
|
|
123
|
+
db.listTransactions(p.company_id, {
|
|
124
|
+
type: p.type,
|
|
125
|
+
startDate: p.start_date,
|
|
126
|
+
endDate: p.end_date,
|
|
127
|
+
limit: p.limit ?? 50,
|
|
128
|
+
}),
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
case "finance_summary":
|
|
132
|
+
return json(
|
|
133
|
+
db.getFinanceSummary(p.company_id, p.start_date, p.end_date),
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
// ── 客户管理 ──
|
|
137
|
+
case "add_contact":
|
|
138
|
+
return json(
|
|
139
|
+
db.createContact({
|
|
140
|
+
company_id: p.company_id,
|
|
141
|
+
name: p.name,
|
|
142
|
+
phone: p.phone ?? "",
|
|
143
|
+
email: p.email ?? "",
|
|
144
|
+
company_name: p.company_name ?? "",
|
|
145
|
+
tags: p.tags ?? "[]",
|
|
146
|
+
notes: p.notes ?? "",
|
|
147
|
+
last_contact_date: new Date().toISOString().slice(0, 10),
|
|
148
|
+
}),
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
case "list_contacts":
|
|
152
|
+
return json(db.listContacts(p.company_id, p.tag));
|
|
153
|
+
|
|
154
|
+
case "update_contact": {
|
|
155
|
+
const updateData: Record<string, string> = {};
|
|
156
|
+
if (p.name) updateData.name = p.name;
|
|
157
|
+
if (p.phone) updateData.phone = p.phone;
|
|
158
|
+
if (p.email) updateData.email = p.email;
|
|
159
|
+
if (p.company_name) updateData.company_name = p.company_name;
|
|
160
|
+
if (p.tags) updateData.tags = p.tags;
|
|
161
|
+
if (p.notes) updateData.notes = p.notes;
|
|
162
|
+
if (p.last_contact_date) updateData.last_contact_date = p.last_contact_date;
|
|
163
|
+
return json(db.updateContact(p.contact_id, updateData) ?? { error: "联系人不存在" });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
case "delete_contact":
|
|
167
|
+
return json({ deleted: db.deleteContact(p.contact_id) });
|
|
168
|
+
|
|
169
|
+
// ── Dashboard ──
|
|
170
|
+
case "dashboard":
|
|
171
|
+
return json(db.getDashboardStats());
|
|
172
|
+
|
|
173
|
+
// ── Company Skills (OpenClaw agent-level skills) ──
|
|
174
|
+
case "set_company_skills": {
|
|
175
|
+
const resolvedId = resolveCompanyId(db, p.company_id);
|
|
176
|
+
const key = `company_skills_${resolvedId}`;
|
|
177
|
+
const value = JSON.stringify(p.skills ?? []);
|
|
178
|
+
db.execute(
|
|
179
|
+
`INSERT INTO opc_tool_config (key, value) VALUES (?, ?)
|
|
180
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value`,
|
|
181
|
+
key, value,
|
|
182
|
+
);
|
|
183
|
+
return json({ ok: true, company_id: resolvedId, skills: p.skills });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
case "get_company_skills": {
|
|
187
|
+
const resolvedId = resolveCompanyId(db, p.company_id);
|
|
188
|
+
const key = `company_skills_${resolvedId}`;
|
|
189
|
+
const row = db.queryOne("SELECT value FROM opc_tool_config WHERE key = ?", key) as { value: string } | null;
|
|
190
|
+
const skills = row ? (JSON.parse(row.value) as string[]) : [];
|
|
191
|
+
return json({ company_id: resolvedId, skills });
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
default:
|
|
195
|
+
return json({ error: `未知操作: ${(p as { action: string }).action}` });
|
|
196
|
+
}
|
|
197
|
+
} catch (err) {
|
|
198
|
+
return json({ error: err instanceof Error ? err.message : String(err) });
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
{ name: "opc_manage" },
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
api.logger.info("opc: 已注册 opc_manage 工具");
|
|
206
|
+
}
|