runwork 0.12.0 → 0.13.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.
@@ -11,6 +11,7 @@ import { RUNWORK_AGENT_DEFAULTS, AGENT_DEFAULTS_SCHEMA_VERSION } from '../agents
11
11
  import { resolveAgentDefaults } from '../agents/defaults-merge.js';
12
12
  import { collectTelemetryEvents, printTelemetryVerbose, summarizeTelemetryForDryRun, } from './sync-telemetry.js';
13
13
  import { generateIntroSkill, generateInstructionHint, buildAppSkillDescription } from '../agents/intro-skill.js';
14
+ import { buildShareConversationSkill, buildSaveConversationSkill, BUILT_IN_SKILL_NAMES } from '../agents/conversation-skills.js';
14
15
  import { computeSyncPlan } from '../sync/change-detect.js';
15
16
  import { printSyncSummary, resolveConflict, resolveConflictNonInteractive } from '../sync/conflict-ui.js';
16
17
  import { executeSyncPlan } from '../sync/executor.js';
@@ -194,11 +195,27 @@ export async function syncFromState(state, statePath, credentials, opts) {
194
195
  }
195
196
  // Read local skills for change detection
196
197
  const localSkills = readLocalSkills(state);
198
+ // Built-in skills (shipped by the CLI from code) are CANONICAL: they
199
+ // are never pushed to the workspace and never pulled from it. The diff
200
+ // engine must not see them at all -- if it did, a workspace skill that
201
+ // happens to share a name (e.g. someone uploaded `share-conversation`
202
+ // manually) would either push the built-in upstream or pull the stale
203
+ // workspace copy on top, both wrong. Filter both sides before the diff.
204
+ const builtInNameSet = new Set(BUILT_IN_SKILL_NAMES);
205
+ const collidingRemote = remoteSkills.filter(s => builtInNameSet.has(s.name));
206
+ if (collidingRemote.length > 0) {
207
+ console.log(`\n Warning: ${collidingRemote.length} workspace skill${collidingRemote.length === 1 ? '' : 's'} ` +
208
+ `shadow${collidingRemote.length === 1 ? 's' : ''} built-in CLI skill${collidingRemote.length === 1 ? '' : 's'}: ` +
209
+ `${collidingRemote.map(s => s.name).join(', ')}. The CLI built-in versions will be used locally; ` +
210
+ `the workspace copies are stale duplicates and can be safely deleted from the dashboard.`);
211
+ }
212
+ const localSkillsForDiff = localSkills.filter(s => !builtInNameSet.has(s.name));
213
+ const remoteSkillsForDiff = remoteSkills.filter(s => !builtInNameSet.has(s.name));
197
214
  // Compute sync plan
198
215
  const plan = computeSyncPlan({
199
216
  storedHashes: state.skillHashes || {},
200
- localSkills,
201
- remoteSkills,
217
+ localSkills: localSkillsForDiff,
218
+ remoteSkills: remoteSkillsForDiff,
202
219
  });
203
220
  // In pull-only mode, move pushes and conflicts to skips
204
221
  if (opts.pullOnly) {
@@ -306,7 +323,28 @@ export async function syncFromState(state, statePath, credentials, opts) {
306
323
  }
307
324
  catch { /* ignore */ }
308
325
  }
326
+ // Built-in skills shipped by the CLI on every sync. Named upfront so we can
327
+ // surface them by name in the per-adapter sync log -- helps users (and
328
+ // their agents) confirm that /runwork, /share-conversation, etc. landed.
329
+ const builtInSkills = [
330
+ introSkill,
331
+ buildShareConversationSkill(),
332
+ buildSaveConversationSkill(),
333
+ ];
334
+ const builtInNames = builtInSkills.map(s => s.name);
335
+ // Counters surfaced at the end of the sync as a one-line summary so the
336
+ // user can see at a glance how much actually got written this run.
337
+ const summary = {
338
+ adaptersProcessed: 0,
339
+ adaptersFailed: 0,
340
+ skillWrites: 0,
341
+ skillFilesWritten: 0,
342
+ mcpServerWrites: 0,
343
+ instructionHintWrites: 0,
344
+ hookInstallCalls: 0,
345
+ };
309
346
  for (const adapter of adapters) {
347
+ let adapterFailedAnyScope = false;
310
348
  for (const scope of scopes) {
311
349
  try {
312
350
  // Write skills: for project scope, filter to only the current app's skill.
@@ -316,6 +354,11 @@ export async function syncFromState(state, statePath, credentials, opts) {
316
354
  if (adapter.supportsSkills()) {
317
355
  const skipAppSkills = adapter.mcpProvidesSkills && mcpEntries.length > 0;
318
356
  const scopeSkills = remoteSkills.filter(s => {
357
+ // Built-in CLI skills are written from the canonical built-in array
358
+ // below; a workspace skill with the same name is a shadow that
359
+ // would overwrite the built-in if we let it through. Skip it.
360
+ if (builtInNameSet.has(s.name))
361
+ return false;
319
362
  // App-sourced skills are already available via MCP skill_* tools
320
363
  if (skipAppSkills && s.source === 'app')
321
364
  return false;
@@ -325,29 +368,57 @@ export async function syncFromState(state, statePath, credentials, opts) {
325
368
  }
326
369
  return true;
327
370
  });
328
- const allSkillFiles = [introSkill, ...scopeSkills.map(s => ({
329
- name: s.name,
330
- filename: s.name.toLowerCase().replace(/[^a-z0-9]+/g, '-'),
331
- content: s.content,
332
- description: s.source === 'app'
333
- ? (buildAppSkillDescription(s.name, registries) || `${s.name} - Runwork workspace application`)
334
- : `${s.name} - Runwork workspace skill`,
335
- }))];
371
+ const workspaceSkillFiles = scopeSkills.map(s => ({
372
+ name: s.name,
373
+ filename: s.name.toLowerCase().replace(/[^a-z0-9]+/g, '-'),
374
+ content: s.content,
375
+ description: s.source === 'app'
376
+ ? (buildAppSkillDescription(s.name, registries) || `${s.name} - Runwork workspace application`)
377
+ : `${s.name} - Runwork workspace skill`,
378
+ }));
379
+ const allSkillFiles = [...builtInSkills, ...workspaceSkillFiles];
336
380
  await adapter.writeSkills(allSkillFiles, scope);
381
+ summary.skillWrites++;
382
+ summary.skillFilesWritten += allSkillFiles.length;
383
+ console.log(` [${adapter.name}] Wrote ${allSkillFiles.length} skills (${scope}): ` +
384
+ `${builtInSkills.length} built-in [${builtInNames.join(', ')}], ` +
385
+ `${workspaceSkillFiles.length} workspace`);
386
+ }
387
+ else {
388
+ console.log(` [${adapter.name}] Skipped skills (${scope}): adapter does not support skills`);
337
389
  }
338
390
  // Write MCP configs
339
391
  if (adapter.supportsMcpScope(scope) && mcpEntries.length > 0) {
340
392
  await adapter.writeMcpServers(mcpEntries, scope);
393
+ summary.mcpServerWrites++;
341
394
  console.log(` [${adapter.name}] Updated ${mcpEntries.length} MCP server${mcpEntries.length > 1 ? 's' : ''} (${scope})`);
342
395
  }
396
+ else if (mcpEntries.length === 0) {
397
+ console.log(` [${adapter.name}] Skipped MCP servers (${scope}): no workspace MCP servers configured`);
398
+ }
399
+ else if (!adapter.supportsMcpScope(scope)) {
400
+ console.log(` [${adapter.name}] Skipped MCP servers (${scope}): adapter does not support MCP at this scope`);
401
+ }
343
402
  // Write instruction hint
344
403
  await adapter.writeInstructionHint(instructionHint, scope);
404
+ summary.instructionHintWrites++;
345
405
  console.log(` [${adapter.name}] Updated instruction hints (${scope})`);
406
+ // Install built-in hooks (Claude Code's SessionStart, etc.). Called
407
+ // after writeSkills so the plugin tree already exists. Skipped by
408
+ // adapters that don't implement it.
409
+ if (adapter.writeBuiltInHooks) {
410
+ await adapter.writeBuiltInHooks(scope);
411
+ summary.hookInstallCalls++;
412
+ }
346
413
  }
347
414
  catch (err) {
415
+ adapterFailedAnyScope = true;
348
416
  console.warn(` [${adapter.name}] Failed (${scope}): ${err instanceof Error ? err.message : err}`);
349
417
  }
350
418
  }
419
+ summary.adaptersProcessed++;
420
+ if (adapterFailedAnyScope)
421
+ summary.adaptersFailed++;
351
422
  }
352
423
  // Pull team config from server. A failure here must not stop the
353
424
  // unified user-scope write below, which applies network and minimum-permission
@@ -569,6 +640,20 @@ export async function syncFromState(state, statePath, credentials, opts) {
569
640
  catch {
570
641
  // Telemetry failures are non-fatal
571
642
  }
643
+ // One-line summary so the user (and any agent reading sync output) can
644
+ // see at a glance what got written this run, without scrolling the whole
645
+ // per-adapter log.
646
+ const summaryParts = [];
647
+ summaryParts.push(`${summary.adaptersProcessed} adapter${summary.adaptersProcessed === 1 ? '' : 's'}`);
648
+ if (summary.adaptersFailed > 0)
649
+ summaryParts.push(`${summary.adaptersFailed} failed`);
650
+ summaryParts.push(`${summary.skillWrites} skill write${summary.skillWrites === 1 ? '' : 's'} (${summary.skillFilesWritten} files)`);
651
+ if (summary.mcpServerWrites > 0)
652
+ summaryParts.push(`${summary.mcpServerWrites} MCP config write${summary.mcpServerWrites === 1 ? '' : 's'}`);
653
+ if (summary.hookInstallCalls > 0)
654
+ summaryParts.push(`${summary.hookInstallCalls} hook install${summary.hookInstallCalls === 1 ? '' : 's'}`);
655
+ summaryParts.push(`${summary.instructionHintWrites} instruction hint write${summary.instructionHintWrites === 1 ? '' : 's'}`);
656
+ console.log(`\n Summary: ${summaryParts.join(', ')}.`);
572
657
  }
573
658
  export const syncCommand = new Command('sync')
574
659
  .description('Sync skills bidirectionally and refresh MCP configs from workspace')
@@ -1 +1 @@
1
- export declare const VERSION = "0.12.0";
1
+ export declare const VERSION = "0.13.1";
@@ -1,2 +1,2 @@
1
1
  // Auto-generated by scripts/embed-types.ts -- do not edit
2
- export const VERSION = "0.12.0";
2
+ export const VERSION = "0.13.1";
package/dist/index.js CHANGED
@@ -27,6 +27,10 @@ import { buildPluginCommand } from './commands/build-plugin.js';
27
27
  import { uninstallCommand } from './commands/uninstall.js';
28
28
  import { appsCommand } from './commands/apps.js';
29
29
  import { doctorCommand } from './commands/doctor.js';
30
+ import { shareConvoCommand } from './commands/share-convo.js';
31
+ import { saveConvoCommand } from './commands/save-convo.js';
32
+ import { inboxCommand } from './commands/inbox.js';
33
+ import { resumeCommand } from './commands/resume.js';
30
34
  import { handleGitCredentialRequest } from './git/credentials.js';
31
35
  import { VERSION } from './generated/version.js';
32
36
  import { shouldOutputJson, jsonOut } from './utils/output.js';
@@ -79,6 +83,10 @@ program.addCommand(buildPluginCommand);
79
83
  program.addCommand(uninstallCommand);
80
84
  program.addCommand(appsCommand);
81
85
  program.addCommand(doctorCommand);
86
+ program.addCommand(shareConvoCommand);
87
+ program.addCommand(saveConvoCommand);
88
+ program.addCommand(inboxCommand);
89
+ program.addCommand(resumeCommand);
82
90
  program.addHelpText('after', '\nFor AI agents: run "runwork info --json" to discover app context and available commands.');
83
91
  // JSON help: intercept --help when --json is active or stdout is not a TTY.
84
92
  // Outputs the full command tree as structured JSON for AI agents.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runwork",
3
- "version": "0.12.0",
3
+ "version": "0.13.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)",