@moonshot-ai/kimi-code 0.39.0 → 0.40.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.
Files changed (85) hide show
  1. package/dist/main.mjs +13287 -16480
  2. package/dist-web/assets/{CodeBlockNode-TQKPIcUp.js → CodeBlockNode-BNOCy4ai.js} +2 -2
  3. package/dist-web/assets/{Tooltip-3Kylewm2.js → Tooltip-DebxPz-0.js} +1 -1
  4. package/dist-web/assets/{abnfDiagram-VRR7QNED-DZ5l2NNF.js → abnfDiagram-VRR7QNED-Bo8g46XA.js} +1 -1
  5. package/dist-web/assets/{arc-C525Mmp6.js → arc-CxCMb0Ef.js} +1 -1
  6. package/dist-web/assets/{architectureDiagram-ZJ3FMSHR-DsZ4TpMi.js → architectureDiagram-ZJ3FMSHR-ZNnAzHHt.js} +1 -1
  7. package/dist-web/assets/{blockDiagram-677ZJIJ3-DHUznNgz.js → blockDiagram-677ZJIJ3-DeafJYAk.js} +1 -1
  8. package/dist-web/assets/{c4Diagram-LMCZKHZV-BRKLxvwh.js → c4Diagram-LMCZKHZV-oGCYLt-v.js} +1 -1
  9. package/dist-web/assets/channel-B8eMh4sk.js +1 -0
  10. package/dist-web/assets/{chunk-2Q5K7J3B-CldwFnN4.js → chunk-2Q5K7J3B-CdhiweKX.js} +1 -1
  11. package/dist-web/assets/{chunk-32BRIVSS-0ImWpiFu.js → chunk-32BRIVSS-mfMW4-bV.js} +1 -1
  12. package/dist-web/assets/{chunk-5VM5RSS4-K_NNkrmw.js → chunk-5VM5RSS4-BRWxG5tM.js} +1 -1
  13. package/dist-web/assets/{chunk-EX3LRPZG-Du5tJK2w.js → chunk-EX3LRPZG-DoQjV3QX.js} +1 -1
  14. package/dist-web/assets/{chunk-JWPE2WC7-CnsTQcA8.js → chunk-JWPE2WC7-C7S97jLg.js} +1 -1
  15. package/dist-web/assets/{chunk-MOJQB5TN-BWiH3s-c.js → chunk-MOJQB5TN-DcgqWRh_.js} +1 -1
  16. package/dist-web/assets/{chunk-RYQCIY6F-Bg4GHN0V.js → chunk-RYQCIY6F-DRHEFBXE.js} +1 -1
  17. package/dist-web/assets/{chunk-V7JOEXUC-5w7loxVR.js → chunk-V7JOEXUC-Ci6LL3n4.js} +1 -1
  18. package/dist-web/assets/{chunk-VR4S4FIN-Cp8JVohH.js → chunk-VR4S4FIN-DW1NNlBr.js} +1 -1
  19. package/dist-web/assets/{chunk-XXDRQBXY-BwEKpC0s.js → chunk-XXDRQBXY-4LplLZkT.js} +1 -1
  20. package/dist-web/assets/classDiagram-OUVF2IWQ-DDHH-ciJ.js +1 -0
  21. package/dist-web/assets/classDiagram-v2-EOCWNBFH-DDHH-ciJ.js +1 -0
  22. package/dist-web/assets/{cose-bilkent-JH36ORCC-CyvFRDuS.js → cose-bilkent-JH36ORCC-B9Aj57Fl.js} +1 -1
  23. package/dist-web/assets/{cynefin-VYW2F7L2-Ct8il_Gb.js → cynefin-VYW2F7L2-DEnbetzx.js} +1 -1
  24. package/dist-web/assets/{cynefinDiagram-TSTJHNR4-BP26cYtj.js → cynefinDiagram-TSTJHNR4-BrabEtIe.js} +1 -1
  25. package/dist-web/assets/{dagre-VKFMJZFB-BuaL-XNK.js → dagre-VKFMJZFB-BDxu-EPk.js} +1 -1
  26. package/dist-web/assets/{diagram-FQU43EPY-BXKGXolr.js → diagram-FQU43EPY-D5Cw9BLp.js} +1 -1
  27. package/dist-web/assets/{diagram-G47NLZAW-DsoHq3g9.js → diagram-G47NLZAW-CgstEr0d.js} +1 -1
  28. package/dist-web/assets/{diagram-NH7WQ7WH-l2Pip_5t.js → diagram-NH7WQ7WH-BO_tIsIn.js} +1 -1
  29. package/dist-web/assets/{diagram-OA4YK3LP-DO8otT0J.js → diagram-OA4YK3LP-BiXs8uY6.js} +1 -1
  30. package/dist-web/assets/{diagram-WEI45ONY-tJhlXXXb.js → diagram-WEI45ONY-DzHIyeb8.js} +1 -1
  31. package/dist-web/assets/{ebnfDiagram-CCIWWBDH-DuVJXtzP.js → ebnfDiagram-CCIWWBDH-KkwEvI7D.js} +1 -1
  32. package/dist-web/assets/{erDiagram-Q63AITRT-q8LwmVdA.js → erDiagram-Q63AITRT-Ca__aYfw.js} +1 -1
  33. package/dist-web/assets/{flowDiagram-23GEKE2U-CSk8qFwd.js → flowDiagram-23GEKE2U-CW0gFw60.js} +1 -1
  34. package/dist-web/assets/{ganttDiagram-NO4QXBWP-BliHEJI2.js → ganttDiagram-NO4QXBWP-BA6Xg4gl.js} +1 -1
  35. package/dist-web/assets/{gitGraphDiagram-IHSO6WYX-DbTFMBma.js → gitGraphDiagram-IHSO6WYX-CuVLyl1X.js} +1 -1
  36. package/dist-web/assets/index--0t1wzw_.js +812 -0
  37. package/dist-web/assets/{index-DuIqvsra.js → index--RgMhz1s.js} +1 -1
  38. package/dist-web/assets/{index-BlRksujk.js → index-BAprvwXC.js} +1 -1
  39. package/dist-web/assets/{index-Dq61ZbBp.js → index-D6YNmmDH.js} +2 -2
  40. package/dist-web/assets/index-DDrI0Pxr.css +32 -0
  41. package/dist-web/assets/{index10-C0TQjdWb.js → index10-BOfr-M0b.js} +1 -1
  42. package/dist-web/assets/{index11-DNt97Omj.js → index11-BuI7qYB6.js} +1 -1
  43. package/dist-web/assets/{index5-DPyXoDa2.js → index5-DEKGWcGw.js} +1 -1
  44. package/dist-web/assets/{index6-RUyA_B9p.js → index6-BW9QZVgb.js} +1 -1
  45. package/dist-web/assets/{index7-ClRJOaWc.js → index7-N0eJaunx.js} +1 -1
  46. package/dist-web/assets/{index8-Dx-vzK_w.js → index8-DQP5_GpC.js} +1 -1
  47. package/dist-web/assets/{infoDiagram-FWYZ7A6U-Bge59IZG.js → infoDiagram-FWYZ7A6U-CX4ZaiQy.js} +1 -1
  48. package/dist-web/assets/{ishikawaDiagram-FXEZZL3T-y8Y7SwAW.js → ishikawaDiagram-FXEZZL3T-BRv7GetB.js} +1 -1
  49. package/dist-web/assets/{journeyDiagram-5HDEW3XC-B1mQtsyC.js → journeyDiagram-5HDEW3XC-DnAUfwJf.js} +1 -1
  50. package/dist-web/assets/{kanban-definition-HUTT4EX6-Bpgk0PjP.js → kanban-definition-HUTT4EX6-DgSN0HrV.js} +1 -1
  51. package/dist-web/assets/{linear-Cu3jckud.js → linear-DLj646oc.js} +1 -1
  52. package/dist-web/assets/{mermaid.core-CGrLstUA.js → mermaid.core-CqiFQExc.js} +4 -4
  53. package/dist-web/assets/{mindmap-definition-LN4V7U3C-jRWZCKa4.js → mindmap-definition-LN4V7U3C-Cny-i6X0.js} +1 -1
  54. package/dist-web/assets/{pegDiagram-2B236MQR-BBHHexxE.js → pegDiagram-2B236MQR-CBag-DmB.js} +1 -1
  55. package/dist-web/assets/{pieDiagram-ENE6RG2P-h3KzynJ8.js → pieDiagram-ENE6RG2P-CTlOLW1d.js} +1 -1
  56. package/dist-web/assets/{quadrantDiagram-ABIIQ3AL-DFm-eCQb.js → quadrantDiagram-ABIIQ3AL-BjfJ-MB6.js} +1 -1
  57. package/dist-web/assets/{railroadDiagram-RFXS5EU6-Buc-7xRH.js → railroadDiagram-RFXS5EU6-B94MRAkj.js} +1 -1
  58. package/dist-web/assets/{requirementDiagram-TGXJPOKE-CSxNTrK4.js → requirementDiagram-TGXJPOKE-Blc39rih.js} +1 -1
  59. package/dist-web/assets/{sankeyDiagram-HTMAVEWB-Dd9T4UrJ.js → sankeyDiagram-HTMAVEWB-RXRXikwd.js} +1 -1
  60. package/dist-web/assets/{sequenceDiagram-DBY2YBRQ-Cr-Sj8Pb.js → sequenceDiagram-DBY2YBRQ-BQbflmP-.js} +1 -1
  61. package/dist-web/assets/{sizeCapture-X5ZJPWSS-Cl-kJspZ.js → sizeCapture-X5ZJPWSS-BVkVkPWz.js} +1 -1
  62. package/dist-web/assets/{stateDiagram-2N3HPSRC-BYfWdLSL.js → stateDiagram-2N3HPSRC-DazF4orO.js} +1 -1
  63. package/dist-web/assets/stateDiagram-v2-6OUMAXLB-BtkBtrYE.js +1 -0
  64. package/dist-web/assets/{swimlanes-5IMT3BWC-48M2vHyU.js → swimlanes-5IMT3BWC-C91Fo-8f.js} +2 -2
  65. package/dist-web/assets/swimlanesDiagram-G3AALYLV-DhDMBrjk.js +8 -0
  66. package/dist-web/assets/{timeline-definition-FHXFAJF6-CsGFMDtB.js → timeline-definition-FHXFAJF6-BIlewsXZ.js} +1 -1
  67. package/dist-web/assets/{vennDiagram-L72KCM5P-B5PLrg4a.js → vennDiagram-L72KCM5P-mckuIa7o.js} +1 -1
  68. package/dist-web/assets/{vue.runtime.esm-bundler-Ii2EKmOx.js → vue.runtime.esm-bundler-D7X8t2I9.js} +1 -1
  69. package/dist-web/assets/{wardleyDiagram-EHGQE667-B6OLyL9l.js → wardleyDiagram-EHGQE667-L9Dc-js-.js} +1 -1
  70. package/dist-web/assets/{xychartDiagram-FW5EYKEG-DfHxJPKU.js → xychartDiagram-FW5EYKEG-C2C9LElX.js} +1 -1
  71. package/dist-web/index.html +2 -2
  72. package/package.json +3 -4
  73. package/scripts/postinstall/migrate.mjs +34 -37
  74. package/scripts/postinstall/platform.mjs +29 -0
  75. package/scripts/postinstall/reach.mjs +12 -29
  76. package/scripts/postinstall/takeover.mjs +98 -0
  77. package/scripts/postinstall/ui.mjs +91 -0
  78. package/scripts/postinstall.mjs +59 -153
  79. package/dist-web/assets/channel-5_2atmZK.js +0 -1
  80. package/dist-web/assets/classDiagram-OUVF2IWQ-CNk36E8O.js +0 -1
  81. package/dist-web/assets/classDiagram-v2-EOCWNBFH-CNk36E8O.js +0 -1
  82. package/dist-web/assets/index-BxiMZi52.css +0 -32
  83. package/dist-web/assets/index-CvgiEu-R.js +0 -818
  84. package/dist-web/assets/stateDiagram-v2-6OUMAXLB-D2Op2fy3.js +0 -1
  85. package/dist-web/assets/swimlanesDiagram-G3AALYLV-gPkWeD2t.js +0 -8
@@ -28,32 +28,13 @@
28
28
 
29
29
  import { spawn } from 'node:child_process';
30
30
  import { promises as fs } from 'node:fs';
31
- import { delimiter, dirname, join, sep } from 'node:path';
31
+ import { delimiter, dirname, join } from 'node:path';
32
+
33
+ import { executableCandidates, pathFlavor } from './platform.mjs';
32
34
 
33
35
  const LEGACY_BIN = 'kimi';
34
36
  const IS_WINDOWS = process.platform === 'win32';
35
37
 
36
- /**
37
- * Expand a basename like `kimi` into the set of filenames the OS
38
- * would actually match on PATH.
39
- *
40
- * On POSIX: just `['kimi']`.
41
- *
42
- * On Windows: `['kimi', 'kimi.exe', 'kimi.cmd', …]` — every
43
- * extension in `PATHEXT`. Without this, our PATH walk would miss
44
- * the typical `kimi.exe` shim produced by `uv tool install` on
45
- * Windows.
46
- */
47
- export function executableCandidates(basename) {
48
- if (!IS_WINDOWS) return [basename];
49
- const pathext = (process.env['PATHEXT'] ?? '.EXE;.CMD;.BAT;.COM')
50
- .toLowerCase()
51
- .split(';')
52
- .map((e) => e.trim())
53
- .filter(Boolean);
54
- return [basename, ...pathext.map((ext) => basename + ext)];
55
- }
56
-
57
38
  /**
58
39
  * Identify which package manager ran us. `npm_config_user_agent` is
59
40
  * set by npm, yarn (classic + berry), and pnpm, and starts with the
@@ -223,7 +204,7 @@ export async function ownPackageRoot(startDir) {
223
204
  return null;
224
205
  }
225
206
 
226
- async function isExecutableFile(filePath) {
207
+ async function isExecutableFile(filePath, platform) {
227
208
  try {
228
209
  const info = await fs.stat(filePath);
229
210
  if (!info.isFile()) return false;
@@ -231,7 +212,7 @@ async function isExecutableFile(filePath) {
231
212
  // existence + a recognized extension is what PATHEXT-style lookup
232
213
  // checks. Callers only pass us candidates that already match an
233
214
  // extension in `executableCandidates()`, so "is a file" suffices.
234
- if (IS_WINDOWS) return true;
215
+ if (platform === 'win32') return true;
235
216
  return (info.mode & 0o111) !== 0;
236
217
  } catch {
237
218
  return false;
@@ -304,20 +285,22 @@ export async function findFirstResolvableKimi(
304
285
  pathString,
305
286
  actionableShimPaths,
306
287
  allDetectedShimPaths,
288
+ platform = process.platform,
307
289
  ) {
308
290
  if (!ownRoot || !pathString) return { kind: 'none' };
309
- const ownPrefix = ownRoot + sep;
310
- const candidates = executableCandidates(LEGACY_BIN);
291
+ const { delimiter: flavorDelimiter, join: flavorJoin, sep: flavorSep } = pathFlavor(platform);
292
+ const ownPrefix = ownRoot + flavorSep;
293
+ const candidates = executableCandidates(LEGACY_BIN, platform);
311
294
  const skipSet = new Set(actionableShimPaths ?? []);
312
295
  const knownLegacySet = new Set(allDetectedShimPaths ?? []);
313
296
  const seenDirs = new Set();
314
- for (const dir of pathString.split(delimiter)) {
297
+ for (const dir of pathString.split(flavorDelimiter)) {
315
298
  if (!dir || seenDirs.has(dir)) continue;
316
299
  seenDirs.add(dir);
317
300
  for (const name of candidates) {
318
- const shim = join(dir, name);
301
+ const shim = flavorJoin(dir, name);
319
302
  if (skipSet.has(shim)) continue;
320
- if (!(await isExecutableFile(shim))) continue;
303
+ if (!(await isExecutableFile(shim, platform))) continue;
321
304
  const kind = await classifyShim(shim, ownRoot, ownPrefix);
322
305
  if (kind === 'unreadable') continue;
323
306
  if (kind === 'own') return { kind: 'own' };
@@ -0,0 +1,98 @@
1
+ import { findFirstResolvableKimi } from './reach.mjs';
2
+ import {
3
+ classifyShim,
4
+ deleteShim,
5
+ detectLegacyShims,
6
+ renameInPlace,
7
+ } from './migrate.mjs';
8
+
9
+ export async function planTakeover(ownRoot, detectionPath, reachabilityPath, platform) {
10
+ const detections = await detectLegacyShims(ownRoot, detectionPath, platform);
11
+ if (detections.length === 0) return { kind: /** @type {const} */ ('noop') };
12
+
13
+ const classifications = await Promise.all(
14
+ detections.map(async (detection) => {
15
+ const c = await classifyShim(detection.shimPath, platform);
16
+ return { ...c, detection };
17
+ }),
18
+ );
19
+
20
+ const actionable = classifications.filter((c) => c.kind !== 'blocked');
21
+ const blocked = classifications.filter((c) => c.kind === 'blocked');
22
+ const blocker = await findFirstResolvableKimi(
23
+ ownRoot,
24
+ reachabilityPath,
25
+ actionable.map((c) => c.shimPath),
26
+ classifications.map((c) => c.shimPath),
27
+ platform,
28
+ );
29
+
30
+ if (blocker.kind === 'own') {
31
+ return { kind: /** @type {const} */ ('proceed'), detections, classifications, actionable, blocked };
32
+ }
33
+ if (blocker.kind === 'blocked-legacy') {
34
+ return { kind: /** @type {const} */ ('blocked'), blocked, actionable };
35
+ }
36
+ if (blocker.kind === 'foreign') {
37
+ return { kind: /** @type {const} */ ('foreign'), path: blocker.path };
38
+ }
39
+ return { kind: /** @type {const} */ ('not-on-path'), detection: detections[0] };
40
+ }
41
+
42
+ export async function executeTakeover(classifications) {
43
+ const renames = [];
44
+ const consolidates = [];
45
+ const skippedForeignTarget = [];
46
+ const deletes = [];
47
+ const errors = [];
48
+ let preserved = false;
49
+
50
+ for (const c of classifications) {
51
+ if (c.kind === 'blocked') continue;
52
+
53
+ if (!preserved) {
54
+ if (c.kind === 'renameable') {
55
+ const r = await renameInPlace(c.shimPath, c.target);
56
+ if (r.success) {
57
+ renames.push(c);
58
+ preserved = true;
59
+ } else {
60
+ errors.push({ ...c, ...r });
61
+ }
62
+ continue;
63
+ }
64
+ if (c.kind === 'consolidate') {
65
+ const r = await deleteShim(c.shimPath);
66
+ if (r.success) {
67
+ consolidates.push(c);
68
+ preserved = true;
69
+ } else {
70
+ errors.push({ ...c, ...r });
71
+ }
72
+ continue;
73
+ }
74
+ if (c.kind === 'delete-only') {
75
+ const r = await deleteShim(c.shimPath);
76
+ if (r.success) {
77
+ skippedForeignTarget.push(c);
78
+ } else {
79
+ errors.push({ ...c, ...r });
80
+ }
81
+ continue;
82
+ }
83
+ } else {
84
+ const r = await deleteShim(c.shimPath);
85
+ if (r.success) {
86
+ deletes.push(c);
87
+ } else {
88
+ errors.push({ ...c, ...r });
89
+ }
90
+ }
91
+ }
92
+
93
+ return { renames, consolidates, skippedForeignTarget, deletes, errors, preserved };
94
+ }
95
+
96
+ export async function verifyTakeover(ownRoot, reachabilityPath, allDetectedShimPaths, platform) {
97
+ return findFirstResolvableKimi(ownRoot, reachabilityPath, [], allDetectedShimPaths, platform);
98
+ }
@@ -406,6 +406,97 @@ export function logForeignKimiInTheWay(foreignPath, pm) {
406
406
  );
407
407
  }
408
408
 
409
+ /**
410
+ * A takeover that was executed but did NOT hold up under post-execution
411
+ * verification: `kimi` still resolves to a legacy/foreign file (or to
412
+ * nothing at all). The heading must not claim success — list what was
413
+ * actually changed, what still blocks resolution, and how to finish
414
+ * the job by hand.
415
+ */
416
+ export function logMigrationIncomplete(input, pm) {
417
+ const { outcomes, verify } = input;
418
+ const { renames, consolidates, skippedForeignTarget, deletes, errors } = outcomes;
419
+ const isWindows = process.platform === 'win32';
420
+ const reinstallCmd = pmGlobalInstallCommand(pm, '@moonshot-ai/kimi-code');
421
+
422
+ const lines = [warningHeading('Couldn\'t finish switching to the new kimi'), ''];
423
+
424
+ if (verify.kind === 'blocked-legacy') {
425
+ lines.push(
426
+ pad(' Typing `kimi` still runs the old version. This file is in'),
427
+ pad(' the way and we couldn\'t change it:'),
428
+ pathInBox(verify.shim),
429
+ '',
430
+ );
431
+ } else if (verify.kind === 'foreign') {
432
+ lines.push(
433
+ pad(' Typing `kimi` runs a file we don\'t recognize (not the new'),
434
+ pad(' CLI, not the old one). It\'s still in the way:'),
435
+ pathInBox(verify.path),
436
+ '',
437
+ );
438
+ } else {
439
+ lines.push(
440
+ pad(' Typing `kimi` currently finds nothing at all — the old'),
441
+ pad(' shim was removed but the new one isn\'t reachable yet.'),
442
+ '',
443
+ );
444
+ }
445
+
446
+ const changed = [
447
+ ...renames.map((c) => c.shimPath + ' -> ' + c.target),
448
+ ...consolidates.map((c) => c.shimPath + ' (removed; kimi-legacy kept)'),
449
+ ...skippedForeignTarget.map((c) => c.shimPath + ' (removed)'),
450
+ ...deletes.map((c) => c.shimPath + ' (removed)'),
451
+ ];
452
+ if (changed.length > 0) {
453
+ lines.push(pad(' Changes already made:'));
454
+ for (const line of changed) lines.push(pathInBox(line));
455
+ lines.push('');
456
+ }
457
+
458
+ if (errors.length > 0) {
459
+ lines.push(pad(' Changes that didn\'t go through:'));
460
+ for (const e of errors) {
461
+ lines.push(pathInBox(e.shimPath + ' (' + (e.message ?? e.code ?? 'error') + ')'));
462
+ }
463
+ lines.push('');
464
+ }
465
+
466
+ if (verify.kind === 'blocked-legacy') {
467
+ const c = verify;
468
+ lines.push(pad(' Delete it yourself, then install again:'));
469
+ if (isWindows) {
470
+ lines.push(pathInBox('Remove-Item ' + quotePowerShellPath(c.shim)));
471
+ } else {
472
+ lines.push(pathInBox('rm ' + quotePosixPath(c.shim)));
473
+ lines.push(pad(' (use sudo if it\'s in a system directory)'));
474
+ }
475
+ lines.push(pathInBox(reinstallCmd), '');
476
+ } else if (verify.kind === 'foreign') {
477
+ lines.push(
478
+ pad(' Delete or rename that file, then install again:'),
479
+ pathInBox(reinstallCmd),
480
+ '',
481
+ );
482
+ } else {
483
+ lines.push(
484
+ pad(' Open a new terminal and check `which kimi`. If it finds'),
485
+ pad(' nothing, reinstall to restore a working shim:'),
486
+ pathInBox(reinstallCmd),
487
+ '',
488
+ );
489
+ }
490
+
491
+ if (renames.length > 0 || consolidates.length > 0) {
492
+ lines.push(
493
+ pad(' The old version is still available as `kimi-legacy`.'),
494
+ );
495
+ }
496
+
497
+ emit(renderBox(lines));
498
+ }
499
+
409
500
  /**
410
501
  * The legacy `kimi` was found, but the directory where the package
411
502
  * manager placed the new `kimi` shim is not on the user's PATH.
@@ -37,9 +37,10 @@
37
37
  * - `./postinstall/migrate.mjs` — legacy detection,
38
38
  * `kimi`-vs-`kimi-legacy` classification, the rename / unlink
39
39
  * primitives.
40
+ * - `./postinstall/takeover.mjs` — the plan → execute → verify
41
+ * state machine this orchestrator drives.
40
42
  * - `./postinstall/ui.mjs` — `notify()` (with `/dev/tty` fallback),
41
- * ANSI styling, the fixed-width box, and the five outcome
42
- * renderers.
43
+ * ANSI styling, the fixed-width box, and the outcome renderers.
43
44
  *
44
45
  * ## Workflow
45
46
  *
@@ -60,41 +61,26 @@
60
61
  * shell can't be probed). Sharing one probe keeps detection
61
62
  * and reachability symmetric and avoids running `$SHELL -l`
62
63
  * twice.
63
- * 5. Detect EVERY previous Python `kimi-cli` shim on the detection
64
- * PATH (`detectLegacyShims`). Returns `[]` for fresh-install /
65
- * no-op. Multiple results happen when the user has installed
66
- * `kimi-cli` through more than one Python tool (uv + pipx, or
67
- * sudo-pip + pip-user). PATH order is preserved.
68
- * 6. Pre-flight classify each shim (`classifyShim`) — pure
69
- * filesystem inspection, no writes. Each shim ends up
70
- * `renameable`, `consolidate`, `delete-only`, or `blocked`.
71
- * 7. Decide abort vs proceed against the WHOLE set:
72
- * `findFirstResolvableKimi` walks PATH treating the actionable
73
- * shims as gone and reports what wins:
74
- * - `own` → proceed to execute.
75
- * - `blocked-legacy` → a legacy we can't remove still wins.
76
- * Surface `logMigrationBlocked` with sudo / admin
77
- * instructions; touch nothing.
78
- * - `foreign` → some `kimi` we don't recognize (a user's own
79
- * file) wins. Surface `logForeignKimiInTheWay` asking the
80
- * user to delete or rename their own file; touch nothing.
81
- * - `none` → no `kimi` on PATH at all (our shim's bin dir
82
- * isn't in the shell's PATH). Surface
83
- * `logNewCliNotOnPath`; touch nothing.
84
- * 8. Execute. The FIRST classification in PATH order that we can
85
- * touch becomes `kimi-legacy` (preserves what `kimi` referred
86
- * to before this install). Each subsequent shim is `unlink`ed —
87
- * keeping it as a dormant duplicate adds no value. If the
88
- * first shim's `kimi-legacy` target is already user-managed,
89
- * we delete `kimi` anyway (still achieves takeover) and tell
90
- * the user we couldn't preserve a fallback. Extension is
91
- * preserved on Windows (`kimi.exe` → `kimi-legacy.exe`).
92
- * 9. One end-of-orchestration notice (`logMigrationDone`)
93
- * summarizes every action — renames, consolidates,
94
- * delete-only, deletes, and harmless blocked leftovers. The
95
- * takeover-success line only fires on this path because Step 7
96
- * already certified it.
97
- * 10. The manager completes the install with its usual summary.
64
+ * 5. `planTakeover`: detect EVERY previous Python `kimi-cli` shim
65
+ * on the detection PATH, pre-flight classify each (no writes),
66
+ * and simulate PATH resolution with the actionable shims gone:
67
+ * - `own` wins → proceed.
68
+ * - a blocked legacy still wins → `logMigrationBlocked`.
69
+ * - a foreign `kimi` wins → `logForeignKimiInTheWay`.
70
+ * - nothing resolves → `logNewCliNotOnPath`.
71
+ * The abort branches touch NOTHING.
72
+ * 6. `executeTakeover`: the FIRST shim in PATH order that can be
73
+ * preserved becomes `kimi-legacy`; each subsequent shim is
74
+ * `unlink`ed. A failed preserve attempt does not promote the
75
+ * next shim to deletion — it gets its own preserve attempt, so
76
+ * a usable legacy fallback survives whenever one is possible.
77
+ * 7. `verifyTakeover`: walk the reachability PATH as it actually
78
+ * is AFTER execution. Only `{ kind: 'own' }` renders the
79
+ * success box (`logMigrationDone`); anything else renders
80
+ * `logMigrationIncomplete` — what changed, what still blocks,
81
+ * and how to finish by hand. The pre-flight simulation in
82
+ * step 5 is never reported as proof of success.
83
+ * 8. The manager completes the install with its usual summary.
98
84
  * This script always exits 0; any uncaught error is swallowed
99
85
  * by the top-level `catch` so the install never fails because
100
86
  * of the migration.
@@ -102,21 +88,20 @@
102
88
 
103
89
  import {
104
90
  detectPackageManager,
105
- findFirstResolvableKimi,
106
91
  isGlobalInstall,
107
92
  ownPackageRoot,
108
93
  postinstallPaths,
109
94
  } from './postinstall/reach.mjs';
110
95
  import {
111
- classifyShim,
112
- deleteShim,
113
- detectLegacyShims,
114
- renameInPlace,
115
- } from './postinstall/migrate.mjs';
96
+ executeTakeover,
97
+ planTakeover,
98
+ verifyTakeover,
99
+ } from './postinstall/takeover.mjs';
116
100
  import {
117
101
  logForeignKimiInTheWay,
118
102
  logMigrationBlocked,
119
103
  logMigrationDone,
104
+ logMigrationIncomplete,
120
105
  logNewCliNotOnPath,
121
106
  notify,
122
107
  } from './postinstall/ui.mjs';
@@ -143,124 +128,45 @@ async function main() {
143
128
  // installer's env).
144
129
  const paths = await postinstallPaths();
145
130
 
146
- // Step 4: detect EVERY previous Python `kimi-cli` shim on the
147
- // detection PATH. A user with both `uv tool install` and `pipx
148
- // install` would have two; we must address all of them or the
149
- // survivor still shadows the new CLI.
150
- const detections = await detectLegacyShims(ownRoot, paths.detection);
151
- if (detections.length === 0) return;
152
-
153
- // Step 5: pre-flight classify every shim WITHOUT touching the
154
- // filesystem yet. The orchestrator decides abort-or-proceed against
155
- // the whole set rather than discovering mid-loop that we got partway
156
- // and have to backtrack.
157
- const classifications = await Promise.all(
158
- detections.map(async (detection) => {
159
- const c = await classifyShim(detection.shimPath);
160
- return { ...c, detection };
161
- }),
162
- );
163
-
164
- // Step 6: figure out what wins PATH resolution once every shim we
165
- // CAN touch is treated as gone. Three possible blockers:
166
- // - a legacy shim we couldn't classify as actionable (sudo/admin
167
- // needed)
168
- // - an unrelated `kimi` we don't recognize (a user's own wrapper
169
- // script — they own the decision)
170
- // - nothing resolves (our shim isn't on PATH at all)
171
- // For each we render a different notice and touch NOTHING. The
172
- // common-case fourth result is "our shim wins" — we proceed.
173
- const actionable = classifications.filter((c) => c.kind !== 'blocked');
174
- const blocked = classifications.filter((c) => c.kind === 'blocked');
175
- const actionableShimPaths = actionable.map((c) => c.shimPath);
176
- const allDetectedShimPaths = classifications.map((c) => c.shimPath);
177
-
178
- const blocker = await findFirstResolvableKimi(
131
+ // Step 4: plan against the whole detected shim set without writing.
132
+ const plan = await planTakeover(
179
133
  ownRoot,
134
+ paths.detection,
180
135
  paths.reachability,
181
- actionableShimPaths,
182
- allDetectedShimPaths,
136
+ process.platform,
183
137
  );
184
- if (blocker.kind !== 'own') {
185
- if (blocker.kind === 'blocked-legacy') {
186
- logMigrationBlocked(blocked, actionable, pm);
187
- } else if (blocker.kind === 'foreign') {
188
- logForeignKimiInTheWay(blocker.path, pm);
189
- } else {
190
- // 'none' — our shim isn't on PATH at all.
191
- logNewCliNotOnPath(detections[0], pm);
192
- }
138
+ if (plan.kind === 'noop') return;
139
+ if (plan.kind === 'blocked') {
140
+ logMigrationBlocked(plan.blocked, plan.actionable, pm);
193
141
  return;
194
142
  }
195
-
196
- // Step 7: execute. The FIRST classification in PATH order that
197
- // we can touch becomes `kimi-legacy` (preserves what the user's
198
- // `kimi` used to refer to). Every subsequent shim is just
199
- // deleted — keeping it as a dormant duplicate adds no value.
200
- const renames = [];
201
- const consolidates = [];
202
- const skippedForeignTarget = [];
203
- const deletes = [];
204
- const errors = [];
205
- let preservedFirst = false;
206
-
207
- for (const c of classifications) {
208
- if (c.kind === 'blocked') continue; // already established harmless
209
-
210
- if (!preservedFirst) {
211
- preservedFirst = true;
212
- if (c.kind === 'renameable') {
213
- const r = await renameInPlace(c.shimPath, c.target);
214
- if (r.success) {
215
- renames.push(c);
216
- } else {
217
- errors.push({ ...c, ...r });
218
- }
219
- continue;
220
- }
221
- if (c.kind === 'consolidate') {
222
- const r = await deleteShim(c.shimPath);
223
- if (r.success) {
224
- consolidates.push(c);
225
- } else {
226
- errors.push({ ...c, ...r });
227
- }
228
- continue;
229
- }
230
- if (c.kind === 'delete-only') {
231
- const r = await deleteShim(c.shimPath);
232
- if (r.success) {
233
- skippedForeignTarget.push(c);
234
- } else {
235
- errors.push({ ...c, ...r });
236
- }
237
- continue;
238
- }
239
- } else {
240
- // Not the first actionable shim. Just delete it.
241
- const r = await deleteShim(c.shimPath);
242
- if (r.success) {
243
- deletes.push(c);
244
- } else {
245
- errors.push({ ...c, ...r });
246
- }
247
- }
143
+ if (plan.kind === 'foreign') {
144
+ logForeignKimiInTheWay(plan.path, pm);
145
+ return;
146
+ }
147
+ if (plan.kind === 'not-on-path') {
148
+ logNewCliNotOnPath(plan.detection, pm);
149
+ return;
248
150
  }
249
151
 
250
- // Step 8: one notice summarizing everything that happened. The
251
- // takeover-success language is only emitted when we know it's true
252
- // (we already passed the reachability gate above).
253
- logMigrationDone(
254
- {
255
- renames,
256
- consolidates,
257
- skippedForeignTarget,
258
- deletes,
259
- blockedHarmless: blocked,
260
- errors,
261
- },
262
- pm,
152
+ // Step 5: execute (preserve the first preservable shim as
153
+ // `kimi-legacy`, delete the rest).
154
+ const outcomes = await executeTakeover(plan.classifications);
155
+
156
+ // Step 6: post-execution verification — the ONLY ground for a
157
+ // success claim. If reality diverged from the step-4 simulation
158
+ // (a rename failed, a new shim appeared), report it honestly.
159
+ const verify = await verifyTakeover(
160
+ ownRoot,
161
+ paths.reachability,
162
+ plan.classifications.map((c) => c.shimPath),
163
+ process.platform,
263
164
  );
165
+ if (verify.kind === 'own') {
166
+ logMigrationDone({ ...outcomes, blockedHarmless: plan.blocked }, pm);
167
+ return;
168
+ }
169
+ logMigrationIncomplete({ outcomes, verify, blocked: plan.blocked }, pm);
264
170
  }
265
171
 
266
172
  main().catch((err) => {
@@ -1 +0,0 @@
1
- import{U as a,O as n}from"./mermaid.core-CGrLstUA.js";const t=(r,o)=>a.lang.round(n.parse(r)[o]);export{t as c};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-V7JOEXUC-5w7loxVR.js";import{_ as i}from"./mermaid.core-CGrLstUA.js";import"./chunk-5VM5RSS4-K_NNkrmw.js";import"./chunk-XXDRQBXY-BwEKpC0s.js";import"./chunk-VR4S4FIN-Cp8JVohH.js";import"./chunk-32BRIVSS-0ImWpiFu.js";import"./index-CvgiEu-R.js";import"./_commonjsHelpers-CqkleIqs.js";var f={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{f as diagram};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-V7JOEXUC-5w7loxVR.js";import{_ as i}from"./mermaid.core-CGrLstUA.js";import"./chunk-5VM5RSS4-K_NNkrmw.js";import"./chunk-XXDRQBXY-BwEKpC0s.js";import"./chunk-VR4S4FIN-Cp8JVohH.js";import"./chunk-32BRIVSS-0ImWpiFu.js";import"./index-CvgiEu-R.js";import"./_commonjsHelpers-CqkleIqs.js";var f={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{f as diagram};