runwork 0.6.2 → 0.8.3
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/dist/__tests__/install-scripts.test.d.ts +1 -0
- package/dist/__tests__/install-scripts.test.js +247 -0
- package/dist/agents/__tests__/claude-code-plugin.test.d.ts +1 -0
- package/dist/agents/__tests__/claude-code-plugin.test.js +47 -0
- package/dist/agents/__tests__/claude-code-stats.test.js +143 -78
- package/dist/agents/__tests__/claude-desktop-plugin-tree.test.d.ts +1 -0
- package/dist/agents/__tests__/claude-desktop-plugin-tree.test.js +161 -0
- package/dist/agents/__tests__/claude-desktop-plugin.test.d.ts +1 -0
- package/dist/agents/__tests__/claude-desktop-plugin.test.js +117 -0
- package/dist/agents/__tests__/claude-desktop-rpm.test.d.ts +1 -0
- package/dist/agents/__tests__/claude-desktop-rpm.test.js +126 -0
- package/dist/agents/__tests__/codex-instructions.test.d.ts +1 -0
- package/dist/agents/__tests__/codex-instructions.test.js +57 -0
- package/dist/agents/__tests__/codex-stats.test.js +9 -0
- package/dist/agents/__tests__/graceful-degradation.test.js +3 -3
- package/dist/agents/__tests__/intro-skill.test.js +11 -2
- package/dist/agents/__tests__/skill-slug.test.d.ts +1 -0
- package/dist/agents/__tests__/skill-slug.test.js +77 -0
- package/dist/agents/claude-code.d.ts +3 -1
- package/dist/agents/claude-code.js +233 -58
- package/dist/agents/claude-desktop-plugin-tree.d.ts +44 -0
- package/dist/agents/claude-desktop-plugin-tree.js +92 -0
- package/dist/agents/claude-desktop.d.ts +20 -1
- package/dist/agents/claude-desktop.js +238 -61
- package/dist/agents/cline.d.ts +3 -1
- package/dist/agents/cline.js +44 -2
- package/dist/agents/codex.d.ts +3 -1
- package/dist/agents/codex.js +85 -14
- package/dist/agents/cursor.d.ts +12 -1
- package/dist/agents/cursor.js +98 -25
- package/dist/agents/gemini.d.ts +3 -1
- package/dist/agents/gemini.js +42 -4
- package/dist/agents/generic-adapter.d.ts +3 -1
- package/dist/agents/generic-adapter.js +59 -4
- package/dist/agents/intro-skill.js +77 -31
- package/dist/agents/types.d.ts +42 -2
- package/dist/agents/types.js +61 -6
- package/dist/agents/utils/instruction-hint.d.ts +10 -0
- package/dist/agents/utils/instruction-hint.js +46 -0
- package/dist/agents/utils/json-config.d.ts +6 -1
- package/dist/agents/utils/json-config.js +31 -4
- package/dist/agents/windsurf.d.ts +3 -1
- package/dist/agents/windsurf.js +37 -2
- package/dist/commands/__tests__/sync-telemetry.test.d.ts +1 -0
- package/dist/commands/__tests__/sync-telemetry.test.js +281 -0
- package/dist/commands/__tests__/upgrade.test.d.ts +1 -0
- package/dist/commands/__tests__/upgrade.test.js +174 -0
- package/dist/commands/build-plugin.d.ts +2 -0
- package/dist/commands/build-plugin.js +135 -0
- package/dist/commands/init.d.ts +8 -2
- package/dist/commands/init.js +19 -7
- package/dist/commands/sync-telemetry.d.ts +33 -0
- package/dist/commands/sync-telemetry.js +186 -0
- package/dist/commands/sync.d.ts +1 -0
- package/dist/commands/sync.js +84 -118
- package/dist/commands/uninstall.d.ts +2 -0
- package/dist/commands/uninstall.js +145 -0
- package/dist/commands/upgrade.d.ts +12 -0
- package/dist/commands/upgrade.js +67 -16
- package/dist/generated/bundled-types.js +36 -36
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/health/__tests__/cli-distribution-checks.test.d.ts +1 -0
- package/dist/health/__tests__/cli-distribution-checks.test.js +199 -0
- package/dist/health/checks.d.ts +2 -0
- package/dist/health/checks.js +131 -18
- package/dist/health/runner.js +6 -2
- package/dist/index.js +4 -0
- package/dist/sync/executor.d.ts +2 -0
- package/dist/sync/executor.js +8 -4
- package/dist/types.d.ts +2 -0
- package/dist/ui/banner.js +15 -6
- package/dist/utils/__tests__/sqlite.test.js +84 -72
- package/dist/utils/sqlite-adapter.d.ts +48 -0
- package/dist/utils/sqlite-adapter.js +147 -0
- package/dist/utils/sqlite.d.ts +12 -5
- package/dist/utils/sqlite.js +12 -22
- package/dist/utils/zip.d.ts +6 -0
- package/dist/utils/zip.js +28 -3
- package/package.json +2 -4
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'fs';
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { homedir, platform } from 'os';
|
|
4
4
|
import { execFileSync } from 'child_process';
|
|
5
5
|
import { whichBinary } from '../utils/which.js';
|
|
6
6
|
import { buildSkillMd } from './types.js';
|
|
7
|
-
import { mergeJsonMcpServers, readJsonConfig, writeJsonConfig } from './utils/json-config.js';
|
|
8
|
-
import { writeHintToFile, writeTeamInstructionsToFile } from './utils/instruction-hint.js';
|
|
7
|
+
import { mergeJsonMcpServers, readJsonConfig, writeJsonConfig, removeRunworkMcpServers } from './utils/json-config.js';
|
|
8
|
+
import { writeHintToFile, writeTeamInstructionsToFile, removeHintFromFile, removeTeamInstructionsFromFile } from './utils/instruction-hint.js';
|
|
9
9
|
const PLUGIN_NAME = 'runwork';
|
|
10
10
|
const PLUGIN_VERSION = '1.0.0';
|
|
11
11
|
function getPluginJson() {
|
|
@@ -33,6 +33,7 @@ function getMarketplaceJson() {
|
|
|
33
33
|
export class ClaudeCodeAdapter {
|
|
34
34
|
name = 'Claude Code';
|
|
35
35
|
slug = 'claude-code';
|
|
36
|
+
mcpProvidesSkills = true;
|
|
36
37
|
async detect() {
|
|
37
38
|
if (whichBinary('claude'))
|
|
38
39
|
return true;
|
|
@@ -79,6 +80,9 @@ export class ClaudeCodeAdapter {
|
|
|
79
80
|
const pluginDir = this.getPluginDir();
|
|
80
81
|
mkdirSync(pluginDir, { recursive: true });
|
|
81
82
|
const pluginMcpPath = join(pluginDir, '.mcp.json');
|
|
83
|
+
const marketDir = this.getMarketplaceDir();
|
|
84
|
+
mkdirSync(marketDir, { recursive: true });
|
|
85
|
+
const marketplaceMcpPath = join(marketDir, '.mcp.json');
|
|
82
86
|
const pluginEntries = {};
|
|
83
87
|
for (const s of servers) {
|
|
84
88
|
pluginEntries[s.name] = {
|
|
@@ -89,6 +93,7 @@ export class ClaudeCodeAdapter {
|
|
|
89
93
|
};
|
|
90
94
|
}
|
|
91
95
|
writeJsonConfig(pluginMcpPath, pluginEntries);
|
|
96
|
+
writeJsonConfig(marketplaceMcpPath, pluginEntries);
|
|
92
97
|
}
|
|
93
98
|
}
|
|
94
99
|
async writeSkills(skills, scope) {
|
|
@@ -97,7 +102,8 @@ export class ClaudeCodeAdapter {
|
|
|
97
102
|
? join(process.cwd(), '.claude', 'skills')
|
|
98
103
|
: join(homedir(), '.claude', 'skills');
|
|
99
104
|
for (const skill of skills) {
|
|
100
|
-
const skillDir = join(baseDir, skill.
|
|
105
|
+
const skillDir = join(baseDir, skill.filename);
|
|
106
|
+
cleanupOldSkillDir(baseDir, skill);
|
|
101
107
|
mkdirSync(skillDir, { recursive: true });
|
|
102
108
|
writeFileSync(join(skillDir, 'SKILL.md'), buildSkillMd(skill));
|
|
103
109
|
}
|
|
@@ -110,7 +116,8 @@ export class ClaudeCodeAdapter {
|
|
|
110
116
|
writeFileSync(join(pluginJsonDir, 'plugin.json'), JSON.stringify(getPluginJson(), null, 2));
|
|
111
117
|
// Write skill files inside the plugin
|
|
112
118
|
for (const skill of skills) {
|
|
113
|
-
const skillDir = join(pluginDir, 'skills', skill.
|
|
119
|
+
const skillDir = join(pluginDir, 'skills', skill.filename);
|
|
120
|
+
cleanupOldSkillDir(join(pluginDir, 'skills'), skill);
|
|
114
121
|
mkdirSync(skillDir, { recursive: true });
|
|
115
122
|
writeFileSync(join(skillDir, 'SKILL.md'), buildSkillMd(skill));
|
|
116
123
|
}
|
|
@@ -120,7 +127,8 @@ export class ClaudeCodeAdapter {
|
|
|
120
127
|
mkdirSync(marketPluginJsonDir, { recursive: true });
|
|
121
128
|
writeFileSync(join(marketPluginJsonDir, 'plugin.json'), JSON.stringify(getPluginJson(), null, 2));
|
|
122
129
|
for (const skill of skills) {
|
|
123
|
-
const skillDir = join(marketDir, 'skills', skill.
|
|
130
|
+
const skillDir = join(marketDir, 'skills', skill.filename);
|
|
131
|
+
cleanupOldSkillDir(join(marketDir, 'skills'), skill);
|
|
124
132
|
mkdirSync(skillDir, { recursive: true });
|
|
125
133
|
writeFileSync(join(skillDir, 'SKILL.md'), buildSkillMd(skill));
|
|
126
134
|
}
|
|
@@ -195,72 +203,224 @@ export class ClaudeCodeAdapter {
|
|
|
195
203
|
const idx = arr.indexOf(marker);
|
|
196
204
|
return idx >= 0 ? arr.slice(0, idx) : [...arr];
|
|
197
205
|
}
|
|
206
|
+
// ── Cleanup (uninstall) ────────────────────────────────────────────
|
|
207
|
+
async cleanup(scope, manifest) {
|
|
208
|
+
// 1. Remove MCP server entries
|
|
209
|
+
if (scope === 'project') {
|
|
210
|
+
removeRunworkMcpServers(join(process.cwd(), '.mcp.json'), 'mcpServers');
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
removeRunworkMcpServers(join(homedir(), '.claude', 'settings.json'), 'mcpServers');
|
|
214
|
+
}
|
|
215
|
+
// 2. Remove only skill directories that we synced (listed in manifest)
|
|
216
|
+
const skillsDir = scope === 'project'
|
|
217
|
+
? join(process.cwd(), '.claude', 'skills')
|
|
218
|
+
: join(homedir(), '.claude', 'skills');
|
|
219
|
+
if (existsSync(skillsDir) && manifest?.skillFilenames.length) {
|
|
220
|
+
const allowed = new Set(manifest.skillFilenames);
|
|
221
|
+
for (const entry of readdirSync(skillsDir)) {
|
|
222
|
+
if (!allowed.has(entry))
|
|
223
|
+
continue;
|
|
224
|
+
try {
|
|
225
|
+
rmSync(join(skillsDir, entry), { recursive: true, force: true });
|
|
226
|
+
}
|
|
227
|
+
catch { /* best-effort */ }
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// 3. Remove instruction hints and team instructions
|
|
231
|
+
const instructionFile = scope === 'project'
|
|
232
|
+
? join(process.cwd(), '.claude', 'CLAUDE.md')
|
|
233
|
+
: join(homedir(), '.claude', 'CLAUDE.md');
|
|
234
|
+
removeHintFromFile(instructionFile);
|
|
235
|
+
removeTeamInstructionsFromFile(instructionFile);
|
|
236
|
+
// 4. Remove agent config overrides (managed permission entries, model, enabledPlugins)
|
|
237
|
+
if (scope === 'user') {
|
|
238
|
+
const settingsPath = join(homedir(), '.claude', 'settings.json');
|
|
239
|
+
if (existsSync(settingsPath)) {
|
|
240
|
+
try {
|
|
241
|
+
const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
|
242
|
+
// Remove managed permission entries
|
|
243
|
+
if (settings.permissions) {
|
|
244
|
+
const MANAGED_MARKER = '# runwork-managed';
|
|
245
|
+
if (settings.permissions.allow) {
|
|
246
|
+
const idx = settings.permissions.allow.indexOf(MANAGED_MARKER);
|
|
247
|
+
if (idx >= 0)
|
|
248
|
+
settings.permissions.allow = settings.permissions.allow.slice(0, idx);
|
|
249
|
+
}
|
|
250
|
+
if (settings.permissions.deny) {
|
|
251
|
+
const idx = settings.permissions.deny.indexOf(MANAGED_MARKER);
|
|
252
|
+
if (idx >= 0)
|
|
253
|
+
settings.permissions.deny = settings.permissions.deny.slice(0, idx);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
// Remove plugin enablement
|
|
257
|
+
if (settings.enabledPlugins) {
|
|
258
|
+
delete settings.enabledPlugins[`${PLUGIN_NAME}@runwork`];
|
|
259
|
+
}
|
|
260
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
261
|
+
}
|
|
262
|
+
catch { /* best-effort */ }
|
|
263
|
+
}
|
|
264
|
+
// 5. Remove plugin directories
|
|
265
|
+
const pluginDir = this.getPluginDir();
|
|
266
|
+
const marketRoot = this.getMarketplaceRoot();
|
|
267
|
+
for (const dir of [pluginDir, marketRoot]) {
|
|
268
|
+
if (existsSync(dir)) {
|
|
269
|
+
try {
|
|
270
|
+
rmSync(dir, { recursive: true, force: true });
|
|
271
|
+
}
|
|
272
|
+
catch { /* best-effort */ }
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
// 6. Remove from installed_plugins.json
|
|
276
|
+
const pluginsBase = this.getPluginsBaseDir();
|
|
277
|
+
const installedPath = join(pluginsBase, 'installed_plugins.json');
|
|
278
|
+
if (existsSync(installedPath)) {
|
|
279
|
+
try {
|
|
280
|
+
const installed = readJsonConfig(installedPath);
|
|
281
|
+
if (installed.plugins) {
|
|
282
|
+
delete installed.plugins[`${PLUGIN_NAME}@runwork`];
|
|
283
|
+
writeJsonConfig(installedPath, installed);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
catch { /* best-effort */ }
|
|
287
|
+
}
|
|
288
|
+
// 7. Remove from known_marketplaces.json
|
|
289
|
+
const marketplacesPath = join(pluginsBase, 'known_marketplaces.json');
|
|
290
|
+
if (existsSync(marketplacesPath)) {
|
|
291
|
+
try {
|
|
292
|
+
const marketplaces = readJsonConfig(marketplacesPath);
|
|
293
|
+
delete marketplaces['runwork'];
|
|
294
|
+
writeJsonConfig(marketplacesPath, marketplaces);
|
|
295
|
+
}
|
|
296
|
+
catch { /* best-effort */ }
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
198
300
|
// ── Usage stats (passive, best-effort) ─────────────────────────────
|
|
199
301
|
async readUsageStats(lastSyncAt) {
|
|
302
|
+
// Source of truth: ~/.claude/projects/<encoded-cwd>/<session-id>.jsonl
|
|
303
|
+
// Each line is one user/assistant/system entry. The file grows continuously
|
|
304
|
+
// during a live session, so mtime + per-entry timestamp give an accurate
|
|
305
|
+
// post-sync activity picture. (stats-cache.json was abandoned upstream and
|
|
306
|
+
// session-env/ only records session-start hooks.)
|
|
200
307
|
try {
|
|
201
308
|
const claudeDir = join(homedir(), '.claude');
|
|
202
309
|
if (!existsSync(claudeDir))
|
|
203
310
|
return null;
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
tokensUsed: 0,
|
|
222
|
-
aiLinesAdded: 0,
|
|
223
|
-
aiLinesRemoved: 0,
|
|
224
|
-
lastActiveAt: recentDays.length > 0
|
|
225
|
-
? new Date(recentDays[recentDays.length - 1].date).toISOString()
|
|
226
|
-
: null,
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
}
|
|
311
|
+
const projectsDir = join(claudeDir, 'projects');
|
|
312
|
+
if (!existsSync(projectsDir))
|
|
313
|
+
return null;
|
|
314
|
+
const sinceMs = lastSyncAt ? new Date(lastSyncAt).getTime() : 0;
|
|
315
|
+
let sessionCount = 0;
|
|
316
|
+
let messageCount = 0;
|
|
317
|
+
let toolCallCount = 0;
|
|
318
|
+
let inputTokens = 0;
|
|
319
|
+
let outputTokens = 0;
|
|
320
|
+
let cacheReadTokens = 0;
|
|
321
|
+
let latestActivityMs = 0;
|
|
322
|
+
let cwdEntries;
|
|
323
|
+
try {
|
|
324
|
+
cwdEntries = readdirSync(projectsDir);
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
327
|
+
return null;
|
|
230
328
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
329
|
+
for (const cwd of cwdEntries) {
|
|
330
|
+
const cwdPath = join(projectsDir, cwd);
|
|
331
|
+
let files;
|
|
332
|
+
try {
|
|
333
|
+
files = readdirSync(cwdPath);
|
|
334
|
+
}
|
|
335
|
+
catch {
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
for (const file of files) {
|
|
339
|
+
if (!file.endsWith('.jsonl'))
|
|
340
|
+
continue;
|
|
341
|
+
const filePath = join(cwdPath, file);
|
|
342
|
+
let stat;
|
|
239
343
|
try {
|
|
240
|
-
|
|
241
|
-
if (stat.mtimeMs > sinceMs)
|
|
242
|
-
recentCount++;
|
|
243
|
-
if (stat.mtimeMs > latestMtime)
|
|
244
|
-
latestMtime = stat.mtimeMs;
|
|
344
|
+
stat = statSync(filePath);
|
|
245
345
|
}
|
|
246
346
|
catch {
|
|
247
347
|
continue;
|
|
248
348
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
349
|
+
if (stat.mtimeMs <= sinceMs)
|
|
350
|
+
continue;
|
|
351
|
+
let content;
|
|
352
|
+
try {
|
|
353
|
+
content = readFileSync(filePath, 'utf-8');
|
|
354
|
+
}
|
|
355
|
+
catch {
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
let sessionHadActivity = false;
|
|
359
|
+
for (const line of content.split('\n')) {
|
|
360
|
+
if (!line)
|
|
361
|
+
continue;
|
|
362
|
+
let entry;
|
|
363
|
+
try {
|
|
364
|
+
entry = JSON.parse(line);
|
|
365
|
+
}
|
|
366
|
+
catch {
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
const tsRaw = entry.timestamp;
|
|
370
|
+
if (typeof tsRaw !== 'string')
|
|
371
|
+
continue;
|
|
372
|
+
const ts = Date.parse(tsRaw);
|
|
373
|
+
if (!Number.isFinite(ts) || ts <= sinceMs)
|
|
374
|
+
continue;
|
|
375
|
+
sessionHadActivity = true;
|
|
376
|
+
if (ts > latestActivityMs)
|
|
377
|
+
latestActivityMs = ts;
|
|
378
|
+
const type = entry.type;
|
|
379
|
+
if (type === 'user' || type === 'assistant') {
|
|
380
|
+
messageCount++;
|
|
381
|
+
}
|
|
382
|
+
if (type === 'assistant') {
|
|
383
|
+
const msg = entry.message;
|
|
384
|
+
if (msg && typeof msg === 'object') {
|
|
385
|
+
const msgRecord = msg;
|
|
386
|
+
const blocks = Array.isArray(msgRecord.content) ? msgRecord.content : [];
|
|
387
|
+
for (const block of blocks) {
|
|
388
|
+
if (block && typeof block === 'object'
|
|
389
|
+
&& block.type === 'tool_use') {
|
|
390
|
+
toolCallCount++;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
const usage = msgRecord.usage;
|
|
394
|
+
if (usage && typeof usage === 'object') {
|
|
395
|
+
const u = usage;
|
|
396
|
+
if (typeof u.input_tokens === 'number')
|
|
397
|
+
inputTokens += u.input_tokens;
|
|
398
|
+
if (typeof u.output_tokens === 'number')
|
|
399
|
+
outputTokens += u.output_tokens;
|
|
400
|
+
if (typeof u.cache_read_input_tokens === 'number') {
|
|
401
|
+
cacheReadTokens += u.cache_read_input_tokens;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (sessionHadActivity)
|
|
408
|
+
sessionCount++;
|
|
261
409
|
}
|
|
262
410
|
}
|
|
263
|
-
|
|
411
|
+
if (sessionCount === 0)
|
|
412
|
+
return null;
|
|
413
|
+
return {
|
|
414
|
+
hasNewActivity: true,
|
|
415
|
+
sessionCount,
|
|
416
|
+
messageCount,
|
|
417
|
+
toolCallCount,
|
|
418
|
+
tokensUsed: inputTokens + outputTokens,
|
|
419
|
+
aiLinesAdded: 0,
|
|
420
|
+
aiLinesRemoved: 0,
|
|
421
|
+
lastActiveAt: latestActivityMs > 0 ? new Date(latestActivityMs).toISOString() : null,
|
|
422
|
+
extra: { inputTokens, outputTokens, cacheReadTokens },
|
|
423
|
+
};
|
|
264
424
|
}
|
|
265
425
|
catch {
|
|
266
426
|
return null;
|
|
@@ -371,3 +531,18 @@ export class ClaudeCodeAdapter {
|
|
|
371
531
|
}
|
|
372
532
|
}
|
|
373
533
|
}
|
|
534
|
+
/**
|
|
535
|
+
* Remove old skill directory that used the display name (with spaces) if it
|
|
536
|
+
* differs from the new slug-based directory name.
|
|
537
|
+
*/
|
|
538
|
+
function cleanupOldSkillDir(baseDir, skill) {
|
|
539
|
+
if (skill.name === skill.filename)
|
|
540
|
+
return;
|
|
541
|
+
const oldDir = join(baseDir, skill.name);
|
|
542
|
+
if (existsSync(oldDir)) {
|
|
543
|
+
try {
|
|
544
|
+
rmSync(oldDir, { recursive: true, force: true });
|
|
545
|
+
}
|
|
546
|
+
catch { /* best-effort */ }
|
|
547
|
+
}
|
|
548
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { McpServerEntry, SkillFile } from './types.js';
|
|
2
|
+
export interface PluginMetadata {
|
|
3
|
+
pluginName: string;
|
|
4
|
+
pluginVersion: string;
|
|
5
|
+
description: string;
|
|
6
|
+
authorName: string;
|
|
7
|
+
}
|
|
8
|
+
export interface PluginTreeInput extends PluginMetadata {
|
|
9
|
+
skills: SkillFile[];
|
|
10
|
+
mcpServers: McpServerEntry[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Write <destDir>/.claude-plugin/plugin.json.
|
|
14
|
+
*/
|
|
15
|
+
export declare function writePluginMetadata(destDir: string, metadata: PluginMetadata): void;
|
|
16
|
+
/**
|
|
17
|
+
* Write <destDir>/.mcp.json with the { mcpServers: { ... } } wrapper
|
|
18
|
+
* Claude Desktop expects. Token from Authorization header is appended to
|
|
19
|
+
* the URL so the connector auto-authenticates without a manual OAuth click.
|
|
20
|
+
*/
|
|
21
|
+
export declare function writePluginMcpConfig(destDir: string, mcpServers: McpServerEntry[]): void;
|
|
22
|
+
/**
|
|
23
|
+
* Write <destDir>/skills/runwork-team-instructions/SKILL.md containing the
|
|
24
|
+
* workspace team instructions (plus any per-agent extra instructions that
|
|
25
|
+
* the caller has already merged in). Called from both the sync path and the
|
|
26
|
+
* plugin zip build path so both produce the same on-disk layout.
|
|
27
|
+
*/
|
|
28
|
+
export declare function writePluginTeamInstructions(destDir: string, instructions: string): void;
|
|
29
|
+
/**
|
|
30
|
+
* Write <destDir>/skills/<filename>/SKILL.md for each skill. Cleans up
|
|
31
|
+
* legacy directories that used display name with spaces (e.g. "Write Cold
|
|
32
|
+
* Email" instead of kebab-cased "write-cold-email").
|
|
33
|
+
*/
|
|
34
|
+
export declare function writePluginSkills(destDir: string, skills: SkillFile[]): void;
|
|
35
|
+
/**
|
|
36
|
+
* Convenience wrapper that writes the entire plugin tree in one shot.
|
|
37
|
+
* Used by the build-plugin command to materialise a zip-ready directory.
|
|
38
|
+
*
|
|
39
|
+
* Layout:
|
|
40
|
+
* <destDir>/.claude-plugin/plugin.json
|
|
41
|
+
* <destDir>/.mcp.json
|
|
42
|
+
* <destDir>/skills/<filename>/SKILL.md
|
|
43
|
+
*/
|
|
44
|
+
export declare function writePluginTree(destDir: string, input: PluginTreeInput): void;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { mkdirSync, rmSync, existsSync, writeFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { appendTokenToUrl, buildSkillMd } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Write <destDir>/.claude-plugin/plugin.json.
|
|
6
|
+
*/
|
|
7
|
+
export function writePluginMetadata(destDir, metadata) {
|
|
8
|
+
const pluginJsonDir = join(destDir, '.claude-plugin');
|
|
9
|
+
mkdirSync(pluginJsonDir, { recursive: true });
|
|
10
|
+
writeFileSync(join(pluginJsonDir, 'plugin.json'), JSON.stringify({
|
|
11
|
+
name: metadata.pluginName,
|
|
12
|
+
version: metadata.pluginVersion,
|
|
13
|
+
description: metadata.description,
|
|
14
|
+
author: { name: metadata.authorName },
|
|
15
|
+
}, null, 2));
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Write <destDir>/.mcp.json with the { mcpServers: { ... } } wrapper
|
|
19
|
+
* Claude Desktop expects. Token from Authorization header is appended to
|
|
20
|
+
* the URL so the connector auto-authenticates without a manual OAuth click.
|
|
21
|
+
*/
|
|
22
|
+
export function writePluginMcpConfig(destDir, mcpServers) {
|
|
23
|
+
mkdirSync(destDir, { recursive: true });
|
|
24
|
+
const mcpEntries = {};
|
|
25
|
+
for (const server of mcpServers) {
|
|
26
|
+
const token = server.headers?.Authorization?.replace(/^Bearer\s+/i, '');
|
|
27
|
+
const connectorUrl = token ? appendTokenToUrl(server.url, token) : server.url;
|
|
28
|
+
mcpEntries[server.name] = {
|
|
29
|
+
type: 'http',
|
|
30
|
+
url: connectorUrl,
|
|
31
|
+
...(server.description ? { description: server.description } : {}),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
writeFileSync(join(destDir, '.mcp.json'), JSON.stringify({ mcpServers: mcpEntries }, null, 2));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Write <destDir>/skills/runwork-team-instructions/SKILL.md containing the
|
|
38
|
+
* workspace team instructions (plus any per-agent extra instructions that
|
|
39
|
+
* the caller has already merged in). Called from both the sync path and the
|
|
40
|
+
* plugin zip build path so both produce the same on-disk layout.
|
|
41
|
+
*/
|
|
42
|
+
export function writePluginTeamInstructions(destDir, instructions) {
|
|
43
|
+
const skillDir = join(destDir, 'skills', 'runwork-team-instructions');
|
|
44
|
+
mkdirSync(skillDir, { recursive: true });
|
|
45
|
+
const skillContent = `---\nname: runwork-team-instructions\ndescription: Team instructions from your Runwork workspace. Always follow these guidelines.\n---\n\n${instructions}`;
|
|
46
|
+
writeFileSync(join(skillDir, 'SKILL.md'), skillContent);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Write <destDir>/skills/<filename>/SKILL.md for each skill. Cleans up
|
|
50
|
+
* legacy directories that used display name with spaces (e.g. "Write Cold
|
|
51
|
+
* Email" instead of kebab-cased "write-cold-email").
|
|
52
|
+
*/
|
|
53
|
+
export function writePluginSkills(destDir, skills) {
|
|
54
|
+
const skillsRoot = join(destDir, 'skills');
|
|
55
|
+
mkdirSync(skillsRoot, { recursive: true });
|
|
56
|
+
for (const skill of skills) {
|
|
57
|
+
if (skill.name !== skill.filename) {
|
|
58
|
+
const legacyDir = join(skillsRoot, skill.name);
|
|
59
|
+
if (existsSync(legacyDir)) {
|
|
60
|
+
try {
|
|
61
|
+
rmSync(legacyDir, { recursive: true, force: true });
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
/* best-effort */
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const skillDir = join(skillsRoot, skill.filename);
|
|
69
|
+
mkdirSync(skillDir, { recursive: true });
|
|
70
|
+
writeFileSync(join(skillDir, 'SKILL.md'), buildSkillMd(skill));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Convenience wrapper that writes the entire plugin tree in one shot.
|
|
75
|
+
* Used by the build-plugin command to materialise a zip-ready directory.
|
|
76
|
+
*
|
|
77
|
+
* Layout:
|
|
78
|
+
* <destDir>/.claude-plugin/plugin.json
|
|
79
|
+
* <destDir>/.mcp.json
|
|
80
|
+
* <destDir>/skills/<filename>/SKILL.md
|
|
81
|
+
*/
|
|
82
|
+
export function writePluginTree(destDir, input) {
|
|
83
|
+
mkdirSync(destDir, { recursive: true });
|
|
84
|
+
writePluginMetadata(destDir, {
|
|
85
|
+
pluginName: input.pluginName,
|
|
86
|
+
pluginVersion: input.pluginVersion,
|
|
87
|
+
description: input.description,
|
|
88
|
+
authorName: input.authorName,
|
|
89
|
+
});
|
|
90
|
+
writePluginMcpConfig(destDir, input.mcpServers);
|
|
91
|
+
writePluginSkills(destDir, input.skills);
|
|
92
|
+
}
|
|
@@ -1,4 +1,21 @@
|
|
|
1
|
-
import type { AgentAdapter, AgentConfigOverride, AgentUsageStats, McpServerEntry, SkillFile } from './types.js';
|
|
1
|
+
import type { AgentAdapter, AgentConfigOverride, AgentUsageStats, CleanupManifest, McpServerEntry, SkillFile } from './types.js';
|
|
2
|
+
export interface RpmPluginLocation {
|
|
3
|
+
/** Full path to the rpm plugin directory, e.g. `<org>/rpm/plugin_01YBqw...` */
|
|
4
|
+
pluginPath: string;
|
|
5
|
+
/** The org-level directory that contains `rpm/` */
|
|
6
|
+
orgDir: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Look for a plugin named `pluginName` in any `rpm/manifest.json`. These
|
|
10
|
+
* entries are created by Claude Desktop when the user uploads a plugin zip
|
|
11
|
+
* through Customize > Create plugin > Upload plugin. If found, sync should
|
|
12
|
+
* update the plugin content files inside the rpm plugin directory instead
|
|
13
|
+
* of writing to the legacy `cowork_plugins/` layout.
|
|
14
|
+
*
|
|
15
|
+
* We never modify `rpm/manifest.json` itself — Claude Desktop owns that
|
|
16
|
+
* registry and assigns the plugin id we read from it here.
|
|
17
|
+
*/
|
|
18
|
+
export declare function findRpmPluginByName(pluginName: string): RpmPluginLocation | null;
|
|
2
19
|
export declare class ClaudeDesktopAdapter implements AgentAdapter {
|
|
3
20
|
name: string;
|
|
4
21
|
slug: string;
|
|
@@ -8,8 +25,10 @@ export declare class ClaudeDesktopAdapter implements AgentAdapter {
|
|
|
8
25
|
writeMcpServers(servers: McpServerEntry[], _scope: 'project' | 'user'): Promise<void>;
|
|
9
26
|
writeSkills(skills: SkillFile[], _scope: 'project' | 'user'): Promise<void>;
|
|
10
27
|
writeInstructionHint(_hint: string, _scope: 'project' | 'user'): Promise<void>;
|
|
28
|
+
buildPluginZip(skills: SkillFile[], mcpServers: McpServerEntry[], teamInstructions: string | null, outputPath: string): Promise<void>;
|
|
11
29
|
writeTeamInstructions(instructions: string, _scope: 'project' | 'user'): Promise<void>;
|
|
12
30
|
writeAgentConfig(config: AgentConfigOverride, _scope: 'project' | 'user'): Promise<void>;
|
|
31
|
+
cleanup(_scope: 'project' | 'user', _manifest?: CleanupManifest): Promise<void>;
|
|
13
32
|
readUsageStats(lastSyncAt: string | null): Promise<AgentUsageStats | null>;
|
|
14
33
|
readVersion(): Promise<string | null>;
|
|
15
34
|
/**
|