pi-cursor-sdk 0.1.19 → 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.
- package/CHANGELOG.md +52 -0
- package/README.md +72 -11
- package/docs/cursor-dogfood-checklist.md +57 -0
- package/docs/cursor-live-smoke-checklist.md +116 -10
- package/docs/cursor-model-ux-spec.md +60 -19
- package/docs/cursor-native-tool-replay.md +21 -11
- package/docs/cursor-native-tool-visual-audit.md +104 -59
- package/docs/cursor-testing-lessons.md +10 -5
- package/docs/cursor-tool-surfaces.md +69 -0
- package/package.json +37 -11
- package/scripts/debug-provider-events.d.mts +59 -0
- package/scripts/debug-provider-events.mjs +70 -175
- package/scripts/debug-sdk-events.d.mts +90 -0
- package/scripts/debug-sdk-events.mjs +36 -98
- package/scripts/fixtures/plan-strip-shim/index.ts +12 -0
- package/scripts/isolated-cursor-smoke.sh +264 -102
- package/scripts/lib/cursor-child-process.d.mts +10 -0
- package/scripts/lib/cursor-child-process.mjs +50 -0
- package/scripts/lib/cursor-cli-args.d.mts +63 -0
- package/scripts/lib/cursor-cli-args.mjs +129 -0
- package/scripts/lib/cursor-script-fail.d.mts +1 -0
- package/scripts/lib/cursor-script-fail.mjs +13 -0
- package/scripts/lib/cursor-sdk-output-filter.d.mts +5 -0
- package/scripts/lib/cursor-smoke-env.d.mts +38 -0
- package/scripts/lib/cursor-smoke-env.mjs +81 -0
- package/scripts/lib/cursor-smoke-shell.sh +174 -0
- package/scripts/lib/cursor-visual-render.d.mts +15 -0
- package/scripts/lib/cursor-visual-render.mjs +131 -0
- package/scripts/probe-mcp-coldstart.mjs +226 -0
- package/scripts/refresh-cursor-model-snapshots.mjs +29 -65
- package/scripts/steering-rpc-smoke.mjs +170 -65
- package/scripts/tmux-live-smoke.sh +152 -98
- package/scripts/visual-tui-smoke.mjs +659 -0
- package/shared/cursor-sdk-event-debug-env.d.mts +12 -0
- package/shared/cursor-sdk-event-debug-env.mjs +13 -0
- package/shared/cursor-sensitive-text.d.mts +1 -0
- package/{scripts/lib/cursor-probe-utils.mjs → shared/cursor-sensitive-text.mjs} +1 -13
- package/shared/cursor-setting-sources.d.mts +5 -0
- package/shared/cursor-setting-sources.mjs +22 -0
- package/src/context.ts +21 -12
- package/src/cursor-bridge-contract.ts +1 -3
- package/src/cursor-incomplete-tool-visibility.ts +72 -49
- package/src/cursor-mcp-timeout-override.ts +66 -11
- package/src/cursor-native-tool-display-registration.ts +63 -27
- package/src/cursor-native-tool-display-replay.ts +246 -143
- package/src/cursor-native-tool-display-state.ts +2 -0
- package/src/cursor-native-tool-display-tools.ts +149 -41
- package/src/cursor-provider-live-run-drain.ts +1 -52
- package/src/cursor-provider-run-finalizer.ts +235 -0
- package/src/cursor-provider-run-outcome.ts +149 -0
- package/src/cursor-provider-turn-api-key.ts +8 -0
- package/src/cursor-provider-turn-coordinator.ts +113 -440
- package/src/cursor-provider-turn-display-router.ts +216 -0
- package/src/cursor-provider-turn-emit.ts +59 -0
- package/src/cursor-provider-turn-finalize.ts +119 -0
- package/src/cursor-provider-turn-lifecycle-emitter.ts +97 -0
- package/src/cursor-provider-turn-message-offset.ts +15 -0
- package/src/cursor-provider-turn-prepare.ts +216 -0
- package/src/cursor-provider-turn-runner.ts +138 -0
- package/src/cursor-provider-turn-sdk-normalizer.ts +88 -0
- package/src/cursor-provider-turn-send.ts +103 -0
- package/src/cursor-provider-turn-shell-output.ts +107 -0
- package/src/cursor-provider-turn-tool-ledger.ts +126 -0
- package/src/cursor-provider-turn-types.ts +87 -0
- package/src/cursor-provider.ts +16 -482
- package/src/cursor-replay-activity-builders.ts +276 -0
- package/src/cursor-replay-source-names.ts +33 -0
- package/src/cursor-replay-summary-args.ts +191 -0
- package/src/cursor-replay-tool-details.ts +464 -0
- package/src/cursor-run-final-text.ts +56 -0
- package/src/cursor-sdk-abort-error-guard.ts +4 -0
- package/src/cursor-sdk-event-debug-constants.ts +14 -5
- package/src/cursor-sdk-event-debug.ts +8 -2
- package/src/cursor-sensitive-text.ts +3 -36
- package/src/cursor-session-agent.ts +265 -88
- package/src/cursor-setting-sources.ts +7 -10
- package/src/cursor-state.ts +232 -28
- package/src/cursor-tool-lifecycle.ts +17 -42
- package/src/cursor-tool-manifest.ts +41 -0
- package/src/cursor-tool-names.ts +18 -79
- package/src/cursor-tool-presentation-registry.ts +556 -0
- package/src/cursor-tool-transcript.ts +1 -1
- package/src/cursor-tool-visibility.ts +39 -0
- package/src/cursor-transcript-tool-formatters.ts +0 -59
- package/src/cursor-transcript-tool-specs.ts +169 -232
- package/src/cursor-transcript-utils.ts +0 -44
- package/src/cursor-web-tool-activity.ts +10 -60
- package/src/cursor-web-tool-args.ts +39 -0
- package/src/index.ts +4 -10
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
set -euo pipefail
|
|
6
6
|
|
|
7
7
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
8
|
+
# shellcheck source=scripts/lib/cursor-smoke-shell.sh
|
|
9
|
+
. "$ROOT/scripts/lib/cursor-smoke-shell.sh"
|
|
10
|
+
SMOKE_LOG_PREFIX=isolated-smoke
|
|
11
|
+
|
|
8
12
|
REAL_HOME="${REAL_HOME:-$HOME}"
|
|
9
13
|
PI_AGENT_DIR="${PI_AGENT_DIR:-$REAL_HOME/.pi/agent}"
|
|
10
14
|
AUTH_JSON="${AUTH_JSON:-$PI_AGENT_DIR/auth.json}"
|
|
@@ -13,8 +17,6 @@ ISOLATED="${ISOLATED:-/tmp/pi-cursor-sdk-isolated-$(date +%Y%m%dT%H%M%S)}"
|
|
|
13
17
|
PI_LIVE_TIMEOUT="${PI_LIVE_TIMEOUT:-45}"
|
|
14
18
|
SKIP_LIVE="${SKIP_LIVE:-0}"
|
|
15
19
|
SKIP_UNIT="${SKIP_UNIT:-0}"
|
|
16
|
-
PI_BIN="${PI_BIN:-pi}"
|
|
17
|
-
PI_PATH="${PI_PATH:-/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin}"
|
|
18
20
|
|
|
19
21
|
PACK_DIR="$ISOLATED/pack"
|
|
20
22
|
EXTRACT_DIR="$ISOLATED/extract"
|
|
@@ -23,6 +25,19 @@ SESSION_ROOT="$ISOLATED/sessions"
|
|
|
23
25
|
SHIM_DIR="$ROOT/scripts/fixtures/plan-strip-shim"
|
|
24
26
|
HOME_DIR="$ISOLATED/home"
|
|
25
27
|
|
|
28
|
+
PI_BIN="${PI_BIN:-}"
|
|
29
|
+
NODE_BIN=""
|
|
30
|
+
NPM_BIN=""
|
|
31
|
+
RG_BIN=""
|
|
32
|
+
ENV_BIN=""
|
|
33
|
+
SHELL_BIN="${BASH:-/bin/bash}"
|
|
34
|
+
SEALED_PATH=""
|
|
35
|
+
DEBUG_ENV_UNSETS=()
|
|
36
|
+
TOOL_ENV=()
|
|
37
|
+
PI_DEFAULT_ENV=()
|
|
38
|
+
PI_NONE_ENV=()
|
|
39
|
+
SELF_TEST_TEMP_DIR=""
|
|
40
|
+
|
|
26
41
|
print_help() {
|
|
27
42
|
cat <<EOF
|
|
28
43
|
Isolated /tmp install smoke for pi-cursor-sdk (native replay + plan-strip resync).
|
|
@@ -38,15 +53,21 @@ Environment:
|
|
|
38
53
|
REAL_HOME Source for auth.json (default: \$HOME).
|
|
39
54
|
AUTH_JSON Path to pi auth.json to seed isolated HOME (default: ~/.pi/agent/auth.json).
|
|
40
55
|
PI_LIVE_TIMEOUT Per live pi check timeout in seconds (default: 45).
|
|
41
|
-
PI_BIN pi
|
|
42
|
-
PI_PATH PATH for isolated pi runs.
|
|
56
|
+
PI_BIN Optional pi command/path to resolve from the parent PATH (default: pi).
|
|
43
57
|
SKIP_LIVE=1 Run unit tests + pack only; skip live Cursor calls.
|
|
44
58
|
SKIP_UNIT=1 Skip repo unit tests (live checks only).
|
|
45
59
|
CURSOR_API_KEY Optional fallback when auth.json lacks cursor provider.
|
|
46
60
|
|
|
47
61
|
Prerequisites:
|
|
48
|
-
node, npm,
|
|
49
|
-
~/.pi/agent/auth.json with cursor provider OR CURSOR_API_KEY
|
|
62
|
+
SKIP_LIVE=1: node, npm, env, tar on PATH; pi is not required.
|
|
63
|
+
Live checks: pi, rg, python3, and ~/.pi/agent/auth.json with cursor provider OR CURSOR_API_KEY.
|
|
64
|
+
Resolved node/npm/env paths from the parent shell are reused for pack-only work; live checks then resolve pi/rg.
|
|
65
|
+
Pi and npm shims run with the resolved node directory first on PATH.
|
|
66
|
+
Child pi runs clear Cursor SDK event-debug env. Live provider checks force PI_CURSOR_SETTING_SOURCES=none; install/list checks explicitly unset it.
|
|
67
|
+
|
|
68
|
+
Options:
|
|
69
|
+
-h, --help Show this help.
|
|
70
|
+
--self-test Run sealed PATH/env probes without live Cursor auth.
|
|
50
71
|
|
|
51
72
|
Exit codes:
|
|
52
73
|
0 all requested checks passed
|
|
@@ -54,89 +75,213 @@ Exit codes:
|
|
|
54
75
|
EOF
|
|
55
76
|
}
|
|
56
77
|
|
|
57
|
-
log() {
|
|
58
|
-
|
|
78
|
+
log() { smoke_log "$@"; }
|
|
79
|
+
fail() { smoke_fail "$@"; }
|
|
80
|
+
seed_pi_agent_home() { smoke_seed_pi_agent_home "$@"; }
|
|
81
|
+
has_auth_provider() { smoke_has_auth_provider "$1" "$HOME_DIR/.pi/agent/auth.json"; }
|
|
82
|
+
run_with_timeout() { smoke_run_with_timeout_or_fail "$@"; }
|
|
83
|
+
|
|
84
|
+
build_smoke_env_arrays() {
|
|
85
|
+
smoke_build_cursor_sdk_event_debug_unsets
|
|
86
|
+
DEBUG_ENV_UNSETS=( "${SMOKE_CURSOR_SDK_EVENT_DEBUG_ENV_UNSETS[@]}" )
|
|
87
|
+
TOOL_ENV=( "$ENV_BIN" "${DEBUG_ENV_UNSETS[@]}" "PATH=$SEALED_PATH" )
|
|
88
|
+
PI_DEFAULT_ENV=( "$ENV_BIN" -i "${DEBUG_ENV_UNSETS[@]}" -u PI_CURSOR_SETTING_SOURCES HOME="$HOME_DIR" PATH="$SEALED_PATH" MISE_DISABLE=1 )
|
|
89
|
+
PI_NONE_ENV=( "$ENV_BIN" -i "${DEBUG_ENV_UNSETS[@]}" HOME="$HOME_DIR" PATH="$SEALED_PATH" MISE_DISABLE=1 PI_CURSOR_SETTING_SOURCES=none )
|
|
59
90
|
}
|
|
60
91
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
92
|
+
run_in_dir() {
|
|
93
|
+
local label="$1"
|
|
94
|
+
local timeout_secs="$2"
|
|
95
|
+
local dir="$3"
|
|
96
|
+
shift 3
|
|
97
|
+
run_with_timeout "$label" "$timeout_secs" "$SHELL_BIN" -c 'cd "$1" || exit 97; shift; exec "$@"' sh "$dir" "$@"
|
|
64
98
|
}
|
|
65
99
|
|
|
66
|
-
|
|
67
|
-
local
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
else
|
|
74
|
-
log "WARN: no auth.json at $AUTH_JSON"
|
|
75
|
-
fi
|
|
76
|
-
if [[ -f "$PI_AGENT_DIR/models.json" ]]; then
|
|
77
|
-
cp "$PI_AGENT_DIR/models.json" "$home/.pi/agent/models.json"
|
|
78
|
-
fi
|
|
100
|
+
run_in_dir_capture_combined() {
|
|
101
|
+
local label="$1"
|
|
102
|
+
local timeout_secs="$2"
|
|
103
|
+
local dir="$3"
|
|
104
|
+
local output="$4"
|
|
105
|
+
shift 4
|
|
106
|
+
run_with_timeout "$label" "$timeout_secs" "$SHELL_BIN" -c 'cd "$1" || exit 97; output="$2"; shift 2; exec "$@" >"$output" 2>&1' sh "$dir" "$output" "$@"
|
|
79
107
|
}
|
|
80
108
|
|
|
81
|
-
|
|
82
|
-
local
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
sys.exit(1)
|
|
90
|
-
sys.exit(0 if provider in data and data[provider] else 1)
|
|
91
|
-
PY
|
|
109
|
+
run_in_dir_capture_split() {
|
|
110
|
+
local label="$1"
|
|
111
|
+
local timeout_secs="$2"
|
|
112
|
+
local dir="$3"
|
|
113
|
+
local stdout="$4"
|
|
114
|
+
local stderr="$5"
|
|
115
|
+
shift 5
|
|
116
|
+
run_with_timeout "$label" "$timeout_secs" "$SHELL_BIN" -c 'cd "$1" || exit 97; stdout="$2"; stderr="$3"; shift 3; exec "$@" </dev/null >"$stdout" 2>"$stderr"' sh "$dir" "$stdout" "$stderr" "$@"
|
|
92
117
|
}
|
|
93
118
|
|
|
94
|
-
|
|
95
|
-
local
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
119
|
+
validate_replay_jsonl() {
|
|
120
|
+
local dir="$1"
|
|
121
|
+
"$NODE_BIN" "$ROOT/scripts/validate-smoke-jsonl.mjs" --replay-errors-only "$dir"
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
run_self_test() {
|
|
125
|
+
local temp_dir bin_dir fake_pi fake_node fake_node_marker env_capture hostile_path captured_path node_dir name
|
|
126
|
+
local old_path old_pi_bin old_pi_bin_was_set
|
|
127
|
+
local no_pi_bin fake_npm fake_npm_marker no_pi_repo no_pi_isolated no_pi_path no_pi_output_file no_pi_status
|
|
128
|
+
temp_dir="$(mktemp -d /tmp/pi-cursor-sdk-isolated-smoke-self-test.XXXXXX)"
|
|
129
|
+
SELF_TEST_TEMP_DIR="$temp_dir"
|
|
130
|
+
trap '[[ -z "${SELF_TEST_TEMP_DIR:-}" ]] || rm -rf "$SELF_TEST_TEMP_DIR"' EXIT
|
|
131
|
+
bin_dir="$temp_dir/bin"
|
|
132
|
+
mkdir -p "$bin_dir"
|
|
133
|
+
fake_pi="$bin_dir/pi"
|
|
134
|
+
fake_node="$bin_dir/node"
|
|
135
|
+
fake_node_marker="$temp_dir/fake-node-used"
|
|
136
|
+
env_capture="$temp_dir/fake-pi.env"
|
|
137
|
+
cat >"$fake_pi" <<EOF_SELFTEST_PI
|
|
138
|
+
#!/usr/bin/env node
|
|
139
|
+
const { writeFileSync } = require("node:fs");
|
|
140
|
+
writeFileSync("$env_capture", Object.entries(process.env).map(([key, value]) => key + "=" + (value ?? "")).join("\\n") + "\\n", "utf8");
|
|
141
|
+
EOF_SELFTEST_PI
|
|
142
|
+
cat >"$fake_node" <<EOF_SELFTEST_NODE
|
|
143
|
+
#!/usr/bin/env bash
|
|
144
|
+
echo fake-node-used > "$fake_node_marker"
|
|
145
|
+
exit 99
|
|
146
|
+
EOF_SELFTEST_NODE
|
|
147
|
+
chmod +x "$fake_pi" "$fake_node"
|
|
148
|
+
|
|
149
|
+
ENV_BIN="$(smoke_resolve_cmd env)"
|
|
150
|
+
NODE_BIN="$(smoke_resolve_cmd node)"
|
|
151
|
+
if [[ "$SHELL_BIN" != /* ]]; then
|
|
152
|
+
SHELL_BIN="$(smoke_resolve_cmd "$SHELL_BIN")"
|
|
106
153
|
fi
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
154
|
+
smoke_load_cursor_sdk_event_debug_env_names "$NODE_BIN" "$ROOT/shared/cursor-sdk-event-debug-env.mjs"
|
|
155
|
+
hostile_path="$bin_dir:$PATH"
|
|
156
|
+
old_path="$PATH"
|
|
157
|
+
old_pi_bin="${PI_BIN-}"
|
|
158
|
+
old_pi_bin_was_set=0
|
|
159
|
+
[[ ${PI_BIN+x} ]] && old_pi_bin_was_set=1
|
|
160
|
+
unset PI_BIN
|
|
161
|
+
PATH="$hostile_path"
|
|
162
|
+
[[ "$(smoke_resolve_cmd "${PI_BIN:-pi}")" == "$fake_pi" ]] || fail "self-test failed: default PI_BIN did not resolve through parent PATH"
|
|
163
|
+
PI_BIN="$fake_pi"
|
|
164
|
+
[[ "$(smoke_resolve_cmd "${PI_BIN:-pi}")" == "$fake_pi" ]] || fail "self-test failed: absolute PI_BIN was not honored"
|
|
165
|
+
PATH="$old_path"
|
|
166
|
+
if (( old_pi_bin_was_set )); then
|
|
167
|
+
PI_BIN="$old_pi_bin"
|
|
168
|
+
else
|
|
169
|
+
unset PI_BIN
|
|
114
170
|
fi
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
171
|
+
|
|
172
|
+
[[ "$(smoke_build_sealed_node_path "$NODE_BIN" "")" != *: ]] || fail "self-test failed: empty inherited PATH left a trailing PATH separator"
|
|
173
|
+
SEALED_PATH="$(smoke_build_sealed_node_path "$NODE_BIN" "$hostile_path")"
|
|
174
|
+
HOME_DIR="$temp_dir/home"
|
|
175
|
+
mkdir -p "$HOME_DIR"
|
|
176
|
+
build_smoke_env_arrays
|
|
177
|
+
node_dir="$(dirname "$NODE_BIN")"
|
|
178
|
+
|
|
179
|
+
PI_CURSOR_SETTING_SOURCES=all \
|
|
180
|
+
PI_CURSOR_SDK_EVENT_DEBUG=1 \
|
|
181
|
+
PI_CURSOR_SDK_EVENT_DEBUG_DIR="$temp_dir/debug-dir" \
|
|
182
|
+
PI_CURSOR_SDK_EVENT_DEBUG_RUN_DIR="$temp_dir/debug-run-dir" \
|
|
183
|
+
PI_CURSOR_SDK_EVENT_DEBUG_SESSION_DIR="$temp_dir/debug-session-dir" \
|
|
184
|
+
PI_CURSOR_SDK_EVENT_DEBUG_STDERR=1 \
|
|
185
|
+
"${PI_NONE_ENV[@]}" "$fake_pi" --version
|
|
186
|
+
[[ ! -e "$fake_node_marker" ]] || fail "self-test failed: sealed PATH still used hostile fake node"
|
|
187
|
+
captured_path="$(awk -F= '$1 == "PATH" { print substr($0, 6); exit }' "$env_capture")"
|
|
188
|
+
[[ "${captured_path%%:*}" == "$node_dir" ]] || fail "self-test failed: PATH did not start with resolved node dir"
|
|
189
|
+
grep -qx "HOME=$HOME_DIR" "$env_capture" || fail "self-test failed: isolated HOME was not set"
|
|
190
|
+
grep -qx 'MISE_DISABLE=1' "$env_capture" || fail "self-test failed: MISE_DISABLE was not set"
|
|
191
|
+
grep -qx 'PI_CURSOR_SETTING_SOURCES=none' "$env_capture" || fail "self-test failed: live pi env did not force PI_CURSOR_SETTING_SOURCES=none"
|
|
192
|
+
for name in "${SMOKE_CURSOR_SDK_EVENT_DEBUG_ENV_NAMES[@]}"; do
|
|
193
|
+
if grep -q "^${name}=" "$env_capture"; then
|
|
194
|
+
fail "self-test failed: $name was not cleared"
|
|
124
195
|
fi
|
|
125
|
-
sleep 1
|
|
126
|
-
waited=$((waited + 1))
|
|
127
196
|
done
|
|
128
|
-
wait "$pid" || fail "$label exited $?"
|
|
129
|
-
}
|
|
130
197
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
198
|
+
PI_CURSOR_SETTING_SOURCES=all \
|
|
199
|
+
PI_CURSOR_SDK_EVENT_DEBUG=1 \
|
|
200
|
+
PI_CURSOR_SDK_EVENT_DEBUG_DIR="$temp_dir/debug-dir" \
|
|
201
|
+
PI_CURSOR_SDK_EVENT_DEBUG_RUN_DIR="$temp_dir/debug-run-dir" \
|
|
202
|
+
PI_CURSOR_SDK_EVENT_DEBUG_SESSION_DIR="$temp_dir/debug-session-dir" \
|
|
203
|
+
PI_CURSOR_SDK_EVENT_DEBUG_STDERR=1 \
|
|
204
|
+
"${PI_DEFAULT_ENV[@]}" "$fake_pi" --version
|
|
205
|
+
if grep -q '^PI_CURSOR_SETTING_SOURCES=' "$env_capture"; then
|
|
206
|
+
fail "self-test failed: default pi env did not unset PI_CURSOR_SETTING_SOURCES"
|
|
207
|
+
fi
|
|
208
|
+
for name in "${SMOKE_CURSOR_SDK_EVENT_DEBUG_ENV_NAMES[@]}"; do
|
|
209
|
+
if grep -q "^${name}=" "$env_capture"; then
|
|
210
|
+
fail "self-test failed: default pi env leaked $name"
|
|
211
|
+
fi
|
|
212
|
+
done
|
|
213
|
+
|
|
214
|
+
PI_CURSOR_SDK_EVENT_DEBUG=1 \
|
|
215
|
+
PI_CURSOR_SDK_EVENT_DEBUG_DIR="$temp_dir/debug-dir" \
|
|
216
|
+
"${TOOL_ENV[@]}" "$fake_pi" --version
|
|
217
|
+
[[ ! -e "$fake_node_marker" ]] || fail "self-test failed: tool env still used hostile fake node"
|
|
218
|
+
captured_path="$(awk -F= '$1 == "PATH" { print substr($0, 6); exit }' "$env_capture")"
|
|
219
|
+
[[ "${captured_path%%:*}" == "$node_dir" ]] || fail "self-test failed: tool PATH did not start with resolved node dir"
|
|
220
|
+
for name in "${SMOKE_CURSOR_SDK_EVENT_DEBUG_ENV_NAMES[@]}"; do
|
|
221
|
+
if grep -q "^${name}=" "$env_capture"; then
|
|
222
|
+
fail "self-test failed: tool env leaked $name"
|
|
223
|
+
fi
|
|
224
|
+
done
|
|
225
|
+
|
|
226
|
+
no_pi_bin="$temp_dir/no-pi-bin"
|
|
227
|
+
fake_npm="$no_pi_bin/npm"
|
|
228
|
+
fake_npm_marker="$temp_dir/fake-npm-pack-used"
|
|
229
|
+
no_pi_repo="$temp_dir/no-pi-repo"
|
|
230
|
+
no_pi_isolated="$temp_dir/no-pi-isolated"
|
|
231
|
+
mkdir -p "$no_pi_bin" "$no_pi_repo"
|
|
232
|
+
ln -s "$NODE_BIN" "$no_pi_bin/node"
|
|
233
|
+
cat >"$fake_npm" <<EOF_SELFTEST_NPM
|
|
234
|
+
#!/usr/bin/env bash
|
|
235
|
+
set -euo pipefail
|
|
236
|
+
if [[ "\${1:-}" != "pack" ]]; then
|
|
237
|
+
printf 'fake npm only supports pack, got: %s\\n' "\$*" >&2
|
|
238
|
+
exit 64
|
|
239
|
+
fi
|
|
240
|
+
destination=""
|
|
241
|
+
shift
|
|
242
|
+
while (( \$# )); do
|
|
243
|
+
case "\$1" in
|
|
244
|
+
--pack-destination)
|
|
245
|
+
destination="\${2:-}"
|
|
246
|
+
shift 2
|
|
247
|
+
;;
|
|
248
|
+
*)
|
|
249
|
+
shift
|
|
250
|
+
;;
|
|
251
|
+
esac
|
|
252
|
+
done
|
|
253
|
+
[[ -n "\$destination" ]] || { printf 'missing --pack-destination\\n' >&2; exit 64; }
|
|
254
|
+
mkdir -p "\$destination/.fake/package"
|
|
255
|
+
printf '{"name":"fake-package","version":"1.0.0"}\\n' > "\$destination/.fake/package/package.json"
|
|
256
|
+
tar -czf "\$destination/fake-package-1.0.0.tgz" -C "\$destination/.fake" package
|
|
257
|
+
printf 'pack\\n' > "$fake_npm_marker"
|
|
258
|
+
EOF_SELFTEST_NPM
|
|
259
|
+
chmod +x "$fake_npm"
|
|
260
|
+
no_pi_path="$no_pi_bin:/usr/bin:/bin"
|
|
261
|
+
no_pi_output_file="$temp_dir/no-pi-output.txt"
|
|
262
|
+
set +e
|
|
263
|
+
PATH="$no_pi_path" REAL_HOME="$temp_dir/no-auth" PI_BIN=pi-must-not-exist REPO="$no_pi_repo" ISOLATED="$no_pi_isolated" SKIP_LIVE=1 SKIP_UNIT=1 "$SHELL_BIN" "$ROOT/scripts/isolated-cursor-smoke.sh" >"$no_pi_output_file" 2>&1
|
|
264
|
+
no_pi_status=$?
|
|
265
|
+
set -e
|
|
266
|
+
if [[ "$no_pi_status" != "0" ]]; then
|
|
267
|
+
cat "$no_pi_output_file" >&2 || true
|
|
268
|
+
fail "self-test failed: SKIP_LIVE=1 required pi or another live-only prerequisite"
|
|
269
|
+
fi
|
|
270
|
+
[[ -f "$fake_npm_marker" ]] || fail "self-test failed: no-pi SKIP_LIVE path did not run pack"
|
|
271
|
+
! grep -q 'missing required command: pi' "$no_pi_output_file" || fail "self-test failed: no-pi SKIP_LIVE path still resolved pi"
|
|
272
|
+
grep -q 'SKIP_LIVE=1' "$no_pi_output_file" || fail "self-test failed: no-pi SKIP_LIVE path did not reach skip-live exit"
|
|
273
|
+
|
|
274
|
+
printf '[isolated-smoke] self-test PASS\n'
|
|
134
275
|
}
|
|
135
276
|
|
|
136
277
|
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
|
137
278
|
print_help
|
|
138
279
|
exit 0
|
|
139
280
|
fi
|
|
281
|
+
if [[ "${1:-}" == "--self-test" ]]; then
|
|
282
|
+
run_self_test
|
|
283
|
+
exit 0
|
|
284
|
+
fi
|
|
140
285
|
|
|
141
286
|
if [[ -f "${SECRETS_FILE:-$REAL_HOME/.secrets}" ]]; then
|
|
142
287
|
set +u
|
|
@@ -145,82 +290,99 @@ if [[ -f "${SECRETS_FILE:-$REAL_HOME/.secrets}" ]]; then
|
|
|
145
290
|
set -u
|
|
146
291
|
fi
|
|
147
292
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
293
|
+
NODE_BIN="$(smoke_resolve_cmd node)"
|
|
294
|
+
NPM_BIN="$(smoke_resolve_cmd npm)"
|
|
295
|
+
ENV_BIN="$(smoke_resolve_cmd env)"
|
|
296
|
+
if [[ "$SHELL_BIN" != /* ]]; then
|
|
297
|
+
SHELL_BIN="$(smoke_resolve_cmd "$SHELL_BIN")"
|
|
298
|
+
fi
|
|
299
|
+
smoke_load_cursor_sdk_event_debug_env_names "$NODE_BIN" "$ROOT/shared/cursor-sdk-event-debug-env.mjs"
|
|
300
|
+
SEALED_PATH="$(smoke_build_sealed_node_path "$NODE_BIN" "$PATH")"
|
|
301
|
+
build_smoke_env_arrays
|
|
152
302
|
|
|
153
303
|
mkdir -p "$PACK_DIR" "$EXTRACT_DIR" "$PROJECT_DIR" "$SESSION_ROOT" "$HOME_DIR"
|
|
154
304
|
seed_pi_agent_home "$HOME_DIR"
|
|
155
305
|
|
|
156
306
|
log "isolated root: $ISOLATED"
|
|
157
307
|
log "HOME=$HOME_DIR"
|
|
308
|
+
log "node=$NODE_BIN"
|
|
309
|
+
log "npm=$NPM_BIN"
|
|
158
310
|
|
|
159
311
|
if [[ "$SKIP_UNIT" != "1" ]]; then
|
|
160
312
|
log "preflight: repo unit tests"
|
|
161
|
-
|
|
313
|
+
run_in_dir "npm test" 120 "$REPO" "${TOOL_ENV[@]}" "$NPM_BIN" test
|
|
162
314
|
fi
|
|
163
315
|
|
|
316
|
+
log "npm pack from $REPO"
|
|
317
|
+
run_in_dir_capture_combined "npm pack" 120 "$REPO" "$ISOLATED/npm-pack.log" "${TOOL_ENV[@]}" "$NPM_BIN" pack --pack-destination "$PACK_DIR"
|
|
318
|
+
PACK_TGZ=""
|
|
319
|
+
for candidate in "$PACK_DIR"/*.tgz; do
|
|
320
|
+
[[ -e "$candidate" ]] || continue
|
|
321
|
+
if [[ -z "$PACK_TGZ" || "$candidate" -nt "$PACK_TGZ" ]]; then
|
|
322
|
+
PACK_TGZ="$candidate"
|
|
323
|
+
fi
|
|
324
|
+
done
|
|
325
|
+
[[ -n "$PACK_TGZ" && -f "$PACK_TGZ" ]] || fail "missing pack tarball"
|
|
326
|
+
tar -xzf "$PACK_TGZ" -C "$EXTRACT_DIR"
|
|
327
|
+
[[ -d "$EXTRACT_DIR/package" ]] || fail "extract missing package/ dir"
|
|
328
|
+
|
|
164
329
|
if [[ "$SKIP_LIVE" == "1" ]]; then
|
|
165
|
-
log "SKIP_LIVE=1 — skipping live pi checks"
|
|
330
|
+
log "SKIP_LIVE=1 — skipping live pi checks after unit + pack"
|
|
166
331
|
exit 0
|
|
167
332
|
fi
|
|
168
333
|
|
|
334
|
+
PI_BIN="$(smoke_resolve_cmd "${PI_BIN:-pi}")"
|
|
335
|
+
RG_BIN="$(smoke_resolve_cmd rg)"
|
|
336
|
+
smoke_require_cmd python3
|
|
337
|
+
log "pi=$PI_BIN"
|
|
338
|
+
log "rg=$RG_BIN"
|
|
339
|
+
|
|
169
340
|
if ! has_auth_provider cursor && [[ -z "${CURSOR_API_KEY:-}" ]]; then
|
|
170
341
|
fail "no cursor auth in $HOME_DIR/.pi/agent/auth.json and CURSOR_API_KEY unset"
|
|
171
342
|
fi
|
|
172
343
|
|
|
173
|
-
command -v "$PI_BIN" >/dev/null || fail "PI_BIN not found: $PI_BIN"
|
|
174
|
-
|
|
175
|
-
log "npm pack from $REPO"
|
|
176
|
-
(cd "$REPO" && npm pack --pack-destination "$PACK_DIR" >/dev/null 2>&1)
|
|
177
|
-
PACK_TGZ="$(ls -t "$PACK_DIR"/*.tgz | head -1)"
|
|
178
|
-
[[ -f "$PACK_TGZ" ]] || fail "missing pack tarball"
|
|
179
|
-
tar -xzf "$PACK_TGZ" -C "$EXTRACT_DIR"
|
|
180
|
-
[[ -d "$EXTRACT_DIR/package" ]] || fail "extract missing package/ dir"
|
|
181
|
-
|
|
182
344
|
log "npm install packed extension deps"
|
|
183
|
-
|
|
345
|
+
run_in_dir_capture_combined "npm install --omit=dev" 120 "$EXTRACT_DIR/package" "$ISOLATED/npm-install.log" "${TOOL_ENV[@]}" "$NPM_BIN" install --omit=dev
|
|
184
346
|
|
|
185
347
|
log "pi install -l (clean HOME)"
|
|
186
348
|
cp "$REPO/README.md" "$PROJECT_DIR/README.md"
|
|
187
|
-
|
|
188
|
-
bash -c "cd '$PROJECT_DIR' && '$PI_BIN' install -l '$EXTRACT_DIR/package' >/dev/null"
|
|
349
|
+
run_in_dir_capture_combined "pi install" 30 "$PROJECT_DIR" "$ISOLATED/pi-install.log" "${PI_DEFAULT_ENV[@]}" "$PI_BIN" install -l "$EXTRACT_DIR/package"
|
|
189
350
|
|
|
190
|
-
|
|
191
|
-
|
|
351
|
+
PI_LIST_OUT="$ISOLATED/pi-list.txt"
|
|
352
|
+
run_in_dir_capture_combined "pi list" 15 "$PROJECT_DIR" "$PI_LIST_OUT" "${PI_DEFAULT_ENV[@]}" "$PI_BIN" list
|
|
353
|
+
"$RG_BIN" -q "extract/package" "$PI_LIST_OUT" || fail "packed extension not installed"
|
|
192
354
|
|
|
193
|
-
|
|
355
|
+
PI_CURSOR_ENV=( "${PI_NONE_ENV[@]}" )
|
|
194
356
|
if [[ -n "${CURSOR_API_KEY:-}" ]]; then
|
|
195
|
-
|
|
357
|
+
PI_CURSOR_ENV+=( CURSOR_API_KEY="$CURSOR_API_KEY" )
|
|
196
358
|
fi
|
|
197
359
|
|
|
198
360
|
log "check: list-models"
|
|
199
361
|
LIST_OUT="$ISOLATED/list-models.txt"
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
362
|
+
run_in_dir_capture_combined "list-models" 30 "$PROJECT_DIR" "$LIST_OUT" "${PI_CURSOR_ENV[@]}" \
|
|
363
|
+
"$PI_BIN" --cursor-no-fast --list-models cursor
|
|
364
|
+
"$RG_BIN" -q "composer-2\\.5|composer-2-5" "$LIST_OUT" || fail "composer-2.5 not listed (see $LIST_OUT)"
|
|
203
365
|
|
|
204
366
|
log "check: basic provider prompt"
|
|
205
367
|
BASIC_DIR="$SESSION_ROOT/basic"
|
|
206
368
|
mkdir -p "$BASIC_DIR"
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
369
|
+
run_in_dir_capture_split "basic prompt" "$PI_LIVE_TIMEOUT" "$PROJECT_DIR" "$ISOLATED/basic.stdout.txt" "$ISOLATED/basic.stderr.txt" "${PI_CURSOR_ENV[@]}" \
|
|
370
|
+
"$PI_BIN" --cursor-no-fast --model cursor/composer-2.5 --session-dir "$BASIC_DIR" --no-tools -p 'Reply exactly: PI_CURSOR_ISOLATED_OK'
|
|
371
|
+
"$RG_BIN" -q "PI_CURSOR_ISOLATED_OK" "$ISOLATED/basic.stdout.txt" || fail "basic prompt missing PI_CURSOR_ISOLATED_OK"
|
|
210
372
|
validate_replay_jsonl "$BASIC_DIR"
|
|
211
373
|
|
|
212
374
|
log "check: native replay"
|
|
213
375
|
REPLAY_DIR="$SESSION_ROOT/native-replay"
|
|
214
376
|
mkdir -p "$REPLAY_DIR"
|
|
215
|
-
|
|
216
|
-
|
|
377
|
+
run_in_dir_capture_split "native replay" "$PI_LIVE_TIMEOUT" "$PROJECT_DIR" "$ISOLATED/replay.stdout.txt" "$ISOLATED/replay.stderr.txt" "${PI_CURSOR_ENV[@]}" PI_CURSOR_NATIVE_TOOL_DISPLAY=1 \
|
|
378
|
+
"$PI_BIN" --cursor-no-fast --model cursor/composer-2.5 --session-dir "$REPLAY_DIR" -p 'Read ./README.md briefly, then answer README_SEEN=yes if it mentions pi-cursor-sdk.'
|
|
217
379
|
validate_replay_jsonl "$REPLAY_DIR"
|
|
218
380
|
|
|
219
381
|
log "check: plan-strip shim (plan-mode execute reset)"
|
|
220
382
|
PLAN_DIR="$SESSION_ROOT/plan-strip"
|
|
221
383
|
mkdir -p "$PLAN_DIR"
|
|
222
|
-
|
|
223
|
-
|
|
384
|
+
run_in_dir_capture_split "plan-strip replay" "$PI_LIVE_TIMEOUT" "$PROJECT_DIR" "$ISOLATED/plan.stdout.txt" "$ISOLATED/plan.stderr.txt" "${PI_CURSOR_ENV[@]}" PI_CURSOR_NATIVE_TOOL_DISPLAY=1 \
|
|
385
|
+
"$PI_BIN" -e "$SHIM_DIR" --cursor-no-fast --model cursor/composer-2.5 --session-dir "$PLAN_DIR" -p 'After reset, read README.md and answer PLAN_STRIP_OK=yes.'
|
|
224
386
|
validate_replay_jsonl "$PLAN_DIR"
|
|
225
387
|
|
|
226
388
|
log "PASS isolated install smoke: $ISOLATED"
|
|
@@ -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
|
+
};
|