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.
Files changed (46) hide show
  1. package/README.md +166 -0
  2. package/index.ts +145 -0
  3. package/openclaw.plugin.json +17 -0
  4. package/package.json +47 -0
  5. package/skills/basic-crm/SKILL.md +81 -0
  6. package/skills/basic-finance/SKILL.md +100 -0
  7. package/skills/business-monitoring/SKILL.md +120 -0
  8. package/skills/company-lifecycle/SKILL.md +99 -0
  9. package/skills/company-registration/SKILL.md +80 -0
  10. package/skills/finance-tax/SKILL.md +150 -0
  11. package/skills/hr-assistant/SKILL.md +127 -0
  12. package/skills/investment-management/SKILL.md +101 -0
  13. package/skills/legal-assistant/SKILL.md +113 -0
  14. package/skills/media-ops/SKILL.md +101 -0
  15. package/skills/procurement-management/SKILL.md +91 -0
  16. package/skills/project-management/SKILL.md +125 -0
  17. package/src/api/companies.ts +193 -0
  18. package/src/api/dashboard.ts +25 -0
  19. package/src/api/routes.ts +14 -0
  20. package/src/db/index.ts +63 -0
  21. package/src/db/migrations.ts +67 -0
  22. package/src/db/schema.ts +518 -0
  23. package/src/db/sqlite-adapter.ts +366 -0
  24. package/src/opc/company-manager.ts +82 -0
  25. package/src/opc/context-injector.ts +186 -0
  26. package/src/opc/reminder-service.ts +289 -0
  27. package/src/opc/types.ts +330 -0
  28. package/src/opc/workspace-factory.ts +189 -0
  29. package/src/tools/acquisition-tool.ts +150 -0
  30. package/src/tools/asset-package-tool.ts +283 -0
  31. package/src/tools/finance-tool.ts +244 -0
  32. package/src/tools/hr-tool.ts +211 -0
  33. package/src/tools/investment-tool.ts +201 -0
  34. package/src/tools/legal-tool.ts +191 -0
  35. package/src/tools/lifecycle-tool.ts +251 -0
  36. package/src/tools/media-tool.ts +174 -0
  37. package/src/tools/monitoring-tool.ts +207 -0
  38. package/src/tools/opb-tool.ts +193 -0
  39. package/src/tools/opc-tool.ts +206 -0
  40. package/src/tools/procurement-tool.ts +191 -0
  41. package/src/tools/project-tool.ts +203 -0
  42. package/src/tools/schemas.ts +163 -0
  43. package/src/tools/staff-tool.ts +211 -0
  44. package/src/utils/tool-helper.ts +16 -0
  45. package/src/web/config-ui.ts +3501 -0
  46. package/src/web/landing-page.ts +269 -0
@@ -0,0 +1,101 @@
1
+ ---
2
+ name: media-ops
3
+ description: |
4
+ 新媒体运营技能。当用户提到公众号、小红书、抖音、微博、知乎、B站、内容发布、自媒体、内容日历时激活。
5
+ ---
6
+
7
+ # 新媒体运营技能
8
+
9
+ 使用 `opc_media` 工具进行内容管理、发布日历和平台运营指导。
10
+
11
+ ## 内容管理
12
+
13
+ ### 创建内容
14
+
15
+ ```json
16
+ {
17
+ "action": "create_content",
18
+ "company_id": "公司ID",
19
+ "title": "一人公司如何高效管理时间",
20
+ "platform": "微信公众号",
21
+ "content_type": "article",
22
+ "content": "文章正文或脚本内容...",
23
+ "scheduled_date": "2025-04-15",
24
+ "tags": "[\"时间管理\", \"一人公司\", \"效率\"]"
25
+ }
26
+ ```
27
+
28
+ 支持平台: `微信公众号` / `小红书` / `抖音` / `微博` / `知乎` / `B站` / `其他`
29
+
30
+ 内容类型: `article`(图文) / `short_video`(短视频) / `image`(图片) / `live`(直播) / `other`
31
+
32
+ ### 查看内容列表
33
+
34
+ ```json
35
+ {
36
+ "action": "list_content",
37
+ "company_id": "公司ID",
38
+ "platform": "小红书",
39
+ "status": "draft"
40
+ }
41
+ ```
42
+
43
+ 内容状态: `draft`(草稿) → `scheduled`(已排期) → `published`(已发布) → `archived`(归档)
44
+
45
+ ### 更新内容
46
+
47
+ ```json
48
+ {
49
+ "action": "update_content",
50
+ "content_id": "内容ID",
51
+ "status": "published",
52
+ "published_date": "2025-04-15",
53
+ "metrics": "{\"views\": 5200, \"likes\": 180, \"comments\": 32, \"shares\": 15}"
54
+ }
55
+ ```
56
+
57
+ 发布后可记录数据指标(阅读量、点赞、评论、转发)。
58
+
59
+ ## 发布日历
60
+
61
+ ```json
62
+ {
63
+ "action": "content_calendar",
64
+ "company_id": "公司ID",
65
+ "month": "2025-04"
66
+ }
67
+ ```
68
+
69
+ 返回该月已排期和已发布的所有内容,方便规划发布节奏。
70
+
71
+ ## 平台指南
72
+
73
+ ```json
74
+ {
75
+ "action": "platform_guide",
76
+ "platform": "小红书"
77
+ }
78
+ ```
79
+
80
+ 返回该平台的内容格式建议、最佳发布时间和运营技巧。
81
+
82
+ 可用平台指南: `微信公众号` / `小红书` / `抖音` / `微博` / `知乎` / `B站`
83
+
84
+ ## 各平台要点速查
85
+
86
+ | 平台 | 内容格式 | 最佳时间 |
87
+ |---|---|---|
88
+ | 微信公众号 | 图文 800-2000字 | 早7-9/中12-13/晚20-22 |
89
+ | 小红书 | 图文笔记 300-800字 | 中12-14/晚19-22 |
90
+ | 抖音 | 竖屏短视频 15-60秒 | 中12-13/晚18-22 |
91
+ | 微博 | 140字+配图/视频 | 工作日10-12/20-23 |
92
+ | 知乎 | 长文 1000-3000字 | 工作日10-12/20-22 |
93
+ | B站 | 横屏中长视频 3-15分钟 | 周末全天/工作日18-23 |
94
+
95
+ ## 使用建议
96
+
97
+ 1. 帮用户规划每周 2-3 篇内容,保持稳定输出
98
+ 2. 同一内容可适配多个平台(改标题/格式/长度)
99
+ 3. 发布后提醒用户回填 `metrics` 数据,追踪效果
100
+ 4. 根据平台特点调整内容风格(知乎专业深度、小红书轻松实用)
101
+ 5. 使用 `content_calendar` 确保发布节奏均匀,避免断更
@@ -0,0 +1,91 @@
1
+ ---
2
+ name: procurement-management
3
+ description: |
4
+ 服务采购管理技能。当用户提到采购、服务商、供应商、订单、外包、SaaS订阅、服务续费时激活。
5
+ ---
6
+
7
+ # 服务采购管理技能
8
+
9
+ 使用 `opc_procurement` 工具进行服务项目管理、采购订单管理和费用汇总。
10
+
11
+ ## 服务项目管理
12
+
13
+ ### 添加服务项目
14
+
15
+ ```json
16
+ {
17
+ "action": "create_service",
18
+ "company_id": "公司ID",
19
+ "name": "阿里云 ECS",
20
+ "category": "saas",
21
+ "provider": "阿里云",
22
+ "unit_price": 500,
23
+ "billing_cycle": "monthly",
24
+ "description": "云服务器 2核4G"
25
+ }
26
+ ```
27
+
28
+ 服务类别: saas, outsource(外包), subscription(订阅), consulting(咨询), other
29
+
30
+ 计费周期: monthly(月付), quarterly(季付), yearly(年付), one_time(一次性)
31
+
32
+ ### 查看服务列表
33
+
34
+ ```json
35
+ {
36
+ "action": "list_services",
37
+ "company_id": "公司ID",
38
+ "category": "saas",
39
+ "status": "active"
40
+ }
41
+ ```
42
+
43
+ ## 采购订单管理
44
+
45
+ ### 创建采购订单
46
+
47
+ ```json
48
+ {
49
+ "action": "create_order",
50
+ "company_id": "公司ID",
51
+ "service_id": "服务ID",
52
+ "title": "2025年Q2云服务器续费",
53
+ "amount": 1500,
54
+ "order_date": "2025-04-01",
55
+ "delivery_date": "2025-04-01",
56
+ "notes": "季度预付"
57
+ }
58
+ ```
59
+
60
+ ### 查看订单列表
61
+
62
+ ```json
63
+ {
64
+ "action": "list_orders",
65
+ "company_id": "公司ID",
66
+ "status": "pending"
67
+ }
68
+ ```
69
+
70
+ 订单状态: `pending` → `approved` → `paid`,或 `cancelled`
71
+
72
+ ## 采购汇总
73
+
74
+ ### 查看采购统计
75
+
76
+ ```json
77
+ {
78
+ "action": "order_summary",
79
+ "company_id": "公司ID"
80
+ }
81
+ ```
82
+
83
+ 返回: 总订单数、总金额、按状态/类别分组统计、活跃服务月度成本。
84
+
85
+ ## 使用建议
86
+
87
+ 1. 先 `create_service` 建立服务项目清单(如云服务、设计外包、法律顾问)
88
+ 2. 每次采购时 `create_order` 关联对应服务
89
+ 3. 定期 `order_summary` 查看采购支出分布
90
+ 4. 注意 `billing_cycle` 字段,方便预算规划
91
+ 5. 对比采购支出和收入,及时调整成本结构
@@ -0,0 +1,125 @@
1
+ ---
2
+ name: project-management
3
+ description: |
4
+ 项目管理技能。当用户提到项目、任务、看板、排期、工时、里程碑、项目进度、任务分配时激活。
5
+ ---
6
+
7
+ # 项目管理技能
8
+
9
+ 使用 `opc_project` 工具进行项目创建、任务管理和进度追踪。
10
+
11
+ ## 项目管理
12
+
13
+ ### 创建项目
14
+
15
+ ```json
16
+ {
17
+ "action": "create_project",
18
+ "company_id": "公司ID",
19
+ "name": "XX客户官网重构",
20
+ "description": "响应式重构,含前后端和部署",
21
+ "start_date": "2025-04-01",
22
+ "end_date": "2025-06-30",
23
+ "budget": 150000
24
+ }
25
+ ```
26
+
27
+ ### 查看项目列表
28
+
29
+ ```json
30
+ {
31
+ "action": "list_projects",
32
+ "company_id": "公司ID",
33
+ "status": "active"
34
+ }
35
+ ```
36
+
37
+ 项目状态: `planning`(规划中) → `active`(进行中) → `completed`(已完成),或 `paused`(暂停) / `cancelled`(取消)
38
+
39
+ ### 更新项目
40
+
41
+ ```json
42
+ {
43
+ "action": "update_project",
44
+ "project_id": "项目ID",
45
+ "status": "active",
46
+ "spent": 50000,
47
+ "description": "新增移动端适配需求"
48
+ }
49
+ ```
50
+
51
+ ## 任务管理
52
+
53
+ ### 添加任务
54
+
55
+ ```json
56
+ {
57
+ "action": "add_task",
58
+ "project_id": "项目ID",
59
+ "company_id": "公司ID",
60
+ "title": "完成首页UI设计",
61
+ "description": "参照竞品分析结果,设计3版方案",
62
+ "assignee": "张三",
63
+ "priority": "high",
64
+ "due_date": "2025-04-15",
65
+ "hours_estimated": 16
66
+ }
67
+ ```
68
+
69
+ 优先级: `urgent`(紧急) / `high`(高) / `medium`(中) / `low`(低)
70
+
71
+ ### 查看任务列表
72
+
73
+ ```json
74
+ {
75
+ "action": "list_tasks",
76
+ "project_id": "项目ID",
77
+ "status": "in_progress"
78
+ }
79
+ ```
80
+
81
+ 任务按优先级和截止日期排序。
82
+
83
+ 任务状态: `todo`(待办) → `in_progress`(进行中) → `review`(审核) → `done`(完成)
84
+
85
+ ### 更新任务
86
+
87
+ ```json
88
+ {
89
+ "action": "update_task",
90
+ "task_id": "任务ID",
91
+ "status": "done",
92
+ "hours_actual": 20
93
+ }
94
+ ```
95
+
96
+ ## 项目概况
97
+
98
+ ```json
99
+ {
100
+ "action": "project_summary",
101
+ "project_id": "项目ID"
102
+ }
103
+ ```
104
+
105
+ 返回: 项目基本信息、各状态任务统计(数量/预估工时/实际工时)、逾期任务列表。
106
+
107
+ ## 看板视图
108
+
109
+ ```json
110
+ {
111
+ "action": "kanban",
112
+ "project_id": "项目ID"
113
+ }
114
+ ```
115
+
116
+ 返回四列看板: `todo` / `in_progress` / `review` / `done`
117
+
118
+ ## 使用建议
119
+
120
+ 1. 每个项目创建后先拆解为 5-10 个具体任务
121
+ 2. 任务粒度建议 2-8 小时,太大需再拆分
122
+ 3. 完成任务时填写 `hours_actual`,便于后续项目报价参考
123
+ 4. 定期使用 `project_summary` 查看进度和逾期风险
124
+ 5. 一人公司项目 `assignee` 可填自己名字或留空
125
+ 6. 预算管理: 通过 `update_project` 更新 `spent` 字段追踪支出
@@ -0,0 +1,193 @@
1
+ /**
2
+ * 星环OPC中心 — 公司 CRUD REST API
3
+ *
4
+ * 路由:
5
+ * GET /opc/api/companies — 列出所有公司
6
+ * GET /opc/api/companies/:id — 获取单个公司
7
+ * POST /opc/api/companies — 创建公司
8
+ * PUT /opc/api/companies/:id — 更新公司
9
+ * DELETE /opc/api/companies/:id — 删除公司
10
+ * GET /opc/api/companies/:id/transactions — 获取公司交易
11
+ * GET /opc/api/companies/:id/contacts — 获取公司客户
12
+ * GET /opc/api/companies/:id/finance — 获取公司财务摘要
13
+ */
14
+
15
+ import type { IncomingMessage, ServerResponse } from "node:http";
16
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
17
+ import type { OpcDatabase } from "../db/index.js";
18
+ import { CompanyManager } from "../opc/company-manager.js";
19
+ import type { OpcCompanyStatus } from "../opc/types.js";
20
+
21
+ const OPC_API_PREFIX = "/opc/api/companies";
22
+
23
+ function sendJson(res: ServerResponse, data: unknown, status = 200): void {
24
+ res.writeHead(status, { "Content-Type": "application/json; charset=utf-8" });
25
+ res.end(JSON.stringify(data));
26
+ }
27
+
28
+ function sendError(res: ServerResponse, message: string, status = 400): void {
29
+ sendJson(res, { error: message }, status);
30
+ }
31
+
32
+ async function readBody(req: IncomingMessage, maxBytes = 1024 * 1024): Promise<string> {
33
+ return new Promise((resolve, reject) => {
34
+ let body = "";
35
+ let bytes = 0;
36
+ req.on("data", (chunk: Buffer) => {
37
+ bytes += chunk.length;
38
+ if (bytes > maxBytes) {
39
+ reject(new Error("Request body too large"));
40
+ req.destroy();
41
+ return;
42
+ }
43
+ body += chunk.toString();
44
+ });
45
+ req.on("end", () => resolve(body));
46
+ req.on("error", reject);
47
+ });
48
+ }
49
+
50
+ function parseJson(body: string): Record<string, unknown> | null {
51
+ try {
52
+ return JSON.parse(body) as Record<string, unknown>;
53
+ } catch {
54
+ return null;
55
+ }
56
+ }
57
+
58
+ export function registerCompanyRoutes(api: OpenClawPluginApi, db: OpcDatabase): void {
59
+ const manager = new CompanyManager(db);
60
+
61
+ api.registerHttpHandler(async (req, res) => {
62
+ const rawUrl = req.url ?? "";
63
+ const method = req.method?.toUpperCase() ?? "GET";
64
+
65
+ // 解析 URL,分离路径和查询参数
66
+ const urlObj = new URL(rawUrl, "http://localhost");
67
+ const pathname = urlObj.pathname;
68
+
69
+ // 只处理 /opc/api/companies 开头的请求
70
+ if (!pathname.startsWith(OPC_API_PREFIX)) {
71
+ return false;
72
+ }
73
+
74
+ const subPath = pathname.slice(OPC_API_PREFIX.length);
75
+
76
+ try {
77
+ // GET/POST /opc/api/companies
78
+ if (subPath === "" || subPath === "/") {
79
+ if (method === "GET") {
80
+ const status = urlObj.searchParams.get("status") as OpcCompanyStatus | null;
81
+ sendJson(res, manager.listCompanies(status ?? undefined));
82
+ return true;
83
+ }
84
+ if (method === "POST") {
85
+ const body = parseJson(await readBody(req));
86
+ if (!body) {
87
+ sendError(res, "Invalid JSON body");
88
+ return true;
89
+ }
90
+ const name = body.name as string | undefined;
91
+ const industry = body.industry as string | undefined;
92
+ const owner_name = body.owner_name as string | undefined;
93
+ if (!name || !industry || !owner_name) {
94
+ sendError(res, "缺少必填字段: name, industry, owner_name");
95
+ return true;
96
+ }
97
+ const company = manager.registerCompany({
98
+ name,
99
+ industry,
100
+ owner_name,
101
+ owner_contact: (body.owner_contact as string) ?? undefined,
102
+ registered_capital: (body.registered_capital as number) ?? undefined,
103
+ description: (body.description as string) ?? undefined,
104
+ });
105
+ sendJson(res, company, 201);
106
+ return true;
107
+ }
108
+ sendError(res, "Method not allowed", 405);
109
+ return true;
110
+ }
111
+
112
+ // 提取 ID 和子路径
113
+ const match = subPath.match(/^\/([^/]+)(\/.*)?$/);
114
+ if (!match) {
115
+ sendError(res, "Invalid path", 404);
116
+ return true;
117
+ }
118
+
119
+ const companyId = match[1];
120
+ const tail = match[2] ?? "";
121
+
122
+ // GET /opc/api/companies/:id/transactions
123
+ if (tail === "/transactions" && method === "GET") {
124
+ sendJson(res, db.listTransactions(companyId, { limit: 100 }));
125
+ return true;
126
+ }
127
+
128
+ // GET /opc/api/companies/:id/contacts
129
+ if (tail === "/contacts" && method === "GET") {
130
+ sendJson(res, db.listContacts(companyId));
131
+ return true;
132
+ }
133
+
134
+ // GET /opc/api/companies/:id/finance
135
+ if (tail === "/finance" && method === "GET") {
136
+ sendJson(res, db.getFinanceSummary(companyId));
137
+ return true;
138
+ }
139
+
140
+ // 以下处理 /opc/api/companies/:id(无子路径)
141
+ if (tail !== "") {
142
+ sendError(res, "Not found", 404);
143
+ return true;
144
+ }
145
+
146
+ switch (method) {
147
+ // GET /opc/api/companies/:id
148
+ case "GET": {
149
+ const company = manager.getCompany(companyId);
150
+ if (!company) {
151
+ sendError(res, "公司不存在", 404);
152
+ } else {
153
+ sendJson(res, company);
154
+ }
155
+ return true;
156
+ }
157
+
158
+ // POST /opc/api/companies (companyId 在这种情况下是数据的一部分)
159
+ // 实际的 POST 创建走 subPath === "" 分支,这里不需要处理
160
+
161
+ // PUT /opc/api/companies/:id
162
+ case "PUT": {
163
+ const body = parseJson(await readBody(req));
164
+ if (!body) {
165
+ sendError(res, "Invalid JSON body");
166
+ return true;
167
+ }
168
+ const updated = manager.updateCompany(companyId, body as Record<string, string>);
169
+ if (!updated) {
170
+ sendError(res, "公司不存在", 404);
171
+ } else {
172
+ sendJson(res, updated);
173
+ }
174
+ return true;
175
+ }
176
+
177
+ // DELETE /opc/api/companies/:id
178
+ case "DELETE": {
179
+ const deleted = manager.deleteCompany(companyId);
180
+ sendJson(res, { deleted });
181
+ return true;
182
+ }
183
+
184
+ default:
185
+ sendError(res, "Method not allowed", 405);
186
+ return true;
187
+ }
188
+ } catch (err) {
189
+ sendError(res, err instanceof Error ? err.message : String(err), 500);
190
+ return true;
191
+ }
192
+ });
193
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * 星环OPC中心 — Dashboard 统计 API
3
+ *
4
+ * 路由:
5
+ * GET /opc/api/dashboard/stats — 平台整体统计
6
+ */
7
+
8
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
9
+ import type { OpcDatabase } from "../db/index.js";
10
+
11
+ export function registerDashboardRoutes(api: OpenClawPluginApi, db: OpcDatabase): void {
12
+ api.registerHttpRoute({
13
+ path: "/opc/api/dashboard/stats",
14
+ handler: (_req, res) => {
15
+ try {
16
+ const stats = db.getDashboardStats();
17
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
18
+ res.end(JSON.stringify(stats));
19
+ } catch (err) {
20
+ res.writeHead(500, { "Content-Type": "application/json; charset=utf-8" });
21
+ res.end(JSON.stringify({ error: err instanceof Error ? err.message : String(err) }));
22
+ }
23
+ },
24
+ });
25
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * 星环OPC中心 — HTTP API 路由注册
3
+ */
4
+
5
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
6
+ import type { OpcDatabase } from "../db/index.js";
7
+ import { registerCompanyRoutes } from "./companies.js";
8
+ import { registerDashboardRoutes } from "./dashboard.js";
9
+
10
+ export function registerHttpRoutes(api: OpenClawPluginApi, db: OpcDatabase): void {
11
+ registerCompanyRoutes(api, db);
12
+ registerDashboardRoutes(api, db);
13
+ api.logger.info("opc: 已注册 HTTP API 路由");
14
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * 星环OPC中心 — 数据库抽象层
3
+ */
4
+
5
+ import type {
6
+ OpcCompany,
7
+ OpcCompanyStatus,
8
+ OpcContact,
9
+ OpcEmployee,
10
+ OpcTransaction,
11
+ } from "../opc/types.js";
12
+
13
+ export interface OpcDatabase {
14
+ close(): void;
15
+
16
+ // Companies
17
+ createCompany(data: Omit<OpcCompany, "id" | "created_at" | "updated_at">): OpcCompany;
18
+ getCompany(id: string): OpcCompany | null;
19
+ listCompanies(status?: OpcCompanyStatus): OpcCompany[];
20
+ updateCompany(id: string, data: Partial<OpcCompany>): OpcCompany | null;
21
+ deleteCompany(id: string): boolean;
22
+
23
+ // Employees
24
+ createEmployee(data: Omit<OpcEmployee, "id" | "created_at">): OpcEmployee;
25
+ getEmployee(id: string): OpcEmployee | null;
26
+ listEmployees(companyId: string): OpcEmployee[];
27
+
28
+ // Transactions
29
+ createTransaction(data: Omit<OpcTransaction, "id" | "created_at">): OpcTransaction;
30
+ getTransaction(id: string): OpcTransaction | null;
31
+ listTransactions(
32
+ companyId: string,
33
+ opts?: { type?: string; startDate?: string; endDate?: string; limit?: number },
34
+ ): OpcTransaction[];
35
+ getFinanceSummary(
36
+ companyId: string,
37
+ startDate?: string,
38
+ endDate?: string,
39
+ ): { total_income: number; total_expense: number; net: number; count: number };
40
+
41
+ // Contacts
42
+ createContact(data: Omit<OpcContact, "id" | "created_at" | "updated_at">): OpcContact;
43
+ getContact(id: string): OpcContact | null;
44
+ listContacts(companyId: string, tag?: string): OpcContact[];
45
+ updateContact(id: string, data: Partial<OpcContact>): OpcContact | null;
46
+ deleteContact(id: string): boolean;
47
+
48
+ // Dashboard
49
+ getDashboardStats(): {
50
+ total_companies: number;
51
+ active_companies: number;
52
+ total_transactions: number;
53
+ total_contacts: number;
54
+ total_revenue: number;
55
+ total_expense: number;
56
+ };
57
+
58
+ // Generic (Phase 2)
59
+ query(sql: string, ...params: unknown[]): unknown[];
60
+ queryOne(sql: string, ...params: unknown[]): unknown | null;
61
+ execute(sql: string, ...params: unknown[]): { changes: number };
62
+ genId(): string;
63
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * 星环OPC中心 — 数据库版本迁移
3
+ */
4
+
5
+ import type Database from "better-sqlite3";
6
+
7
+ export type Migration = {
8
+ version: number;
9
+ description: string;
10
+ up: (db: Database.Database) => void;
11
+ };
12
+
13
+ export const migrations: Migration[] = [
14
+ {
15
+ version: 1,
16
+ description: "Initial schema — companies, employees, transactions, contacts",
17
+ up(_db) {
18
+ // Tables and indexes are created in initializeDatabase.
19
+ // This migration exists as the baseline version marker.
20
+ },
21
+ },
22
+ {
23
+ version: 2,
24
+ description: "Phase 3 — investment, procurement, lifecycle, monitoring, tool_config",
25
+ up(_db) {
26
+ // Tables and indexes are created in initializeDatabase via OPC_TABLES/OPC_INDEXES.
27
+ // This migration exists as the Phase 3 version marker.
28
+ },
29
+ },
30
+ {
31
+ version: 3,
32
+ description: "OPB Canvas — one-person business canvas table",
33
+ up(_db) {
34
+ // opc_opb_canvas table is created in initializeDatabase via OPC_TABLES/OPC_INDEXES.
35
+ // This migration exists as the OPB Canvas version marker.
36
+ },
37
+ },
38
+ ];
39
+
40
+ /**
41
+ * Run pending migrations up to the latest version.
42
+ */
43
+ export function runMigrations(db: Database.Database): void {
44
+ db.exec(`
45
+ CREATE TABLE IF NOT EXISTS opc_migrations (
46
+ version INTEGER PRIMARY KEY,
47
+ description TEXT NOT NULL,
48
+ applied_at TEXT NOT NULL DEFAULT (datetime('now'))
49
+ )
50
+ `);
51
+
52
+ const applied = new Set(
53
+ db
54
+ .prepare("SELECT version FROM opc_migrations")
55
+ .all()
56
+ .map((row) => (row as { version: number }).version),
57
+ );
58
+
59
+ for (const m of migrations) {
60
+ if (applied.has(m.version)) continue;
61
+ m.up(db);
62
+ db.prepare("INSERT INTO opc_migrations (version, description) VALUES (?, ?)").run(
63
+ m.version,
64
+ m.description,
65
+ );
66
+ }
67
+ }