@keystrokehq/cli 0.1.86 → 0.1.88
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/README.md +2 -2
- package/dist/index.mjs +181 -9
- package/dist/index.mjs.map +1 -1
- package/dist/maybe-auto-update-x2nClrff.mjs +139 -0
- package/dist/maybe-auto-update-x2nClrff.mjs.map +1 -0
- package/dist/{maybe-auto-update-B3xm1MYv.mjs → run-package-manager-update-BycMpenj.mjs} +183 -114
- package/dist/run-package-manager-update-BycMpenj.mjs.map +1 -0
- package/dist/skills-bundle/_AGENTS.mcp.md +9 -0
- package/dist/skills-bundle/_AGENTS.md +13 -3
- package/package.json +1 -1
- package/dist/maybe-auto-update-B3xm1MYv.mjs.map +0 -1
- package/dist/version-ikvpTH-2.mjs +0 -187
- package/dist/version-ikvpTH-2.mjs.map +0 -1
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { _ as isNewerVersion, a as formatReleaseAgeBlockMessage, g as resolveCliRoot, i as fetchLatestCliRelease, l as readCliVersion, n as computeReleaseAgeRetryAfter, o as isReleaseAgeBlock, r as readPnpmMinimumReleaseAgeMinutes, s as detectCliInstall, t as runPackageManagerUpdate, y as getCliConfigDir } from "./run-package-manager-update-BycMpenj.mjs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
//#region src/update/update-block-cache.ts
|
|
7
|
+
const CACHE_FILE = "update-check.json";
|
|
8
|
+
const GENERIC_RETRY_MS = 1440 * 60 * 1e3;
|
|
9
|
+
function cachePath(configDir = getCliConfigDir()) {
|
|
10
|
+
return join(configDir, CACHE_FILE);
|
|
11
|
+
}
|
|
12
|
+
function readUpdateBlockCache(configDir) {
|
|
13
|
+
const path = cachePath(configDir);
|
|
14
|
+
if (!existsSync(path)) return {};
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
17
|
+
} catch {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function writeUpdateBlockCache(cache, configDir) {
|
|
22
|
+
const dir = configDir ?? getCliConfigDir();
|
|
23
|
+
mkdirSync(dir, { recursive: true });
|
|
24
|
+
writeFileSync(cachePath(dir), `${JSON.stringify(cache, null, 2)}\n`, "utf8");
|
|
25
|
+
}
|
|
26
|
+
function shouldSkipCachedUpdate(cache, registryLatest, now = Date.now()) {
|
|
27
|
+
if (cache.registryLatest !== registryLatest) return false;
|
|
28
|
+
if (cache.blockedUntil) {
|
|
29
|
+
const blockedUntilMs = Date.parse(cache.blockedUntil);
|
|
30
|
+
if (Number.isFinite(blockedUntilMs) && blockedUntilMs > now) return true;
|
|
31
|
+
}
|
|
32
|
+
if (cache.lastFailedAt) {
|
|
33
|
+
const lastFailedMs = Date.parse(cache.lastFailedAt);
|
|
34
|
+
if (Number.isFinite(lastFailedMs) && now - lastFailedMs < GENERIC_RETRY_MS) return true;
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
function clearUpdateBlockCache(configDir) {
|
|
39
|
+
writeUpdateBlockCache({}, configDir);
|
|
40
|
+
}
|
|
41
|
+
function recordReleaseAgeBlock(options) {
|
|
42
|
+
writeUpdateBlockCache({
|
|
43
|
+
registryLatest: options.registryLatest,
|
|
44
|
+
blockedUntil: options.retryAfter?.toISOString()
|
|
45
|
+
}, options.configDir);
|
|
46
|
+
}
|
|
47
|
+
function recordGenericUpdateFailure(registryLatest, configDir) {
|
|
48
|
+
writeUpdateBlockCache({
|
|
49
|
+
registryLatest,
|
|
50
|
+
lastFailedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
51
|
+
}, configDir);
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/update/maybe-auto-update.ts
|
|
55
|
+
function isTopLevelUpdateCommand(args) {
|
|
56
|
+
const valueFlags = new Set([
|
|
57
|
+
"--project",
|
|
58
|
+
"--organization",
|
|
59
|
+
"--dir"
|
|
60
|
+
]);
|
|
61
|
+
for (let i = 0; i < args.length; i++) {
|
|
62
|
+
const arg = args[i];
|
|
63
|
+
if (arg === void 0) return false;
|
|
64
|
+
if (arg === "--") return args[i + 1] === "update";
|
|
65
|
+
if (arg.startsWith("-")) {
|
|
66
|
+
if (arg === "--local" || arg === "-h" || arg === "--help" || arg === "-V" || arg === "--version" || arg.startsWith("-V")) continue;
|
|
67
|
+
if (arg.includes("=")) continue;
|
|
68
|
+
if (valueFlags.has(arg) || arg.startsWith("--")) i++;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
return arg === "update";
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
function shouldSkipAutoUpdate(argv) {
|
|
76
|
+
if (process.env.KEYSTROKE_DEV || process.env.KEYSTROKE_SKIP_UPDATE || process.env.KEYSTROKE_UPDATING) return true;
|
|
77
|
+
if (process.env.CI === "true" || process.env.CI === "1") return true;
|
|
78
|
+
const args = argv.slice(2);
|
|
79
|
+
if (args.length === 0) return false;
|
|
80
|
+
if (isTopLevelUpdateCommand(args)) return true;
|
|
81
|
+
return args.every((arg) => arg === "-V" || arg === "--version" || arg === "-h" || arg === "--help" || arg.startsWith("-V"));
|
|
82
|
+
}
|
|
83
|
+
function reexecCli(argv) {
|
|
84
|
+
const result = spawnSync(process.execPath, argv.slice(1), {
|
|
85
|
+
stdio: "inherit",
|
|
86
|
+
env: {
|
|
87
|
+
...process.env,
|
|
88
|
+
KEYSTROKE_UPDATING: "1"
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
process.exit(result.status ?? 1);
|
|
92
|
+
}
|
|
93
|
+
function releaseAgeRetryAfter(install, publishedAt) {
|
|
94
|
+
if (install.packageManager !== "pnpm") return;
|
|
95
|
+
return computeReleaseAgeRetryAfter(publishedAt, readPnpmMinimumReleaseAgeMinutes());
|
|
96
|
+
}
|
|
97
|
+
async function maybeAutoUpdate(argv) {
|
|
98
|
+
if (shouldSkipAutoUpdate(argv)) return;
|
|
99
|
+
const install = detectCliInstall(resolveCliRoot(import.meta.url));
|
|
100
|
+
if (!install || install.kind === "local" && !install.projectRoot) return;
|
|
101
|
+
const currentVersion = readCliVersion();
|
|
102
|
+
const release = await fetchLatestCliRelease();
|
|
103
|
+
if (!release || !isNewerVersion(release.version, currentVersion)) {
|
|
104
|
+
clearUpdateBlockCache();
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (shouldSkipCachedUpdate(readUpdateBlockCache(), release.version)) return;
|
|
108
|
+
process.stderr.write(`Updating @keystrokehq/cli ${currentVersion} -> ${release.version} via ${install.packageManager}...\n`);
|
|
109
|
+
const result = runPackageManagerUpdate(install);
|
|
110
|
+
const installedVersion = readCliVersion();
|
|
111
|
+
if (isNewerVersion(installedVersion, currentVersion)) {
|
|
112
|
+
clearUpdateBlockCache();
|
|
113
|
+
process.stderr.write(`Updated @keystrokehq/cli ${currentVersion} -> ${installedVersion}.\n`);
|
|
114
|
+
reexecCli(argv);
|
|
115
|
+
}
|
|
116
|
+
if (result.ok) {
|
|
117
|
+
clearUpdateBlockCache();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (isReleaseAgeBlock(result.output)) {
|
|
121
|
+
const retryAfter = releaseAgeRetryAfter(install, release.publishedAt);
|
|
122
|
+
recordReleaseAgeBlock({
|
|
123
|
+
registryLatest: release.version,
|
|
124
|
+
retryAfter
|
|
125
|
+
});
|
|
126
|
+
process.stderr.write(`${formatReleaseAgeBlockMessage({
|
|
127
|
+
currentVersion,
|
|
128
|
+
availableVersion: release.version,
|
|
129
|
+
retryAfter
|
|
130
|
+
})}\n`);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
recordGenericUpdateFailure(release.version);
|
|
134
|
+
process.stderr.write("Auto-update failed; continuing with the current version.\n");
|
|
135
|
+
}
|
|
136
|
+
//#endregion
|
|
137
|
+
export { maybeAutoUpdate };
|
|
138
|
+
|
|
139
|
+
//# sourceMappingURL=maybe-auto-update-x2nClrff.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"maybe-auto-update-x2nClrff.mjs","names":[],"sources":["../src/update/update-block-cache.ts","../src/update/maybe-auto-update.ts"],"sourcesContent":["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 isTopLevelUpdateCommand(args: string[]): boolean {\n const valueFlags = new Set([\"--project\", \"--organization\", \"--dir\"]);\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n if (arg === undefined) {\n return false;\n }\n\n if (arg === \"--\") {\n return args[i + 1] === \"update\";\n }\n\n if (arg.startsWith(\"-\")) {\n if (\n arg === \"--local\" ||\n arg === \"-h\" ||\n arg === \"--help\" ||\n arg === \"-V\" ||\n arg === \"--version\" ||\n arg.startsWith(\"-V\")\n ) {\n continue;\n }\n\n if (arg.includes(\"=\")) {\n continue;\n }\n\n if (valueFlags.has(arg) || arg.startsWith(\"--\")) {\n i++;\n }\n continue;\n }\n\n return arg === \"update\";\n }\n\n return false;\n}\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 // Explicit `keystroke update` owns CLI + project package updates.\n if (isTopLevelUpdateCommand(args)) {\n return true;\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,8 +1,187 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { St as LOCAL_PLATFORM_ORIGIN, br as originFromPublicUrl } from "./dist-CEmkrm9b.mjs";
|
|
3
|
+
import Conf from "conf";
|
|
4
|
+
import { homedir } from "node:os";
|
|
3
5
|
import { dirname, join } from "node:path";
|
|
4
|
-
import { existsSync,
|
|
6
|
+
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
5
7
|
import { spawnSync } from "node:child_process";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
//#region src/resolve-platform-url.ts
|
|
10
|
+
const DEFAULT_WEB_URL = "https://keystroke.ai";
|
|
11
|
+
const DEFAULT_PLATFORM_URL = "https://api.keystroke.ai";
|
|
12
|
+
function webOriginFromUrl(webUrl) {
|
|
13
|
+
return originFromPublicUrl(webUrl, webUrl.replace(/\/+$/, ""));
|
|
14
|
+
}
|
|
15
|
+
/** Known web origins with a fixed platform API — undefined for custom/staging URLs. */
|
|
16
|
+
function knownPlatformUrlForWebUrl(webUrl) {
|
|
17
|
+
const webOrigin = webOriginFromUrl(webUrl);
|
|
18
|
+
if (webOrigin === "http://localhost:3000" || webOrigin === "http://127.0.0.1:3000") return LOCAL_PLATFORM_ORIGIN;
|
|
19
|
+
if (webOrigin === "https://keystroke.ai") return DEFAULT_PLATFORM_URL;
|
|
20
|
+
}
|
|
21
|
+
function resolvePlatformUrlForWebUrl(webUrl, options = {}) {
|
|
22
|
+
const explicit = options.platformUrl?.trim();
|
|
23
|
+
if (explicit) return explicit.replace(/\/+$/, "");
|
|
24
|
+
const known = knownPlatformUrlForWebUrl(webUrl);
|
|
25
|
+
if (known) return known;
|
|
26
|
+
return (options.fallback?.trim() || "https://api.keystroke.ai").replace(/\/+$/, "");
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/config.ts
|
|
30
|
+
function getCliConfigDir(cwd = join(homedir(), ".keystroke")) {
|
|
31
|
+
return cwd;
|
|
32
|
+
}
|
|
33
|
+
function getConfigDir(config) {
|
|
34
|
+
return dirname(config.path);
|
|
35
|
+
}
|
|
36
|
+
function getEffectiveApiTarget(config) {
|
|
37
|
+
const explicit = config.get("apiTarget");
|
|
38
|
+
if (explicit === "local" || explicit === "platform") return explicit;
|
|
39
|
+
if (process.env.KEYSTROKE_API_KEY?.trim()) return "platform";
|
|
40
|
+
return "local";
|
|
41
|
+
}
|
|
42
|
+
function syncPlatformUrlWithWebUrl(config) {
|
|
43
|
+
const known = knownPlatformUrlForWebUrl(config.get("webUrl"));
|
|
44
|
+
if (!known) return;
|
|
45
|
+
if (config.get("platformUrl") !== known) config.set("platformUrl", known);
|
|
46
|
+
}
|
|
47
|
+
function createCliConfig(cwd = getCliConfigDir()) {
|
|
48
|
+
const config = new Conf({
|
|
49
|
+
projectName: "keystroke",
|
|
50
|
+
cwd,
|
|
51
|
+
schema: {
|
|
52
|
+
webUrl: {
|
|
53
|
+
type: "string",
|
|
54
|
+
default: DEFAULT_WEB_URL
|
|
55
|
+
},
|
|
56
|
+
platformUrl: {
|
|
57
|
+
type: "string",
|
|
58
|
+
default: DEFAULT_PLATFORM_URL
|
|
59
|
+
},
|
|
60
|
+
apiTarget: {
|
|
61
|
+
type: "string",
|
|
62
|
+
enum: ["local", "platform"]
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
syncPlatformUrlWithWebUrl(config);
|
|
67
|
+
return config;
|
|
68
|
+
}
|
|
69
|
+
function getWebUrl(config) {
|
|
70
|
+
return config.get("webUrl");
|
|
71
|
+
}
|
|
72
|
+
function getPlatformUrl(config) {
|
|
73
|
+
const apiKey = process.env.KEYSTROKE_API_KEY?.trim();
|
|
74
|
+
const fromEnv = process.env.KEYSTROKE_PLATFORM_URL?.trim();
|
|
75
|
+
if (apiKey && fromEnv) return fromEnv.replace(/\/+$/, "");
|
|
76
|
+
return resolvePlatformUrlForWebUrl(getWebUrl(config), { fallback: config.get("platformUrl") });
|
|
77
|
+
}
|
|
78
|
+
//#endregion
|
|
79
|
+
//#region src/update/compare-version.ts
|
|
80
|
+
function parseVersion(version) {
|
|
81
|
+
const [major = 0, minor = 0, patch = 0] = (version.trim().replace(/^v/, "").split("-")[0] ?? "").split(".").map((part) => {
|
|
82
|
+
const value = Number.parseInt(part, 10);
|
|
83
|
+
return Number.isFinite(value) ? value : 0;
|
|
84
|
+
});
|
|
85
|
+
return [
|
|
86
|
+
major,
|
|
87
|
+
minor,
|
|
88
|
+
patch
|
|
89
|
+
];
|
|
90
|
+
}
|
|
91
|
+
function isNewerVersion(latest, current) {
|
|
92
|
+
const [latestMajor, latestMinor, latestPatch] = parseVersion(latest);
|
|
93
|
+
const [currentMajor, currentMinor, currentPatch] = parseVersion(current);
|
|
94
|
+
if (latestMajor !== currentMajor) return latestMajor > currentMajor;
|
|
95
|
+
if (latestMinor !== currentMinor) return latestMinor > currentMinor;
|
|
96
|
+
return latestPatch > currentPatch;
|
|
97
|
+
}
|
|
98
|
+
//#endregion
|
|
99
|
+
//#region src/project/resolve-cli-root.ts
|
|
100
|
+
function isCliPackage(dir) {
|
|
101
|
+
const packageJsonPath = join(dir, "package.json");
|
|
102
|
+
if (!existsSync(packageJsonPath)) return false;
|
|
103
|
+
return JSON.parse(readFileSync(packageJsonPath, "utf8")).name === "@keystrokehq/cli";
|
|
104
|
+
}
|
|
105
|
+
function resolveCliRoot(fromModuleUrl) {
|
|
106
|
+
let dir = dirname(fileURLToPath(fromModuleUrl));
|
|
107
|
+
while (dir !== dirname(dir)) {
|
|
108
|
+
if (isCliPackage(dir)) return dir;
|
|
109
|
+
dir = dirname(dir);
|
|
110
|
+
}
|
|
111
|
+
throw new Error("Could not resolve keystroke CLI package root");
|
|
112
|
+
}
|
|
113
|
+
//#endregion
|
|
114
|
+
//#region src/init/package-manager.ts
|
|
115
|
+
const MANAGERS = [
|
|
116
|
+
"pnpm",
|
|
117
|
+
"npm",
|
|
118
|
+
"yarn",
|
|
119
|
+
"bun"
|
|
120
|
+
];
|
|
121
|
+
function detectPackageManager() {
|
|
122
|
+
const userAgent = process.env.npm_config_user_agent ?? "";
|
|
123
|
+
for (const manager of MANAGERS) if (userAgent.startsWith(manager)) return manager;
|
|
124
|
+
if (process.env.PNPM_HOME) return "pnpm";
|
|
125
|
+
return "npm";
|
|
126
|
+
}
|
|
127
|
+
function resolvePackageManager(explicit) {
|
|
128
|
+
if (!explicit) return detectPackageManager();
|
|
129
|
+
const normalized = explicit.trim().toLowerCase();
|
|
130
|
+
if (!MANAGERS.includes(normalized)) throw new Error(`Unsupported package manager "${explicit}". Use npm, pnpm, yarn, or bun.`);
|
|
131
|
+
return normalized;
|
|
132
|
+
}
|
|
133
|
+
/** Resolve GitHub Packages auth for @keystrokehq/* when NODE_AUTH_TOKEN is unset. */
|
|
134
|
+
function resolveGithubPackagesToken() {
|
|
135
|
+
if (process.env.NODE_AUTH_TOKEN) return process.env.NODE_AUTH_TOKEN;
|
|
136
|
+
for (const key of ["GITHUB_TOKEN", "GH_TOKEN"]) {
|
|
137
|
+
const value = process.env[key];
|
|
138
|
+
if (value) return value;
|
|
139
|
+
}
|
|
140
|
+
const gh = spawnSync("gh", ["auth", "token"], { encoding: "utf8" });
|
|
141
|
+
if (gh.status === 0) {
|
|
142
|
+
const token = gh.stdout.trim();
|
|
143
|
+
if (token) return token;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function installDependencies(cwd, manager) {
|
|
147
|
+
const token = resolveGithubPackagesToken();
|
|
148
|
+
if (spawnSync(manager, ["install"], {
|
|
149
|
+
cwd,
|
|
150
|
+
stdio: "inherit",
|
|
151
|
+
env: token ? {
|
|
152
|
+
...process.env,
|
|
153
|
+
NODE_AUTH_TOKEN: token
|
|
154
|
+
} : process.env
|
|
155
|
+
}).status !== 0) throw new Error(`${manager} install failed`);
|
|
156
|
+
}
|
|
157
|
+
function installPlaygroundDependencies(cwd) {
|
|
158
|
+
if (spawnSync("pnpm", ["install", "--ignore-workspace"], {
|
|
159
|
+
cwd,
|
|
160
|
+
stdio: "inherit",
|
|
161
|
+
env: process.env
|
|
162
|
+
}).status !== 0) throw new Error("pnpm install failed");
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Build every workspace package so the playground's `link:` deps resolve to
|
|
166
|
+
* compiled `dist/`. Without this, `keystroke dev` fails with
|
|
167
|
+
* `ERR_MODULE_NOT_FOUND` against whichever package was never built. Turbo
|
|
168
|
+
* caches, so this is only slow on the first run.
|
|
169
|
+
*/
|
|
170
|
+
function buildPlaygroundWorkspace(monorepoRoot) {
|
|
171
|
+
const { NODE_OPTIONS: _drop, ...buildEnv } = process.env;
|
|
172
|
+
if (spawnSync("pnpm", ["run", "build"], {
|
|
173
|
+
cwd: monorepoRoot,
|
|
174
|
+
stdio: "inherit",
|
|
175
|
+
env: buildEnv
|
|
176
|
+
}).status !== 0) throw new Error("workspace build failed (pnpm run build)");
|
|
177
|
+
}
|
|
178
|
+
//#endregion
|
|
179
|
+
//#region src/version.ts
|
|
180
|
+
function readCliVersion() {
|
|
181
|
+
const packageJsonPath = join(resolveCliRoot(import.meta.url), "package.json");
|
|
182
|
+
return JSON.parse(readFileSync(packageJsonPath, "utf8")).version ?? "0.0.0";
|
|
183
|
+
}
|
|
184
|
+
//#endregion
|
|
6
185
|
//#region src/update/detect-cli-install.ts
|
|
7
186
|
function realpathSafe(path) {
|
|
8
187
|
try {
|
|
@@ -273,116 +452,6 @@ function runPackageManagerUpdate(install) {
|
|
|
273
452
|
return firstAttempt;
|
|
274
453
|
}
|
|
275
454
|
//#endregion
|
|
276
|
-
|
|
277
|
-
const CACHE_FILE = "update-check.json";
|
|
278
|
-
const GENERIC_RETRY_MS = 1440 * 60 * 1e3;
|
|
279
|
-
function cachePath(configDir = getCliConfigDir()) {
|
|
280
|
-
return join(configDir, CACHE_FILE);
|
|
281
|
-
}
|
|
282
|
-
function readUpdateBlockCache(configDir) {
|
|
283
|
-
const path = cachePath(configDir);
|
|
284
|
-
if (!existsSync(path)) return {};
|
|
285
|
-
try {
|
|
286
|
-
return JSON.parse(readFileSync(path, "utf8"));
|
|
287
|
-
} catch {
|
|
288
|
-
return {};
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
function writeUpdateBlockCache(cache, configDir) {
|
|
292
|
-
const dir = configDir ?? getCliConfigDir();
|
|
293
|
-
mkdirSync(dir, { recursive: true });
|
|
294
|
-
writeFileSync(cachePath(dir), `${JSON.stringify(cache, null, 2)}\n`, "utf8");
|
|
295
|
-
}
|
|
296
|
-
function shouldSkipCachedUpdate(cache, registryLatest, now = Date.now()) {
|
|
297
|
-
if (cache.registryLatest !== registryLatest) return false;
|
|
298
|
-
if (cache.blockedUntil) {
|
|
299
|
-
const blockedUntilMs = Date.parse(cache.blockedUntil);
|
|
300
|
-
if (Number.isFinite(blockedUntilMs) && blockedUntilMs > now) return true;
|
|
301
|
-
}
|
|
302
|
-
if (cache.lastFailedAt) {
|
|
303
|
-
const lastFailedMs = Date.parse(cache.lastFailedAt);
|
|
304
|
-
if (Number.isFinite(lastFailedMs) && now - lastFailedMs < GENERIC_RETRY_MS) return true;
|
|
305
|
-
}
|
|
306
|
-
return false;
|
|
307
|
-
}
|
|
308
|
-
function clearUpdateBlockCache(configDir) {
|
|
309
|
-
writeUpdateBlockCache({}, configDir);
|
|
310
|
-
}
|
|
311
|
-
function recordReleaseAgeBlock(options) {
|
|
312
|
-
writeUpdateBlockCache({
|
|
313
|
-
registryLatest: options.registryLatest,
|
|
314
|
-
blockedUntil: options.retryAfter?.toISOString()
|
|
315
|
-
}, options.configDir);
|
|
316
|
-
}
|
|
317
|
-
function recordGenericUpdateFailure(registryLatest, configDir) {
|
|
318
|
-
writeUpdateBlockCache({
|
|
319
|
-
registryLatest,
|
|
320
|
-
lastFailedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
321
|
-
}, configDir);
|
|
322
|
-
}
|
|
323
|
-
//#endregion
|
|
324
|
-
//#region src/update/maybe-auto-update.ts
|
|
325
|
-
function shouldSkipAutoUpdate(argv) {
|
|
326
|
-
if (process.env.KEYSTROKE_DEV || process.env.KEYSTROKE_SKIP_UPDATE || process.env.KEYSTROKE_UPDATING) return true;
|
|
327
|
-
if (process.env.CI === "true" || process.env.CI === "1") return true;
|
|
328
|
-
const args = argv.slice(2);
|
|
329
|
-
if (args.length === 0) return false;
|
|
330
|
-
return args.every((arg) => arg === "-V" || arg === "--version" || arg === "-h" || arg === "--help" || arg.startsWith("-V"));
|
|
331
|
-
}
|
|
332
|
-
function reexecCli(argv) {
|
|
333
|
-
const result = spawnSync(process.execPath, argv.slice(1), {
|
|
334
|
-
stdio: "inherit",
|
|
335
|
-
env: {
|
|
336
|
-
...process.env,
|
|
337
|
-
KEYSTROKE_UPDATING: "1"
|
|
338
|
-
}
|
|
339
|
-
});
|
|
340
|
-
process.exit(result.status ?? 1);
|
|
341
|
-
}
|
|
342
|
-
function releaseAgeRetryAfter(install, publishedAt) {
|
|
343
|
-
if (install.packageManager !== "pnpm") return;
|
|
344
|
-
return computeReleaseAgeRetryAfter(publishedAt, readPnpmMinimumReleaseAgeMinutes());
|
|
345
|
-
}
|
|
346
|
-
async function maybeAutoUpdate(argv) {
|
|
347
|
-
if (shouldSkipAutoUpdate(argv)) return;
|
|
348
|
-
const install = detectCliInstall(resolveCliRoot(import.meta.url));
|
|
349
|
-
if (!install || install.kind === "local" && !install.projectRoot) return;
|
|
350
|
-
const currentVersion = readCliVersion();
|
|
351
|
-
const release = await fetchLatestCliRelease();
|
|
352
|
-
if (!release || !isNewerVersion(release.version, currentVersion)) {
|
|
353
|
-
clearUpdateBlockCache();
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
356
|
-
if (shouldSkipCachedUpdate(readUpdateBlockCache(), release.version)) return;
|
|
357
|
-
process.stderr.write(`Updating @keystrokehq/cli ${currentVersion} -> ${release.version} via ${install.packageManager}...\n`);
|
|
358
|
-
const result = runPackageManagerUpdate(install);
|
|
359
|
-
const installedVersion = readCliVersion();
|
|
360
|
-
if (isNewerVersion(installedVersion, currentVersion)) {
|
|
361
|
-
clearUpdateBlockCache();
|
|
362
|
-
process.stderr.write(`Updated @keystrokehq/cli ${currentVersion} -> ${installedVersion}.\n`);
|
|
363
|
-
reexecCli(argv);
|
|
364
|
-
}
|
|
365
|
-
if (result.ok) {
|
|
366
|
-
clearUpdateBlockCache();
|
|
367
|
-
return;
|
|
368
|
-
}
|
|
369
|
-
if (isReleaseAgeBlock(result.output)) {
|
|
370
|
-
const retryAfter = releaseAgeRetryAfter(install, release.publishedAt);
|
|
371
|
-
recordReleaseAgeBlock({
|
|
372
|
-
registryLatest: release.version,
|
|
373
|
-
retryAfter
|
|
374
|
-
});
|
|
375
|
-
process.stderr.write(`${formatReleaseAgeBlockMessage({
|
|
376
|
-
currentVersion,
|
|
377
|
-
availableVersion: release.version,
|
|
378
|
-
retryAfter
|
|
379
|
-
})}\n`);
|
|
380
|
-
return;
|
|
381
|
-
}
|
|
382
|
-
recordGenericUpdateFailure(release.version);
|
|
383
|
-
process.stderr.write("Auto-update failed; continuing with the current version.\n");
|
|
384
|
-
}
|
|
385
|
-
//#endregion
|
|
386
|
-
export { maybeAutoUpdate };
|
|
455
|
+
export { getWebUrl as C, resolvePlatformUrlForWebUrl as E, getPlatformUrl as S, DEFAULT_WEB_URL as T, isNewerVersion as _, formatReleaseAgeBlockMessage as a, getConfigDir as b, detectManagerFromLockfile as c, detectPackageManager as d, installDependencies as f, resolveCliRoot as g, resolvePackageManager as h, fetchLatestCliRelease as i, readCliVersion as l, resolveGithubPackagesToken as m, computeReleaseAgeRetryAfter as n, isReleaseAgeBlock as o, installPlaygroundDependencies as p, readPnpmMinimumReleaseAgeMinutes as r, detectCliInstall as s, runPackageManagerUpdate as t, buildPlaygroundWorkspace as u, createCliConfig as v, DEFAULT_PLATFORM_URL as w, getEffectiveApiTarget as x, getCliConfigDir as y };
|
|
387
456
|
|
|
388
|
-
//# sourceMappingURL=
|
|
457
|
+
//# sourceMappingURL=run-package-manager-update-BycMpenj.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-package-manager-update-BycMpenj.mjs","names":[],"sources":["../src/resolve-platform-url.ts","../src/config.ts","../src/update/compare-version.ts","../src/project/resolve-cli-root.ts","../src/init/package-manager.ts","../src/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"],"sourcesContent":["import { LOCAL_PLATFORM_ORIGIN, originFromPublicUrl } from \"@keystrokehq/shared\";\n\nexport const DEFAULT_WEB_URL = \"https://keystroke.ai\";\nexport const DEFAULT_PLATFORM_URL = \"https://api.keystroke.ai\";\nexport const LOCAL_WEB_URL = \"http://localhost:3000\";\n\nexport type ResolvePlatformUrlOptions = {\n platformUrl?: string;\n /** Used when webUrl does not match a known deployment. */\n fallback?: string;\n};\n\nfunction webOriginFromUrl(webUrl: string): string {\n return originFromPublicUrl(webUrl, webUrl.replace(/\\/+$/, \"\"));\n}\n\n/** Known web origins with a fixed platform API — undefined for custom/staging URLs. */\nexport function knownPlatformUrlForWebUrl(webUrl: string): string | undefined {\n const webOrigin = webOriginFromUrl(webUrl);\n\n if (webOrigin === LOCAL_WEB_URL || webOrigin === \"http://127.0.0.1:3000\") {\n return LOCAL_PLATFORM_ORIGIN;\n }\n\n if (webOrigin === DEFAULT_WEB_URL) {\n return DEFAULT_PLATFORM_URL;\n }\n\n return undefined;\n}\n\nexport function resolvePlatformUrlForWebUrl(\n webUrl: string,\n options: ResolvePlatformUrlOptions = {},\n): string {\n const explicit = options.platformUrl?.trim();\n if (explicit) {\n return explicit.replace(/\\/+$/, \"\");\n }\n\n const known = knownPlatformUrlForWebUrl(webUrl);\n if (known) {\n return known;\n }\n\n const fallback = options.fallback?.trim() || DEFAULT_PLATFORM_URL;\n return fallback.replace(/\\/+$/, \"\");\n}\n","import Conf from \"conf\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\nimport {\n DEFAULT_PLATFORM_URL,\n DEFAULT_WEB_URL,\n knownPlatformUrlForWebUrl,\n resolvePlatformUrlForWebUrl,\n} from \"./resolve-platform-url\";\n\nexport type ApiTargetMode = \"local\" | \"platform\";\n\nexport type CliConfig = {\n webUrl: string;\n platformUrl: string;\n apiTarget?: ApiTargetMode;\n};\n\nexport function getCliConfigDir(cwd = join(homedir(), \".keystroke\")): string {\n return cwd;\n}\n\nexport function getConfigDir(config: Conf<CliConfig>): string {\n return dirname(config.path);\n}\n\nexport function getEffectiveApiTarget(config: Conf<CliConfig>): ApiTargetMode {\n const explicit = config.get(\"apiTarget\");\n if (explicit === \"local\" || explicit === \"platform\") {\n return explicit;\n }\n\n if (process.env.KEYSTROKE_API_KEY?.trim()) {\n return \"platform\";\n }\n\n return \"local\";\n}\n\nfunction syncPlatformUrlWithWebUrl(config: Conf<CliConfig>): void {\n const webUrl = config.get(\"webUrl\");\n const known = knownPlatformUrlForWebUrl(webUrl);\n if (!known) {\n return;\n }\n\n const stored = config.get(\"platformUrl\");\n if (stored !== known) {\n config.set(\"platformUrl\", known);\n }\n}\n\nexport function createCliConfig(cwd = getCliConfigDir()): Conf<CliConfig> {\n const config = new Conf<CliConfig>({\n projectName: \"keystroke\",\n cwd,\n schema: {\n webUrl: {\n type: \"string\",\n default: DEFAULT_WEB_URL,\n },\n platformUrl: {\n type: \"string\",\n default: DEFAULT_PLATFORM_URL,\n },\n apiTarget: {\n type: \"string\",\n enum: [\"local\", \"platform\"],\n },\n },\n });\n\n syncPlatformUrlWithWebUrl(config);\n return config;\n}\n\nexport function getWebUrl(config: Conf<CliConfig>): string {\n return config.get(\"webUrl\");\n}\n\nexport function getPlatformUrl(config: Conf<CliConfig>): string {\n const apiKey = process.env.KEYSTROKE_API_KEY?.trim();\n const fromEnv = process.env.KEYSTROKE_PLATFORM_URL?.trim();\n if (apiKey && fromEnv) {\n return fromEnv.replace(/\\/+$/, \"\");\n }\n return resolvePlatformUrlForWebUrl(getWebUrl(config), {\n fallback: config.get(\"platformUrl\"),\n });\n}\n","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, readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nfunction isCliPackage(dir: string): boolean {\n const packageJsonPath = join(dir, \"package.json\");\n if (!existsSync(packageJsonPath)) {\n return false;\n }\n\n const pkg = JSON.parse(readFileSync(packageJsonPath, \"utf8\")) as { name?: string };\n return pkg.name === \"@keystrokehq/cli\";\n}\n\nexport function resolveCliRoot(fromModuleUrl: string): string {\n let dir = dirname(fileURLToPath(fromModuleUrl));\n\n while (dir !== dirname(dir)) {\n if (isCliPackage(dir)) {\n return dir;\n }\n\n dir = dirname(dir);\n }\n\n throw new Error(\"Could not resolve keystroke CLI package root\");\n}\n","import { spawnSync } from \"node:child_process\";\n\nexport type PackageManager = \"npm\" | \"pnpm\" | \"yarn\" | \"bun\";\n\nconst MANAGERS: PackageManager[] = [\"pnpm\", \"npm\", \"yarn\", \"bun\"];\n\nexport function detectPackageManager(): PackageManager {\n const userAgent = process.env.npm_config_user_agent ?? \"\";\n\n for (const manager of MANAGERS) {\n if (userAgent.startsWith(manager)) {\n return manager;\n }\n }\n\n if (process.env.PNPM_HOME) {\n return \"pnpm\";\n }\n\n return \"npm\";\n}\n\nexport function resolvePackageManager(explicit?: string): PackageManager {\n if (!explicit) {\n return detectPackageManager();\n }\n\n const normalized = explicit.trim().toLowerCase();\n if (!MANAGERS.includes(normalized as PackageManager)) {\n throw new Error(`Unsupported package manager \"${explicit}\". Use npm, pnpm, yarn, or bun.`);\n }\n\n return normalized as PackageManager;\n}\n\n/** Resolve GitHub Packages auth for @keystrokehq/* when NODE_AUTH_TOKEN is unset. */\nexport function resolveGithubPackagesToken(): string | undefined {\n if (process.env.NODE_AUTH_TOKEN) {\n return process.env.NODE_AUTH_TOKEN;\n }\n\n for (const key of [\"GITHUB_TOKEN\", \"GH_TOKEN\"] as const) {\n const value = process.env[key];\n if (value) {\n return value;\n }\n }\n\n const gh = spawnSync(\"gh\", [\"auth\", \"token\"], { encoding: \"utf8\" });\n if (gh.status === 0) {\n const token = gh.stdout.trim();\n if (token) {\n return token;\n }\n }\n\n return undefined;\n}\n\nexport function installDependencies(cwd: string, manager: PackageManager): void {\n const token = resolveGithubPackagesToken();\n const env = token ? { ...process.env, NODE_AUTH_TOKEN: token } : process.env;\n\n const result = spawnSync(manager, [\"install\"], {\n cwd,\n stdio: \"inherit\",\n env,\n });\n\n if (result.status !== 0) {\n throw new Error(`${manager} install failed`);\n }\n}\n\nexport function installPlaygroundDependencies(cwd: string): void {\n const result = spawnSync(\"pnpm\", [\"install\", \"--ignore-workspace\"], {\n cwd,\n stdio: \"inherit\",\n env: process.env,\n });\n\n if (result.status !== 0) {\n throw new Error(\"pnpm install failed\");\n }\n}\n\n/**\n * Build every workspace package so the playground's `link:` deps resolve to\n * compiled `dist/`. Without this, `keystroke dev` fails with\n * `ERR_MODULE_NOT_FOUND` against whichever package was never built. Turbo\n * caches, so this is only slow on the first run.\n */\nexport function buildPlaygroundWorkspace(monorepoRoot: string): void {\n // Build with a clean Node env: the `keystroke-dev` wrapper sets\n // NODE_OPTIONS=--conditions=development, which would make tsdown/turbo resolve\n // their own deps to src/ and fail. Dropping NODE_OPTIONS builds to dist/ normally.\n const { NODE_OPTIONS: _drop, ...buildEnv } = process.env;\n const result = spawnSync(\"pnpm\", [\"run\", \"build\"], {\n cwd: monorepoRoot,\n stdio: \"inherit\",\n env: buildEnv,\n });\n\n if (result.status !== 0) {\n throw new Error(\"workspace build failed (pnpm run build)\");\n }\n}\n","import { readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { resolveCliRoot } from \"./project/resolve-cli-root\";\n\nexport function readCliVersion(): string {\n const packageJsonPath = join(resolveCliRoot(import.meta.url), \"package.json\");\n const pkg = JSON.parse(readFileSync(packageJsonPath, \"utf8\")) as { version?: string };\n return pkg.version ?? \"0.0.0\";\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\nexport function 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"],"mappings":"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"}
|
|
@@ -30,6 +30,15 @@ Draft or in-progress modules, full vs filtered deploy, and `@keystroke ignore`:
|
|
|
30
30
|
|
|
31
31
|
## Working habits
|
|
32
32
|
|
|
33
|
+
### Clarify as you build
|
|
34
|
+
|
|
35
|
+
Act as the user's automation engineer, not just an implementer. Before and while building, inspect the existing code, integration catalog, and connected credentials. Resolve what you can yourself; ask when a choice changes the outcome, access, or architecture.
|
|
36
|
+
|
|
37
|
+
- When several integrations, credentials, or designs fit, give the concrete options, recommend one briefly, and ask the user to choose. Never expose secret values.
|
|
38
|
+
- Prefer a workflow for a known, reliable path and an agent when runtime judgment, tool choice, or conversation is required.
|
|
39
|
+
- Batch related questions into one short message. Don't conduct an open-ended interview or ask what code, docs, or tools can answer.
|
|
40
|
+
- Use sensible, reversible defaults and state assumptions. Confirm before consequential side effects or ambiguous access.
|
|
41
|
+
|
|
33
42
|
### Building agents
|
|
34
43
|
|
|
35
44
|
Agents are useful when a task needs judgment, language understanding, tool use, or multi-turn context.
|
|
@@ -12,7 +12,16 @@ Users range from staff engineers to Zapier-caliber automation builders. You are
|
|
|
12
12
|
- **Narrate in outcomes, not plumbing.** "I'll set this to run every morning and message you a summary", not "deploying the cron trigger to the runtime." Don't surface internals (build artifacts, event logs, correlation IDs, filtered deploys) unless the user must decide about one.
|
|
13
13
|
- **Don't hand them the work.** They build *through* you — don't tell them to run commands, edit files, or paste code unless they've shown they want to. Do it yourself and report the result.
|
|
14
14
|
- **Simplify the talk, never the work.** Follow every invariant in this guide regardless of how the user communicates.
|
|
15
|
-
|
|
15
|
+
|
|
16
|
+
## Clarify as you build
|
|
17
|
+
|
|
18
|
+
Act as the user's automation engineer, not just an implementer. Before and while building, inspect the existing code, integration catalog, and connected credentials. Resolve what you can yourself; ask when a choice changes the outcome, access, or architecture.
|
|
19
|
+
|
|
20
|
+
- Clarify the trigger, inputs, destination, account, and approval or failure behavior when they are unclear.
|
|
21
|
+
- When several integrations, credentials, or designs fit, give the concrete options, recommend one briefly, and ask the user to choose. Never expose secret values.
|
|
22
|
+
- Recommend a workflow for a known, reliable path and an agent when runtime judgment, tool choice, or conversation is required.
|
|
23
|
+
- Batch related questions into one short message. Don't conduct an open-ended interview or ask what code, docs, or CLI inspection can answer.
|
|
24
|
+
- Use sensible, reversible defaults and state assumptions. Confirm before consequential side effects or ambiguous access.
|
|
16
25
|
|
|
17
26
|
## Local codebase vs platform project
|
|
18
27
|
|
|
@@ -59,9 +68,10 @@ Lint, typecheck, and test run through the globally installed CLI (`npm i -g @key
|
|
|
59
68
|
|
|
60
69
|
**Deploy is the gate.** `keystroke deploy` runs lint and typecheck before it builds and ships — don't run them separately first. Attempt deploy, fix what it reports, redeploy. Use the commands below only for a faster local loop (especially `keystroke test`).
|
|
61
70
|
|
|
62
|
-
**Keep `@keystrokehq/*` on latest.** The CLI auto-updates itself (global install). `keystroke deploy` / `
|
|
71
|
+
**Keep `@keystrokehq/*` on latest.** The CLI auto-updates itself (global install). `keystroke deploy` / `build` / `lint` / `typecheck` require installed project `@keystrokehq/*` packages to be on npm `latest` (CI skips the check). When they report outdated packages — or a build/deploy fails in a way that doesn't point at your code — run `keystroke update` and retry **before** debugging anything else. A registry minimum-release-age can hold a release back.
|
|
63
72
|
|
|
64
73
|
```bash
|
|
74
|
+
keystroke update # bump @keystrokehq/* deps + CLI to latest
|
|
65
75
|
keystroke deploy # lint + typecheck + build + ship dist/ — start here
|
|
66
76
|
keystroke test # optional local loop (unit + integration)
|
|
67
77
|
pnpm test # same — package.json scripts call the CLI
|
|
@@ -310,7 +320,7 @@ Invoke/inspect commands print JSON to stdout (exit 0 on success, 1 on failure)
|
|
|
310
320
|
|
|
311
321
|
## Common gotchas
|
|
312
322
|
|
|
313
|
-
1. **Stale `@keystrokehq/*` versions** — the first suspect for any build or deploy failure;
|
|
323
|
+
1. **Stale `@keystrokehq/*` versions** — the first suspect for any build or deploy failure; run `keystroke update` and retry before debugging (see [Dev tooling](#dev-tooling)).
|
|
314
324
|
2. **Action calling an action** — throws at runtime and fails lint. Compose in a workflow, or attach the integration action directly.
|
|
315
325
|
3. **Bad model id** — must be exact `vendor/model-id` from the catalog; don't kebab the version. Fails at deploy, not typecheck.
|
|
316
326
|
4. **LLM structured output: use `.nullish()`, not `.optional()`** — models emit `"field": null` for "not applicable", and Zod `.optional()` rejects `null` (the step fails). Use `.nullish()` (or `.nullable()`) for any optional field in an `outputSchema`.
|