coding-tool-x 3.7.10 → 3.7.12
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/CHANGELOG.md +3 -0
- package/README.md +1 -1
- package/dist/web/assets/{Analytics-CkUtnoz9.js → Analytics-8dRv1aYW.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-BwPqhE-N.js → ConfigTemplates-BQRXSICO.js} +1 -1
- package/dist/web/assets/Home-BtzWhKIa.js +1 -0
- package/dist/web/assets/Home-DYVy4cGZ.css +1 -0
- package/dist/web/assets/{PluginManager-JMXWl37b.js → PluginManager-BTa-Ygf6.js} +1 -1
- package/dist/web/assets/{ProjectList-CZwDgVvt.js → ProjectList-F_cB8ldA.js} +1 -1
- package/dist/web/assets/{SessionList-2fZmN7Au.js → SessionList-LOrbPQvu.js} +1 -1
- package/dist/web/assets/SkillManager-BsX8Qpj0.css +1 -0
- package/dist/web/assets/SkillManager-BzI_t-yV.js +1 -0
- package/dist/web/assets/{WorkspaceManager-B3lhhBD0.js → WorkspaceManager-Db9RGiz7.js} +1 -1
- package/dist/web/assets/index-DIWJmyL6.js +2 -0
- package/dist/web/assets/{index-C5nLTbOk.css → index-bJRG3RXH.css} +1 -1
- package/dist/web/index.html +2 -2
- package/docs/superpowers/plans/2026-08-01-omp-skill-scan-settings.md +759 -0
- package/docs/superpowers/specs/2026-08-01-omp-skill-scan-settings-design.md +224 -0
- package/package.json +1 -1
- package/src/commands/proxy-control.js +12 -12
- package/src/commands/toggle-proxy.js +16 -17
- package/src/commands/ui.js +11 -1
- package/src/config/default.js +1 -1
- package/src/config/paths.js +52 -29
- package/src/index.js +42 -16
- package/src/server/api/omp-channels.js +6 -4
- package/src/server/api/omp-config.js +1 -1
- package/src/server/api/omp-proxy.js +13 -42
- package/src/server/api/plugins.js +119 -47
- package/src/server/api/skills.js +63 -10
- package/src/server/index.js +3 -3
- package/src/server/omp-proxy-server.js +142 -10
- package/src/server/services/channel-scheduler.js +59 -9
- package/src/server/services/model-definition-schema.js +2 -1
- package/src/server/services/omp-auth-gateway-client.js +62 -0
- package/src/server/services/omp-channels.js +168 -12
- package/src/server/services/omp-config.js +71 -6
- package/src/server/services/omp-gateway-routing.js +295 -0
- package/src/server/services/omp-gateway.js +919 -0
- package/src/server/services/omp-native-plugin-adapter.js +456 -0
- package/src/server/services/omp-session-log-observer.js +138 -0
- package/src/server/services/omp-sessions.js +80 -0
- package/src/server/services/omp-settings-manager.js +338 -87
- package/src/server/services/omp-skill-discovery.js +358 -0
- package/src/server/services/omp-skill-settings-service.js +97 -0
- package/src/server/services/pi-omp-migration.js +273 -0
- package/src/server/services/platform-resolution.js +44 -0
- package/src/server/services/plugins-service.js +85 -561
- package/src/server/services/project-path-validation.js +65 -0
- package/src/server/services/skill-service.js +169 -77
- package/dist/web/assets/Home-BANl91zG.js +0 -1
- package/dist/web/assets/Home-CqnBXFxj.css +0 -1
- package/dist/web/assets/SkillManager-Bhzhkeis.js +0 -1
- package/dist/web/assets/SkillManager-D7pd-d_P.css +0 -1
- package/dist/web/assets/index-LYvv7wtx.js +0 -2
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { PATHS } = require('../../config/paths');
|
|
4
|
+
|
|
5
|
+
function isObject(value) {
|
|
6
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function clone(value) {
|
|
10
|
+
return value == null ? value : JSON.parse(JSON.stringify(value));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function itemIdentity(item) {
|
|
14
|
+
if (!isObject(item)) return `${typeof item}:${JSON.stringify(item)}`;
|
|
15
|
+
const identity = item.id
|
|
16
|
+
|| item.pluginId
|
|
17
|
+
|| item.source
|
|
18
|
+
|| item.sourceUri
|
|
19
|
+
|| item.repoUrl
|
|
20
|
+
|| item.url
|
|
21
|
+
|| item.localPath
|
|
22
|
+
|| item.name;
|
|
23
|
+
return identity ? `object:${identity}` : `json:${JSON.stringify(item)}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function mergeValues(current, legacy) {
|
|
27
|
+
if (Array.isArray(current) && Array.isArray(legacy)) {
|
|
28
|
+
const result = current.map(clone);
|
|
29
|
+
const indexes = new Map(result.map((item, index) => [itemIdentity(item), index]));
|
|
30
|
+
for (const item of legacy) {
|
|
31
|
+
const identity = itemIdentity(item);
|
|
32
|
+
if (!indexes.has(identity)) {
|
|
33
|
+
indexes.set(identity, result.length);
|
|
34
|
+
result.push(clone(item));
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const index = indexes.get(identity);
|
|
38
|
+
result[index] = mergeValues(result[index], item);
|
|
39
|
+
}
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
if (isObject(current) && isObject(legacy)) {
|
|
43
|
+
const result = clone(current);
|
|
44
|
+
for (const [key, value] of Object.entries(legacy)) {
|
|
45
|
+
result[key] = Object.prototype.hasOwnProperty.call(result, key)
|
|
46
|
+
? mergeValues(result[key], value)
|
|
47
|
+
: clone(value);
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
return clone(current);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function ensureDir(dirPath) {
|
|
55
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function readJson(filePath) {
|
|
59
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function writeJson(filePath, value) {
|
|
63
|
+
ensureDir(path.dirname(filePath));
|
|
64
|
+
fs.writeFileSync(filePath, JSON.stringify(value, null, 2), 'utf8');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function uniqueArchivePath(archiveRoot, category, sourcePath) {
|
|
68
|
+
const base = path.join(archiveRoot, category, path.basename(sourcePath));
|
|
69
|
+
if (!fs.existsSync(base)) return base;
|
|
70
|
+
const extension = path.extname(base);
|
|
71
|
+
const stem = extension ? base.slice(0, -extension.length) : base;
|
|
72
|
+
let index = 1;
|
|
73
|
+
while (fs.existsSync(`${stem}.${index}${extension}`)) index += 1;
|
|
74
|
+
return `${stem}.${index}${extension}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function moveEntry(sourcePath, targetPath) {
|
|
78
|
+
ensureDir(path.dirname(targetPath));
|
|
79
|
+
try {
|
|
80
|
+
fs.renameSync(sourcePath, targetPath);
|
|
81
|
+
return;
|
|
82
|
+
} catch {
|
|
83
|
+
const stat = fs.statSync(sourcePath);
|
|
84
|
+
if (stat.isDirectory()) {
|
|
85
|
+
ensureDir(targetPath);
|
|
86
|
+
for (const entry of fs.readdirSync(sourcePath)) {
|
|
87
|
+
moveEntry(path.join(sourcePath, entry), path.join(targetPath, entry));
|
|
88
|
+
}
|
|
89
|
+
fs.rmdirSync(sourcePath);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
93
|
+
fs.unlinkSync(sourcePath);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function archiveEntry(sourcePath, archiveRoot, category) {
|
|
98
|
+
if (!sourcePath || !fs.existsSync(sourcePath)) return '';
|
|
99
|
+
const targetPath = uniqueArchivePath(archiveRoot, category, sourcePath);
|
|
100
|
+
moveEntry(sourcePath, targetPath);
|
|
101
|
+
return targetPath;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function mergeJsonFile(sourcePath, targetPath, archiveRoot, category) {
|
|
105
|
+
if (!sourcePath || !targetPath || !fs.existsSync(sourcePath)) return false;
|
|
106
|
+
const legacy = readJson(sourcePath);
|
|
107
|
+
const merged = fs.existsSync(targetPath)
|
|
108
|
+
? mergeValues(readJson(targetPath), legacy)
|
|
109
|
+
: legacy;
|
|
110
|
+
writeJson(targetPath, merged);
|
|
111
|
+
archiveEntry(sourcePath, archiveRoot, category);
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function mergeDirectory(sourceDir, targetDir, archiveRoot) {
|
|
116
|
+
if (!sourceDir || !targetDir || !fs.existsSync(sourceDir)) return false;
|
|
117
|
+
ensureDir(targetDir);
|
|
118
|
+
for (const entry of fs.readdirSync(sourceDir, { withFileTypes: true })) {
|
|
119
|
+
const sourcePath = path.join(sourceDir, entry.name);
|
|
120
|
+
const targetPath = path.join(targetDir, entry.name);
|
|
121
|
+
if (!fs.existsSync(targetPath)) {
|
|
122
|
+
moveEntry(sourcePath, targetPath);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (entry.isDirectory() && fs.statSync(targetPath).isDirectory()) {
|
|
126
|
+
mergeDirectory(sourcePath, targetPath, archiveRoot);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (entry.isFile() && path.extname(entry.name) === '.json') {
|
|
130
|
+
try {
|
|
131
|
+
mergeJsonFile(sourcePath, targetPath, archiveRoot, 'local-skill-conflicts');
|
|
132
|
+
continue;
|
|
133
|
+
} catch {
|
|
134
|
+
// Non-JSON conflicts are archived without replacing the OMP copy.
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
archiveEntry(sourcePath, archiveRoot, 'local-skill-conflicts');
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
fs.rmdirSync(sourceDir);
|
|
141
|
+
} catch {
|
|
142
|
+
// A partially migrated directory is intentionally left for the next retry.
|
|
143
|
+
}
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function siblingFile(targetPath, fileName) {
|
|
148
|
+
return targetPath ? path.join(path.dirname(targetPath), fileName) : '';
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function siblingDir(targetPath, directoryName) {
|
|
152
|
+
return targetPath ? path.join(path.dirname(targetPath), directoryName) : '';
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function migratePiStorage(paths = PATHS) {
|
|
156
|
+
const archiveRoot = path.join(paths?.legacy?.dir || path.dirname(paths?.channels?.omp || process.cwd()), 'pi-omp');
|
|
157
|
+
const warnings = [];
|
|
158
|
+
let migrated = 0;
|
|
159
|
+
const migrations = [
|
|
160
|
+
['channels', siblingFile(paths?.channels?.omp, 'pi.json'), paths?.channels?.omp],
|
|
161
|
+
['active-channel', siblingFile(paths?.activeChannel?.omp, 'pi.json'), paths?.activeChannel?.omp],
|
|
162
|
+
['skill-repos', siblingFile(paths?.skillRepos?.omp, 'pi.json'), paths?.skillRepos?.omp],
|
|
163
|
+
['plugin-repos', siblingFile(paths?.pluginRepos?.omp, 'pi.json'), paths?.pluginRepos?.omp]
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
for (const [category, source, target] of migrations) {
|
|
167
|
+
try {
|
|
168
|
+
if (mergeJsonFile(source, target, archiveRoot, category)) migrated += 1;
|
|
169
|
+
} catch (error) {
|
|
170
|
+
warnings.push(`Failed to migrate legacy pi ${category}: ${error.message}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
if (mergeDirectory(
|
|
176
|
+
siblingDir(paths?.localSkills?.omp, 'pi'),
|
|
177
|
+
paths?.localSkills?.omp,
|
|
178
|
+
archiveRoot
|
|
179
|
+
)) migrated += 1;
|
|
180
|
+
} catch (error) {
|
|
181
|
+
warnings.push(`Failed to migrate legacy pi local skills: ${error.message}`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
for (const [category, target, legacyName] of [
|
|
185
|
+
['skill-cache', paths?.skillCaches?.omp, 'pi.json'],
|
|
186
|
+
['plugin-cache', paths?.pluginMarketCache?.omp, 'pi-market.json']
|
|
187
|
+
]) {
|
|
188
|
+
const source = siblingFile(target, legacyName);
|
|
189
|
+
if (!source || !fs.existsSync(source)) continue;
|
|
190
|
+
try {
|
|
191
|
+
archiveEntry(source, archiveRoot, category);
|
|
192
|
+
archiveEntry(target, archiveRoot, category);
|
|
193
|
+
migrated += 1;
|
|
194
|
+
} catch (error) {
|
|
195
|
+
warnings.push(`Failed to archive legacy pi ${category}: ${error.message}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return { migrated, warnings };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function marketplaceSource(repo = {}) {
|
|
203
|
+
if (typeof repo === 'string') return repo.trim();
|
|
204
|
+
return String(
|
|
205
|
+
repo.source
|
|
206
|
+
|| repo.sourceUri
|
|
207
|
+
|| repo.repoUrl
|
|
208
|
+
|| repo.url
|
|
209
|
+
|| repo.localPath
|
|
210
|
+
|| (repo.owner && repo.name ? `${repo.owner}/${repo.name}` : '')
|
|
211
|
+
).trim();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function loadImportState(statePath) {
|
|
215
|
+
try {
|
|
216
|
+
const state = readJson(statePath);
|
|
217
|
+
return isObject(state) ? state : { imported: {} };
|
|
218
|
+
} catch {
|
|
219
|
+
return { imported: {} };
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function importLegacyPluginRepos(adapter, paths = PATHS, options = {}) {
|
|
224
|
+
const configPath = paths?.pluginRepos?.omp;
|
|
225
|
+
const statePath = path.join(paths?.legacy?.dir || path.dirname(configPath || process.cwd()), 'pi-omp-marketplace-import.json');
|
|
226
|
+
const warnings = [];
|
|
227
|
+
if (!configPath || !fs.existsSync(configPath)) return { warnings, imported: 0 };
|
|
228
|
+
|
|
229
|
+
let config;
|
|
230
|
+
try {
|
|
231
|
+
config = readJson(configPath);
|
|
232
|
+
} catch (error) {
|
|
233
|
+
return {
|
|
234
|
+
warnings: [`Legacy OMP marketplace config is invalid and was retained: ${error.message}`],
|
|
235
|
+
imported: 0
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const repos = Array.isArray(config?.repos) ? config.repos : [];
|
|
240
|
+
const state = loadImportState(statePath);
|
|
241
|
+
state.imported = isObject(state.imported) ? state.imported : {};
|
|
242
|
+
const nativeRepos = adapter.listMarketplaces(options);
|
|
243
|
+
const existing = new Set();
|
|
244
|
+
for (const repo of nativeRepos) {
|
|
245
|
+
for (const identity of [repo.id, repo.name, repo.source, repo.sourceUri]) {
|
|
246
|
+
if (identity) existing.add(String(identity));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
let imported = 0;
|
|
251
|
+
for (const repo of repos) {
|
|
252
|
+
const source = marketplaceSource(repo);
|
|
253
|
+
if (!source || state.imported[source] || existing.has(source)) continue;
|
|
254
|
+
try {
|
|
255
|
+
adapter.addMarketplace(source, options);
|
|
256
|
+
state.imported[source] = { importedAt: new Date().toISOString() };
|
|
257
|
+
writeJson(statePath, state);
|
|
258
|
+
existing.add(source);
|
|
259
|
+
imported += 1;
|
|
260
|
+
} catch (error) {
|
|
261
|
+
warnings.push(`Failed to import legacy OMP marketplace "${source}": ${error.message}`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return { warnings, imported };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
module.exports = {
|
|
269
|
+
importLegacyPluginRepos,
|
|
270
|
+
marketplaceSource,
|
|
271
|
+
mergeValues,
|
|
272
|
+
migratePiStorage
|
|
273
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const SUPPORTED_PLATFORMS = Object.freeze([
|
|
2
|
+
'claude',
|
|
3
|
+
'codex',
|
|
4
|
+
'gemini',
|
|
5
|
+
'opencode',
|
|
6
|
+
'omp'
|
|
7
|
+
]);
|
|
8
|
+
|
|
9
|
+
function resolveManagedPlatform(rawPlatform, options = {}) {
|
|
10
|
+
const fallback = options.fallback || 'claude';
|
|
11
|
+
const raw = rawPlatform == null ? '' : String(rawPlatform);
|
|
12
|
+
const normalized = raw.trim().toLowerCase();
|
|
13
|
+
|
|
14
|
+
if (!normalized) {
|
|
15
|
+
return {
|
|
16
|
+
platform: fallback,
|
|
17
|
+
warning: null,
|
|
18
|
+
deprecated: false
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (normalized === 'pi') {
|
|
23
|
+
return {
|
|
24
|
+
platform: 'omp',
|
|
25
|
+
warning: 'Platform "pi" is deprecated; use "omp".',
|
|
26
|
+
deprecated: true
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!SUPPORTED_PLATFORMS.includes(normalized)) {
|
|
31
|
+
throw new Error(`Invalid platform: ${raw.trim() || raw}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
platform: normalized,
|
|
36
|
+
warning: null,
|
|
37
|
+
deprecated: false
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = {
|
|
42
|
+
SUPPORTED_PLATFORMS,
|
|
43
|
+
resolveManagedPlatform
|
|
44
|
+
};
|