pi-cursor-sdk 0.1.20 → 0.1.21

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 (88) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +49 -9
  3. package/docs/cursor-dogfood-checklist.md +57 -0
  4. package/docs/cursor-live-smoke-checklist.md +115 -9
  5. package/docs/cursor-model-ux-spec.md +57 -17
  6. package/docs/cursor-native-tool-replay.md +15 -7
  7. package/docs/cursor-native-tool-visual-audit.md +104 -59
  8. package/docs/cursor-testing-lessons.md +8 -3
  9. package/docs/cursor-tool-surfaces.md +69 -0
  10. package/package.json +34 -10
  11. package/scripts/debug-provider-events.d.mts +59 -0
  12. package/scripts/debug-provider-events.mjs +70 -175
  13. package/scripts/debug-sdk-events.d.mts +90 -0
  14. package/scripts/debug-sdk-events.mjs +36 -98
  15. package/scripts/fixtures/plan-strip-shim/index.ts +12 -0
  16. package/scripts/isolated-cursor-smoke.sh +264 -102
  17. package/scripts/lib/cursor-child-process.d.mts +10 -0
  18. package/scripts/lib/cursor-child-process.mjs +50 -0
  19. package/scripts/lib/cursor-cli-args.d.mts +63 -0
  20. package/scripts/lib/cursor-cli-args.mjs +129 -0
  21. package/scripts/lib/cursor-script-fail.d.mts +1 -0
  22. package/scripts/lib/cursor-script-fail.mjs +13 -0
  23. package/scripts/lib/cursor-sdk-output-filter.d.mts +5 -0
  24. package/scripts/lib/cursor-smoke-env.d.mts +38 -0
  25. package/scripts/lib/cursor-smoke-env.mjs +81 -0
  26. package/scripts/lib/cursor-smoke-shell.sh +174 -0
  27. package/scripts/lib/cursor-visual-render.d.mts +15 -0
  28. package/scripts/lib/cursor-visual-render.mjs +131 -0
  29. package/scripts/probe-mcp-coldstart.mjs +20 -38
  30. package/scripts/refresh-cursor-model-snapshots.mjs +29 -65
  31. package/scripts/steering-rpc-smoke.mjs +170 -65
  32. package/scripts/tmux-live-smoke.sh +152 -98
  33. package/scripts/visual-tui-smoke.mjs +659 -0
  34. package/shared/cursor-sdk-event-debug-env.d.mts +12 -0
  35. package/shared/cursor-sdk-event-debug-env.mjs +13 -0
  36. package/shared/cursor-sensitive-text.d.mts +1 -0
  37. package/{scripts/lib/cursor-probe-utils.mjs → shared/cursor-sensitive-text.mjs} +1 -13
  38. package/shared/cursor-setting-sources.d.mts +5 -0
  39. package/shared/cursor-setting-sources.mjs +22 -0
  40. package/src/context.ts +21 -12
  41. package/src/cursor-bridge-contract.ts +1 -3
  42. package/src/cursor-incomplete-tool-visibility.ts +22 -5
  43. package/src/cursor-native-tool-display-registration.ts +63 -27
  44. package/src/cursor-native-tool-display-replay.ts +246 -144
  45. package/src/cursor-native-tool-display-state.ts +2 -0
  46. package/src/cursor-native-tool-display-tools.ts +149 -41
  47. package/src/cursor-provider-live-run-drain.ts +1 -52
  48. package/src/cursor-provider-run-finalizer.ts +235 -0
  49. package/src/cursor-provider-run-outcome.ts +149 -0
  50. package/src/cursor-provider-turn-api-key.ts +8 -0
  51. package/src/cursor-provider-turn-coordinator.ts +98 -446
  52. package/src/cursor-provider-turn-display-router.ts +216 -0
  53. package/src/cursor-provider-turn-emit.ts +59 -0
  54. package/src/cursor-provider-turn-finalize.ts +119 -0
  55. package/src/cursor-provider-turn-lifecycle-emitter.ts +97 -0
  56. package/src/cursor-provider-turn-message-offset.ts +15 -0
  57. package/src/cursor-provider-turn-prepare.ts +216 -0
  58. package/src/cursor-provider-turn-runner.ts +138 -0
  59. package/src/cursor-provider-turn-sdk-normalizer.ts +88 -0
  60. package/src/cursor-provider-turn-send.ts +103 -0
  61. package/src/cursor-provider-turn-shell-output.ts +107 -0
  62. package/src/cursor-provider-turn-tool-ledger.ts +126 -0
  63. package/src/cursor-provider-turn-types.ts +87 -0
  64. package/src/cursor-provider.ts +16 -504
  65. package/src/cursor-replay-activity-builders.ts +276 -0
  66. package/src/cursor-replay-source-names.ts +33 -0
  67. package/src/cursor-replay-summary-args.ts +191 -0
  68. package/src/cursor-replay-tool-details.ts +464 -0
  69. package/src/cursor-run-final-text.ts +56 -0
  70. package/src/cursor-sdk-abort-error-guard.ts +4 -0
  71. package/src/cursor-sdk-event-debug-constants.ts +14 -5
  72. package/src/cursor-sdk-event-debug.ts +2 -1
  73. package/src/cursor-sensitive-text.ts +3 -36
  74. package/src/cursor-session-agent.ts +3 -1
  75. package/src/cursor-setting-sources.ts +7 -10
  76. package/src/cursor-state.ts +232 -28
  77. package/src/cursor-tool-lifecycle.ts +9 -8
  78. package/src/cursor-tool-manifest.ts +41 -0
  79. package/src/cursor-tool-names.ts +18 -106
  80. package/src/cursor-tool-presentation-registry.ts +556 -0
  81. package/src/cursor-tool-transcript.ts +1 -1
  82. package/src/cursor-tool-visibility.ts +3 -27
  83. package/src/cursor-transcript-tool-formatters.ts +0 -59
  84. package/src/cursor-transcript-tool-specs.ts +158 -233
  85. package/src/cursor-transcript-utils.ts +0 -44
  86. package/src/cursor-web-tool-activity.ts +10 -60
  87. package/src/cursor-web-tool-args.ts +39 -0
  88. package/src/index.ts +4 -10
@@ -0,0 +1,10 @@
1
+ import type { ChildProcess } from "node:child_process";
2
+
3
+ export declare const DEFAULT_CHILD_SHUTDOWN_GRACE_MS: number;
4
+ export declare function waitForChildClose(child: ChildProcess): Promise<number>;
5
+ export declare function signalChild(child: ChildProcess, signal: NodeJS.Signals): void;
6
+ export declare function terminateChild(
7
+ child: ChildProcess,
8
+ options?: { graceMs?: number },
9
+ ): Promise<void>;
10
+ export declare function parseJsonLines(stdout: string): unknown[];
@@ -0,0 +1,50 @@
1
+ export const DEFAULT_CHILD_SHUTDOWN_GRACE_MS = 2_000;
2
+
3
+ export function waitForChildClose(child) {
4
+ if (child.exitCode !== null || child.signalCode !== null) return Promise.resolve(child.exitCode ?? 1);
5
+ return new Promise((resolve) => {
6
+ child.once("close", (code) => resolve(code ?? 1));
7
+ });
8
+ }
9
+
10
+ export function signalChild(child, signal) {
11
+ if (!child.pid) return;
12
+ try {
13
+ if (process.platform === "win32") {
14
+ child.kill(signal);
15
+ } else {
16
+ process.kill(-child.pid, signal);
17
+ }
18
+ } catch {
19
+ try {
20
+ child.kill(signal);
21
+ } catch {
22
+ // child already exited
23
+ }
24
+ }
25
+ }
26
+
27
+ export async function terminateChild(child, { graceMs = DEFAULT_CHILD_SHUTDOWN_GRACE_MS } = {}) {
28
+ child.stdin?.destroy?.();
29
+ if (child.exitCode !== null || child.signalCode !== null) return;
30
+ signalChild(child, "SIGTERM");
31
+ const killTimer = setTimeout(() => signalChild(child, "SIGKILL"), graceMs);
32
+ try {
33
+ await waitForChildClose(child);
34
+ } finally {
35
+ clearTimeout(killTimer);
36
+ }
37
+ }
38
+
39
+ export function parseJsonLines(stdout) {
40
+ const events = [];
41
+ for (const line of stdout.split("\n")) {
42
+ if (!line.trim()) continue;
43
+ try {
44
+ events.push(JSON.parse(line));
45
+ } catch {
46
+ // ignore partial lines
47
+ }
48
+ }
49
+ return events;
50
+ }
@@ -0,0 +1,63 @@
1
+ export interface CursorCliValueFlagSpec<TValue = string> {
2
+ [key: string]: unknown;
3
+ names: readonly string[];
4
+ takesValue?: true;
5
+ repeat?: boolean;
6
+ allowDashValue?: boolean;
7
+ assign?: (value: string, flagName: string) => TValue;
8
+ }
9
+
10
+ export interface CursorCliBooleanFlagSpec<TValue = boolean> {
11
+ [key: string]: unknown;
12
+ names: readonly string[];
13
+ takesValue: false;
14
+ repeat?: boolean;
15
+ assign?: (value: true, flagName: string) => TValue;
16
+ }
17
+
18
+ export type CursorCliFlagSpec<TValue = string> = CursorCliValueFlagSpec<TValue> | CursorCliBooleanFlagSpec<TValue>;
19
+
20
+ export type CursorCliFlagSpecMap<TArgs extends Record<string, unknown>> = {
21
+ [K in keyof TArgs]?: CursorCliFlagSpec<unknown>;
22
+ };
23
+
24
+ export type ParsedCursorCliArgs<TDefaults extends Record<string, unknown>> = TDefaults & { help: boolean };
25
+
26
+ export declare function readArgvValue(
27
+ argv: readonly string[],
28
+ index: number,
29
+ flagName: string,
30
+ fail: (message: string) => never,
31
+ options?: { allowDashValue?: boolean },
32
+ ): string;
33
+ export declare function parseArgv<TDefaults extends Record<string, unknown>>(
34
+ argv: readonly string[],
35
+ options: { defaults: TDefaults; flags: CursorCliFlagSpecMap<TDefaults>; fail: (message: string) => never },
36
+ ): ParsedCursorCliArgs<TDefaults>;
37
+ export declare function defaultSettingSourcesFromEnv(env?: NodeJS.ProcessEnv): string[] | undefined;
38
+ export declare function defaultApiKeyFromEnv(env?: NodeJS.ProcessEnv): string | undefined;
39
+ export declare function readArgvApiKey(argv: readonly string[]): string | undefined;
40
+ export declare function apiKeySecretsFromProcess(
41
+ argv?: readonly string[],
42
+ env?: NodeJS.ProcessEnv,
43
+ ): Array<string | undefined>;
44
+ export declare function requireApiKey(
45
+ args: { apiKey?: string },
46
+ env: NodeJS.ProcessEnv,
47
+ fail: (message: string) => never,
48
+ ): string;
49
+ export declare function defaultTimestampedDir(prefix: string, baseDir?: string): string;
50
+ export declare const commonProbePathFlag: <TKey extends string>(key: TKey) => CursorCliValueFlagSpec<string>;
51
+ export declare const commonProbeStringFlag: <TKey extends string>(key: TKey) => CursorCliValueFlagSpec<string>;
52
+ export declare const commonBooleanFlag: (...names: string[]) => CursorCliBooleanFlagSpec<boolean>;
53
+ export declare const commonRepeatStringFlag: (...names: string[]) => CursorCliValueFlagSpec<string>;
54
+ export declare const commonProbeFlags: {
55
+ readonly cwd: CursorCliValueFlagSpec<string>;
56
+ readonly model: CursorCliValueFlagSpec<string>;
57
+ readonly prompt: CursorCliValueFlagSpec<string>;
58
+ readonly out: CursorCliValueFlagSpec<string>;
59
+ readonly sessionDir: CursorCliValueFlagSpec<string>;
60
+ readonly promptFile: CursorCliValueFlagSpec<string>;
61
+ readonly apiKey: CursorCliValueFlagSpec<string>;
62
+ readonly settingSources: CursorCliValueFlagSpec<string[] | undefined>;
63
+ };
@@ -0,0 +1,129 @@
1
+ import { resolve } from "node:path";
2
+ import { CURSOR_SETTING_SOURCES_ENV, resolveCursorSettingSources } from "../../shared/cursor-setting-sources.mjs";
3
+
4
+ export function readArgvValue(argv, index, flagName, fail, options = {}) {
5
+ const current = argv[index];
6
+ if (!current || (!options.allowDashValue && current.startsWith("--"))) {
7
+ fail(`${flagName} requires a value`);
8
+ }
9
+ return current;
10
+ }
11
+
12
+ function assignParsedArg(args, key, spec, raw, flagName) {
13
+ const value = spec.assign ? spec.assign(raw, flagName) : raw;
14
+ if (spec.repeat) {
15
+ args[key] = [...(Array.isArray(args[key]) ? args[key] : []), value];
16
+ return;
17
+ }
18
+ args[key] = value;
19
+ }
20
+
21
+ export function parseArgv(argv, { defaults, flags, fail }) {
22
+ const args = { ...defaults, help: false };
23
+ for (let index = 0; index < argv.length; index++) {
24
+ const arg = argv[index];
25
+ if (arg === "-h" || arg === "--help") {
26
+ args.help = true;
27
+ continue;
28
+ }
29
+
30
+ let matched = false;
31
+ for (const [key, spec] of Object.entries(flags)) {
32
+ for (const flagName of spec.names) {
33
+ if (arg === flagName) {
34
+ if (spec.takesValue === false) {
35
+ assignParsedArg(args, key, spec, true, flagName);
36
+ } else {
37
+ const raw = readArgvValue(argv, ++index, flagName, fail, { allowDashValue: spec.allowDashValue === true });
38
+ assignParsedArg(args, key, spec, raw, flagName);
39
+ }
40
+ matched = true;
41
+ break;
42
+ }
43
+ if (arg.startsWith(`${flagName}=`)) {
44
+ if (spec.takesValue === false) fail(`${flagName} does not accept a value`);
45
+ const raw = arg.slice(flagName.length + 1);
46
+ assignParsedArg(args, key, spec, raw, flagName);
47
+ matched = true;
48
+ break;
49
+ }
50
+ }
51
+ if (matched) break;
52
+ }
53
+ if (!matched) fail(`unknown argument: ${arg}`);
54
+ }
55
+ return args;
56
+ }
57
+
58
+ export function defaultSettingSourcesFromEnv(env = process.env) {
59
+ return resolveCursorSettingSources(env[CURSOR_SETTING_SOURCES_ENV]);
60
+ }
61
+
62
+ export function defaultApiKeyFromEnv(env = process.env) {
63
+ return env.CURSOR_API_KEY?.trim() || undefined;
64
+ }
65
+
66
+ export function readArgvApiKey(argv) {
67
+ for (let index = 0; index < argv.length; index++) {
68
+ const arg = argv[index];
69
+ if (arg === "--api-key") {
70
+ const value = argv[index + 1];
71
+ return typeof value === "string" ? value.trim() : undefined;
72
+ }
73
+ if (arg.startsWith("--api-key=")) return arg.slice("--api-key=".length).trim();
74
+ }
75
+ return undefined;
76
+ }
77
+
78
+ export function apiKeySecretsFromProcess(argv = process.argv.slice(2), env = process.env) {
79
+ return [defaultApiKeyFromEnv(env), readArgvApiKey(argv)];
80
+ }
81
+
82
+ export function requireApiKey(args, env, fail) {
83
+ const apiKey = args.apiKey ?? defaultApiKeyFromEnv(env);
84
+ if (!apiKey) {
85
+ fail("Cursor API key is required. Set CURSOR_API_KEY or pass --api-key.");
86
+ }
87
+ return apiKey;
88
+ }
89
+
90
+ export function defaultTimestampedDir(prefix, baseDir = "/tmp") {
91
+ const stamp = new Date().toISOString().replace(/[:.]/g, "-");
92
+ return resolve(baseDir, `${prefix}-${stamp}`);
93
+ }
94
+
95
+ export const commonProbePathFlag = (key) => ({
96
+ names: [`--${key}`],
97
+ assign: (value) => resolve(value),
98
+ });
99
+
100
+ export const commonProbeStringFlag = (key) => ({
101
+ names: [`--${key}`],
102
+ });
103
+
104
+ export const commonBooleanFlag = (...names) => ({
105
+ names,
106
+ takesValue: false,
107
+ });
108
+
109
+ export const commonRepeatStringFlag = (...names) => ({
110
+ names,
111
+ repeat: true,
112
+ });
113
+
114
+ export const commonProbeFlags = {
115
+ cwd: commonProbePathFlag("cwd"),
116
+ model: commonProbeStringFlag("model"),
117
+ prompt: commonProbeStringFlag("prompt"),
118
+ out: commonProbePathFlag("out"),
119
+ sessionDir: { names: ["--session-dir"], assign: (value) => resolve(value) },
120
+ promptFile: { names: ["--prompt-file"], assign: (value) => resolve(value) },
121
+ apiKey: {
122
+ names: ["--api-key"],
123
+ assign: (value) => value.trim(),
124
+ },
125
+ settingSources: {
126
+ names: ["--setting-sources"],
127
+ assign: (value) => resolveCursorSettingSources(value),
128
+ },
129
+ };
@@ -0,0 +1 @@
1
+ export declare function createScriptFail(prefix: string): (message: string, secrets?: string | string[]) => never;
@@ -0,0 +1,13 @@
1
+ import { scrubSensitiveText } from "../../shared/cursor-sensitive-text.mjs";
2
+
3
+ export function createScriptFail(prefix) {
4
+ return (message, secrets = []) => {
5
+ const secretList = Array.isArray(secrets) ? secrets : [secrets];
6
+ let scrubbed = scrubSensitiveText(message);
7
+ for (const secret of secretList) {
8
+ if (secret) scrubbed = scrubSensitiveText(scrubbed, secret);
9
+ }
10
+ console.error(`${prefix}: ${scrubbed}`);
11
+ process.exit(1);
12
+ };
13
+ }
@@ -0,0 +1,5 @@
1
+ export declare const CURSOR_SDK_STARTUP_NOISE_PATTERNS: readonly string[];
2
+ export declare function isCursorSdkOutputSuppressed(): boolean;
3
+ export declare function suppressCursorSdkOutput<T>(operation: () => T): T;
4
+ export declare function isCursorSdkStartupNoise(text: string): boolean;
5
+ export declare function installCursorSdkOutputFilter(): () => void;
@@ -0,0 +1,38 @@
1
+ export {
2
+ CURSOR_SDK_EVENT_DEBUG_DIR_ENV,
3
+ CURSOR_SDK_EVENT_DEBUG_ENV,
4
+ CURSOR_SDK_EVENT_DEBUG_ENV_NAMES,
5
+ CURSOR_SDK_EVENT_DEBUG_RUN_DIR_ENV,
6
+ CURSOR_SDK_EVENT_DEBUG_SESSION_DIR_ENV,
7
+ CURSOR_SDK_EVENT_DEBUG_STDERR_ENV,
8
+ } from "../../shared/cursor-sdk-event-debug-env.mjs";
9
+
10
+ export declare function sealedNodePath(nodePath?: string, envPath?: string): string;
11
+ export declare function clearCursorSdkEventDebugEnv<TEnv extends Record<string, string | undefined>>(env: TEnv): TEnv;
12
+ export declare function buildCursorSmokeEnv(options?: {
13
+ baseEnv?: Record<string, string | undefined>;
14
+ nodePath?: string;
15
+ settingSources?: string | null;
16
+ nativeToolDisplay?: boolean;
17
+ registerNativeTools?: boolean;
18
+ bridge?: boolean;
19
+ exposeBuiltinTools?: boolean;
20
+ term?: string;
21
+ eventDebugDir?: string;
22
+ }): Record<string, string | undefined>;
23
+ export declare function buildCursorSmokeEnvPlan(options?: {
24
+ baseEnv?: Record<string, string | undefined>;
25
+ nodePath?: string;
26
+ settingSources?: string | null;
27
+ nativeToolDisplay?: boolean;
28
+ registerNativeTools?: boolean;
29
+ bridge?: boolean;
30
+ exposeBuiltinTools?: boolean;
31
+ term?: string;
32
+ eventDebugDir?: string;
33
+ }): {
34
+ env: Record<string, string | undefined>;
35
+ sealedPath: string;
36
+ clearEnvNames: string[];
37
+ envEntries: Array<[string, string]>;
38
+ };
@@ -0,0 +1,81 @@
1
+ import { delimiter, dirname } from "node:path";
2
+ import {
3
+ CURSOR_SDK_EVENT_DEBUG_DIR_ENV,
4
+ CURSOR_SDK_EVENT_DEBUG_ENV,
5
+ CURSOR_SDK_EVENT_DEBUG_ENV_NAMES,
6
+ CURSOR_SDK_EVENT_DEBUG_RUN_DIR_ENV,
7
+ CURSOR_SDK_EVENT_DEBUG_SESSION_DIR_ENV,
8
+ CURSOR_SDK_EVENT_DEBUG_STDERR_ENV,
9
+ } from "../../shared/cursor-sdk-event-debug-env.mjs";
10
+
11
+ export {
12
+ CURSOR_SDK_EVENT_DEBUG_DIR_ENV,
13
+ CURSOR_SDK_EVENT_DEBUG_ENV,
14
+ CURSOR_SDK_EVENT_DEBUG_ENV_NAMES,
15
+ CURSOR_SDK_EVENT_DEBUG_RUN_DIR_ENV,
16
+ CURSOR_SDK_EVENT_DEBUG_SESSION_DIR_ENV,
17
+ CURSOR_SDK_EVENT_DEBUG_STDERR_ENV,
18
+ };
19
+
20
+ export function sealedNodePath(nodePath = process.execPath, envPath = process.env.PATH ?? "") {
21
+ return [dirname(nodePath), envPath].filter(Boolean).join(delimiter);
22
+ }
23
+
24
+ export function clearCursorSdkEventDebugEnv(env) {
25
+ for (const name of CURSOR_SDK_EVENT_DEBUG_ENV_NAMES) delete env[name];
26
+ return env;
27
+ }
28
+
29
+ function boolEnv(value) {
30
+ return value ? "1" : "0";
31
+ }
32
+
33
+ function pushIfDefined(entries, name, value) {
34
+ if (value !== undefined) entries.push([name, value]);
35
+ }
36
+
37
+ export function buildCursorSmokeEnv({
38
+ baseEnv = process.env,
39
+ nodePath = process.execPath,
40
+ settingSources,
41
+ nativeToolDisplay,
42
+ registerNativeTools,
43
+ bridge,
44
+ exposeBuiltinTools,
45
+ term,
46
+ eventDebugDir,
47
+ } = {}) {
48
+ const env = clearCursorSdkEventDebugEnv({ ...baseEnv });
49
+ env.PATH = sealedNodePath(nodePath, baseEnv.PATH ?? "");
50
+ if (settingSources === null) delete env.PI_CURSOR_SETTING_SOURCES;
51
+ else if (settingSources !== undefined) env.PI_CURSOR_SETTING_SOURCES = settingSources;
52
+ if (nativeToolDisplay !== undefined) env.PI_CURSOR_NATIVE_TOOL_DISPLAY = boolEnv(nativeToolDisplay);
53
+ if (registerNativeTools !== undefined) env.PI_CURSOR_REGISTER_NATIVE_TOOLS = boolEnv(registerNativeTools);
54
+ if (bridge !== undefined) env.PI_CURSOR_PI_TOOL_BRIDGE = boolEnv(bridge);
55
+ if (exposeBuiltinTools !== undefined) env.PI_CURSOR_EXPOSE_BUILTIN_TOOLS = boolEnv(exposeBuiltinTools);
56
+ if (term !== undefined) env.TERM = term;
57
+ if (eventDebugDir !== undefined) {
58
+ env[CURSOR_SDK_EVENT_DEBUG_ENV] = "1";
59
+ env[CURSOR_SDK_EVENT_DEBUG_DIR_ENV] = eventDebugDir;
60
+ }
61
+ return env;
62
+ }
63
+
64
+ export function buildCursorSmokeEnvPlan(options = {}) {
65
+ const env = buildCursorSmokeEnv(options);
66
+ const envEntries = [];
67
+ pushIfDefined(envEntries, "PI_CURSOR_SETTING_SOURCES", options.settingSources === null ? undefined : options.settingSources);
68
+ pushIfDefined(envEntries, "PI_CURSOR_NATIVE_TOOL_DISPLAY", options.nativeToolDisplay === undefined ? undefined : boolEnv(options.nativeToolDisplay));
69
+ pushIfDefined(envEntries, "PI_CURSOR_REGISTER_NATIVE_TOOLS", options.registerNativeTools === undefined ? undefined : boolEnv(options.registerNativeTools));
70
+ pushIfDefined(envEntries, "PI_CURSOR_PI_TOOL_BRIDGE", options.bridge === undefined ? undefined : boolEnv(options.bridge));
71
+ pushIfDefined(envEntries, "PI_CURSOR_EXPOSE_BUILTIN_TOOLS", options.exposeBuiltinTools === undefined ? undefined : boolEnv(options.exposeBuiltinTools));
72
+ pushIfDefined(envEntries, "TERM", options.term);
73
+ pushIfDefined(envEntries, CURSOR_SDK_EVENT_DEBUG_ENV, options.eventDebugDir === undefined ? undefined : "1");
74
+ pushIfDefined(envEntries, CURSOR_SDK_EVENT_DEBUG_DIR_ENV, options.eventDebugDir);
75
+ return {
76
+ env,
77
+ sealedPath: env.PATH,
78
+ clearEnvNames: [...CURSOR_SDK_EVENT_DEBUG_ENV_NAMES],
79
+ envEntries,
80
+ };
81
+ }
@@ -0,0 +1,174 @@
1
+ # Shared maintainer smoke shell helpers (timeout, logging, auth seeding).
2
+ # Source from top-level smoke scripts: . "$(dirname "$0")/lib/cursor-smoke-shell.sh"
3
+
4
+ : "${SMOKE_LOG_PREFIX:=smoke}"
5
+ SMOKE_KILL_GRACE_SECS="${SMOKE_KILL_GRACE_SECS:-2}"
6
+ SMOKE_CURSOR_SDK_EVENT_DEBUG_ENV_NAMES=()
7
+ SMOKE_CURSOR_SDK_EVENT_DEBUG_ENV_UNSETS=()
8
+
9
+ smoke_log() {
10
+ printf '[%s] %s\n' "$SMOKE_LOG_PREFIX" "$*"
11
+ }
12
+
13
+ smoke_fail() {
14
+ printf '[%s] FAIL: %s\n' "$SMOKE_LOG_PREFIX" "$*" >&2
15
+ exit 1
16
+ }
17
+
18
+ smoke_require_cmd() {
19
+ command -v "$1" >/dev/null 2>&1 || smoke_fail "missing required command: $1"
20
+ }
21
+
22
+ smoke_resolve_cmd() {
23
+ local name="$1"
24
+ local path
25
+ if ! path="$(command -v -- "$name" 2>/dev/null)" || [[ -z "$path" ]]; then
26
+ smoke_fail "missing required command: $name"
27
+ fi
28
+ if [[ "$path" != /* ]]; then
29
+ smoke_fail "required command $name did not resolve to an absolute path: $path"
30
+ fi
31
+ printf '%s\n' "$path"
32
+ }
33
+
34
+ smoke_build_sealed_node_path() {
35
+ local node_bin="$1"
36
+ local base_path
37
+ if (( $# >= 2 )); then
38
+ base_path="$2"
39
+ else
40
+ base_path="$PATH"
41
+ fi
42
+ if [[ -n "$base_path" ]]; then
43
+ printf '%s:%s\n' "$(dirname "$node_bin")" "$base_path"
44
+ else
45
+ printf '%s\n' "$(dirname "$node_bin")"
46
+ fi
47
+ }
48
+
49
+ smoke_load_cursor_sdk_event_debug_env_names() {
50
+ local node_bin="$1"
51
+ local module_path="$2"
52
+ local name
53
+ SMOKE_CURSOR_SDK_EVENT_DEBUG_ENV_NAMES=()
54
+ while IFS= read -r name; do
55
+ [[ -n "$name" ]] || continue
56
+ SMOKE_CURSOR_SDK_EVENT_DEBUG_ENV_NAMES+=( "$name" )
57
+ done < <("$node_bin" --input-type=module -e 'import { pathToFileURL } from "node:url"; const mod = await import(pathToFileURL(process.argv[1]).href); for (const name of mod.CURSOR_SDK_EVENT_DEBUG_ENV_NAMES) console.log(name);' "$module_path")
58
+ if [[ "${#SMOKE_CURSOR_SDK_EVENT_DEBUG_ENV_NAMES[@]}" -eq 0 ]]; then
59
+ smoke_fail "failed to load Cursor SDK event-debug env names from $module_path"
60
+ fi
61
+ }
62
+
63
+ smoke_build_cursor_sdk_event_debug_unsets() {
64
+ local name
65
+ SMOKE_CURSOR_SDK_EVENT_DEBUG_ENV_UNSETS=()
66
+ for name in "${SMOKE_CURSOR_SDK_EVENT_DEBUG_ENV_NAMES[@]}"; do
67
+ SMOKE_CURSOR_SDK_EVENT_DEBUG_ENV_UNSETS+=( -u "$name" )
68
+ done
69
+ }
70
+
71
+ # Run a command with a wall-clock timeout. Prefer GNU/BSD timeout; fall back to a
72
+ # process-group kill watcher with TERM then KILL (same semantics as tmux live smoke).
73
+ smoke_run_with_timeout() {
74
+ local timeout_secs="$1"
75
+ shift
76
+ if command -v timeout >/dev/null 2>&1; then
77
+ timeout "$timeout_secs" "$@"
78
+ return $?
79
+ fi
80
+ if command -v gtimeout >/dev/null 2>&1; then
81
+ gtimeout "$timeout_secs" "$@"
82
+ return $?
83
+ fi
84
+
85
+ local restore_monitor=0
86
+ case $- in
87
+ *m*) ;;
88
+ *)
89
+ restore_monitor=1
90
+ set -m
91
+ ;;
92
+ esac
93
+
94
+ "$@" &
95
+ local pid=$!
96
+ (
97
+ sleep "$timeout_secs"
98
+ kill -TERM "-$pid" 2>/dev/null || kill -TERM "$pid" 2>/dev/null || true
99
+ sleep "$SMOKE_KILL_GRACE_SECS"
100
+ kill -KILL "-$pid" 2>/dev/null || kill -KILL "$pid" 2>/dev/null || true
101
+ ) &
102
+ local watcher=$!
103
+ local code=0
104
+ if wait "$pid"; then
105
+ code=0
106
+ else
107
+ code=$?
108
+ fi
109
+ kill "$watcher" 2>/dev/null || true
110
+ wait "$watcher" 2>/dev/null || true
111
+ if (( restore_monitor )); then
112
+ set +m
113
+ fi
114
+ return "$code"
115
+ }
116
+
117
+ # Run with timeout; map exit 124/137/143 to a smoke_fail timeout message.
118
+ smoke_run_with_timeout_or_fail() {
119
+ local label="$1"
120
+ local timeout_secs="$2"
121
+ shift 2
122
+ smoke_log "$label (timeout ${timeout_secs}s)"
123
+ local restore_errexit=0
124
+ case $- in
125
+ *e*)
126
+ restore_errexit=1
127
+ set +e
128
+ ;;
129
+ esac
130
+ local rc=0
131
+ smoke_run_with_timeout "$timeout_secs" "$@"
132
+ rc=$?
133
+ if (( restore_errexit )); then
134
+ set -e
135
+ fi
136
+ if [[ "$rc" -eq 0 ]]; then
137
+ return 0
138
+ fi
139
+ case "$rc" in
140
+ 124|137|143) smoke_fail "$label timed out after ${timeout_secs}s" ;;
141
+ *) smoke_fail "$label exited $rc" ;;
142
+ esac
143
+ }
144
+
145
+ smoke_seed_pi_agent_home() {
146
+ local home="$1"
147
+ local auth_json="${2:-${AUTH_JSON:-${REAL_HOME:-$HOME}/.pi/agent/auth.json}}"
148
+ local models_src="${3:-${PI_AGENT_DIR:-${REAL_HOME:-$HOME}/.pi/agent}/models.json}"
149
+ mkdir -p "$home/.pi/agent"
150
+ if [[ -f "$auth_json" ]]; then
151
+ cp "$auth_json" "$home/.pi/agent/auth.json"
152
+ chmod 600 "$home/.pi/agent/auth.json"
153
+ smoke_log "seeded $home/.pi/agent/auth.json"
154
+ else
155
+ smoke_log "WARN: no auth.json at $auth_json"
156
+ fi
157
+ if [[ -f "$models_src" ]]; then
158
+ cp "$models_src" "$home/.pi/agent/models.json"
159
+ fi
160
+ }
161
+
162
+ smoke_has_auth_provider() {
163
+ local provider="$1"
164
+ local auth_path="$2"
165
+ python3 - "$provider" "$auth_path" <<'PY'
166
+ import json, sys
167
+ provider, path = sys.argv[1], sys.argv[2]
168
+ try:
169
+ data = json.load(open(path))
170
+ except FileNotFoundError:
171
+ sys.exit(1)
172
+ sys.exit(0 if provider in data and data[provider] else 1)
173
+ PY
174
+ }
@@ -0,0 +1,15 @@
1
+ export declare function buildTerminalHtml(args: {
2
+ ansi: string;
3
+ plain: string;
4
+ options: {
5
+ label: string;
6
+ model: string;
7
+ mode: string;
8
+ cwd: string;
9
+ sessionId: string;
10
+ width: number;
11
+ height: number;
12
+ historyLines: number;
13
+ };
14
+ }): string;
15
+ export declare function writeTerminalScreenshot(htmlPath: string, pngPath: string, width: number, height: number): Promise<void>;