runwork 0.6.2 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/install-scripts.test.d.ts +1 -0
- package/dist/__tests__/install-scripts.test.js +247 -0
- package/dist/agents/__tests__/claude-code-plugin.test.d.ts +1 -0
- package/dist/agents/__tests__/claude-code-plugin.test.js +47 -0
- package/dist/agents/__tests__/claude-code-stats.test.js +143 -78
- package/dist/agents/__tests__/claude-desktop-plugin-tree.test.d.ts +1 -0
- package/dist/agents/__tests__/claude-desktop-plugin-tree.test.js +161 -0
- package/dist/agents/__tests__/claude-desktop-plugin.test.d.ts +1 -0
- package/dist/agents/__tests__/claude-desktop-plugin.test.js +117 -0
- package/dist/agents/__tests__/claude-desktop-rpm.test.d.ts +1 -0
- package/dist/agents/__tests__/claude-desktop-rpm.test.js +126 -0
- package/dist/agents/__tests__/codex-instructions.test.d.ts +1 -0
- package/dist/agents/__tests__/codex-instructions.test.js +57 -0
- package/dist/agents/__tests__/codex-stats.test.js +9 -0
- package/dist/agents/__tests__/graceful-degradation.test.js +3 -3
- package/dist/agents/__tests__/intro-skill.test.js +11 -2
- package/dist/agents/__tests__/skill-slug.test.d.ts +1 -0
- package/dist/agents/__tests__/skill-slug.test.js +77 -0
- package/dist/agents/claude-code.d.ts +3 -1
- package/dist/agents/claude-code.js +233 -58
- package/dist/agents/claude-desktop-plugin-tree.d.ts +44 -0
- package/dist/agents/claude-desktop-plugin-tree.js +92 -0
- package/dist/agents/claude-desktop.d.ts +20 -1
- package/dist/agents/claude-desktop.js +238 -61
- package/dist/agents/cline.d.ts +3 -1
- package/dist/agents/cline.js +44 -2
- package/dist/agents/codex.d.ts +3 -1
- package/dist/agents/codex.js +85 -14
- package/dist/agents/cursor.d.ts +12 -1
- package/dist/agents/cursor.js +98 -25
- package/dist/agents/gemini.d.ts +3 -1
- package/dist/agents/gemini.js +42 -4
- package/dist/agents/generic-adapter.d.ts +3 -1
- package/dist/agents/generic-adapter.js +59 -4
- package/dist/agents/intro-skill.js +77 -31
- package/dist/agents/types.d.ts +42 -2
- package/dist/agents/types.js +61 -6
- package/dist/agents/utils/instruction-hint.d.ts +10 -0
- package/dist/agents/utils/instruction-hint.js +46 -0
- package/dist/agents/utils/json-config.d.ts +6 -1
- package/dist/agents/utils/json-config.js +31 -4
- package/dist/agents/windsurf.d.ts +3 -1
- package/dist/agents/windsurf.js +37 -2
- package/dist/commands/__tests__/sync-telemetry.test.d.ts +1 -0
- package/dist/commands/__tests__/sync-telemetry.test.js +281 -0
- package/dist/commands/__tests__/upgrade.test.d.ts +1 -0
- package/dist/commands/__tests__/upgrade.test.js +174 -0
- package/dist/commands/build-plugin.d.ts +2 -0
- package/dist/commands/build-plugin.js +135 -0
- package/dist/commands/init.d.ts +8 -2
- package/dist/commands/init.js +19 -7
- package/dist/commands/sync-telemetry.d.ts +33 -0
- package/dist/commands/sync-telemetry.js +186 -0
- package/dist/commands/sync.d.ts +1 -0
- package/dist/commands/sync.js +84 -118
- package/dist/commands/uninstall.d.ts +2 -0
- package/dist/commands/uninstall.js +145 -0
- package/dist/commands/upgrade.d.ts +12 -0
- package/dist/commands/upgrade.js +67 -16
- package/dist/generated/bundled-types.js +36 -36
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/health/__tests__/cli-distribution-checks.test.d.ts +1 -0
- package/dist/health/__tests__/cli-distribution-checks.test.js +199 -0
- package/dist/health/checks.d.ts +2 -0
- package/dist/health/checks.js +131 -18
- package/dist/health/runner.js +6 -2
- package/dist/index.js +4 -0
- package/dist/sync/executor.d.ts +2 -0
- package/dist/sync/executor.js +8 -4
- package/dist/types.d.ts +2 -0
- package/dist/ui/banner.js +15 -6
- package/dist/utils/__tests__/sqlite.test.js +84 -72
- package/dist/utils/sqlite-adapter.d.ts +48 -0
- package/dist/utils/sqlite-adapter.js +147 -0
- package/dist/utils/sqlite.d.ts +12 -5
- package/dist/utils/sqlite.js +12 -22
- package/dist/utils/zip.d.ts +6 -0
- package/dist/utils/zip.js +28 -3
- package/package.json +2 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "0.
|
|
1
|
+
export declare const VERSION = "0.8.2";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Auto-generated by scripts/embed-types.ts -- do not edit
|
|
2
|
-
export const VERSION = "0.
|
|
2
|
+
export const VERSION = "0.8.2";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the first-party distribution checks added to runwork doctor.
|
|
3
|
+
*
|
|
4
|
+
* These checks rely on fetch() to hit https://runwork.ai/cli/latest.json.
|
|
5
|
+
* We stub globalThis.fetch to redirect those calls to a local HTTP server
|
|
6
|
+
* so the suite has no outbound network dependency.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
9
|
+
import { createServer } from 'node:http';
|
|
10
|
+
import { checkCliVersion, checkCliArtifactReachable, checkCliInstallLocation, } from '../checks.js';
|
|
11
|
+
function startServer() {
|
|
12
|
+
return new Promise((resolvePromise) => {
|
|
13
|
+
let currentManifest = null;
|
|
14
|
+
let manifestStatus = 200;
|
|
15
|
+
const artifactStatuses = new Map();
|
|
16
|
+
const server = createServer((req, res) => {
|
|
17
|
+
const url = req.url ?? '/';
|
|
18
|
+
if (url === '/cli/latest.json') {
|
|
19
|
+
if (currentManifest === null) {
|
|
20
|
+
res.writeHead(manifestStatus, { 'Content-Type': 'application/json' });
|
|
21
|
+
res.end('{"error": "not available"}');
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
res.writeHead(manifestStatus, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
25
|
+
res.end(JSON.stringify(currentManifest));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
// Artifact route: /cli/releases/<version>/<artifact>
|
|
29
|
+
const releaseMatch = url.match(/^\/cli\/releases\/[^/]+\/runwork-([^/]+)\.(tar\.gz|zip)$/);
|
|
30
|
+
if (releaseMatch) {
|
|
31
|
+
const key = releaseMatch[1];
|
|
32
|
+
const status = artifactStatuses.get(key) ?? 200;
|
|
33
|
+
res.writeHead(status, { 'Content-Type': 'application/octet-stream' });
|
|
34
|
+
res.end('');
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
res.writeHead(404);
|
|
38
|
+
res.end('not found');
|
|
39
|
+
});
|
|
40
|
+
server.listen(0, '127.0.0.1', () => {
|
|
41
|
+
const addr = server.address();
|
|
42
|
+
if (addr && typeof addr === 'object') {
|
|
43
|
+
const port = addr.port;
|
|
44
|
+
resolvePromise({
|
|
45
|
+
url: `http://127.0.0.1:${port}`,
|
|
46
|
+
stop: () => new Promise((stop) => server.close(() => stop())),
|
|
47
|
+
setManifest(m, s = 200) {
|
|
48
|
+
currentManifest = m;
|
|
49
|
+
manifestStatus = s;
|
|
50
|
+
},
|
|
51
|
+
setArtifactStatus(key, s) {
|
|
52
|
+
artifactStatuses.set(key, s);
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Redirect fetch calls to https://runwork.ai/... at the real test server.
|
|
61
|
+
* Returns a restore function.
|
|
62
|
+
*/
|
|
63
|
+
function redirectFetch(baseUrl) {
|
|
64
|
+
const realFetch = globalThis.fetch;
|
|
65
|
+
globalThis.fetch = ((input, init) => {
|
|
66
|
+
const href = typeof input === 'string' ? input : input.toString();
|
|
67
|
+
if (href.startsWith('https://runwork.ai')) {
|
|
68
|
+
const redirected = href.replace('https://runwork.ai', baseUrl);
|
|
69
|
+
return realFetch(redirected, init);
|
|
70
|
+
}
|
|
71
|
+
return realFetch(input, init);
|
|
72
|
+
});
|
|
73
|
+
return () => {
|
|
74
|
+
globalThis.fetch = realFetch;
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
describe('checkCliVersion (runwork.ai backed)', () => {
|
|
78
|
+
let server;
|
|
79
|
+
let restoreFetch;
|
|
80
|
+
beforeEach(async () => {
|
|
81
|
+
server = await startServer();
|
|
82
|
+
restoreFetch = redirectFetch(server.url);
|
|
83
|
+
});
|
|
84
|
+
afterEach(async () => {
|
|
85
|
+
restoreFetch();
|
|
86
|
+
await server.stop();
|
|
87
|
+
});
|
|
88
|
+
it('skips with an explicit unreachable message when the manifest 404s', async () => {
|
|
89
|
+
server.setManifest(null, 404);
|
|
90
|
+
const result = await checkCliVersion();
|
|
91
|
+
expect(result.status).toBe('skip');
|
|
92
|
+
expect(result.message).toContain('unreachable');
|
|
93
|
+
});
|
|
94
|
+
it('passes when manifest version matches current VERSION', async () => {
|
|
95
|
+
// We cannot read VERSION here without depending on the generated file
|
|
96
|
+
// at test time; instead, serve "0.0.0" and expect the test to compare.
|
|
97
|
+
// To make this deterministic, we fetch the current VERSION from the
|
|
98
|
+
// module under test via a side channel. See the runner for the value.
|
|
99
|
+
const { VERSION } = await import('../../generated/version.js');
|
|
100
|
+
server.setManifest({ version: VERSION });
|
|
101
|
+
const result = await checkCliVersion();
|
|
102
|
+
expect(result.status).toBe('pass');
|
|
103
|
+
expect(result.message).toContain(VERSION);
|
|
104
|
+
});
|
|
105
|
+
it('warns when the manifest has a newer version', async () => {
|
|
106
|
+
server.setManifest({ version: '99.99.99' });
|
|
107
|
+
const result = await checkCliVersion();
|
|
108
|
+
expect(result.status).toBe('warn');
|
|
109
|
+
expect(result.message).toContain('99.99.99');
|
|
110
|
+
expect(result.fix).toBe('runwork upgrade');
|
|
111
|
+
});
|
|
112
|
+
it('skips when the manifest has no version field', async () => {
|
|
113
|
+
server.setManifest({ name: 'runwork' });
|
|
114
|
+
const result = await checkCliVersion();
|
|
115
|
+
expect(result.status).toBe('skip');
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
describe('checkCliArtifactReachable', () => {
|
|
119
|
+
let server;
|
|
120
|
+
let restoreFetch;
|
|
121
|
+
beforeEach(async () => {
|
|
122
|
+
server = await startServer();
|
|
123
|
+
restoreFetch = redirectFetch(server.url);
|
|
124
|
+
});
|
|
125
|
+
afterEach(async () => {
|
|
126
|
+
restoreFetch();
|
|
127
|
+
await server.stop();
|
|
128
|
+
});
|
|
129
|
+
function platformKey() {
|
|
130
|
+
const os = process.platform === 'darwin' ? 'darwin' : process.platform === 'linux' ? 'linux' : 'windows';
|
|
131
|
+
const arch = process.arch === 'arm64' ? 'arm64' : 'x64';
|
|
132
|
+
return `${os}-${arch}`;
|
|
133
|
+
}
|
|
134
|
+
it('fails when the manifest is unreachable', async () => {
|
|
135
|
+
server.setManifest(null, 500);
|
|
136
|
+
const result = await checkCliArtifactReachable();
|
|
137
|
+
expect(result.status).toBe('fail');
|
|
138
|
+
expect(result.message).toContain('unreachable');
|
|
139
|
+
});
|
|
140
|
+
it('fails when the manifest has no artifact for the host platform', async () => {
|
|
141
|
+
server.setManifest({
|
|
142
|
+
version: '99.99.99',
|
|
143
|
+
artifacts: {
|
|
144
|
+
'unknown-unknown': { path: '/cli/releases/99.99.99/runwork-unknown-unknown.tar.gz', sha256: 'deadbeef' },
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
const result = await checkCliArtifactReachable();
|
|
148
|
+
expect(result.status).toBe('fail');
|
|
149
|
+
expect(result.message).toMatch(/no artifact for/);
|
|
150
|
+
});
|
|
151
|
+
it('passes when the artifact HEAD returns 200', async () => {
|
|
152
|
+
const key = platformKey();
|
|
153
|
+
const ext = key === 'windows-x64' ? 'zip' : 'tar.gz';
|
|
154
|
+
server.setManifest({
|
|
155
|
+
version: '99.99.99',
|
|
156
|
+
artifacts: {
|
|
157
|
+
[key]: {
|
|
158
|
+
path: `/cli/releases/99.99.99/runwork-${key}.${ext}`,
|
|
159
|
+
sha256: 'deadbeef',
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
server.setArtifactStatus(key, 200);
|
|
164
|
+
const result = await checkCliArtifactReachable();
|
|
165
|
+
expect(result.status).toBe('pass');
|
|
166
|
+
expect(result.message).toContain(key);
|
|
167
|
+
});
|
|
168
|
+
it('fails when the artifact HEAD returns a non-2xx status', async () => {
|
|
169
|
+
const key = platformKey();
|
|
170
|
+
const ext = key === 'windows-x64' ? 'zip' : 'tar.gz';
|
|
171
|
+
server.setManifest({
|
|
172
|
+
version: '99.99.99',
|
|
173
|
+
artifacts: {
|
|
174
|
+
[key]: {
|
|
175
|
+
path: `/cli/releases/99.99.99/runwork-${key}.${ext}`,
|
|
176
|
+
sha256: 'deadbeef',
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
server.setArtifactStatus(key, 404);
|
|
181
|
+
const result = await checkCliArtifactReachable();
|
|
182
|
+
expect(result.status).toBe('fail');
|
|
183
|
+
expect(result.message).toContain('404');
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
describe('checkCliInstallLocation', () => {
|
|
187
|
+
const originalExecPath = process.execPath;
|
|
188
|
+
const originalArgv1 = process.argv[1];
|
|
189
|
+
afterEach(() => {
|
|
190
|
+
Object.defineProperty(process, 'execPath', { value: originalExecPath, writable: true });
|
|
191
|
+
process.argv[1] = originalArgv1;
|
|
192
|
+
});
|
|
193
|
+
it('warns when process does not run from the canonical ~/.runwork/bin/runwork', async () => {
|
|
194
|
+
Object.defineProperty(process, 'execPath', { value: '/usr/local/bin/node', writable: true });
|
|
195
|
+
process.argv[1] = '/opt/homebrew/bin/runwork';
|
|
196
|
+
const result = await checkCliInstallLocation();
|
|
197
|
+
expect(result.status).toBe('warn');
|
|
198
|
+
});
|
|
199
|
+
});
|
package/dist/health/checks.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export interface DoctorContext {
|
|
|
10
10
|
export declare function buildContext(): DoctorContext;
|
|
11
11
|
export declare function checkSystemDeps(): Promise<CheckResult>;
|
|
12
12
|
export declare function checkCliVersion(): Promise<CheckResult>;
|
|
13
|
+
export declare function checkCliArtifactReachable(): Promise<CheckResult>;
|
|
14
|
+
export declare function checkCliInstallLocation(): Promise<CheckResult>;
|
|
13
15
|
export declare function checkAuthAndNetwork(ctx: DoctorContext): Promise<{
|
|
14
16
|
auth: CheckResult;
|
|
15
17
|
network: CheckResult;
|
package/dist/health/checks.js
CHANGED
|
@@ -1,11 +1,37 @@
|
|
|
1
1
|
import { execFileSync } from 'child_process';
|
|
2
2
|
import { existsSync, readFileSync } from 'fs';
|
|
3
|
-
import { join } from 'path';
|
|
4
|
-
import { homedir } from 'os';
|
|
3
|
+
import { join, sep } from 'path';
|
|
4
|
+
import { homedir, platform as osPlatform, arch as osArch } from 'os';
|
|
5
5
|
import { VERSION } from '../generated/version.js';
|
|
6
6
|
import { getCredentials } from '../auth/store.js';
|
|
7
7
|
import { ApiClient } from '../api/client.js';
|
|
8
8
|
import { detectAgents, getAdapterBySlug } from '../agents/detect.js';
|
|
9
|
+
// First-party distribution endpoints. Defaults match the plan, can be
|
|
10
|
+
// overridden via RUNWORK_DOWNLOAD_BASE_URL for staging or debugging.
|
|
11
|
+
const BASE_URL = process.env.RUNWORK_DOWNLOAD_BASE_URL || 'https://runwork.ai';
|
|
12
|
+
const LATEST_JSON_URL = `${BASE_URL}/cli/latest.json`;
|
|
13
|
+
function detectPlatform() {
|
|
14
|
+
const rawOs = osPlatform();
|
|
15
|
+
const rawArch = osArch();
|
|
16
|
+
const os = rawOs === 'darwin' ? 'darwin' : rawOs === 'linux' ? 'linux' : rawOs === 'win32' ? 'windows' : null;
|
|
17
|
+
const arch = rawArch === 'x64' ? 'x64' : rawArch === 'arm64' ? 'arm64' : null;
|
|
18
|
+
return {
|
|
19
|
+
os,
|
|
20
|
+
arch,
|
|
21
|
+
key: os && arch ? `${os}-${arch}` : null,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
async function fetchReleaseManifest() {
|
|
25
|
+
try {
|
|
26
|
+
const response = await fetch(LATEST_JSON_URL, { cache: 'no-store' });
|
|
27
|
+
if (!response.ok)
|
|
28
|
+
return null;
|
|
29
|
+
return await response.json();
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
9
35
|
export function buildContext() {
|
|
10
36
|
const credentials = getCredentials();
|
|
11
37
|
const client = credentials ? new ApiClient(credentials) : null;
|
|
@@ -46,29 +72,116 @@ export async function checkSystemDeps() {
|
|
|
46
72
|
}
|
|
47
73
|
// ── 2. CLI version ───────────────────────────────────────────────────
|
|
48
74
|
export async function checkCliVersion() {
|
|
75
|
+
const manifest = await fetchReleaseManifest();
|
|
76
|
+
if (!manifest) {
|
|
77
|
+
return {
|
|
78
|
+
name: 'cli-version',
|
|
79
|
+
status: 'skip',
|
|
80
|
+
message: `${VERSION} (${LATEST_JSON_URL} unreachable)`,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
const latest = manifest.version?.replace(/^v/, '') || null;
|
|
84
|
+
if (!latest) {
|
|
85
|
+
return { name: 'cli-version', status: 'skip', message: `${VERSION} (could not parse latest)` };
|
|
86
|
+
}
|
|
87
|
+
if (latest === VERSION) {
|
|
88
|
+
return { name: 'cli-version', status: 'pass', message: `${VERSION} (latest)` };
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
name: 'cli-version',
|
|
92
|
+
status: 'warn',
|
|
93
|
+
message: `${VERSION} (${latest} available)`,
|
|
94
|
+
fix: 'runwork upgrade',
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
// ── 2a. CLI artifact reachability for this host ──────────────────────
|
|
98
|
+
export async function checkCliArtifactReachable() {
|
|
99
|
+
const platform = detectPlatform();
|
|
100
|
+
if (!platform.key) {
|
|
101
|
+
return {
|
|
102
|
+
name: 'cli-artifact',
|
|
103
|
+
status: 'skip',
|
|
104
|
+
message: `unsupported host (${osPlatform()}/${osArch()})`,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
const manifest = await fetchReleaseManifest();
|
|
108
|
+
if (!manifest) {
|
|
109
|
+
return {
|
|
110
|
+
name: 'cli-artifact',
|
|
111
|
+
status: 'fail',
|
|
112
|
+
message: `${LATEST_JSON_URL} unreachable`,
|
|
113
|
+
fix: 'check network / outbound access to runwork.ai',
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const entry = manifest.artifacts?.[platform.key];
|
|
117
|
+
if (!entry?.path) {
|
|
118
|
+
return {
|
|
119
|
+
name: 'cli-artifact',
|
|
120
|
+
status: 'fail',
|
|
121
|
+
message: `manifest has no artifact for ${platform.key}`,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
const artifactUrl = `${BASE_URL}${entry.path}`;
|
|
49
125
|
try {
|
|
50
|
-
const response = await fetch('
|
|
126
|
+
const response = await fetch(artifactUrl, { method: 'HEAD' });
|
|
51
127
|
if (!response.ok) {
|
|
52
|
-
return {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return { name: 'cli-version', status: 'skip', message: `${VERSION} (could not parse latest)` };
|
|
58
|
-
}
|
|
59
|
-
if (latest === VERSION) {
|
|
60
|
-
return { name: 'cli-version', status: 'pass', message: `${VERSION} (latest)` };
|
|
128
|
+
return {
|
|
129
|
+
name: 'cli-artifact',
|
|
130
|
+
status: 'fail',
|
|
131
|
+
message: `${artifactUrl} returned HTTP ${response.status}`,
|
|
132
|
+
};
|
|
61
133
|
}
|
|
134
|
+
return { name: 'cli-artifact', status: 'pass', message: `${platform.key} reachable` };
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
62
138
|
return {
|
|
63
|
-
name: 'cli-
|
|
64
|
-
status: '
|
|
65
|
-
message: `${
|
|
66
|
-
fix: 'runwork upgrade',
|
|
139
|
+
name: 'cli-artifact',
|
|
140
|
+
status: 'fail',
|
|
141
|
+
message: `${artifactUrl} unreachable: ${msg}`,
|
|
67
142
|
};
|
|
68
143
|
}
|
|
69
|
-
|
|
70
|
-
|
|
144
|
+
}
|
|
145
|
+
// ── 2b. Canonical install location ───────────────────────────────────
|
|
146
|
+
export async function checkCliInstallLocation() {
|
|
147
|
+
const isWindows = osPlatform() === 'win32';
|
|
148
|
+
const home = homedir();
|
|
149
|
+
const canonicalDir = join(home, '.runwork', 'bin');
|
|
150
|
+
const canonicalBinary = isWindows
|
|
151
|
+
? join(canonicalDir, 'runwork.exe')
|
|
152
|
+
: join(canonicalDir, 'runwork');
|
|
153
|
+
// process.execPath points at the running binary in a compiled bun binary;
|
|
154
|
+
// in npm/node mode it points at node, so also consider argv[1].
|
|
155
|
+
const candidates = [process.execPath, process.argv[1] || ''].filter(Boolean);
|
|
156
|
+
const runsFromCanonical = candidates.some((p) => normalizePath(p) === normalizePath(canonicalBinary));
|
|
157
|
+
if (runsFromCanonical) {
|
|
158
|
+
return {
|
|
159
|
+
name: 'cli-install-location',
|
|
160
|
+
status: 'pass',
|
|
161
|
+
message: `canonical (${canonicalBinary})`,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
// Second best: canonical binary exists but the user is running a different copy.
|
|
165
|
+
if (existsSync(canonicalBinary)) {
|
|
166
|
+
return {
|
|
167
|
+
name: 'cli-install-location',
|
|
168
|
+
status: 'warn',
|
|
169
|
+
message: `running from ${candidates[0] || '(unknown)'}, canonical binary also exists at ${canonicalBinary}`,
|
|
170
|
+
fix: `use the canonical binary by ensuring ${canonicalDir} comes first on PATH, or uninstall the duplicate`,
|
|
171
|
+
};
|
|
71
172
|
}
|
|
173
|
+
// Third case: no canonical binary. That is normal for a system-managed install (brew, npm, etc).
|
|
174
|
+
return {
|
|
175
|
+
name: 'cli-install-location',
|
|
176
|
+
status: 'warn',
|
|
177
|
+
message: `not at canonical location (running from ${candidates[0] || '(unknown)'})`,
|
|
178
|
+
fix: `reinstall with the first-party installer to unify with the desktop app: curl -fsSL ${BASE_URL}/install.sh | sh`,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function normalizePath(p) {
|
|
182
|
+
// Collapse trailing separators and case-normalise on Windows.
|
|
183
|
+
const trimmed = p.replace(new RegExp(`[${sep === '\\' ? '\\\\' : sep}]+$`), '');
|
|
184
|
+
return osPlatform() === 'win32' ? trimmed.toLowerCase() : trimmed;
|
|
72
185
|
}
|
|
73
186
|
// ── 3 & 4. Authentication + Network (combined) ──────────────────────
|
|
74
187
|
export async function checkAuthAndNetwork(ctx) {
|
package/dist/health/runner.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import { buildContext, checkSystemDeps, checkCliVersion, checkAuthAndNetwork, checkProjectConfig, checkAppExists, checkGitRemote, checkAgentSetup, } from './checks.js';
|
|
1
|
+
import { buildContext, checkSystemDeps, checkCliVersion, checkCliArtifactReachable, checkCliInstallLocation, checkAuthAndNetwork, checkProjectConfig, checkAppExists, checkGitRemote, checkAgentSetup, } from './checks.js';
|
|
2
2
|
export async function runAllChecks() {
|
|
3
3
|
const ctx = buildContext();
|
|
4
4
|
const checks = [];
|
|
5
5
|
// 1. System deps
|
|
6
6
|
checks.push(await checkSystemDeps());
|
|
7
|
-
// 2. CLI version
|
|
7
|
+
// 2. CLI version (runwork.ai/cli/latest.json)
|
|
8
8
|
checks.push(await checkCliVersion());
|
|
9
|
+
// 2a. CLI artifact reachability for this host platform
|
|
10
|
+
checks.push(await checkCliArtifactReachable());
|
|
11
|
+
// 2b. Canonical install location
|
|
12
|
+
checks.push(await checkCliInstallLocation());
|
|
9
13
|
// 3 & 4. Auth + Network (combined call)
|
|
10
14
|
const { auth, network } = await checkAuthAndNetwork(ctx);
|
|
11
15
|
checks.push(auth);
|
package/dist/index.js
CHANGED
|
@@ -22,6 +22,8 @@ import { agentsCommand } from './commands/agents.js';
|
|
|
22
22
|
import { mcpCommand } from './commands/mcp.js';
|
|
23
23
|
import { setupCommand } from './commands/setup.js';
|
|
24
24
|
import { syncCommand } from './commands/sync.js';
|
|
25
|
+
import { buildPluginCommand } from './commands/build-plugin.js';
|
|
26
|
+
import { uninstallCommand } from './commands/uninstall.js';
|
|
25
27
|
import { appsCommand } from './commands/apps.js';
|
|
26
28
|
import { doctorCommand } from './commands/doctor.js';
|
|
27
29
|
import { handleGitCredentialRequest } from './git/credentials.js';
|
|
@@ -58,6 +60,8 @@ program.addCommand(agentsCommand);
|
|
|
58
60
|
program.addCommand(mcpCommand);
|
|
59
61
|
program.addCommand(setupCommand);
|
|
60
62
|
program.addCommand(syncCommand);
|
|
63
|
+
program.addCommand(buildPluginCommand);
|
|
64
|
+
program.addCommand(uninstallCommand);
|
|
61
65
|
program.addCommand(appsCommand);
|
|
62
66
|
program.addCommand(doctorCommand);
|
|
63
67
|
program.addHelpText('after', '\nFor AI agents: run "runwork info --json" to discover app context and available commands.');
|
package/dist/sync/executor.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ interface ExecutorContext {
|
|
|
6
6
|
workspaceId: string;
|
|
7
7
|
adapters: AgentAdapter[];
|
|
8
8
|
scopes: Array<'project' | 'user'>;
|
|
9
|
+
/** True when MCP servers are configured; app-sourced skills are skipped since MCP provides them */
|
|
10
|
+
hasMcp?: boolean;
|
|
9
11
|
}
|
|
10
12
|
/**
|
|
11
13
|
* Execute the resolved sync plan: pull remote skills, push local skills,
|
package/dist/sync/executor.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { toSlug } from '../agents/types.js';
|
|
1
2
|
import { contentHash } from './hash.js';
|
|
2
3
|
/**
|
|
3
4
|
* Execute the resolved sync plan: pull remote skills, push local skills,
|
|
@@ -10,7 +11,7 @@ export async function executeSyncPlan(plan, resolvedConflicts, ctx) {
|
|
|
10
11
|
if (!action.remoteContent)
|
|
11
12
|
continue;
|
|
12
13
|
const hash = contentHash(action.remoteContent);
|
|
13
|
-
writeSkillToAgents(action.name, action.remoteContent, ctx);
|
|
14
|
+
writeSkillToAgents(action.name, action.remoteContent, action.source, ctx);
|
|
14
15
|
newHashes[action.name] = {
|
|
15
16
|
localHash: hash,
|
|
16
17
|
remoteHash: hash,
|
|
@@ -54,7 +55,7 @@ export async function executeSyncPlan(plan, resolvedConflicts, ctx) {
|
|
|
54
55
|
console.log(` Pushed (conflict resolved): ${action.name}`);
|
|
55
56
|
}
|
|
56
57
|
else if (resolution === 'remote' && action.remoteContent) {
|
|
57
|
-
writeSkillToAgents(action.name, action.remoteContent, ctx);
|
|
58
|
+
writeSkillToAgents(action.name, action.remoteContent, action.source, ctx);
|
|
58
59
|
const hash = contentHash(action.remoteContent);
|
|
59
60
|
newHashes[action.name] = { localHash: hash, remoteHash: hash, source: 'external', remoteId: action.remoteId };
|
|
60
61
|
console.log(` Pulled (conflict resolved): ${action.name}`);
|
|
@@ -70,14 +71,17 @@ export async function executeSyncPlan(plan, resolvedConflicts, ctx) {
|
|
|
70
71
|
}
|
|
71
72
|
return newHashes;
|
|
72
73
|
}
|
|
73
|
-
function writeSkillToAgents(name, content, ctx) {
|
|
74
|
+
function writeSkillToAgents(name, content, source, ctx) {
|
|
74
75
|
const skillFile = {
|
|
75
76
|
name,
|
|
76
|
-
filename: name
|
|
77
|
+
filename: toSlug(name),
|
|
77
78
|
content,
|
|
78
79
|
description: extractDescription(content),
|
|
79
80
|
};
|
|
80
81
|
for (const adapter of ctx.adapters) {
|
|
82
|
+
// Skip app-sourced skills for adapters where MCP already provides them as skill_* tools
|
|
83
|
+
if (source === 'app' && ctx.hasMcp && adapter.mcpProvidesSkills)
|
|
84
|
+
continue;
|
|
81
85
|
for (const scope of ctx.scopes) {
|
|
82
86
|
if (!adapter.supportsSkills())
|
|
83
87
|
continue;
|
package/dist/types.d.ts
CHANGED
|
@@ -130,6 +130,8 @@ export interface SetupState {
|
|
|
130
130
|
lastSyncAt: string;
|
|
131
131
|
mcpServers: string[];
|
|
132
132
|
skills: string[];
|
|
133
|
+
/** Kebab-case filenames of synced skills (used for safe cleanup) */
|
|
134
|
+
skillFilenames?: string[];
|
|
133
135
|
skillHashes?: Record<string, {
|
|
134
136
|
localHash: string;
|
|
135
137
|
remoteHash: string;
|
package/dist/ui/banner.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { homedir } from 'os';
|
|
2
2
|
import { bold, cyan, dim, green } from './colors.js';
|
|
3
|
+
/** Format an absolute path for display, collapsing $HOME to ~. */
|
|
4
|
+
function prettyPath(dir) {
|
|
5
|
+
const home = homedir();
|
|
6
|
+
if (dir === home)
|
|
7
|
+
return '~';
|
|
8
|
+
if (dir.startsWith(home + '/'))
|
|
9
|
+
return '~' + dir.slice(home.length);
|
|
10
|
+
return dir;
|
|
11
|
+
}
|
|
3
12
|
export const SUPPORTED_AGENTS = [
|
|
4
13
|
{ id: 'claude-code', name: 'Claude Code', command: 'cd {dir} && claude' },
|
|
5
14
|
{ id: 'cursor', name: 'Cursor', command: 'cursor {dir}' },
|
|
@@ -55,16 +64,16 @@ export async function runAgentWizard(directory, configuredAgents) {
|
|
|
55
64
|
console.log(getAgentGuidance(directory));
|
|
56
65
|
return;
|
|
57
66
|
}
|
|
58
|
-
const
|
|
67
|
+
const displayDir = prettyPath(directory);
|
|
59
68
|
// If we already know which agents are configured, skip the prompt
|
|
60
69
|
if (configuredAgents && configuredAgents.length > 0) {
|
|
61
70
|
const knownAgent = SUPPORTED_AGENTS.find(a => configuredAgents.includes(a.id));
|
|
62
71
|
if (knownAgent) {
|
|
63
|
-
const agentCmd = knownAgent.command.replace(/{dir}/g,
|
|
72
|
+
const agentCmd = knownAgent.command.replace(/{dir}/g, displayDir);
|
|
64
73
|
console.log('');
|
|
65
74
|
console.log(bold('Get started:'));
|
|
66
75
|
console.log('');
|
|
67
|
-
console.log(` ${dim('1.')} cd ${
|
|
76
|
+
console.log(` ${dim('1.')} cd ${displayDir} && runwork dev`);
|
|
68
77
|
if (knownAgent.id === 'claude-desktop') {
|
|
69
78
|
console.log(` ${dim('2.')} ${agentCmd}`);
|
|
70
79
|
}
|
|
@@ -90,11 +99,11 @@ export async function runAgentWizard(directory, configuredAgents) {
|
|
|
90
99
|
const agent = SUPPORTED_AGENTS.find(a => a.id === result);
|
|
91
100
|
if (!agent)
|
|
92
101
|
return;
|
|
93
|
-
const agentCmd = agent.command.replace(/{dir}/g,
|
|
102
|
+
const agentCmd = agent.command.replace(/{dir}/g, displayDir);
|
|
94
103
|
console.log('');
|
|
95
104
|
console.log(bold('Get started:'));
|
|
96
105
|
console.log('');
|
|
97
|
-
console.log(` ${dim('1.')} cd ${
|
|
106
|
+
console.log(` ${dim('1.')} cd ${displayDir} && runwork dev`);
|
|
98
107
|
if (agent.id === 'claude-desktop') {
|
|
99
108
|
console.log(` ${dim('2.')} ${agentCmd}`);
|
|
100
109
|
console.log('');
|