runwork 0.5.1 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/dist/agents/__tests__/claude-code-stats.test.d.ts +1 -0
  2. package/dist/agents/__tests__/claude-code-stats.test.js +153 -0
  3. package/dist/agents/__tests__/claude-desktop-stats.test.d.ts +1 -0
  4. package/dist/agents/__tests__/claude-desktop-stats.test.js +280 -0
  5. package/dist/agents/__tests__/codex-stats.test.d.ts +1 -0
  6. package/dist/agents/__tests__/codex-stats.test.js +262 -0
  7. package/dist/agents/__tests__/cursor-stats.test.d.ts +1 -0
  8. package/dist/agents/__tests__/cursor-stats.test.js +183 -0
  9. package/dist/agents/__tests__/graceful-degradation.test.d.ts +1 -0
  10. package/dist/agents/__tests__/graceful-degradation.test.js +123 -0
  11. package/dist/agents/__tests__/instruction-hint.test.d.ts +1 -0
  12. package/dist/agents/__tests__/instruction-hint.test.js +69 -0
  13. package/dist/agents/__tests__/intro-skill.test.d.ts +1 -0
  14. package/dist/agents/__tests__/intro-skill.test.js +148 -0
  15. package/dist/agents/__tests__/schema-validation.test.d.ts +1 -0
  16. package/dist/agents/__tests__/schema-validation.test.js +212 -0
  17. package/dist/agents/claude-code.d.ts +16 -1
  18. package/dist/agents/claude-code.js +344 -21
  19. package/dist/agents/claude-desktop.d.ts +10 -1
  20. package/dist/agents/claude-desktop.js +250 -8
  21. package/dist/agents/cline.d.ts +13 -0
  22. package/dist/agents/cline.js +88 -0
  23. package/dist/agents/codex.d.ts +7 -2
  24. package/dist/agents/codex.js +130 -18
  25. package/dist/agents/cursor.d.ts +6 -1
  26. package/dist/agents/cursor.js +164 -7
  27. package/dist/agents/detect.js +30 -7
  28. package/dist/agents/gemini.d.ts +6 -3
  29. package/dist/agents/gemini.js +57 -20
  30. package/dist/agents/generic-adapter.d.ts +23 -0
  31. package/dist/agents/generic-adapter.js +106 -0
  32. package/dist/agents/intro-skill.d.ts +36 -0
  33. package/dist/agents/intro-skill.js +358 -0
  34. package/dist/agents/registry.d.ts +53 -0
  35. package/dist/agents/registry.js +219 -0
  36. package/dist/agents/types.d.ts +44 -1
  37. package/dist/agents/types.js +11 -4
  38. package/dist/agents/utils/instruction-hint.d.ts +11 -0
  39. package/dist/agents/utils/instruction-hint.js +62 -0
  40. package/dist/agents/utils/json-config.js +3 -3
  41. package/dist/agents/windsurf.d.ts +2 -0
  42. package/dist/agents/windsurf.js +26 -9
  43. package/dist/api/client.d.ts +85 -2
  44. package/dist/api/client.js +40 -10
  45. package/dist/auth/__tests__/login-flow.test.d.ts +1 -0
  46. package/dist/auth/__tests__/login-flow.test.js +125 -0
  47. package/dist/auth/login-flow.d.ts +16 -0
  48. package/dist/auth/login-flow.js +101 -1
  49. package/dist/commands/clone.d.ts +9 -1
  50. package/dist/commands/clone.js +40 -12
  51. package/dist/commands/deploy.js +27 -4
  52. package/dist/commands/dev.js +35 -1
  53. package/dist/commands/doctor.d.ts +2 -0
  54. package/dist/commands/doctor.js +82 -0
  55. package/dist/commands/info.js +6 -2
  56. package/dist/commands/init.d.ts +10 -2
  57. package/dist/commands/init.js +58 -15
  58. package/dist/commands/integrations.js +47 -10
  59. package/dist/commands/login.js +31 -4
  60. package/dist/commands/mcp.js +143 -15
  61. package/dist/commands/setup.js +47 -122
  62. package/dist/commands/skills.js +230 -12
  63. package/dist/commands/sync.d.ts +8 -0
  64. package/dist/commands/sync.js +398 -65
  65. package/dist/commands/welcome.js +6 -3
  66. package/dist/generated/version.d.ts +1 -1
  67. package/dist/generated/version.js +1 -1
  68. package/dist/health/__tests__/checks.test.d.ts +1 -0
  69. package/dist/health/__tests__/checks.test.js +223 -0
  70. package/dist/health/checks.d.ts +20 -0
  71. package/dist/health/checks.js +392 -0
  72. package/dist/health/runner.d.ts +2 -0
  73. package/dist/health/runner.js +26 -0
  74. package/dist/health/types.d.ts +12 -0
  75. package/dist/health/types.js +1 -0
  76. package/dist/index.js +32 -0
  77. package/dist/sync/__tests__/change-detect.test.d.ts +1 -0
  78. package/dist/sync/__tests__/change-detect.test.js +123 -0
  79. package/dist/sync/__tests__/hash.test.d.ts +1 -0
  80. package/dist/sync/__tests__/hash.test.js +18 -0
  81. package/dist/sync/change-detect.d.ts +19 -0
  82. package/dist/sync/change-detect.js +136 -0
  83. package/dist/sync/conflict-ui.d.ts +5 -0
  84. package/dist/sync/conflict-ui.js +78 -0
  85. package/dist/sync/executor.d.ts +21 -0
  86. package/dist/sync/executor.js +98 -0
  87. package/dist/sync/hash.d.ts +1 -0
  88. package/dist/sync/hash.js +4 -0
  89. package/dist/sync/types.d.ts +24 -0
  90. package/dist/sync/types.js +1 -0
  91. package/dist/types.d.ts +12 -0
  92. package/dist/ui/__tests__/banner.test.js +17 -1
  93. package/dist/ui/banner.d.ts +1 -1
  94. package/dist/ui/banner.js +27 -2
  95. package/dist/utils/__tests__/resolve.test.d.ts +1 -0
  96. package/dist/utils/__tests__/resolve.test.js +95 -0
  97. package/dist/utils/__tests__/sqlite.test.d.ts +1 -0
  98. package/dist/utils/__tests__/sqlite.test.js +77 -0
  99. package/dist/utils/agent-guidance.d.ts +32 -0
  100. package/dist/utils/agent-guidance.js +106 -0
  101. package/dist/utils/curl-parser.d.ts +1 -1
  102. package/dist/utils/curl-parser.js +144 -26
  103. package/dist/utils/fs.js +2 -2
  104. package/dist/utils/help-json.d.ts +56 -0
  105. package/dist/utils/help-json.js +85 -0
  106. package/dist/utils/resolve.d.ts +12 -0
  107. package/dist/utils/resolve.js +52 -0
  108. package/dist/utils/setup-state.d.ts +3 -0
  109. package/dist/utils/setup-state.js +19 -0
  110. package/dist/utils/sqlite.d.ts +7 -0
  111. package/dist/utils/sqlite.js +24 -0
  112. package/dist/utils/which.d.ts +6 -0
  113. package/dist/utils/which.js +18 -0
  114. package/package.json +7 -4
  115. package/dist/agents/copilot-cli.d.ts +0 -10
  116. package/dist/agents/copilot-cli.js +0 -46
  117. package/dist/agents/copilot-vscode.d.ts +0 -10
  118. package/dist/agents/copilot-vscode.js +0 -37
@@ -0,0 +1,85 @@
1
+ /**
2
+ * JSON help output for AI agents.
3
+ *
4
+ * When --json is active (explicitly or via non-TTY), `runwork --help` and
5
+ * `runwork <command> --help` output a machine-readable command tree instead
6
+ * of Commander's human-readable text.
7
+ *
8
+ * This lets an agent discover the full CLI surface in a single call.
9
+ */
10
+ function serializeOption(opt) {
11
+ const result = {
12
+ flags: opt.flags,
13
+ description: opt.description,
14
+ required: opt.required,
15
+ optional: opt.optional,
16
+ variadic: opt.variadic,
17
+ };
18
+ if (opt.defaultValue !== undefined)
19
+ result.defaultValue = opt.defaultValue;
20
+ if (opt.argChoices)
21
+ result.choices = opt.argChoices;
22
+ if (opt.envVar)
23
+ result.envVar = opt.envVar;
24
+ return result;
25
+ }
26
+ function serializeArgument(arg) {
27
+ const result = {
28
+ name: arg.name(),
29
+ description: arg.description,
30
+ required: arg.required,
31
+ variadic: arg.variadic,
32
+ };
33
+ if (arg.defaultValue !== undefined)
34
+ result.defaultValue = arg.defaultValue;
35
+ if (arg.argChoices)
36
+ result.choices = arg.argChoices;
37
+ return result;
38
+ }
39
+ function serializeCommand(cmd) {
40
+ const subcommands = cmd.commands
41
+ .filter((c) => !c._hidden)
42
+ .map(serializeCommand);
43
+ // Get command-level options (exclude inherited global options)
44
+ const options = cmd.options
45
+ .filter((o) => !o.hidden)
46
+ .map(serializeOption);
47
+ const registeredArguments = cmd.registeredArguments ?? [];
48
+ return {
49
+ name: cmd.name(),
50
+ description: cmd.description(),
51
+ aliases: cmd.aliases(),
52
+ usage: cmd.usage(),
53
+ options,
54
+ arguments: registeredArguments.map(serializeArgument),
55
+ subcommands,
56
+ };
57
+ }
58
+ /**
59
+ * Serialize the full command tree to a JSON-friendly structure.
60
+ */
61
+ export function buildHelpJson(program) {
62
+ const globalOptions = program.options
63
+ .filter((o) => !o.hidden)
64
+ .map(serializeOption);
65
+ const commands = program.commands
66
+ .filter((c) => !c._hidden)
67
+ .map(serializeCommand);
68
+ return {
69
+ version: program.version() ?? 'unknown',
70
+ name: program.name(),
71
+ description: program.description(),
72
+ usage: program.usage(),
73
+ globalOptions,
74
+ commands,
75
+ };
76
+ }
77
+ /**
78
+ * Serialize a single command (for `runwork <cmd> --help --json`).
79
+ */
80
+ export function buildCommandHelpJson(cmd, version) {
81
+ return {
82
+ version,
83
+ ...serializeCommand(cmd),
84
+ };
85
+ }
@@ -0,0 +1,12 @@
1
+ import type { ApiClient } from '../api/client.js';
2
+ import type { AppInfo, WorkspaceInfo } from '../types.js';
3
+ /**
4
+ * Resolve a workspace by name or ID from the user's accessible workspaces.
5
+ * Tries exact ID match first, then case-insensitive name match.
6
+ */
7
+ export declare function resolveWorkspace(client: ApiClient, nameOrId: string): Promise<WorkspaceInfo>;
8
+ /**
9
+ * Resolve an app by name or ID from the user's accessible apps.
10
+ * Tries exact ID match first, then case-insensitive name match.
11
+ */
12
+ export declare function resolveApp(client: ApiClient, nameOrId: string): Promise<AppInfo>;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Resolve a workspace by name or ID from the user's accessible workspaces.
3
+ * Tries exact ID match first, then case-insensitive name match.
4
+ */
5
+ export async function resolveWorkspace(client, nameOrId) {
6
+ const workspaces = await client.listWorkspaces();
7
+ if (workspaces.length === 0) {
8
+ console.error('No workspaces found. Create one at runwork.ai first.');
9
+ process.exit(1);
10
+ }
11
+ // Exact ID match
12
+ const byId = workspaces.find(w => w.id === nameOrId);
13
+ if (byId)
14
+ return byId;
15
+ // Case-insensitive name match
16
+ const lower = nameOrId.toLowerCase();
17
+ const byName = workspaces.filter(w => w.name.toLowerCase() === lower);
18
+ if (byName.length === 1)
19
+ return byName[0];
20
+ if (byName.length > 1) {
21
+ console.error(`Ambiguous workspace name "${nameOrId}". Matches: ${byName.map(w => `${w.name} (${w.id})`).join(', ')}`);
22
+ process.exit(1);
23
+ }
24
+ console.error(`Workspace "${nameOrId}" not found. Available: ${workspaces.map(w => w.name).join(', ')}`);
25
+ process.exit(1);
26
+ }
27
+ /**
28
+ * Resolve an app by name or ID from the user's accessible apps.
29
+ * Tries exact ID match first, then case-insensitive name match.
30
+ */
31
+ export async function resolveApp(client, nameOrId) {
32
+ const apps = await client.listApps();
33
+ if (apps.length === 0) {
34
+ console.error('No apps found.');
35
+ process.exit(1);
36
+ }
37
+ // Exact ID match
38
+ const byId = apps.find(a => a.id === nameOrId);
39
+ if (byId)
40
+ return byId;
41
+ // Case-insensitive name match
42
+ const lower = nameOrId.toLowerCase();
43
+ const byName = apps.filter(a => a.name.toLowerCase() === lower);
44
+ if (byName.length === 1)
45
+ return byName[0];
46
+ if (byName.length > 1) {
47
+ console.error(`Ambiguous app name "${nameOrId}". Matches: ${byName.map(a => `${a.name} (${a.id})`).join(', ')}`);
48
+ process.exit(1);
49
+ }
50
+ console.error(`App "${nameOrId}" not found. Available: ${apps.map(a => a.name).join(', ')}`);
51
+ process.exit(1);
52
+ }
@@ -0,0 +1,3 @@
1
+ import type { SetupState } from '../types.js';
2
+ /** Load setup state, checking project-level then user-level. */
3
+ export declare function loadSetupState(): SetupState | null;
@@ -0,0 +1,19 @@
1
+ import { existsSync, readFileSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { homedir } from 'os';
4
+ /** Load setup state, checking project-level then user-level. */
5
+ export function loadSetupState() {
6
+ const projectPath = join(process.cwd(), '.runwork', 'setup.json');
7
+ const userPath = join(homedir(), '.runwork', 'setup.json');
8
+ for (const p of [projectPath, userPath]) {
9
+ if (existsSync(p)) {
10
+ try {
11
+ return JSON.parse(readFileSync(p, 'utf-8'));
12
+ }
13
+ catch {
14
+ continue;
15
+ }
16
+ }
17
+ }
18
+ return null;
19
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Query a SQLite database using better-sqlite3.
3
+ * Cross-platform, no CLI dependency.
4
+ * Opens database in readonly mode with a 5-second busy timeout.
5
+ * Returns empty string on any error (missing DB, locked, corrupted).
6
+ */
7
+ export declare function querySqlite(dbPath: string, sql: string): string;
@@ -0,0 +1,24 @@
1
+ import Database from 'better-sqlite3';
2
+ /**
3
+ * Query a SQLite database using better-sqlite3.
4
+ * Cross-platform, no CLI dependency.
5
+ * Opens database in readonly mode with a 5-second busy timeout.
6
+ * Returns empty string on any error (missing DB, locked, corrupted).
7
+ */
8
+ export function querySqlite(dbPath, sql) {
9
+ let db = null;
10
+ try {
11
+ db = new Database(dbPath, { readonly: true, timeout: 5000 });
12
+ const rows = db.prepare(sql).all();
13
+ return rows.map(row => Object.values(row).join('|')).join('\n');
14
+ }
15
+ catch {
16
+ return '';
17
+ }
18
+ finally {
19
+ try {
20
+ db?.close();
21
+ }
22
+ catch { /* ignore close errors */ }
23
+ }
24
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Cross-platform binary detection.
3
+ * Uses `which` on macOS/Linux, `where.exe` on Windows.
4
+ * Returns the resolved path on success, null if not found.
5
+ */
6
+ export declare function whichBinary(name: string): string | null;
@@ -0,0 +1,18 @@
1
+ import { execFileSync } from 'child_process';
2
+ import { platform } from 'os';
3
+ /**
4
+ * Cross-platform binary detection.
5
+ * Uses `which` on macOS/Linux, `where.exe` on Windows.
6
+ * Returns the resolved path on success, null if not found.
7
+ */
8
+ export function whichBinary(name) {
9
+ try {
10
+ const cmd = platform() === 'win32' ? 'where.exe' : 'which';
11
+ const result = execFileSync(cmd, [name], { stdio: 'pipe' }).toString().trim();
12
+ // `where.exe` may return multiple lines; take the first
13
+ return result.split(/[\r\n]+/)[0].trim() || null;
14
+ }
15
+ catch {
16
+ return null;
17
+ }
18
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runwork",
3
- "version": "0.5.1",
3
+ "version": "0.6.1",
4
4
  "description": "CLI for Runwork: develop, preview, and deploy Runwork apps from your local machine.",
5
5
  "license": "UNLICENSED",
6
6
  "author": "Runwork <info@runwork.ai> (https://www.runwork.ai)",
@@ -34,6 +34,7 @@
34
34
  "dev": "tsc --watch",
35
35
  "typecheck": "tsc --noEmit",
36
36
  "test": "vitest run",
37
+ "test:local": "RUNWORK_TEST_LOCAL_AGENTS=1 vitest run",
37
38
  "prepublishOnly": "bun run prebuild && tsc",
38
39
  "release": "npm publish && npm dist-tag add runwork@$(node -p \"require('./package.json').version\") next",
39
40
  "release:binaries": "bun run scripts/release-binaries.ts",
@@ -42,16 +43,18 @@
42
43
  },
43
44
  "dependencies": {
44
45
  "@clack/prompts": "^1.1.0",
46
+ "better-sqlite3": "^12.8.0",
45
47
  "chokidar": "^4.0.0",
46
48
  "commander": "^13.0.0",
47
- "curlconverter": "^4.12.0",
48
49
  "fflate": "^0.8.2",
49
50
  "open": "^10.0.0",
50
- "picocolors": "^1.1.1"
51
+ "picocolors": "^1.1.1",
52
+ "smol-toml": "^1.6.1"
51
53
  },
52
54
  "devDependencies": {
53
- "typescript": "^5.7.0",
55
+ "@types/better-sqlite3": "^7.6.13",
54
56
  "@types/node": "^22.0.0",
57
+ "typescript": "^5.7.0",
55
58
  "vitest": "^3.2.0"
56
59
  },
57
60
  "engines": {
@@ -1,10 +0,0 @@
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
- }
@@ -1,46 +0,0 @@
1
- import { execFileSync } from 'child_process';
2
- import { join } from 'path';
3
- import { homedir } from 'os';
4
- import { mergeJsonMcpServers } from './utils/json-config.js';
5
- export class CopilotCliAdapter {
6
- name = 'GitHub Copilot CLI';
7
- slug = 'copilot-cli';
8
- async detect() {
9
- try {
10
- execFileSync('which', ['copilot'], { stdio: 'pipe' });
11
- return true;
12
- }
13
- catch {
14
- try {
15
- execFileSync('gh', ['copilot', '--version'], { stdio: 'pipe' });
16
- return true;
17
- }
18
- catch {
19
- return false;
20
- }
21
- }
22
- }
23
- supportsMcpScope(_scope) {
24
- return true;
25
- }
26
- supportsSkills() {
27
- return false;
28
- }
29
- async writeMcpServers(servers, scope) {
30
- const filePath = scope === 'project'
31
- ? join(process.cwd(), '.copilot', 'mcp-config.json')
32
- : join(homedir(), '.copilot', 'mcp-config.json');
33
- const entries = {};
34
- for (const s of servers) {
35
- entries[s.name] = {
36
- type: 'http',
37
- url: s.url,
38
- ...(s.headers ? { headers: s.headers } : {}),
39
- };
40
- }
41
- mergeJsonMcpServers(filePath, entries, 'mcpServers');
42
- }
43
- async writeSkills(_skills, _scope) {
44
- // Not supported
45
- }
46
- }
@@ -1,10 +0,0 @@
1
- import type { AgentAdapter, McpServerEntry, SkillFile } from './types.js';
2
- export declare class CopilotVscodeAdapter 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
- }
@@ -1,37 +0,0 @@
1
- import { execFileSync } from 'child_process';
2
- import { join } from 'path';
3
- import { mergeJsonMcpServers } from './utils/json-config.js';
4
- export class CopilotVscodeAdapter {
5
- name = 'GitHub Copilot (VS Code)';
6
- slug = 'copilot-vscode';
7
- async detect() {
8
- try {
9
- const output = execFileSync('code', ['--list-extensions'], { stdio: 'pipe', encoding: 'utf-8' });
10
- return output.includes('GitHub.copilot');
11
- }
12
- catch {
13
- return false;
14
- }
15
- }
16
- supportsMcpScope(scope) {
17
- return scope === 'project';
18
- }
19
- supportsSkills() {
20
- return false;
21
- }
22
- async writeMcpServers(servers, _scope) {
23
- const filePath = join(process.cwd(), '.vscode', 'mcp.json');
24
- const entries = {};
25
- for (const s of servers) {
26
- entries[s.name] = {
27
- type: 'http',
28
- url: s.url,
29
- ...(s.headers ? { headers: s.headers } : {}),
30
- };
31
- }
32
- mergeJsonMcpServers(filePath, entries, 'servers');
33
- }
34
- async writeSkills(_skills, _scope) {
35
- // Not supported
36
- }
37
- }