@cursorpool-dev/cli 0.5.6 → 0.5.8

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 (108) hide show
  1. package/node_modules/@cursor-pool/extension/dist/extension.js +116 -46
  2. package/node_modules/@cursor-pool/extension/package.json +3 -3
  3. package/node_modules/@cursor-pool/extension/src/api.ts +17 -2
  4. package/node_modules/@cursor-pool/extension/src/panel.ts +26 -3
  5. package/node_modules/@cursor-pool/extension/test/panel.test.ts +34 -1
  6. package/node_modules/@cursor-pool/patcher/package.json +2 -2
  7. package/node_modules/@cursor-pool/patcher/src/marker.ts +5 -1
  8. package/node_modules/@cursor-pool/patcher/src/workbenchAuthGateMarker.ts +58 -7
  9. package/node_modules/@cursor-pool/patcher/test/patchCursorAgentExec.test.ts +20 -0
  10. package/node_modules/@cursor-pool/patcher/test/patchCursorWorkbench.test.ts +193 -2
  11. package/node_modules/@cursor-pool/service/package.json +2 -2
  12. package/node_modules/@cursor-pool/service/src/platformSession.ts +30 -7
  13. package/node_modules/@cursor-pool/service/src/server.ts +1 -0
  14. package/node_modules/@cursor-pool/service/test/platformSession.test.ts +5 -4
  15. package/node_modules/@cursor-pool/service/test/server.test.ts +130 -0
  16. package/node_modules/@cursor-pool/shared/package.json +1 -1
  17. package/node_modules/@cursor-pool/shared/src/manifest.ts +35 -0
  18. package/node_modules/@cursor-pool/shared/test/manifest.test.ts +43 -9
  19. package/node_modules/@cursor-pool/takeover-plans/package.json +12 -0
  20. package/node_modules/@cursor-pool/takeover-plans/src/index.ts +22 -0
  21. package/node_modules/@cursor-pool/takeover-plans/src/plans.ts +37 -0
  22. package/node_modules/@cursor-pool/takeover-plans/src/types.ts +9 -0
  23. package/node_modules/@cursor-pool/takeover-plans/test/registry.test.ts +23 -0
  24. package/node_modules/@esbuild/linux-x64/README.md +3 -0
  25. package/node_modules/@esbuild/linux-x64/bin/esbuild +0 -0
  26. package/node_modules/@esbuild/linux-x64/package.json +20 -0
  27. package/node_modules/esbuild/LICENSE.md +21 -0
  28. package/node_modules/esbuild/README.md +3 -0
  29. package/node_modules/esbuild/bin/esbuild +223 -0
  30. package/node_modules/esbuild/install.js +300 -0
  31. package/node_modules/esbuild/lib/main.d.ts +716 -0
  32. package/node_modules/esbuild/lib/main.js +2532 -0
  33. package/node_modules/esbuild/package.json +74 -0
  34. package/node_modules/tsx/LICENSE +21 -0
  35. package/node_modules/tsx/README.md +32 -0
  36. package/node_modules/tsx/dist/cjs/api/index.cjs +1 -0
  37. package/node_modules/tsx/dist/cjs/api/index.d.cts +35 -0
  38. package/node_modules/tsx/dist/cjs/api/index.d.mts +35 -0
  39. package/node_modules/tsx/dist/cjs/api/index.mjs +1 -0
  40. package/node_modules/tsx/dist/cjs/index.cjs +1 -0
  41. package/node_modules/tsx/dist/cjs/index.mjs +1 -0
  42. package/node_modules/tsx/dist/cli.cjs +54 -0
  43. package/node_modules/tsx/dist/cli.mjs +55 -0
  44. package/node_modules/tsx/dist/client-D3mGB526.cjs +1 -0
  45. package/node_modules/tsx/dist/client-D_mPDF5S.mjs +1 -0
  46. package/node_modules/tsx/dist/esm/api/index.cjs +1 -0
  47. package/node_modules/tsx/dist/esm/api/index.d.cts +35 -0
  48. package/node_modules/tsx/dist/esm/api/index.d.mts +35 -0
  49. package/node_modules/tsx/dist/esm/api/index.mjs +1 -0
  50. package/node_modules/tsx/dist/esm/index.cjs +1 -0
  51. package/node_modules/tsx/dist/esm/index.mjs +1 -0
  52. package/node_modules/tsx/dist/get-pipe-path-D4YM6rQt.cjs +1 -0
  53. package/node_modules/tsx/dist/get-pipe-path-_tAJyU_v.mjs +1 -0
  54. package/node_modules/tsx/dist/index-BWFBUo6r.cjs +1 -0
  55. package/node_modules/tsx/dist/index-D9F1FXzN.cjs +14 -0
  56. package/node_modules/tsx/dist/index-XurvG3JN.mjs +14 -0
  57. package/node_modules/tsx/dist/index-gbaejti9.mjs +1 -0
  58. package/node_modules/tsx/dist/lexer-DQCqS3nf.mjs +3 -0
  59. package/node_modules/tsx/dist/lexer-DgIbo0BU.cjs +3 -0
  60. package/node_modules/tsx/dist/loader.cjs +1 -0
  61. package/node_modules/tsx/dist/loader.mjs +1 -0
  62. package/node_modules/tsx/dist/node-features-B9BBLzwu.mjs +1 -0
  63. package/node_modules/tsx/dist/node-features-CQLdkVE6.cjs +1 -0
  64. package/node_modules/tsx/dist/package-CGdS2_oX.cjs +1 -0
  65. package/node_modules/tsx/dist/package-DyJMwVU5.mjs +1 -0
  66. package/node_modules/tsx/dist/patch-repl.cjs +1 -0
  67. package/node_modules/tsx/dist/patch-repl.mjs +1 -0
  68. package/node_modules/tsx/dist/preflight.cjs +1 -0
  69. package/node_modules/tsx/dist/preflight.mjs +1 -0
  70. package/node_modules/tsx/dist/register-BOkp8V6j.cjs +10 -0
  71. package/node_modules/tsx/dist/register-BnTWPeIB.mjs +10 -0
  72. package/node_modules/tsx/dist/register-CHVGxKtC.cjs +2 -0
  73. package/node_modules/tsx/dist/register-D_B8UL5H.mjs +2 -0
  74. package/node_modules/tsx/dist/repl.cjs +3 -0
  75. package/node_modules/tsx/dist/repl.mjs +3 -0
  76. package/node_modules/tsx/dist/require-CjvaJWEr.cjs +1 -0
  77. package/node_modules/tsx/dist/require-DzmC1hVr.mjs +1 -0
  78. package/node_modules/tsx/dist/suppress-warnings.cjs +1 -0
  79. package/node_modules/tsx/dist/suppress-warnings.mjs +1 -0
  80. package/node_modules/tsx/dist/temporary-directory-B83uKxJF.cjs +1 -0
  81. package/node_modules/tsx/dist/temporary-directory-BDDVQOvU.mjs +1 -0
  82. package/node_modules/tsx/dist/types-Cxp8y2TL.d.ts +5 -0
  83. package/node_modules/tsx/package.json +67 -0
  84. package/package.json +11 -6
  85. package/src/autostart.ts +5 -1
  86. package/src/compat.ts +193 -47
  87. package/src/cursor.ts +59 -3
  88. package/src/extensionBundle.ts +1 -1
  89. package/src/extensionLink.ts +28 -7
  90. package/src/install.ts +176 -24
  91. package/src/installRecord.ts +2 -0
  92. package/src/patchSet.ts +12 -6
  93. package/src/platform.ts +3 -3
  94. package/src/repair.ts +10 -1
  95. package/src/restore.ts +12 -4
  96. package/src/serviceProcess.ts +2 -1
  97. package/src/status.ts +6 -0
  98. package/src/trial.ts +1 -0
  99. package/test/autostart.test.ts +23 -2
  100. package/test/compat.test.ts +238 -3
  101. package/test/cursor-pool-bin.test.ts +1 -0
  102. package/test/cursor.test.ts +60 -1
  103. package/test/e2e-install.test.ts +53 -0
  104. package/test/extensionLink.test.ts +48 -2
  105. package/test/install.test.ts +191 -6
  106. package/test/repair.test.ts +1 -0
  107. package/test/serviceProcess.test.ts +10 -1
  108. package/test/status.test.ts +1 -0
package/src/compat.ts CHANGED
@@ -5,72 +5,171 @@ import type {
5
5
  CompatibilityManifestEntry,
6
6
  CompatibilityManifestEnvelope,
7
7
  } from '@cursor-pool/shared/manifest';
8
- import { CURSOR_AGENT_EXEC_RELATIVE_PATH } from '@cursor-pool/patcher';
8
+ import {
9
+ CURSOR_AGENT_EXEC_RELATIVE_PATH,
10
+ CURSOR_WORKBENCH_RELATIVE_PATH,
11
+ } from '@cursor-pool/patcher';
9
12
  import type { CliEnvironment } from './environment';
10
13
  import type { CursorInfo } from './cursor';
11
14
 
12
- export const DEFAULT_COMPAT_ENTRIES: CompatibilityManifestEntry[] = [
13
- {
15
+ function macPatchTargets(agentSha256: string, workbenchSha256: string) {
16
+ return [
17
+ {
18
+ name: 'agent-exec',
19
+ targetRelativePath: CURSOR_AGENT_EXEC_RELATIVE_PATH,
20
+ expectedSha256: agentSha256,
21
+ patchStrategy: 'cursor-agent-exec-snippet',
22
+ verifyMarker: 'cursor-pool',
23
+ },
24
+ {
25
+ name: 'workbench',
26
+ targetRelativePath: CURSOR_WORKBENCH_RELATIVE_PATH,
27
+ expectedSha256: workbenchSha256,
28
+ patchStrategy: 'cursor-workbench-auth-gate',
29
+ verifyMarker: 'cursor-pool-workbench',
30
+ },
31
+ ];
32
+ }
33
+
34
+ function macCompatEntry(input: {
35
+ arch: 'arm64' | 'x64';
36
+ officialMajorVersion: string;
37
+ verifiedCursorVersion: string;
38
+ cursorVersion: string;
39
+ cursorCommit: string;
40
+ adapterVersion: string;
41
+ structureFamily: string;
42
+ takeoverPlanId: string;
43
+ agentSha256: string;
44
+ workbenchSha256: string;
45
+ userMessage: string;
46
+ }): CompatibilityManifestEntry {
47
+ return {
14
48
  platform: 'darwin',
15
- arch: 'arm64',
16
- cursorVersion: '3.5.38',
17
- cursorCommit: '009bb5a3600dd98fe1c1f25798f767f686e14750',
49
+ arch: input.arch,
50
+ officialSourceUrl: 'https://cursor.com/download',
51
+ officialDownloadUrl: `https://api2.cursor.sh/updates/download/golden/darwin-${input.arch}/cursor/${input.officialMajorVersion}`,
52
+ officialDownloadPlatform: `darwin-${input.arch}`,
53
+ verifiedCursorVersion: input.verifiedCursorVersion,
54
+ cursorVersion: input.officialMajorVersion,
55
+ cursorCommit: input.cursorCommit,
18
56
  supportStatus: 'supported',
57
+ adapterVersion: input.adapterVersion,
58
+ takeoverPlanId: input.takeoverPlanId,
59
+ structureFamily: input.structureFamily,
60
+ patchTargets: macPatchTargets(input.agentSha256, input.workbenchSha256),
19
61
  targetRelativePath: CURSOR_AGENT_EXEC_RELATIVE_PATH,
20
- expectedSha256: 'cb18f0237278884a39e2ce2b8664255e12689ad0803c20096c38e86c36acc51f',
21
- structureSignature: 'ep1-verified-current',
62
+ expectedSha256: input.agentSha256,
63
+ structureSignature: input.structureFamily,
22
64
  patchStrategy: 'cursor-agent-exec-snippet',
23
65
  verifyMarker: 'cursor-pool',
24
66
  restoreStrategy: 'external-backup',
25
- minCliVersion: '0.5.6',
26
- minExtensionVersion: '0.5.6',
27
- minServiceVersion: '0.5.6',
67
+ minCliVersion: '0.5.8',
68
+ minExtensionVersion: '0.5.8',
69
+ minServiceVersion: '0.5.8',
28
70
  requiresWritableAppBundle: true,
29
71
  requiresAdHocResign: true,
30
- userMessage: 'Cursor 3.5.38 is supported for MVP-0.',
31
- },
32
- {
33
- platform: 'darwin',
72
+ userMessage: input.userMessage,
73
+ };
74
+ }
75
+
76
+ export const DEFAULT_COMPAT_ENTRIES: CompatibilityManifestEntry[] = [
77
+ macCompatEntry({
78
+ arch: 'arm64',
79
+ officialMajorVersion: '3.4',
80
+ verifiedCursorVersion: '3.4.20',
81
+ cursorVersion: '3.4.20',
82
+ cursorCommit: '0cf8b06883f54e26bb4f0fb8647c9500ccb43310',
83
+ adapterVersion: '0.4.8',
84
+ structureFamily: 'mac-agent-D-workbench-U0',
85
+ takeoverPlanId: 'cursor-3.4-mac-agent-d-workbench-u0',
86
+ agentSha256: 'c63ae78a3b72df82db224839d552b1f8ca38011e45a37a55ae4ccd75fcdc125f',
87
+ workbenchSha256: '01a7005df6a76159b4edafee3c4a233003626e082212f9d042a81239a2bde388',
88
+ userMessage: 'Cursor 3.4.20 macOS arm64 is supported by the v0.4 adapter.',
89
+ }),
90
+ macCompatEntry({
34
91
  arch: 'x64',
92
+ officialMajorVersion: '3.4',
93
+ verifiedCursorVersion: '3.4.20',
94
+ cursorVersion: '3.4.20',
95
+ cursorCommit: '0cf8b06883f54e26bb4f0fb8647c9500ccb43310',
96
+ adapterVersion: '0.4.8',
97
+ structureFamily: 'mac-agent-D-workbench-U0',
98
+ takeoverPlanId: 'cursor-3.4-mac-agent-d-workbench-u0',
99
+ agentSha256: 'c63ae78a3b72df82db224839d552b1f8ca38011e45a37a55ae4ccd75fcdc125f',
100
+ workbenchSha256: '01a7005df6a76159b4edafee3c4a233003626e082212f9d042a81239a2bde388',
101
+ userMessage: 'Cursor 3.4.20 macOS x64 is supported by the v0.4 adapter.',
102
+ }),
103
+ macCompatEntry({
104
+ arch: 'arm64',
105
+ officialMajorVersion: '3.5',
106
+ verifiedCursorVersion: '3.5.38',
35
107
  cursorVersion: '3.5.38',
36
108
  cursorCommit: '009bb5a3600dd98fe1c1f25798f767f686e14750',
37
- supportStatus: 'supported',
38
- targetRelativePath: CURSOR_AGENT_EXEC_RELATIVE_PATH,
39
- expectedSha256: 'cb18f0237278884a39e2ce2b8664255e12689ad0803c20096c38e86c36acc51f',
40
- structureSignature: 'ep1-verified-current',
41
- patchStrategy: 'cursor-agent-exec-snippet',
42
- verifyMarker: 'cursor-pool',
43
- restoreStrategy: 'external-backup',
44
- minCliVersion: '0.5.6',
45
- minExtensionVersion: '0.5.6',
46
- minServiceVersion: '0.5.6',
47
- requiresWritableAppBundle: true,
48
- requiresAdHocResign: true,
49
- userMessage: 'Cursor 3.5.38 is supported for MVP-0 under Rosetta-launched installers.',
50
- },
51
- {
52
- platform: 'darwin',
109
+ adapterVersion: '0.5.8',
110
+ structureFamily: 'mac-agent-F-workbench-p-L0',
111
+ takeoverPlanId: 'cursor-3.5-mac-agent-f-workbench-p-l0',
112
+ agentSha256: 'cb18f0237278884a39e2ce2b8664255e12689ad0803c20096c38e86c36acc51f',
113
+ workbenchSha256: '19762fb81b7d5f1a42ffa8c0c487648432874d0892cfcdb32824b107f6dcf99d',
114
+ userMessage: 'Cursor 3.5.38 macOS arm64 is supported by the v0.5 adapter.',
115
+ }),
116
+ macCompatEntry({
117
+ arch: 'x64',
118
+ officialMajorVersion: '3.5',
119
+ verifiedCursorVersion: '3.5.38',
120
+ cursorVersion: '3.5.38',
121
+ cursorCommit: '009bb5a3600dd98fe1c1f25798f767f686e14750',
122
+ adapterVersion: '0.5.8',
123
+ structureFamily: 'mac-agent-F-workbench-p-L0',
124
+ takeoverPlanId: 'cursor-3.5-mac-agent-f-workbench-p-l0',
125
+ agentSha256: 'cb18f0237278884a39e2ce2b8664255e12689ad0803c20096c38e86c36acc51f',
126
+ workbenchSha256: '19762fb81b7d5f1a42ffa8c0c487648432874d0892cfcdb32824b107f6dcf99d',
127
+ userMessage: 'Cursor 3.5.38 macOS x64 is supported by the v0.5 adapter.',
128
+ }),
129
+ macCompatEntry({
53
130
  arch: 'arm64',
131
+ officialMajorVersion: '3.6',
132
+ verifiedCursorVersion: '3.6.21',
54
133
  cursorVersion: '3.6.21',
55
134
  cursorCommit: 'e7a7e93f4d75f8272503ecf33cedbaae10114a10',
56
- supportStatus: 'supported',
57
- targetRelativePath: CURSOR_AGENT_EXEC_RELATIVE_PATH,
58
- expectedSha256: '222512631b78fddcdca3fa76c0dd458a7a86751dde19c998ceb31b3fe1905ebf',
59
- structureSignature: 'ep1-verified-cursor-3-6-21',
60
- patchStrategy: 'cursor-agent-exec-snippet',
61
- verifyMarker: 'cursor-pool',
62
- restoreStrategy: 'external-backup',
63
- minCliVersion: '0.5.6',
64
- minExtensionVersion: '0.5.6',
65
- minServiceVersion: '0.5.6',
66
- requiresWritableAppBundle: true,
67
- requiresAdHocResign: true,
68
- userMessage: 'Cursor 3.6.21 is supported for MVP-0.',
69
- },
135
+ adapterVersion: '0.5.8',
136
+ structureFamily: 'mac-agent-c-workbench-h-uv',
137
+ takeoverPlanId: 'cursor-3.6-mac-agent-c-workbench-h-uv',
138
+ agentSha256: '222512631b78fddcdca3fa76c0dd458a7a86751dde19c998ceb31b3fe1905ebf',
139
+ workbenchSha256: '97562e30eebf41b9e162eb100966a2caf09c1c2fcf55bc87d5a84f9de67511f5',
140
+ userMessage: 'Cursor 3.6.21 macOS arm64 is supported by the v0.5 adapter.',
141
+ }),
142
+ macCompatEntry({
143
+ arch: 'arm64',
144
+ officialMajorVersion: '3.6',
145
+ verifiedCursorVersion: '3.6.31',
146
+ cursorVersion: '3.6.31',
147
+ cursorCommit: '81fcf2931d7687b4ff3f3017858d0c6dee7e2a60',
148
+ adapterVersion: '0.5.8',
149
+ structureFamily: 'mac-agent-c-workbench-h-uv',
150
+ takeoverPlanId: 'cursor-3.6-mac-agent-c-workbench-h-uv',
151
+ agentSha256: '05bfa29eacb8271c378765ead4bf881f806b97549dd13367183aa7a9331c1131',
152
+ workbenchSha256: '97562e30eebf41b9e162eb100966a2caf09c1c2fcf55bc87d5a84f9de67511f5',
153
+ userMessage: 'Cursor 3.6.31 macOS arm64 is supported by the v0.5 adapter.',
154
+ }),
155
+ macCompatEntry({
156
+ arch: 'arm64',
157
+ officialMajorVersion: '3.7',
158
+ verifiedCursorVersion: '3.7.12',
159
+ cursorVersion: '3.7.12',
160
+ cursorCommit: 'b887a26c4f70bd8136bfffeda812b24194ec9ce0',
161
+ adapterVersion: '0.6.0',
162
+ structureFamily: 'mac-3.7-agent-Et-workbench-wv',
163
+ takeoverPlanId: 'cursor-3.7-mac-agent-et-workbench-wv',
164
+ agentSha256: '9ce7a2f40a98a27eb1b609a79e0e1707bad5fbb02493693f6f18945a7640dde4',
165
+ workbenchSha256: 'e91aa502a84d5b1653a2c1f3a71a2d4160ab5f1de5809dd230756ecc0cc27db9',
166
+ userMessage: 'Cursor 3.7.12 macOS arm64 is supported by the v0.6 adapter.',
167
+ }),
70
168
  ];
71
169
 
72
170
  export type ResolveCompatOptions = {
73
171
  entries?: CompatibilityManifestEntry[];
172
+ adapterVersion?: string;
74
173
  };
75
174
 
76
175
  export type CompatManifestFetchResponse = {
@@ -121,6 +220,34 @@ function asBoolean(value: unknown, field: string) {
121
220
  return value;
122
221
  }
123
222
 
223
+ function asOptionalString(value: unknown, field: string) {
224
+ if (value === undefined) {
225
+ return undefined;
226
+ }
227
+ return asString(value, field);
228
+ }
229
+
230
+ function normalizePatchTargets(value: unknown) {
231
+ if (value === undefined) {
232
+ return undefined;
233
+ }
234
+ if (!Array.isArray(value)) {
235
+ throw new Error('compat manifest patchTargets invalid');
236
+ }
237
+ return value.map((target) => {
238
+ if (!isRecord(target)) {
239
+ throw new Error('compat manifest patchTarget invalid');
240
+ }
241
+ return {
242
+ name: asString(target.name, 'patchTargets.name'),
243
+ targetRelativePath: asString(target.targetRelativePath, 'patchTargets.targetRelativePath'),
244
+ expectedSha256: asString(target.expectedSha256, 'patchTargets.expectedSha256'),
245
+ patchStrategy: asString(target.patchStrategy, 'patchTargets.patchStrategy'),
246
+ verifyMarker: asString(target.verifyMarker, 'patchTargets.verifyMarker'),
247
+ };
248
+ });
249
+ }
250
+
124
251
  function normalizeRule(value: unknown): RemoteCompatibilityManifestRule {
125
252
  if (!isRecord(value)) {
126
253
  throw new Error('compat manifest rule invalid');
@@ -136,12 +263,23 @@ function normalizeRule(value: unknown): RemoteCompatibilityManifestRule {
136
263
  ) {
137
264
  throw new Error('compat manifest revision invalid');
138
265
  }
266
+ const adapterVersion = asOptionalString(value.adapterVersion, 'adapterVersion');
267
+ const structureFamily = asOptionalString(value.structureFamily, 'structureFamily');
268
+ const patchTargets = normalizePatchTargets(value.patchTargets);
139
269
  return {
140
270
  platform: asString(value.platform, 'platform'),
141
271
  arch: asString(value.arch, 'arch'),
272
+ ...(value.officialSourceUrl === undefined ? {} : { officialSourceUrl: asString(value.officialSourceUrl, 'officialSourceUrl') }),
273
+ ...(value.officialDownloadUrl === undefined ? {} : { officialDownloadUrl: asString(value.officialDownloadUrl, 'officialDownloadUrl') }),
274
+ ...(value.officialDownloadPlatform === undefined ? {} : { officialDownloadPlatform: asString(value.officialDownloadPlatform, 'officialDownloadPlatform') }),
275
+ ...(value.verifiedCursorVersion === undefined ? {} : { verifiedCursorVersion: asString(value.verifiedCursorVersion, 'verifiedCursorVersion') }),
142
276
  cursorVersion: asString(value.cursorVersion, 'cursorVersion'),
143
277
  cursorCommit: asString(value.cursorCommit, 'cursorCommit'),
144
278
  supportStatus,
279
+ ...(adapterVersion === undefined ? {} : { adapterVersion }),
280
+ takeoverPlanId: asString(value.takeoverPlanId, 'takeoverPlanId'),
281
+ ...(structureFamily === undefined ? {} : { structureFamily }),
282
+ ...(patchTargets === undefined ? {} : { patchTargets }),
145
283
  targetRelativePath: asString(value.targetRelativePath, 'targetRelativePath'),
146
284
  expectedSha256: asString(value.expectedSha256, 'expectedSha256'),
147
285
  structureSignature: asString(value.structureSignature, 'structureSignature'),
@@ -163,8 +301,10 @@ function canonicalRuleSegment(rule: RemoteCompatibilityManifestRule) {
163
301
  rule.platform,
164
302
  rule.arch,
165
303
  rule.cursorVersion,
304
+ rule.verifiedCursorVersion ?? '',
166
305
  rule.cursorCommit,
167
306
  rule.supportStatus,
307
+ rule.takeoverPlanId,
168
308
  String(rule.revision ?? 1),
169
309
  ].join(':');
170
310
  }
@@ -216,6 +356,11 @@ export function verifyCompatManifestEnvelope(
216
356
  return rules.map(({ revision, ...rule }) => rule);
217
357
  }
218
358
 
359
+ function officialCursorMajorVersion(cursorVersion: string) {
360
+ const match = cursorVersion.match(/^(\d+\.\d+)(?:\.|$)/);
361
+ return match?.[1] ?? cursorVersion;
362
+ }
363
+
219
364
  export function compatManifestUrlFromApiBaseUrl(apiBaseUrl: string) {
220
365
  return `${apiBaseUrl.replace(/\/+$/, '')}/api/client/compatibility/manifest`;
221
366
  }
@@ -265,8 +410,9 @@ export function resolveCompatEntry(
265
410
  (candidate) =>
266
411
  candidate.platform === environment.platform &&
267
412
  candidate.arch === environment.arch &&
268
- candidate.cursorVersion === cursor.version &&
269
- candidate.cursorCommit === cursor.commit,
413
+ candidate.cursorVersion === officialCursorMajorVersion(cursor.version) &&
414
+ candidate.cursorCommit === cursor.commit &&
415
+ (!options.adapterVersion || candidate.adapterVersion === options.adapterVersion),
270
416
  );
271
417
 
272
418
  if (!entry) {
package/src/cursor.ts CHANGED
@@ -1,7 +1,12 @@
1
- import { readFile } from 'node:fs/promises';
2
- import { join, win32 } from 'node:path';
1
+ import { execFile } from 'node:child_process';
2
+ import { createHash } from 'node:crypto';
3
+ import { promisify } from 'node:util';
4
+ import { chmod, mkdir, readFile, rm, stat } from 'node:fs/promises';
5
+ import { basename, join, win32 } from 'node:path';
6
+ import { homedir } from 'node:os';
3
7
 
4
8
  export const DEFAULT_MACOS_CURSOR_APP_PATH = '/Applications/Cursor.app';
9
+ const execFileAsync = promisify(execFile);
5
10
 
6
11
  export type CursorInfo = {
7
12
  appPath: string;
@@ -14,6 +19,7 @@ export type FindCursorOptions = {
14
19
  productRelativePath?: string;
15
20
  platform?: NodeJS.Platform;
16
21
  env?: NodeJS.ProcessEnv;
22
+ appImageExtract?: (appImagePath: string, outputRoot: string) => Promise<string>;
17
23
  };
18
24
 
19
25
  type CursorProductJson = {
@@ -43,7 +49,8 @@ function readString(value: unknown) {
43
49
  }
44
50
 
45
51
  export async function findCursor(options: FindCursorOptions = {}): Promise<CursorInfo> {
46
- const appPath = options.appPath ?? defaultCursorAppPath(options);
52
+ const rawAppPath = options.appPath ?? defaultCursorAppPath(options);
53
+ const appPath = await resolveCursorAppPath(rawAppPath, options);
47
54
  const productJsonPath = await resolveProductJsonPath(appPath, options);
48
55
  const product = JSON.parse(await readFile(productJsonPath, 'utf8')) as CursorProductJson;
49
56
  const version = readString(product.version);
@@ -66,6 +73,55 @@ export async function findCursor(options: FindCursorOptions = {}): Promise<Curso
66
73
  };
67
74
  }
68
75
 
76
+ async function pathIsFile(path: string) {
77
+ try {
78
+ return (await stat(path)).isFile();
79
+ } catch {
80
+ return false;
81
+ }
82
+ }
83
+
84
+ function linuxAppImageExtractRoot(appImagePath: string) {
85
+ const hash = createHash('sha256').update(appImagePath).digest('hex').slice(0, 16);
86
+ const name = basename(appImagePath).replace(/\.AppImage$/i, '').replace(/[^A-Za-z0-9._-]/g, '-');
87
+ return join(homedir(), '.cursor-pool/appimages', `${name}-${hash}`);
88
+ }
89
+
90
+ async function defaultExtractLinuxAppImage(appImagePath: string, outputRoot: string) {
91
+ await rm(outputRoot, { recursive: true, force: true });
92
+ await mkdir(outputRoot, { recursive: true });
93
+ await chmod(appImagePath, 0o755).catch(() => undefined);
94
+ await execFileAsync(appImagePath, ['--appimage-extract'], {
95
+ cwd: outputRoot,
96
+ maxBuffer: 1024 * 1024 * 20,
97
+ });
98
+ return join(outputRoot, 'squashfs-root');
99
+ }
100
+
101
+ async function resolveCursorAppPath(appPath: string, options: FindCursorOptions) {
102
+ if (
103
+ (options.platform ?? process.platform) !== 'linux' ||
104
+ !/\.AppImage$/i.test(appPath) ||
105
+ !(await pathIsFile(appPath))
106
+ ) {
107
+ return appPath;
108
+ }
109
+
110
+ const outputRoot = linuxAppImageExtractRoot(appPath);
111
+ const extractedPath = join(outputRoot, 'squashfs-root');
112
+ try {
113
+ await readFile(join(extractedPath, 'usr/share/cursor/resources/app/product.json'), 'utf8');
114
+ return extractedPath;
115
+ } catch (error) {
116
+ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
117
+ throw error;
118
+ }
119
+ }
120
+
121
+ const extract = options.appImageExtract ?? defaultExtractLinuxAppImage;
122
+ return extract(appPath, outputRoot);
123
+ }
124
+
69
125
  async function resolveProductJsonPath(appPath: string, options: FindCursorOptions) {
70
126
  if (options.productRelativePath) {
71
127
  return join(appPath, options.productRelativePath);
@@ -55,7 +55,7 @@ async function exists(path: string) {
55
55
  function buildRuntimeManifest(sourceManifest: Record<string, unknown>) {
56
56
  return {
57
57
  name: 'cursorpool',
58
- version: typeof sourceManifest.version === 'string' ? sourceManifest.version : '0.5.6',
58
+ version: typeof sourceManifest.version === 'string' ? sourceManifest.version : '0.5.8',
59
59
  displayName: 'Cursor Pool 平台模式',
60
60
  publisher: 'cursor-pool',
61
61
  type: sourceManifest.type,
@@ -4,7 +4,8 @@ import { basename, dirname, join, resolve } from 'node:path';
4
4
  import { resolveExtensionInstallPath } from './extensionBundle';
5
5
  import type { ExtensionState } from './trial';
6
6
 
7
- export const LINKED_EXTENSION_DIRNAME = 'cursor-pool.extension-0.5.6';
7
+ export const LINKED_EXTENSION_DIRNAME = 'cursor-pool.extension-0.5.8';
8
+ const LINKED_EXTENSION_DIRNAME_PREFIX = 'cursor-pool.extension-';
8
9
  const RUNTIME_EXTENSION_ID = 'cursor-pool.cursorpool';
9
10
  const STALE_EXTENSION_IDS = new Set([
10
11
  RUNTIME_EXTENSION_ID,
@@ -48,10 +49,17 @@ async function readRuntimeManifest(linkedPath: string) {
48
49
  };
49
50
  const publisher = typeof manifest.publisher === 'string' ? manifest.publisher : 'cursor-pool';
50
51
  const name = typeof manifest.name === 'string' ? manifest.name : 'cursorpool';
51
- const version = typeof manifest.version === 'string' ? manifest.version : '0.5.6';
52
+ const version = typeof manifest.version === 'string' ? manifest.version : '0.5.8';
52
53
  return { id: `${publisher}.${name}`, version };
53
54
  }
54
55
 
56
+ function linkedExtensionDirnameForVersion(version: string) {
57
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(version)) {
58
+ throw new Error(`Unsafe extension version: ${version}`);
59
+ }
60
+ return `${LINKED_EXTENSION_DIRNAME_PREFIX}${version}`;
61
+ }
62
+
55
63
  export async function refreshCursorExtensionsIndex(cursorExtensionsDir: string, linkedPath: string) {
56
64
  const indexPath = join(resolveExtensionInstallPath(cursorExtensionsDir), 'extensions.json');
57
65
  if (!(await exists(indexPath))) {
@@ -63,11 +71,13 @@ export async function refreshCursorExtensionsIndex(cursorExtensionsDir: string,
63
71
  relativeLocation?: string;
64
72
  }>;
65
73
  const manifest = await readRuntimeManifest(linkedPath);
74
+ const relativeLocation = basename(linkedPath);
66
75
  const nextEntries = entries.filter((entry) => {
67
76
  const id = entry.identifier?.id;
77
+ const relativeLocation = entry.relativeLocation ?? '';
68
78
  return (
69
79
  !STALE_EXTENSION_IDS.has(id ?? '') &&
70
- entry.relativeLocation !== LINKED_EXTENSION_DIRNAME &&
80
+ !relativeLocation.startsWith(LINKED_EXTENSION_DIRNAME_PREFIX) &&
71
81
  entry.relativeLocation !== 'keg1255.cursorpool-1.0.52'
72
82
  );
73
83
  });
@@ -79,7 +89,7 @@ export async function refreshCursorExtensionsIndex(cursorExtensionsDir: string,
79
89
  path: linkedPath,
80
90
  scheme: 'file',
81
91
  },
82
- relativeLocation: LINKED_EXTENSION_DIRNAME,
92
+ relativeLocation,
83
93
  version: manifest.version,
84
94
  });
85
95
  await writeFile(indexPath, `${JSON.stringify(nextEntries, null, 2)}\n`, 'utf8');
@@ -92,9 +102,9 @@ function assertSafeLinkedExtensionPath(linkedPath: string) {
92
102
  (segment) => segment === 'extensions' || segment === 'Extensions',
93
103
  );
94
104
 
95
- if (basename(resolvedLinkedPath) !== LINKED_EXTENSION_DIRNAME || !hasExtensionsParent) {
105
+ if (!basename(resolvedLinkedPath).startsWith(LINKED_EXTENSION_DIRNAME_PREFIX) || !hasExtensionsParent) {
96
106
  throw new Error(
97
- `Unsafe linked extension path for recursive removal: ${linkedPath}. Expected an extensions/${LINKED_EXTENSION_DIRNAME} bundle path.`,
107
+ `Unsafe linked extension path for recursive removal: ${linkedPath}. Expected an extensions/${LINKED_EXTENSION_DIRNAME_PREFIX}<version> bundle path.`,
98
108
  );
99
109
  }
100
110
  }
@@ -103,6 +113,14 @@ export function linkedExtensionPathForDir(cursorExtensionsDir: string) {
103
113
  return join(resolveExtensionInstallPath(cursorExtensionsDir), LINKED_EXTENSION_DIRNAME);
104
114
  }
105
115
 
116
+ export async function linkedExtensionPathForSource(cursorExtensionsDir: string, sourceBundlePath: string) {
117
+ const manifest = await readRuntimeManifest(sourceBundlePath);
118
+ return join(
119
+ resolveExtensionInstallPath(cursorExtensionsDir),
120
+ linkedExtensionDirnameForVersion(manifest.version),
121
+ );
122
+ }
123
+
106
124
  export async function getLinkedExtensionState(linkedPath: string | undefined): Promise<LinkedExtensionState> {
107
125
  if (!linkedPath) {
108
126
  return 'missing';
@@ -119,7 +137,10 @@ export async function linkExtensionBundle({
119
137
  cursorExtensionsDir,
120
138
  }: LinkExtensionBundleOptions): Promise<LinkExtensionBundleResult> {
121
139
  const resolvedSourceBundlePath = resolveExtensionInstallPath(sourceBundlePath);
122
- const linkedPath = linkedExtensionPathForDir(cursorExtensionsDir);
140
+ const linkedPath =
141
+ (await getLinkedExtensionState(resolvedSourceBundlePath)) === 'missing'
142
+ ? linkedExtensionPathForDir(cursorExtensionsDir)
143
+ : await linkedExtensionPathForSource(cursorExtensionsDir, resolvedSourceBundlePath);
123
144
 
124
145
  if ((await getLinkedExtensionState(resolvedSourceBundlePath)) === 'missing') {
125
146
  return { state: 'missing', linkedPath };