runwork 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/__tests__/claude-code-stats.test.d.ts +1 -0
- package/dist/agents/__tests__/claude-code-stats.test.js +153 -0
- package/dist/agents/__tests__/claude-desktop-stats.test.d.ts +1 -0
- package/dist/agents/__tests__/claude-desktop-stats.test.js +280 -0
- package/dist/agents/__tests__/codex-stats.test.d.ts +1 -0
- package/dist/agents/__tests__/codex-stats.test.js +262 -0
- package/dist/agents/__tests__/cursor-stats.test.d.ts +1 -0
- package/dist/agents/__tests__/cursor-stats.test.js +183 -0
- package/dist/agents/__tests__/graceful-degradation.test.d.ts +1 -0
- package/dist/agents/__tests__/graceful-degradation.test.js +123 -0
- package/dist/agents/__tests__/instruction-hint.test.d.ts +1 -0
- package/dist/agents/__tests__/instruction-hint.test.js +69 -0
- package/dist/agents/__tests__/intro-skill.test.d.ts +1 -0
- package/dist/agents/__tests__/intro-skill.test.js +135 -0
- package/dist/agents/__tests__/schema-validation.test.d.ts +1 -0
- package/dist/agents/__tests__/schema-validation.test.js +212 -0
- package/dist/agents/claude-code.d.ts +16 -1
- package/dist/agents/claude-code.js +344 -21
- package/dist/agents/claude-desktop.d.ts +10 -1
- package/dist/agents/claude-desktop.js +250 -8
- package/dist/agents/cline.d.ts +13 -0
- package/dist/agents/cline.js +88 -0
- package/dist/agents/codex.d.ts +7 -2
- package/dist/agents/codex.js +130 -18
- package/dist/agents/cursor.d.ts +6 -1
- package/dist/agents/cursor.js +164 -7
- package/dist/agents/detect.js +30 -7
- package/dist/agents/gemini.d.ts +6 -3
- package/dist/agents/gemini.js +57 -20
- package/dist/agents/generic-adapter.d.ts +23 -0
- package/dist/agents/generic-adapter.js +106 -0
- package/dist/agents/intro-skill.d.ts +36 -0
- package/dist/agents/intro-skill.js +358 -0
- package/dist/agents/registry.d.ts +53 -0
- package/dist/agents/registry.js +219 -0
- package/dist/agents/types.d.ts +44 -1
- package/dist/agents/types.js +11 -4
- package/dist/agents/utils/instruction-hint.d.ts +11 -0
- package/dist/agents/utils/instruction-hint.js +62 -0
- package/dist/agents/utils/json-config.js +3 -3
- package/dist/agents/windsurf.d.ts +2 -0
- package/dist/agents/windsurf.js +26 -9
- package/dist/api/client.d.ts +85 -2
- package/dist/api/client.js +40 -10
- package/dist/auth/__tests__/login-flow.test.d.ts +1 -0
- package/dist/auth/__tests__/login-flow.test.js +125 -0
- package/dist/auth/login-flow.d.ts +16 -0
- package/dist/auth/login-flow.js +101 -1
- package/dist/commands/clone.d.ts +9 -1
- package/dist/commands/clone.js +40 -12
- package/dist/commands/deploy.js +27 -4
- package/dist/commands/dev.js +35 -1
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.js +82 -0
- package/dist/commands/info.js +6 -2
- package/dist/commands/init.d.ts +10 -2
- package/dist/commands/init.js +58 -15
- package/dist/commands/integrations.js +47 -10
- package/dist/commands/login.js +31 -4
- package/dist/commands/mcp.js +143 -15
- package/dist/commands/setup.js +47 -122
- package/dist/commands/skills.js +230 -12
- package/dist/commands/sync.d.ts +8 -0
- package/dist/commands/sync.js +398 -65
- package/dist/commands/welcome.js +6 -3
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/health/__tests__/checks.test.d.ts +1 -0
- package/dist/health/__tests__/checks.test.js +223 -0
- package/dist/health/checks.d.ts +20 -0
- package/dist/health/checks.js +392 -0
- package/dist/health/runner.d.ts +2 -0
- package/dist/health/runner.js +26 -0
- package/dist/health/types.d.ts +12 -0
- package/dist/health/types.js +1 -0
- package/dist/index.js +32 -0
- package/dist/sync/__tests__/change-detect.test.d.ts +1 -0
- package/dist/sync/__tests__/change-detect.test.js +123 -0
- package/dist/sync/__tests__/hash.test.d.ts +1 -0
- package/dist/sync/__tests__/hash.test.js +18 -0
- package/dist/sync/change-detect.d.ts +19 -0
- package/dist/sync/change-detect.js +136 -0
- package/dist/sync/conflict-ui.d.ts +5 -0
- package/dist/sync/conflict-ui.js +78 -0
- package/dist/sync/executor.d.ts +21 -0
- package/dist/sync/executor.js +98 -0
- package/dist/sync/hash.d.ts +1 -0
- package/dist/sync/hash.js +4 -0
- package/dist/sync/types.d.ts +24 -0
- package/dist/sync/types.js +1 -0
- package/dist/types.d.ts +12 -0
- package/dist/ui/__tests__/banner.test.js +17 -1
- package/dist/ui/banner.d.ts +1 -1
- package/dist/ui/banner.js +27 -2
- package/dist/utils/__tests__/resolve.test.d.ts +1 -0
- package/dist/utils/__tests__/resolve.test.js +95 -0
- package/dist/utils/__tests__/sqlite.test.d.ts +1 -0
- package/dist/utils/__tests__/sqlite.test.js +77 -0
- package/dist/utils/agent-guidance.d.ts +32 -0
- package/dist/utils/agent-guidance.js +106 -0
- package/dist/utils/fs.js +2 -2
- package/dist/utils/help-json.d.ts +56 -0
- package/dist/utils/help-json.js +85 -0
- package/dist/utils/resolve.d.ts +12 -0
- package/dist/utils/resolve.js +52 -0
- package/dist/utils/setup-state.d.ts +3 -0
- package/dist/utils/setup-state.js +19 -0
- package/dist/utils/sqlite.d.ts +7 -0
- package/dist/utils/sqlite.js +24 -0
- package/dist/utils/which.d.ts +6 -0
- package/dist/utils/which.js +18 -0
- package/package.json +7 -3
- package/dist/agents/copilot-cli.d.ts +0 -10
- package/dist/agents/copilot-cli.js +0 -46
- package/dist/agents/copilot-vscode.d.ts +0 -10
- package/dist/agents/copilot-vscode.js +0 -37
package/dist/auth/login-flow.js
CHANGED
|
@@ -20,6 +20,106 @@ export async function performLogin(baseUrl) {
|
|
|
20
20
|
console.log(`If browser didn't open, visit: ${loginUrl}`);
|
|
21
21
|
console.log('');
|
|
22
22
|
console.log('Waiting for authentication...');
|
|
23
|
+
return pollAndSave(client, sessionId, url);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Initiate a login session without opening the browser.
|
|
27
|
+
* Prints the login URL to stdout and polls for completion.
|
|
28
|
+
* If printOnly is true, prints the URL and returns null (exits without polling).
|
|
29
|
+
*/
|
|
30
|
+
export async function performLoginNoOpen(baseUrl, printOnly) {
|
|
31
|
+
const url = baseUrl || DEFAULT_BASE_URL;
|
|
32
|
+
const client = new ApiClient({ apiKey: '', email: '', baseUrl: url });
|
|
33
|
+
const { loginUrl } = await client.initiateLogin();
|
|
34
|
+
// Print the URL for the agent/human to use
|
|
35
|
+
console.log(loginUrl);
|
|
36
|
+
if (printOnly) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
console.error('Waiting for authentication...');
|
|
40
|
+
return pollAndSave(client, extractSessionId(loginUrl), url);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Resume a previously initiated login session by providing the login URL.
|
|
44
|
+
* Extracts the session ID from the URL and polls until authenticated.
|
|
45
|
+
*/
|
|
46
|
+
export async function performLoginFromUrl(loginUrl) {
|
|
47
|
+
const sessionId = extractSessionId(loginUrl);
|
|
48
|
+
const baseUrl = extractBaseUrl(loginUrl);
|
|
49
|
+
const client = new ApiClient({ apiKey: '', email: '', baseUrl });
|
|
50
|
+
console.log('Waiting for authentication...');
|
|
51
|
+
return pollAndSave(client, sessionId, baseUrl);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Authenticate directly with an API key (no browser flow).
|
|
55
|
+
* Validates the key by calling the API, then saves credentials.
|
|
56
|
+
*/
|
|
57
|
+
export async function performLoginWithApiKey(apiKey, baseUrl) {
|
|
58
|
+
const url = baseUrl || DEFAULT_BASE_URL;
|
|
59
|
+
const client = new ApiClient({ apiKey, email: '', baseUrl: url });
|
|
60
|
+
// Validate the key by fetching workspaces
|
|
61
|
+
try {
|
|
62
|
+
const workspaces = await client.listWorkspaces();
|
|
63
|
+
const defaultWs = workspaces[0];
|
|
64
|
+
const creds = {
|
|
65
|
+
apiKey,
|
|
66
|
+
email: 'api-key-auth',
|
|
67
|
+
baseUrl: url,
|
|
68
|
+
defaultWorkspaceId: defaultWs?.id,
|
|
69
|
+
defaultWorkspaceName: defaultWs?.name,
|
|
70
|
+
};
|
|
71
|
+
saveCredentials(creds);
|
|
72
|
+
await configureGitCredentials(url);
|
|
73
|
+
console.log('Logged in with API key.');
|
|
74
|
+
if (defaultWs) {
|
|
75
|
+
console.log(`Default workspace: ${defaultWs.name}`);
|
|
76
|
+
}
|
|
77
|
+
return creds;
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
81
|
+
console.error(`Invalid API key: ${message}`);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Extract session ID from a login URL.
|
|
87
|
+
* Supports both query param (?session=xxx) and path segment formats.
|
|
88
|
+
*/
|
|
89
|
+
function extractSessionId(loginUrl) {
|
|
90
|
+
try {
|
|
91
|
+
const url = new URL(loginUrl);
|
|
92
|
+
const session = url.searchParams.get('session');
|
|
93
|
+
if (session)
|
|
94
|
+
return session;
|
|
95
|
+
// Try path-based: /cli-auth/SESSION_ID
|
|
96
|
+
const pathParts = url.pathname.split('/');
|
|
97
|
+
const authIdx = pathParts.indexOf('cli-auth');
|
|
98
|
+
if (authIdx >= 0 && pathParts[authIdx + 1]) {
|
|
99
|
+
return pathParts[authIdx + 1];
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// Not a valid URL
|
|
104
|
+
}
|
|
105
|
+
console.error(`Could not extract session ID from URL: ${loginUrl}`);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Extract the base URL (origin) from a login URL.
|
|
110
|
+
*/
|
|
111
|
+
function extractBaseUrl(loginUrl) {
|
|
112
|
+
try {
|
|
113
|
+
return new URL(loginUrl).origin;
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return DEFAULT_BASE_URL;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Poll for login completion and save credentials.
|
|
121
|
+
*/
|
|
122
|
+
async function pollAndSave(client, sessionId, baseUrl) {
|
|
23
123
|
const maxAttempts = 60;
|
|
24
124
|
const pollInterval = 5000;
|
|
25
125
|
for (let i = 0; i < maxAttempts; i++) {
|
|
@@ -27,7 +127,7 @@ export async function performLogin(baseUrl) {
|
|
|
27
127
|
const result = await client.pollLogin(sessionId);
|
|
28
128
|
if (result) {
|
|
29
129
|
saveCredentials(result);
|
|
30
|
-
await configureGitCredentials(result.baseUrl ||
|
|
130
|
+
await configureGitCredentials(result.baseUrl || baseUrl);
|
|
31
131
|
console.log(`Logged in as ${result.email}`);
|
|
32
132
|
return result;
|
|
33
133
|
}
|
package/dist/commands/clone.d.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { ApiClient } from '../api/client.js';
|
|
3
3
|
import type { AppInfo } from '../types.js';
|
|
4
|
-
export
|
|
4
|
+
export interface CloneResult {
|
|
5
|
+
appId: string;
|
|
6
|
+
appName: string;
|
|
7
|
+
slug: string;
|
|
8
|
+
directory: string;
|
|
9
|
+
workspaceId: string;
|
|
10
|
+
workspaceName: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function execClone(client: ApiClient, app: AppInfo, directory?: string): Promise<CloneResult>;
|
|
5
13
|
export declare const cloneCommand: Command;
|
package/dist/commands/clone.js
CHANGED
|
@@ -5,10 +5,13 @@ import { join, resolve } from 'path';
|
|
|
5
5
|
import { requireAuth } from '../auth/store.js';
|
|
6
6
|
import { ApiClient } from '../api/client.js';
|
|
7
7
|
import { promptSelect } from '../utils/prompt.js';
|
|
8
|
+
import { resolveApp } from '../utils/resolve.js';
|
|
8
9
|
import { generateManifest, saveManifest } from '../template/manifest.js';
|
|
9
10
|
import { extractZip } from '../utils/zip.js';
|
|
10
11
|
import { removeNestedGitDirs } from '../utils/fs.js';
|
|
11
12
|
import { runAgentWizard } from '../ui/banner.js';
|
|
13
|
+
import { shouldOutputJson, jsonOut } from '../utils/output.js';
|
|
14
|
+
import { buildCloneGuide, buildErrorResponse } from '../utils/agent-guidance.js';
|
|
12
15
|
export async function execClone(client, app, directory) {
|
|
13
16
|
const slug = app.slug || app.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
14
17
|
const dir = directory || slug;
|
|
@@ -39,7 +42,7 @@ export async function execClone(client, app, directory) {
|
|
|
39
42
|
execFileSync('git', ['remote', 'add', 'runwork', remoteUrl], { cwd: dir, stdio: 'pipe' });
|
|
40
43
|
}
|
|
41
44
|
try {
|
|
42
|
-
execFileSync('git', ['fetch', 'runwork', 'main'], { cwd: dir, stdio: '
|
|
45
|
+
execFileSync('git', ['fetch', 'runwork', 'main'], { cwd: dir, stdio: 'pipe' });
|
|
43
46
|
// Reset to remote HEAD - overlays git-tracked files on top of template
|
|
44
47
|
execFileSync('git', ['reset', '--mixed', 'runwork/main'], { cwd: dir, stdio: 'pipe' });
|
|
45
48
|
// Checkout git-tracked files (overwrites template files where they differ)
|
|
@@ -66,33 +69,58 @@ export async function execClone(client, app, directory) {
|
|
|
66
69
|
catch {
|
|
67
70
|
// App may be new with no registries yet
|
|
68
71
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
+
return {
|
|
73
|
+
appId: app.id,
|
|
74
|
+
appName: app.name,
|
|
75
|
+
slug,
|
|
76
|
+
directory: resolve(dir),
|
|
77
|
+
workspaceId: app.workspaceId,
|
|
78
|
+
workspaceName: app.workspaceName,
|
|
79
|
+
};
|
|
72
80
|
}
|
|
73
81
|
export const cloneCommand = new Command('clone')
|
|
74
82
|
.description('Clone a Runwork app to local development')
|
|
75
83
|
.argument('[appId]', 'App ID to clone (interactive if omitted)')
|
|
76
84
|
.argument('[directory]', 'Target directory')
|
|
77
|
-
.
|
|
85
|
+
.option('--app <name-or-id>', 'App name or ID (skips interactive selection)')
|
|
86
|
+
.action(async (appId, directory, options) => {
|
|
78
87
|
const creds = requireAuth();
|
|
79
88
|
const client = new ApiClient(creds);
|
|
89
|
+
const useJson = shouldOutputJson(undefined);
|
|
80
90
|
let app;
|
|
81
|
-
|
|
82
|
-
|
|
91
|
+
const appRef = options?.app || appId;
|
|
92
|
+
if (appRef) {
|
|
93
|
+
app = await resolveApp(client, appRef);
|
|
83
94
|
}
|
|
84
95
|
else {
|
|
85
96
|
const apps = await client.listApps();
|
|
86
97
|
if (apps.length === 0) {
|
|
98
|
+
if (useJson) {
|
|
99
|
+
jsonOut(buildErrorResponse('clone', 'No apps found', 'No apps are available to clone.', ['Create an app first with: runwork init my-app']));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
87
102
|
console.error('No apps found.');
|
|
88
103
|
process.exit(1);
|
|
89
104
|
}
|
|
90
105
|
const choice = await promptSelect('Select app to clone:', apps.map(a => ({ label: `${a.name} (${a.workspaceName})`, value: a })));
|
|
91
106
|
app = choice.value;
|
|
92
107
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
await
|
|
97
|
-
|
|
108
|
+
if (!useJson) {
|
|
109
|
+
console.log(`Cloning "${app.name}"...`);
|
|
110
|
+
}
|
|
111
|
+
const cloneResult = await execClone(client, app, directory);
|
|
112
|
+
if (useJson) {
|
|
113
|
+
const response = {
|
|
114
|
+
success: true,
|
|
115
|
+
command: 'clone',
|
|
116
|
+
result: cloneResult,
|
|
117
|
+
guide: buildCloneGuide(cloneResult.appName, cloneResult.slug),
|
|
118
|
+
};
|
|
119
|
+
jsonOut(response);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
console.log(`\nApp "${cloneResult.appName}" cloned to ${cloneResult.directory}/`);
|
|
123
|
+
console.log(`Remote: ${client.getGitRemoteUrl(cloneResult.workspaceId, cloneResult.appId)}`);
|
|
124
|
+
await runAgentWizard(cloneResult.directory);
|
|
125
|
+
console.log(`Next: cd ${cloneResult.slug} && runwork dev`);
|
|
98
126
|
});
|
package/dist/commands/deploy.js
CHANGED
|
@@ -4,13 +4,14 @@ import { readFileSync, existsSync } from 'fs';
|
|
|
4
4
|
import { requireAuth } from '../auth/store.js';
|
|
5
5
|
import { ApiClient } from '../api/client.js';
|
|
6
6
|
import { shouldOutputJson, jsonOut } from '../utils/output.js';
|
|
7
|
+
import { buildDeployGuide, buildErrorResponse } from '../utils/agent-guidance.js';
|
|
7
8
|
export const deployCommand = new Command('deploy')
|
|
8
9
|
.description('Deploy the current app to production')
|
|
9
10
|
.action(async (_opts, command) => {
|
|
10
11
|
const useJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
11
12
|
if (!existsSync('.runwork.json')) {
|
|
12
13
|
if (useJson) {
|
|
13
|
-
|
|
14
|
+
jsonOut(buildErrorResponse('deploy', 'No .runwork.json found', 'This directory is not a Runwork app.', ['Run runwork init to create an app first', 'Or cd into an existing app directory']));
|
|
14
15
|
process.exit(1);
|
|
15
16
|
}
|
|
16
17
|
console.error('No .runwork.json found. Run `runwork init` first.');
|
|
@@ -42,7 +43,7 @@ export const deployCommand = new Command('deploy')
|
|
|
42
43
|
}
|
|
43
44
|
else {
|
|
44
45
|
if (useJson) {
|
|
45
|
-
|
|
46
|
+
jsonOut(buildErrorResponse('deploy', 'Sync failed before deployment', 'Files could not be synced to the remote.', ['Run runwork dev first to ensure files sync correctly', 'Check your network connection', 'Retry runwork deploy']));
|
|
46
47
|
process.exit(1);
|
|
47
48
|
}
|
|
48
49
|
console.error('No commits to deploy. Commit your changes first.');
|
|
@@ -51,9 +52,31 @@ export const deployCommand = new Command('deploy')
|
|
|
51
52
|
// Trigger deploy
|
|
52
53
|
if (!useJson)
|
|
53
54
|
console.log('Deploying...');
|
|
54
|
-
|
|
55
|
+
let result;
|
|
56
|
+
try {
|
|
57
|
+
result = await client.triggerDeploy(config.appId);
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
61
|
+
if (useJson) {
|
|
62
|
+
jsonOut(buildErrorResponse('deploy', message, 'The deployment API returned an error.', ['Check for TypeScript errors in your code', 'Run runwork dev to test in the sandbox first', 'Retry runwork deploy']));
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
console.error(`Deploy failed: ${message}`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
55
68
|
if (useJson) {
|
|
56
|
-
|
|
69
|
+
const response = {
|
|
70
|
+
success: true,
|
|
71
|
+
command: 'deploy',
|
|
72
|
+
result: {
|
|
73
|
+
deployed: true,
|
|
74
|
+
url: result.deploymentUrl,
|
|
75
|
+
appName: config.appName,
|
|
76
|
+
},
|
|
77
|
+
guide: buildDeployGuide(),
|
|
78
|
+
};
|
|
79
|
+
jsonOut(response);
|
|
57
80
|
return;
|
|
58
81
|
}
|
|
59
82
|
console.log(`Deployed: ${result.deploymentUrl}`);
|
package/dist/commands/dev.js
CHANGED
|
@@ -16,6 +16,7 @@ import { createStatusLine } from '../ui/status-line.js';
|
|
|
16
16
|
import { createKeyboardListener } from '../ui/keyboard.js';
|
|
17
17
|
import { bold, dim, green, yellow, cyan } from '../ui/colors.js';
|
|
18
18
|
import { shouldOutputJson, jsonLine } from '../utils/output.js';
|
|
19
|
+
import { buildDevSessionGuide } from '../utils/agent-guidance.js';
|
|
19
20
|
async function populateSkill(projectDir, client, appId) {
|
|
20
21
|
try {
|
|
21
22
|
const { skill } = await client.getAppSkill(appId);
|
|
@@ -110,6 +111,39 @@ export async function execDev(options) {
|
|
|
110
111
|
const syncResult = syncWithRemote(cwd);
|
|
111
112
|
if (useJson) {
|
|
112
113
|
jsonLine({ event: 'startup', phase: 'sync', status: syncResult.status, pushed: syncResult.pushed, timestamp: ts() });
|
|
114
|
+
if (syncResult.status === 'sync-failed') {
|
|
115
|
+
if (syncResult.error === 'stash-conflict') {
|
|
116
|
+
jsonLine({
|
|
117
|
+
event: 'error',
|
|
118
|
+
phase: 'sync',
|
|
119
|
+
timestamp: ts(),
|
|
120
|
+
error: {
|
|
121
|
+
message: 'Merge conflicts detected after stash pop',
|
|
122
|
+
diagnosis: 'Local changes could not be automatically merged with the remote state. The stash pop resulted in conflicts that require manual resolution.',
|
|
123
|
+
suggestions: [
|
|
124
|
+
'Resolve the merge conflicts in the affected files',
|
|
125
|
+
'Run git add on resolved files, then git commit',
|
|
126
|
+
'Run runwork dev again after resolving conflicts',
|
|
127
|
+
],
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
jsonLine({
|
|
133
|
+
event: 'error',
|
|
134
|
+
phase: 'sync',
|
|
135
|
+
timestamp: ts(),
|
|
136
|
+
error: {
|
|
137
|
+
message: syncResult.error ?? 'File sync failed',
|
|
138
|
+
diagnosis: 'Could not sync files to the sandbox. This usually means the sandbox session expired or the network connection was interrupted.',
|
|
139
|
+
suggestions: [
|
|
140
|
+
'Run runwork dev again to start a fresh session',
|
|
141
|
+
'Check your network connection',
|
|
142
|
+
'Run runwork doctor to diagnose environment issues',
|
|
143
|
+
],
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
}
|
|
113
147
|
}
|
|
114
148
|
else {
|
|
115
149
|
switch (syncResult.status) {
|
|
@@ -139,7 +173,7 @@ export async function execDev(options) {
|
|
|
139
173
|
}
|
|
140
174
|
// Emit session_started (JSON) or show banner (human)
|
|
141
175
|
if (useJson) {
|
|
142
|
-
jsonLine({ event: 'session_started', previewUrl: session.previewUrl, appName: config.appName, timestamp: ts() });
|
|
176
|
+
jsonLine({ event: 'session_started', previewUrl: session.previewUrl, appName: config.appName, timestamp: ts(), guide: buildDevSessionGuide() });
|
|
143
177
|
}
|
|
144
178
|
else {
|
|
145
179
|
console.log(getDevBanner({ appName: config.appName, previewUrl: session.previewUrl, workspaceName: config.workspaceName }));
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { shouldOutputJson, jsonOut } from '../utils/output.js';
|
|
3
|
+
import { bold, dim, green, yellow, cyan } from '../ui/colors.js';
|
|
4
|
+
import { runAllChecks } from '../health/runner.js';
|
|
5
|
+
const STATUS_LABELS = {
|
|
6
|
+
pass: green('[pass]'),
|
|
7
|
+
warn: yellow('[warn]'),
|
|
8
|
+
fail: bold('[FAIL]'),
|
|
9
|
+
skip: dim('[skip]'),
|
|
10
|
+
};
|
|
11
|
+
const CHECK_LABELS = {
|
|
12
|
+
'system-deps': 'System dependencies',
|
|
13
|
+
'cli-version': 'CLI version',
|
|
14
|
+
'auth': 'Authentication',
|
|
15
|
+
'network': 'Network',
|
|
16
|
+
'project-config': 'Project config',
|
|
17
|
+
'app-exists': 'App exists',
|
|
18
|
+
'git-remote': 'Git remote',
|
|
19
|
+
'agent-setup': 'Agent setup',
|
|
20
|
+
};
|
|
21
|
+
function printHumanReport(report) {
|
|
22
|
+
console.log('');
|
|
23
|
+
console.log(bold('runwork doctor'));
|
|
24
|
+
console.log('');
|
|
25
|
+
for (const check of report.checks) {
|
|
26
|
+
const status = STATUS_LABELS[check.status] || check.status;
|
|
27
|
+
const label = (CHECK_LABELS[check.name] || check.name).padEnd(22);
|
|
28
|
+
console.log(` ${status} ${label} ${dim(check.message)}`);
|
|
29
|
+
if (check.details && check.details.length > 1) {
|
|
30
|
+
for (const detail of check.details) {
|
|
31
|
+
console.log(` ${dim('- ' + detail)}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// Summary
|
|
36
|
+
const failures = report.checks.filter(c => c.status === 'fail');
|
|
37
|
+
const warnings = report.checks.filter(c => c.status === 'warn');
|
|
38
|
+
console.log('');
|
|
39
|
+
if (failures.length > 0) {
|
|
40
|
+
console.log(` ${failures.length} issue${failures.length !== 1 ? 's' : ''} found:`);
|
|
41
|
+
for (const f of failures) {
|
|
42
|
+
const label = CHECK_LABELS[f.name] || f.name;
|
|
43
|
+
if (f.fix) {
|
|
44
|
+
console.log(` - ${label}: ${cyan(f.fix)}`);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
console.log(` - ${label}: ${f.message}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (warnings.length > 0 && failures.length === 0) {
|
|
52
|
+
console.log(` ${warnings.length} warning${warnings.length !== 1 ? 's' : ''}:`);
|
|
53
|
+
for (const w of warnings) {
|
|
54
|
+
const label = CHECK_LABELS[w.name] || w.name;
|
|
55
|
+
if (w.fix) {
|
|
56
|
+
console.log(` - ${label}: ${cyan(w.fix)}`);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
console.log(` - ${label}: ${w.message}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (failures.length === 0 && warnings.length === 0) {
|
|
64
|
+
console.log(` ${green('All checks passed.')}`);
|
|
65
|
+
}
|
|
66
|
+
console.log('');
|
|
67
|
+
}
|
|
68
|
+
export const doctorCommand = new Command('doctor')
|
|
69
|
+
.description('Check system health: auth, network, project config, agent setup')
|
|
70
|
+
.action(async (_opts, command) => {
|
|
71
|
+
const asJson = shouldOutputJson(command.optsWithGlobals().json);
|
|
72
|
+
const report = await runAllChecks();
|
|
73
|
+
if (asJson) {
|
|
74
|
+
jsonOut(report);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
printHumanReport(report);
|
|
78
|
+
}
|
|
79
|
+
if (report.checks.some(c => c.status === 'fail')) {
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
});
|
package/dist/commands/info.js
CHANGED
|
@@ -2,6 +2,7 @@ import { Command } from 'commander';
|
|
|
2
2
|
import { readFileSync, existsSync } from 'fs';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import { shouldOutputJson, jsonOut } from '../utils/output.js';
|
|
5
|
+
import { buildInfoGuide } from '../utils/agent-guidance.js';
|
|
5
6
|
import { fetchAppInfo } from '../utils/app-info.js';
|
|
6
7
|
import { bold, cyan, dim, green, yellow, gray } from '../ui/colors.js';
|
|
7
8
|
import { VERSION } from '../generated/version.js';
|
|
@@ -186,7 +187,10 @@ export function formatSources(sources) {
|
|
|
186
187
|
function printHumanOutput(data) {
|
|
187
188
|
console.log('');
|
|
188
189
|
const pad = (label) => label.padEnd(12);
|
|
189
|
-
|
|
190
|
+
const wsLabel = data.workspace.name && data.workspace.name !== data.workspace.id
|
|
191
|
+
? `${data.workspace.name} ${dim('(' + data.workspace.id + ')')}`
|
|
192
|
+
: data.workspace.id;
|
|
193
|
+
console.log(` ${dim(pad('Workspace:'))}${wsLabel}`);
|
|
190
194
|
if (data.app) {
|
|
191
195
|
console.log('');
|
|
192
196
|
console.log(bold('App Info'));
|
|
@@ -368,7 +372,7 @@ export const infoCommand = new Command('info')
|
|
|
368
372
|
},
|
|
369
373
|
};
|
|
370
374
|
if (asJson) {
|
|
371
|
-
jsonOut(output);
|
|
375
|
+
jsonOut({ ...output, guide: buildInfoGuide() });
|
|
372
376
|
}
|
|
373
377
|
else {
|
|
374
378
|
printHumanOutput(output);
|
package/dist/commands/init.d.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { ApiClient } from '../api/client.js';
|
|
3
3
|
import type { WorkspaceInfo } from '../types.js';
|
|
4
|
-
export
|
|
4
|
+
export interface InitResult {
|
|
5
|
+
appId: string;
|
|
6
|
+
appName: string;
|
|
7
|
+
slug: string;
|
|
8
|
+
directory: string;
|
|
9
|
+
workspaceId: string;
|
|
10
|
+
workspaceName: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function execInit(client: ApiClient, appName: string, workspace: WorkspaceInfo): Promise<InitResult>;
|
|
5
13
|
/** Full create flow: prompt for name/workspace, init, and run agent wizard */
|
|
6
|
-
export declare function runCreateFlow(name?: string): Promise<void>;
|
|
14
|
+
export declare function runCreateFlow(name?: string, workspaceFlag?: string): Promise<void>;
|
|
7
15
|
export declare const initCommand: Command;
|
package/dist/commands/init.js
CHANGED
|
@@ -5,10 +5,13 @@ import { join, resolve } from 'path';
|
|
|
5
5
|
import { requireAuth } from '../auth/store.js';
|
|
6
6
|
import { ApiClient } from '../api/client.js';
|
|
7
7
|
import { promptSelect, promptInput } from '../utils/prompt.js';
|
|
8
|
+
import { resolveWorkspace } from '../utils/resolve.js';
|
|
8
9
|
import { generateManifest, saveManifest } from '../template/manifest.js';
|
|
9
10
|
import { extractZip } from '../utils/zip.js';
|
|
10
11
|
import { removeNestedGitDirs } from '../utils/fs.js';
|
|
11
12
|
import { runAgentWizard } from '../ui/banner.js';
|
|
13
|
+
import { shouldOutputJson, jsonOut } from '../utils/output.js';
|
|
14
|
+
import { buildInitGuide, buildErrorResponse } from '../utils/agent-guidance.js';
|
|
12
15
|
export async function execInit(client, appName, workspace) {
|
|
13
16
|
const app = await client.initApp(workspace.id, appName);
|
|
14
17
|
const slug = app.slug || appName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
@@ -42,7 +45,7 @@ export async function execInit(client, appName, workspace) {
|
|
|
42
45
|
writeFileSync(join(dir, '.runwork.json'), JSON.stringify(config, null, 2));
|
|
43
46
|
// Initialize git
|
|
44
47
|
if (!existsSync(join(dir, '.git'))) {
|
|
45
|
-
execFileSync('git', ['init'], { cwd: dir, stdio: '
|
|
48
|
+
execFileSync('git', ['init'], { cwd: dir, stdio: 'pipe' });
|
|
46
49
|
}
|
|
47
50
|
// Add runwork remote
|
|
48
51
|
const remoteUrl = client.getGitRemoteUrl(workspace.id, app.id);
|
|
@@ -64,34 +67,74 @@ export async function execInit(client, appName, workspace) {
|
|
|
64
67
|
catch {
|
|
65
68
|
// Nothing to commit is fine
|
|
66
69
|
}
|
|
67
|
-
execFileSync('git', ['push', '-u', 'runwork', 'main'], { cwd: dir, stdio: '
|
|
70
|
+
execFileSync('git', ['push', '-u', 'runwork', 'main'], { cwd: dir, stdio: 'pipe' });
|
|
68
71
|
console.log(`\nApp "${app.name}" initialized in ${dir}/`);
|
|
69
|
-
return
|
|
72
|
+
return {
|
|
73
|
+
appId: app.id,
|
|
74
|
+
appName: app.name,
|
|
75
|
+
slug,
|
|
76
|
+
directory: resolve(dir),
|
|
77
|
+
workspaceId: workspace.id,
|
|
78
|
+
workspaceName: workspace.name,
|
|
79
|
+
};
|
|
70
80
|
}
|
|
71
81
|
/** Full create flow: prompt for name/workspace, init, and run agent wizard */
|
|
72
|
-
export async function runCreateFlow(name) {
|
|
82
|
+
export async function runCreateFlow(name, workspaceFlag) {
|
|
83
|
+
const useJson = shouldOutputJson(undefined);
|
|
73
84
|
const creds = requireAuth();
|
|
74
85
|
const client = new ApiClient(creds);
|
|
75
86
|
const appName = name || await promptInput('App name');
|
|
76
87
|
if (!appName) {
|
|
88
|
+
if (useJson) {
|
|
89
|
+
jsonOut(buildErrorResponse('init', 'App name is required.', 'No app name was provided.', ['Provide an app name as an argument: runwork init <name>']));
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
77
92
|
console.error('App name is required.');
|
|
78
93
|
process.exit(1);
|
|
79
94
|
}
|
|
80
|
-
|
|
81
|
-
if (
|
|
82
|
-
|
|
83
|
-
|
|
95
|
+
let workspace;
|
|
96
|
+
if (workspaceFlag) {
|
|
97
|
+
workspace = await resolveWorkspace(client, workspaceFlag);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
const workspaces = await client.listWorkspaces();
|
|
101
|
+
if (workspaces.length === 0) {
|
|
102
|
+
if (useJson) {
|
|
103
|
+
jsonOut(buildErrorResponse('init', 'No workspaces found.', 'The authenticated account has no workspaces.', ['Create a workspace at runwork.ai first.']));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
console.error('No workspaces found. Create one at runwork.ai first.');
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
if (workspaces.length === 1) {
|
|
110
|
+
workspace = workspaces[0];
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
const workspaceChoice = await promptSelect('Select workspace:', workspaces.map(w => ({ label: w.name, value: w })));
|
|
114
|
+
workspace = workspaceChoice.value;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (!useJson) {
|
|
118
|
+
console.log(`Creating "${appName}" in workspace "${workspace.name}"...`);
|
|
119
|
+
}
|
|
120
|
+
const initResult = await execInit(client, appName, workspace);
|
|
121
|
+
if (useJson) {
|
|
122
|
+
const response = {
|
|
123
|
+
success: true,
|
|
124
|
+
command: 'init',
|
|
125
|
+
result: initResult,
|
|
126
|
+
guide: buildInitGuide(initResult.appName, initResult.slug),
|
|
127
|
+
};
|
|
128
|
+
jsonOut(response);
|
|
129
|
+
return;
|
|
84
130
|
}
|
|
85
|
-
|
|
86
|
-
const workspace = workspaceChoice.value;
|
|
87
|
-
console.log(`Creating "${appName}" in workspace "${workspace.name}"...`);
|
|
88
|
-
const dir = await execInit(client, appName, workspace);
|
|
89
|
-
await runAgentWizard(dir);
|
|
131
|
+
await runAgentWizard(initResult.directory);
|
|
90
132
|
}
|
|
91
133
|
export const initCommand = new Command('init')
|
|
92
134
|
.alias('create')
|
|
93
135
|
.description('Create a new Runwork app')
|
|
94
136
|
.argument('[name]', 'App name')
|
|
95
|
-
.
|
|
96
|
-
|
|
137
|
+
.option('--workspace <name-or-id>', 'Workspace name or ID (skips interactive selection)')
|
|
138
|
+
.action(async (name, options) => {
|
|
139
|
+
await runCreateFlow(name, options?.workspace);
|
|
97
140
|
});
|