@coralai/sps-cli 0.49.16 → 0.50.1

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 (177) hide show
  1. package/dist/commands/cardAdd.d.ts.map +1 -1
  2. package/dist/commands/cardAdd.js +47 -67
  3. package/dist/commands/cardAdd.js.map +1 -1
  4. package/dist/commands/cardMarkComplete.js +1 -1
  5. package/dist/commands/cardMarkComplete.js.map +1 -1
  6. package/dist/commands/cardMarkStarted.js +1 -1
  7. package/dist/commands/cardMarkStarted.js.map +1 -1
  8. package/dist/commands/consoleCommand.js +3 -3
  9. package/dist/commands/consoleCommand.js.map +1 -1
  10. package/dist/commands/hookCommand.d.ts +25 -0
  11. package/dist/commands/hookCommand.d.ts.map +1 -1
  12. package/dist/commands/hookCommand.js +2 -2
  13. package/dist/commands/hookCommand.js.map +1 -1
  14. package/dist/console/index.d.ts +15 -0
  15. package/dist/console/index.d.ts.map +1 -0
  16. package/dist/console/index.js +180 -0
  17. package/dist/console/index.js.map +1 -0
  18. package/dist/console/lib/lockFile.d.ts +17 -0
  19. package/dist/console/lib/lockFile.d.ts.map +1 -0
  20. package/dist/console/lib/lockFile.js +61 -0
  21. package/dist/console/lib/lockFile.js.map +1 -0
  22. package/dist/console/lib/portPicker.d.ts +3 -0
  23. package/dist/console/lib/portPicker.d.ts.map +1 -0
  24. package/dist/console/lib/portPicker.js +25 -0
  25. package/dist/console/lib/portPicker.js.map +1 -0
  26. package/dist/console/lib/resultToJson.d.ts +18 -0
  27. package/dist/console/lib/resultToJson.d.ts.map +1 -0
  28. package/dist/console/lib/resultToJson.js +19 -0
  29. package/dist/console/lib/resultToJson.js.map +1 -0
  30. package/dist/console/routes/cards.d.ts +26 -0
  31. package/dist/console/routes/cards.d.ts.map +1 -0
  32. package/dist/console/routes/cards.js +85 -0
  33. package/dist/console/routes/cards.js.map +1 -0
  34. package/dist/console/routes/chat.d.ts +37 -0
  35. package/dist/console/routes/chat.d.ts.map +1 -0
  36. package/dist/console/routes/chat.js +378 -0
  37. package/dist/console/routes/chat.js.map +1 -0
  38. package/dist/console/routes/logs.d.ts +17 -0
  39. package/dist/console/routes/logs.d.ts.map +1 -0
  40. package/dist/console/routes/logs.js +159 -0
  41. package/dist/console/routes/logs.js.map +1 -0
  42. package/dist/console/routes/pipeline.d.ts +10 -0
  43. package/dist/console/routes/pipeline.d.ts.map +1 -0
  44. package/dist/console/routes/pipeline.js +39 -0
  45. package/dist/console/routes/pipeline.js.map +1 -0
  46. package/dist/console/routes/projects.d.ts +15 -0
  47. package/dist/console/routes/projects.d.ts.map +1 -0
  48. package/dist/console/routes/projects.js +132 -0
  49. package/dist/console/routes/projects.js.map +1 -0
  50. package/dist/console/routes/skills.d.ts +4 -0
  51. package/dist/console/routes/skills.d.ts.map +1 -0
  52. package/dist/console/routes/skills.js +91 -0
  53. package/dist/console/routes/skills.js.map +1 -0
  54. package/dist/console/routes/system.d.ts +10 -0
  55. package/dist/console/routes/system.d.ts.map +1 -0
  56. package/dist/console/routes/system.js +84 -0
  57. package/dist/console/routes/system.js.map +1 -0
  58. package/dist/console/routes/workers.d.ts +5 -0
  59. package/dist/console/routes/workers.d.ts.map +1 -0
  60. package/dist/console/routes/workers.js +149 -0
  61. package/dist/console/routes/workers.js.map +1 -0
  62. package/dist/console/sse/eventBus.d.ts +25 -0
  63. package/dist/console/sse/eventBus.d.ts.map +1 -0
  64. package/dist/console/sse/eventBus.js +32 -0
  65. package/dist/console/sse/eventBus.js.map +1 -0
  66. package/dist/console/sse/projectStream.d.ts +13 -0
  67. package/dist/console/sse/projectStream.d.ts.map +1 -0
  68. package/dist/console/sse/projectStream.js +84 -0
  69. package/dist/console/sse/projectStream.js.map +1 -0
  70. package/dist/core/checklist.d.ts +1 -1
  71. package/dist/core/state.d.ts +1 -1
  72. package/dist/engines/MonitorEngine.d.ts +1 -1
  73. package/dist/engines/SchedulerEngine.d.ts +1 -1
  74. package/dist/engines/StageEngine.d.ts +1 -1
  75. package/dist/infra/chokidarWatchers.d.ts +38 -0
  76. package/dist/infra/chokidarWatchers.d.ts.map +1 -0
  77. package/dist/infra/chokidarWatchers.js +213 -0
  78. package/dist/infra/chokidarWatchers.js.map +1 -0
  79. package/dist/infra/clock.d.ts +35 -0
  80. package/dist/infra/clock.d.ts.map +1 -0
  81. package/dist/infra/clock.js +45 -0
  82. package/dist/infra/clock.js.map +1 -0
  83. package/dist/infra/filesystem.d.ts +89 -0
  84. package/dist/infra/filesystem.d.ts.map +1 -0
  85. package/dist/infra/filesystem.js +247 -0
  86. package/dist/infra/filesystem.js.map +1 -0
  87. package/dist/infra/spawn.d.ts +67 -0
  88. package/dist/infra/spawn.d.ts.map +1 -0
  89. package/dist/infra/spawn.js +116 -0
  90. package/dist/infra/spawn.js.map +1 -0
  91. package/dist/infra/sseBus.d.ts +36 -0
  92. package/dist/infra/sseBus.d.ts.map +1 -0
  93. package/dist/infra/sseBus.js +72 -0
  94. package/dist/infra/sseBus.js.map +1 -0
  95. package/dist/interfaces/ACPClient.d.ts +1 -1
  96. package/dist/interfaces/ACPClient.d.ts.map +1 -1
  97. package/dist/interfaces/RepoBackend.d.ts +1 -1
  98. package/dist/interfaces/TaskBackend.d.ts +1 -1
  99. package/dist/main.js +1 -1
  100. package/dist/main.js.map +1 -1
  101. package/dist/providers/GitLabRepoBackend.d.ts +1 -1
  102. package/dist/providers/LocalACPClient.d.ts +1 -1
  103. package/dist/providers/LocalACPClient.d.ts.map +1 -1
  104. package/dist/providers/MarkdownTaskBackend.d.ts +1 -1
  105. package/dist/services/CardService.d.ts +86 -0
  106. package/dist/services/CardService.d.ts.map +1 -0
  107. package/dist/services/CardService.js +313 -0
  108. package/dist/services/CardService.js.map +1 -0
  109. package/dist/services/ChatService.d.ts +62 -0
  110. package/dist/services/ChatService.d.ts.map +1 -0
  111. package/dist/services/ChatService.js +157 -0
  112. package/dist/services/ChatService.js.map +1 -0
  113. package/dist/services/LogService.d.ts +46 -0
  114. package/dist/services/LogService.d.ts.map +1 -0
  115. package/dist/services/LogService.js +185 -0
  116. package/dist/services/LogService.js.map +1 -0
  117. package/dist/services/PipelineService.d.ts +71 -0
  118. package/dist/services/PipelineService.d.ts.map +1 -0
  119. package/dist/services/PipelineService.js +349 -0
  120. package/dist/services/PipelineService.js.map +1 -0
  121. package/dist/services/ProjectService.d.ts +105 -0
  122. package/dist/services/ProjectService.d.ts.map +1 -0
  123. package/dist/services/ProjectService.js +346 -0
  124. package/dist/services/ProjectService.js.map +1 -0
  125. package/dist/services/SkillService.d.ts +38 -0
  126. package/dist/services/SkillService.d.ts.map +1 -0
  127. package/dist/services/SkillService.js +301 -0
  128. package/dist/services/SkillService.js.map +1 -0
  129. package/dist/services/SystemService.d.ts +67 -0
  130. package/dist/services/SystemService.d.ts.map +1 -0
  131. package/dist/services/SystemService.js +278 -0
  132. package/dist/services/SystemService.js.map +1 -0
  133. package/dist/services/WorkerService.d.ts +83 -0
  134. package/dist/services/WorkerService.d.ts.map +1 -0
  135. package/dist/services/WorkerService.js +262 -0
  136. package/dist/services/WorkerService.js.map +1 -0
  137. package/dist/services/container.d.ts +44 -0
  138. package/dist/services/container.d.ts.map +1 -0
  139. package/dist/services/container.js +104 -0
  140. package/dist/services/container.js.map +1 -0
  141. package/dist/services/defaults.d.ts +15 -0
  142. package/dist/services/defaults.d.ts.map +1 -0
  143. package/dist/services/defaults.js +11 -0
  144. package/dist/services/defaults.js.map +1 -0
  145. package/dist/services/executors.d.ts +38 -0
  146. package/dist/services/executors.d.ts.map +1 -0
  147. package/dist/services/executors.js +157 -0
  148. package/dist/services/executors.js.map +1 -0
  149. package/dist/services/ports.d.ts +17 -0
  150. package/dist/services/ports.d.ts.map +1 -0
  151. package/dist/services/ports.js +2 -0
  152. package/dist/services/ports.js.map +1 -0
  153. package/dist/shared/domainEvents.d.ts +118 -0
  154. package/dist/shared/domainEvents.d.ts.map +1 -0
  155. package/dist/shared/domainEvents.js +40 -0
  156. package/dist/shared/domainEvents.js.map +1 -0
  157. package/dist/shared/errors.d.ts +59 -0
  158. package/dist/shared/errors.d.ts.map +1 -0
  159. package/dist/shared/errors.js +79 -0
  160. package/dist/shared/errors.js.map +1 -0
  161. package/dist/shared/result.d.ts +51 -0
  162. package/dist/shared/result.d.ts.map +1 -0
  163. package/dist/shared/result.js +48 -0
  164. package/dist/shared/result.js.map +1 -0
  165. package/dist/shared/runtimePaths.d.ts +78 -0
  166. package/dist/shared/runtimePaths.d.ts.map +1 -0
  167. package/dist/shared/runtimePaths.js +179 -0
  168. package/dist/shared/runtimePaths.js.map +1 -0
  169. package/dist/shared/runtimeSchemas.d.ts +324 -0
  170. package/dist/shared/runtimeSchemas.d.ts.map +1 -0
  171. package/dist/shared/runtimeSchemas.js +124 -0
  172. package/dist/shared/runtimeSchemas.js.map +1 -0
  173. package/dist/shared/types.d.ts +97 -0
  174. package/dist/shared/types.d.ts.map +1 -0
  175. package/dist/shared/types.js +11 -0
  176. package/dist/shared/types.js.map +1 -0
  177. package/package.json +1 -1
@@ -0,0 +1,301 @@
1
+ /**
2
+ * @module services/SkillService
3
+ * @description Skill 注册 + link/unlink/freeze service
4
+ *
5
+ * @layer services
6
+ *
7
+ * 包装 core/skillStore 的能力,让 Delivery 层不再直接调 Domain。
8
+ * Phase 3 会用 SkillRegistry port 替代,当前先内部 wrap。
9
+ */
10
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
11
+ import { dirname, resolve } from 'node:path';
12
+ import { fileURLToPath } from 'node:url';
13
+ const __dirname = dirname(fileURLToPath(import.meta.url));
14
+ import { domainError } from '../shared/errors.js';
15
+ import { err, ok } from '../shared/result.js';
16
+ import { projectConfFile, projectsDir, userSkillsDir } from '../shared/runtimePaths.js';
17
+ export class SkillService {
18
+ deps;
19
+ constructor(deps) {
20
+ this.deps = deps;
21
+ }
22
+ /** 列出 user-level skills。可选带 project 参数以得到 stateInProject。 */
23
+ async list(project) {
24
+ const { listUserSkills, inspectProjectSkill } = await import('../core/skillStore.js');
25
+ const users = listUserSkills();
26
+ const enriched = users.map((u) => {
27
+ const fm = readFrontmatter(resolve(u.userPath, 'SKILL.md'));
28
+ const base = {
29
+ name: u.name,
30
+ category: classifyCategory(u.name),
31
+ description: fm.description,
32
+ origin: fm.origin,
33
+ linkedProjects: collectLinkedProjects(u.name).map((p) => p.project),
34
+ };
35
+ if (project) {
36
+ const repo = findProjectRepoDir(project);
37
+ const state = repo
38
+ ? inspectProjectSkill(repo, u.name)?.state ?? 'absent'
39
+ : 'absent';
40
+ return { ...base, stateInProject: state };
41
+ }
42
+ return base;
43
+ });
44
+ return ok(enriched);
45
+ }
46
+ async get(name) {
47
+ if (!isValidSkillName(name)) {
48
+ return err(domainError('validation', 'INVALID_SKILL_NAME', 'skill 名非法'));
49
+ }
50
+ const { listUserSkills } = await import('../core/skillStore.js');
51
+ const found = listUserSkills().find((u) => u.name === name);
52
+ if (!found) {
53
+ return err(domainError('not-found', 'SKILL_NOT_FOUND', `skill ${name} 不存在`));
54
+ }
55
+ const fm = readFrontmatter(resolve(found.userPath, 'SKILL.md'));
56
+ const body = readFileOrEmpty(resolve(found.userPath, 'SKILL.md'));
57
+ const refsDir = resolve(found.userPath, 'references');
58
+ const references = existsSync(refsDir)
59
+ ? readdirSync(refsDir)
60
+ .filter((f) => f.endsWith('.md'))
61
+ .map((f) => {
62
+ const full = resolve(refsDir, f);
63
+ return { name: f, lines: readFileOrEmpty(full).split('\n').length };
64
+ })
65
+ : [];
66
+ return ok({
67
+ name,
68
+ category: classifyCategory(name),
69
+ description: fm.description,
70
+ origin: fm.origin,
71
+ body,
72
+ references,
73
+ linkedProjects: collectLinkedProjects(name).map((p) => p.project),
74
+ });
75
+ }
76
+ async sync() {
77
+ const skillStore = await import('../core/skillStore.js');
78
+ // syncBundledSkillsToUser 需要 bundled skills 目录参数。defaults 让它自己查找内置。
79
+ // Phase 3 重构 bundled 目录定位时再传显式参数。
80
+ const bundled = findBundledSkillsDir();
81
+ if (!bundled) {
82
+ // 没有 bundled skills 就是成功 noop
83
+ return ok(undefined);
84
+ }
85
+ try {
86
+ skillStore.syncBundledSkillsToUser(bundled);
87
+ }
88
+ catch (cause) {
89
+ return err(domainError('internal', 'SKILL_SYNC_FAIL', 'skill 同步失败', { cause }));
90
+ }
91
+ return ok(undefined);
92
+ }
93
+ async link(skill, project) {
94
+ if (!isValidSkillName(skill))
95
+ return err(invalidSkill());
96
+ if (!isValidProject(project))
97
+ return err(invalidProject());
98
+ const repo = findProjectRepoDir(project);
99
+ if (!repo)
100
+ return err(projectNotFound(project));
101
+ const { addSkillToProject } = await import('../core/skillStore.js');
102
+ const result = addSkillToProject(repo, skill);
103
+ if (result === 'skipped-absent') {
104
+ return err(domainError('not-found', 'SKILL_NOT_FOUND', `skill ${skill} 不在 user registry`));
105
+ }
106
+ // 已在工程中(symlink 或 frozen copy)→ 幂等 ok,不再 emit
107
+ if (result === 'skipped-linked' || result === 'skipped-frozen') {
108
+ return ok(undefined);
109
+ }
110
+ this.deps.events.emit({
111
+ type: 'skill.linked',
112
+ project,
113
+ skill,
114
+ ts: Date.now(),
115
+ });
116
+ return ok(undefined);
117
+ }
118
+ async unlink(skill, project) {
119
+ if (!isValidSkillName(skill))
120
+ return err(invalidSkill());
121
+ if (!isValidProject(project))
122
+ return err(invalidProject());
123
+ const repo = findProjectRepoDir(project);
124
+ if (!repo)
125
+ return err(projectNotFound(project));
126
+ const { removeSkillFromProject } = await import('../core/skillStore.js');
127
+ const removed = removeSkillFromProject(repo, skill);
128
+ if (!removed)
129
+ return ok(undefined); // idempotent
130
+ this.deps.events.emit({
131
+ type: 'skill.unlinked',
132
+ project,
133
+ skill,
134
+ ts: Date.now(),
135
+ });
136
+ return ok(undefined);
137
+ }
138
+ async freeze(skill, project) {
139
+ if (!isValidSkillName(skill))
140
+ return err(invalidSkill());
141
+ if (!isValidProject(project))
142
+ return err(invalidProject());
143
+ const repo = findProjectRepoDir(project);
144
+ if (!repo)
145
+ return err(projectNotFound(project));
146
+ const { freezeSkillInProject } = await import('../core/skillStore.js');
147
+ if (!freezeSkillInProject(repo, skill)) {
148
+ return err(domainError('conflict', 'FREEZE_FAIL', '冻结失败 —— 可能未 link'));
149
+ }
150
+ return ok(undefined);
151
+ }
152
+ async unfreeze(skill, project) {
153
+ if (!isValidSkillName(skill))
154
+ return err(invalidSkill());
155
+ if (!isValidProject(project))
156
+ return err(invalidProject());
157
+ const repo = findProjectRepoDir(project);
158
+ if (!repo)
159
+ return err(projectNotFound(project));
160
+ const { unfreezeSkillInProject } = await import('../core/skillStore.js');
161
+ if (!unfreezeSkillInProject(repo, skill)) {
162
+ return err(domainError('conflict', 'UNFREEZE_FAIL', '解冻失败 —— 可能未冻结'));
163
+ }
164
+ return ok(undefined);
165
+ }
166
+ }
167
+ // ─── Helpers ──────────────────────────────────────────────────────
168
+ function classifyCategory(name) {
169
+ const LANGUAGES = new Set(['python', 'typescript', 'golang', 'rust', 'kotlin', 'swift', 'java']);
170
+ const ENDS = new Set(['frontend', 'backend', 'mobile', 'database', 'devops']);
171
+ const PERSONAS = new Set([
172
+ 'backend-architect',
173
+ 'frontend-developer',
174
+ 'code-reviewer',
175
+ 'database-optimizer',
176
+ 'devops-automator',
177
+ 'security-engineer',
178
+ 'qa-tester',
179
+ ]);
180
+ const WORKFLOWS = new Set([
181
+ 'coding-standards',
182
+ 'tdd-workflow',
183
+ 'git-workflow',
184
+ 'architecture-decision-records',
185
+ 'debugging-workflow',
186
+ ]);
187
+ if (LANGUAGES.has(name))
188
+ return 'language';
189
+ if (ENDS.has(name))
190
+ return 'end';
191
+ if (PERSONAS.has(name))
192
+ return 'persona';
193
+ if (WORKFLOWS.has(name))
194
+ return 'workflow';
195
+ return 'other';
196
+ }
197
+ function readFrontmatter(path) {
198
+ if (!existsSync(path))
199
+ return { description: '', origin: '' };
200
+ try {
201
+ const raw = readFileSync(path, 'utf-8');
202
+ if (!raw.startsWith('---'))
203
+ return { description: '', origin: '' };
204
+ const end = raw.indexOf('\n---', 3);
205
+ if (end === -1)
206
+ return { description: '', origin: '' };
207
+ const yaml = raw.slice(3, end);
208
+ const desc = yaml.match(/^description:\s*(.+)$/m)?.[1] ?? '';
209
+ const origin = yaml.match(/^origin:\s*(.+)$/m)?.[1] ?? '';
210
+ return {
211
+ description: desc.trim().replace(/^["']|["']$/g, ''),
212
+ origin: origin.trim().replace(/^["']|["']$/g, ''),
213
+ };
214
+ }
215
+ catch {
216
+ return { description: '', origin: '' };
217
+ }
218
+ }
219
+ function readFileOrEmpty(path) {
220
+ try {
221
+ return readFileSync(path, 'utf-8');
222
+ }
223
+ catch {
224
+ return '';
225
+ }
226
+ }
227
+ function collectLinkedProjects(skillName) {
228
+ const root = projectsDir();
229
+ if (!existsSync(root))
230
+ return [];
231
+ const out = [];
232
+ const projects = readdirSync(root, { withFileTypes: true })
233
+ .filter((e) => e.isDirectory())
234
+ .map((e) => e.name);
235
+ for (const name of projects) {
236
+ const repo = findProjectRepoDir(name);
237
+ if (!repo || !existsSync(repo))
238
+ continue;
239
+ // Avoid importing skillStore here —— dynamic import in methods that need it
240
+ // 此 helper 仅供 list/get 内部预计算,用脏读直接 fs 判定:
241
+ const linked = resolve(repo, '.claude', 'skills', skillName);
242
+ if (existsSync(linked)) {
243
+ const frozen = existsSync(resolve(linked, '.frozen'));
244
+ out.push({ project: name, state: frozen ? 'frozen' : 'linked' });
245
+ }
246
+ }
247
+ return out;
248
+ }
249
+ function findProjectRepoDir(project) {
250
+ const confPath = projectConfFile(project);
251
+ if (!existsSync(confPath))
252
+ return null;
253
+ try {
254
+ const content = readFileSync(confPath, 'utf-8');
255
+ const match = content.match(/^(?:export\s+)?(?:PROJECT_DIR|REPO_DIR)=["']?([^"'\n]+)["']?/m);
256
+ return match ? match[1] ?? null : null;
257
+ }
258
+ catch {
259
+ return null;
260
+ }
261
+ }
262
+ function isValidProject(project) {
263
+ return typeof project === 'string' && /^[a-zA-Z0-9_-]+$/.test(project);
264
+ }
265
+ function isValidSkillName(name) {
266
+ return typeof name === 'string' && /^[a-zA-Z0-9_-]+$/.test(name);
267
+ }
268
+ function invalidSkill() {
269
+ return domainError('validation', 'INVALID_SKILL_NAME', 'skill 名非法');
270
+ }
271
+ function invalidProject() {
272
+ return domainError('validation', 'INVALID_PROJECT_NAME', '项目名非法');
273
+ }
274
+ function projectNotFound(name) {
275
+ return domainError('not-found', 'PROJECT_NOT_FOUND', `项目 ${name} 不存在`);
276
+ }
277
+ /**
278
+ * 定位仓库里的 `skills/` bundled 目录(project-template 模板同级)。
279
+ * 和 console-server/routes/skills.ts::findBundledSkillsDir 逻辑一致 ——
280
+ * Phase 3 迁移 Delivery 时再合并到 shared helper。
281
+ */
282
+ function findBundledSkillsDir() {
283
+ const candidates = [
284
+ resolve(process.cwd(), 'skills'),
285
+ // 从 src/services/SkillService.ts 往上数:services → src → workflow-cli → skills
286
+ resolve(__dirname, '..', '..', 'skills'),
287
+ ];
288
+ for (const c of candidates) {
289
+ try {
290
+ if (existsSync(c))
291
+ return c;
292
+ }
293
+ catch {
294
+ /* ignore */
295
+ }
296
+ }
297
+ return null;
298
+ }
299
+ // void —— 让 TypeScript 不要 warn userSkillsDir 没直接用
300
+ void userSkillsDir;
301
+ //# sourceMappingURL=SkillService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SkillService.js","sourceRoot":"","sources":["../../src/services/SkillService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ import type { Clock } from '../infra/clock.js';
2
+ import type { FileSystem } from '../infra/filesystem.js';
3
+ import type { ProcessSpawner } from '../infra/spawn.js';
4
+ import { type DomainError } from '../shared/errors.js';
5
+ import { type Result } from '../shared/result.js';
6
+ export interface SystemInfo {
7
+ readonly version: string;
8
+ readonly nodeVersion: string;
9
+ readonly startedAt: string;
10
+ readonly uptimeMs: number;
11
+ readonly platform: string;
12
+ readonly pid: number;
13
+ }
14
+ export interface EnvEntry {
15
+ readonly key: string;
16
+ readonly value: string;
17
+ readonly masked: boolean;
18
+ }
19
+ export interface EnvListing {
20
+ readonly path: string;
21
+ readonly exists: boolean;
22
+ readonly entries: EnvEntry[];
23
+ }
24
+ export interface EnvRaw {
25
+ readonly path: string;
26
+ readonly exists: boolean;
27
+ readonly content: string;
28
+ readonly etag: string;
29
+ }
30
+ export interface LatestVersion {
31
+ readonly current: string;
32
+ readonly latest: string;
33
+ readonly upToDate: boolean;
34
+ }
35
+ export interface UpgradeResult {
36
+ readonly ok: boolean;
37
+ readonly output: string;
38
+ }
39
+ export interface DoctorReport {
40
+ readonly project: string;
41
+ readonly issues: string[];
42
+ readonly ok: boolean;
43
+ }
44
+ export interface SystemServiceDeps {
45
+ readonly fs: FileSystem;
46
+ readonly clock: Clock;
47
+ readonly spawner: ProcessSpawner;
48
+ readonly version: string;
49
+ readonly startedAt: Date;
50
+ }
51
+ export declare class SystemService {
52
+ private readonly deps;
53
+ constructor(deps: SystemServiceDeps);
54
+ info(): SystemInfo;
55
+ readEnv(): Promise<Result<EnvListing, DomainError>>;
56
+ readEnvRaw(): Promise<Result<EnvRaw, DomainError>>;
57
+ writeEnv(content: string, etag: string | undefined): Promise<Result<{
58
+ etag: string;
59
+ }, DomainError>>;
60
+ latestVersion(): Promise<Result<LatestVersion, DomainError>>;
61
+ /** 执行 npm i -g —— pipeline 在跑时拒绝以保证原子性。 */
62
+ upgrade(): Promise<Result<UpgradeResult, DomainError>>;
63
+ doctorAll(): Promise<Result<DoctorReport[], DomainError>>;
64
+ /** 列出当前在跑 pipeline 的项目名 —— upgrade 前置检查用 */
65
+ private listRunningPipelines;
66
+ }
67
+ //# sourceMappingURL=SystemService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SystemService.d.ts","sourceRoot":"","sources":["../../src/services/SystemService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,278 @@
1
+ /**
2
+ * @module services/SystemService
3
+ * @description 系统级信息 + ~/.coral/env CRUD + npm upgrade + doctor
4
+ *
5
+ * @layer services
6
+ */
7
+ import { spawn } from 'node:child_process';
8
+ import { createHash } from 'node:crypto';
9
+ import { chmodSync } from 'node:fs';
10
+ import { domainError } from '../shared/errors.js';
11
+ import { err, ok } from '../shared/result.js';
12
+ import { globalEnvFile, projectConfFile, projectsDir, runtimeDir, supervisorPidFile, WorkerMarkerFilenameRe, } from '../shared/runtimePaths.js';
13
+ import { resolve } from 'node:path';
14
+ const SECRET_KEY_PATTERNS = [
15
+ /_TOKEN$/,
16
+ /_KEY$/,
17
+ /_SECRET$/,
18
+ /_PASSWORD$/,
19
+ /_PASS$/,
20
+ /^(ANTHROPIC|OPENAI|CLAUDE|PLANE|TRELLO|MATRIX)_/,
21
+ ];
22
+ export class SystemService {
23
+ deps;
24
+ constructor(deps) {
25
+ this.deps = deps;
26
+ }
27
+ info() {
28
+ return {
29
+ version: this.deps.version,
30
+ nodeVersion: process.version,
31
+ startedAt: this.deps.startedAt.toISOString(),
32
+ uptimeMs: this.deps.clock.now() - this.deps.startedAt.getTime(),
33
+ platform: process.platform,
34
+ pid: process.pid,
35
+ };
36
+ }
37
+ async readEnv() {
38
+ const path = globalEnvFile();
39
+ if (!this.deps.fs.exists(path)) {
40
+ return ok({ path, exists: false, entries: [] });
41
+ }
42
+ try {
43
+ const raw = this.deps.fs.readFile(path);
44
+ const entries = [];
45
+ for (const line of raw.split('\n')) {
46
+ const t = line.trim();
47
+ if (!t || t.startsWith('#'))
48
+ continue;
49
+ const m = t.match(/^(?:export\s+)?([A-Z_][A-Z0-9_]*)=["']?(.*?)["']?\s*$/);
50
+ if (!m)
51
+ continue;
52
+ const key = m[1] ?? '';
53
+ const value = m[2] ?? '';
54
+ const masked = isSecret(key);
55
+ entries.push({ key, value: masked ? maskSecret(value) : value, masked });
56
+ }
57
+ return ok({ path, exists: true, entries });
58
+ }
59
+ catch (cause) {
60
+ return err(domainError('internal', 'ENV_READ_FAIL', 'env 文件读取失败', { cause }));
61
+ }
62
+ }
63
+ async readEnvRaw() {
64
+ const path = globalEnvFile();
65
+ if (!this.deps.fs.exists(path)) {
66
+ return ok({ path, exists: false, content: '', etag: '' });
67
+ }
68
+ try {
69
+ const content = this.deps.fs.readFile(path);
70
+ return ok({ path, exists: true, content, etag: hashEtag(content) });
71
+ }
72
+ catch (cause) {
73
+ return err(domainError('internal', 'ENV_READ_FAIL', 'env 文件读取失败', { cause }));
74
+ }
75
+ }
76
+ async writeEnv(content, etag) {
77
+ const path = globalEnvFile();
78
+ const exists = this.deps.fs.exists(path);
79
+ if (exists) {
80
+ if (!etag) {
81
+ return err(domainError('validation', 'ETAG_REQUIRED', 'etag 必填(已有文件)'));
82
+ }
83
+ const currentContent = this.deps.fs.readFile(path);
84
+ const currentEtag = hashEtag(currentContent);
85
+ if (etag !== currentEtag) {
86
+ return err(domainError('conflict', 'ENV_ETAG_MISMATCH', 'env 已被其它编辑修改,请重新加载', { details: { currentEtag } }));
87
+ }
88
+ }
89
+ try {
90
+ this.deps.fs.writeFileAtomic(path, content);
91
+ // 保持 0600 权限 —— env 常含 secret
92
+ try {
93
+ chmodSync(path, 0o600);
94
+ }
95
+ catch {
96
+ /* best effort */
97
+ }
98
+ }
99
+ catch (cause) {
100
+ return err(domainError('internal', 'ENV_WRITE_FAIL', 'env 写入失败', { cause }));
101
+ }
102
+ return ok({ etag: hashEtag(content) });
103
+ }
104
+ async latestVersion() {
105
+ // `spawner` is wired as ProcessSpawner for sps CLI; `npm view` is a different
106
+ // binary so we spawn it directly (still isolated to the service layer).
107
+ return new Promise((resolvePromise) => {
108
+ const child = spawn('npm', ['view', '@coralai/sps-cli', 'version'], {
109
+ stdio: ['ignore', 'pipe', 'pipe'],
110
+ });
111
+ let stdout = '';
112
+ let stderr = '';
113
+ const timer = setTimeout(() => {
114
+ try {
115
+ child.kill('SIGKILL');
116
+ }
117
+ catch {
118
+ /* noop */
119
+ }
120
+ resolvePromise(err(domainError('external', 'NPM_VIEW_TIMEOUT', 'npm view 超时 (10s)', {
121
+ details: { timeoutMs: 10_000 },
122
+ })));
123
+ }, 10_000);
124
+ child.stdout?.on('data', (d) => (stdout += d.toString()));
125
+ child.stderr?.on('data', (d) => (stderr += d.toString()));
126
+ child.on('close', (code) => {
127
+ clearTimeout(timer);
128
+ if (code === 0) {
129
+ const latest = stdout.trim();
130
+ resolvePromise(ok({
131
+ current: this.deps.version,
132
+ latest,
133
+ upToDate: this.deps.version === latest,
134
+ }));
135
+ }
136
+ else {
137
+ resolvePromise(err(domainError('external', 'NPM_VIEW_FAIL', 'npm view 失败', {
138
+ details: { stderr: stderr.trim(), exitCode: code },
139
+ })));
140
+ }
141
+ });
142
+ child.on('error', (e) => {
143
+ clearTimeout(timer);
144
+ resolvePromise(err(domainError('external', 'NPM_VIEW_ERROR', 'npm view 出错', {
145
+ cause: e,
146
+ details: { message: e.message },
147
+ })));
148
+ });
149
+ });
150
+ }
151
+ /** 执行 npm i -g —— pipeline 在跑时拒绝以保证原子性。 */
152
+ async upgrade() {
153
+ const running = this.listRunningPipelines();
154
+ if (running.length > 0) {
155
+ return err(domainError('conflict', 'PIPELINES_RUNNING', '有 pipeline 在跑,升级前请先停止', {
156
+ details: { projects: running },
157
+ }));
158
+ }
159
+ return new Promise((resolvePromise) => {
160
+ const child = spawn('npm', ['i', '-g', '@coralai/sps-cli@latest'], {
161
+ stdio: ['ignore', 'pipe', 'pipe'],
162
+ });
163
+ let output = '';
164
+ const timer = setTimeout(() => {
165
+ try {
166
+ child.kill('SIGKILL');
167
+ }
168
+ catch {
169
+ /* noop */
170
+ }
171
+ resolvePromise(ok({ ok: false, output: output + '\n[timeout 120s]' }));
172
+ }, 120_000);
173
+ child.stdout?.on('data', (d) => (output += d.toString()));
174
+ child.stderr?.on('data', (d) => (output += d.toString()));
175
+ child.on('close', (code) => {
176
+ clearTimeout(timer);
177
+ resolvePromise(ok({ ok: code === 0, output }));
178
+ });
179
+ child.on('error', (e) => {
180
+ clearTimeout(timer);
181
+ resolvePromise(ok({ ok: false, output: output + `\n[spawn error: ${e.message}]` }));
182
+ });
183
+ });
184
+ }
185
+ async doctorAll() {
186
+ const root = projectsDir();
187
+ if (!this.deps.fs.exists(root)) {
188
+ return ok([]);
189
+ }
190
+ let names;
191
+ try {
192
+ names = this.deps.fs.readDir(root).filter((e) => e.isDirectory).map((e) => e.name);
193
+ }
194
+ catch (cause) {
195
+ return err(domainError('internal', 'PROJECTS_READ_FAIL', 'projects 目录读取失败', { cause }));
196
+ }
197
+ const now = this.deps.clock.now();
198
+ const report = [];
199
+ for (const name of names) {
200
+ const issues = [];
201
+ if (!this.deps.fs.exists(projectConfFile(name)))
202
+ issues.push('missing conf');
203
+ const cardsDir = resolve(root, name, 'cards');
204
+ if (!this.deps.fs.exists(cardsDir))
205
+ issues.push('missing cards/');
206
+ const runtime = runtimeDir(name);
207
+ if (this.deps.fs.exists(runtime)) {
208
+ try {
209
+ const markers = this.deps.fs
210
+ .readDir(runtime)
211
+ .filter((e) => e.isFile && WorkerMarkerFilenameRe.test(e.name));
212
+ for (const mf of markers) {
213
+ const stat = this.deps.fs.stat(resolve(runtime, mf.name));
214
+ if (stat) {
215
+ const ageMin = Math.floor((now - stat.mtimeMs) / 60000);
216
+ if (ageMin > 60)
217
+ issues.push(`stale marker ${mf.name} (${ageMin}m)`);
218
+ }
219
+ }
220
+ }
221
+ catch {
222
+ /* ignore */
223
+ }
224
+ }
225
+ report.push({ project: name, issues, ok: issues.length === 0 });
226
+ }
227
+ return ok(report);
228
+ }
229
+ /** 列出当前在跑 pipeline 的项目名 —— upgrade 前置检查用 */
230
+ listRunningPipelines() {
231
+ const root = projectsDir();
232
+ if (!this.deps.fs.exists(root))
233
+ return [];
234
+ const out = [];
235
+ try {
236
+ const names = this.deps.fs.readDir(root).filter((e) => e.isDirectory).map((e) => e.name);
237
+ for (const name of names) {
238
+ const pidFile = supervisorPidFile(name);
239
+ if (!this.deps.fs.exists(pidFile))
240
+ continue;
241
+ try {
242
+ const pid = Number.parseInt(this.deps.fs.readFile(pidFile).trim(), 10);
243
+ if (pid > 0) {
244
+ try {
245
+ process.kill(pid, 0);
246
+ out.push(name);
247
+ }
248
+ catch {
249
+ /* dead pid */
250
+ }
251
+ }
252
+ }
253
+ catch {
254
+ /* unreadable */
255
+ }
256
+ }
257
+ }
258
+ catch {
259
+ /* ignore */
260
+ }
261
+ return out;
262
+ }
263
+ }
264
+ // ─── Helpers ──────────────────────────────────────────────────────
265
+ function maskSecret(value) {
266
+ if (!value)
267
+ return '';
268
+ if (value.length <= 6)
269
+ return '****';
270
+ return value.slice(0, 4) + '****';
271
+ }
272
+ function isSecret(key) {
273
+ return SECRET_KEY_PATTERNS.some((p) => p.test(key));
274
+ }
275
+ function hashEtag(content) {
276
+ return createHash('sha256').update(content).digest('hex').slice(0, 16);
277
+ }
278
+ //# sourceMappingURL=SystemService.js.map