peaks-cli 1.0.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/LICENSE +52 -0
- package/README.md +417 -0
- package/bin/peaks.js +2 -0
- package/dist/src/cli/cli-helpers.d.ts +25 -0
- package/dist/src/cli/cli-helpers.js +78 -0
- package/dist/src/cli/commands/capability-commands.d.ts +5 -0
- package/dist/src/cli/commands/capability-commands.js +46 -0
- package/dist/src/cli/commands/capability-worker-config-sc-commands.d.ts +3 -0
- package/dist/src/cli/commands/capability-worker-config-sc-commands.js +10 -0
- package/dist/src/cli/commands/config-commands.d.ts +3 -0
- package/dist/src/cli/commands/config-commands.js +212 -0
- package/dist/src/cli/commands/core-artifact-commands.d.ts +3 -0
- package/dist/src/cli/commands/core-artifact-commands.js +200 -0
- package/dist/src/cli/commands/sc-commands.d.ts +3 -0
- package/dist/src/cli/commands/sc-commands.js +37 -0
- package/dist/src/cli/commands/worker-commands.d.ts +3 -0
- package/dist/src/cli/commands/worker-commands.js +52 -0
- package/dist/src/cli/commands/workflow-commands.d.ts +3 -0
- package/dist/src/cli/commands/workflow-commands.js +257 -0
- package/dist/src/cli/index.d.ts +1 -0
- package/dist/src/cli/index.js +14 -0
- package/dist/src/cli/program.d.ts +4 -0
- package/dist/src/cli/program.js +13 -0
- package/dist/src/services/artifacts/artifact-service.d.ts +43 -0
- package/dist/src/services/artifacts/artifact-service.js +97 -0
- package/dist/src/services/artifacts/workspace-service.d.ts +33 -0
- package/dist/src/services/artifacts/workspace-service.js +254 -0
- package/dist/src/services/config/config-service.d.ts +29 -0
- package/dist/src/services/config/config-service.js +501 -0
- package/dist/src/services/config/config-types.d.ts +63 -0
- package/dist/src/services/config/config-types.js +16 -0
- package/dist/src/services/config/model-routing.d.ts +4 -0
- package/dist/src/services/config/model-routing.js +15 -0
- package/dist/src/services/doctor/doctor-service.d.ts +18 -0
- package/dist/src/services/doctor/doctor-service.js +68 -0
- package/dist/src/services/memory/project-memory-service.d.ts +79 -0
- package/dist/src/services/memory/project-memory-service.js +306 -0
- package/dist/src/services/profiles/profile-service.d.ts +6 -0
- package/dist/src/services/profiles/profile-service.js +19 -0
- package/dist/src/services/providers/minimax-provider-service.d.ts +24 -0
- package/dist/src/services/providers/minimax-provider-service.js +143 -0
- package/dist/src/services/providers/minimax-worker-service.d.ts +21 -0
- package/dist/src/services/providers/minimax-worker-service.js +80 -0
- package/dist/src/services/proxy/proxy-service.d.ts +7 -0
- package/dist/src/services/proxy/proxy-service.js +31 -0
- package/dist/src/services/rd/rd-service.d.ts +88 -0
- package/dist/src/services/rd/rd-service.js +370 -0
- package/dist/src/services/recommendations/capability-availability.d.ts +5 -0
- package/dist/src/services/recommendations/capability-availability.js +40 -0
- package/dist/src/services/recommendations/capability-map-service.d.ts +7 -0
- package/dist/src/services/recommendations/capability-map-service.js +131 -0
- package/dist/src/services/recommendations/capability-seed-items.d.ts +2 -0
- package/dist/src/services/recommendations/capability-seed-items.js +131 -0
- package/dist/src/services/recommendations/capability-seed-mappings.d.ts +2 -0
- package/dist/src/services/recommendations/capability-seed-mappings.js +42 -0
- package/dist/src/services/recommendations/capability-seed-sources.d.ts +2 -0
- package/dist/src/services/recommendations/capability-seed-sources.js +35 -0
- package/dist/src/services/recommendations/recommendation-service.d.ts +8 -0
- package/dist/src/services/recommendations/recommendation-service.js +106 -0
- package/dist/src/services/recommendations/recommendation-types.d.ts +129 -0
- package/dist/src/services/recommendations/recommendation-types.js +1 -0
- package/dist/src/services/recommendations/seed-capability-catalog.d.ts +3 -0
- package/dist/src/services/recommendations/seed-capability-catalog.js +3 -0
- package/dist/src/services/refactor/refactor-service.d.ts +9 -0
- package/dist/src/services/refactor/refactor-service.js +33 -0
- package/dist/src/services/sc/index.d.ts +1 -0
- package/dist/src/services/sc/index.js +1 -0
- package/dist/src/services/sc/sc-service.d.ts +79 -0
- package/dist/src/services/sc/sc-service.js +223 -0
- package/dist/src/services/skills/skill-registry.d.ts +17 -0
- package/dist/src/services/skills/skill-registry.js +40 -0
- package/dist/src/services/standards/project-standards-service.d.ts +82 -0
- package/dist/src/services/standards/project-standards-service.js +383 -0
- package/dist/src/services/tech/tech-service.d.ts +69 -0
- package/dist/src/services/tech/tech-service.js +236 -0
- package/dist/src/services/workflow/workflow-autonomous-service.d.ts +99 -0
- package/dist/src/services/workflow/workflow-autonomous-service.js +526 -0
- package/dist/src/services/workflow/workflow-router-service.d.ts +85 -0
- package/dist/src/services/workflow/workflow-router-service.js +213 -0
- package/dist/src/shared/change-id.d.ts +15 -0
- package/dist/src/shared/change-id.js +76 -0
- package/dist/src/shared/frontmatter.d.ts +6 -0
- package/dist/src/shared/frontmatter.js +47 -0
- package/dist/src/shared/fs-utils.d.ts +4 -0
- package/dist/src/shared/fs-utils.js +16 -0
- package/dist/src/shared/fs.d.ts +4 -0
- package/dist/src/shared/fs.js +26 -0
- package/dist/src/shared/path-utils.d.ts +13 -0
- package/dist/src/shared/path-utils.js +56 -0
- package/dist/src/shared/paths.d.ts +6 -0
- package/dist/src/shared/paths.js +40 -0
- package/dist/src/shared/planner-response.d.ts +21 -0
- package/dist/src/shared/planner-response.js +26 -0
- package/dist/src/shared/platform.d.ts +6 -0
- package/dist/src/shared/platform.js +11 -0
- package/dist/src/shared/process.d.ts +5 -0
- package/dist/src/shared/process.js +12 -0
- package/dist/src/shared/result.d.ts +13 -0
- package/dist/src/shared/result.js +32 -0
- package/package.json +49 -0
- package/schemas/approval-record.schema.json +14 -0
- package/schemas/artifact-manifest.schema.json +16 -0
- package/schemas/artifact-retention-report.schema.json +17 -0
- package/schemas/artifact-workspace.schema.json +22 -0
- package/schemas/capability-availability.schema.json +36 -0
- package/schemas/capability-item.schema.json +37 -0
- package/schemas/capability-source.schema.json +30 -0
- package/schemas/change-impact.schema.json +15 -0
- package/schemas/context-capsule.schema.json +16 -0
- package/schemas/recommendation-plan.schema.json +37 -0
- package/schemas/refactor-slice-spec.schema.json +19 -0
- package/scripts/clean-dist.mjs +8 -0
- package/scripts/install-skills.mjs +76 -0
- package/scripts/watch.mjs +389 -0
- package/skills/peaks-prd/SKILL.md +42 -0
- package/skills/peaks-prd/references/artifact-contracts.md +3 -0
- package/skills/peaks-prd/references/command-migration.md +3 -0
- package/skills/peaks-prd/references/workflow.md +11 -0
- package/skills/peaks-qa/SKILL.md +45 -0
- package/skills/peaks-qa/references/artifact-contracts.md +3 -0
- package/skills/peaks-qa/references/command-migration.md +3 -0
- package/skills/peaks-qa/references/regression-gates.md +16 -0
- package/skills/peaks-rd/SKILL.md +56 -0
- package/skills/peaks-rd/references/artifact-contracts.md +3 -0
- package/skills/peaks-rd/references/command-migration.md +3 -0
- package/skills/peaks-rd/references/refactor-workflow.md +31 -0
- package/skills/peaks-sc/SKILL.md +30 -0
- package/skills/peaks-sc/references/artifact-contracts.md +3 -0
- package/skills/peaks-sc/references/artifact-retention.md +14 -0
- package/skills/peaks-sc/references/command-migration.md +3 -0
- package/skills/peaks-solo/SKILL.md +63 -0
- package/skills/peaks-solo/references/artifact-contracts.md +3 -0
- package/skills/peaks-solo/references/command-migration.md +3 -0
- package/skills/peaks-solo/references/refactor-mode.md +22 -0
- package/skills/peaks-solo/references/workflow.md +14 -0
- package/skills/peaks-txt/SKILL.md +48 -0
- package/skills/peaks-txt/references/artifact-contracts.md +3 -0
- package/skills/peaks-txt/references/command-migration.md +3 -0
- package/skills/peaks-txt/references/context-capsule.md +20 -0
- package/skills/peaks-ui/SKILL.md +35 -0
- package/skills/peaks-ui/references/artifact-contracts.md +3 -0
- package/skills/peaks-ui/references/command-migration.md +3 -0
- package/skills/peaks-ui/references/workflow.md +11 -0
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, isAbsolute, relative, resolve } from 'node:path';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { DEFAULT_CONFIG } from './config-types.js';
|
|
5
|
+
function getUserConfigPath() {
|
|
6
|
+
return resolve(homedir(), '.peaks', 'config.json');
|
|
7
|
+
}
|
|
8
|
+
function isInsidePath(childPath, parentPath) {
|
|
9
|
+
const relativePath = relative(parentPath, childPath);
|
|
10
|
+
return relativePath === '' || (!relativePath.startsWith('..') && !isAbsolute(relativePath));
|
|
11
|
+
}
|
|
12
|
+
function isSafeProjectConfigMarker(projectRoot) {
|
|
13
|
+
const peaksPath = resolve(projectRoot, '.peaks');
|
|
14
|
+
const markerPath = resolve(peaksPath, 'config.json');
|
|
15
|
+
try {
|
|
16
|
+
const projectRootReal = realpathSync(projectRoot);
|
|
17
|
+
const peaksReal = realpathSync(peaksPath);
|
|
18
|
+
const markerReal = realpathSync(markerPath);
|
|
19
|
+
if (!isInsidePath(peaksReal, projectRootReal))
|
|
20
|
+
return false;
|
|
21
|
+
if (!isInsidePath(markerReal, projectRootReal))
|
|
22
|
+
return false;
|
|
23
|
+
return isInsidePath(markerReal, peaksReal);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function findProjectRoot(startPath) {
|
|
30
|
+
let current = resolve(startPath);
|
|
31
|
+
let parent = dirname(current);
|
|
32
|
+
while (current !== parent) {
|
|
33
|
+
if (existsSync(resolve(current, '.peaks', 'config.json')) && isSafeProjectConfigMarker(current)) {
|
|
34
|
+
return current;
|
|
35
|
+
}
|
|
36
|
+
parent = current;
|
|
37
|
+
current = dirname(parent);
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
function getProjectConfigPath(projectRoot) {
|
|
42
|
+
if (!projectRoot)
|
|
43
|
+
return null;
|
|
44
|
+
if (!isSafeProjectConfigMarker(projectRoot))
|
|
45
|
+
return null;
|
|
46
|
+
return resolve(projectRoot, '.peaks', 'config.json');
|
|
47
|
+
}
|
|
48
|
+
function readJsonFile(path) {
|
|
49
|
+
if (!path || !existsSync(path))
|
|
50
|
+
return null;
|
|
51
|
+
try {
|
|
52
|
+
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function ensureDir(dirPath) {
|
|
59
|
+
if (!existsSync(dirPath)) {
|
|
60
|
+
mkdirSync(dirPath, { recursive: true });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const UNSAFE_NESTED_PATH_SEGMENTS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
64
|
+
function getNestedPathParts(path) {
|
|
65
|
+
return path.replace(/\[(\d+)\]/g, '.$1').split('.').filter(Boolean);
|
|
66
|
+
}
|
|
67
|
+
function hasUnsafeNestedPathSegment(parts) {
|
|
68
|
+
return parts.some((part) => UNSAFE_NESTED_PATH_SEGMENTS.has(part));
|
|
69
|
+
}
|
|
70
|
+
function getNestedValue(obj, path) {
|
|
71
|
+
const parts = getNestedPathParts(path);
|
|
72
|
+
if (parts.length === 0 || hasUnsafeNestedPathSegment(parts)) {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
let current = obj;
|
|
76
|
+
for (const part of parts) {
|
|
77
|
+
if (current === null || current === undefined || typeof current !== 'object' || !Object.prototype.hasOwnProperty.call(current, part)) {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
current = current[part];
|
|
81
|
+
}
|
|
82
|
+
return current;
|
|
83
|
+
}
|
|
84
|
+
function setNestedValue(obj, path, value) {
|
|
85
|
+
const parts = getNestedPathParts(path);
|
|
86
|
+
if (parts.length === 0 || hasUnsafeNestedPathSegment(parts)) {
|
|
87
|
+
throw new Error('Unsafe config path');
|
|
88
|
+
}
|
|
89
|
+
let current = obj;
|
|
90
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
91
|
+
const part = parts[i];
|
|
92
|
+
if (!Object.prototype.hasOwnProperty.call(current, part) || typeof current[part] !== 'object' || current[part] === null || Array.isArray(current[part])) {
|
|
93
|
+
current[part] = {};
|
|
94
|
+
}
|
|
95
|
+
current = current[part];
|
|
96
|
+
}
|
|
97
|
+
const last = parts[parts.length - 1];
|
|
98
|
+
current[last] = value;
|
|
99
|
+
}
|
|
100
|
+
function removeProjectProviderSecrets(config) {
|
|
101
|
+
const { providers, ...safeConfig } = config;
|
|
102
|
+
return safeConfig;
|
|
103
|
+
}
|
|
104
|
+
export function isConfigLayer(value) {
|
|
105
|
+
return value === 'user' || value === 'project';
|
|
106
|
+
}
|
|
107
|
+
export function isSensitiveConfigPath(path) {
|
|
108
|
+
const normalized = path.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
109
|
+
return normalized.includes('apikey') || normalized.includes('accesskey') || normalized.includes('privatekey') || normalized.includes('token') || normalized.includes('secret') || normalized.includes('password') || normalized.includes('bearer') || normalized.includes('credential') || normalized.includes('auth');
|
|
110
|
+
}
|
|
111
|
+
function isProviderConfigPath(path) {
|
|
112
|
+
return path === 'providers' || path.startsWith('providers.');
|
|
113
|
+
}
|
|
114
|
+
function isSecretKey(key) {
|
|
115
|
+
return isSensitiveConfigPath(key);
|
|
116
|
+
}
|
|
117
|
+
function sanitizeBaseUrlForDisplay(value) {
|
|
118
|
+
try {
|
|
119
|
+
const url = new URL(value);
|
|
120
|
+
url.username = '';
|
|
121
|
+
url.password = '';
|
|
122
|
+
url.search = '';
|
|
123
|
+
url.hash = '';
|
|
124
|
+
return url.toString();
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return '[invalid-url-redacted]';
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const MINIMAX_API_HOST = 'api.minimaxi.com';
|
|
131
|
+
function isProviderBaseUrlPath(path) {
|
|
132
|
+
return /^providers\.[^.]+\.baseUrl$/.test(path);
|
|
133
|
+
}
|
|
134
|
+
function isValidProviderBaseUrl(value) {
|
|
135
|
+
try {
|
|
136
|
+
const url = new URL(value);
|
|
137
|
+
return url.protocol === 'https:' && url.username.length === 0 && url.password.length === 0 && url.search.length === 0 && url.hash.length === 0;
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function isValidMiniMaxBaseUrl(value) {
|
|
144
|
+
try {
|
|
145
|
+
const url = new URL(value);
|
|
146
|
+
return url.protocol === 'https:' && url.hostname === MINIMAX_API_HOST && url.username.length === 0 && url.password.length === 0 && url.search.length === 0 && url.hash.length === 0;
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function getMiniMaxBaseUrlCandidate(key, value) {
|
|
153
|
+
if (key === 'providers.minimax.baseUrl') {
|
|
154
|
+
return value;
|
|
155
|
+
}
|
|
156
|
+
if (key === 'providers.minimax' && value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
157
|
+
return value.baseUrl;
|
|
158
|
+
}
|
|
159
|
+
if (key === 'providers' && value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
160
|
+
return value.minimax?.baseUrl;
|
|
161
|
+
}
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
function validateProviderBaseUrl(value) {
|
|
165
|
+
if (value !== undefined && (typeof value !== 'string' || !isValidProviderBaseUrl(value))) {
|
|
166
|
+
throw new Error('Provider base URL must be HTTPS without embedded credentials, query, or fragment');
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function validateMiniMaxBaseUrl(value) {
|
|
170
|
+
if (value !== undefined && (typeof value !== 'string' || !isValidMiniMaxBaseUrl(value))) {
|
|
171
|
+
throw new Error('MiniMax base URL must be the MiniMax HTTPS endpoint without embedded credentials');
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function getProxyUrlCandidate(key, value) {
|
|
175
|
+
if (key === 'proxy.httpProxy') {
|
|
176
|
+
return value;
|
|
177
|
+
}
|
|
178
|
+
if (key === 'proxy' && isRecord(value)) {
|
|
179
|
+
return value.httpProxy;
|
|
180
|
+
}
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
function isProxyConfigPath(path) {
|
|
184
|
+
return path === 'proxy' || path.startsWith('proxy.');
|
|
185
|
+
}
|
|
186
|
+
function validateModelProviderConfig(providers) {
|
|
187
|
+
validateMiniMaxBaseUrl(providers.minimax?.baseUrl);
|
|
188
|
+
for (const [providerId, provider] of Object.entries(providers)) {
|
|
189
|
+
if (providerId !== 'minimax') {
|
|
190
|
+
validateProviderBaseUrl(provider?.baseUrl);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function validateProviderConfig(partial) {
|
|
195
|
+
validateModelProviderConfig(partial.providers ?? {});
|
|
196
|
+
}
|
|
197
|
+
function isValidProxyUrl(value) {
|
|
198
|
+
try {
|
|
199
|
+
const url = new URL(value);
|
|
200
|
+
return (url.protocol === 'http:' || url.protocol === 'https:') && url.username.length === 0 && url.password.length === 0 && url.pathname === '/' && url.search.length === 0 && url.hash.length === 0;
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function validateProxyUrl(value) {
|
|
207
|
+
if (value !== undefined && (typeof value !== 'string' || !isValidProxyUrl(value))) {
|
|
208
|
+
throw new Error('Proxy URL must be an HTTP or HTTPS URL without embedded credentials');
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function validateProxyConfig(partial) {
|
|
212
|
+
validateProxyUrl(partial.proxy?.httpProxy);
|
|
213
|
+
}
|
|
214
|
+
function isRecord(value) {
|
|
215
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
216
|
+
}
|
|
217
|
+
function toWorkspaceConfig(value) {
|
|
218
|
+
if (!isRecord(value))
|
|
219
|
+
return null;
|
|
220
|
+
const { workspaceId, name, rootPath, installedCapabilityIds } = value;
|
|
221
|
+
if (typeof workspaceId !== 'string' || typeof name !== 'string' || typeof rootPath !== 'string' || !Array.isArray(installedCapabilityIds) || !installedCapabilityIds.every((id) => typeof id === 'string')) {
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
let artifactRepo;
|
|
225
|
+
if (isRecord(value.artifactRepo) && (value.artifactRepo.provider === 'github' || value.artifactRepo.provider === 'gitlab') && typeof value.artifactRepo.owner === 'string' && typeof value.artifactRepo.name === 'string') {
|
|
226
|
+
artifactRepo = { provider: value.artifactRepo.provider, owner: value.artifactRepo.owner, name: value.artifactRepo.name };
|
|
227
|
+
}
|
|
228
|
+
return artifactRepo ? { workspaceId, name, rootPath, installedCapabilityIds, artifactRepo } : { workspaceId, name, rootPath, installedCapabilityIds };
|
|
229
|
+
}
|
|
230
|
+
function toWorkspaceConfigs(value) {
|
|
231
|
+
return Array.isArray(value) ? value.map(toWorkspaceConfig).filter((workspace) => workspace !== null) : [];
|
|
232
|
+
}
|
|
233
|
+
function toProviderModelConfig(value) {
|
|
234
|
+
if (!isRecord(value))
|
|
235
|
+
return {};
|
|
236
|
+
return {
|
|
237
|
+
...(typeof value.model === 'string' && value.model.trim().length > 0 ? { model: value.model.trim() } : {}),
|
|
238
|
+
...(typeof value.baseUrl === 'string' ? { baseUrl: value.baseUrl } : {}),
|
|
239
|
+
...(typeof value.apiKey === 'string' ? { apiKey: value.apiKey } : {})
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function toMiniMaxProviderConfig(value) {
|
|
243
|
+
return toProviderModelConfig(value);
|
|
244
|
+
}
|
|
245
|
+
const TOKEN_CONFIG_KEYS = new Set(['AnthropicApiKey', 'OpenAiApiKey', 'GitHubToken', 'GitLabToken']);
|
|
246
|
+
function toTokenRef(value) {
|
|
247
|
+
if (!isRecord(value))
|
|
248
|
+
return null;
|
|
249
|
+
const env = typeof value.env === 'string' ? value.env.trim() : '';
|
|
250
|
+
const keychain = typeof value.keychain === 'string' ? value.keychain.trim() : '';
|
|
251
|
+
if (env.length > 0) {
|
|
252
|
+
return { env };
|
|
253
|
+
}
|
|
254
|
+
if (keychain.length > 0) {
|
|
255
|
+
return { keychain };
|
|
256
|
+
}
|
|
257
|
+
if (value.ghCli === true) {
|
|
258
|
+
return { ghCli: true };
|
|
259
|
+
}
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
function toTokenConfig(value) {
|
|
263
|
+
if (!isRecord(value))
|
|
264
|
+
return {};
|
|
265
|
+
const tokens = {};
|
|
266
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
267
|
+
if (!TOKEN_CONFIG_KEYS.has(key))
|
|
268
|
+
continue;
|
|
269
|
+
const tokenRef = toTokenRef(entry);
|
|
270
|
+
if (tokenRef) {
|
|
271
|
+
tokens[key] = tokenRef;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return tokens;
|
|
275
|
+
}
|
|
276
|
+
function toModelProviderConfig(value) {
|
|
277
|
+
if (!isRecord(value))
|
|
278
|
+
return {};
|
|
279
|
+
return Object.fromEntries(Object.entries(value).map(([providerId, providerConfig]) => [providerId, toProviderModelConfig(providerConfig)]));
|
|
280
|
+
}
|
|
281
|
+
function toProxyConfig(value) {
|
|
282
|
+
if (!isRecord(value))
|
|
283
|
+
return null;
|
|
284
|
+
return typeof value.httpProxy === 'string' && isValidProxyUrl(value.httpProxy) ? { httpProxy: value.httpProxy } : null;
|
|
285
|
+
}
|
|
286
|
+
function getProjectWritePath() {
|
|
287
|
+
const projectPath = getProjectConfigPath(findProjectRoot(process.cwd()));
|
|
288
|
+
if (!projectPath) {
|
|
289
|
+
throw new Error('Project config not found');
|
|
290
|
+
}
|
|
291
|
+
return projectPath;
|
|
292
|
+
}
|
|
293
|
+
export function containsSensitiveConfigValue(value) {
|
|
294
|
+
if (Array.isArray(value)) {
|
|
295
|
+
return value.some(containsSensitiveConfigValue);
|
|
296
|
+
}
|
|
297
|
+
if (value === null || typeof value !== 'object') {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
return Object.entries(value).some(([key, entry]) => isSecretKey(key) || containsSensitiveConfigValue(entry));
|
|
301
|
+
}
|
|
302
|
+
export function redactConfigSecrets(value, path = '') {
|
|
303
|
+
if (Array.isArray(value)) {
|
|
304
|
+
return value.map((item, index) => redactConfigSecrets(item, `${path}[${index}]`));
|
|
305
|
+
}
|
|
306
|
+
if (value === null || typeof value !== 'object') {
|
|
307
|
+
if (isProviderBaseUrlPath(path) && typeof value === 'string') {
|
|
308
|
+
return sanitizeBaseUrlForDisplay(value);
|
|
309
|
+
}
|
|
310
|
+
return value;
|
|
311
|
+
}
|
|
312
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => {
|
|
313
|
+
const nextPath = path ? `${path}.${key}` : key;
|
|
314
|
+
if (isSecretKey(key)) {
|
|
315
|
+
return [key, '***'];
|
|
316
|
+
}
|
|
317
|
+
if (isProviderBaseUrlPath(nextPath) && typeof entry === 'string') {
|
|
318
|
+
return [key, sanitizeBaseUrlForDisplay(entry)];
|
|
319
|
+
}
|
|
320
|
+
return [key, redactConfigSecrets(entry, nextPath)];
|
|
321
|
+
}));
|
|
322
|
+
}
|
|
323
|
+
function createMiniMaxProviderStatus(config) {
|
|
324
|
+
const baseUrl = config.baseUrl?.trim();
|
|
325
|
+
const apiKey = config.apiKey?.trim();
|
|
326
|
+
const baseUrlConfigured = typeof baseUrl === 'string' && baseUrl.length > 0 && isValidMiniMaxBaseUrl(baseUrl);
|
|
327
|
+
const apiKeyConfigured = typeof apiKey === 'string' && apiKey.length > 0;
|
|
328
|
+
return {
|
|
329
|
+
provider: 'minimax',
|
|
330
|
+
configured: baseUrlConfigured && apiKeyConfigured,
|
|
331
|
+
baseUrlConfigured,
|
|
332
|
+
apiKeyConfigured,
|
|
333
|
+
storage: 'user-plaintext-v1',
|
|
334
|
+
nextActions: baseUrlConfigured && apiKeyConfigured ? [] : ['Export MINIMAX_API_KEY and rerun peaks config provider minimax set --base-url <url>']
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
export function getMiniMaxProviderConfig() {
|
|
338
|
+
return toMiniMaxProviderConfig(readJsonFile(getUserConfigPath())?.providers?.minimax);
|
|
339
|
+
}
|
|
340
|
+
export function getMiniMaxProviderStatus() {
|
|
341
|
+
return createMiniMaxProviderStatus(getMiniMaxProviderConfig());
|
|
342
|
+
}
|
|
343
|
+
export function setMiniMaxProviderConfig(input) {
|
|
344
|
+
validateMiniMaxBaseUrl(input.baseUrl);
|
|
345
|
+
const userConfig = readJsonFile(getUserConfigPath()) ?? {};
|
|
346
|
+
const existingProviders = toModelProviderConfig(userConfig.providers);
|
|
347
|
+
const providers = {
|
|
348
|
+
...existingProviders,
|
|
349
|
+
minimax: {
|
|
350
|
+
...existingProviders.minimax,
|
|
351
|
+
...input
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
validateMiniMaxBaseUrl(providers.minimax?.baseUrl);
|
|
355
|
+
writeConfig({ providers }, 'user');
|
|
356
|
+
return createMiniMaxProviderStatus(providers.minimax ?? {});
|
|
357
|
+
}
|
|
358
|
+
function toPeaksConfig(value) {
|
|
359
|
+
if (!isRecord(value))
|
|
360
|
+
return {};
|
|
361
|
+
const proxy = toProxyConfig(value.proxy);
|
|
362
|
+
return {
|
|
363
|
+
...(typeof value.version === 'string' ? { version: value.version } : {}),
|
|
364
|
+
...(typeof value.currentWorkspace === 'string' ? { currentWorkspace: value.currentWorkspace } : {}),
|
|
365
|
+
...(Array.isArray(value.workspaces) ? { workspaces: toWorkspaceConfigs(value.workspaces) } : {}),
|
|
366
|
+
...(typeof value.language === 'string' ? { language: value.language } : {}),
|
|
367
|
+
...(typeof value.model === 'string' && ['haiku', 'sonnet', 'opus', 'minimax'].includes(value.model) ? { model: value.model } : {}),
|
|
368
|
+
...(typeof value.economyMode === 'boolean' ? { economyMode: value.economyMode } : {}),
|
|
369
|
+
...(typeof value.swarmMode === 'boolean' ? { swarmMode: value.swarmMode } : {}),
|
|
370
|
+
...(isRecord(value.tokens) ? { tokens: toTokenConfig(value.tokens) } : {}),
|
|
371
|
+
...(isRecord(value.providers) ? { providers: toModelProviderConfig(value.providers) } : {}),
|
|
372
|
+
...(proxy ? { proxy } : {})
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
export function readConfig(projectRoot) {
|
|
376
|
+
const detectedRoot = projectRoot ?? findProjectRoot(process.cwd());
|
|
377
|
+
const userPath = getUserConfigPath();
|
|
378
|
+
const projectPath = getProjectConfigPath(detectedRoot);
|
|
379
|
+
const userConfig = toPeaksConfig(readJsonFile(userPath));
|
|
380
|
+
const projectConfig = removeProjectProviderSecrets(toPeaksConfig(readJsonFile(projectPath)));
|
|
381
|
+
const { proxy: projectProxy, ...projectConfigWithoutProxy } = projectConfig;
|
|
382
|
+
return {
|
|
383
|
+
...DEFAULT_CONFIG,
|
|
384
|
+
...userConfig,
|
|
385
|
+
...projectConfigWithoutProxy
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
export function writeConfig(partial, layer = 'user') {
|
|
389
|
+
if (!isConfigLayer(layer)) {
|
|
390
|
+
throw new Error('Invalid config layer');
|
|
391
|
+
}
|
|
392
|
+
if (layer === 'project' && (partial.providers !== undefined || partial.proxy !== undefined || containsSensitiveConfigValue(partial))) {
|
|
393
|
+
throw new Error('Sensitive config keys must be stored in the user config layer');
|
|
394
|
+
}
|
|
395
|
+
validateProviderConfig(partial);
|
|
396
|
+
validateProxyConfig(partial);
|
|
397
|
+
if (layer === 'project') {
|
|
398
|
+
const projectPath = getProjectWritePath();
|
|
399
|
+
ensureDir(dirname(projectPath));
|
|
400
|
+
const existing = readJsonFile(projectPath) ?? {};
|
|
401
|
+
const merged = { ...existing, ...partial };
|
|
402
|
+
writeFileSync(projectPath, JSON.stringify(merged, null, 2), 'utf-8');
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
const userPath = getUserConfigPath();
|
|
406
|
+
ensureDir(dirname(userPath));
|
|
407
|
+
const userPathDir = dirname(userPath);
|
|
408
|
+
ensureDir(userPathDir);
|
|
409
|
+
const existing = readJsonFile(userPath) ?? {};
|
|
410
|
+
const merged = { ...existing, ...partial };
|
|
411
|
+
writeFileSync(userPath, JSON.stringify(merged, null, 2), 'utf-8');
|
|
412
|
+
}
|
|
413
|
+
export function getConfig(options = {}) {
|
|
414
|
+
const projectRoot = findProjectRoot(process.cwd());
|
|
415
|
+
const userConfig = readJsonFile(getUserConfigPath()) ?? {};
|
|
416
|
+
const projectConfig = removeProjectProviderSecrets(readJsonFile(getProjectConfigPath(projectRoot)) ?? {});
|
|
417
|
+
const { proxy: projectProxy, ...projectConfigWithoutProxy } = projectConfig;
|
|
418
|
+
const source = options.layer === 'user' ? userConfig : options.layer === 'project' ? projectConfig : { ...userConfig, ...projectConfigWithoutProxy };
|
|
419
|
+
const config = isRecord(source) ? { ...source, ...(source.tokens !== undefined ? { tokens: toTokenConfig(source.tokens) } : {}) } : source;
|
|
420
|
+
if (options.key !== undefined) {
|
|
421
|
+
return getNestedValue(config, options.key);
|
|
422
|
+
}
|
|
423
|
+
return config;
|
|
424
|
+
}
|
|
425
|
+
export function setConfig(options) {
|
|
426
|
+
const layer = options.layer ?? 'user';
|
|
427
|
+
if (!isConfigLayer(layer)) {
|
|
428
|
+
throw new Error('Invalid config layer');
|
|
429
|
+
}
|
|
430
|
+
if (layer === 'project' && (isProviderConfigPath(options.key) || isProxyConfigPath(options.key) || isSensitiveConfigPath(options.key) || containsSensitiveConfigValue(options.value))) {
|
|
431
|
+
throw new Error('Sensitive config keys must be stored in the user config layer');
|
|
432
|
+
}
|
|
433
|
+
validateMiniMaxBaseUrl(getMiniMaxBaseUrlCandidate(options.key, options.value));
|
|
434
|
+
if (options.key === 'providers') {
|
|
435
|
+
validateModelProviderConfig(toModelProviderConfig(options.value));
|
|
436
|
+
}
|
|
437
|
+
else if (options.key.startsWith('providers.') && !options.key.startsWith('providers.minimax.')) {
|
|
438
|
+
const providerId = getNestedPathParts(options.key)[1];
|
|
439
|
+
if (options.key === `providers.${providerId}`) {
|
|
440
|
+
validateModelProviderConfig({ [providerId]: toProviderModelConfig(options.value) });
|
|
441
|
+
}
|
|
442
|
+
else if (isProviderBaseUrlPath(options.key)) {
|
|
443
|
+
validateProviderBaseUrl(options.value);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
validateProxyUrl(getProxyUrlCandidate(options.key, options.value));
|
|
447
|
+
const targetPath = layer === 'project' ? getProjectWritePath() : getUserConfigPath();
|
|
448
|
+
ensureDir(dirname(targetPath));
|
|
449
|
+
const existing = readJsonFile(targetPath) ?? {};
|
|
450
|
+
const updated = { ...existing };
|
|
451
|
+
setNestedValue(updated, options.key, options.value);
|
|
452
|
+
writeFileSync(targetPath, JSON.stringify(updated, null, 2), 'utf-8');
|
|
453
|
+
}
|
|
454
|
+
export function getWorkspaceConfig(workspaceId, projectRoot) {
|
|
455
|
+
const config = readConfig(projectRoot ?? findProjectRoot(process.cwd()));
|
|
456
|
+
return config.workspaces.find((w) => w.workspaceId === workspaceId) ?? null;
|
|
457
|
+
}
|
|
458
|
+
function readLayerConfig(layer) {
|
|
459
|
+
const config = getConfig({ layer });
|
|
460
|
+
return isRecord(config)
|
|
461
|
+
? {
|
|
462
|
+
currentWorkspace: typeof config.currentWorkspace === 'string' ? config.currentWorkspace : null,
|
|
463
|
+
workspaces: toWorkspaceConfigs(config.workspaces)
|
|
464
|
+
}
|
|
465
|
+
: { currentWorkspace: null, workspaces: [] };
|
|
466
|
+
}
|
|
467
|
+
export function addWorkspace(workspace, layer = 'user') {
|
|
468
|
+
const config = readLayerConfig(layer);
|
|
469
|
+
const workspaces = config.workspaces;
|
|
470
|
+
const existing = workspaces.findIndex((w) => w.workspaceId === workspace.workspaceId);
|
|
471
|
+
const updatedWorkspaces = existing >= 0
|
|
472
|
+
? workspaces.map((existingWorkspace) => existingWorkspace.workspaceId === workspace.workspaceId ? workspace : existingWorkspace)
|
|
473
|
+
: [...workspaces, workspace];
|
|
474
|
+
writeConfig({ workspaces: updatedWorkspaces }, layer);
|
|
475
|
+
}
|
|
476
|
+
export function removeWorkspace(workspaceId, layer = 'user') {
|
|
477
|
+
const config = readLayerConfig(layer);
|
|
478
|
+
const workspaces = config.workspaces;
|
|
479
|
+
const idx = workspaces.findIndex((w) => w.workspaceId === workspaceId);
|
|
480
|
+
if (idx < 0)
|
|
481
|
+
return false;
|
|
482
|
+
const updatedWorkspaces = workspaces.filter((w) => w.workspaceId !== workspaceId);
|
|
483
|
+
const currentWorkspace = config.currentWorkspace === workspaceId ? updatedWorkspaces[0]?.workspaceId ?? null : config.currentWorkspace ?? null;
|
|
484
|
+
writeConfig({ workspaces: updatedWorkspaces, currentWorkspace }, layer);
|
|
485
|
+
return true;
|
|
486
|
+
}
|
|
487
|
+
export function setCurrentWorkspace(workspaceId, layer = 'user') {
|
|
488
|
+
const config = readLayerConfig(layer);
|
|
489
|
+
const workspaces = config.workspaces;
|
|
490
|
+
const exists = workspaces.some((w) => w.workspaceId === workspaceId);
|
|
491
|
+
if (!exists)
|
|
492
|
+
return false;
|
|
493
|
+
writeConfig({ currentWorkspace: workspaceId }, layer);
|
|
494
|
+
return true;
|
|
495
|
+
}
|
|
496
|
+
export function getCurrentWorkspaceConfig() {
|
|
497
|
+
const config = readConfig();
|
|
498
|
+
if (!config.currentWorkspace)
|
|
499
|
+
return null;
|
|
500
|
+
return getWorkspaceConfig(config.currentWorkspace);
|
|
501
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export type TokenRef = {
|
|
2
|
+
env: string;
|
|
3
|
+
} | {
|
|
4
|
+
keychain: string;
|
|
5
|
+
} | {
|
|
6
|
+
ghCli: true;
|
|
7
|
+
};
|
|
8
|
+
export type TokenConfig = {
|
|
9
|
+
AnthropicApiKey?: TokenRef;
|
|
10
|
+
OpenAiApiKey?: TokenRef;
|
|
11
|
+
GitHubToken?: TokenRef;
|
|
12
|
+
GitLabToken?: TokenRef;
|
|
13
|
+
};
|
|
14
|
+
export type ModelPreference = 'haiku' | 'sonnet' | 'opus' | 'minimax';
|
|
15
|
+
export type ModelProviderId = 'minimax' | string;
|
|
16
|
+
export type ExecutionModelId = string;
|
|
17
|
+
export type ProviderModelConfig = {
|
|
18
|
+
model?: ExecutionModelId;
|
|
19
|
+
baseUrl?: string;
|
|
20
|
+
apiKey?: string;
|
|
21
|
+
};
|
|
22
|
+
export type MiniMaxProviderConfig = ProviderModelConfig;
|
|
23
|
+
export type ModelProviderConfig = {
|
|
24
|
+
minimax?: MiniMaxProviderConfig;
|
|
25
|
+
[providerId: string]: ProviderModelConfig | undefined;
|
|
26
|
+
};
|
|
27
|
+
export type ProxyConfig = {
|
|
28
|
+
httpProxy?: string;
|
|
29
|
+
};
|
|
30
|
+
export type WorkspaceConfig = {
|
|
31
|
+
workspaceId: string;
|
|
32
|
+
name: string;
|
|
33
|
+
rootPath: string;
|
|
34
|
+
artifactRepo?: {
|
|
35
|
+
provider: 'github' | 'gitlab';
|
|
36
|
+
owner: string;
|
|
37
|
+
name: string;
|
|
38
|
+
};
|
|
39
|
+
installedCapabilityIds: string[];
|
|
40
|
+
};
|
|
41
|
+
export type PeaksConfig = {
|
|
42
|
+
version: string;
|
|
43
|
+
currentWorkspace: string | null;
|
|
44
|
+
workspaces: WorkspaceConfig[];
|
|
45
|
+
language: string;
|
|
46
|
+
model: ModelPreference;
|
|
47
|
+
economyMode: boolean;
|
|
48
|
+
swarmMode: boolean;
|
|
49
|
+
tokens: TokenConfig;
|
|
50
|
+
providers: ModelProviderConfig;
|
|
51
|
+
proxy: ProxyConfig;
|
|
52
|
+
};
|
|
53
|
+
export type ConfigLayer = 'user' | 'project';
|
|
54
|
+
export type ConfigGetOptions = {
|
|
55
|
+
key?: string;
|
|
56
|
+
layer?: ConfigLayer;
|
|
57
|
+
};
|
|
58
|
+
export type ConfigSetOptions = {
|
|
59
|
+
key: string;
|
|
60
|
+
value: unknown;
|
|
61
|
+
layer?: ConfigLayer;
|
|
62
|
+
};
|
|
63
|
+
export declare const DEFAULT_CONFIG: PeaksConfig;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const DEFAULT_CONFIG = {
|
|
2
|
+
version: '0.1.0',
|
|
3
|
+
currentWorkspace: null,
|
|
4
|
+
workspaces: [],
|
|
5
|
+
language: 'en',
|
|
6
|
+
model: 'sonnet',
|
|
7
|
+
economyMode: true,
|
|
8
|
+
swarmMode: true,
|
|
9
|
+
tokens: {},
|
|
10
|
+
providers: {
|
|
11
|
+
minimax: {
|
|
12
|
+
model: 'minimax-2.7'
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
proxy: {}
|
|
16
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type ModelProviderConfig, type PeaksConfig } from './config-types.js';
|
|
2
|
+
export declare const STRONGEST_MODEL_ID: "claude-opus-4-7";
|
|
3
|
+
export declare function getConfiguredExecutionModelId(providers: ModelProviderConfig | undefined): string;
|
|
4
|
+
export declare function getEconomyAwareExecutionModelId(config: Pick<PeaksConfig, 'economyMode' | 'providers'>): string;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { DEFAULT_CONFIG } from './config-types.js';
|
|
2
|
+
export const STRONGEST_MODEL_ID = 'claude-opus-4-7';
|
|
3
|
+
export function getConfiguredExecutionModelId(providers) {
|
|
4
|
+
const providerConfigs = Object.values(providers ?? DEFAULT_CONFIG.providers);
|
|
5
|
+
const configuredModel = providerConfigs
|
|
6
|
+
.map((provider) => provider?.model?.trim())
|
|
7
|
+
.find((model) => typeof model === 'string' && model.length > 0);
|
|
8
|
+
if (!configuredModel) {
|
|
9
|
+
throw new Error('Execution model must be configured in providers');
|
|
10
|
+
}
|
|
11
|
+
return configuredModel;
|
|
12
|
+
}
|
|
13
|
+
export function getEconomyAwareExecutionModelId(config) {
|
|
14
|
+
return config.economyMode ? getConfiguredExecutionModelId(config.providers) : STRONGEST_MODEL_ID;
|
|
15
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type DoctorCheck = {
|
|
2
|
+
id: string;
|
|
3
|
+
ok: boolean;
|
|
4
|
+
message: string;
|
|
5
|
+
};
|
|
6
|
+
export type DoctorReport = {
|
|
7
|
+
checks: DoctorCheck[];
|
|
8
|
+
summary: {
|
|
9
|
+
ok: boolean;
|
|
10
|
+
passed: number;
|
|
11
|
+
failed: number;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
export type DoctorOptions = {
|
|
15
|
+
schemasBaseDir?: string;
|
|
16
|
+
skillsBaseDir?: string;
|
|
17
|
+
};
|
|
18
|
+
export declare function runDoctor(options?: DoctorOptions): Promise<DoctorReport>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { readText } from '../../shared/fs.js';
|
|
5
|
+
import { requiredSchemaFiles, requiredSkillNames, schemasDir } from '../../shared/paths.js';
|
|
6
|
+
import { getErrorMessage } from '../../shared/result.js';
|
|
7
|
+
import { loadSkillRegistry } from '../skills/skill-registry.js';
|
|
8
|
+
export async function runDoctor(options = {}) {
|
|
9
|
+
const checks = [];
|
|
10
|
+
const registry = await loadSkillRegistry(options.skillsBaseDir);
|
|
11
|
+
const skills = registry.skills;
|
|
12
|
+
const skillNames = new Set(skills.map((skill) => skill.name));
|
|
13
|
+
for (const requiredSkill of requiredSkillNames) {
|
|
14
|
+
checks.push({
|
|
15
|
+
id: `skill:${requiredSkill}`,
|
|
16
|
+
ok: skillNames.has(requiredSkill),
|
|
17
|
+
message: skillNames.has(requiredSkill)
|
|
18
|
+
? `Required skill ${requiredSkill} exists`
|
|
19
|
+
: `Missing required skill ${requiredSkill}`
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
for (const skill of skills) {
|
|
23
|
+
checks.push({
|
|
24
|
+
id: `skill-name:${skill.directory}`,
|
|
25
|
+
ok: skill.name === skill.directory,
|
|
26
|
+
message: skill.name === skill.directory
|
|
27
|
+
? `Skill ${skill.name} matches its directory`
|
|
28
|
+
: `Skill ${skill.directory} declares mismatched name ${skill.name}`
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
for (const failure of registry.failures) {
|
|
32
|
+
checks.push({
|
|
33
|
+
id: `skill-parse:${failure.directory}`,
|
|
34
|
+
ok: false,
|
|
35
|
+
message: `Skill ${failure.directory} has invalid metadata: ${failure.message}`
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
const schemaRoot = options.schemasBaseDir ?? schemasDir;
|
|
39
|
+
for (const schemaFile of requiredSchemaFiles) {
|
|
40
|
+
try {
|
|
41
|
+
JSON.parse(await readText(join(schemaRoot, schemaFile)));
|
|
42
|
+
checks.push({ id: `schema:${schemaFile}`, ok: true, message: `Schema ${schemaFile} is valid JSON` });
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
checks.push({
|
|
46
|
+
id: `schema:${schemaFile}`,
|
|
47
|
+
ok: false,
|
|
48
|
+
message: `Schema ${schemaFile} is missing or invalid: ${getErrorMessage(error)}`
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const userConfigPath = join(homedir(), '.peaks', 'config.json');
|
|
53
|
+
const hasUserConfig = existsSync(userConfigPath);
|
|
54
|
+
checks.push({
|
|
55
|
+
id: 'config:user',
|
|
56
|
+
ok: hasUserConfig,
|
|
57
|
+
message: hasUserConfig ? 'User config exists at ~/.peaks/config.json' : 'User config not found at ~/.peaks/config.json'
|
|
58
|
+
});
|
|
59
|
+
const failed = checks.filter((check) => !check.ok).length;
|
|
60
|
+
return {
|
|
61
|
+
checks,
|
|
62
|
+
summary: {
|
|
63
|
+
ok: failed === 0,
|
|
64
|
+
passed: checks.length - failed,
|
|
65
|
+
failed
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|