runwork 0.5.1 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/__tests__/claude-code-stats.test.d.ts +1 -0
- package/dist/agents/__tests__/claude-code-stats.test.js +153 -0
- package/dist/agents/__tests__/claude-desktop-stats.test.d.ts +1 -0
- package/dist/agents/__tests__/claude-desktop-stats.test.js +280 -0
- package/dist/agents/__tests__/codex-stats.test.d.ts +1 -0
- package/dist/agents/__tests__/codex-stats.test.js +262 -0
- package/dist/agents/__tests__/cursor-stats.test.d.ts +1 -0
- package/dist/agents/__tests__/cursor-stats.test.js +183 -0
- package/dist/agents/__tests__/graceful-degradation.test.d.ts +1 -0
- package/dist/agents/__tests__/graceful-degradation.test.js +123 -0
- package/dist/agents/__tests__/instruction-hint.test.d.ts +1 -0
- package/dist/agents/__tests__/instruction-hint.test.js +69 -0
- package/dist/agents/__tests__/intro-skill.test.d.ts +1 -0
- package/dist/agents/__tests__/intro-skill.test.js +148 -0
- package/dist/agents/__tests__/schema-validation.test.d.ts +1 -0
- package/dist/agents/__tests__/schema-validation.test.js +212 -0
- package/dist/agents/claude-code.d.ts +16 -1
- package/dist/agents/claude-code.js +344 -21
- package/dist/agents/claude-desktop.d.ts +10 -1
- package/dist/agents/claude-desktop.js +250 -8
- package/dist/agents/cline.d.ts +13 -0
- package/dist/agents/cline.js +88 -0
- package/dist/agents/codex.d.ts +7 -2
- package/dist/agents/codex.js +130 -18
- package/dist/agents/cursor.d.ts +6 -1
- package/dist/agents/cursor.js +164 -7
- package/dist/agents/detect.js +30 -7
- package/dist/agents/gemini.d.ts +6 -3
- package/dist/agents/gemini.js +57 -20
- package/dist/agents/generic-adapter.d.ts +23 -0
- package/dist/agents/generic-adapter.js +106 -0
- package/dist/agents/intro-skill.d.ts +36 -0
- package/dist/agents/intro-skill.js +358 -0
- package/dist/agents/registry.d.ts +53 -0
- package/dist/agents/registry.js +219 -0
- package/dist/agents/types.d.ts +44 -1
- package/dist/agents/types.js +11 -4
- package/dist/agents/utils/instruction-hint.d.ts +11 -0
- package/dist/agents/utils/instruction-hint.js +62 -0
- package/dist/agents/utils/json-config.js +3 -3
- package/dist/agents/windsurf.d.ts +2 -0
- package/dist/agents/windsurf.js +26 -9
- package/dist/api/client.d.ts +85 -2
- package/dist/api/client.js +40 -10
- package/dist/auth/__tests__/login-flow.test.d.ts +1 -0
- package/dist/auth/__tests__/login-flow.test.js +125 -0
- package/dist/auth/login-flow.d.ts +16 -0
- package/dist/auth/login-flow.js +101 -1
- package/dist/commands/clone.d.ts +9 -1
- package/dist/commands/clone.js +40 -12
- package/dist/commands/deploy.js +27 -4
- package/dist/commands/dev.js +35 -1
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.js +82 -0
- package/dist/commands/info.js +6 -2
- package/dist/commands/init.d.ts +10 -2
- package/dist/commands/init.js +58 -15
- package/dist/commands/integrations.js +47 -10
- package/dist/commands/login.js +31 -4
- package/dist/commands/mcp.js +143 -15
- package/dist/commands/setup.js +47 -122
- package/dist/commands/skills.js +230 -12
- package/dist/commands/sync.d.ts +8 -0
- package/dist/commands/sync.js +398 -65
- package/dist/commands/welcome.js +6 -3
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/health/__tests__/checks.test.d.ts +1 -0
- package/dist/health/__tests__/checks.test.js +223 -0
- package/dist/health/checks.d.ts +20 -0
- package/dist/health/checks.js +392 -0
- package/dist/health/runner.d.ts +2 -0
- package/dist/health/runner.js +26 -0
- package/dist/health/types.d.ts +12 -0
- package/dist/health/types.js +1 -0
- package/dist/index.js +32 -0
- package/dist/sync/__tests__/change-detect.test.d.ts +1 -0
- package/dist/sync/__tests__/change-detect.test.js +123 -0
- package/dist/sync/__tests__/hash.test.d.ts +1 -0
- package/dist/sync/__tests__/hash.test.js +18 -0
- package/dist/sync/change-detect.d.ts +19 -0
- package/dist/sync/change-detect.js +136 -0
- package/dist/sync/conflict-ui.d.ts +5 -0
- package/dist/sync/conflict-ui.js +78 -0
- package/dist/sync/executor.d.ts +21 -0
- package/dist/sync/executor.js +98 -0
- package/dist/sync/hash.d.ts +1 -0
- package/dist/sync/hash.js +4 -0
- package/dist/sync/types.d.ts +24 -0
- package/dist/sync/types.js +1 -0
- package/dist/types.d.ts +12 -0
- package/dist/ui/__tests__/banner.test.js +17 -1
- package/dist/ui/banner.d.ts +1 -1
- package/dist/ui/banner.js +27 -2
- package/dist/utils/__tests__/resolve.test.d.ts +1 -0
- package/dist/utils/__tests__/resolve.test.js +95 -0
- package/dist/utils/__tests__/sqlite.test.d.ts +1 -0
- package/dist/utils/__tests__/sqlite.test.js +77 -0
- package/dist/utils/agent-guidance.d.ts +32 -0
- package/dist/utils/agent-guidance.js +106 -0
- package/dist/utils/curl-parser.d.ts +1 -1
- package/dist/utils/curl-parser.js +144 -26
- package/dist/utils/fs.js +2 -2
- package/dist/utils/help-json.d.ts +56 -0
- package/dist/utils/help-json.js +85 -0
- package/dist/utils/resolve.d.ts +12 -0
- package/dist/utils/resolve.js +52 -0
- package/dist/utils/setup-state.d.ts +3 -0
- package/dist/utils/setup-state.js +19 -0
- package/dist/utils/sqlite.d.ts +7 -0
- package/dist/utils/sqlite.js +24 -0
- package/dist/utils/which.d.ts +6 -0
- package/dist/utils/which.js +18 -0
- package/package.json +7 -4
- package/dist/agents/copilot-cli.d.ts +0 -10
- package/dist/agents/copilot-cli.js +0 -46
- package/dist/agents/copilot-vscode.d.ts +0 -10
- package/dist/agents/copilot-vscode.js +0 -37
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, readdirSync, writeFileSync, mkdirSync } from 'fs';
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, statSync, writeFileSync, mkdirSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { homedir, platform } from 'os';
|
|
4
4
|
import { buildSkillMd } from './types.js';
|
|
@@ -57,6 +57,19 @@ function getPluginJson() {
|
|
|
57
57
|
author: { name: 'Runwork' },
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
|
+
function getMarketplaceJson() {
|
|
61
|
+
return {
|
|
62
|
+
name: 'runwork',
|
|
63
|
+
owner: { name: 'Runwork' },
|
|
64
|
+
plugins: [
|
|
65
|
+
{
|
|
66
|
+
name: PLUGIN_NAME,
|
|
67
|
+
source: `./plugins/${PLUGIN_NAME}`,
|
|
68
|
+
description: 'Skills and tools from your Runwork workspace',
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
};
|
|
72
|
+
}
|
|
60
73
|
export class ClaudeDesktopAdapter {
|
|
61
74
|
name = 'Claude Desktop (Cowork)';
|
|
62
75
|
slug = 'claude-desktop';
|
|
@@ -87,6 +100,25 @@ export class ClaudeDesktopAdapter {
|
|
|
87
100
|
};
|
|
88
101
|
}
|
|
89
102
|
mergeJsonMcpServers(getMcpConfigPath(), entries, 'mcpServers');
|
|
103
|
+
// Also write .mcp.json inside the Cowork plugin directory
|
|
104
|
+
const pluginsDir = findCoworkPluginsDir();
|
|
105
|
+
if (pluginsDir) {
|
|
106
|
+
const cacheDir = join(pluginsDir, 'cache', 'runwork', PLUGIN_NAME, PLUGIN_VERSION);
|
|
107
|
+
const marketDir = join(pluginsDir, 'marketplaces', 'runwork', 'plugins', PLUGIN_NAME);
|
|
108
|
+
const pluginEntries = {};
|
|
109
|
+
for (const s of servers) {
|
|
110
|
+
pluginEntries[s.name] = {
|
|
111
|
+
type: 'http',
|
|
112
|
+
url: s.url,
|
|
113
|
+
...(s.headers ? { headers: s.headers } : {}),
|
|
114
|
+
...(s.description ? { description: s.description } : {}),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
for (const dir of [cacheDir, marketDir]) {
|
|
118
|
+
mkdirSync(dir, { recursive: true });
|
|
119
|
+
writeJsonConfig(join(dir, '.mcp.json'), pluginEntries);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
90
122
|
}
|
|
91
123
|
async writeSkills(skills, _scope) {
|
|
92
124
|
const pluginsDir = findCoworkPluginsDir();
|
|
@@ -96,7 +128,8 @@ export class ClaudeDesktopAdapter {
|
|
|
96
128
|
}
|
|
97
129
|
// Write to both cache (installed copy) and marketplaces (source)
|
|
98
130
|
const cacheDir = join(pluginsDir, 'cache', 'runwork', PLUGIN_NAME, PLUGIN_VERSION);
|
|
99
|
-
const
|
|
131
|
+
const marketRoot = join(pluginsDir, 'marketplaces', 'runwork');
|
|
132
|
+
const marketDir = join(marketRoot, 'plugins', PLUGIN_NAME);
|
|
100
133
|
// Create plugin.json in both locations
|
|
101
134
|
for (const dir of [cacheDir, marketDir]) {
|
|
102
135
|
const pluginJsonDir = join(dir, '.claude-plugin');
|
|
@@ -126,16 +159,225 @@ export class ClaudeDesktopAdapter {
|
|
|
126
159
|
lastUpdated: new Date().toISOString(),
|
|
127
160
|
}];
|
|
128
161
|
writeJsonConfig(installedPath, installed);
|
|
162
|
+
// Write marketplace.json catalog
|
|
163
|
+
const marketCatalogDir = join(marketRoot, '.claude-plugin');
|
|
164
|
+
mkdirSync(marketCatalogDir, { recursive: true });
|
|
165
|
+
writeFileSync(join(marketCatalogDir, 'marketplace.json'), JSON.stringify(getMarketplaceJson(), null, 2));
|
|
129
166
|
// Register in known_marketplaces.json
|
|
130
167
|
const marketplacesPath = join(pluginsDir, 'known_marketplaces.json');
|
|
131
168
|
const marketplaces = readJsonConfig(marketplacesPath);
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
169
|
+
marketplaces['runwork'] = {
|
|
170
|
+
source: { source: 'directory', path: marketRoot },
|
|
171
|
+
installLocation: marketRoot,
|
|
172
|
+
lastUpdated: new Date().toISOString(),
|
|
173
|
+
};
|
|
174
|
+
writeJsonConfig(marketplacesPath, marketplaces);
|
|
175
|
+
}
|
|
176
|
+
async writeInstructionHint(_hint, _scope) {
|
|
177
|
+
// Claude Desktop/Cowork uses project instructions within the app UI, not files.
|
|
178
|
+
// The plugin system handles skill discovery.
|
|
179
|
+
}
|
|
180
|
+
async writeTeamInstructions(instructions, _scope) {
|
|
181
|
+
// Write team instructions as a skill in the CoWork plugin system.
|
|
182
|
+
// This makes them available to Claude Desktop sessions via the plugin.
|
|
183
|
+
const pluginsDir = findCoworkPluginsDir();
|
|
184
|
+
if (!pluginsDir)
|
|
185
|
+
return;
|
|
186
|
+
const skillContent = `---\nname: runwork-team-instructions\ndescription: Team instructions from your Runwork workspace. Always follow these guidelines.\n---\n\n${instructions}`;
|
|
187
|
+
// Write to both cache and marketplace locations
|
|
188
|
+
const cacheDir = join(pluginsDir, 'cache', 'runwork', PLUGIN_NAME, PLUGIN_VERSION, 'skills', 'runwork-team-instructions');
|
|
189
|
+
const marketDir = join(pluginsDir, 'marketplaces', 'runwork', 'plugins', PLUGIN_NAME, 'skills', 'runwork-team-instructions');
|
|
190
|
+
for (const dir of [cacheDir, marketDir]) {
|
|
191
|
+
mkdirSync(dir, { recursive: true });
|
|
192
|
+
writeFileSync(join(dir, 'SKILL.md'), skillContent);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// ── Agent config (limited to preferences) ──────────────────────────
|
|
196
|
+
async writeAgentConfig(config, _scope) {
|
|
197
|
+
const configPath = getMcpConfigPath();
|
|
198
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
199
|
+
let desktopConfig = {};
|
|
200
|
+
if (existsSync(configPath)) {
|
|
201
|
+
try {
|
|
202
|
+
desktopConfig = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
203
|
+
}
|
|
204
|
+
catch { /* start fresh */ }
|
|
205
|
+
}
|
|
206
|
+
if (!desktopConfig.preferences)
|
|
207
|
+
desktopConfig.preferences = {};
|
|
208
|
+
const prefs = desktopConfig.preferences;
|
|
209
|
+
if (config.permissionRules) {
|
|
210
|
+
if (config.permissionRules.allow?.includes('web_search')) {
|
|
211
|
+
prefs.coworkWebSearchEnabled = true;
|
|
212
|
+
}
|
|
213
|
+
if (config.permissionRules.deny?.includes('web_search')) {
|
|
214
|
+
prefs.coworkWebSearchEnabled = false;
|
|
215
|
+
}
|
|
216
|
+
if (config.permissionRules.allow?.includes('scheduled_tasks')) {
|
|
217
|
+
prefs.coworkScheduledTasksEnabled = true;
|
|
218
|
+
prefs.ccdScheduledTasksEnabled = true;
|
|
219
|
+
}
|
|
220
|
+
if (config.permissionRules.deny?.includes('scheduled_tasks')) {
|
|
221
|
+
prefs.coworkScheduledTasksEnabled = false;
|
|
222
|
+
prefs.ccdScheduledTasksEnabled = false;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
writeFileSync(configPath, JSON.stringify(desktopConfig, null, 2));
|
|
226
|
+
}
|
|
227
|
+
// ── Usage stats (passive, best-effort) ─────────────────────────────
|
|
228
|
+
async readUsageStats(lastSyncAt) {
|
|
229
|
+
try {
|
|
230
|
+
const os = platform();
|
|
231
|
+
let claudeAppDir;
|
|
232
|
+
if (os === 'darwin') {
|
|
233
|
+
claudeAppDir = join(homedir(), 'Library', 'Application Support', 'Claude');
|
|
234
|
+
}
|
|
235
|
+
else if (os === 'win32') {
|
|
236
|
+
claudeAppDir = join(process.env.APPDATA || join(homedir(), 'AppData', 'Roaming'), 'Claude');
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
claudeAppDir = join(homedir(), '.config', 'Claude');
|
|
240
|
+
}
|
|
241
|
+
if (!existsSync(claudeAppDir))
|
|
242
|
+
return null;
|
|
243
|
+
const sinceMs = lastSyncAt ? new Date(lastSyncAt).getTime() : 0;
|
|
244
|
+
let sessionCount = 0;
|
|
245
|
+
let latestActivity = 0;
|
|
246
|
+
const modelsUsed = new Set();
|
|
247
|
+
let maxMcpTools = 0;
|
|
248
|
+
// Read agent mode session JSON files
|
|
249
|
+
// Path: local-agent-mode-sessions/{orgId}/{userId}/*.json
|
|
250
|
+
const agentSessionsDir = join(claudeAppDir, 'local-agent-mode-sessions');
|
|
251
|
+
if (existsSync(agentSessionsDir)) {
|
|
252
|
+
this.walkSessionDirs(agentSessionsDir, sinceMs, (session) => {
|
|
253
|
+
const sessionTime = session.lastActivityAt ?? session.createdAt ?? 0;
|
|
254
|
+
if (sessionTime > sinceMs)
|
|
255
|
+
sessionCount++;
|
|
256
|
+
if (sessionTime > latestActivity)
|
|
257
|
+
latestActivity = sessionTime;
|
|
258
|
+
if (session.model)
|
|
259
|
+
modelsUsed.add(session.model);
|
|
260
|
+
if (session.enabledMcpTools && typeof session.enabledMcpTools === 'object') {
|
|
261
|
+
const toolCount = Object.keys(session.enabledMcpTools).length;
|
|
262
|
+
if (toolCount > maxMcpTools)
|
|
263
|
+
maxMcpTools = toolCount;
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
// Also check code sessions launched from Desktop
|
|
268
|
+
const codeSessionsDir = join(claudeAppDir, 'claude-code-sessions');
|
|
269
|
+
if (existsSync(codeSessionsDir)) {
|
|
270
|
+
this.walkSessionDirs(codeSessionsDir, sinceMs, (session) => {
|
|
271
|
+
const sessionTime = session.lastActivityAt ?? session.createdAt ?? 0;
|
|
272
|
+
if (sessionTime > sinceMs)
|
|
273
|
+
sessionCount++;
|
|
274
|
+
if (sessionTime > latestActivity)
|
|
275
|
+
latestActivity = sessionTime;
|
|
276
|
+
if (session.model)
|
|
277
|
+
modelsUsed.add(session.model);
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
// Check scheduled tasks
|
|
281
|
+
let scheduledTaskRuns = 0;
|
|
282
|
+
const scheduledTasksPath = join(claudeAppDir, 'scheduled-tasks.json');
|
|
283
|
+
if (existsSync(scheduledTasksPath)) {
|
|
284
|
+
try {
|
|
285
|
+
const raw = readFileSync(scheduledTasksPath, 'utf-8');
|
|
286
|
+
const parsed = JSON.parse(raw);
|
|
287
|
+
const tasks = Array.isArray(parsed) ? parsed : Object.values(parsed);
|
|
288
|
+
for (const task of tasks) {
|
|
289
|
+
const t = task;
|
|
290
|
+
if (t.lastRunAt && new Date(t.lastRunAt).getTime() > sinceMs) {
|
|
291
|
+
scheduledTaskRuns++;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
catch { /* best-effort */ }
|
|
296
|
+
}
|
|
297
|
+
if (sessionCount === 0 && latestActivity <= sinceMs && scheduledTaskRuns === 0)
|
|
298
|
+
return null;
|
|
299
|
+
return {
|
|
300
|
+
hasNewActivity: sessionCount > 0 || scheduledTaskRuns > 0,
|
|
301
|
+
sessionCount,
|
|
302
|
+
messageCount: 0,
|
|
303
|
+
toolCallCount: 0,
|
|
304
|
+
tokensUsed: 0,
|
|
305
|
+
aiLinesAdded: 0,
|
|
306
|
+
aiLinesRemoved: 0,
|
|
307
|
+
lastActiveAt: latestActivity > 0 ? new Date(latestActivity).toISOString() : null,
|
|
308
|
+
modelsUsed: Array.from(modelsUsed),
|
|
309
|
+
mcpToolCount: maxMcpTools > 0 ? maxMcpTools : undefined,
|
|
310
|
+
extra: scheduledTaskRuns > 0 ? { scheduledTaskRuns } : undefined,
|
|
137
311
|
};
|
|
138
|
-
writeJsonConfig(marketplacesPath, marketplaces);
|
|
139
312
|
}
|
|
313
|
+
catch {
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
async readVersion() {
|
|
318
|
+
try {
|
|
319
|
+
const os = platform();
|
|
320
|
+
let claudeAppDir;
|
|
321
|
+
if (os === 'darwin') {
|
|
322
|
+
claudeAppDir = join(homedir(), 'Library', 'Application Support', 'Claude');
|
|
323
|
+
}
|
|
324
|
+
else if (os === 'win32') {
|
|
325
|
+
claudeAppDir = join(process.env.APPDATA || join(homedir(), 'AppData', 'Roaming'), 'Claude');
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
claudeAppDir = join(homedir(), '.config', 'Claude');
|
|
329
|
+
}
|
|
330
|
+
const versionPath = join(claudeAppDir, 'claude-code', 'sdk-version');
|
|
331
|
+
if (existsSync(versionPath)) {
|
|
332
|
+
return readFileSync(versionPath, 'utf-8').trim();
|
|
333
|
+
}
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
catch {
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Walk the org/user/session directory tree and invoke callback for each session JSON.
|
|
342
|
+
*/
|
|
343
|
+
walkSessionDirs(baseDir, _sinceMs, onSession) {
|
|
344
|
+
try {
|
|
345
|
+
for (const orgDir of readdirSync(baseDir)) {
|
|
346
|
+
if (orgDir.startsWith('.') || orgDir === 'skills-plugin')
|
|
347
|
+
continue;
|
|
348
|
+
const orgPath = join(baseDir, orgDir);
|
|
349
|
+
try {
|
|
350
|
+
if (!statSync(orgPath).isDirectory())
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
catch {
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
for (const userDir of readdirSync(orgPath)) {
|
|
357
|
+
if (userDir.startsWith('.'))
|
|
358
|
+
continue;
|
|
359
|
+
const userPath = join(orgPath, userDir);
|
|
360
|
+
try {
|
|
361
|
+
if (!statSync(userPath).isDirectory())
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
for (const file of readdirSync(userPath)) {
|
|
368
|
+
if (!file.endsWith('.json'))
|
|
369
|
+
continue;
|
|
370
|
+
try {
|
|
371
|
+
const session = JSON.parse(readFileSync(join(userPath, file), 'utf-8'));
|
|
372
|
+
onSession(session);
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
catch { /* best-effort */ }
|
|
140
382
|
}
|
|
141
383
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AgentAdapter, AgentConfigOverride, McpServerEntry, SkillFile } from './types.js';
|
|
2
|
+
export declare class ClineAdapter implements AgentAdapter {
|
|
3
|
+
name: string;
|
|
4
|
+
slug: string;
|
|
5
|
+
detect(): Promise<boolean>;
|
|
6
|
+
supportsMcpScope(scope: 'project' | 'user'): boolean;
|
|
7
|
+
supportsSkills(): boolean;
|
|
8
|
+
writeMcpServers(servers: McpServerEntry[], _scope: 'project' | 'user'): Promise<void>;
|
|
9
|
+
writeSkills(skills: SkillFile[], scope: 'project' | 'user'): Promise<void>;
|
|
10
|
+
writeInstructionHint(hint: string, scope: 'project' | 'user'): Promise<void>;
|
|
11
|
+
writeTeamInstructions(instructions: string, scope: 'project' | 'user'): Promise<void>;
|
|
12
|
+
writeAgentConfig(config: AgentConfigOverride, scope: 'project' | 'user'): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { whichBinary } from '../utils/which.js';
|
|
5
|
+
import { buildSkillMd } from './types.js';
|
|
6
|
+
import { mergeJsonMcpServers } from './utils/json-config.js';
|
|
7
|
+
export class ClineAdapter {
|
|
8
|
+
name = 'Cline';
|
|
9
|
+
slug = 'cline';
|
|
10
|
+
async detect() {
|
|
11
|
+
return !!whichBinary('cline');
|
|
12
|
+
}
|
|
13
|
+
supportsMcpScope(scope) {
|
|
14
|
+
return scope === 'user';
|
|
15
|
+
}
|
|
16
|
+
supportsSkills() {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
async writeMcpServers(servers, _scope) {
|
|
20
|
+
const configPath = join(homedir(), '.cline', 'data', 'settings', 'cline_mcp_settings.json');
|
|
21
|
+
const entries = {};
|
|
22
|
+
for (const s of servers) {
|
|
23
|
+
entries[s.name] = {
|
|
24
|
+
url: s.url,
|
|
25
|
+
...(s.headers ? { headers: s.headers } : {}),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
mergeJsonMcpServers(configPath, entries, 'mcpServers');
|
|
29
|
+
}
|
|
30
|
+
async writeSkills(skills, scope) {
|
|
31
|
+
if (scope === 'user')
|
|
32
|
+
return;
|
|
33
|
+
const rulesDir = join(process.cwd(), '.clinerules');
|
|
34
|
+
mkdirSync(rulesDir, { recursive: true });
|
|
35
|
+
for (const skill of skills) {
|
|
36
|
+
writeFileSync(join(rulesDir, `${skill.filename}.md`), buildSkillMd(skill));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async writeInstructionHint(hint, scope) {
|
|
40
|
+
if (scope === 'user')
|
|
41
|
+
return;
|
|
42
|
+
const filePath = join(process.cwd(), '.clinerules', 'runwork.md');
|
|
43
|
+
mkdirSync(join(filePath, '..'), { recursive: true });
|
|
44
|
+
writeFileSync(filePath, hint);
|
|
45
|
+
}
|
|
46
|
+
async writeTeamInstructions(instructions, scope) {
|
|
47
|
+
if (scope === 'user')
|
|
48
|
+
return;
|
|
49
|
+
const filePath = join(process.cwd(), '.clinerules', 'runwork-team.md');
|
|
50
|
+
mkdirSync(join(filePath, '..'), { recursive: true });
|
|
51
|
+
writeFileSync(filePath, instructions);
|
|
52
|
+
}
|
|
53
|
+
// ── Agent config (globalState.json) ────────────────────────────────
|
|
54
|
+
async writeAgentConfig(config, scope) {
|
|
55
|
+
if (scope !== 'user')
|
|
56
|
+
return;
|
|
57
|
+
const globalStatePath = join(homedir(), '.cline', 'data', 'globalState.json');
|
|
58
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
59
|
+
let state = {};
|
|
60
|
+
if (existsSync(globalStatePath)) {
|
|
61
|
+
try {
|
|
62
|
+
state = JSON.parse(readFileSync(globalStatePath, 'utf-8'));
|
|
63
|
+
}
|
|
64
|
+
catch { /* start fresh */ }
|
|
65
|
+
}
|
|
66
|
+
if (config.modelPreference) {
|
|
67
|
+
state.apiModelId = config.modelPreference;
|
|
68
|
+
}
|
|
69
|
+
if (config.permissionRules) {
|
|
70
|
+
if (!state.autoApprovalSettings)
|
|
71
|
+
state.autoApprovalSettings = {};
|
|
72
|
+
if (!state.autoApprovalSettings.actions)
|
|
73
|
+
state.autoApprovalSettings.actions = {};
|
|
74
|
+
if (config.permissionRules.defaultMode === 'auto') {
|
|
75
|
+
state.autoApprovalSettings.enabled = true;
|
|
76
|
+
state.autoApprovalSettings.actions.readFiles = true;
|
|
77
|
+
state.autoApprovalSettings.actions.editFiles = true;
|
|
78
|
+
state.autoApprovalSettings.actions.executeSafeCommands = true;
|
|
79
|
+
state.autoApprovalSettings.actions.useMcp = true;
|
|
80
|
+
}
|
|
81
|
+
else if (config.permissionRules.defaultMode === 'default') {
|
|
82
|
+
state.autoApprovalSettings.enabled = false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
mkdirSync(join(globalStatePath, '..'), { recursive: true });
|
|
86
|
+
writeFileSync(globalStatePath, JSON.stringify(state, null, 2));
|
|
87
|
+
}
|
|
88
|
+
}
|
package/dist/agents/codex.d.ts
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import type { AgentAdapter, McpServerEntry, SkillFile } from './types.js';
|
|
1
|
+
import type { AgentAdapter, AgentConfigOverride, AgentUsageStats, McpServerEntry, SkillFile } from './types.js';
|
|
2
2
|
export declare class CodexAdapter implements AgentAdapter {
|
|
3
3
|
name: string;
|
|
4
4
|
slug: string;
|
|
5
5
|
detect(): Promise<boolean>;
|
|
6
6
|
supportsMcpScope(_scope: 'project' | 'user'): boolean;
|
|
7
7
|
supportsSkills(): boolean;
|
|
8
|
-
writeMcpServers(servers: McpServerEntry[],
|
|
8
|
+
writeMcpServers(servers: McpServerEntry[], _scope: 'project' | 'user'): Promise<void>;
|
|
9
9
|
writeSkills(skills: SkillFile[], scope: 'project' | 'user'): Promise<void>;
|
|
10
|
+
writeInstructionHint(hint: string, scope: 'project' | 'user'): Promise<void>;
|
|
11
|
+
writeTeamInstructions(instructions: string, scope: 'project' | 'user'): Promise<void>;
|
|
12
|
+
writeAgentConfig(config: AgentConfigOverride, scope: 'project' | 'user'): Promise<void>;
|
|
13
|
+
readUsageStats(lastSyncAt: string | null): Promise<AgentUsageStats | null>;
|
|
14
|
+
readVersion(): Promise<string | null>;
|
|
10
15
|
}
|
package/dist/agents/codex.js
CHANGED
|
@@ -1,20 +1,16 @@
|
|
|
1
|
-
import { mkdirSync, writeFileSync } from 'fs';
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { homedir } from 'os';
|
|
4
|
-
import {
|
|
4
|
+
import { parse, stringify } from 'smol-toml';
|
|
5
5
|
import { buildSkillMd } from './types.js';
|
|
6
|
-
import {
|
|
6
|
+
import { writeHintToFile, writeTeamInstructionsToFile } from './utils/instruction-hint.js';
|
|
7
|
+
import { querySqlite } from '../utils/sqlite.js';
|
|
8
|
+
import { whichBinary } from '../utils/which.js';
|
|
7
9
|
export class CodexAdapter {
|
|
8
|
-
name = 'Codex
|
|
10
|
+
name = 'Codex';
|
|
9
11
|
slug = 'codex';
|
|
10
12
|
async detect() {
|
|
11
|
-
|
|
12
|
-
execFileSync('which', ['codex'], { stdio: 'pipe' });
|
|
13
|
-
return true;
|
|
14
|
-
}
|
|
15
|
-
catch {
|
|
16
|
-
return false;
|
|
17
|
-
}
|
|
13
|
+
return !!whichBinary('codex');
|
|
18
14
|
}
|
|
19
15
|
supportsMcpScope(_scope) {
|
|
20
16
|
return true;
|
|
@@ -22,15 +18,31 @@ export class CodexAdapter {
|
|
|
22
18
|
supportsSkills() {
|
|
23
19
|
return true;
|
|
24
20
|
}
|
|
25
|
-
async writeMcpServers(servers,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
21
|
+
async writeMcpServers(servers, _scope) {
|
|
22
|
+
// Codex uses config.toml with [mcp_servers.*] sections
|
|
23
|
+
const configPath = join(homedir(), '.codex', 'config.toml');
|
|
24
|
+
let parsed = {};
|
|
25
|
+
if (existsSync(configPath)) {
|
|
26
|
+
parsed = parse(readFileSync(configPath, 'utf-8'));
|
|
27
|
+
}
|
|
28
|
+
if (!parsed.mcp_servers || typeof parsed.mcp_servers !== 'object') {
|
|
29
|
+
parsed.mcp_servers = {};
|
|
30
|
+
}
|
|
31
|
+
const mcpServers = parsed.mcp_servers;
|
|
32
|
+
// Remove existing runwork-* entries
|
|
33
|
+
for (const key of Object.keys(mcpServers)) {
|
|
34
|
+
if (key.startsWith('runwork-'))
|
|
35
|
+
delete mcpServers[key];
|
|
36
|
+
}
|
|
37
|
+
// Add new entries
|
|
30
38
|
for (const s of servers) {
|
|
31
|
-
|
|
39
|
+
mcpServers[s.name] = {
|
|
40
|
+
url: s.url,
|
|
41
|
+
...(s.headers?.Authorization ? { bearer_token_env_var: 'RUNWORK_MCP_TOKEN' } : {}),
|
|
42
|
+
};
|
|
32
43
|
}
|
|
33
|
-
|
|
44
|
+
mkdirSync(join(configPath, '..'), { recursive: true });
|
|
45
|
+
writeFileSync(configPath, stringify(parsed));
|
|
34
46
|
}
|
|
35
47
|
async writeSkills(skills, scope) {
|
|
36
48
|
const baseDir = scope === 'project'
|
|
@@ -42,4 +54,104 @@ export class CodexAdapter {
|
|
|
42
54
|
writeFileSync(join(skillDir, 'SKILL.md'), buildSkillMd(skill));
|
|
43
55
|
}
|
|
44
56
|
}
|
|
57
|
+
async writeInstructionHint(hint, scope) {
|
|
58
|
+
const filePath = scope === 'project'
|
|
59
|
+
? join(process.cwd(), 'AGENTS.md')
|
|
60
|
+
: join(homedir(), '.codex', 'instructions.md');
|
|
61
|
+
writeHintToFile(filePath, hint);
|
|
62
|
+
}
|
|
63
|
+
async writeTeamInstructions(instructions, scope) {
|
|
64
|
+
const filePath = scope === 'project'
|
|
65
|
+
? join(process.cwd(), 'AGENTS.md')
|
|
66
|
+
: join(homedir(), '.codex', 'instructions.md');
|
|
67
|
+
writeTeamInstructionsToFile(filePath, instructions);
|
|
68
|
+
}
|
|
69
|
+
// ── Agent config (TOML) ───────────────────────────────────────────
|
|
70
|
+
async writeAgentConfig(config, scope) {
|
|
71
|
+
const configPath = scope === 'project'
|
|
72
|
+
? join(process.cwd(), '.codex', 'config.toml')
|
|
73
|
+
: join(homedir(), '.codex', 'config.toml');
|
|
74
|
+
let parsed = {};
|
|
75
|
+
if (existsSync(configPath)) {
|
|
76
|
+
parsed = parse(readFileSync(configPath, 'utf-8'));
|
|
77
|
+
}
|
|
78
|
+
if (config.modelPreference) {
|
|
79
|
+
parsed.model = config.modelPreference;
|
|
80
|
+
}
|
|
81
|
+
if (config.permissionRules?.defaultMode) {
|
|
82
|
+
const modeMap = {
|
|
83
|
+
auto: 'never',
|
|
84
|
+
default: 'on-request',
|
|
85
|
+
plan: 'untrusted',
|
|
86
|
+
};
|
|
87
|
+
parsed.approval_policy = modeMap[config.permissionRules.defaultMode] ?? config.permissionRules.defaultMode;
|
|
88
|
+
}
|
|
89
|
+
mkdirSync(join(configPath, '..'), { recursive: true });
|
|
90
|
+
writeFileSync(configPath, stringify(parsed));
|
|
91
|
+
}
|
|
92
|
+
// ── Usage stats (passive, best-effort) ─────────────────────────────
|
|
93
|
+
async readUsageStats(lastSyncAt) {
|
|
94
|
+
try {
|
|
95
|
+
const codexDir = join(homedir(), '.codex');
|
|
96
|
+
if (!existsSync(codexDir))
|
|
97
|
+
return null;
|
|
98
|
+
const sinceMs = lastSyncAt ? new Date(lastSyncAt).getTime() : 0;
|
|
99
|
+
const sinceSec = Math.floor(sinceMs / 1000);
|
|
100
|
+
const dbPath = join(codexDir, 'state_5.sqlite');
|
|
101
|
+
if (!existsSync(dbPath))
|
|
102
|
+
return null;
|
|
103
|
+
const countResult = querySqlite(dbPath, `SELECT count(*) FROM threads WHERE created_at > ${sinceSec}`);
|
|
104
|
+
const sessionCount = parseInt(countResult) || 0;
|
|
105
|
+
const tokensResult = querySqlite(dbPath, `SELECT coalesce(sum(tokens_used), 0) FROM threads WHERE created_at > ${sinceSec}`);
|
|
106
|
+
const tokensUsed = parseInt(tokensResult) || 0;
|
|
107
|
+
const latestResult = querySqlite(dbPath, `SELECT max(updated_at) FROM threads`);
|
|
108
|
+
const latestSec = parseInt(latestResult) || 0;
|
|
109
|
+
const versionsResult = querySqlite(dbPath, `SELECT DISTINCT cli_version FROM threads WHERE cli_version IS NOT NULL ORDER BY created_at DESC LIMIT 3`);
|
|
110
|
+
const versions = versionsResult ? versionsResult.split(/[\r\n]+/).filter(Boolean) : [];
|
|
111
|
+
let messageCount = 0;
|
|
112
|
+
const historyPath = join(codexDir, 'history.jsonl');
|
|
113
|
+
if (existsSync(historyPath)) {
|
|
114
|
+
const content = readFileSync(historyPath, 'utf-8').trim();
|
|
115
|
+
if (content) {
|
|
116
|
+
for (const line of content.split(/[\r\n]+/)) {
|
|
117
|
+
try {
|
|
118
|
+
const entry = JSON.parse(line);
|
|
119
|
+
if (entry.ts && entry.ts > sinceSec)
|
|
120
|
+
messageCount++;
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (sessionCount === 0 && (latestSec * 1000) <= sinceMs)
|
|
129
|
+
return null;
|
|
130
|
+
return {
|
|
131
|
+
hasNewActivity: sessionCount > 0,
|
|
132
|
+
sessionCount,
|
|
133
|
+
messageCount,
|
|
134
|
+
toolCallCount: 0,
|
|
135
|
+
tokensUsed,
|
|
136
|
+
aiLinesAdded: 0,
|
|
137
|
+
aiLinesRemoved: 0,
|
|
138
|
+
lastActiveAt: latestSec > 0 ? new Date(latestSec * 1000).toISOString() : null,
|
|
139
|
+
extra: versions.length > 0 ? { cliVersions: versions } : undefined,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
async readVersion() {
|
|
147
|
+
try {
|
|
148
|
+
const versionPath = join(homedir(), '.codex', 'version.json');
|
|
149
|
+
if (existsSync(versionPath)) {
|
|
150
|
+
const data = JSON.parse(readFileSync(versionPath, 'utf-8'));
|
|
151
|
+
return data.latest_version ?? null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch { /* best-effort */ }
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
45
157
|
}
|
package/dist/agents/cursor.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentAdapter, McpServerEntry, SkillFile } from './types.js';
|
|
1
|
+
import type { AgentAdapter, AgentConfigOverride, AgentUsageStats, McpServerEntry, SkillFile } from './types.js';
|
|
2
2
|
export declare class CursorAdapter implements AgentAdapter {
|
|
3
3
|
name: string;
|
|
4
4
|
slug: string;
|
|
@@ -7,4 +7,9 @@ export declare class CursorAdapter implements AgentAdapter {
|
|
|
7
7
|
supportsSkills(): boolean;
|
|
8
8
|
writeMcpServers(servers: McpServerEntry[], scope: 'project' | 'user'): Promise<void>;
|
|
9
9
|
writeSkills(skills: SkillFile[], scope: 'project' | 'user'): Promise<void>;
|
|
10
|
+
writeInstructionHint(hint: string, scope: 'project' | 'user'): Promise<void>;
|
|
11
|
+
writeTeamInstructions(instructions: string, scope: 'project' | 'user'): Promise<void>;
|
|
12
|
+
writeAgentConfig(config: AgentConfigOverride, scope: 'project' | 'user'): Promise<void>;
|
|
13
|
+
readUsageStats(lastSyncAt: string | null): Promise<AgentUsageStats | null>;
|
|
14
|
+
readVersion(): Promise<string | null>;
|
|
10
15
|
}
|