@emmaneugene/pi-cursor-sdk 0.4.2 → 0.5.0
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 +16 -0
- package/README.md +19 -39
- package/dist/bundled-context-windows.js +6 -13
- package/dist/context.js +0 -3
- package/dist/cursor-config.js +0 -3
- package/dist/cursor-live-run-coordinator.js +11 -0
- package/dist/cursor-pi-tool-bridge-run.js +3 -0
- package/dist/cursor-provider-turn-prepare.js +0 -1
- package/dist/cursor-state.js +3 -16
- package/dist/index.js +0 -2
- package/dist/model-discovery.js +11 -47
- package/docs/cursor-dogfood-checklist.md +1 -1
- package/docs/cursor-live-smoke-checklist.md +1 -2
- package/docs/cursor-model-ux-spec.md +80 -151
- package/docs/cursor-native-tool-replay.md +2 -3
- package/docs/cursor-native-tool-visual-audit.md +1 -1
- package/docs/cursor-testing-lessons.md +1 -1
- package/docs/cursor-tool-surfaces.md +3 -6
- package/docs/platform-smoke-implementation.md +1 -1
- package/docs/platform-smoke.md +1 -1
- package/package.json +1 -1
- package/scripts/isolated-cursor-smoke.sh +1 -1
- package/scripts/lib/local-resume-smoke-harness.mjs +2 -1
- package/scripts/local-resume-smoke.mjs +1 -1
- package/scripts/platform-smoke/card-detect.mjs +1 -1
- package/scripts/refresh-cursor-model-snapshots.mjs +3 -3
- package/shared/cursor-model-selection-identities.d.mts +1 -4
- package/shared/cursor-model-selection-identities.mjs +21 -74
- package/src/bundled-context-windows.ts +6 -13
- package/src/context.ts +1 -5
- package/src/cursor-config.ts +9 -13
- package/src/cursor-live-run-coordinator.ts +11 -0
- package/src/cursor-pi-tool-bridge-run.ts +4 -0
- package/src/cursor-pi-tool-bridge-types.ts +1 -0
- package/src/cursor-provider-turn-prepare.ts +0 -1
- package/src/cursor-state.ts +3 -22
- package/src/index.ts +0 -3
- package/src/model-discovery.ts +16 -62
- package/dist/cursor-question-tool.js +0 -194
- package/node_modules/cross-spawn/node_modules/which/CHANGELOG.md +0 -166
- package/src/cursor-question-tool.ts +0 -267
package/src/model-discovery.ts
CHANGED
|
@@ -49,15 +49,11 @@ async function getDiscoveryApiKey(apiKey?: string): Promise<string | undefined>
|
|
|
49
49
|
|
|
50
50
|
export interface CursorModelMetadata {
|
|
51
51
|
piModelId: string;
|
|
52
|
-
baseModelId: string;
|
|
53
|
-
selectionModelId: string;
|
|
54
52
|
displayName: string;
|
|
55
53
|
defaultParams: ModelParameterValue[];
|
|
56
|
-
context?: string;
|
|
57
54
|
contextWindow: number;
|
|
58
55
|
supportsFast: boolean;
|
|
59
56
|
defaultFast: boolean;
|
|
60
|
-
fastOverride?: boolean;
|
|
61
57
|
supportsReasoning: boolean;
|
|
62
58
|
thinkingLevelMap?: ThinkingLevelMap;
|
|
63
59
|
parameterIds: {
|
|
@@ -158,48 +154,20 @@ function getDefaultParams(item: ModelListItem): ModelParameterValue[] {
|
|
|
158
154
|
return cloneParams(defaultVariant?.params ?? []);
|
|
159
155
|
}
|
|
160
156
|
|
|
161
|
-
function replaceParam(
|
|
162
|
-
params: ModelParameterValue[],
|
|
163
|
-
id: string,
|
|
164
|
-
value: string,
|
|
165
|
-
): ModelParameterValue[] {
|
|
166
|
-
let replaced = false;
|
|
167
|
-
const next = params.map((param) => {
|
|
168
|
-
if (param.id !== id) return { ...param };
|
|
169
|
-
replaced = true;
|
|
170
|
-
return { id, value };
|
|
171
|
-
});
|
|
172
|
-
if (!replaced) next.push({ id, value });
|
|
173
|
-
return next;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
157
|
function getParamValue(params: ModelParameterValue[], id: string): string | undefined {
|
|
177
158
|
return params.find((param) => param.id === id)?.value;
|
|
178
159
|
}
|
|
179
160
|
|
|
180
|
-
function getModelName(item: ModelListItem, context?: string, alias?: string, fastOverride?: boolean): string {
|
|
181
|
-
const displayName = item.displayName || item.id;
|
|
182
|
-
const qualifiers: string[] = [];
|
|
183
|
-
if (alias) qualifiers.push(alias);
|
|
184
|
-
if (fastOverride === true) qualifiers.push("fast");
|
|
185
|
-
if (fastOverride === false) qualifiers.push("slow");
|
|
186
|
-
const baseName = qualifiers.length > 0 ? `${displayName} (${qualifiers.join(", ")})` : displayName;
|
|
187
|
-
return context ? `${baseName} @ ${context}` : baseName;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
161
|
function getContextWindow(
|
|
191
162
|
contextWindowCache: Map<string, number>,
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
163
|
+
modelId: string,
|
|
164
|
+
defaultContext: string | undefined,
|
|
165
|
+
contextWindowKey: string,
|
|
195
166
|
): number {
|
|
196
|
-
for (const key of new Set(selectionKeys)) {
|
|
197
|
-
const contextWindow = contextWindowCache.get(key);
|
|
198
|
-
if (contextWindow !== undefined) return contextWindow;
|
|
199
|
-
}
|
|
200
167
|
return (
|
|
201
|
-
(
|
|
202
|
-
|
|
168
|
+
contextWindowCache.get(modelId) ??
|
|
169
|
+
contextWindowCache.get(contextWindowKey) ??
|
|
170
|
+
(defaultContext ? parseContextWindow(defaultContext) : undefined) ??
|
|
203
171
|
contextWindowCache.get("default") ??
|
|
204
172
|
FALLBACK_CONTEXT_WINDOW
|
|
205
173
|
);
|
|
@@ -207,27 +175,20 @@ function getContextWindow(
|
|
|
207
175
|
|
|
208
176
|
function toMetadata(
|
|
209
177
|
item: ModelListItem,
|
|
210
|
-
piModelId: string,
|
|
211
|
-
selectionModelId: string,
|
|
212
178
|
defaultParams: ModelParameterValue[],
|
|
213
|
-
|
|
179
|
+
defaultContext: string | undefined,
|
|
214
180
|
contextWindowCache: Map<string, number>,
|
|
215
|
-
|
|
216
|
-
fastOverride?: boolean,
|
|
181
|
+
contextWindowKey: string,
|
|
217
182
|
): CursorModelMetadata {
|
|
218
183
|
const thinkingLevelMap = getThinkingLevelMap(item);
|
|
219
184
|
const fastValue = getParamValue(defaultParams, "fast")?.toLowerCase();
|
|
220
185
|
return {
|
|
221
|
-
piModelId,
|
|
222
|
-
baseModelId: item.id,
|
|
223
|
-
selectionModelId,
|
|
186
|
+
piModelId: item.id,
|
|
224
187
|
displayName: item.displayName || item.id,
|
|
225
188
|
defaultParams: cloneParams(defaultParams),
|
|
226
|
-
|
|
227
|
-
contextWindow: getContextWindow(contextWindowCache, contextWindowKeys, context, item.id),
|
|
189
|
+
contextWindow: getContextWindow(contextWindowCache, item.id, defaultContext, contextWindowKey),
|
|
228
190
|
supportsFast: getParameter(item, "fast") !== undefined,
|
|
229
191
|
defaultFast: fastValue === "true",
|
|
230
|
-
...(fastOverride !== undefined ? { fastOverride } : {}),
|
|
231
192
|
supportsReasoning: thinkingLevelMap !== undefined,
|
|
232
193
|
...(thinkingLevelMap ? { thinkingLevelMap } : {}),
|
|
233
194
|
parameterIds: {
|
|
@@ -256,23 +217,16 @@ function toModelConfig(metadata: CursorModelMetadata, name: string): ProviderMod
|
|
|
256
217
|
function registerModelItems(items: ModelListItem[]): ProviderModelConfig[] {
|
|
257
218
|
metadataByPiModelId.clear();
|
|
258
219
|
const contextWindowCache = loadContextWindowCache();
|
|
259
|
-
return getCursorModelSelectionIdentities(items).map(({ model: item,
|
|
260
|
-
const defaultParams = getDefaultParams(item);
|
|
261
|
-
const contextParams = context ? replaceParam(defaultParams, "context", context) : defaultParams;
|
|
262
|
-
const params = fastOverride === undefined ? contextParams : replaceParam(contextParams, "fast", fastOverride ? "true" : "false");
|
|
220
|
+
return getCursorModelSelectionIdentities(items).map(({ model: item, piModelId, defaultContext, contextWindowKey }) => {
|
|
263
221
|
const metadata = toMetadata(
|
|
264
222
|
item,
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
params,
|
|
268
|
-
context,
|
|
223
|
+
getDefaultParams(item),
|
|
224
|
+
defaultContext,
|
|
269
225
|
contextWindowCache,
|
|
270
|
-
|
|
271
|
-
fastOverride,
|
|
226
|
+
contextWindowKey,
|
|
272
227
|
);
|
|
273
228
|
metadataByPiModelId.set(piModelId, metadata);
|
|
274
|
-
|
|
275
|
-
return toModelConfig(metadata, getModelName(item, context, alias, fastOverride));
|
|
229
|
+
return toModelConfig(metadata, metadata.displayName);
|
|
276
230
|
});
|
|
277
231
|
}
|
|
278
232
|
|
|
@@ -354,7 +308,7 @@ export function buildCursorModelSelection(
|
|
|
354
308
|
setParam(params, "fast", fastEnabled ? "true" : "false");
|
|
355
309
|
}
|
|
356
310
|
|
|
357
|
-
return params.length > 0 ? { id: metadata.
|
|
311
|
+
return params.length > 0 ? { id: metadata.piModelId, params } : { id: metadata.piModelId };
|
|
358
312
|
}
|
|
359
313
|
|
|
360
314
|
function sanitizeDiscoveryError(error: unknown, apiKey: string): string | undefined {
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
import { Text } from "@earendil-works/pi-tui";
|
|
2
|
-
import { Type } from "typebox";
|
|
3
|
-
import { arePiToolsDisabled } from "./cursor-active-tools.js";
|
|
4
|
-
import { parseEnvBoolean } from "./cursor-env-boolean.js";
|
|
5
|
-
import { isCursorModel } from "./cursor-model.js";
|
|
6
|
-
import { registerCursorModelLifecycle } from "./cursor-model-lifecycle.js";
|
|
7
|
-
import { resolveCursorPiToolBridgeEnabled } from "./cursor-pi-tool-bridge-env.js";
|
|
8
|
-
export const CURSOR_ASK_QUESTION_TOOL_NAME = "cursor_ask_question";
|
|
9
|
-
export const CURSOR_ASK_QUESTION_ENV = "PI_CURSOR_ASK_QUESTION";
|
|
10
|
-
export function resolveCursorAskQuestionEnabled(env = process.env) {
|
|
11
|
-
return parseEnvBoolean(env[CURSOR_ASK_QUESTION_ENV], true);
|
|
12
|
-
}
|
|
13
|
-
/** Package-namespaced event while `cursor_ask_question` awaits pi UI input. */
|
|
14
|
-
export const CURSOR_ASK_QUESTION_BLOCKED_EVENT = "pi-cursor-sdk:ask-question:blocked";
|
|
15
|
-
const QuestionOptionSchema = Type.Union([
|
|
16
|
-
Type.String(),
|
|
17
|
-
Type.Object({
|
|
18
|
-
label: Type.String({ description: "User-facing option label" }),
|
|
19
|
-
value: Type.Optional(Type.String({ description: "Optional value returned to Cursor; defaults to label" })),
|
|
20
|
-
description: Type.Optional(Type.String({ description: "Optional helper text shown by compatible pi UIs" })),
|
|
21
|
-
}),
|
|
22
|
-
]);
|
|
23
|
-
const QuestionSchema = Type.Object({
|
|
24
|
-
id: Type.Optional(Type.String({ description: "Stable question identifier" })),
|
|
25
|
-
question: Type.Optional(Type.String({ description: "Question to ask the user" })),
|
|
26
|
-
prompt: Type.Optional(Type.String({ description: "Alias for question" })),
|
|
27
|
-
options: Type.Optional(Type.Array(QuestionOptionSchema, { description: "Choices the user can select" })),
|
|
28
|
-
choices: Type.Optional(Type.Array(QuestionOptionSchema, { description: "Alias for options" })),
|
|
29
|
-
allowCustom: Type.Optional(Type.Boolean({ description: "Allow a typed answer in addition to listed options; defaults to true" })),
|
|
30
|
-
});
|
|
31
|
-
const CursorAskQuestionParamsSchema = Type.Object({
|
|
32
|
-
question: Type.Optional(Type.String({ description: "Question to ask the user" })),
|
|
33
|
-
prompt: Type.Optional(Type.String({ description: "Alias for question" })),
|
|
34
|
-
options: Type.Optional(Type.Array(QuestionOptionSchema, { description: "Choices the user can select" })),
|
|
35
|
-
choices: Type.Optional(Type.Array(QuestionOptionSchema, { description: "Alias for options" })),
|
|
36
|
-
allowCustom: Type.Optional(Type.Boolean({ description: "Allow a typed answer in addition to listed options; defaults to true" })),
|
|
37
|
-
questions: Type.Optional(Type.Array(QuestionSchema, { description: "Ask multiple questions sequentially" })),
|
|
38
|
-
});
|
|
39
|
-
function normalizeOption(option, index) {
|
|
40
|
-
if (typeof option === "string") {
|
|
41
|
-
const trimmed = option.trim();
|
|
42
|
-
return trimmed ? { label: trimmed, value: trimmed } : undefined;
|
|
43
|
-
}
|
|
44
|
-
const label = option.label?.trim() || option.value?.trim() || `Option ${index + 1}`;
|
|
45
|
-
return {
|
|
46
|
-
label,
|
|
47
|
-
value: option.value?.trim() || label,
|
|
48
|
-
...(option.description?.trim() ? { description: option.description.trim() } : {}),
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
function normalizeOptions(options) {
|
|
52
|
-
return (options ?? []).map(normalizeOption).filter((option) => option !== undefined);
|
|
53
|
-
}
|
|
54
|
-
function normalizeQuestion(raw, index) {
|
|
55
|
-
const question = raw.question?.trim() || raw.prompt?.trim();
|
|
56
|
-
if (!question)
|
|
57
|
-
return undefined;
|
|
58
|
-
return {
|
|
59
|
-
id: raw.id?.trim() || `question_${index + 1}`,
|
|
60
|
-
question,
|
|
61
|
-
options: normalizeOptions(raw.options ?? raw.choices),
|
|
62
|
-
allowCustom: raw.allowCustom !== false,
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
function normalizeQuestions(params) {
|
|
66
|
-
const rawQuestions = Array.isArray(params.questions) && params.questions.length > 0 ? params.questions : [params];
|
|
67
|
-
return rawQuestions.map(normalizeQuestion).filter((question) => question !== undefined);
|
|
68
|
-
}
|
|
69
|
-
function summarizeAnswers(answers) {
|
|
70
|
-
if (answers.length === 0)
|
|
71
|
-
return "No answer was collected.";
|
|
72
|
-
if (answers.length === 1) {
|
|
73
|
-
const [answer] = answers;
|
|
74
|
-
return answer.cancelled || answer.answer === null ? "User cancelled the question." : `User answered: ${answer.answer}`;
|
|
75
|
-
}
|
|
76
|
-
return [
|
|
77
|
-
"User answered:",
|
|
78
|
-
...answers.map((answer) => {
|
|
79
|
-
const value = answer.cancelled || answer.answer === null ? "cancelled" : answer.answer;
|
|
80
|
-
return `- ${answer.id}: ${value}`;
|
|
81
|
-
}),
|
|
82
|
-
].join("\n");
|
|
83
|
-
}
|
|
84
|
-
function buildDetails(questions, answers, uiAvailable) {
|
|
85
|
-
return {
|
|
86
|
-
questions,
|
|
87
|
-
answers,
|
|
88
|
-
uiAvailable,
|
|
89
|
-
cancelled: answers.some((answer) => answer.cancelled),
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
async function askOneQuestion(question, ctx) {
|
|
93
|
-
if (question.options.length > 0) {
|
|
94
|
-
const labels = question.options.map((option) => option.description ? `${option.label} — ${option.description}` : option.label);
|
|
95
|
-
const customLabel = "Type a custom answer";
|
|
96
|
-
const choices = question.allowCustom ? [...labels, customLabel] : labels;
|
|
97
|
-
const selected = await ctx.ui.select(question.question, choices);
|
|
98
|
-
if (!selected) {
|
|
99
|
-
return { id: question.id, question: question.question, answer: null, wasCustom: false, cancelled: true };
|
|
100
|
-
}
|
|
101
|
-
if (selected === customLabel) {
|
|
102
|
-
const customAnswer = await ctx.ui.input(question.question, "Type your answer");
|
|
103
|
-
const trimmed = customAnswer?.trim();
|
|
104
|
-
return trimmed
|
|
105
|
-
? { id: question.id, question: question.question, answer: trimmed, value: trimmed, wasCustom: true, cancelled: false }
|
|
106
|
-
: { id: question.id, question: question.question, answer: null, wasCustom: true, cancelled: true };
|
|
107
|
-
}
|
|
108
|
-
const selectedIndex = labels.indexOf(selected);
|
|
109
|
-
const selectedOption = selectedIndex >= 0 ? question.options[selectedIndex] : undefined;
|
|
110
|
-
const answer = selectedOption?.label ?? selected;
|
|
111
|
-
return {
|
|
112
|
-
id: question.id,
|
|
113
|
-
question: question.question,
|
|
114
|
-
answer,
|
|
115
|
-
value: selectedOption?.value ?? answer,
|
|
116
|
-
wasCustom: false,
|
|
117
|
-
cancelled: false,
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
const answer = await ctx.ui.input(question.question, "Type your answer");
|
|
121
|
-
const trimmed = answer?.trim();
|
|
122
|
-
return trimmed
|
|
123
|
-
? { id: question.id, question: question.question, answer: trimmed, value: trimmed, wasCustom: true, cancelled: false }
|
|
124
|
-
: { id: question.id, question: question.question, answer: null, wasCustom: true, cancelled: true };
|
|
125
|
-
}
|
|
126
|
-
function syncCursorQuestionToolForModel(pi, model) {
|
|
127
|
-
const activeToolNames = new Set(pi.getActiveTools());
|
|
128
|
-
const shouldBeActive = !arePiToolsDisabled(pi) && isCursorModel(model) && resolveCursorPiToolBridgeEnabled();
|
|
129
|
-
const alreadyActive = activeToolNames.has(CURSOR_ASK_QUESTION_TOOL_NAME);
|
|
130
|
-
if (shouldBeActive === alreadyActive)
|
|
131
|
-
return;
|
|
132
|
-
if (shouldBeActive) {
|
|
133
|
-
activeToolNames.add(CURSOR_ASK_QUESTION_TOOL_NAME);
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
136
|
-
activeToolNames.delete(CURSOR_ASK_QUESTION_TOOL_NAME);
|
|
137
|
-
}
|
|
138
|
-
pi.setActiveTools([...activeToolNames]);
|
|
139
|
-
}
|
|
140
|
-
function emitCursorAskQuestionBlockedEvent(pi, payload) {
|
|
141
|
-
pi.events.emit(CURSOR_ASK_QUESTION_BLOCKED_EVENT, payload);
|
|
142
|
-
}
|
|
143
|
-
export function registerCursorQuestionTool(pi) {
|
|
144
|
-
if (!resolveCursorAskQuestionEnabled())
|
|
145
|
-
return;
|
|
146
|
-
pi.registerTool({
|
|
147
|
-
name: CURSOR_ASK_QUESTION_TOOL_NAME,
|
|
148
|
-
label: "Cursor question",
|
|
149
|
-
description: "Ask the user a clarifying question from Cursor. Use when user preferences materially affect the next step; provide options when possible.",
|
|
150
|
-
promptSnippet: "Ask the user a clarifying question through pi UI when material choices affect Cursor's next step",
|
|
151
|
-
executionMode: "sequential",
|
|
152
|
-
parameters: CursorAskQuestionParamsSchema,
|
|
153
|
-
promptGuidelines: [
|
|
154
|
-
"Use cursor_ask_question only when running a Cursor model and user input would materially change the plan, scope, platform, or implementation path.",
|
|
155
|
-
"Prefer cursor_ask_question with 2-4 concrete options instead of guessing when Cursor plan mode needs user choices.",
|
|
156
|
-
],
|
|
157
|
-
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
158
|
-
const questions = normalizeQuestions(params);
|
|
159
|
-
if (questions.length === 0) {
|
|
160
|
-
throw new Error("No valid question was provided.");
|
|
161
|
-
}
|
|
162
|
-
if (!ctx.hasUI) {
|
|
163
|
-
throw new Error("Cannot ask the user because pi UI is unavailable. Make a reasonable default choice and state the assumption before proceeding.");
|
|
164
|
-
}
|
|
165
|
-
// Emit a package-namespaced blocked signal while the questionnaire
|
|
166
|
-
// awaits input so consumers (e.g. Herdr) can map it to blocked/working.
|
|
167
|
-
emitCursorAskQuestionBlockedEvent(pi, { active: true });
|
|
168
|
-
try {
|
|
169
|
-
const answers = [];
|
|
170
|
-
for (const question of questions) {
|
|
171
|
-
const answer = await askOneQuestion(question, ctx);
|
|
172
|
-
answers.push(answer);
|
|
173
|
-
if (answer.cancelled)
|
|
174
|
-
break;
|
|
175
|
-
}
|
|
176
|
-
return {
|
|
177
|
-
content: [{ type: "text", text: summarizeAnswers(answers) }],
|
|
178
|
-
details: buildDetails(questions, answers, true),
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
finally {
|
|
182
|
-
emitCursorAskQuestionBlockedEvent(pi, { active: false });
|
|
183
|
-
}
|
|
184
|
-
},
|
|
185
|
-
renderCall(args, theme) {
|
|
186
|
-
const questions = normalizeQuestions(args);
|
|
187
|
-
const label = questions[0]?.question ?? "Ask the user";
|
|
188
|
-
return new Text(theme.fg("toolTitle", theme.bold("cursor question ")) + theme.fg("muted", label), 0, 0);
|
|
189
|
-
},
|
|
190
|
-
});
|
|
191
|
-
registerCursorModelLifecycle(pi, (ctx) => {
|
|
192
|
-
syncCursorQuestionToolForModel(pi, ctx.model);
|
|
193
|
-
});
|
|
194
|
-
}
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
# Changes
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
## 2.0.2
|
|
5
|
-
|
|
6
|
-
* Rename bin to `node-which`
|
|
7
|
-
|
|
8
|
-
## 2.0.1
|
|
9
|
-
|
|
10
|
-
* generate changelog and publish on version bump
|
|
11
|
-
* enforce 100% test coverage
|
|
12
|
-
* Promise interface
|
|
13
|
-
|
|
14
|
-
## 2.0.0
|
|
15
|
-
|
|
16
|
-
* Parallel tests, modern JavaScript, and drop support for node < 8
|
|
17
|
-
|
|
18
|
-
## 1.3.1
|
|
19
|
-
|
|
20
|
-
* update deps
|
|
21
|
-
* update travis
|
|
22
|
-
|
|
23
|
-
## v1.3.0
|
|
24
|
-
|
|
25
|
-
* Add nothrow option to which.sync
|
|
26
|
-
* update tap
|
|
27
|
-
|
|
28
|
-
## v1.2.14
|
|
29
|
-
|
|
30
|
-
* appveyor: drop node 5 and 0.x
|
|
31
|
-
* travis-ci: add node 6, drop 0.x
|
|
32
|
-
|
|
33
|
-
## v1.2.13
|
|
34
|
-
|
|
35
|
-
* test: Pass missing option to pass on windows
|
|
36
|
-
* update tap
|
|
37
|
-
* update isexe to 2.0.0
|
|
38
|
-
* neveragain.tech pledge request
|
|
39
|
-
|
|
40
|
-
## v1.2.12
|
|
41
|
-
|
|
42
|
-
* Removed unused require
|
|
43
|
-
|
|
44
|
-
## v1.2.11
|
|
45
|
-
|
|
46
|
-
* Prevent changelog script from being included in package
|
|
47
|
-
|
|
48
|
-
## v1.2.10
|
|
49
|
-
|
|
50
|
-
* Use env.PATH only, not env.Path
|
|
51
|
-
|
|
52
|
-
## v1.2.9
|
|
53
|
-
|
|
54
|
-
* fix for paths starting with ../
|
|
55
|
-
* Remove unused `is-absolute` module
|
|
56
|
-
|
|
57
|
-
## v1.2.8
|
|
58
|
-
|
|
59
|
-
* bullet items in changelog that contain (but don't start with) #
|
|
60
|
-
|
|
61
|
-
## v1.2.7
|
|
62
|
-
|
|
63
|
-
* strip 'update changelog' changelog entries out of changelog
|
|
64
|
-
|
|
65
|
-
## v1.2.6
|
|
66
|
-
|
|
67
|
-
* make the changelog bulleted
|
|
68
|
-
|
|
69
|
-
## v1.2.5
|
|
70
|
-
|
|
71
|
-
* make a changelog, and keep it up to date
|
|
72
|
-
* don't include tests in package
|
|
73
|
-
* Properly handle relative-path executables
|
|
74
|
-
* appveyor
|
|
75
|
-
* Attach error code to Not Found error
|
|
76
|
-
* Make tests pass on Windows
|
|
77
|
-
|
|
78
|
-
## v1.2.4
|
|
79
|
-
|
|
80
|
-
* Fix typo
|
|
81
|
-
|
|
82
|
-
## v1.2.3
|
|
83
|
-
|
|
84
|
-
* update isexe, fix regression in pathExt handling
|
|
85
|
-
|
|
86
|
-
## v1.2.2
|
|
87
|
-
|
|
88
|
-
* update deps, use isexe module, test windows
|
|
89
|
-
|
|
90
|
-
## v1.2.1
|
|
91
|
-
|
|
92
|
-
* Sometimes windows PATH entries are quoted
|
|
93
|
-
* Fixed a bug in the check for group and user mode bits. This bug was introduced during refactoring for supporting strict mode.
|
|
94
|
-
* doc cli
|
|
95
|
-
|
|
96
|
-
## v1.2.0
|
|
97
|
-
|
|
98
|
-
* Add support for opt.all and -as cli flags
|
|
99
|
-
* test the bin
|
|
100
|
-
* update travis
|
|
101
|
-
* Allow checking for multiple programs in bin/which
|
|
102
|
-
* tap 2
|
|
103
|
-
|
|
104
|
-
## v1.1.2
|
|
105
|
-
|
|
106
|
-
* travis
|
|
107
|
-
* Refactored and fixed undefined error on Windows
|
|
108
|
-
* Support strict mode
|
|
109
|
-
|
|
110
|
-
## v1.1.1
|
|
111
|
-
|
|
112
|
-
* test +g exes against secondary groups, if available
|
|
113
|
-
* Use windows exe semantics on cygwin & msys
|
|
114
|
-
* cwd should be first in path on win32, not last
|
|
115
|
-
* Handle lower-case 'env.Path' on Windows
|
|
116
|
-
* Update docs
|
|
117
|
-
* use single-quotes
|
|
118
|
-
|
|
119
|
-
## v1.1.0
|
|
120
|
-
|
|
121
|
-
* Add tests, depend on is-absolute
|
|
122
|
-
|
|
123
|
-
## v1.0.9
|
|
124
|
-
|
|
125
|
-
* which.js: root is allowed to execute files owned by anyone
|
|
126
|
-
|
|
127
|
-
## v1.0.8
|
|
128
|
-
|
|
129
|
-
* don't use graceful-fs
|
|
130
|
-
|
|
131
|
-
## v1.0.7
|
|
132
|
-
|
|
133
|
-
* add license to package.json
|
|
134
|
-
|
|
135
|
-
## v1.0.6
|
|
136
|
-
|
|
137
|
-
* isc license
|
|
138
|
-
|
|
139
|
-
## 1.0.5
|
|
140
|
-
|
|
141
|
-
* Awful typo
|
|
142
|
-
|
|
143
|
-
## 1.0.4
|
|
144
|
-
|
|
145
|
-
* Test for path absoluteness properly
|
|
146
|
-
* win: Allow '' as a pathext if cmd has a . in it
|
|
147
|
-
|
|
148
|
-
## 1.0.3
|
|
149
|
-
|
|
150
|
-
* Remove references to execPath
|
|
151
|
-
* Make `which.sync()` work on Windows by honoring the PATHEXT variable.
|
|
152
|
-
* Make `isExe()` always return true on Windows.
|
|
153
|
-
* MIT
|
|
154
|
-
|
|
155
|
-
## 1.0.2
|
|
156
|
-
|
|
157
|
-
* Only files can be exes
|
|
158
|
-
|
|
159
|
-
## 1.0.1
|
|
160
|
-
|
|
161
|
-
* Respect the PATHEXT env for win32 support
|
|
162
|
-
* should 0755 the bin
|
|
163
|
-
* binary
|
|
164
|
-
* guts
|
|
165
|
-
* package
|
|
166
|
-
* 1st
|