diff --git a/.github/workflows/publish-sim-skills.yml b/.github/workflows/publish-sim-skills.yml new file mode 100644 index 00000000000..468b96060b1 --- /dev/null +++ b/.github/workflows/publish-sim-skills.yml @@ -0,0 +1,148 @@ +name: Publish Sim Skills Package + +on: + push: + branches: [main, staging, dev] + paths: + - 'packages/sim-skills/**' + +permissions: + contents: read + +concurrency: + group: publish-sim-skills-${{ github.ref }} + cancel-in-progress: false + +jobs: + publish-npm: + runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }} + timeout-minutes: 15 + steps: + - name: Checkout repository + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + + - name: Setup Bun + uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 + with: + bun-version: 1.3.14 + + - name: Setup Node + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 + with: + node-version: '22.20.0' + + - name: Cache Bun dependencies + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 + with: + path: | + ~/.bun/install/cache + node_modules + **/node_modules + key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }} + restore-keys: | + ${{ runner.os }}-bun- + + - name: Install dependencies + run: bun install --frozen-lockfile --ignore-scripts + + - name: Verify npm authentication + env: + NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }} + run: bun pm whoami + + - name: Validate skills + working-directory: packages/sim-skills + run: bun run check:skills + + - name: Run tests + working-directory: packages/sim-skills + run: bun run test + + - name: Type-check package + working-directory: packages/sim-skills + run: bun run type-check + + - name: Build package + working-directory: packages/sim-skills + run: bun run build + + - name: Resolve release channel + id: release + working-directory: packages/sim-skills + env: + BRANCH: ${{ github.ref_name }} + run: | + BASE_VERSION="$(bun -p "require('./package.json').version")" + if [[ ! "$BASE_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Package version must be a stable X.Y.Z base, got '$BASE_VERSION'." >&2 + exit 1 + fi + + case "$BRANCH" in + dev) + VERSION="${BASE_VERSION}-dev.${GITHUB_RUN_NUMBER}.${GITHUB_RUN_ATTEMPT}" + TAG="dev" + ;; + staging) + VERSION="${BASE_VERSION}-preview.${GITHUB_RUN_NUMBER}.${GITHUB_RUN_ATTEMPT}" + TAG="staging" + ;; + main) + VERSION="$BASE_VERSION" + TAG="latest" + ;; + *) + echo "Unsupported release branch '$BRANCH'." >&2 + exit 1 + ;; + esac + + bun pm pkg set "version=$VERSION" + RESOLVED_VERSION="$(bun -p "require('./package.json').version")" + if [ "$RESOLVED_VERSION" != "$VERSION" ]; then + echo "Version injection mismatch: wanted '$VERSION', got '$RESOLVED_VERSION'." >&2 + exit 1 + fi + + { + echo "version=$VERSION" + echo "tag=$TAG" + } >> "$GITHUB_OUTPUT" + + - name: Smoke-test packed skill bundle + working-directory: packages/sim-skills + run: | + set -euo pipefail + SMOKE_DIR="$(mktemp -d "$RUNNER_TEMP/sim-skills-smoke.XXXXXX")" + PACKAGE_PATH="$SMOKE_DIR/sim-skills.tgz" + bun pm pack --ignore-scripts --filename "$PACKAGE_PATH" --quiet + tar -xzf "$PACKAGE_PATH" -C "$SMOKE_DIR" + test -x "$SMOKE_DIR/package/dist/index.js" + test -f "$SMOKE_DIR/package/skills/build-sim-workflow/SKILL.md" + test -f "$SMOKE_DIR/package/skills/run-sim-workflow/SKILL.md" + test -f "$SMOKE_DIR/package/skills/deploy-sim-workflow/SKILL.md" + test -f "$SMOKE_DIR/package/skills/sim-table/SKILL.md" + test -f "$SMOKE_DIR/package/skills/sim-knowledge-base/SKILL.md" + + - name: Verify version is unpublished + working-directory: packages/sim-skills + env: + VERSION: ${{ steps.release.outputs.version }} + run: | + if bun pm view "sim-skills@$VERSION" version > /dev/null 2>&1; then + echo "sim-skills@$VERSION is already published. Bump packages/sim-skills/package.json before releasing another build." >&2 + exit 1 + fi + + - name: Publish to npm + working-directory: packages/sim-skills + env: + NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }} + NPM_TAG: ${{ steps.release.outputs.tag }} + run: bun publish --access public --tag "$NPM_TAG" --no-save + + - name: Summarize release + env: + VERSION: ${{ steps.release.outputs.version }} + NPM_TAG: ${{ steps.release.outputs.tag }} + run: echo "Published sim-skills@$VERSION with the '$NPM_TAG' tag." diff --git a/bun.lock b/bun.lock index b31e872b365..b81dbb61261 100644 --- a/bun.lock +++ b/bun.lock @@ -646,6 +646,22 @@ "vitest": "^4.1.0", }, }, + "packages/sim-skills": { + "name": "sim-skills", + "version": "0.1.0", + "bin": { + "sim-skills": "dist/index.js", + }, + "dependencies": { + "skills": "1.5.22", + }, + "devDependencies": { + "@sim/tsconfig": "workspace:*", + "@types/node": "24.2.1", + "typescript": "^7.0.2", + "vitest": "^4.1.0", + }, + }, "packages/terminal-protocol": { "name": "@sim/terminal-protocol", "version": "0.1.0", @@ -4235,6 +4251,8 @@ "sim-setup": ["sim-setup@workspace:packages/sim-setup"], + "sim-skills": ["sim-skills@workspace:packages/sim-skills"], + "simple-update-notifier": ["simple-update-notifier@2.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w=="], "simstudio": ["simstudio@workspace:packages/cli"], @@ -4243,6 +4261,8 @@ "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], + "skills": ["skills@1.5.22", "", { "dependencies": { "tar": "^7.5.20", "yaml": "^2.8.3" }, "bin": { "skills": "bin/cli.mjs", "add-skill": "bin/cli.mjs" } }, "sha512-cHiLjwZEawWFvudIqeeMZlvZayTLbRouydMbblyrdiyH7ZLbqUrSrEEr+Tg+X265iztRlVMsyOYRwpD5JxBsvg=="], + "slice-ansi": ["slice-ansi@5.0.0", "", { "dependencies": { "ansi-styles": "^6.0.0", "is-fullwidth-code-point": "^4.0.0" } }, "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ=="], "slug": ["slug@6.1.0", "", {}, "sha512-x6vLHCMasg4DR2LPiyFGI0gJJhywY6DTiGhCrOMzb3SOk/0JVLIaL4UhyFSHu04SD3uAavrKY/K3zZ3i6iRcgA=="], diff --git a/packages/sim-skills/LICENSE b/packages/sim-skills/LICENSE new file mode 100644 index 00000000000..f4e76aaaac1 --- /dev/null +++ b/packages/sim-skills/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2026 Sim Studio, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/sim-skills/README.md b/packages/sim-skills/README.md new file mode 100644 index 00000000000..08be6a8b861 --- /dev/null +++ b/packages/sim-skills/README.md @@ -0,0 +1,52 @@ +# sim-skills + +Official skills-only `sim` plugin for building workflows and operating Sim resources through the +Sim CLI. The same skills can also be installed directly with the standard Agent Skills installer. + +## Install + +Run the interactive installer: + +```bash +bunx sim-skills +``` + +Install one skill globally for a specific agent: + +```bash +bunx sim-skills --skill build-workflow --agent codex --global --yes +``` + +List the bundled skills without installing them: + +```bash +bunx sim-skills --list +``` + +All options after `sim-skills` are forwarded to the standard Agent Skills installer. `install` and +`add` are optional aliases, so `bunx sim-skills install --list` is equivalent to the last example. + +For native plugin development, point the host at the `sim/` directory. The manifests in +`.codex-plugin/` and `.claude-plugin/` both declare `sim` as the plugin namespace. + +## Namespace + +Native plugin installs expose the skills under the `sim` namespace: + +- `sim:build-workflow` +- `sim:run-workflow` +- `sim:deploy-workflow` +- `sim:table` +- `sim:knowledge-base` + +Direct installs through `bunx sim-skills` install the selected skills without the plugin prefix. + +## Included skills + +- `build-workflow` — discover blocks and author a draft graph with atomic workflow operations. +- `run-workflow` — test saved state, exercise triggers, resume from a block, and diagnose runs. +- `deploy-workflow` — publish and manage workflows as APIs, chats, or MCP tools. +- `table` — design typed tables, load and query rows, import data, and run workflow groups. +- `knowledge-base` — ingest and index documents, configure connectors and tags, and verify retrieval. + +The skills assume the `sim` CLI is installed and authenticated. They never store or print API keys. diff --git a/packages/sim-skills/package.json b/packages/sim-skills/package.json new file mode 100644 index 00000000000..47340f8bf5b --- /dev/null +++ b/packages/sim-skills/package.json @@ -0,0 +1,60 @@ +{ + "name": "sim-skills", + "version": "0.1.0", + "description": "Official Sim plugin and Agent Skills for building workflows and operating Sim resources", + "type": "module", + "bin": { + "sim-skills": "dist/index.js" + }, + "scripts": { + "prebuild": "bun run clean", + "build": "bun build src/index.ts --target=node --format=esm --packages=external --outfile=dist/index.js", + "clean": "bun -e \"import { rmSync } from 'node:fs'; rmSync('dist', { recursive: true, force: true })\"", + "type-check": "tsc --noEmit", + "lint": "biome check --write --unsafe .", + "lint:check": "biome check .", + "format": "biome format --write .", + "format:check": "biome format .", + "test": "vitest run", + "check:skills": "bun run scripts/validate-skills.ts", + "prepublishOnly": "bun run check:skills && bun run test && bun run build" + }, + "files": [ + "dist", + "sim" + ], + "keywords": [ + "sim", + "agent-skills", + "plugin", + "ai-agents", + "workflow", + "automation" + ], + "author": "Sim", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "git+https://github.com/simstudioai/sim.git", + "directory": "packages/sim-skills" + }, + "homepage": "https://github.com/simstudioai/sim/tree/main/packages/sim-skills#readme", + "bugs": { + "url": "https://github.com/simstudioai/sim/issues" + }, + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=22.20.0" + }, + "dependencies": { + "skills": "1.5.22" + }, + "devDependencies": { + "@sim/tsconfig": "workspace:*", + "@types/node": "24.2.1", + "typescript": "^7.0.2", + "vitest": "^4.1.0" + } +} diff --git a/packages/sim-skills/scripts/validate-skills.ts b/packages/sim-skills/scripts/validate-skills.ts new file mode 100644 index 00000000000..4e87a207c18 --- /dev/null +++ b/packages/sim-skills/scripts/validate-skills.ts @@ -0,0 +1,110 @@ +import { readdir, readFile } from 'node:fs/promises' +import { dirname, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' + +const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..') +const pluginRoot = resolve(packageRoot, 'sim') +const skillsDirectory = resolve(pluginRoot, 'skills') +const pluginManifestPaths = [ + resolve(pluginRoot, '.codex-plugin', 'plugin.json'), + resolve(pluginRoot, '.claude-plugin', 'plugin.json'), +] as const +const pluginName = 'sim' +const expectedSkillNames = [ + 'build-workflow', + 'deploy-workflow', + 'knowledge-base', + 'run-workflow', + 'table', +] as const +const skillNamePattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/ +const MAX_SKILL_NAME_LENGTH = 64 +const MAX_SKILL_DESCRIPTION_LENGTH = 1024 + +function frontmatterValue(frontmatter: string, key: string): string | undefined { + const prefix = `${key}:` + return frontmatter + .split('\n') + .find((line) => line.startsWith(prefix)) + ?.slice(prefix.length) + .trim() +} + +function isRecord(value: unknown): value is Record { + return typeof value === 'object' && value !== null && !Array.isArray(value) +} + +async function readJsonObject(path: string): Promise> { + const parsed: unknown = JSON.parse(await readFile(path, 'utf8')) + if (!isRecord(parsed)) throw new Error(`${path}: expected a JSON object`) + return parsed +} + +async function validatePluginManifest(path: string, version: string): Promise { + const manifest = await readJsonObject(path) + if (manifest.name !== pluginName) throw new Error(`${path}: plugin name must be ${pluginName}`) + if (manifest.version !== version) { + throw new Error(`${path}: version must match package version ${version}`) + } + if (typeof manifest.description !== 'string' || !manifest.description.trim()) { + throw new Error(`${path}: description is required`) + } + if (manifest.skills !== './skills/') { + throw new Error(`${path}: skills must point to ./skills/`) + } +} + +async function validateSkill(directoryName: string): Promise { + if (!skillNamePattern.test(directoryName)) { + throw new Error(`${directoryName}: skill directory must be lowercase kebab-case`) + } + if (directoryName.length > MAX_SKILL_NAME_LENGTH) { + throw new Error( + `${directoryName}: skill name must be ${MAX_SKILL_NAME_LENGTH} characters or fewer` + ) + } + + const raw = await readFile(resolve(skillsDirectory, directoryName, 'SKILL.md'), 'utf8') + const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]+)$/) + if (!match) throw new Error(`${directoryName}: SKILL.md must contain YAML frontmatter and a body`) + + const [, frontmatter, body] = match + const name = frontmatterValue(frontmatter, 'name') + const description = frontmatterValue(frontmatter, 'description') + + if (name !== directoryName) { + throw new Error(`${directoryName}: frontmatter name must match the directory`) + } + if (!description) throw new Error(`${directoryName}: description is required`) + if (description.length > MAX_SKILL_DESCRIPTION_LENGTH) { + throw new Error( + `${directoryName}: description must be ${MAX_SKILL_DESCRIPTION_LENGTH} characters or fewer` + ) + } + if (!body.trim()) throw new Error(`${directoryName}: instruction body is required`) + if (/\b(?:TODO|PLACEHOLDER)\b/.test(raw)) { + throw new Error(`${directoryName}: unfinished scaffold marker found`) + } +} + +const entries = await readdir(skillsDirectory, { withFileTypes: true }) +const skillDirectories = entries + .filter((entry) => entry.isDirectory()) + .map((entry) => entry.name) + .sort() +if (skillDirectories.length === 0) throw new Error('sim-skills must contain at least one skill') +if (skillDirectories.join('\n') !== expectedSkillNames.join('\n')) { + throw new Error(`Expected Sim skills: ${expectedSkillNames.join(', ')}`) +} + +const packageManifest = await readJsonObject(resolve(packageRoot, 'package.json')) +const packageVersion = packageManifest.version +if (typeof packageVersion !== 'string' || !packageVersion) { + throw new Error('package.json: version is required') +} + +await Promise.all([ + ...skillDirectories.map(validateSkill), + ...pluginManifestPaths.map((path) => validatePluginManifest(path, packageVersion)), +]) +process.stdout.write(`Validated the ${pluginName} plugin and ${skillDirectories.length} skills.\n`) diff --git a/packages/sim-skills/sim/.claude-plugin/plugin.json b/packages/sim-skills/sim/.claude-plugin/plugin.json new file mode 100644 index 00000000000..36421680b6c --- /dev/null +++ b/packages/sim-skills/sim/.claude-plugin/plugin.json @@ -0,0 +1,15 @@ +{ + "name": "sim", + "displayName": "Sim", + "version": "0.1.0", + "description": "Build, run, deploy, and operate Sim workflows, tables, and knowledge bases.", + "author": { + "name": "Sim", + "url": "https://sim.ai" + }, + "homepage": "https://docs.sim.ai", + "repository": "https://github.com/simstudioai/sim", + "license": "Apache-2.0", + "keywords": ["sim", "workflows", "automation", "tables", "knowledge-bases"], + "skills": "./skills/" +} diff --git a/packages/sim-skills/sim/.codex-plugin/plugin.json b/packages/sim-skills/sim/.codex-plugin/plugin.json new file mode 100644 index 00000000000..845bb5525e9 --- /dev/null +++ b/packages/sim-skills/sim/.codex-plugin/plugin.json @@ -0,0 +1,28 @@ +{ + "name": "sim", + "version": "0.1.0", + "description": "Build, run, deploy, and operate Sim workflows, tables, and knowledge bases.", + "author": { + "name": "Sim", + "url": "https://sim.ai" + }, + "homepage": "https://docs.sim.ai", + "repository": "https://github.com/simstudioai/sim", + "license": "Apache-2.0", + "keywords": ["sim", "workflows", "automation", "tables", "knowledge-bases"], + "skills": "./skills/", + "interface": { + "displayName": "Sim", + "shortDescription": "Build and operate Sim workflows", + "longDescription": "Use the Sim CLI to build, test, deploy, and debug workflows, plus manage tables and knowledge bases.", + "developerName": "Sim", + "category": "Productivity", + "capabilities": ["Read", "Write"], + "websiteURL": "https://sim.ai", + "defaultPrompt": [ + "Build a Sim workflow for this automation", + "Run and debug my Sim workflow", + "Create a Sim table for this data" + ] + } +} diff --git a/packages/sim-skills/sim/skills/build-workflow/SKILL.md b/packages/sim-skills/sim/skills/build-workflow/SKILL.md new file mode 100644 index 00000000000..381c06b32b1 --- /dev/null +++ b/packages/sim-skills/sim/skills/build-workflow/SKILL.md @@ -0,0 +1,206 @@ +--- +name: build-workflow +description: Create or modify Sim workflows through the sim CLI. Use when translating a workflow request into blocks, inputs, connections, variables, and atomic graph edits; not for only running or deploying an existing workflow. +--- + +# Build a Sim Workflow + +Build the smallest valid graph that satisfies the request, using the Sim CLI as the source of truth +for available resources and accepted shapes. + +## Establish context + +- Use the profile the user named. If none was named, inspect configured profiles and current context; + do not silently switch accounts, workspaces, or API origins. +- Find the intended workflow with `sim --output json workflows list`. Create one only when the user + asked for a new workflow. +- Read an existing draft with `sim --output json workflows state get ` before editing it. + Preserve blocks, edges, variables, and deployment state outside the requested change. +- If the workflow is locked or read-only, stop instead of attempting an alternate mutation path. + +## Design before encoding + +- Translate the request into an entry point, required transformations or decisions, external + actions, and an observable terminal result. Decide what successful output should look like before + choosing blocks. +- Treat the graph as a typed program: block inputs are arguments, block outputs are return values, + and references are data flow. Inspect an upstream output schema before referencing a field, and + make sure its shape matches what the downstream input accepts. +- Build the smallest graph that expresses the behavior: + - Connect blocks directly when there is no real decision to make. + - Use a Function block for deterministic parsing, validation, calculation, and reshaping. Do not + spend an Agent block on work that should always produce the same result for the same input. + - Use an Agent block for language understanding, generation, or dynamic tool use. + - Use a Condition block for explicit predicates and a Router block for semantic classification. + - Use a Loop only for iteration. Do not create graph cycles to model repetition. + - Use Parallel only when branches are independent; keep data-dependent work sequential. +- Every added block must be reachable from the intended entry point and contribute to a terminal + path. Trace downstream references before editing or deleting an existing producer. + +## Configure Agent blocks deliberately + +- Use an Agent for generation, summarization, extraction from natural language, diagnosis, or other + work requiring judgment. Use a Function for mechanical parsing, formatting, calculation, and + reshaping; do not use canned strings or random selection to imitate a language task. When fuzzy + classification exists only to choose a branch, prefer a Router over Agent followed by Condition. +- Prefer an integration inside `inputs.tools` when the Agent should decide whether to call it or + choose its arguments. Use a standalone integration block when the action must execute + deterministically at that point in the graph. Event sources remain trigger blocks. +- Inspect the Agent and integration block details before configuring them. Choose a model from the + Agent block's current options. For an integration tool, `type` is the catalog block id and + `operation` is its catalog operation id, never the underlying executable tool id. +- Store the complete tool list at `params.inputs.tools`. Supported authoring shapes are: + +```json +[ + { + "type": "", + "operation": "", + "usageControl": "auto", + "params": { "": "" } + }, + { + "type": "custom-tool", + "customToolId": "", + "usageControl": "auto" + }, + { + "type": "mcp", + "params": { "serverId": "", "toolName": "" }, + "usageControl": "auto" + } +] +``` + +- Put only workflow-author-fixed values in a tool's `params`; omit arguments the model should + choose. Omit `usageControl` or use `auto` when the model may decide, `force` when every Agent run + must call it, and `none` to disable it. +- Resolve custom-tool ids with `custom-tools list` and `custom-tools get`. Resolve MCP server and + tool names with `mcp-servers list` and `mcp-servers tools list `. New custom tools should + use `customToolId`; preserve an existing legacy inline declaration but do not create another. +- Skills are separate from tools: store `[{ "skillId": "" }]` in `params.inputs.skills`, + never in `inputs.tools`. Both `tools` and `skills` are complete replacement arrays, so read state + and retain entries the user did not ask to remove. +- When downstream blocks need stable typed fields, configure the Agent's catalog-declared + structured response format. Structured fields become top-level Agent outputs; inspect the + effective output schema before referencing them instead of assuming a `content` path. + +## Name blocks and write references exactly + +- A block-output reference uses the block's `name`, not its catalog type, operation id, UUID, or + request-local `block_id`. Its prefix is the name lowercased with whitespace and dots removed. + Nothing is inserted: `AWS Alert` becomes `awsalert`, so use ``, never + ``. Existing dashes and underscores remain (`AWS_Alert` becomes + `aws_alert`). +- Prefer plain alphanumeric camelCase names for new blocks, such as `awsAlert`, `parseInput`, and + `step1`. This keeps the stored name readable and the reference prefix predictable. Do not use the + reserved normalized names `loop`, `parallel`, or `variable`, and do not create names that collide + after normalization. +- The field path after the prefix comes from the upstream block's effective output schema and is + case-sensitive. Function block return values are under `result`, so use `` or + `` only when the catalog declares that shape. +- Before applying a batch, enumerate every `` reference in its inputs. Verify the + normalized prefix against the exact upstream block name in workflow state, verify the field path + against the catalog output schema, and verify the source is reachable upstream. A clean workflow + operations lint does not prove block-output references resolve; its `unresolvedReferences` report + covers resource-like references such as credentials, tools, and skills. + +## Discover before composing + +- Search the catalog with `sim --output json blocks list`; inspect a candidate with + `sim --output json blocks get `. +- Use the returned block id, operation ids, input ids, modes, conditions, credential fields, and + outputs exactly. Never invent them from a display name or underlying tool id. +- Inspect `tools list` or `tools get` only when the block response points to a tool and its parameter + or output contract is needed. +- Resolve credentials and resource identifiers before writing them into a graph. Do not embed raw + secrets in an operations file. +- Discover trigger behavior from the catalog. A service trigger may be an integration block with + trigger mode enabled, while a built-in trigger may have its own block type; never substitute a + trigger configuration id for a block id. +- Select models, operations, and modes from the returned schema. Do not guess an id from a label or + reuse an id from another integration. + +## Author one semantic batch + +Prefer `workflows operations apply` over full-state replacement. One batch can create several +blocks, edit existing blocks, delete blocks, and wire their edges atomically. + +Every operation has one of these envelopes: + +```json +[ + { + "operation_type": "add", + "block_id": "local-label", + "params": { "type": "", "name": "referenceSafeName", "inputs": {} } + }, + { + "operation_type": "edit", + "block_id": "", + "params": { "inputs": {} } + }, + { "operation_type": "delete", "block_id": "" }, + { + "operation_type": "insert_into_subflow", + "block_id": "local-child-label", + "params": { + "subflowId": "", + "type": "", + "name": "referenceSafeName", + "inputs": {} + } + }, + { + "operation_type": "extract_from_subflow", + "block_id": "", + "params": { "subflowId": "" } + } +] +``` + +Block configuration belongs in `params.inputs`, keyed by the catalog's input ids. Put block-level +`retry`, `triggerMode`, and `advancedMode` beside `inputs`, not inside it. + +Connections belong on the source block under `params.connections`. Keys are source handles; values +are a target block id, `{ "block": "", "handle": "" }`, or an array of +either. `success` aliases the ordinary `source` handle. Re-sending `connections` replaces all of that +block's outgoing edges. To remove only selected edges, edit with `removeEdges` entries containing +`targetBlockId` and, when needed, `sourceHandle`. + +A non-UUID `block_id` on a new block is a request-local label. Same-batch references are remapped +automatically; later requests must use the UUID returned in `mintedBlockIds`. Never rediscover a new +block by matching its name. The request-local label does not become the block's variable-reference +prefix; `params.name` does. + +## Apply atomically, then verify + +Pass the batch as inline JSON on the first attempt and apply it once with atomic behavior. Do not +create a staging file preemptively: + +```bash +sim --output json workflows operations apply \ + --operations '' --atomic --yes +``` + +Only switch to `--operations @operations.json` if the inline invocation itself becomes impractical +because of shell parsing or command-length limits; keep the batch unchanged when changing how it is +passed to the CLI. + +If the atomic apply is refused, fix the batch rather than retrying a partial or guessed alternative. +Read the state again and verify the requested blocks, inputs, and edges. Treat skipped operations, +dropped inputs, unresolved references, and required-field lint issues as failures to correct. Report +minted ids and any other advisory lint that remains. Confirm that changed references resolve, every +branch reaches the intended destination, nested blocks remain in the correct loop or parallel scope, +and no block was orphaned by a replaced connection set. Recompute reference prefixes from the +persisted block names; do not infer them from request-local labels or reformat them as snake_case. + +Report the workflow id and its exact `webUrl` from the create or list response, formatting the URL as +a clickable link. Do not construct a workflow URL from ids or the profile's API origin; a missing +`webUrl` is a response-contract failure. + +When the request includes a working or tested workflow and execution is safe, use the run skill to +manually test the saved draft with realistic input. Judge the returned values, not only the terminal +status, and fix then rerun when behavior is wrong. Manual runs require a personal API-key profile; +do not deploy merely to test. Do not execute a workflow whose external side effects have not been +authorized. diff --git a/packages/sim-skills/sim/skills/deploy-workflow/SKILL.md b/packages/sim-skills/sim/skills/deploy-workflow/SKILL.md new file mode 100644 index 00000000000..d499b1bb32f --- /dev/null +++ b/packages/sim-skills/sim/skills/deploy-workflow/SKILL.md @@ -0,0 +1,53 @@ +--- +name: deploy-workflow +description: Publish and manage Sim workflows as APIs, chat deployments, or MCP tools through the sim CLI. Use when a tested workflow is ready to expose or deployment state must be inspected or changed; not for building or debugging a draft graph. +--- + +# Deploy a Sim Workflow + +Publish deliberately: identify the requested surface, verify the draft first, and confirm the real +post-deployment state. + +## Preflight + +- Read the draft and current deployment before changing anything. +- Confirm the draft has no required-field lint issues or unresolved credentials and has completed an + appropriate manual run. +- If a live deployment already exists, explain whether this publishes a newer draft or changes its + access configuration. +- Never create, rotate, or reveal an API key unless the user separately asked for key management. + +## Choose one surface + +- **API:** for software calling a workflow as a pipeline. Publish with + `sim --output json workflows deploy ` and inspect with `workflows deployment status`. +- **Chat:** for a person using a shareable conversation. Configure the actual output fields, slug, + title, welcome message, and access policy with `workflows chat publish`; inspect with + `workflows chat status`. A chat deployment also publishes the workflow API. +- **MCP tool:** for another AI agent calling the workflow as a tool. Inspect or create the target with + `workflow-mcp-servers list` or `workflow-mcp-servers create`. Deploy the workflow API first when it + has no active deployment, then publish with `workflow-mcp-servers tools create` using a clear tool + name, description, and descriptions for real deployed workflow inputs. + +Do not choose a surface from convenience. Ask when the intended caller does not make it clear. + +## Access invariants + +- A protected chat must include the required password or allowed-email list. Fail instead of + publishing an unusable access policy. +- Use a lowercase hyphenated chat slug. +- Map chat outputs from real block output fields returned by the catalog or a successful run, not + display labels. MCP generates its schema from deployed workflow inputs; parameter descriptions + must name those real input fields, and unknown names are ignored. +- Reuse an existing MCP server when it is the intended tool collection; do not create duplicates by + default. + +## Verify and report + +After publishing, read the deployment again. Confirm its active status and version and return the +real URL or MCP connection details from the response; never fabricate them. If the draft remains +ahead of the deployed version, say so. + +Undeploy only on an explicit request. Use `workflows undeploy`, `workflows chat unpublish`, or +`workflow-mcp-servers tools delete` for the matching surface, supplying confirmation where the CLI +requires it. Then read the matching deployment status or MCP tool list again to verify it is offline. diff --git a/packages/sim-skills/sim/skills/knowledge-base/SKILL.md b/packages/sim-skills/sim/skills/knowledge-base/SKILL.md new file mode 100644 index 00000000000..3a349204e71 --- /dev/null +++ b/packages/sim-skills/sim/skills/knowledge-base/SKILL.md @@ -0,0 +1,126 @@ +--- +name: knowledge-base +description: Create and operate Sim knowledge bases through the sim CLI. Use when configuring chunking, ingesting local or workspace documents, setting up connectors, managing tags or chunks, and verifying retrieval; not for ordinary table data or workflow graph edits. +--- + +# Operate a Sim Knowledge Base + +Build retrieval from an explicit ingestion source, wait for indexing to finish, and verify it with a +query whose expected answer is present in the source material. + +## Establish context + +- Use the profile the user named. If none was named, inspect configured profiles and current context; + do not silently switch accounts, workspaces, or API origins. +- Find existing resources with `sim --output json knowledge list`. Read the target with + `sim --output json knowledge get ` before changing it. +- Keep ids returned by create, upload, and connector calls. Do not rediscover resources by display + name when an exact id is available. + +## Create with intentional chunking + +For ordinary documents, begin with the server defaults unless the user has a retrieval reason to +change them: + +```bash +sim --output json knowledge create \ + --name "Support handbook" \ + --description "Approved support policies" +``` + +When tuning is required, pass `--chunking-config @chunking.json`. The object accepts `maxSize`, +`minSize`, `overlap`, `strategy`, and strategy-specific `strategyOptions`; inspect +`knowledge create --help` and the current knowledge-base response before composing it. Valid +strategies are `auto`, `text`, `regex`, `recursive`, `sentence`, and `token`. A regex strategy +requires a pattern. Fail on invalid bounds instead of silently falling back to defaults. + +Chunking changes affect subsequent processing. Do not imply that updating the knowledge base has +re-indexed existing completed documents unless those documents were explicitly reprocessed. + +## Choose one ingestion path + +For a local file, upload it directly: + +```bash +sim --output json knowledge documents upload ./handbook.pdf +``` + +Use `--name`, `--recipe`, or `--lang` only when supplied or justified by the source. `--tag` values +map positionally to text slots `tag1` through `tag7`; define and inspect semantic tags first rather +than guessing slot meanings. + +For files already stored in the workspace, use `knowledge from-workspace-files create` so the +server reads the canonical file. For a continuously changing external source, use a connector: + +1. Run `sim --output json connector-types list` and select an exact `connectorType`. +2. Read its returned `auth` mode and `configFields`. Build `sourceConfig` using each field's id or + `canonicalParamId`, honoring `required`, `dependsOn`, and `multi`. +3. Supply an existing OAuth `credentialId` or the required API key without writing the secret to a + committed file or output. +4. Create it with `knowledge connectors create`, then start an explicit sync with + `knowledge connectors sync` when needed. + +Never invent provider-specific `sourceConfig`. Do not use `--rehydrate` unless the user intends to +re-fetch and re-index every existing connector document. + +## Wait for indexing + +Upload and sync acceptance are not retrieval success. Read documents with: + +```bash +sim --output json knowledge documents list \ + --sort-by processingStatus --sort-order asc --limit 100 +``` + +For an exact item, use `knowledge documents get `. A document is +searchable only when it is enabled and `processingStatus` is `completed`. Treat `pending` and +`processing` as nonterminal, and `failed` as a real failure. Use `documents update --retry-processing` +alone only for a failed or stuck document; do not combine that flag with metadata changes. + +For connectors, inspect `knowledge connectors list` and `knowledge connectors documents list` to +distinguish connector sync state from individual document processing. Poll with a bounded stopping +condition and report remaining nonterminal or failed items. + +## Define and filter tags semantically + +List definitions with `knowledge tags list`. Create or save a definition before assigning values, +using `knowledge tags next-slot` when a slot is not already chosen. Text, number, date, and boolean +tags use separate finite slot pools. + +Search and document-list filters use the tag's display name, not its storage slot: + +```json +[{ "tagName": "category", "operator": "eq", "value": "billing" }] +``` + +All filters in one request must match. A missing tag name or inconsistent definition across selected +knowledge bases is an error, never an ignored filter. + +## Verify retrieval + +Run a bounded search after documents reach `completed`: + +```bash +sim --output json knowledge search \ + --kb \ + --query "How are billing disputes escalated?" \ + --top-k 5 \ + --search-mode hybrid +``` + +`top-k` must be a whole number from 1 through 100. Use `vector` for semantic retrieval and `hybrid` +when exact terminology should also influence ranking. Enable reranking only when its extra billed +search unit is justified; it is best-effort, so inspect `rerankerStatus` rather than assuming it ran. + +Verify that returned chunks come from the intended document and contain evidence for a known answer. +An empty result while a document is still indexing is not proof that retrieval is misconfigured. +If completed content is absent, inspect enabled state, tag filters, chunk text, and chunking config; +do not hide a deterministic ingestion or filter error by changing query wording repeatedly. + +## Verify and report + +Report the knowledge-base id, its exact `webUrl` from the create or get response as a clickable link, +ingestion path, document and connector ids, terminal processing statuses, and a concise retrieval +result. Do not construct a knowledge-base URL from ids or the profile's API origin; a missing +`webUrl` is a response-contract failure. Identify any failed document or degraded reranker status. +Never print API keys, OAuth tokens, unrelated document content, or profile credentials. diff --git a/packages/sim-skills/sim/skills/run-workflow/SKILL.md b/packages/sim-skills/sim/skills/run-workflow/SKILL.md new file mode 100644 index 00000000000..c741c868c04 --- /dev/null +++ b/packages/sim-skills/sim/skills/run-workflow/SKILL.md @@ -0,0 +1,66 @@ +--- +name: run-workflow +description: Test and debug Sim workflows through the sim CLI using manual runs, trigger payloads, run-from-block, selected outputs, and run records. Use after authoring or when diagnosing execution; not for graph edits or deployment changes. +--- + +# Run a Sim Workflow + +Choose the execution mode that answers the user's question, then verify the terminal result rather +than treating request acceptance as success. + +## Choose saved state or deployment + +- Use `sim --output json workflows run ` to run the active deployment. +- Add `--manual` to run the current saved draft without deploying it. Manual execution and + run-from-block require a personal API-key profile; a workspace API key is not permitted. +- Do not deploy a draft merely to test it. + +A workflow does not need a Start block for a plain manual run: + +```bash +sim --output json workflows run --manual --input @input.json +``` + +To enter through a runnable trigger, use its block id and provide either explicit input or its +server-derived mock payload: + +```bash +sim --output json workflows run \ + --manual --trigger --mock-payload +``` + +Do not combine `--mock-payload` with `--input`. Do not add `--async` to a manual run. + +## Run from a block + +Run-from-block resumes a saved draft at one block using persisted upstream state from an exact prior +run: + +```bash +sim --output json workflows run \ + --from-block --source-run +``` + +Do not guess a source run or synthesize upstream outputs. Confirm that the source run belongs to the +workflow and contains the state the selected block needs. + +## Keep output focused + +- Use repeated `--select-output ` values when only specific outputs matter. +- Use `--follow` for a synchronous live run. Add `--include-thinking` or `--include-tool-calls` only + when the user needs those diagnostics. +- Use `--async` only for deployed runs that should return immediately. Then wait with + `workflows runs wait` or inspect with `workflows runs get`; do not poll without a stopping bound. + +## Diagnose failures + +1. Read the returned run id, status, error, and selected outputs. +2. Inspect the run with + `sim --output json workflows runs get --workflow --include-output`. +3. Read the workflow state and confirm the failing block's current inputs and connections. +4. Correct the graph with the build skill. Do not hide a deterministic failure behind retries or a + different execution mode. + +Report which mode ran, the terminal status, and the relevant output or error. Include the run id when +the selected execution mode returns one; `--follow` streams omit it. Never print profile credentials +or raw secrets from block inputs. diff --git a/packages/sim-skills/sim/skills/table/SKILL.md b/packages/sim-skills/sim/skills/table/SKILL.md new file mode 100644 index 00000000000..e754724236b --- /dev/null +++ b/packages/sim-skills/sim/skills/table/SKILL.md @@ -0,0 +1,130 @@ +--- +name: table +description: Create and operate Sim tables through the sim CLI. Use when designing typed schemas, loading or querying rows, importing CSV data, or connecting a workflow or enrichment to table columns; not for knowledge-base documents or workflow graph edits. +--- + +# Operate a Sim Table + +Treat the table schema as an API contract. Discover the current table before writing, make the +smallest requested mutation, and verify stored rows or run state afterward. + +## Establish context + +- Use the profile the user named. If none was named, inspect configured profiles and current context; + do not silently switch accounts, workspaces, or API origins. +- Find existing resources with `sim --output json tables list`. Read the target with + `sim --output json tables get ` before changing its schema or data. +- Keep the returned table id. Do not rediscover a table by name after creating it. + +## Design the schema first + +Create a table with at least one explicit column: + +```bash +sim --output json tables create \ + --name contacts \ + --schema @schema.json +``` + +The schema file has this shape: + +```json +{ + "columns": [ + { "name": "email", "type": "string", "required": true, "unique": true }, + { "name": "active", "type": "boolean" }, + { "name": "score", "type": "number" } + ] +} +``` + +Use only the accepted types: `string`, `number`, `currency`, `boolean`, `date`, `json`, and +`select`. A `select` column must define `options` with stable `id` and display `name` values; only a +select may use `multiple`. A `currency` may specify a three-letter `currencyCode`. Mark a natural +key unique when later writes must be idempotent. Do not use `json` to avoid designing queryable +columns. + +Use `tables columns create`, `tables columns update`, or `tables columns delete` for a requested +schema change. Fail on incompatible existing values or uniqueness conflicts instead of weakening +the schema. + +## Write rows deliberately + +Create one row with `--data` or several with `--rows`; the flags are mutually exclusive: + +```bash +sim --output json tables rows create --data @row.json +sim --output json tables rows create --rows @rows.json +``` + +Use `tables rows update --data @patch.json` to merge specified cells into one +known row. Use `tables upsert --on email --data @row.json` only against a unique column. +An upsert's update branch **replaces the complete row** and clears omitted columns, so always send +the complete desired row. + +For CSV input, prefer the import command over parsing client-side: + +```bash +sim --output json tables import ./contacts.csv --name contacts +sim --output json tables import ./updates.csv --table-id --mode append +``` + +`--mode replace` is destructive for an existing table. Use it only when explicitly requested. +Unless the caller deliberately chose `--no-wait`, require the import's terminal result rather than +treating queue acceptance as completion. + +## Query on the server + +Filter and sort with the table commands instead of fetching every row: + +```bash +sim --output json tables rows query \ + --filter '{"all":[{"field":"active","op":"eq","value":true}]}' \ + --sort '[{"field":"score","direction":"desc"}]' \ + --limit 100 +``` + +Use `tables rows count` when only a count is needed and `tables rows search` for a +case-insensitive substring across cells. A negating filter may include null or absent values; add +an explicit `isNotNull` or `isNotEmpty` condition when nulls must be excluded. Avoid `--limit 0` on +an unbounded table. Request `--include-run-state` only when workflow-group diagnostics are needed; +it is incompatible with `--limit 0` and caps a page at 200 rows. + +## Connect a workflow or enrichment + +Read the target workflow and identify real workflow inputs and block output paths before creating a +group. The group body maps table columns to workflow inputs and workflow outputs back to new table +columns: + +```json +{ + "type": "manual", + "workflowId": "", + "name": "Score contacts", + "inputMappings": [{ "inputName": "email", "columnName": "email" }], + "outputs": [{ "blockId": "", "path": "score", "columnName": "score" }] +} +``` + +Create the group with both files: + +```bash +sim --output json tables groups create \ + --group @group.json \ + --output-columns @output-columns.json \ + --no-auto-run +``` + +`output-columns.json` is an array of typed column definitions. Group creation defaults to not +scheduling existing rows; opt into `--auto-run` only when the user intends that metered fan-out. +Afterward, use the group id returned by the API with the explicit row or batch dispatch command and +inspect dispatch and row run state until terminal. + +## Verify and report + +Read the table again after schema changes. Query a bounded sample after data changes and confirm +types, unique keys, and expected values. For groups, confirm the group mapping and relevant row run +state. Report the table id, its exact `webUrl` from the create or get response as a clickable link, +affected row count, group or dispatch ids, and any terminal error. Do not construct a table URL from +ids or the profile's API origin; a missing `webUrl` is a response-contract failure. Never print +profile credentials, connector keys, or private cell values unrelated to the request. diff --git a/packages/sim-skills/src/arguments.test.ts b/packages/sim-skills/src/arguments.test.ts new file mode 100644 index 00000000000..cac0e0e10a8 --- /dev/null +++ b/packages/sim-skills/src/arguments.test.ts @@ -0,0 +1,44 @@ +import { describe, expect, it } from 'vitest' +import { buildInstallerArguments } from './arguments' + +describe('buildInstallerArguments', () => { + it('installs the bundled pack when no arguments are supplied', () => { + expect(buildInstallerArguments('/plugin', [])).toEqual(['add', '/plugin']) + }) + + it('forwards standard installer flags after the local pack', () => { + expect( + buildInstallerArguments('/plugin', [ + '--skill', + 'build-workflow', + '--agent', + 'codex', + '--global', + '--yes', + ]) + ).toEqual([ + 'add', + '/plugin', + '--skill', + 'build-workflow', + '--agent', + 'codex', + '--global', + '--yes', + ]) + }) + + it.each(['install', 'add'])('accepts the %s alias', (command) => { + expect(buildInstallerArguments('/plugin', [command, '--list'])).toEqual([ + 'add', + '/plugin', + '--list', + ]) + }) + + it('rejects unknown commands instead of guessing', () => { + expect(() => buildInstallerArguments('/plugin', ['build-workflow'])).toThrow( + 'Unknown command "build-workflow"' + ) + }) +}) diff --git a/packages/sim-skills/src/arguments.ts b/packages/sim-skills/src/arguments.ts new file mode 100644 index 00000000000..5800186bb48 --- /dev/null +++ b/packages/sim-skills/src/arguments.ts @@ -0,0 +1,23 @@ +/** + * Build the standard installer invocation for this bundled skill pack. + * `install` and `add` are accepted as readable aliases; direct flags keep the + * shortest `bunx sim-skills --skill ...` form. + */ +export function buildInstallerArguments( + pluginRoot: string, + arguments_: readonly string[] +): string[] { + const [command, ...rest] = arguments_ + + if (command === 'install' || command === 'add') { + return ['add', pluginRoot, ...rest] + } + + if (command && !command.startsWith('-')) { + throw new Error( + `Unknown command "${command}". Pass installer flags directly or use "install" or "add".` + ) + } + + return ['add', pluginRoot, ...arguments_] +} diff --git a/packages/sim-skills/src/index.ts b/packages/sim-skills/src/index.ts new file mode 100644 index 00000000000..0403c53570d --- /dev/null +++ b/packages/sim-skills/src/index.ts @@ -0,0 +1,26 @@ +#!/usr/bin/env node + +import { spawnSync } from 'node:child_process' +import { createRequire } from 'node:module' +import { dirname, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' +import { buildInstallerArguments } from './arguments' + +const currentDirectory = dirname(fileURLToPath(import.meta.url)) +const pluginRoot = resolve(currentDirectory, '../sim') +const require = createRequire(import.meta.url) +const installerPackage = require.resolve('skills/package.json') +const installerCli = resolve(dirname(installerPackage), 'bin/cli.mjs') +const installerArguments = buildInstallerArguments(pluginRoot, process.argv.slice(2)) + +const result = spawnSync(process.execPath, [installerCli, ...installerArguments], { + cwd: process.cwd(), + env: process.env, + stdio: 'inherit', +}) + +if (result.error) throw result.error +if (result.signal) throw new Error(`Skills installer terminated with signal ${result.signal}`) +if (result.status === null) throw new Error('Skills installer exited without a status code') + +process.exitCode = result.status diff --git a/packages/sim-skills/tsconfig.json b/packages/sim-skills/tsconfig.json new file mode 100644 index 00000000000..5298cc2e818 --- /dev/null +++ b/packages/sim-skills/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "@sim/tsconfig/base.json", + "include": ["src/**/*", "scripts/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/sim-skills/vitest.config.ts b/packages/sim-skills/vitest.config.ts new file mode 100644 index 00000000000..ceafc241202 --- /dev/null +++ b/packages/sim-skills/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + environment: 'node', + include: ['src/**/*.test.ts'], + }, +})