@keystrokehq/cli 1.0.2 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/README.md +3 -3
  2. package/dist/{dist-DEwhZoDf.mjs → dist-B1SvZCxJ.mjs} +2 -2
  3. package/dist/{dist-DEwhZoDf.mjs.map → dist-B1SvZCxJ.mjs.map} +1 -1
  4. package/dist/dist-BjnCSbC2.mjs +3 -0
  5. package/dist/{dist-DtUH9rOc.mjs → dist-C0QtLjpR.mjs} +6 -8
  6. package/dist/dist-C0QtLjpR.mjs.map +1 -0
  7. package/dist/{dist-_46DIPBj.mjs → dist-DR0-KRom.mjs} +9 -2
  8. package/dist/{dist-_46DIPBj.mjs.map → dist-DR0-KRom.mjs.map} +1 -1
  9. package/dist/index.mjs +11 -10
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/{maybe-auto-update-DGBkB562.mjs → maybe-auto-update-DxAS-9S7.mjs} +2 -2
  12. package/dist/{maybe-auto-update-DGBkB562.mjs.map → maybe-auto-update-DxAS-9S7.mjs.map} +1 -1
  13. package/dist/skills-bundle/_AGENTS.md +5 -5
  14. package/dist/skills-bundle/skills/keystroke-actions/SKILL.md +39 -7
  15. package/dist/skills-bundle/skills/keystroke-actions/references/catalog-and-imports.md +25 -19
  16. package/dist/skills-bundle/skills/keystroke-agents/SKILL.md +1 -1
  17. package/dist/skills-bundle/skills/keystroke-apps/SKILL.md +133 -0
  18. package/dist/skills-bundle/skills/keystroke-apps/references/cli-and-catalog.md +66 -0
  19. package/dist/skills-bundle/skills/keystroke-cli/SKILL.md +2 -2
  20. package/dist/skills-bundle/skills/keystroke-cli/references/api-targets.md +2 -2
  21. package/dist/skills-bundle/skills/keystroke-deploy/SKILL.md +2 -2
  22. package/dist/skills-bundle/skills/keystroke-gateways/SKILL.md +2 -2
  23. package/dist/skills-bundle/skills/keystroke-gateways/references/slack-setup.md +1 -1
  24. package/dist/skills-bundle/skills/keystroke-workflows/SKILL.md +1 -1
  25. package/dist/skills-bundle/skills/keystroke-workflows/references/authoring.md +2 -2
  26. package/dist/{version-CKAYz1xm.mjs → version-TtPeAEMz.mjs} +4 -4
  27. package/dist/version-TtPeAEMz.mjs.map +1 -0
  28. package/package.json +1 -1
  29. package/dist/dist-BrhJhyvP.mjs +0 -3
  30. package/dist/dist-DtUH9rOc.mjs.map +0 -1
  31. package/dist/skills-bundle/skills/keystroke-credentials/SKILL.md +0 -108
  32. package/dist/skills-bundle/skills/keystroke-credentials/references/cli-and-oauth.md +0 -51
  33. package/dist/version-CKAYz1xm.mjs.map +0 -1
@@ -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-CKAYz1xm.mjs";
2
+ import { l as getCliConfigDir, r as detectPackageManager, s as resolveCliRoot, t as readCliVersion } from "./version-TtPeAEMz.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-DGBkB562.mjs.map
408
+ //# sourceMappingURL=maybe-auto-update-DxAS-9S7.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"maybe-auto-update-DGBkB562.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-DxAS-9S7.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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"}
@@ -60,8 +60,8 @@ Required for cloud/platform commands and some trigger operations. Not required f
60
60
  | Agents | `src/agents/` | `@keystrokehq/keystroke/agent` |
61
61
  | Workflows | `src/workflows/` | `@keystrokehq/keystroke/workflow` |
62
62
  | Triggers | `src/triggers/` | `@keystrokehq/keystroke/trigger` |
63
- | Integrations | action/tool imports | `@keystrokehq/googlesuper`, `@keystrokehq/slack`, |
64
- | Credentials | vault + OAuth | `keystroke credentials`, `keystroke connect` |
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
65
  | Skills / files | `src/skills/`, `src/files/` | attached on `defineAgent` |
66
66
 
67
67
  Default-export each module — the server discovers files under `src/`.
@@ -77,7 +77,7 @@ A few things that are easy to skip and cause common failures:
77
77
  - **Research real APIs before mirroring them** — web search and fetch the actual reference & explore relevant docs; don't guess endpoints or payload shapes.
78
78
  - **Run before you depend or deploy** — run the workflow/agent locally and read the real output (`keystroke workflow runs get <key> <run-id> --include steps,trace`) before wiring dependent steps.
79
79
  - **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
- - **Cloud creds aren't in `.env`** — `keystroke deploy` never uploads `.env`; credentials that run in the cloud must be set against the cloud target. See [credentials skill](.agents/skills/keystroke-credentials/SKILL.md).
80
+ - **Connected apps aren't in `.env`** — `keystroke deploy` never uploads `.env`; connect apps on the cloud target with `keystroke connect <slug>`. See [apps skill](.agents/skills/keystroke-apps/SKILL.md).
81
81
  - **Shipping to prod** — full vs filtered deploy, WIP ignore directives: [deploy skill](.agents/skills/keystroke-deploy/SKILL.md).
82
82
 
83
83
  ## Audit & debug (CLI)
@@ -92,7 +92,7 @@ keystroke agent sessions get <agent-key> <session-id> --include messages,trace
92
92
  keystroke workflow run <workflow-key> --input '{}'
93
93
  keystroke workflow runs list <workflow-key>
94
94
  keystroke workflow runs get <workflow-key> <run-id> --include steps,trace
95
- keystroke credentials list
95
+ keystroke app list
96
96
  ```
97
97
 
98
98
  **Cloud** (deployed — `keystroke config use cloud` or after deploy):
@@ -118,4 +118,4 @@ Read `src/` first — existing agents, workflows, actions, and triggers are your
118
118
  | [gateways](.agents/skills/keystroke-gateways/SKILL.md) | Slack / messaging bindings |
119
119
  | [skills](.agents/skills/keystroke-skills/SKILL.md) | `src/skills/` playbooks |
120
120
  | [files](.agents/skills/keystroke-files/SKILL.md) | Agent workspace files |
121
- | [credentials](.agents/skills/keystroke-credentials/SKILL.md) | Vault, OAuth, connect |
121
+ | [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 with defineAction — workflow steps and agent tools, Zod IO, integration imports from Exa/Google/Slack. Use when authoring src/actions/ or browsing keystroke integrations.
3
+ description: Define keystroke actions — workflow steps and agent tools. App-backed actions flow through synced apps (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,6 +9,8 @@ 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 **flows through an app** — sync the app into `src/apps/` first, connect it, then author with `app.action()`. See [apps skill](.agents/skills/keystroke-apps/SKILL.md).
13
+
12
14
  ## Actions are leaf units — never call an action from an action
13
15
 
14
16
  An action's `run` must **not** call another action (yours or an integration action like `postMessage`). Composition belongs in a **workflow**; an integration action is used directly as a workflow step or agent tool — not wrapped in a custom action.
@@ -28,7 +30,32 @@ async run(input) {
28
30
 
29
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.
30
32
 
31
- ## Your own action
33
+ ## App-backed actions (custom integrations)
34
+
35
+ 1. **App first** — create in the dashboard or `keystroke app create`, then `keystroke app sync <slug>` → `src/apps/<name>/app.ts`
36
+ 2. **Connect** — `keystroke connect <slug>` (see [apps skill](.agents/skills/keystroke-apps/SKILL.md))
37
+ 3. **Action** — `app.action()` in `src/actions/`, reading `credentials[app.slug]` in `run`
38
+
39
+ ```ts
40
+ import { z } from "zod";
41
+ import { kwatch } from "../apps/kwatch/app";
42
+
43
+ export const kwatchListAlerts = kwatch.action({
44
+ slug: "kwatch-list-alerts",
45
+ input: z.object({}),
46
+ output: z.object({ ok: z.boolean(), alertCount: z.number() }),
47
+ async run(_input, credentials) {
48
+ const { apiKey } = credentials["keystroke/kwatch"];
49
+ return { ok: true, alertCount: 0 };
50
+ },
51
+ });
52
+ ```
53
+
54
+ Do not use bare `defineAction` for external API work — the app owns the connection.
55
+
56
+ ## Pure actions (no app)
57
+
58
+ Use `defineAction` directly only when the step needs no app connection (pure logic, local transforms):
32
59
 
33
60
  ```ts
34
61
  import { defineAction } from "@keystrokehq/action";
@@ -44,11 +71,16 @@ export const triage = defineAction({
44
71
  });
45
72
  ```
46
73
 
47
- Declare `credentials: [slackCredential] as const` on the action when it needs vault/OAuth resolution at runtime.
74
+ ## Catalog integration actions
75
+
76
+ Official integrations ship as npm packages with pre-built actions. Discover with `keystroke app search`, connect with `keystroke connect <slug>`, then import in workflows or agent tools — not in `src/actions/`:
48
77
 
49
- ## Integration actions
78
+ ```ts
79
+ import { exaSearch } from "@keystrokehq/exa/actions";
80
+ import { postMessage } from "@keystrokehq/slack/actions";
81
+ ```
50
82
 
51
- Published actions (`@keystrokehq/exa/actions`, `@keystrokehq/slack/actions`, etc.) are used as **workflow steps** or **agent tools** — not imported into `src/actions/`. Set integration env vars in `.env` and run `keystroke connect` for OAuth when needed.
83
+ Detail: [catalog-and-imports.md](references/catalog-and-imports.md).
52
84
 
53
85
  ## Call an agent from an action (allowed)
54
86
 
@@ -75,6 +107,6 @@ export const researchSignup = defineAction({
75
107
 
76
108
  ## Next references
77
109
 
78
- - [catalog-and-imports.md](references/catalog-and-imports.md) — plugins, credentials on actions
110
+ - [catalog-and-imports.md](references/catalog-and-imports.md) — catalog discovery, npm imports
79
111
 
80
- Related: [workflows](.agents/skills/keystroke-workflows/SKILL.md), [agents](.agents/skills/keystroke-agents/SKILL.md), [credentials](.agents/skills/keystroke-credentials/SKILL.md).
112
+ Related: [apps](.agents/skills/keystroke-apps/SKILL.md), [workflows](.agents/skills/keystroke-workflows/SKILL.md), [agents](.agents/skills/keystroke-agents/SKILL.md).
@@ -1,14 +1,11 @@
1
1
  # Integration actions
2
2
 
3
- ## Install & configure
3
+ ## Official catalog integrations
4
4
 
5
- 1. Add npm package (e.g. `@keystrokehq/exa`, `@keystrokehq/googlesuper`, `@keystrokehq/slack`)
6
- 2. Set env vars in `.env`
7
- 3. OAuth if needed: `keystroke connect google`
8
-
9
- ## Use integration actions
10
-
11
- Import in `src/workflows/` or on `defineAgent` `tools` — not in `src/actions/`:
5
+ 1. Discover: `keystroke app search <query>` `app show <slug>` → `app actions <slug>`
6
+ 2. Connect: `keystroke connect <slug>`
7
+ 3. Add npm package (e.g. `@keystrokehq/exa`, `@keystrokehq/googlesuper`, `@keystrokehq/slack`)
8
+ 4. Import in `src/workflows/` or on `defineAgent` `tools` — not in `src/actions/`:
12
9
 
13
10
  ```ts
14
11
  import { exaSearch, exaAnswer } from "@keystrokehq/exa/actions";
@@ -16,21 +13,30 @@ import { postMessage } from "@keystrokehq/slack/actions";
16
13
  import { googlesuperSendEmail } from "@keystrokehq/googlesuper/actions";
17
14
  ```
18
15
 
19
- ## Credentials on custom actions
16
+ Catalog packages ship a pre-built app + actions — you do not author `src/apps/` for these.
17
+
18
+ ## Custom integrations (app-first)
19
+
20
+ All custom API work flows through a synced app:
20
21
 
21
- When wrapping an API yourself:
22
+ 1. Create the app (dashboard or `keystroke app create --name ... --field ...`)
23
+ 2. Sync: `keystroke app sync <slug>` → `src/apps/<name>/app.ts`
24
+ 3. Connect: `keystroke connect <slug>`
25
+ 4. Author actions in `src/actions/` with `app.action()`:
22
26
 
23
27
  ```ts
24
- import { defineAction } from "@keystrokehq/action";
25
- import { slackCredential } from "@keystrokehq/slack";
26
-
27
- defineAction({
28
- slug: "my-notify",
29
- credentials: [slackCredential] as const,
30
- run: async (input, credentials) => {
31
- /* */
28
+ import { z } from "zod";
29
+ import { internalApi } from "../apps/internal-api/app";
30
+
31
+ export const fetchStatus = internalApi.action({
32
+ slug: "fetch-status",
33
+ input: z.object({}),
34
+ output: z.object({ ok: z.boolean() }),
35
+ async run(_input, credentials) {
36
+ const { apiKey } = credentials["acme/internal-api"];
37
+ return { ok: true };
32
38
  },
33
39
  });
34
40
  ```
35
41
 
36
- Integration-packaged actions usually handle this for you.
42
+ Re-run `keystroke app sync <slug>` after template changes on the platform. Full app lifecycle: [apps skill](../../keystroke-apps/SKILL.md).
@@ -68,4 +68,4 @@ Follow up in the same session: `--session-id <id>`.
68
68
  - [tools-mcp-codemode.md](references/tools-mcp-codemode.md) — actions as tools, MCP, codemode
69
69
  - [workflows-and-testing.md](references/workflows-and-testing.md) — sessions, workflow handoff
70
70
 
71
- Related: [actions](.agents/skills/keystroke-actions/SKILL.md), [workflows](.agents/skills/keystroke-workflows/SKILL.md), [files](.agents/skills/keystroke-files/SKILL.md), [credentials](.agents/skills/keystroke-credentials/SKILL.md).
71
+ Related: [actions](.agents/skills/keystroke-actions/SKILL.md), [workflows](.agents/skills/keystroke-workflows/SKILL.md), [files](.agents/skills/keystroke-files/SKILL.md), [apps](.agents/skills/keystroke-apps/SKILL.md).
@@ -0,0 +1,133 @@
1
+ ---
2
+ name: keystroke-apps
3
+ description: Connectable apps — defineApp, app.action, catalog discovery via keystroke app search, custom app create/sync, and connect. Use when wiring integrations, OAuth, API keys, or a deployed run fails because an app is not connected.
4
+ metadata:
5
+ keystroke-domain: apps
6
+ ---
7
+
8
+ # Apps
9
+
10
+ An **app** is a connectable integration: a slug, an auth kind, and (for custom apps) a field template. **Connected** = secrets for that app have been uploaded via the connect flow.
11
+
12
+ Secrets never live in source or `.env`. Each **target** (local server vs cloud project) has its own connections — `keystroke deploy` uploads code only, not connections. Connect apps on **every target** you run on.
13
+
14
+ Custom actions that call external APIs **must** go through an app — sync it into `src/apps/` first, then use `app.action()` in `src/actions/`. See [actions skill](.agents/skills/keystroke-actions/SKILL.md).
15
+
16
+ ## Two sources of apps
17
+
18
+ | Source | How you get it | Author in code |
19
+ | ------ | -------------- | -------------- |
20
+ | **Catalog** | ~1000 Composio integrations + first-party (`exa`, `slack`, `googlesuper`) | npm package `@keystrokehq/<slug>` — discover via CLI |
21
+ | **Custom** | Dashboard UI or `keystroke app create`, then `keystroke app sync <slug>` | `src/apps/<name>/app.ts` from sync |
22
+
23
+ ## Discover catalog integrations
24
+
25
+ ```bash
26
+ keystroke auth login
27
+ keystroke app search github # live Composio catalog
28
+ keystroke app show github
29
+ keystroke app actions github --search issue
30
+ keystroke app action GITHUB_CREATE_ISSUE
31
+ keystroke app list # apps registered in your org
32
+ ```
33
+
34
+ Discovery is read-only — there is no `app install` yet. To connect an app, its slug must be in your org registry (`app list`).
35
+
36
+ ## Create and sync custom apps
37
+
38
+ Create in the dashboard or CLI, then pull the template into your project:
39
+
40
+ ```bash
41
+ keystroke app create \
42
+ --name "Internal API" \
43
+ --description "Our internal service" \
44
+ --field apiKey:secret
45
+
46
+ keystroke app sync acme/internal-api # writes src/apps/internal-api/app.ts
47
+ ```
48
+
49
+ Re-run `sync` after template changes on the platform. Then author actions with `app.action()` in `src/actions/`.
50
+
51
+ ## Author with defineApp
52
+
53
+ Synced apps land as `defineApp` in `src/apps/<name>/app.ts`:
54
+
55
+ ```ts
56
+ import { defineApp } from "@keystrokehq/keystroke/app";
57
+ import { z } from "zod";
58
+
59
+ export const kwatch = defineApp({
60
+ slug: "keystroke/kwatch",
61
+ auth: "api_key",
62
+ credential: { apiKey: z.string() },
63
+ });
64
+ ```
65
+
66
+ Actions use `app.action()` — the app binding is automatic:
67
+
68
+ ```ts
69
+ import { kwatch } from "../apps/kwatch/app";
70
+
71
+ export const kwatchListAlerts = kwatch.action({
72
+ slug: "kwatch-list-alerts",
73
+ input: z.object({}),
74
+ output: z.object({ ok: z.boolean(), alertCount: z.number() }),
75
+ async run(_input, credentials) {
76
+ const { apiKey } = credentials["keystroke/kwatch"];
77
+ return { ok: true, alertCount: 0 };
78
+ },
79
+ });
80
+ ```
81
+
82
+ Auth kinds: `keystroke` (MCP/Composio catalog apps), `api_key` (custom fields), `oauth` (token resolved at runtime). In `run`, read connected fields as `credentials[app.slug]`.
83
+
84
+ ## Connect an app
85
+
86
+ `keystroke connect <slug>` opens the dashboard connect flow (OAuth or API-key form, depending on the app). This is how secrets get attached to an app — not via `.env`.
87
+
88
+ **Official integrations** — the cleanest path:
89
+
90
+ ```bash
91
+ keystroke connect google
92
+ keystroke connect slack
93
+ keystroke connect exa
94
+ ```
95
+
96
+ **Custom apps** — sync the template first, then connect:
97
+
98
+ ```bash
99
+ keystroke app sync acme/internal-api
100
+ keystroke connect acme/internal-api
101
+ ```
102
+
103
+ Run `connect` against **each target** you deploy to (local while developing; cloud after `keystroke config use cloud`). The dashboard **Apps** page offers the same flow.
104
+
105
+ Field names in the connect form match the app's synced template. Default scope is **project** — see [cli-and-catalog.md](references/cli-and-catalog.md).
106
+
107
+ ## Before deploy: connect on cloud
108
+
109
+ Cloud starts with **no connected apps**. After the first deploy:
110
+
111
+ ```bash
112
+ keystroke deploy --project <id>
113
+ keystroke config use cloud
114
+ keystroke connect google
115
+ keystroke connect exa
116
+ keystroke app list
117
+ ```
118
+
119
+ A runtime error about a missing connection almost always means the app was connected locally but not on the cloud target.
120
+
121
+ ## Audit
122
+
123
+ ```bash
124
+ keystroke app list # org-registered apps
125
+ ```
126
+
127
+ Failed integration call → confirm target (`keystroke config show`), the app is connected on that target, and the action uses the right slug.
128
+
129
+ ## Next references
130
+
131
+ - [cli-and-catalog.md](references/cli-and-catalog.md) — `keystroke app` commands, connect, scopes, local vs cloud
132
+
133
+ Related: [cli](.agents/skills/keystroke-cli/SKILL.md), [actions](.agents/skills/keystroke-actions/SKILL.md), [gateways](.agents/skills/keystroke-gateways/SKILL.md).
@@ -0,0 +1,66 @@
1
+ # CLI, catalog, connect
2
+
3
+ ## Log in once
4
+
5
+ ```bash
6
+ keystroke auth login
7
+ keystroke auth status
8
+ ```
9
+
10
+ Token is stored in the OS keychain and reused for **cloud** CLI commands. Switching local ↔ cloud does not require re-auth. See [cli skill](.agents/skills/keystroke-cli/SKILL.md).
11
+
12
+ ## keystroke app commands
13
+
14
+ All org-scoped; output is JSON.
15
+
16
+ | Command | Purpose |
17
+ | ------- | ------- |
18
+ | `app list` | Apps registered/connectable in your org |
19
+ | `app search <query>` | Search live Composio catalog (`--category`, `--limit`, `--cursor`) |
20
+ | `app show <slug>` | Single catalog app details |
21
+ | `app actions [slug]` | List actions for an app, or global search with `--search` (no slug) |
22
+ | `app action <tool-slug>` | Full input/output JSON schema for one action |
23
+ | `app create` | Create custom org `api_key` app (`--name`, `--slug`, `--description`, `--field`) |
24
+ | `app sync <slug>` | Write `src/apps/<name>/app.ts` from platform template (`--dir`) |
25
+
26
+ `--field` syntax: `key`, `key:secret`, `key:optional`, or `key:secret:optional` (repeatable).
27
+
28
+ ## Connect an app
29
+
30
+ `keystroke connect <slug>` attaches secrets to an app via the dashboard connect flow:
31
+
32
+ ```bash
33
+ keystroke connect google # official OAuth
34
+ keystroke connect slack
35
+ keystroke connect exa # official API key
36
+ keystroke connect acme/internal-api # custom app (sync first)
37
+ keystroke connect <slug> --print-url # deeplink without opening browser
38
+ ```
39
+
40
+ - **Official integrations** — always prefer `keystroke connect <slug>`
41
+ - **Custom apps** — `keystroke app sync <slug>` first, then `keystroke connect <slug>`
42
+ - Slug must exist in your org registry (`app list`)
43
+ - Connect once per **target** (local dev server vs cloud deployed project)
44
+ - Dashboard at `:3000` → **Apps** offers the same flow
45
+
46
+ ## Scopes
47
+
48
+ When you connect an app, the connection is stored at one scope:
49
+
50
+ | Scope | When |
51
+ | ----- | ---- |
52
+ | `project` (default) | Tie to one project — use unless told otherwise |
53
+ | `org` | Share with every user and project — only on explicit request |
54
+ | `user` | Single user's personal connection |
55
+
56
+ At runtime resolution is **org first, then project**. Get the active project id from `keystroke config show`.
57
+
58
+ ## `.env` vs connected apps
59
+
60
+ | Holds | `.env` | Connected apps (via connect) |
61
+ | ----- | ------ | ------------------------------ |
62
+ | Model keys (`ANTHROPIC_API_KEY`), DB URL, OAuth **provider** config (`SLACK_CLIENT_ID`, `GOOGLE_*`) | yes — local boot only | — |
63
+ | Integration secrets resolved at runtime | — | yes |
64
+ | Reaches the cloud? | no | yes, when connected on cloud target |
65
+
66
+ App secrets are **not** read from `.env` — connect the app with `keystroke connect`. Connections follow the active target (local while `keystroke dev` runs; cloud after deploy). Re-connect on the cloud target after deploy.
@@ -111,7 +111,7 @@ Full detail: [api-targets.md](references/api-targets.md).
111
111
  | `dev`, `start`, `build` | Local project only |
112
112
  | `project list`, `project create` | Platform control plane (org-scoped) |
113
113
  | `deploy` | Platform (sets `activeProjectId` + `apiTarget=platform`) |
114
- | `workflow`, `agent`, `trigger`, `credentials`, `connect`, `health` | Follows resolved target |
114
+ | `workflow`, `agent`, `trigger`, `app`, `connect`, `health` | Follows resolved target |
115
115
  | `auth login` | Web dashboard (`webUrl`); token reused for cloud API calls |
116
116
 
117
117
  ## Audit & debug
@@ -129,4 +129,4 @@ keystroke trigger list --endpoint shared-hub
129
129
  keystroke trigger url incoming-message
130
130
  ```
131
131
 
132
- Related: [deploy](.agents/skills/keystroke-deploy/SKILL.md), [credentials](.agents/skills/keystroke-credentials/SKILL.md), [workflows](.agents/skills/keystroke-workflows/SKILL.md), [agents](.agents/skills/keystroke-agents/SKILL.md), [triggers](.agents/skills/keystroke-triggers/SKILL.md).
132
+ Related: [deploy](.agents/skills/keystroke-deploy/SKILL.md), [apps](.agents/skills/keystroke-apps/SKILL.md), [workflows](.agents/skills/keystroke-workflows/SKILL.md), [agents](.agents/skills/keystroke-agents/SKILL.md), [triggers](.agents/skills/keystroke-triggers/SKILL.md).
@@ -1,6 +1,6 @@
1
1
  # API target routing
2
2
 
3
- The CLI resolves a `baseUrl` before runtime commands (`workflow`, `agent`, `trigger`, `credentials`, `connect`, `health`). Implementation: `apps/cli/src/resolve-api-target.ts`.
3
+ The CLI resolves a `baseUrl` before runtime commands (`workflow`, `agent`, `trigger`, `app`, `connect`, `health`). Implementation: `apps/cli/src/resolve-api-target.ts`.
4
4
 
5
5
  ## Mental model
6
6
 
@@ -15,7 +15,7 @@ Auth is independent of target. `keystroke auth login` stores a bearer token in t
15
15
  | ----------------- | -------------------------- | -------------------------------- |
16
16
  | `serverUrl` | `http://localhost:3001` | Local keystroke server API |
17
17
  | `platformUrl` | `https://api.keystroke.ai` | Keystroke platform control plane |
18
- | `webUrl` | `https://app.keystroke.ai` | Dashboard + device login |
18
+ | `webUrl` | `https://keystroke.ai` | Dashboard + device login |
19
19
  | `activeProjectId` | unset | Deployed project id (persistent) |
20
20
  | `apiTarget` | inferred | `local` or `platform` |
21
21
 
@@ -78,7 +78,7 @@ Detail: [build-and-full-deploy.md](references/build-and-full-deploy.md).
78
78
  - **First deploy with `--filter`** — fails; run full deploy once.
79
79
  - **Wrong `--dir`** — builds a different tree than you edited.
80
80
  - **Expecting full refresh with `--filter`** — only matched modules rebuild; rest comes from active prod.
81
- - **Cloud creds in `.env`** — deploy never uploads `.env`; set credentials on the cloud target. See [credentials skill](../keystroke-credentials/SKILL.md).
81
+ - **Connected apps aren't in `.env`** — deploy never uploads `.env`; connect apps on the cloud target. See [apps skill](../keystroke-apps/SKILL.md).
82
82
 
83
83
  ## Audit deployed runtime
84
84
 
@@ -89,4 +89,4 @@ keystroke workflow run morning-check --input '{}'
89
89
  keystroke trigger list
90
90
  ```
91
91
 
92
- Related: [cli skill](../keystroke-cli/SKILL.md), [credentials skill](../keystroke-credentials/SKILL.md).
92
+ Related: [cli skill](../keystroke-cli/SKILL.md), [apps skill](../keystroke-apps/SKILL.md).
@@ -11,7 +11,7 @@ Gateways route **Slack (and similar) messages** to an agent — users chat in-ch
11
11
 
12
12
  ## Setup (Slack)
13
13
 
14
- 1. Set Slack app env vars in `.env` (see credentials skill)
14
+ 1. Set Slack app env vars in `.env` (see apps skill)
15
15
  2. `keystroke start` — open dashboard at `:3000`
16
16
  3. Complete Slack OAuth install
17
17
  4. **Gateways → Slack → Attach** — pick an agent key + channel
@@ -40,4 +40,4 @@ keystroke agent sessions get <agent-key> <session-id> --include messages,trace
40
40
 
41
41
  - [slack-setup.md](references/slack-setup.md) — env vars, attach flow
42
42
 
43
- Related: [agents](.agents/skills/keystroke-agents/SKILL.md), [credentials](.agents/skills/keystroke-credentials/SKILL.md).
43
+ Related: [agents](.agents/skills/keystroke-agents/SKILL.md), [apps](.agents/skills/keystroke-apps/SKILL.md).
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## .env
4
4
 
5
- Set Slack app credentials per `@keystrokehq/slack` docs (client id, secret, signing secret).
5
+ Set Slack OAuth provider env vars in `.env` (client id, secret, signing secret) — then connect the Slack app with `keystroke connect slack`.
6
6
 
7
7
  ## Install & bind
8
8
 
@@ -56,7 +56,7 @@ Unit-test through `executeWorkflow` from `@keystrokehq/workflow` — never call
56
56
 
57
57
  ## Next references
58
58
 
59
- - [authoring.md](references/authoring.md) — agents in actions, credentials, composition
59
+ - [authoring.md](references/authoring.md) — agents in actions, app connections, composition
60
60
  - [testing.md](references/testing.md) — unit tests, stubbing actions, run/trace debugging
61
61
 
62
62
  Related: [actions](.agents/skills/keystroke-actions/SKILL.md), [triggers](.agents/skills/keystroke-triggers/SKILL.md), [agents](.agents/skills/keystroke-agents/SKILL.md).
@@ -32,9 +32,9 @@ Use a wrapper action only when the agent call should also be an agent tool, or w
32
32
 
33
33
  When an action must call an agent (e.g. the action is reused as an agent tool), call `.prompt()` inside the action's `run`. That prompt is not a separate workflow step — the action is.
34
34
 
35
- ## Credentials
35
+ ## Connecting apps
36
36
 
37
- Actions that call Slack, Google, etc. declare `credentials: [slackCredential]` or use integration actions that handle resolution. Run `keystroke connect slack` before testing.
37
+ Sync the app into `src/apps/`, author with `app.action()`, then `keystroke connect <slug>` before testing. Catalog integration actions from npm packages work once the app is connected.
38
38
 
39
39
  ## Keys
40
40