runwork 0.8.4 → 0.9.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.js +173 -2
- package/dist/agents/__tests__/codex-stats.test.js +93 -0
- package/dist/agents/claude-code.js +140 -19
- package/dist/agents/claude-desktop.d.ts +2 -1
- package/dist/agents/claude-desktop.js +57 -0
- package/dist/agents/codex.d.ts +28 -1
- package/dist/agents/codex.js +213 -2
- package/dist/agents/detect.js +2 -1
- package/dist/agents/detection.d.ts +17 -0
- package/dist/agents/detection.js +89 -0
- package/dist/agents/generic-adapter.js +3 -13
- package/dist/agents/registry-data.d.ts +126 -0
- package/dist/agents/registry-data.js +436 -0
- package/dist/agents/registry.d.ts +8 -48
- package/dist/agents/registry.js +9 -192
- package/dist/agents/types.d.ts +12 -0
- package/dist/auth/store.js +12 -1
- package/dist/commands/init.js +4 -0
- package/dist/commands/sync.js +21 -1
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/utils/which.d.ts +8 -0
- package/dist/utils/which.js +29 -0
- package/package.json +1 -1
package/dist/agents/codex.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'fs';
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { homedir } from 'os';
|
|
4
4
|
import { parse, stringify } from 'smol-toml';
|
|
@@ -10,7 +10,9 @@ function isRunworkManagedCodexKey(key) {
|
|
|
10
10
|
}
|
|
11
11
|
import { writeHintToFile, writeTeamInstructionsToFile, removeHintFromFile, removeTeamInstructionsFromFile } from './utils/instruction-hint.js';
|
|
12
12
|
import { querySqlite } from '../utils/sqlite.js';
|
|
13
|
-
import { whichBinary } from '../utils/which.js';
|
|
13
|
+
import { whichBinary, isAppRunning } from '../utils/which.js';
|
|
14
|
+
import { runAgentDetection } from './detection.js';
|
|
15
|
+
import { getRegistryAgent } from './registry.js';
|
|
14
16
|
export class CodexAdapter {
|
|
15
17
|
name = 'Codex';
|
|
16
18
|
slug = 'codex';
|
|
@@ -107,6 +109,18 @@ export class CodexAdapter {
|
|
|
107
109
|
};
|
|
108
110
|
parsed.approval_policy = modeMap[config.permissionRules.defaultMode] ?? config.permissionRules.defaultMode;
|
|
109
111
|
}
|
|
112
|
+
// Apply minimum permission floors: only upgrade, never downgrade
|
|
113
|
+
if (config.minimumPermissions) {
|
|
114
|
+
for (const { field, order, minimum } of config.minimumPermissions) {
|
|
115
|
+
const current = typeof parsed[field] === 'string' ? parsed[field] : '';
|
|
116
|
+
const currentIdx = order.indexOf(current);
|
|
117
|
+
const minimumIdx = order.indexOf(minimum);
|
|
118
|
+
// Upgrade if current is unknown (not in order) or more restrictive than minimum
|
|
119
|
+
if (currentIdx < minimumIdx) {
|
|
120
|
+
parsed[field] = minimum;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
110
124
|
mkdirSync(join(configPath, '..'), { recursive: true });
|
|
111
125
|
writeFileSync(configPath, stringify(parsed));
|
|
112
126
|
}
|
|
@@ -225,4 +239,201 @@ export class CodexAdapter {
|
|
|
225
239
|
catch { /* best-effort */ }
|
|
226
240
|
return null;
|
|
227
241
|
}
|
|
242
|
+
async readSkillUsage(lastSyncAt) {
|
|
243
|
+
// Codex rollout JSONL files contain a session_meta entry whose
|
|
244
|
+
// `instructions` field lists all skills available in the session.
|
|
245
|
+
// Skills appear as "- name: description (file: path/SKILL.md)".
|
|
246
|
+
// This gives us reach data (which skills were loaded), not invocation.
|
|
247
|
+
//
|
|
248
|
+
// Rollout files live at ~/.codex/sessions/<year>/<month>/<day>/rollout-*.jsonl
|
|
249
|
+
// and are linked from the threads table via rollout_path.
|
|
250
|
+
try {
|
|
251
|
+
const sessionsDir = join(homedir(), '.codex', 'sessions');
|
|
252
|
+
if (!existsSync(sessionsDir))
|
|
253
|
+
return null;
|
|
254
|
+
const sinceMs = lastSyncAt ? new Date(lastSyncAt).getTime() : 0;
|
|
255
|
+
const skillCounts = new Map();
|
|
256
|
+
// Walk session directories by year/month/day
|
|
257
|
+
const walkDir = (dir) => {
|
|
258
|
+
let entries;
|
|
259
|
+
try {
|
|
260
|
+
entries = readdirSync(dir);
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
for (const entry of entries) {
|
|
266
|
+
const fullPath = join(dir, entry);
|
|
267
|
+
if (entry.endsWith('.jsonl')) {
|
|
268
|
+
let fileStat;
|
|
269
|
+
try {
|
|
270
|
+
fileStat = statSync(fullPath);
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
if (fileStat.mtimeMs <= sinceMs)
|
|
276
|
+
continue;
|
|
277
|
+
this.parseRolloutForSkills(fullPath, sinceMs, skillCounts);
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
// Recurse into year/month/day subdirectories
|
|
281
|
+
try {
|
|
282
|
+
if (statSync(fullPath).isDirectory())
|
|
283
|
+
walkDir(fullPath);
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
walkDir(sessionsDir);
|
|
292
|
+
if (skillCounts.size === 0)
|
|
293
|
+
return null;
|
|
294
|
+
const results = [];
|
|
295
|
+
for (const [skillName, { count, lastTs }] of skillCounts) {
|
|
296
|
+
results.push({
|
|
297
|
+
skillName,
|
|
298
|
+
count,
|
|
299
|
+
lastUsedAt: new Date(lastTs).toISOString(),
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
return results;
|
|
303
|
+
}
|
|
304
|
+
catch {
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
/** Parse a rollout JSONL file for skill data */
|
|
309
|
+
parseRolloutForSkills(filePath, sinceMs, skillCounts) {
|
|
310
|
+
let content;
|
|
311
|
+
try {
|
|
312
|
+
content = readFileSync(filePath, 'utf-8');
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
for (const line of content.split('\n')) {
|
|
318
|
+
if (!line)
|
|
319
|
+
continue;
|
|
320
|
+
let entry;
|
|
321
|
+
try {
|
|
322
|
+
entry = JSON.parse(line);
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
if (entry.type !== 'session_meta')
|
|
328
|
+
continue;
|
|
329
|
+
const payload = entry.payload;
|
|
330
|
+
if (!payload)
|
|
331
|
+
continue;
|
|
332
|
+
const tsRaw = entry.timestamp;
|
|
333
|
+
const ts = typeof tsRaw === 'string' ? Date.parse(tsRaw) : 0;
|
|
334
|
+
if (ts <= sinceMs)
|
|
335
|
+
continue;
|
|
336
|
+
const instructions = payload.instructions;
|
|
337
|
+
if (typeof instructions !== 'string')
|
|
338
|
+
continue;
|
|
339
|
+
// Parse "- name: description (file: path/SKILL.md)" entries
|
|
340
|
+
// from the "### Available skills" section
|
|
341
|
+
const skillRegex = /^- ([^:]+):\s+.+\(file:\s+.+\/SKILL\.md\)/gm;
|
|
342
|
+
let match;
|
|
343
|
+
while ((match = skillRegex.exec(instructions)) !== null) {
|
|
344
|
+
const skillName = match[1].trim();
|
|
345
|
+
const existing = skillCounts.get(skillName);
|
|
346
|
+
if (existing) {
|
|
347
|
+
existing.count++;
|
|
348
|
+
if (ts > existing.lastTs)
|
|
349
|
+
existing.lastTs = ts;
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
skillCounts.set(skillName, { count: 1, lastTs: ts });
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
// Only process the first session_meta per file
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
// ── Codex Desktop app workspace registration ──────────────────────
|
|
360
|
+
/**
|
|
361
|
+
* Register a workspace directory in the Codex desktop app's project list.
|
|
362
|
+
* Adds the path to electron-saved-workspace-roots, project-order, and
|
|
363
|
+
* electron-workspace-root-labels. Skips active-workspace-roots to avoid
|
|
364
|
+
* force-switching the user's active project.
|
|
365
|
+
*
|
|
366
|
+
* Returns 'written' if changes were made, 'already_registered' if the
|
|
367
|
+
* path was already present, or 'app_running' if Codex is open and would
|
|
368
|
+
* overwrite our changes.
|
|
369
|
+
*/
|
|
370
|
+
registerDesktopWorkspace(workspacePath, label) {
|
|
371
|
+
const statePath = join(homedir(), '.codex', '.codex-global-state.json');
|
|
372
|
+
// Read current state (or start fresh if file doesn't exist)
|
|
373
|
+
let state = {};
|
|
374
|
+
if (existsSync(statePath)) {
|
|
375
|
+
try {
|
|
376
|
+
state = JSON.parse(readFileSync(statePath, 'utf-8'));
|
|
377
|
+
}
|
|
378
|
+
catch {
|
|
379
|
+
return 'app_running'; // corrupt file, don't touch
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
// Check if already registered in all required keys
|
|
383
|
+
const savedRoots = (Array.isArray(state['electron-saved-workspace-roots'])
|
|
384
|
+
? state['electron-saved-workspace-roots'] : []);
|
|
385
|
+
const projectOrder = (Array.isArray(state['project-order'])
|
|
386
|
+
? state['project-order'] : []);
|
|
387
|
+
const labels = (state['electron-workspace-root-labels'] && typeof state['electron-workspace-root-labels'] === 'object'
|
|
388
|
+
? state['electron-workspace-root-labels'] : {});
|
|
389
|
+
const inSaved = savedRoots.includes(workspacePath);
|
|
390
|
+
const inOrder = projectOrder.includes(workspacePath);
|
|
391
|
+
const inLabels = labels[workspacePath] === label;
|
|
392
|
+
if (inSaved && inOrder && inLabels) {
|
|
393
|
+
return 'already_registered';
|
|
394
|
+
}
|
|
395
|
+
// Codex desktop app holds this file in memory and overwrites on any state
|
|
396
|
+
// change. Writing while it's open is futile. Check if it's running.
|
|
397
|
+
if (isAppRunning('Codex')) {
|
|
398
|
+
return 'app_running';
|
|
399
|
+
}
|
|
400
|
+
// Merge our workspace into the state
|
|
401
|
+
if (!inSaved) {
|
|
402
|
+
savedRoots.push(workspacePath);
|
|
403
|
+
state['electron-saved-workspace-roots'] = savedRoots;
|
|
404
|
+
}
|
|
405
|
+
if (!inOrder) {
|
|
406
|
+
projectOrder.push(workspacePath);
|
|
407
|
+
state['project-order'] = projectOrder;
|
|
408
|
+
}
|
|
409
|
+
labels[workspacePath] = label;
|
|
410
|
+
state['electron-workspace-root-labels'] = labels;
|
|
411
|
+
mkdirSync(join(statePath, '..'), { recursive: true });
|
|
412
|
+
writeFileSync(statePath, JSON.stringify(state));
|
|
413
|
+
return 'written';
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Codex Desktop (OpenAI's GUI app) reads its config from the same `~/.codex`
|
|
418
|
+
* directory as the Codex CLI, so every sync/cleanup behavior inherits from
|
|
419
|
+
* CodexAdapter unchanged. Only the identity (slug/name) and the installation
|
|
420
|
+
* detection differ -- the desktop app is detected by application bundle
|
|
421
|
+
* presence, not by a `codex` binary on PATH.
|
|
422
|
+
*/
|
|
423
|
+
export class CodexDesktopAdapter extends CodexAdapter {
|
|
424
|
+
name = 'Codex';
|
|
425
|
+
slug = 'codex-app';
|
|
426
|
+
async detect() {
|
|
427
|
+
const def = getRegistryAgent('codex-app');
|
|
428
|
+
if (!def)
|
|
429
|
+
return false;
|
|
430
|
+
return runAgentDetection(def.detection);
|
|
431
|
+
}
|
|
432
|
+
// Usage stats come from ~/.codex/state_5.sqlite, which is shared between
|
|
433
|
+
// Codex CLI and Codex Desktop. Leaving the read here would double-count
|
|
434
|
+
// sessions/tokens whenever both adapters are detected on the same machine.
|
|
435
|
+
// Defer to CodexAdapter (slug 'codex') as the sole source of Codex stats.
|
|
436
|
+
async readUsageStats() {
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
228
439
|
}
|
package/dist/agents/detect.js
CHANGED
|
@@ -2,7 +2,7 @@ import { ClaudeCodeAdapter } from './claude-code.js';
|
|
|
2
2
|
import { ClaudeDesktopAdapter } from './claude-desktop.js';
|
|
3
3
|
import { CursorAdapter } from './cursor.js';
|
|
4
4
|
import { WindsurfAdapter } from './windsurf.js';
|
|
5
|
-
import { CodexAdapter } from './codex.js';
|
|
5
|
+
import { CodexAdapter, CodexDesktopAdapter } from './codex.js';
|
|
6
6
|
import { ClineAdapter } from './cline.js';
|
|
7
7
|
import { GeminiAdapter } from './gemini.js';
|
|
8
8
|
import { GenericAgentAdapter } from './generic-adapter.js';
|
|
@@ -17,6 +17,7 @@ const CUSTOM_ADAPTERS = [
|
|
|
17
17
|
new CursorAdapter(),
|
|
18
18
|
new WindsurfAdapter(),
|
|
19
19
|
new CodexAdapter(),
|
|
20
|
+
new CodexDesktopAdapter(),
|
|
20
21
|
new ClineAdapter(),
|
|
21
22
|
new GeminiAdapter(),
|
|
22
23
|
];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared agent-installation detection used by the generic adapter and by
|
|
3
|
+
* custom adapters that opt in to the registry's declared detection rules
|
|
4
|
+
* (e.g. Codex Desktop, which detects via `/Applications/Codex.app` on macOS
|
|
5
|
+
* and the AppX package on Windows rather than a `codex` binary on PATH).
|
|
6
|
+
*
|
|
7
|
+
* Mirrors the desktop's agent-detection.ts logic but uses Node APIs instead
|
|
8
|
+
* of Tauri plugins.
|
|
9
|
+
*/
|
|
10
|
+
import type { AgentDetection } from './registry-data.js';
|
|
11
|
+
/**
|
|
12
|
+
* Synchronous detection — returns true when the agent is installed according
|
|
13
|
+
* to its registry `detection` rules. Supports every method the desktop app
|
|
14
|
+
* understands: `binary`, `path`, `windows-appx`, `windows-start-app`, and
|
|
15
|
+
* nested `any` combinators. Windows-only methods return false off Windows.
|
|
16
|
+
*/
|
|
17
|
+
export declare function runAgentDetection(detection: AgentDetection): boolean;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared agent-installation detection used by the generic adapter and by
|
|
3
|
+
* custom adapters that opt in to the registry's declared detection rules
|
|
4
|
+
* (e.g. Codex Desktop, which detects via `/Applications/Codex.app` on macOS
|
|
5
|
+
* and the AppX package on Windows rather than a `codex` binary on PATH).
|
|
6
|
+
*
|
|
7
|
+
* Mirrors the desktop's agent-detection.ts logic but uses Node APIs instead
|
|
8
|
+
* of Tauri plugins.
|
|
9
|
+
*/
|
|
10
|
+
import { execFileSync } from 'child_process';
|
|
11
|
+
import { existsSync } from 'fs';
|
|
12
|
+
import { homedir, platform } from 'os';
|
|
13
|
+
import { isAbsolute, join } from 'path';
|
|
14
|
+
import { whichBinary } from '../utils/which.js';
|
|
15
|
+
import { resolvePlatformString } from './registry.js';
|
|
16
|
+
function isWindows() {
|
|
17
|
+
return platform() === 'win32';
|
|
18
|
+
}
|
|
19
|
+
function powershellQuote(value) {
|
|
20
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
21
|
+
}
|
|
22
|
+
function toList(value) {
|
|
23
|
+
return Array.isArray(value) ? value : [value];
|
|
24
|
+
}
|
|
25
|
+
function runPowerShell(script) {
|
|
26
|
+
try {
|
|
27
|
+
execFileSync('powershell', ['-NoProfile', '-Command', script], { stdio: 'pipe' });
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function checkPath(target) {
|
|
35
|
+
const resolved = resolvePlatformString(target);
|
|
36
|
+
if (!resolved)
|
|
37
|
+
return false;
|
|
38
|
+
if (isAbsolute(resolved))
|
|
39
|
+
return existsSync(resolved);
|
|
40
|
+
return existsSync(join(homedir(), resolved));
|
|
41
|
+
}
|
|
42
|
+
function checkWindowsAppxPackage(target) {
|
|
43
|
+
if (!isWindows())
|
|
44
|
+
return false;
|
|
45
|
+
for (const pkg of toList(target)) {
|
|
46
|
+
const script = `$p = Get-AppxPackage -Name ${powershellQuote(pkg)} -ErrorAction SilentlyContinue; if ($null -ne $p) { exit 0 } exit 1`;
|
|
47
|
+
if (runPowerShell(script))
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
function checkWindowsStartApp(target) {
|
|
53
|
+
if (!isWindows())
|
|
54
|
+
return false;
|
|
55
|
+
for (const pattern of toList(target)) {
|
|
56
|
+
const script = `$a = Get-StartApps -Name ${powershellQuote(pattern)} -ErrorAction SilentlyContinue | Select-Object -First 1; if ($null -ne $a) { exit 0 } exit 1`;
|
|
57
|
+
if (runPowerShell(script))
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Synchronous detection — returns true when the agent is installed according
|
|
64
|
+
* to its registry `detection` rules. Supports every method the desktop app
|
|
65
|
+
* understands: `binary`, `path`, `windows-appx`, `windows-start-app`, and
|
|
66
|
+
* nested `any` combinators. Windows-only methods return false off Windows.
|
|
67
|
+
*/
|
|
68
|
+
export function runAgentDetection(detection) {
|
|
69
|
+
switch (detection.method) {
|
|
70
|
+
case 'binary': {
|
|
71
|
+
const target = resolvePlatformString(detection.target);
|
|
72
|
+
return !!target && !!whichBinary(target);
|
|
73
|
+
}
|
|
74
|
+
case 'path':
|
|
75
|
+
return checkPath(detection.target);
|
|
76
|
+
case 'windows-appx':
|
|
77
|
+
return checkWindowsAppxPackage(detection.target);
|
|
78
|
+
case 'windows-start-app':
|
|
79
|
+
return checkWindowsStartApp(detection.target);
|
|
80
|
+
case 'any':
|
|
81
|
+
for (const probe of detection.target) {
|
|
82
|
+
if (runAgentDetection(probe))
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
return false;
|
|
86
|
+
default:
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readdirSync, rmSync, writeFileSync } from 'fs';
|
|
2
|
-
import {
|
|
2
|
+
import { join } from 'path';
|
|
3
3
|
import { homedir } from 'os';
|
|
4
|
-
import { whichBinary } from '../utils/which.js';
|
|
5
4
|
import { buildSkillMd } from './types.js';
|
|
6
5
|
import { mergeJsonMcpServers, removeRunworkMcpServers } from './utils/json-config.js';
|
|
7
6
|
import { writeHintToFile, writeTeamInstructionsToFile, removeHintFromFile, removeTeamInstructionsFromFile } from './utils/instruction-hint.js';
|
|
8
7
|
import { resolvePlatformString, resolveToAbsolute } from './registry.js';
|
|
8
|
+
import { runAgentDetection } from './detection.js';
|
|
9
9
|
/**
|
|
10
10
|
* Generic adapter that handles any agent from the registry using its
|
|
11
11
|
* skillsPaths, mcpConfigPath, and instructionFile metadata.
|
|
@@ -25,17 +25,7 @@ export class GenericAgentAdapter {
|
|
|
25
25
|
this.slug = def.slug;
|
|
26
26
|
}
|
|
27
27
|
async detect() {
|
|
28
|
-
|
|
29
|
-
if (!target)
|
|
30
|
-
return false;
|
|
31
|
-
if (this.def.detection.method === 'binary') {
|
|
32
|
-
return !!whichBinary(target);
|
|
33
|
-
}
|
|
34
|
-
// path detection
|
|
35
|
-
if (isAbsolute(target)) {
|
|
36
|
-
return existsSync(target);
|
|
37
|
-
}
|
|
38
|
-
return existsSync(join(homedir(), target));
|
|
28
|
+
return runAgentDetection(this.def.detection);
|
|
39
29
|
}
|
|
40
30
|
supportsMcpScope(scope) {
|
|
41
31
|
if (!this.def.mcpConfigPath)
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified agent registry (types + data + browser-safe lookups).
|
|
3
|
+
*
|
|
4
|
+
* This file is the SINGLE SOURCE OF TRUTH for every agent Runwork knows about:
|
|
5
|
+
* CLI adapters read from here (via `./registry.js`), and the desktop app
|
|
6
|
+
* re-exports from here (via the `@cli-agent-registry` path alias).
|
|
7
|
+
*
|
|
8
|
+
* Keep this file browser-safe: no `os`, `path`, `fs`, or other Node-only
|
|
9
|
+
* imports. Node-only path resolvers live next to this file in `./registry.ts`.
|
|
10
|
+
*/
|
|
11
|
+
export type AgentCategory = 'cli' | 'ide' | 'desktop' | 'extension';
|
|
12
|
+
/** Platform-specific string. Plain string = same on all platforms. */
|
|
13
|
+
export type PlatformString = string | {
|
|
14
|
+
default?: string;
|
|
15
|
+
macos?: string;
|
|
16
|
+
windows?: string;
|
|
17
|
+
linux?: string;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Detection strategy for an agent. Supports simple binary/path checks plus
|
|
21
|
+
* richer Windows-specific methods and a nested `any` combinator used by the
|
|
22
|
+
* desktop app to detect agents that ship as both GUI app and CLI binary.
|
|
23
|
+
*/
|
|
24
|
+
export type AgentDetection = {
|
|
25
|
+
method: 'binary' | 'path';
|
|
26
|
+
target: PlatformString;
|
|
27
|
+
} | {
|
|
28
|
+
method: 'windows-appx' | 'windows-start-app';
|
|
29
|
+
target: string | string[];
|
|
30
|
+
} | {
|
|
31
|
+
method: 'any';
|
|
32
|
+
target: AgentDetection[];
|
|
33
|
+
};
|
|
34
|
+
export interface AgentLaunch {
|
|
35
|
+
app?: {
|
|
36
|
+
macos?: string;
|
|
37
|
+
windows?: string;
|
|
38
|
+
linux?: string;
|
|
39
|
+
};
|
|
40
|
+
cli?: string;
|
|
41
|
+
cliAcceptsPrompt?: boolean;
|
|
42
|
+
}
|
|
43
|
+
export interface AgentQuickStart {
|
|
44
|
+
launchHint?: string;
|
|
45
|
+
panelHint?: string;
|
|
46
|
+
skillCommand?: string;
|
|
47
|
+
examplePrompts?: string[];
|
|
48
|
+
}
|
|
49
|
+
export interface ManualSetupStep {
|
|
50
|
+
id: string;
|
|
51
|
+
label: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Optional downloadable artifact generated inline during a manual setup step
|
|
55
|
+
* (e.g. a plugin zip the user uploads into the agent).
|
|
56
|
+
*/
|
|
57
|
+
export interface AgentManualSetupArtifact {
|
|
58
|
+
label: string;
|
|
59
|
+
filename: string;
|
|
60
|
+
/** CLI subcommand that generates the artifact, e.g. "build-plugin" */
|
|
61
|
+
command: string;
|
|
62
|
+
}
|
|
63
|
+
/** Where a manual-setup step is inserted in the onboarding journey. */
|
|
64
|
+
export type ManualSetupSlot = 'installation' | 'connecting' | 'try-it';
|
|
65
|
+
export interface AgentManualSetup {
|
|
66
|
+
title: string;
|
|
67
|
+
steps: ManualSetupStep[];
|
|
68
|
+
opensAgent?: boolean;
|
|
69
|
+
/** Where in the journey to show this step. Defaults to 'try-it'. */
|
|
70
|
+
showAfter?: ManualSetupSlot;
|
|
71
|
+
downloadArtifact?: AgentManualSetupArtifact;
|
|
72
|
+
}
|
|
73
|
+
export interface AgentDefinition {
|
|
74
|
+
slug: string;
|
|
75
|
+
name: string;
|
|
76
|
+
aliases?: string[];
|
|
77
|
+
description: string;
|
|
78
|
+
category: AgentCategory;
|
|
79
|
+
detection: AgentDetection;
|
|
80
|
+
/** Desktop launch hints (GUI app name, CLI command) */
|
|
81
|
+
launch?: AgentLaunch;
|
|
82
|
+
/** Logo identifier used by the desktop UI */
|
|
83
|
+
logo?: string;
|
|
84
|
+
/** Public download/install URL shown in the desktop onboarding */
|
|
85
|
+
downloadUrl?: string;
|
|
86
|
+
/** Whether the CLI knows how to auto-install this agent */
|
|
87
|
+
autoInstallable?: boolean;
|
|
88
|
+
/** Skill file directories relative to $HOME (global) or project root (project) */
|
|
89
|
+
skillsPaths?: {
|
|
90
|
+
global?: PlatformString;
|
|
91
|
+
project?: PlatformString;
|
|
92
|
+
};
|
|
93
|
+
/** MCP config file path relative to $HOME */
|
|
94
|
+
mcpConfigPath?: PlatformString;
|
|
95
|
+
/** MCP config JSON key where servers are stored (default: 'mcpServers') */
|
|
96
|
+
mcpConfigKey?: string;
|
|
97
|
+
/** First-class agent = full Runwork support (custom adapter or rich desktop onboarding) */
|
|
98
|
+
firstClass?: boolean;
|
|
99
|
+
/** Instruction file path relative to $HOME (global) or project root (project) */
|
|
100
|
+
instructionFile?: {
|
|
101
|
+
global?: PlatformString;
|
|
102
|
+
project?: PlatformString;
|
|
103
|
+
};
|
|
104
|
+
/** Skill file format */
|
|
105
|
+
skillFormat?: 'skill-md' | 'mdc' | 'windsurf-md' | 'gemini-flat';
|
|
106
|
+
/** Desktop onboarding hint copy */
|
|
107
|
+
quickStart?: AgentQuickStart;
|
|
108
|
+
/** Manual setup steps required after installation (desktop onboarding) */
|
|
109
|
+
manualSetup?: AgentManualSetup;
|
|
110
|
+
}
|
|
111
|
+
export declare function getAgent(slug: string): AgentDefinition | undefined;
|
|
112
|
+
export declare function getAgentByName(name: string): AgentDefinition | undefined;
|
|
113
|
+
export declare function getAgents(): AgentDefinition[];
|
|
114
|
+
export declare function getDetectableAgents(): AgentDefinition[];
|
|
115
|
+
export declare function getLaunchableAgents(): AgentDefinition[];
|
|
116
|
+
export declare function getFirstClassAgents(): AgentDefinition[];
|
|
117
|
+
export declare function getAgentsByCategory(category: AgentCategory): AgentDefinition[];
|
|
118
|
+
export declare function isCLIAgent(slug: string): boolean;
|
|
119
|
+
export declare function isIDEAgent(slug: string): boolean;
|
|
120
|
+
export declare function isDesktopAgent(slug: string): boolean;
|
|
121
|
+
/** Alias of {@link getAgent} kept for backward compatibility with CLI callers. */
|
|
122
|
+
export declare const getRegistryAgent: typeof getAgent;
|
|
123
|
+
/** Alias of {@link getAgents} kept for backward compatibility with CLI callers. */
|
|
124
|
+
export declare const getRegistryAgents: typeof getAgents;
|
|
125
|
+
export declare function getFirstClassSlugs(): string[];
|
|
126
|
+
export declare function getCustomAdapterSlugs(): Set<string>;
|