runwork 0.6.1 → 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.
Files changed (80) hide show
  1. package/dist/__tests__/install-scripts.test.d.ts +1 -0
  2. package/dist/__tests__/install-scripts.test.js +247 -0
  3. package/dist/agents/__tests__/claude-code-plugin.test.d.ts +1 -0
  4. package/dist/agents/__tests__/claude-code-plugin.test.js +47 -0
  5. package/dist/agents/__tests__/claude-code-stats.test.js +143 -78
  6. package/dist/agents/__tests__/claude-desktop-plugin-tree.test.d.ts +1 -0
  7. package/dist/agents/__tests__/claude-desktop-plugin-tree.test.js +161 -0
  8. package/dist/agents/__tests__/claude-desktop-plugin.test.d.ts +1 -0
  9. package/dist/agents/__tests__/claude-desktop-plugin.test.js +117 -0
  10. package/dist/agents/__tests__/claude-desktop-rpm.test.d.ts +1 -0
  11. package/dist/agents/__tests__/claude-desktop-rpm.test.js +126 -0
  12. package/dist/agents/__tests__/codex-instructions.test.d.ts +1 -0
  13. package/dist/agents/__tests__/codex-instructions.test.js +57 -0
  14. package/dist/agents/__tests__/codex-stats.test.js +9 -0
  15. package/dist/agents/__tests__/graceful-degradation.test.js +3 -3
  16. package/dist/agents/__tests__/intro-skill.test.js +11 -2
  17. package/dist/agents/__tests__/skill-slug.test.d.ts +1 -0
  18. package/dist/agents/__tests__/skill-slug.test.js +77 -0
  19. package/dist/agents/claude-code.d.ts +3 -1
  20. package/dist/agents/claude-code.js +233 -58
  21. package/dist/agents/claude-desktop-plugin-tree.d.ts +44 -0
  22. package/dist/agents/claude-desktop-plugin-tree.js +92 -0
  23. package/dist/agents/claude-desktop.d.ts +20 -1
  24. package/dist/agents/claude-desktop.js +238 -61
  25. package/dist/agents/cline.d.ts +3 -1
  26. package/dist/agents/cline.js +44 -2
  27. package/dist/agents/codex.d.ts +3 -1
  28. package/dist/agents/codex.js +85 -14
  29. package/dist/agents/cursor.d.ts +12 -1
  30. package/dist/agents/cursor.js +98 -25
  31. package/dist/agents/gemini.d.ts +3 -1
  32. package/dist/agents/gemini.js +42 -4
  33. package/dist/agents/generic-adapter.d.ts +3 -1
  34. package/dist/agents/generic-adapter.js +59 -4
  35. package/dist/agents/intro-skill.js +77 -31
  36. package/dist/agents/types.d.ts +42 -2
  37. package/dist/agents/types.js +61 -6
  38. package/dist/agents/utils/instruction-hint.d.ts +10 -0
  39. package/dist/agents/utils/instruction-hint.js +46 -0
  40. package/dist/agents/utils/json-config.d.ts +6 -1
  41. package/dist/agents/utils/json-config.js +31 -4
  42. package/dist/agents/windsurf.d.ts +3 -1
  43. package/dist/agents/windsurf.js +37 -2
  44. package/dist/commands/__tests__/sync-telemetry.test.d.ts +1 -0
  45. package/dist/commands/__tests__/sync-telemetry.test.js +281 -0
  46. package/dist/commands/__tests__/upgrade.test.d.ts +1 -0
  47. package/dist/commands/__tests__/upgrade.test.js +174 -0
  48. package/dist/commands/build-plugin.d.ts +2 -0
  49. package/dist/commands/build-plugin.js +135 -0
  50. package/dist/commands/init.d.ts +8 -2
  51. package/dist/commands/init.js +19 -7
  52. package/dist/commands/sync-telemetry.d.ts +33 -0
  53. package/dist/commands/sync-telemetry.js +186 -0
  54. package/dist/commands/sync.d.ts +1 -0
  55. package/dist/commands/sync.js +84 -118
  56. package/dist/commands/uninstall.d.ts +2 -0
  57. package/dist/commands/uninstall.js +145 -0
  58. package/dist/commands/upgrade.d.ts +12 -0
  59. package/dist/commands/upgrade.js +67 -16
  60. package/dist/generated/bundled-types.js +36 -36
  61. package/dist/generated/version.d.ts +1 -1
  62. package/dist/generated/version.js +1 -1
  63. package/dist/health/__tests__/cli-distribution-checks.test.d.ts +1 -0
  64. package/dist/health/__tests__/cli-distribution-checks.test.js +199 -0
  65. package/dist/health/checks.d.ts +2 -0
  66. package/dist/health/checks.js +131 -18
  67. package/dist/health/runner.js +6 -2
  68. package/dist/index.js +4 -0
  69. package/dist/sync/executor.d.ts +2 -0
  70. package/dist/sync/executor.js +8 -4
  71. package/dist/types.d.ts +2 -0
  72. package/dist/ui/banner.js +15 -6
  73. package/dist/utils/__tests__/sqlite.test.js +84 -72
  74. package/dist/utils/sqlite-adapter.d.ts +48 -0
  75. package/dist/utils/sqlite-adapter.js +147 -0
  76. package/dist/utils/sqlite.d.ts +12 -5
  77. package/dist/utils/sqlite.js +12 -22
  78. package/dist/utils/zip.d.ts +6 -0
  79. package/dist/utils/zip.js +28 -3
  80. package/package.json +2 -4
@@ -1,7 +1,8 @@
1
- import type { AgentAdapter, AgentConfigOverride, AgentUsageStats, McpServerEntry, SkillFile } from './types.js';
1
+ import type { AgentAdapter, AgentConfigOverride, AgentUsageStats, CleanupManifest, McpServerEntry, SkillFile } from './types.js';
2
2
  export declare class CursorAdapter implements AgentAdapter {
3
3
  name: string;
4
4
  slug: string;
5
+ mcpProvidesSkills: boolean;
5
6
  detect(): Promise<boolean>;
6
7
  supportsMcpScope(_scope: 'project' | 'user'): boolean;
7
8
  supportsSkills(): boolean;
@@ -10,6 +11,16 @@ export declare class CursorAdapter implements AgentAdapter {
10
11
  writeInstructionHint(hint: string, scope: 'project' | 'user'): Promise<void>;
11
12
  writeTeamInstructions(instructions: string, scope: 'project' | 'user'): Promise<void>;
12
13
  writeAgentConfig(config: AgentConfigOverride, scope: 'project' | 'user'): Promise<void>;
14
+ /**
15
+ * Merge domains into Cursor's sandbox network policy (~/.cursor/sandbox.json).
16
+ * Schema per https://cursor.com/docs/reference/sandbox:
17
+ * { "networkPolicy": { "default": "allow" | "deny", "allow": [...], "deny": [...] } }
18
+ * Only appends to the allow list; never flips `default` (respects user's existing policy).
19
+ * If the user has `default: allow`, all traffic is already permitted and entries are harmless.
20
+ * If the user has `default: deny`, our entries take effect and unblock Runwork.
21
+ */
22
+ private mergeSandboxAllowlist;
23
+ cleanup(scope: 'project' | 'user', manifest?: CleanupManifest): Promise<void>;
13
24
  readUsageStats(lastSyncAt: string | null): Promise<AgentUsageStats | null>;
14
25
  readVersion(): Promise<string | null>;
15
26
  }
@@ -1,13 +1,14 @@
1
- import { existsSync, mkdirSync, writeFileSync } from 'fs';
1
+ import { existsSync, mkdirSync, readdirSync, unlinkSync, writeFileSync } from 'fs';
2
2
  import { join } from 'path';
3
3
  import { homedir, platform } from 'os';
4
4
  import { execFileSync } from 'child_process';
5
- import { querySqlite } from '../utils/sqlite.js';
5
+ import { querySqlite, openWritableSqlite } from '../utils/sqlite.js';
6
6
  import { whichBinary } from '../utils/which.js';
7
- import { mergeJsonMcpServers } from './utils/json-config.js';
7
+ import { mergeJsonMcpServers, removeRunworkMcpServers, readJsonConfig, writeJsonConfig } from './utils/json-config.js';
8
8
  export class CursorAdapter {
9
9
  name = 'Cursor';
10
10
  slug = 'cursor';
11
+ mcpProvidesSkills = true;
11
12
  async detect() {
12
13
  const os = platform();
13
14
  if (os === 'darwin' && existsSync('/Applications/Cursor.app'))
@@ -70,6 +71,10 @@ ${skill.content}`;
70
71
  async writeAgentConfig(config, scope) {
71
72
  if (scope !== 'user')
72
73
  return;
74
+ // Sandbox network allowlist: merge into ~/.cursor/cli-config.json
75
+ if (config.networkAllowlist?.length) {
76
+ this.mergeSandboxAllowlist(config.networkAllowlist);
77
+ }
73
78
  const os = platform();
74
79
  let globalDbPath;
75
80
  if (os === 'darwin') {
@@ -83,36 +88,104 @@ ${skill.content}`;
83
88
  }
84
89
  if (!existsSync(globalDbPath))
85
90
  return;
91
+ const db = openWritableSqlite(globalDbPath);
92
+ if (!db) {
93
+ // SQLite runtime unavailable (older Node without node:sqlite, or not running
94
+ // under Bun) or the database is locked/inaccessible. Cursor config write is
95
+ // best-effort, so we silently skip.
96
+ return;
97
+ }
86
98
  try {
87
- const Database = (await import('better-sqlite3')).default;
88
- const db = new Database(globalDbPath, { timeout: 5000 });
89
- try {
90
- const storageKey = 'src.vs.platform.reactivestorage.browser.reactiveStorageServiceImpl.persistentStorage.applicationUser';
91
- const row = db.prepare(`SELECT value FROM ItemTable WHERE key = ?`).get(storageKey);
92
- if (!row?.value)
93
- return;
94
- const data = JSON.parse(row.value);
95
- if (config.modelPreference) {
96
- if (!data.aiSettings)
97
- data.aiSettings = {};
98
- data.aiSettings.composerModel = config.modelPreference;
99
+ const storageKey = 'src.vs.platform.reactivestorage.browser.reactiveStorageServiceImpl.persistentStorage.applicationUser';
100
+ const row = db.prepare(`SELECT value FROM ItemTable WHERE key = ?`).get(storageKey);
101
+ if (!row?.value)
102
+ return;
103
+ const data = JSON.parse(row.value);
104
+ if (config.modelPreference) {
105
+ if (!data.aiSettings)
106
+ data.aiSettings = {};
107
+ data.aiSettings.composerModel = config.modelPreference;
108
+ }
109
+ if (config.permissionRules && data.composerState) {
110
+ if (config.permissionRules.allow) {
111
+ data.composerState.yoloCommandAllowlist = config.permissionRules.allow;
99
112
  }
100
- if (config.permissionRules && data.composerState) {
101
- if (config.permissionRules.allow) {
102
- data.composerState.yoloCommandAllowlist = config.permissionRules.allow;
103
- }
104
- if (config.permissionRules.deny) {
105
- data.composerState.yoloCommandDenylist = config.permissionRules.deny;
106
- }
113
+ if (config.permissionRules.deny) {
114
+ data.composerState.yoloCommandDenylist = config.permissionRules.deny;
107
115
  }
116
+ }
117
+ try {
108
118
  db.prepare(`UPDATE ItemTable SET value = ? WHERE key = ?`).run(JSON.stringify(data), storageKey);
109
119
  }
110
- finally {
111
- db.close();
120
+ catch {
121
+ // Write failed (db locked while Cursor is running). Best-effort skip.
122
+ }
123
+ }
124
+ finally {
125
+ db.close();
126
+ }
127
+ }
128
+ /**
129
+ * Merge domains into Cursor's sandbox network policy (~/.cursor/sandbox.json).
130
+ * Schema per https://cursor.com/docs/reference/sandbox:
131
+ * { "networkPolicy": { "default": "allow" | "deny", "allow": [...], "deny": [...] } }
132
+ * Only appends to the allow list; never flips `default` (respects user's existing policy).
133
+ * If the user has `default: allow`, all traffic is already permitted and entries are harmless.
134
+ * If the user has `default: deny`, our entries take effect and unblock Runwork.
135
+ */
136
+ mergeSandboxAllowlist(domains) {
137
+ const configPath = join(homedir(), '.cursor', 'sandbox.json');
138
+ try {
139
+ const config = readJsonConfig(configPath);
140
+ if (!config.networkPolicy)
141
+ config.networkPolicy = {};
142
+ const policy = config.networkPolicy;
143
+ const existing = (policy.allow ?? []);
144
+ const merged = new Set(existing);
145
+ for (const domain of domains) {
146
+ merged.add(domain);
112
147
  }
148
+ policy.allow = [...merged];
149
+ writeJsonConfig(configPath, config);
113
150
  }
114
151
  catch {
115
- // Cursor config write is best-effort (DB may be locked while Cursor is running)
152
+ // Best-effort: sandbox config write is non-fatal
153
+ }
154
+ }
155
+ // ── Cleanup (uninstall) ────────────────────────────────────────────
156
+ async cleanup(scope, manifest) {
157
+ // 1. Remove MCP entries
158
+ const mcpPath = scope === 'project'
159
+ ? join(process.cwd(), '.cursor', 'mcp.json')
160
+ : join(homedir(), '.cursor', 'mcp.json');
161
+ removeRunworkMcpServers(mcpPath, 'mcpServers');
162
+ // 2. Remove rule files (skills, hint, team instructions)
163
+ const rulesDir = scope === 'project'
164
+ ? join(process.cwd(), '.cursor', 'rules')
165
+ : join(homedir(), '.cursor', 'rules');
166
+ if (existsSync(rulesDir)) {
167
+ // Remove runwork.mdc and runwork-team.mdc
168
+ for (const file of ['runwork.mdc', 'runwork-team.mdc']) {
169
+ const filePath = join(rulesDir, file);
170
+ if (existsSync(filePath)) {
171
+ try {
172
+ unlinkSync(filePath);
173
+ }
174
+ catch { /* best-effort */ }
175
+ }
176
+ }
177
+ // Remove only skill .mdc files that we synced
178
+ if (manifest?.skillFilenames.length) {
179
+ const allowed = new Set(manifest.skillFilenames.map(f => `${f}.mdc`));
180
+ for (const entry of readdirSync(rulesDir)) {
181
+ if (allowed.has(entry)) {
182
+ try {
183
+ unlinkSync(join(rulesDir, entry));
184
+ }
185
+ catch { /* best-effort */ }
186
+ }
187
+ }
188
+ }
116
189
  }
117
190
  }
118
191
  // ── Usage stats (passive, best-effort) ─────────────────────────────
@@ -1,7 +1,8 @@
1
- import type { AgentAdapter, AgentConfigOverride, McpServerEntry, SkillFile } from './types.js';
1
+ import type { AgentAdapter, AgentConfigOverride, CleanupManifest, McpServerEntry, SkillFile } from './types.js';
2
2
  export declare class GeminiAdapter implements AgentAdapter {
3
3
  name: string;
4
4
  slug: string;
5
+ mcpProvidesSkills: boolean;
5
6
  detect(): Promise<boolean>;
6
7
  supportsMcpScope(scope: 'project' | 'user'): boolean;
7
8
  supportsSkills(): boolean;
@@ -10,4 +11,5 @@ export declare class GeminiAdapter implements AgentAdapter {
10
11
  writeInstructionHint(hint: string, scope: 'project' | 'user'): Promise<void>;
11
12
  writeTeamInstructions(instructions: string, scope: 'project' | 'user'): Promise<void>;
12
13
  writeAgentConfig(config: AgentConfigOverride, scope: 'project' | 'user'): Promise<void>;
14
+ cleanup(scope: 'project' | 'user', manifest?: CleanupManifest): Promise<void>;
13
15
  }
@@ -1,13 +1,14 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
1
+ import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'fs';
2
2
  import { join } from 'path';
3
3
  import { homedir } from 'os';
4
4
  import { whichBinary } from '../utils/which.js';
5
5
  import { buildSkillMd } from './types.js';
6
- import { writeHintToFile, writeTeamInstructionsToFile } from './utils/instruction-hint.js';
7
- import { mergeJsonMcpServers } from './utils/json-config.js';
6
+ import { writeHintToFile, writeTeamInstructionsToFile, removeHintFromFile, removeTeamInstructionsFromFile } from './utils/instruction-hint.js';
7
+ import { mergeJsonMcpServers, removeRunworkMcpServers } from './utils/json-config.js';
8
8
  export class GeminiAdapter {
9
9
  name = 'Gemini CLI';
10
10
  slug = 'gemini';
11
+ mcpProvidesSkills = true;
11
12
  async detect() {
12
13
  return !!whichBinary('gemini');
13
14
  }
@@ -33,7 +34,16 @@ export class GeminiAdapter {
33
34
  ? join(process.cwd(), '.gemini', 'skills')
34
35
  : join(homedir(), '.gemini', 'skills');
35
36
  for (const skill of skills) {
36
- const skillDir = join(baseDir, skill.name);
37
+ if (skill.name !== skill.filename) {
38
+ const oldDir = join(baseDir, skill.name);
39
+ if (existsSync(oldDir)) {
40
+ try {
41
+ rmSync(oldDir, { recursive: true, force: true });
42
+ }
43
+ catch { /* best-effort */ }
44
+ }
45
+ }
46
+ const skillDir = join(baseDir, skill.filename);
37
47
  mkdirSync(skillDir, { recursive: true });
38
48
  writeFileSync(join(skillDir, 'SKILL.md'), buildSkillMd(skill));
39
49
  }
@@ -81,4 +91,32 @@ export class GeminiAdapter {
81
91
  mkdirSync(join(settingsPath, '..'), { recursive: true });
82
92
  writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
83
93
  }
94
+ // ── Cleanup (uninstall) ────────────────────────────────────────────
95
+ async cleanup(scope, manifest) {
96
+ // 1. Remove MCP entries (user scope only)
97
+ if (scope === 'user') {
98
+ removeRunworkMcpServers(join(homedir(), '.gemini', 'settings.json'), 'mcpServers');
99
+ }
100
+ // 2. Remove only skill directories that we synced (listed in manifest)
101
+ const skillsDir = scope === 'project'
102
+ ? join(process.cwd(), '.gemini', 'skills')
103
+ : join(homedir(), '.gemini', 'skills');
104
+ if (existsSync(skillsDir) && manifest?.skillFilenames.length) {
105
+ const allowed = new Set(manifest.skillFilenames);
106
+ for (const entry of readdirSync(skillsDir)) {
107
+ if (!allowed.has(entry))
108
+ continue;
109
+ try {
110
+ rmSync(join(skillsDir, entry), { recursive: true, force: true });
111
+ }
112
+ catch { /* best-effort */ }
113
+ }
114
+ }
115
+ // 3. Remove instruction hints and team instructions
116
+ const instructionFile = scope === 'project'
117
+ ? join(process.cwd(), 'GEMINI.md')
118
+ : join(homedir(), '.gemini', 'GEMINI.md');
119
+ removeHintFromFile(instructionFile);
120
+ removeTeamInstructionsFromFile(instructionFile);
121
+ }
84
122
  }
@@ -1,4 +1,4 @@
1
- import type { AgentAdapter, McpServerEntry, SkillFile } from './types.js';
1
+ import type { AgentAdapter, CleanupManifest, McpServerEntry, SkillFile } from './types.js';
2
2
  import type { AgentDefinition } from './registry.js';
3
3
  /**
4
4
  * Generic adapter that handles any agent from the registry using its
@@ -11,6 +11,7 @@ import type { AgentDefinition } from './registry.js';
11
11
  export declare class GenericAgentAdapter implements AgentAdapter {
12
12
  name: string;
13
13
  slug: string;
14
+ mcpProvidesSkills: boolean;
14
15
  private def;
15
16
  constructor(def: AgentDefinition);
16
17
  detect(): Promise<boolean>;
@@ -20,4 +21,5 @@ export declare class GenericAgentAdapter implements AgentAdapter {
20
21
  writeSkills(skills: SkillFile[], scope: 'project' | 'user'): Promise<void>;
21
22
  writeInstructionHint(hint: string, scope: 'project' | 'user'): Promise<void>;
22
23
  writeTeamInstructions(instructions: string, scope: 'project' | 'user'): Promise<void>;
24
+ cleanup(scope: 'project' | 'user', manifest?: CleanupManifest): Promise<void>;
23
25
  }
@@ -1,10 +1,10 @@
1
- import { existsSync, mkdirSync, writeFileSync } from 'fs';
1
+ import { existsSync, mkdirSync, readdirSync, rmSync, writeFileSync } from 'fs';
2
2
  import { isAbsolute, join } from 'path';
3
3
  import { homedir } from 'os';
4
4
  import { whichBinary } from '../utils/which.js';
5
5
  import { buildSkillMd } from './types.js';
6
- import { mergeJsonMcpServers } from './utils/json-config.js';
7
- import { writeHintToFile, writeTeamInstructionsToFile } from './utils/instruction-hint.js';
6
+ import { mergeJsonMcpServers, removeRunworkMcpServers } from './utils/json-config.js';
7
+ import { writeHintToFile, writeTeamInstructionsToFile, removeHintFromFile, removeTeamInstructionsFromFile } from './utils/instruction-hint.js';
8
8
  import { resolvePlatformString, resolveToAbsolute } from './registry.js';
9
9
  /**
10
10
  * Generic adapter that handles any agent from the registry using its
@@ -17,6 +17,7 @@ import { resolvePlatformString, resolveToAbsolute } from './registry.js';
17
17
  export class GenericAgentAdapter {
18
18
  name;
19
19
  slug;
20
+ mcpProvidesSkills = true;
20
21
  def;
21
22
  constructor(def) {
22
23
  this.def = def;
@@ -72,7 +73,16 @@ export class GenericAgentAdapter {
72
73
  if (!baseDir)
73
74
  return;
74
75
  for (const skill of skills) {
75
- const skillDir = join(baseDir, skill.name);
76
+ if (skill.name !== skill.filename) {
77
+ const oldDir = join(baseDir, skill.name);
78
+ if (existsSync(oldDir)) {
79
+ try {
80
+ rmSync(oldDir, { recursive: true, force: true });
81
+ }
82
+ catch { /* best-effort */ }
83
+ }
84
+ }
85
+ const skillDir = join(baseDir, skill.filename);
76
86
  mkdirSync(skillDir, { recursive: true });
77
87
  writeFileSync(join(skillDir, 'SKILL.md'), buildSkillMd(skill));
78
88
  }
@@ -103,4 +113,49 @@ export class GenericAgentAdapter {
103
113
  return;
104
114
  writeTeamInstructionsToFile(filePath, instructions);
105
115
  }
116
+ // ── Cleanup (uninstall) ────────────────────────────────────────────
117
+ async cleanup(scope, manifest) {
118
+ // 1. Remove MCP entries
119
+ if (this.def.mcpConfigPath && scope === 'user') {
120
+ const resolved = resolvePlatformString(this.def.mcpConfigPath);
121
+ if (resolved) {
122
+ const filePath = join(homedir(), resolved);
123
+ removeRunworkMcpServers(filePath, this.def.mcpConfigKey || 'mcpServers');
124
+ }
125
+ }
126
+ // 2. Remove only skill directories that we synced (listed in manifest)
127
+ if (this.def.skillsPaths && manifest?.skillFilenames.length) {
128
+ const pathTemplate = scope === 'project'
129
+ ? this.def.skillsPaths.project
130
+ : this.def.skillsPaths.global;
131
+ if (pathTemplate) {
132
+ const baseDir = resolveToAbsolute(pathTemplate, scope === 'project' ? 'project' : 'global');
133
+ if (baseDir && existsSync(baseDir)) {
134
+ const allowed = new Set(manifest.skillFilenames);
135
+ for (const entry of readdirSync(baseDir)) {
136
+ if (!allowed.has(entry))
137
+ continue;
138
+ const entryPath = join(baseDir, entry);
139
+ try {
140
+ rmSync(entryPath, { recursive: true, force: true });
141
+ }
142
+ catch { /* best-effort */ }
143
+ }
144
+ }
145
+ }
146
+ }
147
+ // 3. Remove instruction hints and team instructions
148
+ if (this.def.instructionFile) {
149
+ const pathTemplate = scope === 'project'
150
+ ? this.def.instructionFile.project
151
+ : this.def.instructionFile.global;
152
+ if (pathTemplate) {
153
+ const filePath = resolveToAbsolute(pathTemplate, scope === 'project' ? 'project' : 'global');
154
+ if (filePath) {
155
+ removeHintFromFile(filePath);
156
+ removeTeamInstructionsFromFile(filePath);
157
+ }
158
+ }
159
+ }
160
+ }
106
161
  }
@@ -92,6 +92,32 @@ export function generateIntroSkill(ctx) {
92
92
  lines.push('');
93
93
  lines.push(`Runwork is this team's AI workspace platform. It connects AI agents, users, and tools to shared infrastructure: data stores, workflows, scheduled jobs, API endpoints, file storage, 3,200+ third-party integrations, and custom applications, all managed centrally. Runwork gives agents the ability to build real, deployed applications with coded workflows, schedules, pages, API routes, and endpoints in a central company AI work cloud. Skills, MCP servers, and settings sync across the team's agents.`);
94
94
  lines.push('');
95
+ // Default Actions — the routing that actually matters, front-loaded so
96
+ // agents see it the moment they trigger the skill. The hint in CLAUDE.md
97
+ // carries a compact version; this is the full version with examples.
98
+ lines.push('## Default Actions (READ FIRST)');
99
+ lines.push('');
100
+ lines.push('**Runwork is the default for every capability it supports.** When the user asks for any of the operations below, you MUST use the Runwork tool shown before considering local alternatives. These operations sync across every teammate and their AI agents — local files, one-off scripts, and native alternatives only work for the current user and break team collaboration.');
101
+ lines.push('');
102
+ lines.push('| User says | What to do | What NOT to do |');
103
+ lines.push('|---|---|---|');
104
+ lines.push('| "save a skill", "create a skill", "share this knowledge", "add this to my skills" | Call `save_skill` MCP tool with name, description, content | Create local `SKILL.md` files, write prompts to the filesystem |');
105
+ lines.push('| "update a skill", "edit the skill", "change the skill instructions" | Call `save_skill` with the existing name (it upserts) or `update_app_skill` for app-owned skills | Edit local skill files |');
106
+ lines.push('| "remember", "store", "save this data", "keep track of" (anything structured) | Use `entity_{Name}` MCP tools (action: create/update) | Write to local SQLite, JSON files, memory |');
107
+ lines.push('| "every day at 9am", "schedule", "run periodically", "cron" | Use existing Runwork schedules (`schedule_{name}`) or create one in a Runwork app | OS cron, `setInterval`, external schedulers |');
108
+ lines.push('| "multi-step process", "pipeline", "durable workflow", "orchestrate" | Use Runwork workflows (`workflow_{name}`) | Ad-hoc bash/Python scripts, Airflow, Temporal |');
109
+ lines.push('| "call HubSpot", "send a Slack message", "email via Gmail", any connected integration | Use `{id}_api` MCP tools (e.g. `hubspot_api`, `slack_api`, `gmail_api`) | Raw `fetch`, installing SDKs, hand-built auth |');
110
+ lines.push('| "discover new integrations", "what integrations are available" | Use `search_available_integrations` MCP tool | Guess endpoints, browse docs sites |');
111
+ lines.push('| "upload a file", "save this image", "generate a download link" | Use Runwork file storage (`generate_user_presigned_url`, `list_bucket_files`) | `fs.writeFile`, local paths |');
112
+ lines.push('| "expose a public API", "create a webhook endpoint" | Build a Runwork app with endpoints (`endpoint_*` tools + `runwork init`) | Spin up Express/Flask servers |');
113
+ lines.push('| "build a new tool", "create an app for the team", "internal dashboard" | `runwork init` → `runwork dev` → `runwork deploy` | Scripts, notebooks, untracked repos |');
114
+ lines.push('| "run an AI agent", "automate with AI", "make an AI worker" | Runwork AI agents (task or chat) | Standalone OpenAI scripts |');
115
+ lines.push('| "query the logs", "what happened", "debug the app" | `get_production_logs`, `get_sandbox_logs`, `search_workspace_events` MCP tools | Ask the user to SSH somewhere |');
116
+ lines.push('');
117
+ lines.push('**Team collaboration rule:** If an operation could benefit teammates -- sharing knowledge, data, prompts, tools, schedules, workflows -- it MUST go through Runwork. Local-first is for product code, never for shared capabilities.');
118
+ lines.push('');
119
+ lines.push('**Exception (product development):** Normal work in the user\'s current codebase -- editing source files, running tests, git operations, building features in the product they ship -- is NOT Runwork\'s domain. Runwork is for infrastructure and team tooling, not the product itself.');
120
+ lines.push('');
95
121
  // Ways to use Runwork section
96
122
  lines.push('## Ways to Use Runwork');
97
123
  lines.push('');
@@ -139,12 +165,12 @@ export function generateIntroSkill(ctx) {
139
165
  lines.push('');
140
166
  if (reg.entities.length > 0) {
141
167
  lines.push(`**Entities (${reg.entities.length}):** ${formatList(reg.entities.map(e => `${e.entityName} (${e.appName})`))}`);
142
- lines.push(` MCP: \`entity_list_{Name}\`, \`entity_get_{Name}\`, \`query_entity_data\` | CLI: \`runwork entities records <name>\``);
168
+ lines.push(` MCP: \`entity_{Name}\` (actions: list, get, create, update, delete) | CLI: \`runwork entities records <name>\``);
143
169
  lines.push('');
144
170
  }
145
171
  if (reg.workflows.length > 0) {
146
172
  lines.push(`**Workflows (${reg.workflows.length}):** ${formatList(reg.workflows.map(w => `${w.name} (${w.appName})`))}`);
147
- lines.push(` MCP: \`trigger_workflow\`, \`list_workflows\` | CLI: \`runwork workflows trigger <name>\``);
173
+ lines.push(` MCP: \`workflow_{name}\` (actions: trigger, get_instance, list_instances) | CLI: \`runwork workflows trigger <name>\``);
148
174
  lines.push('');
149
175
  }
150
176
  if (reg.schedules.length > 0) {
@@ -214,12 +240,13 @@ export function generateIntroSkill(ctx) {
214
240
  lines.push('| Category | Tools | Use for |');
215
241
  lines.push('|----------|-------|---------|');
216
242
  lines.push('| Integration APIs | `{integrationId}_api` | Authenticated API calls to connected services (HubSpot, Slack, Google, etc.). Pass method and path. |');
217
- lines.push('| Entity Data | `entity_list_{Name}`, `entity_get_{Name}`, `entity_create_{Name}`, `query_entity_data` | CRUD operations on stored data |');
243
+ lines.push('| Entity Data | `entity_{Name}` | CRUD operations on stored data (actions: list, get, create, update, delete) |');
218
244
  lines.push('| Endpoints | `endpoint_{method}_{path}` | Call app API endpoints |');
219
- lines.push('| Workflows | `trigger_workflow`, `list_workflows`, `get_workflow_instance` | Trigger and monitor multi-step processes |');
220
- lines.push('| Schedules | `trigger_schedule`, `list_schedules`, `get_schedule_status` | Trigger and monitor background jobs |');
245
+ lines.push('| Workflows | `workflow_{name}` | Trigger and monitor multi-step processes (actions: trigger, get_instance, list_instances) |');
246
+ lines.push('| Schedules | `schedule_{name}` | Trigger and monitor background jobs (actions: trigger, status, history, pause, resume) |');
247
+ lines.push('| Skills | `skill_{name}` | Activate workspace skills for specialized instructions and context |');
221
248
  lines.push('| File Storage | `list_bucket_files`, `generate_user_presigned_url`, `delete_bucket_object` | Manage files in storage buckets |');
222
- lines.push('| Discovery | `search_available_integrations`, `list_integrations`, `list_entities`, `list_apps` | Find available resources and capabilities |');
249
+ lines.push('| Discovery | `search_available_integrations`, `list_integrations`, `list_apps` | Find available resources and capabilities |');
223
250
  lines.push('| Logs | `get_production_logs`, `get_sandbox_logs` | View app logs and debug issues |');
224
251
  lines.push('');
225
252
  // CLI section
@@ -230,7 +257,7 @@ export function generateIntroSkill(ctx) {
230
257
  lines.push('**Finding the CLI:**');
231
258
  lines.push('1. Check if already installed: `which runwork` or `runwork --version`');
232
259
  lines.push('2. If installed via the Runwork Desktop app, the binary may be at `~/.runwork/bin/runwork`. Try: `~/.runwork/bin/runwork --version`');
233
- lines.push('3. To install: `curl -fsSL https://runwork.ai/install.sh | sh` or `npm install -g runwork`');
260
+ lines.push('3. To install: `curl -fsSL https://runwork.ai/install.sh | sh` (macOS/Linux), `brew install runwork-ai/tap/runwork` (Homebrew), or `npx runwork@latest` / `bunx runwork@latest` (any platform)');
234
261
  lines.push('');
235
262
  lines.push('**Key commands:**');
236
263
  lines.push('');
@@ -260,9 +287,11 @@ export function generateIntroSkill(ctx) {
260
287
  lines.push('- **With web dashboard:** Go to https://runwork.ai, create an app, and use the built-in AI agent to build it conversationally');
261
288
  lines.push('- **With desktop app:** The desktop app provides quick access to create and manage apps');
262
289
  lines.push('');
290
+ lines.push('> By default, `runwork init` scaffolds into `~/.runwork/apps/<slug>` so every app has a predictable home. Pass `--here` to scaffold in the current directory instead.');
291
+ lines.push('');
263
292
  lines.push('**CLI development workflow:**');
264
- lines.push('1. `runwork init my-app` -- scaffold the app');
265
- lines.push('2. `cd my-app` -- enter the app directory');
293
+ lines.push('1. `runwork init my-app` -- scaffold the app (created at `~/.runwork/apps/my-app`)');
294
+ lines.push('2. `cd ~/.runwork/apps/my-app` -- enter the app directory');
266
295
  lines.push('3. `runwork dev` -- start development (watches for file changes, syncs automatically)');
267
296
  lines.push('4. Edit files for your needs (see below)');
268
297
  lines.push('5. `runwork deploy` -- deploy to production');
@@ -285,31 +314,33 @@ export function generateIntroSkill(ctx) {
285
314
  lines.push('- Every workflow needs a trigger (endpoint, schedule, or UI button).');
286
315
  lines.push('- Use `search_available_integrations` MCP tool or `runwork integrations search <query>` before adding integrations, never guess IDs.');
287
316
  lines.push('');
288
- // Routing Preferences
289
- lines.push('## When to Use Runwork');
290
- lines.push('');
291
- lines.push('| Need | Use Runwork | Don\'t use Runwork |');
292
- lines.push('|------|------------|-------------------|');
293
- lines.push('| Store structured data | Apps with entities | Local files, in-memory |');
294
- lines.push('| Store files | Apps with file storage | Local filesystem |');
295
- lines.push('| Schedule recurring tasks | Apps with scheduled jobs | Native cron, OS schedulers |');
296
- lines.push('| Multi-step business processes | Apps with workflows | Ad-hoc scripts |');
297
- lines.push('| Third-party service access | Connected integrations (use `{id}_api` MCP tool or CLI, no app needed) | Direct API calls without auth management |');
298
- lines.push('| AI chat or automation | Apps with agents | Standalone AI scripts |');
299
- lines.push('| Share AI expertise | Skills | One-off prompts |');
300
- lines.push('| Connect AI tools | MCP servers | Manual tool configuration |');
301
- lines.push('| Expose APIs externally | Apps with public endpoints | Standalone servers |');
302
- lines.push('| Reuse across projects | Apps with components | Copy-paste between repos |');
303
- lines.push('| Team collaboration | Workspace apps, shared skills | Isolated local tools |');
304
- lines.push('');
305
- lines.push('**When NOT to use Runwork:** Normal development in your current codebase (editing files, running tests, git operations, building features in the product you\'re working on). Runwork is for infrastructure and team tooling.');
317
+ // Capability reference — detailed mapping of needs to Runwork capabilities.
318
+ // The "Default Actions" section at the top handles routing; this is the
319
+ // longer reference for when the agent wants to understand what's available.
320
+ lines.push('## Capability Reference');
321
+ lines.push('');
322
+ lines.push('Detailed mapping from needs to Runwork capabilities. See "Default Actions" above for the trigger-phrase routing.');
323
+ lines.push('');
324
+ lines.push('| Need | Runwork capability | Tool / Command |');
325
+ lines.push('|------|-------------------|----------------|');
326
+ lines.push('| Structured data storage | Entities | `entity_{Name}` MCP, `runwork entities records` |');
327
+ lines.push('| File / blob storage | File storages | `list_bucket_files`, `generate_user_presigned_url` |');
328
+ lines.push('| Recurring jobs | Schedules | `schedule_{name}` MCP, `runwork schedules` |');
329
+ lines.push('| Multi-step durable processes | Workflows | `workflow_{name}` MCP, `runwork workflows trigger` |');
330
+ lines.push('| Third-party service access | Connected integrations | `{id}_api` MCP tools (HubSpot, Slack, Gmail, etc.) |');
331
+ lines.push('| AI chat / automation workers | Agents | Conversational + task agents in apps |');
332
+ lines.push('| Shared prompts & knowledge | Skills | `save_skill` MCP, `runwork skills` |');
333
+ lines.push('| AI tool connectivity | MCP servers | Synced automatically to all agents |');
334
+ lines.push('| Public APIs / webhooks | Endpoints | `endpoint_*` MCP tools, `runwork endpoints` |');
335
+ lines.push('| Cross-app UI / logic reuse | Components | Registered per app |');
336
+ lines.push('| Team collaboration | All of the above | Everything syncs across teammates |');
306
337
  lines.push('');
307
338
  // Helping the user section
308
339
  lines.push('## Helping the User');
309
340
  lines.push('');
310
341
  lines.push('If the user asks about Runwork setup, integrations, or getting started:');
311
342
  lines.push('');
312
- lines.push('- **Install CLI:** `curl -fsSL https://runwork.ai/install.sh | sh` (macOS/Linux) or `npm install -g runwork` (any platform)');
343
+ lines.push('- **Install CLI:** `curl -fsSL https://runwork.ai/install.sh | sh` (macOS/Linux), `brew install runwork-ai/tap/runwork` (Homebrew), or `npx runwork@latest` / `bunx runwork@latest` (any platform)');
313
344
  lines.push('- **Desktop app:** Download from https://runwork.ai (includes bundled CLI at `~/.runwork/bin/runwork`)');
314
345
  lines.push('- **Connect workspace:** `runwork login` then `runwork setup`');
315
346
  lines.push('- **Connect an integration:** Visit the workspace settings in the web dashboard at https://runwork.ai, or use `runwork integrations search <query>` to find services');
@@ -326,7 +357,7 @@ export function generateIntroSkill(ctx) {
326
357
  name: 'runwork',
327
358
  filename: 'runwork',
328
359
  content: lines.join('\n'),
329
- description: 'Your team\'s shared infrastructure platform. Use for: data & file storage, scheduling, background jobs, workflows, integrations (Slack, GitHub, etc.), AI agents, skills, MCP servers, public APIs, and reusable components. Invoke BEFORE using native alternatives for these capabilities. Interact via MCP tools (always available), CLI, web dashboard, desktop app, or API.',
360
+ description: 'TRIGGER when user asks to: save/create/share/update a skill, store or persist data, schedule a recurring job, run a workflow or pipeline, call Slack/HubSpot/Gmail/any third-party integration, upload or persist a file, share team knowledge or expertise, connect AI tools via MCP, expose a public API, or build a team app or internal tool. Runwork is the team\'s shared infrastructure platform -- these operations sync across every teammate\'s AI agents, while local files only work for the current user. Always invoke BEFORE reaching for local files, native cron, SQLite, raw fetch calls, or one-off scripts. Features: skills, MCP servers, entities, file storage, schedules, workflows, integrations, agents, public endpoints, components. Interact via MCP tools (always available), CLI (`runwork`), web dashboard, desktop app, or API.',
330
361
  };
331
362
  }
332
363
  /**
@@ -348,11 +379,26 @@ export function generateInstructionHint(ctx) {
348
379
  '',
349
380
  workspaceIdLine,
350
381
  '',
351
- '**Routing:** For data storage, file storage, scheduling, workflows, integrations, AI agents, automation, skills, or shared team tools -- use Runwork first. Invoke the `runwork` skill before building these from scratch or using native/built-in alternatives. This does NOT apply to normal development in your current codebase -- Runwork is for infrastructure and team tooling, not for modifying the product you\'re working on.',
382
+ '**Default to Runwork -- team sync beats local files.** When the user asks for any of the following, use the Runwork tool shown. These operations sync across every teammate\'s AI agents; local alternatives only work for the current user.',
383
+ '',
384
+ '| User asks for | Use Runwork | Don\'t |',
385
+ '|---|---|---|',
386
+ '| save / create / share / update a skill | `save_skill` MCP tool | local SKILL.md files, prompt snippets |',
387
+ '| store, persist, or remember structured data | `entity_{Name}` MCP tools | local SQLite, JSON files, in-memory |',
388
+ '| schedule a recurring or cron job | Runwork schedules (`schedule_{name}`, `runwork schedules`) | OS cron, `setInterval`, Celery |',
389
+ '| multi-step process, pipeline, or durable workflow | Runwork workflows (`workflow_{name}`) | ad-hoc scripts, shell pipelines |',
390
+ '| call Slack / HubSpot / Gmail / Google / any third-party API | `{id}_api` MCP tools (e.g. `hubspot_api`) | raw `fetch`, hand-built auth, SDK installs |',
391
+ '| upload, save, or serve a file | Runwork file storage (`list_bucket_files`, `generate_user_presigned_url`) | local filesystem, `fs.writeFile` |',
392
+ '| expose a public API / webhook endpoint | Runwork endpoints (`endpoint_*`) | standalone Express/Flask servers |',
393
+ '| share AI prompts, instructions, or expertise with the team | `save_skill` MCP tool | local prompt files, copy-paste |',
394
+ '| connect an AI tool or MCP server | Runwork MCP servers | manual `settings.json` edits per machine |',
395
+ '| build a new internal tool, team app, or automation | `runwork init` -> `runwork dev` -> `runwork deploy` | one-off scripts, notebooks |',
396
+ '',
397
+ '**Exception -- product development:** Editing your current codebase, running tests, git operations, and building features in the product you\'re working on are NOT Runwork territory. Runwork is for infrastructure and team tooling, not the product itself.',
352
398
  '',
353
399
  `**Available now:** ${inventory}`,
354
400
  '',
355
- `Use Runwork MCP tools for data access, integration API calls, and resource management (always available).${integrationToolHint} Use \`runwork\` CLI to create (\`runwork init\`), develop (\`runwork dev\`), and deploy (\`runwork deploy\`) apps. Install CLI: \`curl -fsSL https://runwork.ai/install.sh | sh\`. Web dashboard: ${dashboardUrl}. Use the \`runwork\` skill for full details.`,
401
+ `Runwork MCP tools are always connected -- use them for data access, integration API calls, skill management, and resource discovery.${integrationToolHint} Use \`runwork\` CLI to create (\`runwork init\`), develop (\`runwork dev\`), and deploy (\`runwork deploy\`) apps. Install CLI: \`curl -fsSL https://runwork.ai/install.sh | sh\`. Web dashboard: ${dashboardUrl}. Invoke the \`runwork\` skill for full capability reference.`,
356
402
  '<!-- runwork:end -->',
357
403
  ].join('\n');
358
404
  }