runwork 0.4.1 → 0.5.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 (95) hide show
  1. package/bundled-types/core-agent.d.ts +8 -0
  2. package/bundled-types/core-base-agent.d.ts +4 -0
  3. package/bundled-types/core-types.d.ts +101 -0
  4. package/bundled-types/core-workspace.d.ts +10 -0
  5. package/bundled-types/index.d.ts +2 -0
  6. package/bundled-types/shared/core-types.d.ts +101 -0
  7. package/bundled-types/shared/shared/core-types.d.ts +101 -0
  8. package/bundled-types/task-delegation/index.d.ts +50 -0
  9. package/bundled-types/task-delegation/sandbox-bridge.d.ts +88 -0
  10. package/bundled-types/task-delegation/session-manager.d.ts +86 -0
  11. package/bundled-types/task-delegation/stream-parser.d.ts +25 -0
  12. package/bundled-types/task-delegation/task-delegation/index.d.ts +30 -0
  13. package/bundled-types/task-delegation/task-delegation/sandbox-bridge.d.ts +88 -0
  14. package/bundled-types/task-delegation/task-delegation/session-manager.d.ts +86 -0
  15. package/bundled-types/task-delegation/task-delegation/stream-parser.d.ts +25 -0
  16. package/bundled-types/task-delegation/task-delegation/types.d.ts +165 -0
  17. package/bundled-types/task-delegation/types.d.ts +336 -0
  18. package/bundled-types/task-delegation.d.ts +2 -0
  19. package/dist/__tests__/curl-parser.test.d.ts +1 -0
  20. package/dist/__tests__/curl-parser.test.js +250 -0
  21. package/dist/__tests__/types-manager.test.js +22 -18
  22. package/dist/agents/claude-code.d.ts +10 -0
  23. package/dist/agents/claude-code.js +50 -0
  24. package/dist/agents/claude-desktop.d.ts +10 -0
  25. package/dist/agents/claude-desktop.js +141 -0
  26. package/dist/agents/codex.d.ts +10 -0
  27. package/dist/agents/codex.js +45 -0
  28. package/dist/agents/copilot-cli.d.ts +10 -0
  29. package/dist/agents/copilot-cli.js +46 -0
  30. package/dist/agents/copilot-vscode.d.ts +10 -0
  31. package/dist/agents/copilot-vscode.js +37 -0
  32. package/dist/agents/cursor.d.ts +10 -0
  33. package/dist/agents/cursor.js +55 -0
  34. package/dist/agents/detect.d.ts +4 -0
  35. package/dist/agents/detect.js +43 -0
  36. package/dist/agents/gemini.d.ts +10 -0
  37. package/dist/agents/gemini.js +47 -0
  38. package/dist/agents/types.d.ts +35 -0
  39. package/dist/agents/types.js +13 -0
  40. package/dist/agents/utils/json-config.d.ts +14 -0
  41. package/dist/agents/utils/json-config.js +42 -0
  42. package/dist/agents/utils/toml-config.d.ts +8 -0
  43. package/dist/agents/utils/toml-config.js +27 -0
  44. package/dist/agents/windsurf.d.ts +10 -0
  45. package/dist/agents/windsurf.js +50 -0
  46. package/dist/api/client.d.ts +97 -1
  47. package/dist/api/client.js +199 -0
  48. package/dist/auth/store.js +18 -0
  49. package/dist/commands/agents.d.ts +2 -0
  50. package/dist/commands/agents.js +160 -0
  51. package/dist/commands/api-keys.d.ts +2 -0
  52. package/dist/commands/api-keys.js +95 -0
  53. package/dist/commands/apps.d.ts +2 -0
  54. package/dist/commands/apps.js +82 -0
  55. package/dist/commands/components.d.ts +2 -0
  56. package/dist/commands/components.js +43 -0
  57. package/dist/commands/endpoints.d.ts +2 -0
  58. package/dist/commands/endpoints.js +188 -0
  59. package/dist/commands/entities.d.ts +2 -0
  60. package/dist/commands/entities.js +198 -0
  61. package/dist/commands/files.d.ts +2 -0
  62. package/dist/commands/files.js +192 -0
  63. package/dist/commands/info.d.ts +1 -0
  64. package/dist/commands/info.js +71 -127
  65. package/dist/commands/init.d.ts +2 -0
  66. package/dist/commands/init.js +10 -8
  67. package/dist/commands/integrations.js +105 -12
  68. package/dist/commands/mcp.d.ts +2 -0
  69. package/dist/commands/mcp.js +117 -0
  70. package/dist/commands/schedules.d.ts +2 -0
  71. package/dist/commands/schedules.js +221 -0
  72. package/dist/commands/setup.d.ts +2 -0
  73. package/dist/commands/setup.js +199 -0
  74. package/dist/commands/skills.d.ts +2 -0
  75. package/dist/commands/skills.js +134 -0
  76. package/dist/commands/sync.d.ts +2 -0
  77. package/dist/commands/sync.js +146 -0
  78. package/dist/commands/workflows.d.ts +2 -0
  79. package/dist/commands/workflows.js +187 -0
  80. package/dist/generated/bundled-types.js +35 -33
  81. package/dist/generated/version.d.ts +1 -1
  82. package/dist/generated/version.js +1 -1
  83. package/dist/index.js +26 -0
  84. package/dist/logs/tailer.js +41 -1
  85. package/dist/types.d.ts +204 -0
  86. package/dist/ui/banner.js +7 -6
  87. package/dist/utils/app-info.d.ts +66 -0
  88. package/dist/utils/app-info.js +150 -0
  89. package/dist/utils/curl-parser.d.ts +12 -0
  90. package/dist/utils/curl-parser.js +44 -0
  91. package/dist/utils/data-input.d.ts +1 -0
  92. package/dist/utils/data-input.js +38 -0
  93. package/dist/workspace/resolve.d.ts +19 -0
  94. package/dist/workspace/resolve.js +100 -0
  95. package/package.json +3 -2
@@ -0,0 +1,2 @@
1
+ export { agentTaskToolSchema, runAgentTask, getAgentTaskStatus } from './task-delegation/index';
2
+ export type { AgentTaskResult, AgentTaskInputFile, AgentTaskOutputFile, AgentTaskUsage, ExecutionLimits, AgentTaskExecution, RunAgentTaskOptions, } from './task-delegation/types';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,250 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { parseCurlToRequest } from '../utils/curl-parser.js';
3
+ describe('parseCurlToRequest', () => {
4
+ describe('basic requests', () => {
5
+ it('parses a simple GET request', async () => {
6
+ const result = await parseCurlToRequest("curl 'https://api.example.com/users'");
7
+ expect(result.method).toBe('GET');
8
+ expect(result.path).toBe('/users');
9
+ expect(result.query).toBeUndefined();
10
+ expect(result.body).toBeUndefined();
11
+ });
12
+ it('parses GET with query parameters', async () => {
13
+ const result = await parseCurlToRequest("curl 'https://api.example.com/users?limit=10&offset=20'");
14
+ expect(result.method).toBe('GET');
15
+ expect(result.path).toBe('/users');
16
+ expect(result.query).toBe('limit=10&offset=20');
17
+ });
18
+ it('parses explicit GET method', async () => {
19
+ const result = await parseCurlToRequest("curl -X GET 'https://api.example.com/data'");
20
+ expect(result.method).toBe('GET');
21
+ expect(result.path).toBe('/data');
22
+ });
23
+ it('parses URL without quotes', async () => {
24
+ const result = await parseCurlToRequest('curl https://api.example.com/items');
25
+ expect(result.method).toBe('GET');
26
+ expect(result.path).toBe('/items');
27
+ });
28
+ });
29
+ describe('HTTP methods', () => {
30
+ it('parses POST with -X', async () => {
31
+ const result = await parseCurlToRequest("curl -X POST 'https://api.example.com/users'");
32
+ expect(result.method).toBe('POST');
33
+ });
34
+ it('parses PUT with --request', async () => {
35
+ const result = await parseCurlToRequest("curl --request PUT 'https://api.example.com/users/1'");
36
+ expect(result.method).toBe('PUT');
37
+ });
38
+ it('parses DELETE', async () => {
39
+ const result = await parseCurlToRequest("curl -X DELETE 'https://api.example.com/users/1'");
40
+ expect(result.method).toBe('DELETE');
41
+ });
42
+ it('parses PATCH', async () => {
43
+ const result = await parseCurlToRequest("curl -X PATCH 'https://api.example.com/users/1'");
44
+ expect(result.method).toBe('PATCH');
45
+ });
46
+ it('infers POST when --data is present without -X', async () => {
47
+ const result = await parseCurlToRequest(`curl 'https://api.example.com/users' -d '{"name":"test"}'`);
48
+ expect(result.method).toBe('POST');
49
+ });
50
+ });
51
+ describe('headers', () => {
52
+ it('parses single header with -H', async () => {
53
+ const result = await parseCurlToRequest("curl 'https://api.example.com/users' -H 'Accept: application/json'");
54
+ expect(result.headers['Accept']).toBe('application/json');
55
+ });
56
+ it('parses multiple headers', async () => {
57
+ const result = await parseCurlToRequest("curl 'https://api.example.com/users' -H 'Accept: application/json' -H 'X-Custom: value'");
58
+ expect(result.headers['Accept']).toBe('application/json');
59
+ expect(result.headers['X-Custom']).toBe('value');
60
+ });
61
+ it('strips Authorization header', async () => {
62
+ const result = await parseCurlToRequest("curl 'https://api.example.com/users' -H 'Authorization: Bearer token123' -H 'Accept: application/json'");
63
+ expect(result.headers['Authorization']).toBeUndefined();
64
+ expect(result.headers['Accept']).toBe('application/json');
65
+ });
66
+ it('strips Cookie header', async () => {
67
+ const result = await parseCurlToRequest("curl 'https://api.example.com/users' -H 'Cookie: session=abc' -H 'Accept: application/json'");
68
+ expect(result.headers['Cookie']).toBeUndefined();
69
+ expect(result.headers['Accept']).toBe('application/json');
70
+ });
71
+ it('strips Host header', async () => {
72
+ const result = await parseCurlToRequest("curl 'https://api.example.com/users' -H 'Host: api.example.com'");
73
+ expect(result.headers['Host']).toBeUndefined();
74
+ });
75
+ it('strips User-Agent header', async () => {
76
+ const result = await parseCurlToRequest("curl 'https://api.example.com/users' -H 'User-Agent: Mozilla/5.0' -H 'Accept: text/html'");
77
+ expect(result.headers['User-Agent']).toBeUndefined();
78
+ expect(result.headers['Accept']).toBe('text/html');
79
+ });
80
+ it('strips Connection header', async () => {
81
+ const result = await parseCurlToRequest("curl 'https://api.example.com/users' -H 'Connection: keep-alive'");
82
+ expect(result.headers['Connection']).toBeUndefined();
83
+ });
84
+ it('preserves Content-Type header', async () => {
85
+ const result = await parseCurlToRequest("curl 'https://api.example.com/users' -H 'Content-Type: application/json'");
86
+ expect(result.headers['Content-Type']).toBe('application/json');
87
+ });
88
+ });
89
+ describe('request body', () => {
90
+ it('parses JSON body with -d', async () => {
91
+ const result = await parseCurlToRequest(`curl 'https://api.example.com/users' -X POST -d '{"name":"John","age":30}'`);
92
+ expect(result.body).toEqual({ name: 'John', age: 30 });
93
+ });
94
+ it('parses JSON body with --data', async () => {
95
+ const result = await parseCurlToRequest(`curl 'https://api.example.com/users' -X POST --data '{"key":"value"}'`);
96
+ expect(result.body).toEqual({ key: 'value' });
97
+ });
98
+ it('parses JSON body with --data-raw', async () => {
99
+ const result = await parseCurlToRequest(`curl 'https://api.example.com/users' -X POST --data-raw '{"items":[1,2,3]}'`);
100
+ expect(result.body).toEqual({ items: [1, 2, 3] });
101
+ });
102
+ it('handles form-encoded body as parsed object', async () => {
103
+ const result = await parseCurlToRequest(`curl 'https://api.example.com/submit' -X POST -d 'name=John&age=30'`);
104
+ // curlconverter parses form data into key-value pairs
105
+ expect(result.body).toEqual({ name: 'John', age: '30' });
106
+ });
107
+ it('parses empty body', async () => {
108
+ const result = await parseCurlToRequest("curl 'https://api.example.com/users' -X POST");
109
+ expect(result.body).toBeUndefined();
110
+ });
111
+ });
112
+ describe('Chrome DevTools "Copy as cURL" format', () => {
113
+ it('parses typical Chrome GET request', async () => {
114
+ const curl = `curl 'https://api.hubapi.com/crm/v3/objects/contacts?limit=10&properties=email%2Cfirstname' \\
115
+ -H 'accept: application/json, text/plain, */*' \\
116
+ -H 'accept-language: en-US,en;q=0.9' \\
117
+ -H 'authorization: Bearer REDACTED' \\
118
+ -H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)' \\
119
+ -H 'cookie: session=abc123'`;
120
+ const result = await parseCurlToRequest(curl);
121
+ expect(result.method).toBe('GET');
122
+ expect(result.path).toBe('/crm/v3/objects/contacts');
123
+ expect(result.query).toContain('limit=10');
124
+ expect(result.headers['authorization']).toBeUndefined();
125
+ expect(result.headers['cookie']).toBeUndefined();
126
+ expect(result.headers['user-agent']).toBeUndefined();
127
+ expect(result.headers['accept']).toBe('application/json, text/plain, */*');
128
+ });
129
+ it('parses Chrome POST with JSON body', async () => {
130
+ const curl = `curl 'https://api.hubapi.com/crm/v3/objects/contacts' \\
131
+ -H 'accept: application/json' \\
132
+ -H 'authorization: Bearer REDACTED' \\
133
+ -H 'content-type: application/json' \\
134
+ --data-raw '{"properties":{"email":"test@example.com","firstname":"Test"}}' \\
135
+ --compressed`;
136
+ const result = await parseCurlToRequest(curl);
137
+ expect(result.method).toBe('POST');
138
+ expect(result.path).toBe('/crm/v3/objects/contacts');
139
+ expect(result.body).toEqual({
140
+ properties: { email: 'test@example.com', firstname: 'Test' },
141
+ });
142
+ });
143
+ it('parses Chrome PUT request', async () => {
144
+ const curl = `curl 'https://api.example.com/crm/v3/objects/contacts/123' \\
145
+ -X 'PUT' \\
146
+ -H 'content-type: application/json' \\
147
+ --data-raw '{"properties":{"firstname":"Updated"}}'`;
148
+ const result = await parseCurlToRequest(curl);
149
+ expect(result.method).toBe('PUT');
150
+ expect(result.path).toBe('/crm/v3/objects/contacts/123');
151
+ expect(result.body).toEqual({ properties: { firstname: 'Updated' } });
152
+ });
153
+ it('parses Chrome DELETE request', async () => {
154
+ const curl = `curl 'https://api.example.com/crm/v3/objects/contacts/123' \\
155
+ -X 'DELETE' \\
156
+ -H 'authorization: Bearer REDACTED'`;
157
+ const result = await parseCurlToRequest(curl);
158
+ expect(result.method).toBe('DELETE');
159
+ expect(result.path).toBe('/crm/v3/objects/contacts/123');
160
+ });
161
+ });
162
+ describe('URL path extraction', () => {
163
+ it('extracts path from URL with port', async () => {
164
+ const result = await parseCurlToRequest("curl 'https://api.example.com:8443/v2/users'");
165
+ expect(result.path).toBe('/v2/users');
166
+ });
167
+ it('handles root path', async () => {
168
+ const result = await parseCurlToRequest("curl 'https://api.example.com/'");
169
+ expect(result.path).toBe('/');
170
+ });
171
+ it('handles deeply nested path', async () => {
172
+ const result = await parseCurlToRequest("curl 'https://api.example.com/v3/crm/objects/contacts/search'");
173
+ expect(result.path).toBe('/v3/crm/objects/contacts/search');
174
+ });
175
+ it('preserves URL-encoded characters in path', async () => {
176
+ const result = await parseCurlToRequest("curl 'https://api.example.com/users/john%40example.com'");
177
+ expect(result.path).toBe('/users/john%40example.com');
178
+ });
179
+ });
180
+ describe('edge cases', () => {
181
+ it('handles double-quoted strings', async () => {
182
+ const result = await parseCurlToRequest('curl "https://api.example.com/users" -H "Accept: application/json"');
183
+ expect(result.path).toBe('/users');
184
+ expect(result.headers['Accept']).toBe('application/json');
185
+ });
186
+ it('handles --compressed flag', async () => {
187
+ const result = await parseCurlToRequest("curl 'https://api.example.com/data' --compressed -H 'Accept-Encoding: gzip'");
188
+ expect(result.method).toBe('GET');
189
+ expect(result.path).toBe('/data');
190
+ });
191
+ it('handles -s (silent) flag', async () => {
192
+ const result = await parseCurlToRequest("curl -s 'https://api.example.com/health'");
193
+ expect(result.path).toBe('/health');
194
+ });
195
+ it('handles -L (follow redirects) flag', async () => {
196
+ const result = await parseCurlToRequest("curl -L 'https://api.example.com/redirect'");
197
+ expect(result.path).toBe('/redirect');
198
+ });
199
+ it('throws on empty/invalid input', async () => {
200
+ await expect(parseCurlToRequest('')).rejects.toThrow();
201
+ });
202
+ it('handles multiline curl with backslash continuations', async () => {
203
+ const curl = `curl \\
204
+ 'https://api.example.com/users' \\
205
+ -H 'Accept: application/json' \\
206
+ -H 'X-Request-Id: abc-123'`;
207
+ const result = await parseCurlToRequest(curl);
208
+ expect(result.path).toBe('/users');
209
+ expect(result.headers['Accept']).toBe('application/json');
210
+ expect(result.headers['X-Request-Id']).toBe('abc-123');
211
+ });
212
+ it('handles complex JSON body with nested objects', async () => {
213
+ const curl = `curl 'https://api.example.com/search' -X POST -H 'Content-Type: application/json' --data-raw '{"filters":{"and":[{"property":"email","operator":"EQ","value":"test@example.com"}]},"limit":10}'`;
214
+ const result = await parseCurlToRequest(curl);
215
+ expect(result.body).toEqual({
216
+ filters: {
217
+ and: [{ property: 'email', operator: 'EQ', value: 'test@example.com' }],
218
+ },
219
+ limit: 10,
220
+ });
221
+ });
222
+ it('handles request with many headers (real-world Chrome copy)', async () => {
223
+ const curl = `curl 'https://api.stripe.com/v1/charges?limit=3' \\
224
+ -H 'accept: application/json' \\
225
+ -H 'accept-language: en-US,en;q=0.9,tr;q=0.8' \\
226
+ -H 'authorization: Bearer sk_test_xxx' \\
227
+ -H 'cache-control: no-cache' \\
228
+ -H 'connection: keep-alive' \\
229
+ -H 'cookie: __stripe_mid=abc; __stripe_sid=def' \\
230
+ -H 'pragma: no-cache' \\
231
+ -H 'sec-ch-ua: "Chromium";v="128"' \\
232
+ -H 'sec-fetch-dest: empty' \\
233
+ -H 'sec-fetch-mode: cors' \\
234
+ -H 'sec-fetch-site: same-origin' \\
235
+ -H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)'`;
236
+ const result = await parseCurlToRequest(curl);
237
+ expect(result.method).toBe('GET');
238
+ expect(result.path).toBe('/v1/charges');
239
+ expect(result.query).toBe('limit=3');
240
+ // Proxy-managed headers stripped
241
+ expect(result.headers['authorization']).toBeUndefined();
242
+ expect(result.headers['cookie']).toBeUndefined();
243
+ expect(result.headers['connection']).toBeUndefined();
244
+ expect(result.headers['user-agent']).toBeUndefined();
245
+ // App headers preserved
246
+ expect(result.headers['accept']).toBe('application/json');
247
+ expect(result.headers['cache-control']).toBe('no-cache');
248
+ });
249
+ });
250
+ });
@@ -1,14 +1,22 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  vi.mock('fs');
3
+ vi.mock('../generated/bundled-types.js', () => ({
4
+ BUNDLED_TYPES: {},
5
+ }));
3
6
  const mockFs = await import('fs');
7
+ const bundledTypesModule = await import('../generated/bundled-types.js');
4
8
  const { populateTypes } = await import('../types-manager.js');
9
+ function setBundledTypes(types) {
10
+ Object.keys(bundledTypesModule.BUNDLED_TYPES).forEach(k => delete bundledTypesModule.BUNDLED_TYPES[k]);
11
+ Object.assign(bundledTypesModule.BUNDLED_TYPES, types);
12
+ }
5
13
  describe('types-manager', () => {
6
14
  beforeEach(() => {
7
15
  vi.clearAllMocks();
16
+ setBundledTypes({});
8
17
  });
9
18
  describe('populateTypes()', () => {
10
19
  it('creates .runwork/types directory', async () => {
11
- // No node_modules, no bundled-types
12
20
  vi.mocked(mockFs.existsSync).mockReturnValue(false);
13
21
  const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
14
22
  await populateTypes('/project');
@@ -46,39 +54,35 @@ describe('types-manager', () => {
46
54
  logSpy.mockRestore();
47
55
  });
48
56
  it('falls back to bundled types (Tier 3) when node_modules unavailable', async () => {
49
- vi.mocked(mockFs.existsSync).mockImplementation((path) => {
50
- if (String(path).includes('bundled-types')) {
51
- return true;
52
- }
53
- return false;
57
+ vi.mocked(mockFs.existsSync).mockReturnValue(false);
58
+ setBundledTypes({
59
+ 'framework.d.ts': 'export type Framework = {};',
54
60
  });
55
- vi.mocked(mockFs.readdirSync).mockReturnValue(['framework.d.ts']);
56
61
  const logSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
57
62
  await populateTypes('/project');
58
- // Should have called readdirSync on the bundled-types directory
59
- expect(mockFs.readdirSync).toHaveBeenCalled();
60
- expect(mockFs.copyFileSync).toHaveBeenCalledTimes(1);
63
+ expect(mockFs.writeFileSync).toHaveBeenCalledWith('/project/.runwork/types/framework.d.ts', 'export type Framework = {};');
61
64
  expect(logSpy).toHaveBeenCalledWith('Types populated from bundled types');
62
65
  logSpy.mockRestore();
63
66
  });
64
67
  it('warns when no types found at all', async () => {
65
68
  vi.mocked(mockFs.existsSync).mockReturnValue(false);
69
+ setBundledTypes({});
66
70
  const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
67
71
  await populateTypes('/project');
68
72
  expect(warnSpy).toHaveBeenCalledWith('No type definitions found. Install @runworkai/framework or reinstall the CLI.');
69
73
  warnSpy.mockRestore();
70
74
  });
71
- it('does not copy non-.d.ts files in bundled types fallback', async () => {
72
- vi.mocked(mockFs.existsSync).mockImplementation((path) => {
73
- if (String(path).includes('bundled-types')) {
74
- return true;
75
- }
76
- return false;
75
+ it('writes all bundled type entries in Tier 3 fallback', async () => {
76
+ vi.mocked(mockFs.existsSync).mockReturnValue(false);
77
+ setBundledTypes({
78
+ 'types.d.ts': 'export type A = {};',
79
+ 'utils.d.ts': 'export type B = {};',
77
80
  });
78
- vi.mocked(mockFs.readdirSync).mockReturnValue(['types.d.ts', 'package.json', 'index.js']);
79
81
  const logSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
80
82
  await populateTypes('/project');
81
- expect(mockFs.copyFileSync).toHaveBeenCalledTimes(1);
83
+ expect(mockFs.writeFileSync).toHaveBeenCalledTimes(2);
84
+ expect(mockFs.writeFileSync).toHaveBeenCalledWith('/project/.runwork/types/types.d.ts', 'export type A = {};');
85
+ expect(mockFs.writeFileSync).toHaveBeenCalledWith('/project/.runwork/types/utils.d.ts', 'export type B = {};');
82
86
  logSpy.mockRestore();
83
87
  });
84
88
  });
@@ -0,0 +1,10 @@
1
+ import type { AgentAdapter, McpServerEntry, SkillFile } from './types.js';
2
+ export declare class ClaudeCodeAdapter 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
+ }
@@ -0,0 +1,50 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { homedir } from 'os';
4
+ import { execFileSync } from 'child_process';
5
+ import { buildSkillMd } from './types.js';
6
+ import { mergeJsonMcpServers } from './utils/json-config.js';
7
+ export class ClaudeCodeAdapter {
8
+ name = 'Claude Code';
9
+ slug = 'claude-code';
10
+ async detect() {
11
+ try {
12
+ execFileSync('which', ['claude'], { stdio: 'pipe' });
13
+ return true;
14
+ }
15
+ catch {
16
+ return existsSync('/Applications/Claude.app');
17
+ }
18
+ }
19
+ supportsMcpScope(_scope) {
20
+ return true;
21
+ }
22
+ supportsSkills() {
23
+ return true;
24
+ }
25
+ async writeMcpServers(servers, scope) {
26
+ const filePath = scope === 'project'
27
+ ? join(process.cwd(), '.mcp.json')
28
+ : join(homedir(), '.claude', 'settings.json');
29
+ const entries = {};
30
+ for (const s of servers) {
31
+ entries[s.name] = {
32
+ type: 'http',
33
+ url: s.url,
34
+ ...(s.headers ? { headers: s.headers } : {}),
35
+ };
36
+ }
37
+ mergeJsonMcpServers(filePath, entries, 'mcpServers');
38
+ }
39
+ async writeSkills(skills, scope) {
40
+ const baseDir = scope === 'project'
41
+ ? join(process.cwd(), '.claude', 'skills')
42
+ : join(homedir(), '.claude', 'skills');
43
+ for (const skill of skills) {
44
+ const skillDir = join(baseDir, skill.name);
45
+ mkdirSync(skillDir, { recursive: true });
46
+ const content = buildSkillMd(skill);
47
+ writeFileSync(join(skillDir, 'SKILL.md'), content);
48
+ }
49
+ }
50
+ }
@@ -0,0 +1,10 @@
1
+ import type { AgentAdapter, McpServerEntry, SkillFile } from './types.js';
2
+ export declare class ClaudeDesktopAdapter 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
+ }
@@ -0,0 +1,141 @@
1
+ import { existsSync, readdirSync, writeFileSync, mkdirSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { homedir, platform } from 'os';
4
+ import { buildSkillMd } from './types.js';
5
+ import { mergeJsonMcpServers, readJsonConfig, writeJsonConfig } from './utils/json-config.js';
6
+ const PLUGIN_NAME = 'runwork';
7
+ const PLUGIN_VERSION = '1.0.0';
8
+ function getMcpConfigPath() {
9
+ const os = platform();
10
+ if (os === 'darwin') {
11
+ return join(homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
12
+ }
13
+ if (os === 'win32') {
14
+ return join(process.env.APPDATA || join(homedir(), 'AppData', 'Roaming'), 'Claude', 'claude_desktop_config.json');
15
+ }
16
+ return join(homedir(), '.config', 'Claude', 'claude_desktop_config.json');
17
+ }
18
+ function getCoworkBaseDir() {
19
+ const os = platform();
20
+ if (os === 'darwin') {
21
+ return join(homedir(), 'Library', 'Application Support', 'Claude', 'local-agent-mode-sessions');
22
+ }
23
+ if (os === 'win32') {
24
+ return join(process.env.APPDATA || join(homedir(), 'AppData', 'Roaming'), 'Claude', 'local-agent-mode-sessions');
25
+ }
26
+ return join(homedir(), '.config', 'Claude', 'local-agent-mode-sessions');
27
+ }
28
+ /**
29
+ * Find the cowork_plugins directory by discovering the session/org ID structure.
30
+ * Path: local-agent-mode-sessions/<session-id>/<org-id>/cowork_plugins/
31
+ */
32
+ function findCoworkPluginsDir() {
33
+ const baseDir = getCoworkBaseDir();
34
+ if (!existsSync(baseDir))
35
+ return null;
36
+ try {
37
+ const entries = readdirSync(baseDir).filter(d => !d.startsWith('.') && d !== 'skills-plugin');
38
+ for (const sessionId of entries) {
39
+ const sessionPath = join(baseDir, sessionId);
40
+ const orgDirs = readdirSync(sessionPath).filter(d => !d.startsWith('.'));
41
+ for (const orgId of orgDirs) {
42
+ const pluginsDir = join(sessionPath, orgId, 'cowork_plugins');
43
+ if (existsSync(join(pluginsDir, 'installed_plugins.json'))) {
44
+ return pluginsDir;
45
+ }
46
+ }
47
+ }
48
+ }
49
+ catch { /* ignore */ }
50
+ return null;
51
+ }
52
+ function getPluginJson() {
53
+ return {
54
+ name: PLUGIN_NAME,
55
+ version: PLUGIN_VERSION,
56
+ description: 'Skills and tools from your Runwork workspace',
57
+ author: { name: 'Runwork' },
58
+ };
59
+ }
60
+ export class ClaudeDesktopAdapter {
61
+ name = 'Claude Desktop (Cowork)';
62
+ slug = 'claude-desktop';
63
+ async detect() {
64
+ const os = platform();
65
+ if (os === 'darwin')
66
+ return existsSync('/Applications/Claude.app');
67
+ return existsSync(getMcpConfigPath());
68
+ }
69
+ supportsMcpScope(scope) {
70
+ return scope === 'user';
71
+ }
72
+ supportsSkills() {
73
+ return true;
74
+ }
75
+ async writeMcpServers(servers, _scope) {
76
+ // Claude Desktop Cowork mode only supports stdio transport.
77
+ // Use mcp-remote as a bridge for HTTP/SSE servers.
78
+ const entries = {};
79
+ for (const s of servers) {
80
+ const args = ['mcp-remote', s.url];
81
+ if (s.headers?.Authorization) {
82
+ args.push('--header', `Authorization:${s.headers.Authorization}`);
83
+ }
84
+ entries[s.name] = {
85
+ command: 'npx',
86
+ args: ['-y', ...args],
87
+ };
88
+ }
89
+ mergeJsonMcpServers(getMcpConfigPath(), entries, 'mcpServers');
90
+ }
91
+ async writeSkills(skills, _scope) {
92
+ const pluginsDir = findCoworkPluginsDir();
93
+ if (!pluginsDir) {
94
+ console.warn(' [Claude Desktop] Cowork plugins directory not found. Open Claude Desktop at least once first.');
95
+ return;
96
+ }
97
+ // Write to both cache (installed copy) and marketplaces (source)
98
+ const cacheDir = join(pluginsDir, 'cache', 'runwork', PLUGIN_NAME, PLUGIN_VERSION);
99
+ const marketDir = join(pluginsDir, 'marketplaces', 'runwork', PLUGIN_NAME);
100
+ // Create plugin.json in both locations
101
+ for (const dir of [cacheDir, marketDir]) {
102
+ const pluginJsonDir = join(dir, '.claude-plugin');
103
+ mkdirSync(pluginJsonDir, { recursive: true });
104
+ writeFileSync(join(pluginJsonDir, 'plugin.json'), JSON.stringify(getPluginJson(), null, 2));
105
+ }
106
+ // Write skill files to both locations
107
+ for (const skill of skills) {
108
+ for (const dir of [cacheDir, marketDir]) {
109
+ const skillDir = join(dir, 'skills', skill.name);
110
+ mkdirSync(skillDir, { recursive: true });
111
+ writeFileSync(join(skillDir, 'SKILL.md'), buildSkillMd(skill));
112
+ }
113
+ }
114
+ // Register in installed_plugins.json
115
+ const installedPath = join(pluginsDir, 'installed_plugins.json');
116
+ const installed = readJsonConfig(installedPath);
117
+ if (!installed.version)
118
+ installed.version = 2;
119
+ if (!installed.plugins)
120
+ installed.plugins = {};
121
+ installed.plugins[`${PLUGIN_NAME}@runwork`] = [{
122
+ scope: 'user',
123
+ installPath: cacheDir,
124
+ version: PLUGIN_VERSION,
125
+ installedAt: new Date().toISOString(),
126
+ lastUpdated: new Date().toISOString(),
127
+ }];
128
+ writeJsonConfig(installedPath, installed);
129
+ // Register in known_marketplaces.json
130
+ const marketplacesPath = join(pluginsDir, 'known_marketplaces.json');
131
+ const marketplaces = readJsonConfig(marketplacesPath);
132
+ if (!marketplaces['runwork']) {
133
+ marketplaces['runwork'] = {
134
+ source: { source: 'local', path: join(pluginsDir, 'marketplaces', 'runwork') },
135
+ installLocation: join(pluginsDir, 'marketplaces', 'runwork'),
136
+ lastUpdated: new Date().toISOString(),
137
+ };
138
+ writeJsonConfig(marketplacesPath, marketplaces);
139
+ }
140
+ }
141
+ }
@@ -0,0 +1,10 @@
1
+ import type { AgentAdapter, McpServerEntry, SkillFile } from './types.js';
2
+ export declare class CodexAdapter 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
+ }
@@ -0,0 +1,45 @@
1
+ import { mkdirSync, writeFileSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { homedir } from 'os';
4
+ import { execFileSync } from 'child_process';
5
+ import { buildSkillMd } from './types.js';
6
+ import { mergeTomlMcpServers } from './utils/toml-config.js';
7
+ export class CodexAdapter {
8
+ name = 'Codex CLI';
9
+ slug = 'codex';
10
+ async detect() {
11
+ try {
12
+ execFileSync('which', ['codex'], { stdio: 'pipe' });
13
+ return true;
14
+ }
15
+ catch {
16
+ return false;
17
+ }
18
+ }
19
+ supportsMcpScope(_scope) {
20
+ return true;
21
+ }
22
+ supportsSkills() {
23
+ return true;
24
+ }
25
+ async writeMcpServers(servers, scope) {
26
+ const filePath = scope === 'project'
27
+ ? join(process.cwd(), '.codex', 'config.toml')
28
+ : join(homedir(), '.codex', 'config.toml');
29
+ const entries = {};
30
+ for (const s of servers) {
31
+ entries[s.name] = { url: s.url, bearerTokenEnvVar: 'RUNWORK_API_KEY' };
32
+ }
33
+ mergeTomlMcpServers(filePath, entries);
34
+ }
35
+ async writeSkills(skills, scope) {
36
+ const baseDir = scope === 'project'
37
+ ? join(process.cwd(), '.codex', 'skills')
38
+ : join(homedir(), '.codex', 'skills');
39
+ for (const skill of skills) {
40
+ const skillDir = join(baseDir, skill.name);
41
+ mkdirSync(skillDir, { recursive: true });
42
+ writeFileSync(join(skillDir, 'SKILL.md'), buildSkillMd(skill));
43
+ }
44
+ }
45
+ }
@@ -0,0 +1,10 @@
1
+ import type { AgentAdapter, McpServerEntry, SkillFile } from './types.js';
2
+ export declare class CopilotCliAdapter 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
+ }