runwork 0.20.0 → 0.21.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.
@@ -370,8 +370,8 @@ export declare class WorkspaceContext {
370
370
  /**
371
371
  * Send a notification to a workspace user
372
372
  * Can only send to verified workspace members
373
- * The platform wraps your content in a standard notification template
374
- * (e.g., "You have a new notification from [App Name]")
373
+ * The platform wraps your content in a standard notification template and
374
+ * sends it from your app's name, using `title` as the subject line
375
375
  *
376
376
  * @example
377
377
  * ```typescript
package/dist/index.js CHANGED
@@ -4804,8 +4804,8 @@ export declare class WorkspaceContext {
4804
4804
  /**
4805
4805
  * Send a notification to a workspace user
4806
4806
  * Can only send to verified workspace members
4807
- * The platform wraps your content in a standard notification template
4808
- * (e.g., "You have a new notification from [App Name]")
4807
+ * The platform wraps your content in a standard notification template and
4808
+ * sends it from your app's name, using \`title\` as the subject line
4809
4809
  *
4810
4810
  * @example
4811
4811
  * \`\`\`typescript
@@ -7645,7 +7645,7 @@ function createKeyboardListener() {
7645
7645
  }
7646
7646
 
7647
7647
  // src/generated/version.ts
7648
- var VERSION = "0.20.0";
7648
+ var VERSION = "0.21.0";
7649
7649
 
7650
7650
  // src/commands/dev.ts
7651
7651
  var exports_dev = {};
@@ -10742,6 +10742,110 @@ init_store();
10742
10742
  init_client();
10743
10743
  import { Command as Command13 } from "commander";
10744
10744
  import { readFileSync as readFileSync22, existsSync as existsSync25 } from "fs";
10745
+ // ../../shared/skill/skill-canonical.ts
10746
+ function toSkillSlug(value) {
10747
+ return value.trim().replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
10748
+ }
10749
+ function quoteYamlValue(value) {
10750
+ if (value === "")
10751
+ return '""';
10752
+ if (/[:#[\]{}|>&*!,?'"]/.test(value) || value !== value.trim()) {
10753
+ const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
10754
+ return `"${escaped}"`;
10755
+ }
10756
+ return value;
10757
+ }
10758
+ function unquoteYamlValue(raw) {
10759
+ const value = raw.trim();
10760
+ if (value.length >= 2) {
10761
+ const first = value[0];
10762
+ const last = value[value.length - 1];
10763
+ if (first === '"' && last === '"') {
10764
+ return value.slice(1, -1).replace(/\\(["\\])/g, "$1");
10765
+ }
10766
+ if (first === "'" && last === "'") {
10767
+ return value.slice(1, -1);
10768
+ }
10769
+ }
10770
+ return value;
10771
+ }
10772
+ function parseSkillMd(content) {
10773
+ const lines = content.split(`
10774
+ `);
10775
+ if (lines[0]?.trim() !== "---") {
10776
+ const rawName = lines[0]?.replace(/^#\s*/, "").trim() || "Untitled Skill";
10777
+ return {
10778
+ frontmatter: {
10779
+ name: toSkillSlug(rawName) || "untitled-skill",
10780
+ description: ""
10781
+ },
10782
+ orderedKeys: [],
10783
+ body: content,
10784
+ hadFrontmatter: false
10785
+ };
10786
+ }
10787
+ let closingIndex = -1;
10788
+ for (let i = 1;i < lines.length; i++) {
10789
+ if (lines[i]?.trim() === "---") {
10790
+ closingIndex = i;
10791
+ break;
10792
+ }
10793
+ }
10794
+ if (closingIndex === -1) {
10795
+ return {
10796
+ frontmatter: { name: "untitled-skill", description: "" },
10797
+ orderedKeys: [],
10798
+ body: content,
10799
+ hadFrontmatter: false
10800
+ };
10801
+ }
10802
+ const frontmatterEntries = {};
10803
+ const orderedKeys = [];
10804
+ for (let i = 1;i < closingIndex; i++) {
10805
+ const line = lines[i];
10806
+ if (!line)
10807
+ continue;
10808
+ const colonIndex = line.indexOf(":");
10809
+ if (colonIndex > 0) {
10810
+ const key = line.slice(0, colonIndex).trim();
10811
+ const value = unquoteYamlValue(line.slice(colonIndex + 1));
10812
+ if (!(key in frontmatterEntries))
10813
+ orderedKeys.push(key);
10814
+ frontmatterEntries[key] = value;
10815
+ }
10816
+ }
10817
+ const body = lines.slice(closingIndex + 1).join(`
10818
+ `).trim();
10819
+ return {
10820
+ frontmatter: {
10821
+ ...frontmatterEntries,
10822
+ name: toSkillSlug(frontmatterEntries.name || "") || "untitled-skill",
10823
+ description: frontmatterEntries.description || ""
10824
+ },
10825
+ orderedKeys,
10826
+ body,
10827
+ hadFrontmatter: true
10828
+ };
10829
+ }
10830
+ function buildSkillMd(parts) {
10831
+ const lines = [
10832
+ "---",
10833
+ `name: ${toSkillSlug(parts.name) || "untitled-skill"}`,
10834
+ `description: ${quoteYamlValue(parts.description ?? "")}`
10835
+ ];
10836
+ if (parts.extra) {
10837
+ for (const [key, value] of Object.entries(parts.extra)) {
10838
+ if (key === "name" || key === "description")
10839
+ continue;
10840
+ lines.push(`${key}: ${quoteYamlValue(value)}`);
10841
+ }
10842
+ }
10843
+ lines.push("---", "", (parts.body ?? "").trim());
10844
+ return lines.join(`
10845
+ `);
10846
+ }
10847
+
10848
+ // src/commands/skills.ts
10745
10849
  function truncate(text2, max) {
10746
10850
  if (!text2)
10747
10851
  return "";
@@ -10808,6 +10912,23 @@ async function readStdin2() {
10808
10912
  }
10809
10913
  return Buffer.concat(chunks).toString("utf-8");
10810
10914
  }
10915
+ function buildSkillPushPayload(fileContent, nameArg) {
10916
+ const parsed = parseSkillMd(fileContent);
10917
+ const docName = parsed.hadFrontmatter && parsed.frontmatter.name !== "untitled-skill" ? parsed.frontmatter.name : "";
10918
+ const name = toSkillSlug(nameArg || "") || docName;
10919
+ if (!name)
10920
+ return null;
10921
+ const description = parsed.hadFrontmatter ? parsed.frontmatter.description : "";
10922
+ const extra = {};
10923
+ for (const key of parsed.orderedKeys) {
10924
+ if (key === "name" || key === "description")
10925
+ continue;
10926
+ const value = parsed.frontmatter[key];
10927
+ if (typeof value === "string")
10928
+ extra[key] = value;
10929
+ }
10930
+ return { name, description, content: buildSkillMd({ name, description, body: parsed.body, extra }) };
10931
+ }
10811
10932
  var pushCommand = new Command13("push").description("Upload a local skill file to workspace (upsert by name). Accepts piped content via stdin.").argument("[first]", "Skill name (if piping content) or file path (name from frontmatter)").argument("[second]", "File path (when first arg is the skill name)").option("--workspace <name-or-id>", "Workspace name or ID").action(async (first, second, opts, command) => {
10812
10933
  const useJson = shouldOutputJson(command.optsWithGlobals().json);
10813
10934
  const credentials = requireAuth();
@@ -10849,35 +10970,22 @@ var pushCommand = new Command13("push").description("Upload a local skill file t
10849
10970
  }
10850
10971
  }
10851
10972
  try {
10852
- const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
10853
- let name = nameArg || "";
10854
- let description = "";
10855
- let body = content;
10856
- if (frontmatterMatch) {
10857
- const fm = frontmatterMatch[1];
10858
- body = frontmatterMatch[2];
10859
- const descMatch = fm.match(/^description:\s*(.+)$/m);
10860
- if (descMatch)
10861
- description = descMatch[1].trim();
10862
- if (!name) {
10863
- const nameMatch = fm.match(/^name:\s*(.+)$/m);
10864
- if (nameMatch)
10865
- name = nameMatch[1].trim();
10866
- }
10867
- }
10868
- if (!name) {
10973
+ const payload = buildSkillPushPayload(content, nameArg);
10974
+ if (!payload) {
10869
10975
  console.error(`Skill name required. Provide as first argument or in YAML frontmatter:
10870
10976
  runwork skills push "My Skill" ./skill.md
10871
10977
  cat skill.md | runwork skills push "My Skill"`);
10872
10978
  process.exit(1);
10979
+ return;
10873
10980
  }
10981
+ const { name, description, content: document } = payload;
10874
10982
  const existing = await client.listExternalSkills(workspaceId);
10875
10983
  const match = existing.find((s) => s.name === name);
10876
10984
  if (match) {
10877
10985
  const updated = await client.updateExternalSkill(workspaceId, match.id, {
10878
10986
  name,
10879
10987
  description,
10880
- content: body
10988
+ content: document
10881
10989
  });
10882
10990
  if (useJson) {
10883
10991
  jsonOut({ skill: updated, action: "updated" });
@@ -10888,7 +10996,7 @@ var pushCommand = new Command13("push").description("Upload a local skill file t
10888
10996
  const created = await client.importExternalSkill(workspaceId, {
10889
10997
  name,
10890
10998
  description,
10891
- content: body,
10999
+ content: document,
10892
11000
  importedFrom: "cli"
10893
11001
  });
10894
11002
  if (useJson) {
@@ -11095,109 +11203,6 @@ import { chmodSync, existsSync as existsSync30, mkdirSync as mkdirSync16, readFi
11095
11203
  import { join as join23 } from "path";
11096
11204
  import { homedir as homedir6, platform as platform2 } from "os";
11097
11205
 
11098
- // ../../shared/skill/skill-canonical.ts
11099
- function toSkillSlug(value) {
11100
- return value.trim().replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
11101
- }
11102
- function quoteYamlValue(value) {
11103
- if (value === "")
11104
- return '""';
11105
- if (/[:#[\]{}|>&*!,?'"]/.test(value) || value !== value.trim()) {
11106
- const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
11107
- return `"${escaped}"`;
11108
- }
11109
- return value;
11110
- }
11111
- function unquoteYamlValue(raw) {
11112
- const value = raw.trim();
11113
- if (value.length >= 2) {
11114
- const first = value[0];
11115
- const last = value[value.length - 1];
11116
- if (first === '"' && last === '"') {
11117
- return value.slice(1, -1).replace(/\\(["\\])/g, "$1");
11118
- }
11119
- if (first === "'" && last === "'") {
11120
- return value.slice(1, -1);
11121
- }
11122
- }
11123
- return value;
11124
- }
11125
- function parseSkillMd(content) {
11126
- const lines = content.split(`
11127
- `);
11128
- if (lines[0]?.trim() !== "---") {
11129
- const rawName = lines[0]?.replace(/^#\s*/, "").trim() || "Untitled Skill";
11130
- return {
11131
- frontmatter: {
11132
- name: toSkillSlug(rawName) || "untitled-skill",
11133
- description: ""
11134
- },
11135
- orderedKeys: [],
11136
- body: content,
11137
- hadFrontmatter: false
11138
- };
11139
- }
11140
- let closingIndex = -1;
11141
- for (let i = 1;i < lines.length; i++) {
11142
- if (lines[i]?.trim() === "---") {
11143
- closingIndex = i;
11144
- break;
11145
- }
11146
- }
11147
- if (closingIndex === -1) {
11148
- return {
11149
- frontmatter: { name: "untitled-skill", description: "" },
11150
- orderedKeys: [],
11151
- body: content,
11152
- hadFrontmatter: false
11153
- };
11154
- }
11155
- const frontmatterEntries = {};
11156
- const orderedKeys = [];
11157
- for (let i = 1;i < closingIndex; i++) {
11158
- const line = lines[i];
11159
- if (!line)
11160
- continue;
11161
- const colonIndex = line.indexOf(":");
11162
- if (colonIndex > 0) {
11163
- const key = line.slice(0, colonIndex).trim();
11164
- const value = unquoteYamlValue(line.slice(colonIndex + 1));
11165
- if (!(key in frontmatterEntries))
11166
- orderedKeys.push(key);
11167
- frontmatterEntries[key] = value;
11168
- }
11169
- }
11170
- const body = lines.slice(closingIndex + 1).join(`
11171
- `).trim();
11172
- return {
11173
- frontmatter: {
11174
- ...frontmatterEntries,
11175
- name: toSkillSlug(frontmatterEntries.name || "") || "untitled-skill",
11176
- description: frontmatterEntries.description || ""
11177
- },
11178
- orderedKeys,
11179
- body,
11180
- hadFrontmatter: true
11181
- };
11182
- }
11183
- function buildSkillMd(parts) {
11184
- const lines = [
11185
- "---",
11186
- `name: ${toSkillSlug(parts.name) || "untitled-skill"}`,
11187
- `description: ${quoteYamlValue(parts.description ?? "")}`
11188
- ];
11189
- if (parts.extra) {
11190
- for (const [key, value] of Object.entries(parts.extra)) {
11191
- if (key === "name" || key === "description")
11192
- continue;
11193
- lines.push(`${key}: ${quoteYamlValue(value)}`);
11194
- }
11195
- }
11196
- lines.push("---", "", (parts.body ?? "").trim());
11197
- return lines.join(`
11198
- `);
11199
- }
11200
-
11201
11206
  // src/agents/types.ts
11202
11207
  var RUNWORK_MCP_PREFIX = "Runwork: ";
11203
11208
  var RUNWORK_MCP_PREFIX_LEGACY = "runwork-";
@@ -18051,7 +18056,52 @@ function summarizeTelemetryForDryRun(result) {
18051
18056
  return `Telemetry preview: ${plural(result.events.length, "event")} would be sent (${plural(activeCount, "adapter")} with activity). Use --verbose for details.`;
18052
18057
  }
18053
18058
 
18054
- // src/agents/intro-skill.ts
18059
+ // ../../shared/agent-instructions/runwork-instructions.ts
18060
+ function formatList(items, max = 8) {
18061
+ if (items.length <= max)
18062
+ return items.join(", ");
18063
+ return `${items.slice(0, max).join(", ")} + ${items.length - max} more`;
18064
+ }
18065
+ function plural(n, one, many) {
18066
+ return n > 1 ? many : one;
18067
+ }
18068
+ function pushGroup(parts, group, one, many) {
18069
+ if (group.count <= 0)
18070
+ return;
18071
+ const label = `${group.count} ${plural(group.count, one, many)}`;
18072
+ parts.push(group.names.length > 0 ? `${label} (${formatList(group.names, 3)})` : label);
18073
+ }
18074
+ function buildInventoryLine(inv) {
18075
+ const parts = [];
18076
+ if (inv.appCount > 0)
18077
+ parts.push(`${inv.appCount} ${plural(inv.appCount, "app", "apps")}`);
18078
+ if (inv.entityNames.length > 0) {
18079
+ const n = inv.entityNames.length;
18080
+ parts.push(`${n} ${plural(n, "entity", "entities")} (${formatList(inv.entityNames, 5)})`);
18081
+ }
18082
+ if (inv.fileStorageCount > 0) {
18083
+ parts.push(`${inv.fileStorageCount} ${plural(inv.fileStorageCount, "file storage", "file storages")}`);
18084
+ }
18085
+ pushGroup(parts, inv.schedules, "schedule", "schedules");
18086
+ pushGroup(parts, inv.workflows, "workflow", "workflows");
18087
+ pushGroup(parts, inv.agents, "agent", "agents");
18088
+ if (inv.endpointCount > 0) {
18089
+ parts.push(`${inv.endpointCount} ${plural(inv.endpointCount, "endpoint", "endpoints")}`);
18090
+ }
18091
+ if (inv.componentCount > 0) {
18092
+ parts.push(`${inv.componentCount} ${plural(inv.componentCount, "component", "components")}`);
18093
+ }
18094
+ if (inv.integrationIds.length > 0) {
18095
+ const n = inv.integrationIds.length;
18096
+ parts.push(`${n} ${plural(n, "integration", "integrations")} (${formatList(inv.integrationIds, 5)})`);
18097
+ }
18098
+ if (inv.skillCount > 0)
18099
+ parts.push(`${inv.skillCount} ${plural(inv.skillCount, "skill", "skills")}`);
18100
+ if (inv.mcpServerCount > 0) {
18101
+ parts.push(`${inv.mcpServerCount} MCP ${plural(inv.mcpServerCount, "server", "servers")}`);
18102
+ }
18103
+ return parts.length > 0 ? parts.join(", ") : "no resources deployed yet";
18104
+ }
18055
18105
  function buildPersonaBlock(persona) {
18056
18106
  if (!persona || persona.level === 3)
18057
18107
  return [];
@@ -18080,57 +18130,65 @@ function buildPersonaBlock(persona) {
18080
18130
  "- Keep explanations concise and outcome-focused."
18081
18131
  ];
18082
18132
  }
18083
- function formatList(items, max = 8) {
18084
- if (items.length <= max)
18085
- return items.join(", ");
18086
- return `${items.slice(0, max).join(", ")} + ${items.length - max} more`;
18133
+ var ROUTING_TABLE = [
18134
+ "| User asks for | Use Runwork | Don't |",
18135
+ "|---|---|---|",
18136
+ "| save / create / share / update a skill | `save_skill` MCP tool | local SKILL.md files, prompt snippets |",
18137
+ "| store, persist, or remember structured data | `entity_{Name}` MCP tools | local SQLite, JSON files, in-memory |",
18138
+ "| schedule a recurring or cron job | Runwork schedules (`schedule_{name}`, `runwork schedules`) | OS cron, `setInterval`, Celery |",
18139
+ "| multi-step process, pipeline, or durable workflow | Runwork workflows (`workflow_{name}`) | ad-hoc scripts, shell pipelines |",
18140
+ "| call Slack / HubSpot / Gmail / Google / any third-party API | `{id}_api` MCP tools (e.g. `hubspot_api`) | raw `fetch`, hand-built auth, SDK installs |",
18141
+ "| upload, save, or serve a file | Runwork file storage (`list_bucket_files`, `generate_user_presigned_url`) | local filesystem, `fs.writeFile` |",
18142
+ "| expose a public API / webhook endpoint | Runwork endpoints (`endpoint_*`) | standalone Express/Flask servers |",
18143
+ "| share AI prompts, instructions, or expertise with the team | `save_skill` MCP tool | local prompt files, copy-paste |",
18144
+ "| connect an AI tool or MCP server | Runwork MCP servers | manual `settings.json` edits per machine |",
18145
+ "| build a new internal tool, team app, or automation | `runwork init` -> `runwork dev` -> `runwork deploy` | one-off scripts, notebooks |"
18146
+ ];
18147
+ var PRODUCT_DEV_EXCEPTION = "**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.";
18148
+ function buildRunworkInstructions(ctx) {
18149
+ const { workspaceName, workspaceId, workspaceSlug, inventory, surface = "machine" } = ctx;
18150
+ const dashboardUrl = workspaceSlug ? `https://${workspaceSlug}.runwork.ai` : "https://runwork.ai";
18151
+ const workspaceIdLine = `Workspace ID: \`${workspaceId}\`${workspaceSlug ? ` | Dashboard: ${dashboardUrl}` : ""}`;
18152
+ const connection = surface === "mcp" ? `You are connected to the **${workspaceName}** Runwork workspace` : `This machine is connected to the **${workspaceName}** Runwork workspace`;
18153
+ const integrationToolHint = inventory.integrationIds.length > 0 ? ` Connected integrations are callable via MCP tools (${inventory.integrationIds.map((id) => `\`${id}_api\``).join(", ")}) or CLI (\`runwork integrations call <id> <method> <path>\`).` : "";
18154
+ const lines = [
18155
+ "## Runwork",
18156
+ "",
18157
+ `${connection} -- your team's shared platform for building and running infrastructure: apps, data & file storage, scheduled jobs, workflows, integrations, AI agents, skills, MCP servers, public APIs, and reusable components.`,
18158
+ "",
18159
+ workspaceIdLine,
18160
+ ""
18161
+ ];
18162
+ lines.push("**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.", "", ...ROUTING_TABLE, "", PRODUCT_DEV_EXCEPTION, "", `**Available now:** ${buildInventoryLine(inventory)}`, "", `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.`, ...buildPersonaBlock(ctx.persona));
18163
+ return lines.join(`
18164
+ `);
18087
18165
  }
18088
- function buildInventoryLine(ctx) {
18089
- const parts = [];
18090
- if (ctx.appCount > 0)
18091
- parts.push(`${ctx.appCount} app${ctx.appCount > 1 ? "s" : ""}`);
18092
- if (ctx.registries) {
18093
- const reg = ctx.registries;
18094
- if (reg.entities.length > 0) {
18095
- const uniqueNames = [...new Set(reg.entities.map((e) => e.entityName))];
18096
- parts.push(`${uniqueNames.length} entit${uniqueNames.length > 1 ? "ies" : "y"} (${formatList(uniqueNames, 5)})`);
18097
- }
18098
- if (reg.fileStorages.length > 0)
18099
- parts.push(`${reg.fileStorages.length} file storage${reg.fileStorages.length > 1 ? "s" : ""}`);
18100
- if (reg.schedules.length > 0) {
18101
- const names = reg.schedules.map((s) => s.name).filter(Boolean);
18102
- if (names.length > 0)
18103
- parts.push(`${reg.schedules.length} schedule${reg.schedules.length > 1 ? "s" : ""} (${formatList(names, 3)})`);
18104
- else
18105
- parts.push(`${reg.schedules.length} schedule${reg.schedules.length > 1 ? "s" : ""}`);
18106
- }
18107
- if (reg.workflows.length > 0) {
18108
- const names = reg.workflows.map((w) => w.name).filter(Boolean);
18109
- if (names.length > 0)
18110
- parts.push(`${reg.workflows.length} workflow${reg.workflows.length > 1 ? "s" : ""} (${formatList(names, 3)})`);
18111
- else
18112
- parts.push(`${reg.workflows.length} workflow${reg.workflows.length > 1 ? "s" : ""}`);
18113
- }
18114
- if (reg.agents.length > 0) {
18115
- const names = reg.agents.map((a) => a.name).filter(Boolean);
18116
- if (names.length > 0)
18117
- parts.push(`${reg.agents.length} agent${reg.agents.length > 1 ? "s" : ""} (${formatList(names, 3)})`);
18118
- else
18119
- parts.push(`${reg.agents.length} agent${reg.agents.length > 1 ? "s" : ""}`);
18120
- }
18121
- if (reg.endpoints.length > 0)
18122
- parts.push(`${reg.endpoints.length} endpoint${reg.endpoints.length > 1 ? "s" : ""}`);
18123
- if (reg.components.length > 0)
18124
- parts.push(`${reg.components.length} component${reg.components.length > 1 ? "s" : ""}`);
18125
- }
18126
- if (ctx.connectedIntegrations.length > 0) {
18127
- parts.push(`${ctx.connectedIntegrations.length} integration${ctx.connectedIntegrations.length > 1 ? "s" : ""} (${formatList(ctx.connectedIntegrations, 5)})`);
18128
- }
18129
- if (ctx.skillCount > 0)
18130
- parts.push(`${ctx.skillCount} skill${ctx.skillCount > 1 ? "s" : ""}`);
18131
- if (ctx.mcpServerCount > 0)
18132
- parts.push(`${ctx.mcpServerCount} MCP server${ctx.mcpServerCount > 1 ? "s" : ""}`);
18133
- return parts.length > 0 ? parts.join(", ") : "no resources deployed yet";
18166
+
18167
+ // src/agents/intro-skill.ts
18168
+ function toRunworkInventory(ctx) {
18169
+ const reg = ctx.registries;
18170
+ return {
18171
+ appCount: ctx.appCount,
18172
+ entityNames: reg ? [...new Set(reg.entities.map((e) => e.entityName))] : [],
18173
+ fileStorageCount: reg?.fileStorages.length ?? 0,
18174
+ schedules: {
18175
+ count: reg?.schedules.length ?? 0,
18176
+ names: reg?.schedules.map((s) => s.name).filter(Boolean) ?? []
18177
+ },
18178
+ workflows: {
18179
+ count: reg?.workflows.length ?? 0,
18180
+ names: reg?.workflows.map((w) => w.name).filter(Boolean) ?? []
18181
+ },
18182
+ agents: {
18183
+ count: reg?.agents.length ?? 0,
18184
+ names: reg?.agents.map((a) => a.name).filter(Boolean) ?? []
18185
+ },
18186
+ endpointCount: reg?.endpoints.length ?? 0,
18187
+ componentCount: reg?.components.length ?? 0,
18188
+ integrationIds: ctx.connectedIntegrations,
18189
+ skillCount: ctx.skillCount,
18190
+ mcpServerCount: ctx.mcpServerCount
18191
+ };
18134
18192
  }
18135
18193
  function buildAppSkillDescription(appName, registries) {
18136
18194
  if (!registries)
@@ -18423,39 +18481,17 @@ function generateIntroSkill(ctx) {
18423
18481
  };
18424
18482
  }
18425
18483
  function generateInstructionHint(ctx) {
18426
- const inventory = buildInventoryLine(ctx);
18427
- const integrationToolHint = ctx.connectedIntegrations.length > 0 ? ` Connected integrations are callable via MCP tools (${ctx.connectedIntegrations.map((id) => `\`${id}_api\``).join(", ")}) or CLI (\`runwork integrations call <id> <method> <path>\`).` : "";
18428
- const dashboardUrl = ctx.workspaceSlug ? `https://${ctx.workspaceSlug}.runwork.ai` : "https://runwork.ai";
18429
- const workspaceIdLine = `Workspace ID: \`${ctx.workspaceId}\`${ctx.workspaceSlug ? ` | Dashboard: ${dashboardUrl}` : ""}`;
18484
+ const body = buildRunworkInstructions({
18485
+ workspaceName: ctx.workspaceName,
18486
+ workspaceId: ctx.workspaceId,
18487
+ workspaceSlug: ctx.workspaceSlug,
18488
+ inventory: toRunworkInventory(ctx),
18489
+ persona: ctx.persona,
18490
+ surface: "machine"
18491
+ });
18430
18492
  return [
18431
18493
  "<!-- runwork:start -->",
18432
- "## Runwork",
18433
- "",
18434
- `This machine is connected to the **${ctx.workspaceName}** Runwork workspace -- your team's shared platform for building and running infrastructure: apps, data & file storage, scheduled jobs, workflows, integrations, AI agents, skills, MCP servers, public APIs, and reusable components.`,
18435
- "",
18436
- workspaceIdLine,
18437
- "",
18438
- "**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.",
18439
- "",
18440
- "| User asks for | Use Runwork | Don't |",
18441
- "|---|---|---|",
18442
- "| save / create / share / update a skill | `save_skill` MCP tool | local SKILL.md files, prompt snippets |",
18443
- "| store, persist, or remember structured data | `entity_{Name}` MCP tools | local SQLite, JSON files, in-memory |",
18444
- "| schedule a recurring or cron job | Runwork schedules (`schedule_{name}`, `runwork schedules`) | OS cron, `setInterval`, Celery |",
18445
- "| multi-step process, pipeline, or durable workflow | Runwork workflows (`workflow_{name}`) | ad-hoc scripts, shell pipelines |",
18446
- "| call Slack / HubSpot / Gmail / Google / any third-party API | `{id}_api` MCP tools (e.g. `hubspot_api`) | raw `fetch`, hand-built auth, SDK installs |",
18447
- "| upload, save, or serve a file | Runwork file storage (`list_bucket_files`, `generate_user_presigned_url`) | local filesystem, `fs.writeFile` |",
18448
- "| expose a public API / webhook endpoint | Runwork endpoints (`endpoint_*`) | standalone Express/Flask servers |",
18449
- "| share AI prompts, instructions, or expertise with the team | `save_skill` MCP tool | local prompt files, copy-paste |",
18450
- "| connect an AI tool or MCP server | Runwork MCP servers | manual `settings.json` edits per machine |",
18451
- "| build a new internal tool, team app, or automation | `runwork init` -> `runwork dev` -> `runwork deploy` | one-off scripts, notebooks |",
18452
- "",
18453
- "**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.",
18454
- "",
18455
- `**Available now:** ${inventory}`,
18456
- "",
18457
- `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.`,
18458
- ...buildPersonaBlock(ctx.persona),
18494
+ body,
18459
18495
  "<!-- runwork:end -->"
18460
18496
  ].join(`
18461
18497
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runwork",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
4
4
  "description": "CLI for Runwork: develop, preview, and deploy Runwork apps from your local machine.",
5
5
  "license": "UNLICENSED",
6
6
  "author": "Runwork, Inc. <info@runwork.ai> (https://www.runwork.ai)",