coding-tool-x 3.7.11 → 3.7.13

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 (44) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +1 -1
  3. package/dist/web/assets/{Analytics-Ck_00Brj.js → Analytics-8dRv1aYW.js} +1 -1
  4. package/dist/web/assets/{ConfigTemplates-Cpw2vUa8.js → ConfigTemplates-BQRXSICO.js} +1 -1
  5. package/dist/web/assets/Home-BtzWhKIa.js +1 -0
  6. package/dist/web/assets/Home-DYVy4cGZ.css +1 -0
  7. package/dist/web/assets/{PluginManager-2RlvEJCT.js → PluginManager-BTa-Ygf6.js} +1 -1
  8. package/dist/web/assets/{ProjectList-_ecCngpl.js → ProjectList-F_cB8ldA.js} +1 -1
  9. package/dist/web/assets/{SessionList-DlWcw3hO.js → SessionList-LOrbPQvu.js} +1 -1
  10. package/dist/web/assets/SkillManager-BsX8Qpj0.css +1 -0
  11. package/dist/web/assets/SkillManager-BzI_t-yV.js +1 -0
  12. package/dist/web/assets/{WorkspaceManager-3mqghi09.js → WorkspaceManager-Db9RGiz7.js} +1 -1
  13. package/dist/web/assets/index-DIWJmyL6.js +2 -0
  14. package/dist/web/assets/{index-BceXgSSe.css → index-bJRG3RXH.css} +1 -1
  15. package/dist/web/index.html +2 -2
  16. package/docs/superpowers/plans/2026-08-01-omp-skill-scan-settings.md +759 -0
  17. package/docs/superpowers/specs/2026-08-01-omp-skill-scan-settings-design.md +224 -0
  18. package/package.json +1 -1
  19. package/src/commands/proxy-control.js +12 -12
  20. package/src/commands/toggle-proxy.js +9 -9
  21. package/src/config/paths.js +53 -29
  22. package/src/index.js +1 -1
  23. package/src/server/api/plugins.js +119 -47
  24. package/src/server/api/skills.js +63 -10
  25. package/src/server/omp-proxy-server.js +17 -4
  26. package/src/server/services/channel-health.js +5 -0
  27. package/src/server/services/channel-sync-utils.js +1 -1
  28. package/src/server/services/model-definition-schema.js +2 -1
  29. package/src/server/services/omp-channels.js +213 -26
  30. package/src/server/services/omp-config.js +71 -6
  31. package/src/server/services/omp-native-plugin-adapter.js +456 -0
  32. package/src/server/services/omp-settings-manager.js +3 -1
  33. package/src/server/services/omp-skill-discovery.js +358 -0
  34. package/src/server/services/omp-skill-settings-service.js +97 -0
  35. package/src/server/services/pi-omp-migration.js +273 -0
  36. package/src/server/services/platform-resolution.js +44 -0
  37. package/src/server/services/plugins-service.js +85 -561
  38. package/src/server/services/project-path-validation.js +65 -0
  39. package/src/server/services/skill-service.js +169 -77
  40. package/dist/web/assets/Home-Bft3Kztl.js +0 -1
  41. package/dist/web/assets/Home-CHZJkkjH.css +0 -1
  42. package/dist/web/assets/SkillManager-CnY7IXkX.js +0 -1
  43. package/dist/web/assets/SkillManager-D7pd-d_P.css +0 -1
  44. package/dist/web/assets/index-k5oP7c-A.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
+ };