@keystrokehq/cli 0.1.15 → 0.1.17
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.
- package/dist/{dist-BnIugffH.mjs → dist-BRA_tOTT.mjs} +47 -5
- package/dist/{dist-BnIugffH.mjs.map → dist-BRA_tOTT.mjs.map} +1 -1
- package/dist/dist-BZBvPUyu.mjs +3 -0
- package/dist/{dist-PBiyADK0.mjs → dist-C6KqfgGN.mjs} +3 -3
- package/dist/{dist-PBiyADK0.mjs.map → dist-C6KqfgGN.mjs.map} +1 -1
- package/dist/{dist-B4pkCJsM.mjs → dist-DeRE4uJW.mjs} +2 -2
- package/dist/dist-DeRE4uJW.mjs.map +1 -0
- package/dist/{dist-BW3AMCud.mjs → dist-E9nHDRnf.mjs} +3 -3
- package/dist/{dist-BW3AMCud.mjs.map → dist-E9nHDRnf.mjs.map} +1 -1
- package/dist/index.mjs +27 -15
- package/dist/index.mjs.map +1 -1
- package/dist/{maybe-auto-update-BIarxWf3.mjs → maybe-auto-update-douMZFWJ.mjs} +2 -2
- package/dist/{maybe-auto-update-BIarxWf3.mjs.map → maybe-auto-update-douMZFWJ.mjs.map} +1 -1
- package/dist/skills-bundle/_AGENTS.mcp.md +10 -3
- package/dist/skills-bundle/_AGENTS.md +61 -70
- package/dist/skills-bundle/skills/keystroke-actions/SKILL.md +60 -12
- package/dist/skills-bundle/skills/keystroke-actions/references/catalog-and-imports.md +32 -3
- package/dist/skills-bundle/skills/keystroke-agents/SKILL.md +50 -8
- package/dist/skills-bundle/skills/keystroke-agents/references/models.md +11 -13
- package/dist/skills-bundle/skills/keystroke-agents/references/tools-mcp-codemode.md +45 -3
- package/dist/skills-bundle/skills/keystroke-agents/references/workflows-and-testing.md +1 -1
- package/dist/skills-bundle/skills/keystroke-apps/SKILL.md +26 -13
- package/dist/skills-bundle/skills/keystroke-apps/references/cli-and-catalog.md +47 -16
- package/dist/skills-bundle/skills/keystroke-channels/SKILL.md +66 -0
- package/dist/skills-bundle/skills/keystroke-channels/references/slack-setup.md +41 -0
- package/dist/skills-bundle/skills/keystroke-cli/SKILL.md +41 -93
- package/dist/skills-bundle/skills/keystroke-deploy/SKILL.md +10 -9
- package/dist/skills-bundle/skills/keystroke-deploy/references/build-and-full-deploy.md +3 -1
- package/dist/skills-bundle/skills/keystroke-deploy/references/filtered-deploy.md +3 -2
- package/dist/skills-bundle/skills/keystroke-deploy/references/wip-ignore.md +5 -2
- package/dist/skills-bundle/skills/keystroke-files/SKILL.md +12 -4
- package/dist/skills-bundle/skills/keystroke-skills/SKILL.md +7 -2
- package/dist/skills-bundle/skills/keystroke-triggers/SKILL.md +30 -17
- package/dist/skills-bundle/skills/keystroke-workflows/SKILL.md +27 -12
- package/dist/skills-bundle/skills/keystroke-workflows/references/authoring.md +116 -4
- package/dist/skills-bundle/skills/keystroke-workflows/references/testing.md +17 -9
- package/dist/templates/hello-world/README.md +19 -8
- package/dist/templates/hello-world/src/workflows/greeting.test.ts +1 -1
- package/dist/{version-DScIhncv.mjs → version-CJd1mEoq.mjs} +2 -2
- package/dist/{version-DScIhncv.mjs.map → version-CJd1mEoq.mjs.map} +1 -1
- package/package.json +2 -2
- package/dist/dist-B4pkCJsM.mjs.map +0 -1
- package/dist/dist-c4WWC9_F.mjs +0 -3
- package/dist/skills-bundle/skills/keystroke-cli/references/api-targets.md +0 -87
- package/dist/skills-bundle/skills/keystroke-gateways/SKILL.md +0 -43
- package/dist/skills-bundle/skills/keystroke-gateways/references/slack-setup.md +0 -27
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { l as getCliConfigDir, r as detectPackageManager, s as resolveCliRoot, t as readCliVersion } from "./version-
|
|
2
|
+
import { l as getCliConfigDir, r as detectPackageManager, s as resolveCliRoot, t as readCliVersion } from "./version-CJd1mEoq.mjs";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { spawnSync } from "node:child_process";
|
|
@@ -405,4 +405,4 @@ async function maybeAutoUpdate(argv) {
|
|
|
405
405
|
//#endregion
|
|
406
406
|
export { maybeAutoUpdate };
|
|
407
407
|
|
|
408
|
-
//# sourceMappingURL=maybe-auto-update-
|
|
408
|
+
//# sourceMappingURL=maybe-auto-update-douMZFWJ.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"maybe-auto-update-BIarxWf3.mjs","names":[],"sources":["../src/update/compare-version.ts","../src/update/detect-cli-install.ts","../src/update/detect-release-age-block.ts","../src/update/fetch-latest-version.ts","../src/update/read-pnpm-release-age-minutes.ts","../src/update/resolve-global-pnpm-root.ts","../src/update/build-update-command.ts","../src/update/run-package-manager-update.ts","../src/update/update-block-cache.ts","../src/update/maybe-auto-update.ts"],"sourcesContent":["function parseVersion(version: string): [number, number, number] {\n const normalized = version.trim().replace(/^v/, \"\").split(\"-\")[0] ?? \"\";\n const [major = 0, minor = 0, patch = 0] = normalized.split(\".\").map((part) => {\n const value = Number.parseInt(part, 10);\n return Number.isFinite(value) ? value : 0;\n });\n\n return [major, minor, patch];\n}\n\nexport function isNewerVersion(latest: string, current: string): boolean {\n const [latestMajor, latestMinor, latestPatch] = parseVersion(latest);\n const [currentMajor, currentMinor, currentPatch] = parseVersion(current);\n\n if (latestMajor !== currentMajor) {\n return latestMajor > currentMajor;\n }\n\n if (latestMinor !== currentMinor) {\n return latestMinor > currentMinor;\n }\n\n return latestPatch > currentPatch;\n}\n","import { existsSync, realpathSync } from \"node:fs\";\nimport { spawnSync } from \"node:child_process\";\nimport { dirname, join } from \"node:path\";\n\nimport { detectPackageManager, type PackageManager } from \"../init/package-manager\";\n\nexport type CliInstallKind = \"global\" | \"local\";\n\nexport type CliInstallInfo = {\n kind: CliInstallKind;\n packageManager: PackageManager;\n packageRoot: string;\n projectRoot?: string;\n};\n\nfunction realpathSafe(path: string): string {\n try {\n return realpathSync(path);\n } catch {\n return path;\n }\n}\n\nfunction detectManagerFromLockfile(dir: string): PackageManager | undefined {\n if (existsSync(join(dir, \"pnpm-lock.yaml\"))) {\n return \"pnpm\";\n }\n\n if (existsSync(join(dir, \"bun.lockb\")) || existsSync(join(dir, \"bun.lock\"))) {\n return \"bun\";\n }\n\n if (existsSync(join(dir, \"yarn.lock\"))) {\n return \"yarn\";\n }\n\n if (existsSync(join(dir, \"package-lock.json\"))) {\n return \"npm\";\n }\n\n return undefined;\n}\n\nfunction findLocalProjectRoot(packageRoot: string): string | undefined {\n const normalizedRoot = realpathSafe(packageRoot);\n let dir = dirname(packageRoot);\n\n while (dir !== dirname(dir)) {\n const installedCliPath = join(dir, \"node_modules\", \"@keystrokehq\", \"cli\");\n if (\n existsSync(join(dir, \"package.json\")) &&\n realpathSafe(installedCliPath) === normalizedRoot\n ) {\n return dir;\n }\n\n dir = dirname(dir);\n }\n\n return undefined;\n}\n\nfunction globalRootForManager(manager: PackageManager): string | undefined {\n const commands: Record<PackageManager, { command: string; args: string[] }> = {\n pnpm: { command: \"pnpm\", args: [\"root\", \"-g\"] },\n npm: { command: \"npm\", args: [\"root\", \"-g\"] },\n yarn: { command: \"yarn\", args: [\"global\", \"dir\"] },\n bun: { command: \"bun\", args: [\"pm\", \"bin\"] },\n };\n\n const { command, args } = commands[manager];\n const result = spawnSync(command, args, { encoding: \"utf8\" });\n\n if (result.status !== 0) {\n return undefined;\n }\n\n const root = result.stdout.trim();\n return root ? realpathSafe(root) : undefined;\n}\n\nfunction detectGlobalManager(packageRoot: string): PackageManager {\n const normalizedRoot = realpathSafe(packageRoot);\n\n if (normalizedRoot.includes(`${join(\"\", \".pnpm\")}`)) {\n return \"pnpm\";\n }\n\n for (const manager of [\"pnpm\", \"npm\", \"yarn\", \"bun\"] as const) {\n const globalRoot = globalRootForManager(manager);\n if (globalRoot && normalizedRoot.startsWith(globalRoot)) {\n return manager;\n }\n }\n\n if (process.env.PNPM_HOME) {\n return \"pnpm\";\n }\n\n return detectPackageManager();\n}\n\nexport function detectCliInstall(packageRoot: string): CliInstallInfo | undefined {\n const normalizedRoot = realpathSafe(packageRoot);\n const projectRoot = findLocalProjectRoot(normalizedRoot);\n\n if (projectRoot) {\n const packageManager = detectManagerFromLockfile(projectRoot) ?? detectPackageManager();\n\n return {\n kind: \"local\",\n packageManager,\n packageRoot: normalizedRoot,\n projectRoot,\n };\n }\n\n return {\n kind: \"global\",\n packageManager: detectGlobalManager(normalizedRoot),\n packageRoot: normalizedRoot,\n };\n}\n","const RELEASE_AGE_PATTERNS = [\n /minimumReleaseAge/i,\n /minimum-release-age/i,\n /ERR_PNPM_NO_MATURE_MATCHING_VERSION/,\n /ERR_PNPM_MINIMUM_RELEASE_AGE/,\n /does not meet the minimumReleaseAge constraint/,\n];\n\nexport function isReleaseAgeBlock(output: string): boolean {\n return RELEASE_AGE_PATTERNS.some((pattern) => pattern.test(output));\n}\n\nexport function formatReleaseAgeBlockMessage(options: {\n currentVersion: string;\n availableVersion: string;\n retryAfter?: Date;\n}): string {\n const retry =\n options.retryAfter !== undefined ? ` Retry after ${options.retryAfter.toLocaleString()}.` : \"\";\n\n return (\n `@keystrokehq/cli ${options.availableVersion} is available but blocked by your ` +\n `minimum-release-age setting. You're on ${options.currentVersion}.${retry} ` +\n `Add @keystrokehq/cli to minimumReleaseAgeExclude to install immediately.`\n );\n}\n","const REGISTRY_URL = \"https://registry.npmjs.org/@keystrokehq%2Fcli\";\nconst REQUEST_TIMEOUT_MS = 3_000;\n\ntype RegistryPackage = {\n \"dist-tags\"?: {\n latest?: string;\n };\n time?: Record<string, string>;\n};\n\nexport type CliRegistryRelease = {\n version: string;\n publishedAt?: string;\n};\n\nexport async function fetchLatestCliRelease(): Promise<CliRegistryRelease | undefined> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);\n\n try {\n const response = await fetch(REGISTRY_URL, {\n signal: controller.signal,\n headers: {\n Accept: \"application/json\",\n },\n });\n\n if (!response.ok) {\n return undefined;\n }\n\n const payload = (await response.json()) as RegistryPackage;\n const version = payload[\"dist-tags\"]?.latest?.trim();\n if (!version) {\n return undefined;\n }\n\n return {\n version,\n publishedAt: payload.time?.[version],\n };\n } catch {\n return undefined;\n } finally {\n clearTimeout(timeout);\n }\n}\n\nexport async function fetchLatestCliVersion(): Promise<string | undefined> {\n const release = await fetchLatestCliRelease();\n return release?.version;\n}\n","import { spawnSync } from \"node:child_process\";\n\nexport function readPnpmMinimumReleaseAgeMinutes(): number | undefined {\n const result = spawnSync(\"pnpm\", [\"config\", \"get\", \"minimum-release-age\"], {\n encoding: \"utf8\",\n });\n\n if (result.status !== 0) {\n return undefined;\n }\n\n const value = result.stdout.trim();\n if (!value || value === \"undefined\" || value === \"0\") {\n return undefined;\n }\n\n const minutes = Number.parseInt(value, 10);\n return Number.isFinite(minutes) && minutes > 0 ? minutes : undefined;\n}\n\nexport function computeReleaseAgeRetryAfter(\n publishedAt: string | undefined,\n minimumReleaseAgeMinutes: number | undefined,\n): Date | undefined {\n if (!publishedAt || !minimumReleaseAgeMinutes) {\n return undefined;\n }\n\n const publishedMs = Date.parse(publishedAt);\n if (!Number.isFinite(publishedMs)) {\n return undefined;\n }\n\n return new Date(publishedMs + minimumReleaseAgeMinutes * 60_000);\n}\n","/** pnpm global installs live in `<pnpm-home>/global/<major>/`. */\nexport function resolveGlobalPnpmRoot(packageRoot: string): string | undefined {\n const normalized = packageRoot.replace(/\\\\/g, \"/\");\n const match = normalized.match(/^(.*\\/global\\/\\d+)\\//);\n return match?.[1];\n}\n","import type { CliInstallInfo } from \"./detect-cli-install\";\nimport { resolveGlobalPnpmRoot } from \"./resolve-global-pnpm-root\";\n\nconst PACKAGE_NAME = \"@keystrokehq/cli@latest\";\n\nexport function buildUpdateCommand(install: CliInstallInfo): {\n command: string;\n args: string[];\n cwd?: string;\n} {\n const { kind, packageManager } = install;\n\n if (kind === \"global\") {\n switch (packageManager) {\n case \"pnpm\":\n return {\n command: \"pnpm\",\n args: [\"add\", \"-g\", PACKAGE_NAME],\n cwd: resolveGlobalPnpmRoot(install.packageRoot),\n };\n case \"yarn\":\n return { command: \"yarn\", args: [\"global\", \"add\", PACKAGE_NAME] };\n case \"bun\":\n return { command: \"bun\", args: [\"add\", \"-g\", PACKAGE_NAME] };\n case \"npm\":\n default:\n return { command: \"npm\", args: [\"install\", \"-g\", PACKAGE_NAME] };\n }\n }\n\n const cwd = install.projectRoot;\n switch (packageManager) {\n case \"pnpm\":\n return { command: \"pnpm\", args: [\"add\", \"-D\", PACKAGE_NAME], cwd };\n case \"yarn\":\n return { command: \"yarn\", args: [\"add\", \"-D\", PACKAGE_NAME], cwd };\n case \"bun\":\n return { command: \"bun\", args: [\"add\", \"-D\", PACKAGE_NAME], cwd };\n case \"npm\":\n default:\n return { command: \"npm\", args: [\"install\", \"-D\", PACKAGE_NAME], cwd };\n }\n}\n","import { spawnSync } from \"node:child_process\";\n\nimport { buildUpdateCommand } from \"./build-update-command\";\nimport type { CliInstallInfo } from \"./detect-cli-install\";\n\nexport function updateSpawnEnv(): NodeJS.ProcessEnv {\n const env = { ...process.env };\n const pnpmHome = env.PNPM_HOME;\n\n if (!pnpmHome) {\n return env;\n }\n\n const pathKey = process.platform === \"win32\" ? \"Path\" : \"PATH\";\n const path = env[pathKey] ?? \"\";\n const segments = path.split(process.platform === \"win32\" ? \";\" : \":\");\n const binDir = `${pnpmHome}/bin`;\n const prefix = [binDir, pnpmHome].filter((dir) => !segments.includes(dir));\n\n if (prefix.length > 0) {\n env[pathKey] = [...prefix, path].filter(Boolean).join(process.platform === \"win32\" ? \";\" : \":\");\n }\n\n return env;\n}\n\nexport type PackageManagerRunResult = {\n ok: boolean;\n output: string;\n};\n\nfunction runPackageManager(command: string, args: string[], cwd?: string): PackageManagerRunResult {\n const result = spawnSync(command, args, {\n cwd,\n encoding: \"utf8\",\n env: updateSpawnEnv(),\n });\n\n const output = `${result.stdout ?? \"\"}${result.stderr ?? \"\"}`;\n\n return {\n ok: result.status === 0,\n output,\n };\n}\n\nfunction migratePnpmGlobal(cwd: string): boolean {\n const result = spawnSync(\"pnpm\", [\"install\"], {\n cwd,\n encoding: \"utf8\",\n env: {\n ...updateSpawnEnv(),\n CI: \"true\",\n },\n });\n\n return result.status === 0;\n}\n\nexport function runPackageManagerUpdate(install: CliInstallInfo): PackageManagerRunResult {\n const { command, args, cwd } = buildUpdateCommand(install);\n const firstAttempt = runPackageManager(command, args, cwd);\n\n if (firstAttempt.ok) {\n return firstAttempt;\n }\n\n if (install.kind === \"global\" && install.packageManager === \"pnpm\" && cwd) {\n if (migratePnpmGlobal(cwd)) {\n return runPackageManager(command, args, cwd);\n }\n }\n\n return firstAttempt;\n}\n","import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nimport { getCliConfigDir } from \"../config\";\n\nexport type UpdateBlockCache = {\n registryLatest?: string;\n blockedUntil?: string;\n lastFailedAt?: string;\n};\n\nconst CACHE_FILE = \"update-check.json\";\nconst GENERIC_RETRY_MS = 24 * 60 * 60 * 1000;\n\nfunction cachePath(configDir = getCliConfigDir()): string {\n return join(configDir, CACHE_FILE);\n}\n\nexport function readUpdateBlockCache(configDir?: string): UpdateBlockCache {\n const path = cachePath(configDir);\n if (!existsSync(path)) {\n return {};\n }\n\n try {\n return JSON.parse(readFileSync(path, \"utf8\")) as UpdateBlockCache;\n } catch {\n return {};\n }\n}\n\nexport function writeUpdateBlockCache(cache: UpdateBlockCache, configDir?: string): void {\n const dir = configDir ?? getCliConfigDir();\n mkdirSync(dir, { recursive: true });\n writeFileSync(cachePath(dir), `${JSON.stringify(cache, null, 2)}\\n`, \"utf8\");\n}\n\nexport function shouldSkipCachedUpdate(\n cache: UpdateBlockCache,\n registryLatest: string,\n now = Date.now(),\n): boolean {\n if (cache.registryLatest !== registryLatest) {\n return false;\n }\n\n if (cache.blockedUntil) {\n const blockedUntilMs = Date.parse(cache.blockedUntil);\n if (Number.isFinite(blockedUntilMs) && blockedUntilMs > now) {\n return true;\n }\n }\n\n if (cache.lastFailedAt) {\n const lastFailedMs = Date.parse(cache.lastFailedAt);\n if (Number.isFinite(lastFailedMs) && now - lastFailedMs < GENERIC_RETRY_MS) {\n return true;\n }\n }\n\n return false;\n}\n\nexport function clearUpdateBlockCache(configDir?: string): void {\n writeUpdateBlockCache({}, configDir);\n}\n\nexport function recordReleaseAgeBlock(options: {\n registryLatest: string;\n retryAfter?: Date;\n configDir?: string;\n}): void {\n writeUpdateBlockCache(\n {\n registryLatest: options.registryLatest,\n blockedUntil: options.retryAfter?.toISOString(),\n },\n options.configDir,\n );\n}\n\nexport function recordGenericUpdateFailure(registryLatest: string, configDir?: string): void {\n writeUpdateBlockCache(\n {\n registryLatest,\n lastFailedAt: new Date().toISOString(),\n },\n configDir,\n );\n}\n","import { spawnSync } from \"node:child_process\";\n\nimport { resolveCliRoot } from \"../project/resolve-cli-root\";\nimport { readCliVersion } from \"../version\";\nimport { isNewerVersion } from \"./compare-version\";\nimport { detectCliInstall } from \"./detect-cli-install\";\nimport { formatReleaseAgeBlockMessage, isReleaseAgeBlock } from \"./detect-release-age-block\";\nimport { fetchLatestCliRelease } from \"./fetch-latest-version\";\nimport {\n computeReleaseAgeRetryAfter,\n readPnpmMinimumReleaseAgeMinutes,\n} from \"./read-pnpm-release-age-minutes\";\nimport { runPackageManagerUpdate } from \"./run-package-manager-update\";\nimport {\n clearUpdateBlockCache,\n readUpdateBlockCache,\n recordGenericUpdateFailure,\n recordReleaseAgeBlock,\n shouldSkipCachedUpdate,\n} from \"./update-block-cache\";\n\nfunction shouldSkipAutoUpdate(argv: string[]): boolean {\n if (\n process.env.KEYSTROKE_DEV ||\n process.env.KEYSTROKE_SKIP_UPDATE ||\n process.env.KEYSTROKE_UPDATING\n ) {\n return true;\n }\n\n if (process.env.CI === \"true\" || process.env.CI === \"1\") {\n return true;\n }\n\n const args = argv.slice(2);\n if (args.length === 0) {\n return false;\n }\n\n return args.every(\n (arg) =>\n arg === \"-V\" ||\n arg === \"--version\" ||\n arg === \"-h\" ||\n arg === \"--help\" ||\n arg.startsWith(\"-V\"),\n );\n}\n\nfunction reexecCli(argv: string[]): never {\n const result = spawnSync(process.execPath, argv.slice(1), {\n stdio: \"inherit\",\n env: {\n ...process.env,\n KEYSTROKE_UPDATING: \"1\",\n },\n });\n\n process.exit(result.status ?? 1);\n}\n\nfunction releaseAgeRetryAfter(\n install: NonNullable<ReturnType<typeof detectCliInstall>>,\n publishedAt: string | undefined,\n): Date | undefined {\n if (install.packageManager !== \"pnpm\") {\n return undefined;\n }\n\n return computeReleaseAgeRetryAfter(publishedAt, readPnpmMinimumReleaseAgeMinutes());\n}\n\nexport async function maybeAutoUpdate(argv: string[]): Promise<void> {\n if (shouldSkipAutoUpdate(argv)) {\n return;\n }\n\n const install = detectCliInstall(resolveCliRoot(import.meta.url));\n if (!install || (install.kind === \"local\" && !install.projectRoot)) {\n return;\n }\n\n const currentVersion = readCliVersion();\n const release = await fetchLatestCliRelease();\n\n if (!release || !isNewerVersion(release.version, currentVersion)) {\n clearUpdateBlockCache();\n return;\n }\n\n const cache = readUpdateBlockCache();\n if (shouldSkipCachedUpdate(cache, release.version)) {\n return;\n }\n\n process.stderr.write(\n `Updating @keystrokehq/cli ${currentVersion} -> ${release.version} via ${install.packageManager}...\\n`,\n );\n\n const result = runPackageManagerUpdate(install);\n const installedVersion = readCliVersion();\n\n if (isNewerVersion(installedVersion, currentVersion)) {\n clearUpdateBlockCache();\n process.stderr.write(`Updated @keystrokehq/cli ${currentVersion} -> ${installedVersion}.\\n`);\n reexecCli(argv);\n }\n\n if (result.ok) {\n clearUpdateBlockCache();\n return;\n }\n\n if (isReleaseAgeBlock(result.output)) {\n const retryAfter = releaseAgeRetryAfter(install, release.publishedAt);\n recordReleaseAgeBlock({\n registryLatest: release.version,\n retryAfter,\n });\n process.stderr.write(\n `${formatReleaseAgeBlockMessage({\n currentVersion,\n availableVersion: release.version,\n retryAfter,\n })}\\n`,\n );\n return;\n }\n\n recordGenericUpdateFailure(release.version);\n process.stderr.write(\"Auto-update failed; continuing with the current version.\\n\");\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"maybe-auto-update-douMZFWJ.mjs","names":[],"sources":["../src/update/compare-version.ts","../src/update/detect-cli-install.ts","../src/update/detect-release-age-block.ts","../src/update/fetch-latest-version.ts","../src/update/read-pnpm-release-age-minutes.ts","../src/update/resolve-global-pnpm-root.ts","../src/update/build-update-command.ts","../src/update/run-package-manager-update.ts","../src/update/update-block-cache.ts","../src/update/maybe-auto-update.ts"],"sourcesContent":["function parseVersion(version: string): [number, number, number] {\n const normalized = version.trim().replace(/^v/, \"\").split(\"-\")[0] ?? \"\";\n const [major = 0, minor = 0, patch = 0] = normalized.split(\".\").map((part) => {\n const value = Number.parseInt(part, 10);\n return Number.isFinite(value) ? value : 0;\n });\n\n return [major, minor, patch];\n}\n\nexport function isNewerVersion(latest: string, current: string): boolean {\n const [latestMajor, latestMinor, latestPatch] = parseVersion(latest);\n const [currentMajor, currentMinor, currentPatch] = parseVersion(current);\n\n if (latestMajor !== currentMajor) {\n return latestMajor > currentMajor;\n }\n\n if (latestMinor !== currentMinor) {\n return latestMinor > currentMinor;\n }\n\n return latestPatch > currentPatch;\n}\n","import { existsSync, realpathSync } from \"node:fs\";\nimport { spawnSync } from \"node:child_process\";\nimport { dirname, join } from \"node:path\";\n\nimport { detectPackageManager, type PackageManager } from \"../init/package-manager\";\n\nexport type CliInstallKind = \"global\" | \"local\";\n\nexport type CliInstallInfo = {\n kind: CliInstallKind;\n packageManager: PackageManager;\n packageRoot: string;\n projectRoot?: string;\n};\n\nfunction realpathSafe(path: string): string {\n try {\n return realpathSync(path);\n } catch {\n return path;\n }\n}\n\nfunction detectManagerFromLockfile(dir: string): PackageManager | undefined {\n if (existsSync(join(dir, \"pnpm-lock.yaml\"))) {\n return \"pnpm\";\n }\n\n if (existsSync(join(dir, \"bun.lockb\")) || existsSync(join(dir, \"bun.lock\"))) {\n return \"bun\";\n }\n\n if (existsSync(join(dir, \"yarn.lock\"))) {\n return \"yarn\";\n }\n\n if (existsSync(join(dir, \"package-lock.json\"))) {\n return \"npm\";\n }\n\n return undefined;\n}\n\nfunction findLocalProjectRoot(packageRoot: string): string | undefined {\n const normalizedRoot = realpathSafe(packageRoot);\n let dir = dirname(packageRoot);\n\n while (dir !== dirname(dir)) {\n const installedCliPath = join(dir, \"node_modules\", \"@keystrokehq\", \"cli\");\n if (\n existsSync(join(dir, \"package.json\")) &&\n realpathSafe(installedCliPath) === normalizedRoot\n ) {\n return dir;\n }\n\n dir = dirname(dir);\n }\n\n return undefined;\n}\n\nfunction globalRootForManager(manager: PackageManager): string | undefined {\n const commands: Record<PackageManager, { command: string; args: string[] }> = {\n pnpm: { command: \"pnpm\", args: [\"root\", \"-g\"] },\n npm: { command: \"npm\", args: [\"root\", \"-g\"] },\n yarn: { command: \"yarn\", args: [\"global\", \"dir\"] },\n bun: { command: \"bun\", args: [\"pm\", \"bin\"] },\n };\n\n const { command, args } = commands[manager];\n const result = spawnSync(command, args, { encoding: \"utf8\" });\n\n if (result.status !== 0) {\n return undefined;\n }\n\n const root = result.stdout.trim();\n return root ? realpathSafe(root) : undefined;\n}\n\nfunction detectGlobalManager(packageRoot: string): PackageManager {\n const normalizedRoot = realpathSafe(packageRoot);\n\n if (normalizedRoot.includes(`${join(\"\", \".pnpm\")}`)) {\n return \"pnpm\";\n }\n\n for (const manager of [\"pnpm\", \"npm\", \"yarn\", \"bun\"] as const) {\n const globalRoot = globalRootForManager(manager);\n if (globalRoot && normalizedRoot.startsWith(globalRoot)) {\n return manager;\n }\n }\n\n if (process.env.PNPM_HOME) {\n return \"pnpm\";\n }\n\n return detectPackageManager();\n}\n\nexport function detectCliInstall(packageRoot: string): CliInstallInfo | undefined {\n const normalizedRoot = realpathSafe(packageRoot);\n const projectRoot = findLocalProjectRoot(normalizedRoot);\n\n if (projectRoot) {\n const packageManager = detectManagerFromLockfile(projectRoot) ?? detectPackageManager();\n\n return {\n kind: \"local\",\n packageManager,\n packageRoot: normalizedRoot,\n projectRoot,\n };\n }\n\n return {\n kind: \"global\",\n packageManager: detectGlobalManager(normalizedRoot),\n packageRoot: normalizedRoot,\n };\n}\n","const RELEASE_AGE_PATTERNS = [\n /minimumReleaseAge/i,\n /minimum-release-age/i,\n /ERR_PNPM_NO_MATURE_MATCHING_VERSION/,\n /ERR_PNPM_MINIMUM_RELEASE_AGE/,\n /does not meet the minimumReleaseAge constraint/,\n];\n\nexport function isReleaseAgeBlock(output: string): boolean {\n return RELEASE_AGE_PATTERNS.some((pattern) => pattern.test(output));\n}\n\nexport function formatReleaseAgeBlockMessage(options: {\n currentVersion: string;\n availableVersion: string;\n retryAfter?: Date;\n}): string {\n const retry =\n options.retryAfter !== undefined ? ` Retry after ${options.retryAfter.toLocaleString()}.` : \"\";\n\n return (\n `@keystrokehq/cli ${options.availableVersion} is available but blocked by your ` +\n `minimum-release-age setting. You're on ${options.currentVersion}.${retry} ` +\n `Add @keystrokehq/cli to minimumReleaseAgeExclude to install immediately.`\n );\n}\n","const REGISTRY_URL = \"https://registry.npmjs.org/@keystrokehq%2Fcli\";\nconst REQUEST_TIMEOUT_MS = 3_000;\n\ntype RegistryPackage = {\n \"dist-tags\"?: {\n latest?: string;\n };\n time?: Record<string, string>;\n};\n\nexport type CliRegistryRelease = {\n version: string;\n publishedAt?: string;\n};\n\nexport async function fetchLatestCliRelease(): Promise<CliRegistryRelease | undefined> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);\n\n try {\n const response = await fetch(REGISTRY_URL, {\n signal: controller.signal,\n headers: {\n Accept: \"application/json\",\n },\n });\n\n if (!response.ok) {\n return undefined;\n }\n\n const payload = (await response.json()) as RegistryPackage;\n const version = payload[\"dist-tags\"]?.latest?.trim();\n if (!version) {\n return undefined;\n }\n\n return {\n version,\n publishedAt: payload.time?.[version],\n };\n } catch {\n return undefined;\n } finally {\n clearTimeout(timeout);\n }\n}\n\nexport async function fetchLatestCliVersion(): Promise<string | undefined> {\n const release = await fetchLatestCliRelease();\n return release?.version;\n}\n","import { spawnSync } from \"node:child_process\";\n\nexport function readPnpmMinimumReleaseAgeMinutes(): number | undefined {\n const result = spawnSync(\"pnpm\", [\"config\", \"get\", \"minimum-release-age\"], {\n encoding: \"utf8\",\n });\n\n if (result.status !== 0) {\n return undefined;\n }\n\n const value = result.stdout.trim();\n if (!value || value === \"undefined\" || value === \"0\") {\n return undefined;\n }\n\n const minutes = Number.parseInt(value, 10);\n return Number.isFinite(minutes) && minutes > 0 ? minutes : undefined;\n}\n\nexport function computeReleaseAgeRetryAfter(\n publishedAt: string | undefined,\n minimumReleaseAgeMinutes: number | undefined,\n): Date | undefined {\n if (!publishedAt || !minimumReleaseAgeMinutes) {\n return undefined;\n }\n\n const publishedMs = Date.parse(publishedAt);\n if (!Number.isFinite(publishedMs)) {\n return undefined;\n }\n\n return new Date(publishedMs + minimumReleaseAgeMinutes * 60_000);\n}\n","/** pnpm global installs live in `<pnpm-home>/global/<major>/`. */\nexport function resolveGlobalPnpmRoot(packageRoot: string): string | undefined {\n const normalized = packageRoot.replace(/\\\\/g, \"/\");\n const match = normalized.match(/^(.*\\/global\\/\\d+)\\//);\n return match?.[1];\n}\n","import type { CliInstallInfo } from \"./detect-cli-install\";\nimport { resolveGlobalPnpmRoot } from \"./resolve-global-pnpm-root\";\n\nconst PACKAGE_NAME = \"@keystrokehq/cli@latest\";\n\nexport function buildUpdateCommand(install: CliInstallInfo): {\n command: string;\n args: string[];\n cwd?: string;\n} {\n const { kind, packageManager } = install;\n\n if (kind === \"global\") {\n switch (packageManager) {\n case \"pnpm\":\n return {\n command: \"pnpm\",\n args: [\"add\", \"-g\", PACKAGE_NAME],\n cwd: resolveGlobalPnpmRoot(install.packageRoot),\n };\n case \"yarn\":\n return { command: \"yarn\", args: [\"global\", \"add\", PACKAGE_NAME] };\n case \"bun\":\n return { command: \"bun\", args: [\"add\", \"-g\", PACKAGE_NAME] };\n case \"npm\":\n default:\n return { command: \"npm\", args: [\"install\", \"-g\", PACKAGE_NAME] };\n }\n }\n\n const cwd = install.projectRoot;\n switch (packageManager) {\n case \"pnpm\":\n return { command: \"pnpm\", args: [\"add\", \"-D\", PACKAGE_NAME], cwd };\n case \"yarn\":\n return { command: \"yarn\", args: [\"add\", \"-D\", PACKAGE_NAME], cwd };\n case \"bun\":\n return { command: \"bun\", args: [\"add\", \"-D\", PACKAGE_NAME], cwd };\n case \"npm\":\n default:\n return { command: \"npm\", args: [\"install\", \"-D\", PACKAGE_NAME], cwd };\n }\n}\n","import { spawnSync } from \"node:child_process\";\n\nimport { buildUpdateCommand } from \"./build-update-command\";\nimport type { CliInstallInfo } from \"./detect-cli-install\";\n\nexport function updateSpawnEnv(): NodeJS.ProcessEnv {\n const env = { ...process.env };\n const pnpmHome = env.PNPM_HOME;\n\n if (!pnpmHome) {\n return env;\n }\n\n const pathKey = process.platform === \"win32\" ? \"Path\" : \"PATH\";\n const path = env[pathKey] ?? \"\";\n const segments = path.split(process.platform === \"win32\" ? \";\" : \":\");\n const binDir = `${pnpmHome}/bin`;\n const prefix = [binDir, pnpmHome].filter((dir) => !segments.includes(dir));\n\n if (prefix.length > 0) {\n env[pathKey] = [...prefix, path].filter(Boolean).join(process.platform === \"win32\" ? \";\" : \":\");\n }\n\n return env;\n}\n\nexport type PackageManagerRunResult = {\n ok: boolean;\n output: string;\n};\n\nfunction runPackageManager(command: string, args: string[], cwd?: string): PackageManagerRunResult {\n const result = spawnSync(command, args, {\n cwd,\n encoding: \"utf8\",\n env: updateSpawnEnv(),\n });\n\n const output = `${result.stdout ?? \"\"}${result.stderr ?? \"\"}`;\n\n return {\n ok: result.status === 0,\n output,\n };\n}\n\nfunction migratePnpmGlobal(cwd: string): boolean {\n const result = spawnSync(\"pnpm\", [\"install\"], {\n cwd,\n encoding: \"utf8\",\n env: {\n ...updateSpawnEnv(),\n CI: \"true\",\n },\n });\n\n return result.status === 0;\n}\n\nexport function runPackageManagerUpdate(install: CliInstallInfo): PackageManagerRunResult {\n const { command, args, cwd } = buildUpdateCommand(install);\n const firstAttempt = runPackageManager(command, args, cwd);\n\n if (firstAttempt.ok) {\n return firstAttempt;\n }\n\n if (install.kind === \"global\" && install.packageManager === \"pnpm\" && cwd) {\n if (migratePnpmGlobal(cwd)) {\n return runPackageManager(command, args, cwd);\n }\n }\n\n return firstAttempt;\n}\n","import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nimport { getCliConfigDir } from \"../config\";\n\nexport type UpdateBlockCache = {\n registryLatest?: string;\n blockedUntil?: string;\n lastFailedAt?: string;\n};\n\nconst CACHE_FILE = \"update-check.json\";\nconst GENERIC_RETRY_MS = 24 * 60 * 60 * 1000;\n\nfunction cachePath(configDir = getCliConfigDir()): string {\n return join(configDir, CACHE_FILE);\n}\n\nexport function readUpdateBlockCache(configDir?: string): UpdateBlockCache {\n const path = cachePath(configDir);\n if (!existsSync(path)) {\n return {};\n }\n\n try {\n return JSON.parse(readFileSync(path, \"utf8\")) as UpdateBlockCache;\n } catch {\n return {};\n }\n}\n\nexport function writeUpdateBlockCache(cache: UpdateBlockCache, configDir?: string): void {\n const dir = configDir ?? getCliConfigDir();\n mkdirSync(dir, { recursive: true });\n writeFileSync(cachePath(dir), `${JSON.stringify(cache, null, 2)}\\n`, \"utf8\");\n}\n\nexport function shouldSkipCachedUpdate(\n cache: UpdateBlockCache,\n registryLatest: string,\n now = Date.now(),\n): boolean {\n if (cache.registryLatest !== registryLatest) {\n return false;\n }\n\n if (cache.blockedUntil) {\n const blockedUntilMs = Date.parse(cache.blockedUntil);\n if (Number.isFinite(blockedUntilMs) && blockedUntilMs > now) {\n return true;\n }\n }\n\n if (cache.lastFailedAt) {\n const lastFailedMs = Date.parse(cache.lastFailedAt);\n if (Number.isFinite(lastFailedMs) && now - lastFailedMs < GENERIC_RETRY_MS) {\n return true;\n }\n }\n\n return false;\n}\n\nexport function clearUpdateBlockCache(configDir?: string): void {\n writeUpdateBlockCache({}, configDir);\n}\n\nexport function recordReleaseAgeBlock(options: {\n registryLatest: string;\n retryAfter?: Date;\n configDir?: string;\n}): void {\n writeUpdateBlockCache(\n {\n registryLatest: options.registryLatest,\n blockedUntil: options.retryAfter?.toISOString(),\n },\n options.configDir,\n );\n}\n\nexport function recordGenericUpdateFailure(registryLatest: string, configDir?: string): void {\n writeUpdateBlockCache(\n {\n registryLatest,\n lastFailedAt: new Date().toISOString(),\n },\n configDir,\n );\n}\n","import { spawnSync } from \"node:child_process\";\n\nimport { resolveCliRoot } from \"../project/resolve-cli-root\";\nimport { readCliVersion } from \"../version\";\nimport { isNewerVersion } from \"./compare-version\";\nimport { detectCliInstall } from \"./detect-cli-install\";\nimport { formatReleaseAgeBlockMessage, isReleaseAgeBlock } from \"./detect-release-age-block\";\nimport { fetchLatestCliRelease } from \"./fetch-latest-version\";\nimport {\n computeReleaseAgeRetryAfter,\n readPnpmMinimumReleaseAgeMinutes,\n} from \"./read-pnpm-release-age-minutes\";\nimport { runPackageManagerUpdate } from \"./run-package-manager-update\";\nimport {\n clearUpdateBlockCache,\n readUpdateBlockCache,\n recordGenericUpdateFailure,\n recordReleaseAgeBlock,\n shouldSkipCachedUpdate,\n} from \"./update-block-cache\";\n\nfunction shouldSkipAutoUpdate(argv: string[]): boolean {\n if (\n process.env.KEYSTROKE_DEV ||\n process.env.KEYSTROKE_SKIP_UPDATE ||\n process.env.KEYSTROKE_UPDATING\n ) {\n return true;\n }\n\n if (process.env.CI === \"true\" || process.env.CI === \"1\") {\n return true;\n }\n\n const args = argv.slice(2);\n if (args.length === 0) {\n return false;\n }\n\n return args.every(\n (arg) =>\n arg === \"-V\" ||\n arg === \"--version\" ||\n arg === \"-h\" ||\n arg === \"--help\" ||\n arg.startsWith(\"-V\"),\n );\n}\n\nfunction reexecCli(argv: string[]): never {\n const result = spawnSync(process.execPath, argv.slice(1), {\n stdio: \"inherit\",\n env: {\n ...process.env,\n KEYSTROKE_UPDATING: \"1\",\n },\n });\n\n process.exit(result.status ?? 1);\n}\n\nfunction releaseAgeRetryAfter(\n install: NonNullable<ReturnType<typeof detectCliInstall>>,\n publishedAt: string | undefined,\n): Date | undefined {\n if (install.packageManager !== \"pnpm\") {\n return undefined;\n }\n\n return computeReleaseAgeRetryAfter(publishedAt, readPnpmMinimumReleaseAgeMinutes());\n}\n\nexport async function maybeAutoUpdate(argv: string[]): Promise<void> {\n if (shouldSkipAutoUpdate(argv)) {\n return;\n }\n\n const install = detectCliInstall(resolveCliRoot(import.meta.url));\n if (!install || (install.kind === \"local\" && !install.projectRoot)) {\n return;\n }\n\n const currentVersion = readCliVersion();\n const release = await fetchLatestCliRelease();\n\n if (!release || !isNewerVersion(release.version, currentVersion)) {\n clearUpdateBlockCache();\n return;\n }\n\n const cache = readUpdateBlockCache();\n if (shouldSkipCachedUpdate(cache, release.version)) {\n return;\n }\n\n process.stderr.write(\n `Updating @keystrokehq/cli ${currentVersion} -> ${release.version} via ${install.packageManager}...\\n`,\n );\n\n const result = runPackageManagerUpdate(install);\n const installedVersion = readCliVersion();\n\n if (isNewerVersion(installedVersion, currentVersion)) {\n clearUpdateBlockCache();\n process.stderr.write(`Updated @keystrokehq/cli ${currentVersion} -> ${installedVersion}.\\n`);\n reexecCli(argv);\n }\n\n if (result.ok) {\n clearUpdateBlockCache();\n return;\n }\n\n if (isReleaseAgeBlock(result.output)) {\n const retryAfter = releaseAgeRetryAfter(install, release.publishedAt);\n recordReleaseAgeBlock({\n registryLatest: release.version,\n retryAfter,\n });\n process.stderr.write(\n `${formatReleaseAgeBlockMessage({\n currentVersion,\n availableVersion: release.version,\n retryAfter,\n })}\\n`,\n );\n return;\n }\n\n recordGenericUpdateFailure(release.version);\n process.stderr.write(\"Auto-update failed; continuing with the current version.\\n\");\n}\n"],"mappings":"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"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# keystroke (MCP)
|
|
2
2
|
|
|
3
|
-
Keystroke is
|
|
3
|
+
Keystroke is a code-first AI automation platform — build specialized AI agents and workflow automations as real TypeScript, then deploy them to a managed cloud where they run. Everything is actual code (not brittle JSON), so agents can build, test, and maintain it. There are 1,000+ built-in integrations (Slack, Google, Linear, …), and you can build a connection for any HTTP API (including private internal APIs) or MCP server.
|
|
4
4
|
|
|
5
5
|
You are editing a **keystroke project** in a remote workspace via MCP. Author under `src/`, import from `@keystrokehq/*` npm packages. You do not have framework source — only this project and published deps.
|
|
6
6
|
|
|
7
7
|
Use MCP tools to work on the project: `read_file`, `write_file`, `edit_file`, `list_files`, `delete_file`, `exec_command`, and `deploy_project`. Load authoring guidance with `search_skills` and `get_skill` (see skills map below). The workspace root also has `AGENTS.md` and `.agents/skills/` with the same skill content, readable via `read_file`.
|
|
8
8
|
|
|
9
|
-
The **keystroke platform** is the hosted control plane. After you change code, run `deploy_project` to build and upload; that activates the cloud runtime.
|
|
9
|
+
The **keystroke platform** is the hosted control plane. After you change code, run `deploy_project` to build and upload; that activates the cloud runtime. A project is a single live runtime — a deploy replaces what's running on that project (there's no separate dev and prod within one project), so use filtered deploys and `@keystroke ignore` to iterate quickly.
|
|
10
10
|
|
|
11
11
|
## What you build
|
|
12
12
|
|
|
@@ -26,6 +26,10 @@ Draft or in-progress modules, full vs filtered deploy, and `@keystroke ignore`:
|
|
|
26
26
|
|
|
27
27
|
**Actions are leaf units.** An action never calls another action (including integration actions like `postMessage`). Compose actions in a workflow, or attach an integration action directly as a workflow step / agent tool. An action may call an agent.
|
|
28
28
|
|
|
29
|
+
**Workflow steps.** Workflows orchestrate actions, agent prompts, and LLM calls (`promptLlm`) as steps. There's no first-class "call another workflow" step — share logic by extracting it into `src/lib/` or an action, or expose a workflow as an agent tool (`defineWorkflowTool`) / over HTTP when you want it as its own tracked run.
|
|
30
|
+
|
|
31
|
+
**Agent tools.** Agents can use actions, subagents (`defineSubagentTool`), workflows (`defineWorkflowTool`), and MCP tools as tools.
|
|
32
|
+
|
|
29
33
|
## Working habits
|
|
30
34
|
|
|
31
35
|
A few things that are easy to skip and cause common failures:
|
|
@@ -46,7 +50,10 @@ keystroke workflow run <workflow-key> --input '{}'
|
|
|
46
50
|
keystroke workflow runs list <workflow-key>
|
|
47
51
|
keystroke workflow runs get <workflow-key> <run-id> --include steps,trace
|
|
48
52
|
keystroke agent sessions list <agent-key>
|
|
53
|
+
keystroke agent sessions get <agent-key> <session-id> --include messages,trace
|
|
49
54
|
keystroke trigger list
|
|
55
|
+
keystroke trigger url <trigger-key>
|
|
56
|
+
keystroke trigger runs list <trigger-key>:<workflow-key>
|
|
50
57
|
keystroke app list
|
|
51
58
|
```
|
|
52
59
|
|
|
@@ -64,7 +71,7 @@ Call `search_skills` then `get_skill` with the key below.
|
|
|
64
71
|
| `keystroke-workflows` | Orchestration, action steps |
|
|
65
72
|
| `keystroke-actions` | Shared steps, integration tools |
|
|
66
73
|
| `keystroke-triggers` | Cron, webhook, poll automation |
|
|
67
|
-
| `keystroke-
|
|
74
|
+
| `keystroke-channels` | Slack / external channel bindings |
|
|
68
75
|
| `keystroke-skills` | `src/skills/` playbooks |
|
|
69
76
|
| `keystroke-files` | Agent workspace files |
|
|
70
77
|
| `keystroke-apps` | Connectable apps, catalog, connect |
|
|
@@ -1,121 +1,112 @@
|
|
|
1
1
|
# keystroke
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This codebase is a Keystroke project.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
> Keystroke is a code-first AI automation platform, an n8n alternative built for agents. With it, anyone can build specialized AI agents and workflow automations as real TypeScript, then deploy them to a managed cloud platform where they run. Unlike tools like Zapier and n8n that hide logic behind brittle JSON, everything in Keystroke is actual code that coding agents (Cursor, Claude Code, Codex, etc.) can build, test, and maintain.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
- **Build agents and workflows:** Build agents and workflows with an open TypeScript framework. Run tests, connect integrations, and maintain what you build over time.
|
|
8
|
+
- **Connect to thousands of integrations:** Keystroke has over 1,000 built-in integrations (Slack, Google, Linear, and hundreds more). You can also quickly build a connection for any HTTP API (including private internal APIs) or MCP server.
|
|
9
|
+
- **Deploy what you build:** The Keystroke platform gives teams a managed workspace to run what they build. Deploy agents and workflows from your project, then inspect runs, manage credentials, and collaborate from a shared web platform.
|
|
8
10
|
|
|
9
|
-
##
|
|
11
|
+
## Keystroke projects
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
In Keystroke, everything you build belongs to a project. A project is a shared space for building and running your agents, workflows, triggers, and actions. Think of a project as the unit you build, deploy, give credentials to, invite teammates into, and inspect run history for.
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
| --------------------------------------------- | -------------------------------------------------------------------------------------------------- |
|
|
15
|
-
| **Local** (default `:3002`) | **Development and testing** — iterate on `src/`, run workflows/agents on your machine |
|
|
16
|
-
| **Cloud** (`platformUrl` + `activeProjectId`) | **Invoking and listing deployed resources** — production runs, triggers, remote audit after deploy |
|
|
15
|
+
Keystroke projects use two related ideas:
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
keystroke config use local # switch to local (cloud project id is preserved)
|
|
21
|
-
keystroke config use cloud # switch back to deployed project
|
|
22
|
-
keystroke config use project <id> # swap active cloud project
|
|
23
|
-
keystroke --project <id> workflow run foo --input '{}' # one-off cloud target
|
|
24
|
-
keystroke config show # inspect activeProjectId, apiTarget, dev session
|
|
25
|
-
keystroke project list # platform deploy targets in the active org
|
|
26
|
-
keystroke project create --name "My app" # new target (inactive until deploy)
|
|
27
|
-
keystroke deploy --project <id> # build + upload; activates cloud runtime
|
|
28
|
-
```
|
|
17
|
+
- **Local codebase**: a local directory (this codebase) with `keystroke.config.ts` and `src/` (`keystroke init` scaffolds this). Run it locally with `keystroke start` / `keystroke dev`; no platform project required.
|
|
18
|
+
- **Platform project**: an org-owned cloud record: a deploy target plus a hosted runtime (its own keystroke server, URL, managed credentials). Projects are inactive until their first deploy.
|
|
29
19
|
|
|
30
|
-
`keystroke
|
|
20
|
+
`keystroke deploy --project <id>` links your local codebase to your platform project: it uploads the built local codebase and activates that platform project's server. After the first deploy the project is saved as your active target (in `~/.keystroke`), so later commands — including subsequent deploys — can drop `--project`. Deployed agents and workflows instantly run there with no additional configuration; the web workspace is where users can manage their projects, deployments, logs, credentials, etc.
|
|
31
21
|
|
|
32
|
-
|
|
22
|
+
## How this codebase fits in
|
|
33
23
|
|
|
34
|
-
|
|
24
|
+
This is the local codebase that defines your agents, workflows, triggers, actions, skills, and files.
|
|
35
25
|
|
|
36
|
-
|
|
26
|
+
Projects are scaffolded with an opinionated folder structure (like Next.js for building AI systems):
|
|
37
27
|
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
keystroke
|
|
41
|
-
|
|
42
|
-
|
|
28
|
+
```
|
|
29
|
+
my-app/
|
|
30
|
+
keystroke.config.ts # project configuration
|
|
31
|
+
src/
|
|
32
|
+
agents/ # LLM agents
|
|
33
|
+
actions/ # reusable units of work
|
|
34
|
+
workflows/ # multi-step automations
|
|
35
|
+
triggers/ # schedules, webhooks, app events
|
|
36
|
+
skills/ # project skills for agents
|
|
37
|
+
files/ # context for attaching to agents
|
|
38
|
+
AGENTS.md # this guide
|
|
39
|
+
.agents/skills/ # Keystroke skills and best practices
|
|
43
40
|
```
|
|
44
41
|
|
|
45
|
-
|
|
42
|
+
Keystroke discovers everything under `src/` by convention. Add a file that exports an agent, workflow, trigger, etc, and it becomes part of the project. Because the project is code, agents (Cursor, Claude Code, Codex, etc) can read it, change it, run tests, and maintain it over time.
|
|
46
43
|
|
|
47
|
-
|
|
44
|
+
Each primitive imports from `@keystrokehq/keystroke/<piece>` (`/action`, `/agent`, `/workflow`, `/trigger`, `/app`); integrations are their own `@keystrokehq/<slug>` packages. The example files in `src/` show the exact imports.
|
|
48
45
|
|
|
49
|
-
|
|
50
|
-
keystroke auth login
|
|
51
|
-
```
|
|
46
|
+
## How you work
|
|
52
47
|
|
|
53
|
-
|
|
48
|
+
You build in `src/`, deploy to your project on the platform, then run and inspect what's deployed with the CLI. The loop is: edit → `keystroke deploy` → run/inspect/test → repeat. You should deploy often.
|
|
54
49
|
|
|
55
|
-
|
|
50
|
+
```bash
|
|
51
|
+
keystroke auth login # once; token is automatically stored and reused
|
|
52
|
+
keystroke project list # your projects on the platform
|
|
53
|
+
keystroke deploy --project <id> # build + ship src/ to the platform
|
|
54
|
+
keystroke workflow run greeting --input '{"name":"Ada"}' # run on your project
|
|
55
|
+
keystroke agent prompt hello --message "Hi"
|
|
56
|
+
```
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
| -------------- | -------------------------------------- | --------------------------------------------------------------------------- |
|
|
59
|
-
| Actions | `src/actions/` | `@keystrokehq/keystroke/action` |
|
|
60
|
-
| Agents | `src/agents/` | `@keystrokehq/keystroke/agent` |
|
|
61
|
-
| Workflows | `src/workflows/` | `@keystrokehq/keystroke/workflow` |
|
|
62
|
-
| Triggers | `src/triggers/` | `@keystrokehq/keystroke/trigger` |
|
|
63
|
-
| Apps | `src/apps/` | `@keystrokehq/keystroke/app`, `keystroke app`, `keystroke connect` |
|
|
64
|
-
| Integrations | npm packages + catalog | `@keystrokehq/exa`, `@keystrokehq/slack`, … — discover with `keystroke app search` |
|
|
65
|
-
| Skills / files | `src/skills/`, `src/files/` | attached on `defineAgent` |
|
|
58
|
+
After a deploy, runtime commands (`workflow`, `agent`, `trigger`, `app`, `connect`) operate on that project automatically — no target to manage.
|
|
66
59
|
|
|
67
|
-
|
|
60
|
+
A project is a single live runtime. A deploy replaces what's running on that project — there's no separate dev and prod within one project. You can use `--filter` deploys and `@keystroke ignore` to keep iterate quickly.
|
|
68
61
|
|
|
69
|
-
|
|
62
|
+
To create a new project: `keystroke init my-app --yes`, then deploy.
|
|
70
63
|
|
|
71
|
-
|
|
64
|
+
Full deploy mechanics: [cli skill](.agents/skills/keystroke-cli/SKILL.md), [deploy skill](.agents/skills/keystroke-deploy/SKILL.md).
|
|
72
65
|
|
|
73
66
|
## Working habits
|
|
74
67
|
|
|
75
68
|
A few things that are easy to skip and cause common failures:
|
|
76
69
|
|
|
77
70
|
- **Research real APIs before mirroring them** — web search and fetch the actual reference & explore relevant docs; don't guess endpoints or payload shapes.
|
|
78
|
-
- **Run before you depend
|
|
71
|
+
- **Run before you depend on it** — run the workflow/agent and read the real output (`keystroke workflow runs get <workflow-slug> <run-id> --include steps,trace`) before wiring dependent steps or claiming you are finished building something.
|
|
79
72
|
- **Cover obvious edge cases** and write simple tests as needed (null field, empty array, a step that throws) — see [workflows skill](.agents/skills/keystroke-workflows/SKILL.md).
|
|
80
|
-
- **Connected apps aren't in `.env`** — `keystroke deploy` never uploads `.env`; connect apps
|
|
81
|
-
- **Shipping to prod** — full vs filtered deploy, WIP ignore directives: [deploy skill](.agents/skills/keystroke-deploy/SKILL.md).
|
|
73
|
+
- **Connected apps aren't in `.env`** — `keystroke deploy` never uploads `.env`; connect apps (aka, integrations) for your project with `keystroke connect <slug>`. See [apps skill](.agents/skills/keystroke-apps/SKILL.md).
|
|
74
|
+
- **Shipping to prod** — determine if you should do a full vs filtered deploy, or use WIP ignore directives: [deploy skill](.agents/skills/keystroke-deploy/SKILL.md).
|
|
75
|
+
- **Actions are leaf units** - An action never calls another action (including integration actions like `slackSendMessage`). Compose actions in a workflow, or attach an integration action directly as a workflow step / agent tool. An action may call an agent.
|
|
76
|
+
- **Workflow steps** - Workflows orchestrate actions, agent prompts, and LLM calls (`promptLlm`) as steps. There's no first-class "call another workflow" step — share logic by extracting it into `src/lib/` or an action, or expose a workflow as an agent tool (`defineWorkflowTool`) / over HTTP when you want it as its own tracked run.
|
|
77
|
+
- **Agent tools** - Agents can use actions, subagents (`defineSubagentTool`), workflows (`defineWorkflowTool`), and MCP tools as tools.
|
|
82
78
|
|
|
83
79
|
## Audit & debug (CLI)
|
|
84
80
|
|
|
85
|
-
Use the CLI
|
|
86
|
-
|
|
87
|
-
**Local** (developing / testing):
|
|
81
|
+
Use the CLI to inspect your deployed project.
|
|
88
82
|
|
|
89
83
|
```bash
|
|
90
|
-
keystroke
|
|
91
|
-
keystroke
|
|
92
|
-
keystroke workflow
|
|
93
|
-
keystroke
|
|
94
|
-
keystroke
|
|
84
|
+
keystroke workflow run <workflow-slug> --input '{}'
|
|
85
|
+
keystroke workflow runs list <workflow-slug>
|
|
86
|
+
keystroke workflow runs get <workflow-slug> <run-id> --include steps,trace
|
|
87
|
+
keystroke agent sessions list <agent-slug>
|
|
88
|
+
keystroke agent sessions get <agent-slug> <session-id> --include messages,trace
|
|
95
89
|
keystroke app list
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
**Cloud** (deployed — `keystroke config use cloud` or after deploy):
|
|
99
|
-
|
|
100
|
-
```bash
|
|
101
90
|
keystroke trigger list
|
|
102
|
-
keystroke trigger url <trigger-
|
|
103
|
-
keystroke trigger runs list <trigger-
|
|
91
|
+
keystroke trigger url <trigger-slug>
|
|
92
|
+
keystroke trigger runs list <trigger-slug>:<workflow-slug>
|
|
104
93
|
```
|
|
105
94
|
|
|
106
|
-
Read `src/` first
|
|
95
|
+
Read `src/` first; existing agents, workflows, actions, and triggers are your best map of how this project works.
|
|
96
|
+
|
|
97
|
+
## Skills
|
|
107
98
|
|
|
108
|
-
|
|
99
|
+
Whenever you work with your Keystroke project, you should review relevant skills. Skills can be found in `.agents/skills/` and are folders named with the following format: `keystroke-[name]`.
|
|
109
100
|
|
|
110
101
|
| Skill | Load when |
|
|
111
102
|
| ------------------------------------------------------------ | ---------------------------------- |
|
|
112
|
-
| [cli](.agents/skills/keystroke-cli/SKILL.md) |
|
|
103
|
+
| [cli](.agents/skills/keystroke-cli/SKILL.md) | Deploy, run, inspect; projects & auth |
|
|
113
104
|
| [deploy](.agents/skills/keystroke-deploy/SKILL.md) | Build, full/filtered deploy, WIP ignore |
|
|
114
105
|
| [agents](.agents/skills/keystroke-agents/SKILL.md) | Building or debugging agents |
|
|
115
106
|
| [workflows](.agents/skills/keystroke-workflows/SKILL.md) | Orchestration, action steps |
|
|
116
107
|
| [actions](.agents/skills/keystroke-actions/SKILL.md) | Shared steps, integration tools |
|
|
117
108
|
| [triggers](.agents/skills/keystroke-triggers/SKILL.md) | Cron, webhook, poll automation |
|
|
118
|
-
| [
|
|
109
|
+
| [channels](.agents/skills/keystroke-channels/SKILL.md) | Slack / external channel bindings |
|
|
119
110
|
| [skills](.agents/skills/keystroke-skills/SKILL.md) | `src/skills/` playbooks |
|
|
120
111
|
| [files](.agents/skills/keystroke-files/SKILL.md) | Agent workspace files |
|
|
121
112
|
| [apps](.agents/skills/keystroke-apps/SKILL.md) | Connectable apps, catalog, connect |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: keystroke-actions
|
|
3
|
-
description: Define keystroke actions — workflow steps and agent tools.
|
|
3
|
+
description: Define keystroke actions — workflow steps and agent tools. Custom API actions use a credential (defineCredential) or an app wrapper (app.action); catalog integrations import from npm packages. Use when authoring src/actions/ or wiring integrations.
|
|
4
4
|
metadata:
|
|
5
5
|
keystroke-domain: actions
|
|
6
6
|
---
|
|
@@ -9,28 +9,54 @@ metadata:
|
|
|
9
9
|
|
|
10
10
|
One **executable unit** used everywhere: workflow steps, agent tools, and codemode host calls.
|
|
11
11
|
|
|
12
|
-
Anything that calls an external API **
|
|
12
|
+
Anything that calls an external API needs a **credential**. Two paths: declare a standalone `defineCredential` on a `defineAction` (simplest), or use an **app** wrapper (`app.action()`) when several actions share one connection or you've synced a catalog/custom app into `src/apps/`. See [apps skill](.agents/skills/keystroke-apps/SKILL.md).
|
|
13
13
|
|
|
14
14
|
## Actions are leaf units — never call an action from an action
|
|
15
15
|
|
|
16
|
-
An action's `run` must **not** call another action (yours or an integration action like `
|
|
16
|
+
An action's `run` must **not** call another action (yours or an integration action like `slackSendMessage`). Composition belongs in a **workflow**; an integration action is used directly as a workflow step or agent tool — not wrapped in a custom action.
|
|
17
17
|
|
|
18
18
|
```ts
|
|
19
19
|
// ❌ Don't: an action that calls another action
|
|
20
|
-
import {
|
|
20
|
+
import { slackSendMessage } from "@keystrokehq/slack/actions";
|
|
21
21
|
export const notify = defineAction({ slug: "notify",
|
|
22
|
-
run: async (input) =>
|
|
22
|
+
run: async (input) => slackSendMessage.run({ channel: input.channel, markdown_text: input.text }),
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
// ✅ Do: use the integration action directly as a workflow step
|
|
26
26
|
async run(input) {
|
|
27
|
-
return
|
|
27
|
+
return slackSendMessage.run({ channel: input.channel, markdown_text: buildText(input) });
|
|
28
28
|
}
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
This is enforced at runtime (calling an action inside an action throws) and by lint (`no-restricted-imports` blocks importing `@keystrokehq/*/actions` inside `src/actions/`). An action **may** call an agent — see below.
|
|
32
32
|
|
|
33
|
-
##
|
|
33
|
+
## Custom API actions (standalone credential)
|
|
34
|
+
|
|
35
|
+
The simplest custom integration: declare a `defineCredential` and consume it from a plain `defineAction`. No app needed.
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import { defineAction } from "@keystrokehq/keystroke/action";
|
|
39
|
+
import { defineCredential } from "@keystrokehq/keystroke/credentials";
|
|
40
|
+
import { z } from "zod";
|
|
41
|
+
|
|
42
|
+
const acme = defineCredential({ key: "acme", fields: { apiKey: z.string() } });
|
|
43
|
+
|
|
44
|
+
export const createAcmeTicket = defineAction({
|
|
45
|
+
slug: "create-acme-ticket",
|
|
46
|
+
input: z.object({ title: z.string() }),
|
|
47
|
+
output: z.object({ id: z.string() }),
|
|
48
|
+
credentials: [acme] as const,
|
|
49
|
+
async run(input, credentials) {
|
|
50
|
+
return createTicket({ apiKey: credentials.acme.apiKey, title: input.title });
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Connect it with `keystroke credentials set acme --set apiKey=@env:ACME_API_KEY --scope org`.
|
|
56
|
+
|
|
57
|
+
## App-backed actions (shared connection / synced apps)
|
|
58
|
+
|
|
59
|
+
Reach for an app when several actions share one connection, or when you've synced a catalog/custom app:
|
|
34
60
|
|
|
35
61
|
1. **App first** — create in the dashboard or `keystroke app create`, then `keystroke app sync <slug>` → `src/apps/<name>/app.ts`
|
|
36
62
|
2. **Connect** — `keystroke connect <slug>` (see [apps skill](.agents/skills/keystroke-apps/SKILL.md))
|
|
@@ -51,14 +77,14 @@ export const kwatchListAlerts = kwatch.action({
|
|
|
51
77
|
});
|
|
52
78
|
```
|
|
53
79
|
|
|
54
|
-
|
|
80
|
+
Use an app wrapper (over a standalone credential) when a family of actions should share one connection definition.
|
|
55
81
|
|
|
56
|
-
## Pure actions (no
|
|
82
|
+
## Pure actions (no credential)
|
|
57
83
|
|
|
58
|
-
Use `defineAction`
|
|
84
|
+
Use `defineAction` with no `credentials` when the step needs no connection at all (pure logic, local transforms):
|
|
59
85
|
|
|
60
86
|
```ts
|
|
61
|
-
import { defineAction } from "@keystrokehq/action";
|
|
87
|
+
import { defineAction } from "@keystrokehq/keystroke/action";
|
|
62
88
|
import { z } from "zod";
|
|
63
89
|
|
|
64
90
|
export const triage = defineAction({
|
|
@@ -77,7 +103,7 @@ Official integrations ship as npm packages with pre-built actions. Discover with
|
|
|
77
103
|
|
|
78
104
|
```ts
|
|
79
105
|
import { exaSearch } from "@keystrokehq/exa/actions";
|
|
80
|
-
import {
|
|
106
|
+
import { slackSendMessage } from "@keystrokehq/slack/actions";
|
|
81
107
|
```
|
|
82
108
|
|
|
83
109
|
Detail: [catalog-and-imports.md](references/catalog-and-imports.md).
|
|
@@ -98,6 +124,28 @@ export const researchSignup = defineAction({
|
|
|
98
124
|
});
|
|
99
125
|
```
|
|
100
126
|
|
|
127
|
+
## Credential scope & resolution
|
|
128
|
+
|
|
129
|
+
When a credential's scope isn't pinned, the runtime resolves it **project default first, then org default**. Pin a scope per-use with `.scope()` (the authoring-side counterpart to the CLI `--scope` flag) — it returns a fresh action that still binds like any other step/tool:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
await myAction.scope("user").run(input); // workflow step
|
|
133
|
+
tools: [myAction.scope("user")]; // agent tool
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
When several instances exist for one app + scope and none is the default, pin an exact instance to the consuming step/tool from the CLI: `keystroke credentials assignments assign --workflow <slug> --credential <app>/<slug> --consumer step:<slug>#0` (see [apps skill](.agents/skills/keystroke-apps/SKILL.md)).
|
|
137
|
+
|
|
138
|
+
## Long-running actions: honor the abort signal
|
|
139
|
+
|
|
140
|
+
`run` receives a third `ctx` argument with an `AbortSignal`. Thread it into fetches/SDK calls so the framework can cancel work cleanly:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
async run(input, credentials, ctx) {
|
|
144
|
+
const res = await fetch(url, { signal: ctx?.signal });
|
|
145
|
+
return res.json();
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
101
149
|
## Where actions run
|
|
102
150
|
|
|
103
151
|
| Consumer | Usage |
|
|
@@ -9,15 +9,44 @@
|
|
|
9
9
|
|
|
10
10
|
```ts
|
|
11
11
|
import { exaSearch, exaAnswer } from "@keystrokehq/exa/actions";
|
|
12
|
-
import {
|
|
12
|
+
import { slackSendMessage } from "@keystrokehq/slack/actions";
|
|
13
13
|
import { googlesuperSendEmail } from "@keystrokehq/googlesuper/actions";
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
Catalog packages ship a pre-built app + actions — you do not author `src/apps/` for these.
|
|
17
17
|
|
|
18
|
-
## Custom integrations
|
|
18
|
+
## Custom integrations
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
Two supported paths. Pick based on whether actions share a connection.
|
|
21
|
+
|
|
22
|
+
### Standalone credential (simplest)
|
|
23
|
+
|
|
24
|
+
Declare a `defineCredential` and consume it from a plain `defineAction`:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { defineAction } from "@keystrokehq/keystroke/action";
|
|
28
|
+
import { defineCredential } from "@keystrokehq/keystroke/credentials";
|
|
29
|
+
import { z } from "zod";
|
|
30
|
+
|
|
31
|
+
const acme = defineCredential({ key: "acme", fields: { apiKey: z.string() } });
|
|
32
|
+
|
|
33
|
+
export const fetchStatus = defineAction({
|
|
34
|
+
slug: "fetch-status",
|
|
35
|
+
input: z.object({}),
|
|
36
|
+
output: z.object({ ok: z.boolean() }),
|
|
37
|
+
credentials: [acme] as const,
|
|
38
|
+
async run(_input, credentials) {
|
|
39
|
+
const { apiKey } = credentials.acme;
|
|
40
|
+
return { ok: true };
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Connect it with `keystroke credentials set acme --set apiKey=@env:ACME_API_KEY --scope org`.
|
|
46
|
+
|
|
47
|
+
### App wrapper (shared connection / synced apps)
|
|
48
|
+
|
|
49
|
+
Use an app when several actions share one connection, or when you've synced a custom/catalog app:
|
|
21
50
|
|
|
22
51
|
1. Create the app (dashboard or `keystroke app create --name ... --field ...`)
|
|
23
52
|
2. Sync: `keystroke app sync <slug>` → `src/apps/<name>/app.ts`
|