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.
Files changed (118) hide show
  1. package/dist/agents/__tests__/claude-code-stats.test.d.ts +1 -0
  2. package/dist/agents/__tests__/claude-code-stats.test.js +153 -0
  3. package/dist/agents/__tests__/claude-desktop-stats.test.d.ts +1 -0
  4. package/dist/agents/__tests__/claude-desktop-stats.test.js +280 -0
  5. package/dist/agents/__tests__/codex-stats.test.d.ts +1 -0
  6. package/dist/agents/__tests__/codex-stats.test.js +262 -0
  7. package/dist/agents/__tests__/cursor-stats.test.d.ts +1 -0
  8. package/dist/agents/__tests__/cursor-stats.test.js +183 -0
  9. package/dist/agents/__tests__/graceful-degradation.test.d.ts +1 -0
  10. package/dist/agents/__tests__/graceful-degradation.test.js +123 -0
  11. package/dist/agents/__tests__/instruction-hint.test.d.ts +1 -0
  12. package/dist/agents/__tests__/instruction-hint.test.js +69 -0
  13. package/dist/agents/__tests__/intro-skill.test.d.ts +1 -0
  14. package/dist/agents/__tests__/intro-skill.test.js +148 -0
  15. package/dist/agents/__tests__/schema-validation.test.d.ts +1 -0
  16. package/dist/agents/__tests__/schema-validation.test.js +212 -0
  17. package/dist/agents/claude-code.d.ts +16 -1
  18. package/dist/agents/claude-code.js +344 -21
  19. package/dist/agents/claude-desktop.d.ts +10 -1
  20. package/dist/agents/claude-desktop.js +250 -8
  21. package/dist/agents/cline.d.ts +13 -0
  22. package/dist/agents/cline.js +88 -0
  23. package/dist/agents/codex.d.ts +7 -2
  24. package/dist/agents/codex.js +130 -18
  25. package/dist/agents/cursor.d.ts +6 -1
  26. package/dist/agents/cursor.js +164 -7
  27. package/dist/agents/detect.js +30 -7
  28. package/dist/agents/gemini.d.ts +6 -3
  29. package/dist/agents/gemini.js +57 -20
  30. package/dist/agents/generic-adapter.d.ts +23 -0
  31. package/dist/agents/generic-adapter.js +106 -0
  32. package/dist/agents/intro-skill.d.ts +36 -0
  33. package/dist/agents/intro-skill.js +358 -0
  34. package/dist/agents/registry.d.ts +53 -0
  35. package/dist/agents/registry.js +219 -0
  36. package/dist/agents/types.d.ts +44 -1
  37. package/dist/agents/types.js +11 -4
  38. package/dist/agents/utils/instruction-hint.d.ts +11 -0
  39. package/dist/agents/utils/instruction-hint.js +62 -0
  40. package/dist/agents/utils/json-config.js +3 -3
  41. package/dist/agents/windsurf.d.ts +2 -0
  42. package/dist/agents/windsurf.js +26 -9
  43. package/dist/api/client.d.ts +85 -2
  44. package/dist/api/client.js +40 -10
  45. package/dist/auth/__tests__/login-flow.test.d.ts +1 -0
  46. package/dist/auth/__tests__/login-flow.test.js +125 -0
  47. package/dist/auth/login-flow.d.ts +16 -0
  48. package/dist/auth/login-flow.js +101 -1
  49. package/dist/commands/clone.d.ts +9 -1
  50. package/dist/commands/clone.js +40 -12
  51. package/dist/commands/deploy.js +27 -4
  52. package/dist/commands/dev.js +35 -1
  53. package/dist/commands/doctor.d.ts +2 -0
  54. package/dist/commands/doctor.js +82 -0
  55. package/dist/commands/info.js +6 -2
  56. package/dist/commands/init.d.ts +10 -2
  57. package/dist/commands/init.js +58 -15
  58. package/dist/commands/integrations.js +47 -10
  59. package/dist/commands/login.js +31 -4
  60. package/dist/commands/mcp.js +143 -15
  61. package/dist/commands/setup.js +47 -122
  62. package/dist/commands/skills.js +230 -12
  63. package/dist/commands/sync.d.ts +8 -0
  64. package/dist/commands/sync.js +398 -65
  65. package/dist/commands/welcome.js +6 -3
  66. package/dist/generated/version.d.ts +1 -1
  67. package/dist/generated/version.js +1 -1
  68. package/dist/health/__tests__/checks.test.d.ts +1 -0
  69. package/dist/health/__tests__/checks.test.js +223 -0
  70. package/dist/health/checks.d.ts +20 -0
  71. package/dist/health/checks.js +392 -0
  72. package/dist/health/runner.d.ts +2 -0
  73. package/dist/health/runner.js +26 -0
  74. package/dist/health/types.d.ts +12 -0
  75. package/dist/health/types.js +1 -0
  76. package/dist/index.js +32 -0
  77. package/dist/sync/__tests__/change-detect.test.d.ts +1 -0
  78. package/dist/sync/__tests__/change-detect.test.js +123 -0
  79. package/dist/sync/__tests__/hash.test.d.ts +1 -0
  80. package/dist/sync/__tests__/hash.test.js +18 -0
  81. package/dist/sync/change-detect.d.ts +19 -0
  82. package/dist/sync/change-detect.js +136 -0
  83. package/dist/sync/conflict-ui.d.ts +5 -0
  84. package/dist/sync/conflict-ui.js +78 -0
  85. package/dist/sync/executor.d.ts +21 -0
  86. package/dist/sync/executor.js +98 -0
  87. package/dist/sync/hash.d.ts +1 -0
  88. package/dist/sync/hash.js +4 -0
  89. package/dist/sync/types.d.ts +24 -0
  90. package/dist/sync/types.js +1 -0
  91. package/dist/types.d.ts +12 -0
  92. package/dist/ui/__tests__/banner.test.js +17 -1
  93. package/dist/ui/banner.d.ts +1 -1
  94. package/dist/ui/banner.js +27 -2
  95. package/dist/utils/__tests__/resolve.test.d.ts +1 -0
  96. package/dist/utils/__tests__/resolve.test.js +95 -0
  97. package/dist/utils/__tests__/sqlite.test.d.ts +1 -0
  98. package/dist/utils/__tests__/sqlite.test.js +77 -0
  99. package/dist/utils/agent-guidance.d.ts +32 -0
  100. package/dist/utils/agent-guidance.js +106 -0
  101. package/dist/utils/curl-parser.d.ts +1 -1
  102. package/dist/utils/curl-parser.js +144 -26
  103. package/dist/utils/fs.js +2 -2
  104. package/dist/utils/help-json.d.ts +56 -0
  105. package/dist/utils/help-json.js +85 -0
  106. package/dist/utils/resolve.d.ts +12 -0
  107. package/dist/utils/resolve.js +52 -0
  108. package/dist/utils/setup-state.d.ts +3 -0
  109. package/dist/utils/setup-state.js +19 -0
  110. package/dist/utils/sqlite.d.ts +7 -0
  111. package/dist/utils/sqlite.js +24 -0
  112. package/dist/utils/which.d.ts +6 -0
  113. package/dist/utils/which.js +18 -0
  114. package/package.json +7 -4
  115. package/dist/agents/copilot-cli.d.ts +0 -10
  116. package/dist/agents/copilot-cli.js +0 -46
  117. package/dist/agents/copilot-vscode.d.ts +0 -10
  118. package/dist/agents/copilot-vscode.js +0 -37
@@ -2,6 +2,8 @@ import { existsSync, mkdirSync, writeFileSync } from 'fs';
2
2
  import { join } from 'path';
3
3
  import { homedir, platform } from 'os';
4
4
  import { execFileSync } from 'child_process';
5
+ import { querySqlite } from '../utils/sqlite.js';
6
+ import { whichBinary } from '../utils/which.js';
5
7
  import { mergeJsonMcpServers } from './utils/json-config.js';
6
8
  export class CursorAdapter {
7
9
  name = 'Cursor';
@@ -10,13 +12,7 @@ export class CursorAdapter {
10
12
  const os = platform();
11
13
  if (os === 'darwin' && existsSync('/Applications/Cursor.app'))
12
14
  return true;
13
- try {
14
- execFileSync('which', ['cursor'], { stdio: 'pipe' });
15
- return true;
16
- }
17
- catch {
18
- return false;
19
- }
15
+ return !!whichBinary('cursor');
20
16
  }
21
17
  supportsMcpScope(_scope) {
22
18
  return true;
@@ -52,4 +48,165 @@ ${skill.content}`;
52
48
  writeFileSync(join(rulesDir, `${skill.filename}.mdc`), mdcContent);
53
49
  }
54
50
  }
51
+ async writeInstructionHint(hint, scope) {
52
+ // Cursor uses rules files, not a central instruction file
53
+ // Write as a rule file that's always loaded
54
+ const filePath = scope === 'project'
55
+ ? join(process.cwd(), '.cursor', 'rules', 'runwork.mdc')
56
+ : join(homedir(), '.cursor', 'rules', 'runwork.mdc');
57
+ const mdcContent = `---\ndescription: "Runwork workspace connection"\nalwaysApply: true\n---\n\n${hint}`;
58
+ mkdirSync(join(filePath, '..'), { recursive: true });
59
+ writeFileSync(filePath, mdcContent);
60
+ }
61
+ async writeTeamInstructions(instructions, scope) {
62
+ const filePath = scope === 'project'
63
+ ? join(process.cwd(), '.cursor', 'rules', 'runwork-team.mdc')
64
+ : join(homedir(), '.cursor', 'rules', 'runwork-team.mdc');
65
+ const mdcContent = `---\ndescription: "Team instructions from Runwork workspace"\nalwaysApply: true\n---\n\n${instructions}`;
66
+ mkdirSync(join(filePath, '..'), { recursive: true });
67
+ writeFileSync(filePath, mdcContent);
68
+ }
69
+ // ── Agent config (model, permissions via SQLite) ────────────────────
70
+ async writeAgentConfig(config, scope) {
71
+ if (scope !== 'user')
72
+ return;
73
+ const os = platform();
74
+ let globalDbPath;
75
+ if (os === 'darwin') {
76
+ globalDbPath = join(homedir(), 'Library', 'Application Support', 'Cursor', 'User', 'globalStorage', 'state.vscdb');
77
+ }
78
+ else if (os === 'win32') {
79
+ globalDbPath = join(process.env.APPDATA || join(homedir(), 'AppData', 'Roaming'), 'Cursor', 'User', 'globalStorage', 'state.vscdb');
80
+ }
81
+ else {
82
+ globalDbPath = join(homedir(), '.config', 'Cursor', 'User', 'globalStorage', 'state.vscdb');
83
+ }
84
+ if (!existsSync(globalDbPath))
85
+ return;
86
+ try {
87
+ const Database = (await import('better-sqlite3')).default;
88
+ const db = new Database(globalDbPath, { timeout: 5000 });
89
+ try {
90
+ const storageKey = 'src.vs.platform.reactivestorage.browser.reactiveStorageServiceImpl.persistentStorage.applicationUser';
91
+ const row = db.prepare(`SELECT value FROM ItemTable WHERE key = ?`).get(storageKey);
92
+ if (!row?.value)
93
+ return;
94
+ const data = JSON.parse(row.value);
95
+ if (config.modelPreference) {
96
+ if (!data.aiSettings)
97
+ data.aiSettings = {};
98
+ data.aiSettings.composerModel = config.modelPreference;
99
+ }
100
+ if (config.permissionRules && data.composerState) {
101
+ if (config.permissionRules.allow) {
102
+ data.composerState.yoloCommandAllowlist = config.permissionRules.allow;
103
+ }
104
+ if (config.permissionRules.deny) {
105
+ data.composerState.yoloCommandDenylist = config.permissionRules.deny;
106
+ }
107
+ }
108
+ db.prepare(`UPDATE ItemTable SET value = ? WHERE key = ?`).run(JSON.stringify(data), storageKey);
109
+ }
110
+ finally {
111
+ db.close();
112
+ }
113
+ }
114
+ catch {
115
+ // Cursor config write is best-effort (DB may be locked while Cursor is running)
116
+ }
117
+ }
118
+ // ── Usage stats (passive, best-effort) ─────────────────────────────
119
+ async readUsageStats(lastSyncAt) {
120
+ try {
121
+ const sinceMs = lastSyncAt ? new Date(lastSyncAt).getTime() : 0;
122
+ // Query composerData from state.vscdb (cursorDiskKV table)
123
+ const os = platform();
124
+ let globalDbPath;
125
+ if (os === 'darwin') {
126
+ globalDbPath = join(homedir(), 'Library', 'Application Support', 'Cursor', 'User', 'globalStorage', 'state.vscdb');
127
+ }
128
+ else if (os === 'win32') {
129
+ globalDbPath = join(process.env.APPDATA || join(homedir(), 'AppData', 'Roaming'), 'Cursor', 'User', 'globalStorage', 'state.vscdb');
130
+ }
131
+ else {
132
+ globalDbPath = join(homedir(), '.config', 'Cursor', 'User', 'globalStorage', 'state.vscdb');
133
+ }
134
+ let sessionCount = 0;
135
+ let totalLinesAdded = 0;
136
+ let totalLinesRemoved = 0;
137
+ let latestActivity = 0;
138
+ let agenticSessions = 0;
139
+ let chatSessions = 0;
140
+ if (existsSync(globalDbPath)) {
141
+ const composerResult = querySqlite(globalDbPath, `SELECT value FROM cursorDiskKV WHERE key LIKE 'composerData-%'`);
142
+ if (composerResult) {
143
+ for (const line of composerResult.split('\n')) {
144
+ if (!line.trim())
145
+ continue;
146
+ try {
147
+ const data = JSON.parse(line);
148
+ const createdAt = data.createdAt ?? 0;
149
+ if (createdAt > sinceMs) {
150
+ sessionCount++;
151
+ totalLinesAdded += data.totalLinesAdded ?? 0;
152
+ totalLinesRemoved += data.totalLinesRemoved ?? 0;
153
+ if (data.unifiedMode === 'agent' || data.isAgentic)
154
+ agenticSessions++;
155
+ else
156
+ chatSessions++;
157
+ }
158
+ if (createdAt > latestActivity)
159
+ latestActivity = createdAt;
160
+ }
161
+ catch {
162
+ continue;
163
+ }
164
+ }
165
+ }
166
+ }
167
+ // Check scored_commits for AI contribution data
168
+ // ~/.cursor/ is consistent across platforms (unlike Application Support paths)
169
+ const trackingDbPath = join(homedir(), '.cursor', 'ai-tracking', 'ai-code-tracking.db');
170
+ let aiCommitCount = 0;
171
+ let avgAiPercent = 0;
172
+ if (existsSync(trackingDbPath)) {
173
+ const sinceSec = Math.floor(sinceMs / 1000);
174
+ const commitResult = querySqlite(trackingDbPath, `SELECT count(*), avg(CAST(v2AiPercentage AS REAL)) FROM scored_commits WHERE scoredAt > ${sinceSec}`);
175
+ if (commitResult) {
176
+ const parts = commitResult.split('|');
177
+ aiCommitCount = parseInt(parts[0]) || 0;
178
+ avgAiPercent = parseFloat(parts[1]) || 0;
179
+ }
180
+ }
181
+ if (sessionCount === 0 && latestActivity <= sinceMs)
182
+ return null;
183
+ return {
184
+ hasNewActivity: sessionCount > 0,
185
+ sessionCount,
186
+ messageCount: 0,
187
+ toolCallCount: 0,
188
+ tokensUsed: 0,
189
+ aiLinesAdded: totalLinesAdded,
190
+ aiLinesRemoved: totalLinesRemoved,
191
+ lastActiveAt: latestActivity > 0 ? new Date(latestActivity).toISOString() : null,
192
+ extra: {
193
+ agenticSessions,
194
+ chatSessions,
195
+ ...(aiCommitCount > 0 ? { aiCommitCount, avgAiContributionPercent: Math.round(avgAiPercent) } : {}),
196
+ },
197
+ };
198
+ }
199
+ catch {
200
+ return null;
201
+ }
202
+ }
203
+ async readVersion() {
204
+ try {
205
+ const output = execFileSync('cursor', ['--version'], { stdio: 'pipe' }).toString().trim();
206
+ return output;
207
+ }
208
+ catch {
209
+ return null;
210
+ }
211
+ }
55
212
  }
@@ -1,21 +1,41 @@
1
1
  import { ClaudeCodeAdapter } from './claude-code.js';
2
2
  import { ClaudeDesktopAdapter } from './claude-desktop.js';
3
3
  import { CursorAdapter } from './cursor.js';
4
- import { CodexAdapter } from './codex.js';
5
4
  import { WindsurfAdapter } from './windsurf.js';
6
- import { CopilotVscodeAdapter } from './copilot-vscode.js';
7
- import { CopilotCliAdapter } from './copilot-cli.js';
5
+ import { CodexAdapter } from './codex.js';
6
+ import { ClineAdapter } from './cline.js';
8
7
  import { GeminiAdapter } from './gemini.js';
9
- const ALL_ADAPTERS = [
8
+ import { GenericAgentAdapter } from './generic-adapter.js';
9
+ import { getRegistryAgents, getCustomAdapterSlugs } from './registry.js';
10
+ /**
11
+ * Agents with custom adapter implementations (plugin systems, special formats).
12
+ * These override the generic adapter for their slug.
13
+ */
14
+ const CUSTOM_ADAPTERS = [
10
15
  new ClaudeCodeAdapter(),
11
16
  new ClaudeDesktopAdapter(),
12
17
  new CursorAdapter(),
13
- new CodexAdapter(),
14
18
  new WindsurfAdapter(),
15
- new CopilotVscodeAdapter(),
16
- new CopilotCliAdapter(),
19
+ new CodexAdapter(),
20
+ new ClineAdapter(),
17
21
  new GeminiAdapter(),
18
22
  ];
23
+ const customSlugs = getCustomAdapterSlugs();
24
+ const customMap = new Map(CUSTOM_ADAPTERS.map(a => [a.slug, a]));
25
+ /**
26
+ * Build the full list of adapters: custom adapters for special agents,
27
+ * generic adapters for everything else in the registry.
28
+ */
29
+ function buildAllAdapters() {
30
+ const adapters = [...CUSTOM_ADAPTERS];
31
+ for (const def of getRegistryAgents()) {
32
+ if (customSlugs.has(def.slug))
33
+ continue;
34
+ adapters.push(new GenericAgentAdapter(def));
35
+ }
36
+ return adapters;
37
+ }
38
+ const ALL_ADAPTERS = buildAllAdapters();
19
39
  export async function detectAgents() {
20
40
  const results = await Promise.all(ALL_ADAPTERS.map(async (adapter) => {
21
41
  try {
@@ -29,6 +49,9 @@ export async function detectAgents() {
29
49
  return results.filter((a) => a !== null);
30
50
  }
31
51
  export function getAdapterBySlug(slug) {
52
+ // Check custom adapters first, then fall back to generic
53
+ if (customMap.has(slug))
54
+ return customMap.get(slug);
32
55
  return ALL_ADAPTERS.find(a => a.slug === slug);
33
56
  }
34
57
  export function printNoAgentsMessage() {
@@ -1,10 +1,13 @@
1
- import type { AgentAdapter, McpServerEntry, SkillFile } from './types.js';
1
+ import type { AgentAdapter, AgentConfigOverride, McpServerEntry, SkillFile } from './types.js';
2
2
  export declare class GeminiAdapter implements AgentAdapter {
3
3
  name: string;
4
4
  slug: string;
5
5
  detect(): Promise<boolean>;
6
- supportsMcpScope(_scope: 'project' | 'user'): boolean;
6
+ supportsMcpScope(scope: 'project' | 'user'): boolean;
7
7
  supportsSkills(): boolean;
8
- writeMcpServers(servers: McpServerEntry[], scope: 'project' | 'user'): Promise<void>;
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>;
10
13
  }
@@ -1,31 +1,24 @@
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 { execFileSync } from 'child_process';
4
+ import { whichBinary } from '../utils/which.js';
5
5
  import { buildSkillMd } from './types.js';
6
+ import { writeHintToFile, writeTeamInstructionsToFile } from './utils/instruction-hint.js';
6
7
  import { mergeJsonMcpServers } from './utils/json-config.js';
7
8
  export class GeminiAdapter {
8
9
  name = 'Gemini CLI';
9
10
  slug = 'gemini';
10
11
  async detect() {
11
- try {
12
- execFileSync('which', ['gemini'], { stdio: 'pipe' });
13
- return true;
14
- }
15
- catch {
16
- return false;
17
- }
12
+ return !!whichBinary('gemini');
18
13
  }
19
- supportsMcpScope(_scope) {
20
- return true;
14
+ supportsMcpScope(scope) {
15
+ return scope === 'user';
21
16
  }
22
17
  supportsSkills() {
23
18
  return true;
24
19
  }
25
- async writeMcpServers(servers, scope) {
26
- const filePath = scope === 'project'
27
- ? join(process.cwd(), '.gemini', 'settings.json')
28
- : join(homedir(), '.gemini', 'settings.json');
20
+ async writeMcpServers(servers, _scope) {
21
+ const configPath = join(homedir(), '.gemini', 'settings.json');
29
22
  const entries = {};
30
23
  for (const s of servers) {
31
24
  entries[s.name] = {
@@ -33,15 +26,59 @@ export class GeminiAdapter {
33
26
  ...(s.headers ? { headers: s.headers } : {}),
34
27
  };
35
28
  }
36
- mergeJsonMcpServers(filePath, entries, 'mcpServers');
29
+ mergeJsonMcpServers(configPath, entries, 'mcpServers');
37
30
  }
38
31
  async writeSkills(skills, scope) {
39
32
  const baseDir = scope === 'project'
40
- ? join(process.cwd(), '.gemini', 'commands')
41
- : join(homedir(), '.gemini', 'commands');
42
- mkdirSync(baseDir, { recursive: true });
33
+ ? join(process.cwd(), '.gemini', 'skills')
34
+ : join(homedir(), '.gemini', 'skills');
43
35
  for (const skill of skills) {
44
- writeFileSync(join(baseDir, `${skill.filename}.md`), buildSkillMd(skill));
36
+ const skillDir = join(baseDir, skill.name);
37
+ mkdirSync(skillDir, { recursive: true });
38
+ writeFileSync(join(skillDir, 'SKILL.md'), buildSkillMd(skill));
39
+ }
40
+ }
41
+ async writeInstructionHint(hint, scope) {
42
+ const filePath = scope === 'project'
43
+ ? join(process.cwd(), 'GEMINI.md')
44
+ : join(homedir(), '.gemini', 'GEMINI.md');
45
+ writeHintToFile(filePath, hint);
46
+ }
47
+ async writeTeamInstructions(instructions, scope) {
48
+ const filePath = scope === 'project'
49
+ ? join(process.cwd(), 'GEMINI.md')
50
+ : join(homedir(), '.gemini', 'GEMINI.md');
51
+ writeTeamInstructionsToFile(filePath, instructions);
52
+ }
53
+ // ── Agent config (settings.json) ──────────────────────────────────
54
+ async writeAgentConfig(config, scope) {
55
+ const settingsPath = scope === 'project'
56
+ ? join(process.cwd(), '.gemini', 'settings.json')
57
+ : join(homedir(), '.gemini', 'settings.json');
58
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
59
+ let settings = {};
60
+ if (existsSync(settingsPath)) {
61
+ try {
62
+ settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
63
+ }
64
+ catch { /* start fresh */ }
65
+ }
66
+ if (config.modelPreference) {
67
+ if (!settings.model)
68
+ settings.model = {};
69
+ settings.model.name = config.modelPreference;
70
+ }
71
+ if (config.permissionRules?.allow) {
72
+ if (!settings.tools)
73
+ settings.tools = {};
74
+ settings.tools.allowed = config.permissionRules.allow;
75
+ }
76
+ if (config.permissionRules?.deny) {
77
+ if (!settings.tools)
78
+ settings.tools = {};
79
+ settings.tools.exclude = config.permissionRules.deny;
45
80
  }
81
+ mkdirSync(join(settingsPath, '..'), { recursive: true });
82
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
46
83
  }
47
84
  }
@@ -0,0 +1,23 @@
1
+ import type { AgentAdapter, McpServerEntry, SkillFile } from './types.js';
2
+ import type { AgentDefinition } from './registry.js';
3
+ /**
4
+ * Generic adapter that handles any agent from the registry using its
5
+ * skillsPaths, mcpConfigPath, and instructionFile metadata.
6
+ *
7
+ * This covers the long tail of community agents. Agents with custom behavior
8
+ * (plugin systems, special config formats, usage stats reading) use their
9
+ * own dedicated adapter classes instead.
10
+ */
11
+ export declare class GenericAgentAdapter implements AgentAdapter {
12
+ name: string;
13
+ slug: string;
14
+ private def;
15
+ constructor(def: AgentDefinition);
16
+ detect(): Promise<boolean>;
17
+ supportsMcpScope(scope: 'project' | 'user'): boolean;
18
+ supportsSkills(): boolean;
19
+ writeMcpServers(servers: McpServerEntry[], _scope: 'project' | 'user'): Promise<void>;
20
+ writeSkills(skills: SkillFile[], scope: 'project' | 'user'): Promise<void>;
21
+ writeInstructionHint(hint: string, scope: 'project' | 'user'): Promise<void>;
22
+ writeTeamInstructions(instructions: string, scope: 'project' | 'user'): Promise<void>;
23
+ }
@@ -0,0 +1,106 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from 'fs';
2
+ import { isAbsolute, 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
+ import { writeHintToFile, writeTeamInstructionsToFile } from './utils/instruction-hint.js';
8
+ import { resolvePlatformString, resolveToAbsolute } from './registry.js';
9
+ /**
10
+ * Generic adapter that handles any agent from the registry using its
11
+ * skillsPaths, mcpConfigPath, and instructionFile metadata.
12
+ *
13
+ * This covers the long tail of community agents. Agents with custom behavior
14
+ * (plugin systems, special config formats, usage stats reading) use their
15
+ * own dedicated adapter classes instead.
16
+ */
17
+ export class GenericAgentAdapter {
18
+ name;
19
+ slug;
20
+ def;
21
+ constructor(def) {
22
+ this.def = def;
23
+ this.name = def.name;
24
+ this.slug = def.slug;
25
+ }
26
+ async detect() {
27
+ const target = resolvePlatformString(this.def.detection.target);
28
+ if (!target)
29
+ return false;
30
+ if (this.def.detection.method === 'binary') {
31
+ return !!whichBinary(target);
32
+ }
33
+ // path detection
34
+ if (isAbsolute(target)) {
35
+ return existsSync(target);
36
+ }
37
+ return existsSync(join(homedir(), target));
38
+ }
39
+ supportsMcpScope(scope) {
40
+ if (!this.def.mcpConfigPath)
41
+ return false;
42
+ return scope === 'user';
43
+ }
44
+ supportsSkills() {
45
+ return !!this.def.skillsPaths;
46
+ }
47
+ async writeMcpServers(servers, _scope) {
48
+ if (!this.def.mcpConfigPath)
49
+ return;
50
+ const filePath = join(homedir(), resolvePlatformString(this.def.mcpConfigPath) || '');
51
+ if (!filePath)
52
+ return;
53
+ const entries = {};
54
+ for (const s of servers) {
55
+ entries[s.name] = {
56
+ type: 'http',
57
+ url: s.url,
58
+ ...(s.headers ? { headers: s.headers } : {}),
59
+ };
60
+ }
61
+ mergeJsonMcpServers(filePath, entries, this.def.mcpConfigKey || 'mcpServers');
62
+ }
63
+ async writeSkills(skills, scope) {
64
+ if (!this.def.skillsPaths)
65
+ return;
66
+ const pathTemplate = scope === 'project'
67
+ ? this.def.skillsPaths.project
68
+ : this.def.skillsPaths.global;
69
+ if (!pathTemplate)
70
+ return;
71
+ const baseDir = resolveToAbsolute(pathTemplate, scope === 'project' ? 'project' : 'global');
72
+ if (!baseDir)
73
+ return;
74
+ for (const skill of skills) {
75
+ const skillDir = join(baseDir, skill.name);
76
+ mkdirSync(skillDir, { recursive: true });
77
+ writeFileSync(join(skillDir, 'SKILL.md'), buildSkillMd(skill));
78
+ }
79
+ }
80
+ async writeInstructionHint(hint, scope) {
81
+ if (!this.def.instructionFile)
82
+ return;
83
+ const pathTemplate = scope === 'project'
84
+ ? this.def.instructionFile.project
85
+ : this.def.instructionFile.global;
86
+ if (!pathTemplate)
87
+ return;
88
+ const filePath = resolveToAbsolute(pathTemplate, scope === 'project' ? 'project' : 'global');
89
+ if (!filePath)
90
+ return;
91
+ writeHintToFile(filePath, hint);
92
+ }
93
+ async writeTeamInstructions(instructions, scope) {
94
+ if (!this.def.instructionFile)
95
+ return;
96
+ const pathTemplate = scope === 'project'
97
+ ? this.def.instructionFile.project
98
+ : this.def.instructionFile.global;
99
+ if (!pathTemplate)
100
+ return;
101
+ const filePath = resolveToAbsolute(pathTemplate, scope === 'project' ? 'project' : 'global');
102
+ if (!filePath)
103
+ return;
104
+ writeTeamInstructionsToFile(filePath, instructions);
105
+ }
106
+ }
@@ -0,0 +1,36 @@
1
+ import type { WorkspaceAllData } from '../types.js';
2
+ import type { SkillFile } from './types.js';
3
+ export interface IntroSkillContext {
4
+ workspaceName: string;
5
+ workspaceId: string;
6
+ workspaceSlug?: string;
7
+ registries: WorkspaceAllData | null;
8
+ connectedIntegrations: string[];
9
+ mcpServerCount: number;
10
+ skillCount: number;
11
+ /** Names of external MCP servers configured in the workspace */
12
+ mcpServerNames?: string[];
13
+ /** Names of skills (app + external) configured in the workspace */
14
+ skillNames?: string[];
15
+ }
16
+ export interface InstructionHintContext {
17
+ workspaceName: string;
18
+ workspaceId: string;
19
+ workspaceSlug?: string;
20
+ registries: WorkspaceAllData | null;
21
+ connectedIntegrations: string[];
22
+ appCount: number;
23
+ skillCount: number;
24
+ mcpServerCount: number;
25
+ }
26
+ export declare function buildAppSkillDescription(appName: string, registries: WorkspaceAllData | null): string;
27
+ /**
28
+ * Generate the dynamic Runwork intro skill that teaches agents what Runwork is
29
+ * and what infrastructure is available in this workspace.
30
+ */
31
+ export declare function generateIntroSkill(ctx: IntroSkillContext): SkillFile;
32
+ /**
33
+ * Generate the minimal instruction hint that goes into agent instruction files.
34
+ * Uses HTML comment delimiters so it can be found and replaced on sync.
35
+ */
36
+ export declare function generateInstructionHint(ctx: InstructionHintContext): string;