maxsimcli 3.11.0 → 4.0.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 (197) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/adapters/index.d.ts +0 -11
  3. package/dist/adapters/index.d.ts.map +1 -1
  4. package/dist/adapters/index.js +4 -40
  5. package/dist/adapters/index.js.map +1 -1
  6. package/dist/assets/CHANGELOG.md +36 -0
  7. package/dist/assets/dashboard/client/assets/{index-CZ8WC97G.js → index-C_eAetZJ.js} +66 -66
  8. package/dist/assets/dashboard/client/assets/index-CmiJKqOU.css +32 -0
  9. package/dist/assets/dashboard/client/index.html +2 -2
  10. package/dist/assets/dashboard/server.js +467 -271
  11. package/dist/assets/templates/agents/AGENTS.md +94 -0
  12. package/dist/assets/templates/agents/maxsim-debugger.md +2 -2
  13. package/dist/assets/templates/agents/maxsim-executor.md +5 -5
  14. package/dist/assets/templates/agents/maxsim-phase-researcher.md +2 -2
  15. package/dist/assets/templates/agents/maxsim-plan-checker.md +2 -2
  16. package/dist/assets/templates/agents/maxsim-planner.md +3 -3
  17. package/dist/assets/templates/commands/maxsim/add-todo.md +15 -5
  18. package/dist/assets/templates/commands/maxsim/discuss-phase.md +1 -0
  19. package/dist/assets/templates/commands/maxsim/init-existing.md +4 -0
  20. package/dist/assets/templates/commands/maxsim/new-project.md +4 -0
  21. package/dist/assets/templates/commands/maxsim/settings.md +1 -1
  22. package/dist/assets/templates/references/thinking-partner.md +41 -0
  23. package/dist/assets/templates/skills/batch-worktree/SKILL.md +137 -0
  24. package/dist/assets/templates/skills/brainstorming/SKILL.md +159 -0
  25. package/dist/assets/templates/skills/code-review/SKILL.md +151 -0
  26. package/dist/assets/templates/skills/memory-management/SKILL.md +174 -0
  27. package/dist/assets/templates/skills/roadmap-writing/SKILL.md +198 -0
  28. package/dist/assets/templates/skills/sdd/SKILL.md +175 -0
  29. package/dist/assets/templates/skills/simplify/SKILL.md +185 -0
  30. package/dist/assets/templates/skills/using-maxsim/SKILL.md +120 -0
  31. package/dist/assets/templates/templates/acceptance-criteria.md +10 -0
  32. package/dist/assets/templates/templates/config.json +1 -1
  33. package/dist/assets/templates/templates/decisions.md +10 -0
  34. package/dist/assets/templates/templates/no-gos.md +9 -0
  35. package/dist/assets/templates/workflows/add-tests.md +3 -3
  36. package/dist/assets/templates/workflows/add-todo.md +89 -0
  37. package/dist/assets/templates/workflows/complete-milestone.md +1 -1
  38. package/dist/assets/templates/workflows/discuss-phase.md +85 -1
  39. package/dist/assets/templates/workflows/execute-phase.md +26 -16
  40. package/dist/assets/templates/workflows/execute-plan.md +166 -0
  41. package/dist/assets/templates/workflows/init-existing.md +123 -3
  42. package/dist/assets/templates/workflows/new-milestone.md +4 -0
  43. package/dist/assets/templates/workflows/new-project.md +111 -3
  44. package/dist/assets/templates/workflows/plan-phase.md +5 -5
  45. package/dist/assets/templates/workflows/quick.md +2 -2
  46. package/dist/assets/templates/workflows/settings.md +8 -4
  47. package/dist/assets/templates/workflows/verify-work.md +1 -1
  48. package/dist/cli.cjs +1512 -1026
  49. package/dist/cli.cjs.map +1 -1
  50. package/dist/cli.js +170 -278
  51. package/dist/cli.js.map +1 -1
  52. package/dist/core/artefakte.d.ts +12 -0
  53. package/dist/core/artefakte.d.ts.map +1 -0
  54. package/dist/core/artefakte.js +136 -0
  55. package/dist/core/artefakte.js.map +1 -0
  56. package/dist/core/commands.d.ts +13 -13
  57. package/dist/core/commands.d.ts.map +1 -1
  58. package/dist/core/commands.js +48 -58
  59. package/dist/core/commands.js.map +1 -1
  60. package/dist/core/config.d.ts +4 -3
  61. package/dist/core/config.d.ts.map +1 -1
  62. package/dist/core/config.js +14 -18
  63. package/dist/core/config.js.map +1 -1
  64. package/dist/core/context-loader.d.ts +20 -0
  65. package/dist/core/context-loader.d.ts.map +1 -0
  66. package/dist/core/context-loader.js +154 -0
  67. package/dist/core/context-loader.js.map +1 -0
  68. package/dist/core/core.d.ts +26 -2
  69. package/dist/core/core.d.ts.map +1 -1
  70. package/dist/core/core.js +90 -24
  71. package/dist/core/core.js.map +1 -1
  72. package/dist/core/dashboard-launcher.d.ts +56 -0
  73. package/dist/core/dashboard-launcher.d.ts.map +1 -0
  74. package/dist/core/dashboard-launcher.js +246 -0
  75. package/dist/core/dashboard-launcher.js.map +1 -0
  76. package/dist/core/frontmatter.d.ts +5 -5
  77. package/dist/core/frontmatter.d.ts.map +1 -1
  78. package/dist/core/frontmatter.js +21 -26
  79. package/dist/core/frontmatter.js.map +1 -1
  80. package/dist/core/index.d.ts +10 -3
  81. package/dist/core/index.d.ts.map +1 -1
  82. package/dist/core/index.js +40 -2
  83. package/dist/core/index.js.map +1 -1
  84. package/dist/core/init.d.ts +14 -15
  85. package/dist/core/init.d.ts.map +1 -1
  86. package/dist/core/init.js +93 -155
  87. package/dist/core/init.js.map +1 -1
  88. package/dist/core/milestone.d.ts +3 -3
  89. package/dist/core/milestone.d.ts.map +1 -1
  90. package/dist/core/milestone.js +9 -9
  91. package/dist/core/milestone.js.map +1 -1
  92. package/dist/core/phase.d.ts +9 -9
  93. package/dist/core/phase.d.ts.map +1 -1
  94. package/dist/core/phase.js +65 -63
  95. package/dist/core/phase.js.map +1 -1
  96. package/dist/core/roadmap.d.ts +4 -3
  97. package/dist/core/roadmap.d.ts.map +1 -1
  98. package/dist/core/roadmap.js +46 -108
  99. package/dist/core/roadmap.js.map +1 -1
  100. package/dist/core/skills.d.ts +19 -0
  101. package/dist/core/skills.d.ts.map +1 -0
  102. package/dist/core/skills.js +145 -0
  103. package/dist/core/skills.js.map +1 -0
  104. package/dist/core/start.d.ts +15 -0
  105. package/dist/core/start.d.ts.map +1 -0
  106. package/dist/core/start.js +80 -0
  107. package/dist/core/start.js.map +1 -0
  108. package/dist/core/state.d.ts +13 -13
  109. package/dist/core/state.d.ts.map +1 -1
  110. package/dist/core/state.js +125 -130
  111. package/dist/core/state.js.map +1 -1
  112. package/dist/core/template.d.ts +3 -3
  113. package/dist/core/template.d.ts.map +1 -1
  114. package/dist/core/template.js +12 -14
  115. package/dist/core/template.js.map +1 -1
  116. package/dist/core/types.d.ts +15 -4
  117. package/dist/core/types.d.ts.map +1 -1
  118. package/dist/core/types.js +9 -2
  119. package/dist/core/types.js.map +1 -1
  120. package/dist/core/verify.d.ts +10 -9
  121. package/dist/core/verify.d.ts.map +1 -1
  122. package/dist/core/verify.js +38 -48
  123. package/dist/core/verify.js.map +1 -1
  124. package/dist/core-TFSlUjV1.cjs +4312 -0
  125. package/dist/core-TFSlUjV1.cjs.map +1 -0
  126. package/dist/install/adapters.d.ts +6 -0
  127. package/dist/install/adapters.d.ts.map +1 -0
  128. package/dist/install/adapters.js +65 -0
  129. package/dist/install/adapters.js.map +1 -0
  130. package/dist/install/copy.d.ts +6 -0
  131. package/dist/install/copy.d.ts.map +1 -0
  132. package/dist/install/copy.js +71 -0
  133. package/dist/install/copy.js.map +1 -0
  134. package/dist/install/dashboard.d.ts +16 -0
  135. package/dist/install/dashboard.d.ts.map +1 -0
  136. package/dist/install/dashboard.js +273 -0
  137. package/dist/install/dashboard.js.map +1 -0
  138. package/dist/install/hooks.d.ts +31 -0
  139. package/dist/install/hooks.d.ts.map +1 -0
  140. package/dist/install/hooks.js +260 -0
  141. package/dist/install/hooks.js.map +1 -0
  142. package/dist/install/index.d.ts +2 -0
  143. package/dist/install/index.d.ts.map +1 -0
  144. package/dist/install/index.js +535 -0
  145. package/dist/install/index.js.map +1 -0
  146. package/dist/install/manifest.d.ts +23 -0
  147. package/dist/install/manifest.d.ts.map +1 -0
  148. package/dist/install/manifest.js +129 -0
  149. package/dist/install/manifest.js.map +1 -0
  150. package/dist/install/patches.d.ts +10 -0
  151. package/dist/install/patches.d.ts.map +1 -0
  152. package/dist/install/patches.js +124 -0
  153. package/dist/install/patches.js.map +1 -0
  154. package/dist/install/shared.d.ts +56 -0
  155. package/dist/install/shared.d.ts.map +1 -0
  156. package/dist/install/shared.js +172 -0
  157. package/dist/install/shared.js.map +1 -0
  158. package/dist/install/uninstall.d.ts +5 -0
  159. package/dist/install/uninstall.d.ts.map +1 -0
  160. package/dist/install/uninstall.js +222 -0
  161. package/dist/install/uninstall.js.map +1 -0
  162. package/dist/install.cjs +793 -1648
  163. package/dist/install.cjs.map +1 -1
  164. package/dist/mcp-server.cjs +38 -14
  165. package/dist/mcp-server.cjs.map +1 -1
  166. package/dist/skills-BOSxYUzf.cjs +6812 -0
  167. package/dist/skills-BOSxYUzf.cjs.map +1 -0
  168. package/package.json +1 -1
  169. package/dist/adapters/codex.d.ts +0 -19
  170. package/dist/adapters/codex.d.ts.map +0 -1
  171. package/dist/adapters/codex.js +0 -94
  172. package/dist/adapters/codex.js.map +0 -1
  173. package/dist/adapters/gemini.d.ts +0 -19
  174. package/dist/adapters/gemini.d.ts.map +0 -1
  175. package/dist/adapters/gemini.js +0 -96
  176. package/dist/adapters/gemini.js.map +0 -1
  177. package/dist/adapters/opencode.d.ts +0 -17
  178. package/dist/adapters/opencode.d.ts.map +0 -1
  179. package/dist/adapters/opencode.js +0 -111
  180. package/dist/adapters/opencode.js.map +0 -1
  181. package/dist/adapters/transforms/content.d.ts +0 -39
  182. package/dist/adapters/transforms/content.d.ts.map +0 -1
  183. package/dist/adapters/transforms/content.js +0 -125
  184. package/dist/adapters/transforms/content.js.map +0 -1
  185. package/dist/adapters/transforms/frontmatter.d.ts +0 -42
  186. package/dist/adapters/transforms/frontmatter.d.ts.map +0 -1
  187. package/dist/adapters/transforms/frontmatter.js +0 -204
  188. package/dist/adapters/transforms/frontmatter.js.map +0 -1
  189. package/dist/adapters/transforms/tool-maps.d.ts +0 -20
  190. package/dist/adapters/transforms/tool-maps.d.ts.map +0 -1
  191. package/dist/adapters/transforms/tool-maps.js +0 -64
  192. package/dist/adapters/transforms/tool-maps.js.map +0 -1
  193. package/dist/assets/dashboard/client/assets/index-DzJChB-D.css +0 -32
  194. package/dist/install.d.ts +0 -2
  195. package/dist/install.d.ts.map +0 -1
  196. package/dist/install.js +0 -1841
  197. package/dist/install.js.map +0 -1
package/dist/install.cjs CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
3
  //#region \0rolldown/runtime.js
3
4
  var __create = Object.create;
4
5
  var __defProp = Object.defineProperty;
@@ -33,9 +34,6 @@ let node_path = require("node:path");
33
34
  node_path = __toESM(node_path);
34
35
  let node_os = require("node:os");
35
36
  node_os = __toESM(node_os);
36
- let node_crypto = require("node:crypto");
37
- node_crypto = __toESM(node_crypto);
38
- let node_child_process = require("node:child_process");
39
37
  let node_process = require("node:process");
40
38
  node_process = __toESM(node_process);
41
39
  let node_tty = require("node:tty");
@@ -46,6 +44,9 @@ let node_util = require("node:util");
46
44
  let node_async_hooks = require("node:async_hooks");
47
45
  let node_readline = require("node:readline");
48
46
  node_readline = __toESM(node_readline);
47
+ let node_crypto = require("node:crypto");
48
+ node_crypto = __toESM(node_crypto);
49
+ let node_child_process = require("node:child_process");
49
50
 
50
51
  //#region ../../node_modules/universalify/index.js
51
52
  var require_universalify = /* @__PURE__ */ __commonJSMin(((exports) => {
@@ -5646,7 +5647,6 @@ function ora(options) {
5646
5647
  //#region ../../node_modules/@inquirer/core/dist/lib/key.js
5647
5648
  const isUpKey = (key, keybindings = []) => key.name === "up" || keybindings.includes("vim") && key.name === "k" || keybindings.includes("emacs") && key.ctrl && key.name === "p";
5648
5649
  const isDownKey = (key, keybindings = []) => key.name === "down" || keybindings.includes("vim") && key.name === "j" || keybindings.includes("emacs") && key.ctrl && key.name === "n";
5649
- const isSpaceKey = (key) => key.name === "space";
5650
5650
  const isBackspaceKey = (key) => key.name === "backspace";
5651
5651
  const isTabKey = (key) => key.name === "tab";
5652
5652
  const isNumberKey = (key) => "1234567890".includes(key.name);
@@ -7138,187 +7138,6 @@ var Separator = class {
7138
7138
  }
7139
7139
  };
7140
7140
 
7141
- //#endregion
7142
- //#region ../../node_modules/@inquirer/checkbox/dist/index.js
7143
- const checkboxTheme = {
7144
- icon: {
7145
- checked: (0, node_util.styleText)("green", figures.circleFilled),
7146
- unchecked: figures.circle,
7147
- cursor: figures.pointer,
7148
- disabledChecked: (0, node_util.styleText)("green", figures.circleDouble),
7149
- disabledUnchecked: "-"
7150
- },
7151
- style: {
7152
- disabled: (text) => (0, node_util.styleText)("dim", text),
7153
- renderSelectedChoices: (selectedChoices) => selectedChoices.map((choice) => choice.short).join(", "),
7154
- description: (text) => (0, node_util.styleText)("cyan", text),
7155
- keysHelpTip: (keys) => keys.map(([key, action]) => `${(0, node_util.styleText)("bold", key)} ${(0, node_util.styleText)("dim", action)}`).join((0, node_util.styleText)("dim", " • "))
7156
- },
7157
- i18n: { disabledError: "This option is disabled and cannot be toggled." },
7158
- keybindings: []
7159
- };
7160
- function isSelectable$1(item) {
7161
- return !Separator.isSeparator(item) && !item.disabled;
7162
- }
7163
- function isNavigable$1(item) {
7164
- return !Separator.isSeparator(item);
7165
- }
7166
- function isChecked(item) {
7167
- return !Separator.isSeparator(item) && item.checked;
7168
- }
7169
- function toggle(item) {
7170
- return isSelectable$1(item) ? {
7171
- ...item,
7172
- checked: !item.checked
7173
- } : item;
7174
- }
7175
- function check(checked) {
7176
- return function(item) {
7177
- return isSelectable$1(item) ? {
7178
- ...item,
7179
- checked
7180
- } : item;
7181
- };
7182
- }
7183
- function normalizeChoices$1(choices) {
7184
- return choices.map((choice) => {
7185
- if (Separator.isSeparator(choice)) return choice;
7186
- if (typeof choice === "string") return {
7187
- value: choice,
7188
- name: choice,
7189
- short: choice,
7190
- checkedName: choice,
7191
- disabled: false,
7192
- checked: false
7193
- };
7194
- const name = choice.name ?? String(choice.value);
7195
- const normalizedChoice = {
7196
- value: choice.value,
7197
- name,
7198
- short: choice.short ?? name,
7199
- checkedName: choice.checkedName ?? name,
7200
- disabled: choice.disabled ?? false,
7201
- checked: choice.checked ?? false
7202
- };
7203
- if (choice.description) normalizedChoice.description = choice.description;
7204
- return normalizedChoice;
7205
- });
7206
- }
7207
- var dist_default$2 = createPrompt((config, done) => {
7208
- const { pageSize = 7, loop = true, required, validate = () => true } = config;
7209
- const shortcuts = {
7210
- all: "a",
7211
- invert: "i",
7212
- ...config.shortcuts
7213
- };
7214
- const theme = makeTheme(checkboxTheme, config.theme);
7215
- const { keybindings } = theme;
7216
- const [status, setStatus] = useState("idle");
7217
- const prefix = usePrefix({
7218
- status,
7219
- theme
7220
- });
7221
- const [items, setItems] = useState(normalizeChoices$1(config.choices));
7222
- const bounds = useMemo(() => {
7223
- const first = items.findIndex(isNavigable$1);
7224
- const last = items.findLastIndex(isNavigable$1);
7225
- if (first === -1) throw new ValidationError("[checkbox prompt] No selectable choices. All choices are disabled.");
7226
- return {
7227
- first,
7228
- last
7229
- };
7230
- }, [items]);
7231
- const [active, setActive] = useState(bounds.first);
7232
- const [errorMsg, setError] = useState();
7233
- useKeypress(async (key) => {
7234
- if (isEnterKey(key)) {
7235
- const selection = items.filter(isChecked);
7236
- const isValid = await validate([...selection]);
7237
- if (required && !selection.length) setError("At least one choice must be selected");
7238
- else if (isValid === true) {
7239
- setStatus("done");
7240
- done(selection.map((choice) => choice.value));
7241
- } else setError(isValid || "You must select a valid value");
7242
- } else if (isUpKey(key, keybindings) || isDownKey(key, keybindings)) {
7243
- if (errorMsg) setError(void 0);
7244
- if (loop || isUpKey(key, keybindings) && active !== bounds.first || isDownKey(key, keybindings) && active !== bounds.last) {
7245
- const offset = isUpKey(key, keybindings) ? -1 : 1;
7246
- let next = active;
7247
- do
7248
- next = (next + offset + items.length) % items.length;
7249
- while (!isNavigable$1(items[next]));
7250
- setActive(next);
7251
- }
7252
- } else if (isSpaceKey(key)) {
7253
- const activeItem = items[active];
7254
- if (activeItem && !Separator.isSeparator(activeItem)) if (activeItem.disabled) setError(theme.i18n.disabledError);
7255
- else {
7256
- setError(void 0);
7257
- setItems(items.map((choice, i) => i === active ? toggle(choice) : choice));
7258
- }
7259
- } else if (key.name === shortcuts.all) {
7260
- const selectAll = items.some((choice) => isSelectable$1(choice) && !choice.checked);
7261
- setItems(items.map(check(selectAll)));
7262
- } else if (key.name === shortcuts.invert) setItems(items.map(toggle));
7263
- else if (isNumberKey(key)) {
7264
- const selectedIndex = Number(key.name) - 1;
7265
- let selectableIndex = -1;
7266
- const position = items.findIndex((item) => {
7267
- if (Separator.isSeparator(item)) return false;
7268
- selectableIndex++;
7269
- return selectableIndex === selectedIndex;
7270
- });
7271
- const selectedItem = items[position];
7272
- if (selectedItem && isSelectable$1(selectedItem)) {
7273
- setActive(position);
7274
- setItems(items.map((choice, i) => i === position ? toggle(choice) : choice));
7275
- }
7276
- }
7277
- });
7278
- const message = theme.style.message(config.message, status);
7279
- let description;
7280
- const page = usePagination({
7281
- items,
7282
- active,
7283
- renderItem({ item, isActive }) {
7284
- if (Separator.isSeparator(item)) return ` ${item.separator}`;
7285
- const cursor = isActive ? theme.icon.cursor : " ";
7286
- if (item.disabled) {
7287
- const disabledLabel = typeof item.disabled === "string" ? item.disabled : "(disabled)";
7288
- const checkbox = item.checked ? theme.icon.disabledChecked : theme.icon.disabledUnchecked;
7289
- return theme.style.disabled(`${cursor}${checkbox} ${item.name} ${disabledLabel}`);
7290
- }
7291
- if (isActive) description = item.description;
7292
- const checkbox = item.checked ? theme.icon.checked : theme.icon.unchecked;
7293
- const name = item.checked ? item.checkedName : item.name;
7294
- return (isActive ? theme.style.highlight : (x) => x)(`${cursor}${checkbox} ${name}`);
7295
- },
7296
- pageSize,
7297
- loop
7298
- });
7299
- if (status === "done") {
7300
- const selection = items.filter(isChecked);
7301
- return [
7302
- prefix,
7303
- message,
7304
- theme.style.answer(theme.style.renderSelectedChoices(selection, items))
7305
- ].filter(Boolean).join(" ");
7306
- }
7307
- const keys = [["↑↓", "navigate"], ["space", "select"]];
7308
- if (shortcuts.all) keys.push([shortcuts.all, "all"]);
7309
- if (shortcuts.invert) keys.push([shortcuts.invert, "invert"]);
7310
- keys.push(["⏎", "submit"]);
7311
- const helpLine = theme.style.keysHelpTip(keys);
7312
- return `${[
7313
- [prefix, message].filter(Boolean).join(" "),
7314
- page,
7315
- " ",
7316
- description ? theme.style.description(description) : "",
7317
- errorMsg ? theme.style.error(errorMsg) : "",
7318
- helpLine
7319
- ].filter(Boolean).join("\n").trimEnd()}${cursorHide}`;
7320
- });
7321
-
7322
7141
  //#endregion
7323
7142
  //#region ../../node_modules/@inquirer/confirm/dist/index.js
7324
7143
  function getBooleanValue(value, defaultValue) {
@@ -7701,25 +7520,6 @@ function expandTilde(filePath) {
7701
7520
  return filePath;
7702
7521
  }
7703
7522
  /**
7704
- * Extract YAML frontmatter and body from markdown content.
7705
- * Returns null frontmatter if content doesn't start with ---.
7706
- */
7707
- function extractFrontmatterAndBody(content) {
7708
- if (!content.startsWith("---")) return {
7709
- frontmatter: null,
7710
- body: content
7711
- };
7712
- const endIndex = content.indexOf("---", 3);
7713
- if (endIndex === -1) return {
7714
- frontmatter: null,
7715
- body: content
7716
- };
7717
- return {
7718
- frontmatter: content.substring(3, endIndex).trim(),
7719
- body: content.substring(endIndex + 3)
7720
- };
7721
- }
7722
- /**
7723
7523
  * Process Co-Authored-By lines based on attribution setting.
7724
7524
  * @param content - File content to process
7725
7525
  * @param attribution - null=remove, undefined=keep default, string=replace
@@ -7769,7 +7569,7 @@ function writeSettings(settingsPath, settings) {
7769
7569
  * Get the global config directory for Claude Code.
7770
7570
  * Priority: explicitDir > CLAUDE_CONFIG_DIR env > ~/.claude
7771
7571
  */
7772
- function getGlobalDir$4(explicitDir) {
7572
+ function getGlobalDir$1(explicitDir) {
7773
7573
  if (explicitDir) return expandTilde(explicitDir);
7774
7574
  if (process.env.CLAUDE_CONFIG_DIR) return expandTilde(process.env.CLAUDE_CONFIG_DIR);
7775
7575
  return node_path.join(node_os.homedir(), ".claude");
@@ -7778,7 +7578,7 @@ function getGlobalDir$4(explicitDir) {
7778
7578
  * Get the config directory path relative to home for hook templating.
7779
7579
  * Used for path.join(homeDir, '<configDir>', ...) replacement in hooks.
7780
7580
  */
7781
- function getConfigDirFromHome$4(isGlobal) {
7581
+ function getConfigDirFromHome$1(isGlobal) {
7782
7582
  return "'.claude'";
7783
7583
  }
7784
7584
  /**
@@ -7786,7 +7586,7 @@ function getConfigDirFromHome$4(isGlobal) {
7786
7586
  * For Claude, this is path replacement only — no frontmatter conversion needed.
7787
7587
  * Replaces ~/.claude/ and ./.claude/ references with the actual install path prefix.
7788
7588
  */
7789
- function transformContent$3(content, pathPrefix) {
7589
+ function transformContent(content, pathPrefix) {
7790
7590
  const globalClaudeRegex = /~\/\.claude\//g;
7791
7591
  const localClaudeRegex = /\.\/\.claude\//g;
7792
7592
  let result = content.replace(globalClaudeRegex, pathPrefix);
@@ -7800,1098 +7600,475 @@ function transformContent$3(content, pathPrefix) {
7800
7600
  const claudeAdapter = {
7801
7601
  runtime: "claude",
7802
7602
  dirName: ".claude",
7803
- getGlobalDir: getGlobalDir$4,
7804
- getConfigDirFromHome: getConfigDirFromHome$4,
7805
- transformContent: transformContent$3,
7603
+ getGlobalDir: getGlobalDir$1,
7604
+ getConfigDirFromHome: getConfigDirFromHome$1,
7605
+ transformContent,
7806
7606
  commandStructure: "nested"
7807
7607
  };
7808
7608
 
7809
7609
  //#endregion
7810
- //#region src/adapters/transforms/tool-maps.ts
7811
- /**
7812
- * @maxsim/adapters Tool name mappings per runtime
7813
- *
7814
- * Ported from bin/install.js lines ~327-390
7815
- */
7816
- /** Tool name mapping from Claude Code to OpenCode */
7817
- const claudeToOpencodeTools = {
7818
- AskUserQuestion: "question",
7819
- SlashCommand: "skill",
7820
- TodoWrite: "todowrite",
7821
- WebFetch: "webfetch",
7822
- WebSearch: "websearch"
7823
- };
7824
- /** Tool name mapping from Claude Code to Gemini CLI */
7825
- const claudeToGeminiTools = {
7826
- Read: "read_file",
7827
- Write: "write_file",
7828
- Edit: "replace",
7829
- Bash: "run_shell_command",
7830
- Glob: "glob",
7831
- Grep: "search_file_content",
7832
- WebSearch: "google_web_search",
7833
- WebFetch: "web_fetch",
7834
- TodoWrite: "write_todos",
7835
- AskUserQuestion: "ask_user"
7836
- };
7610
+ //#region src/install/shared.ts
7611
+ const pkg = JSON.parse(node_fs.readFileSync(node_path.resolve(__dirname, "..", "package.json"), "utf-8"));
7612
+ const templatesRoot = node_path.resolve(__dirname, "assets", "templates");
7613
+ const builtInSkills = [
7614
+ "tdd",
7615
+ "systematic-debugging",
7616
+ "verification-before-completion",
7617
+ "simplify",
7618
+ "code-review",
7619
+ "memory-management",
7620
+ "using-maxsim",
7621
+ "brainstorming",
7622
+ "roadmap-writing"
7623
+ ];
7837
7624
  /**
7838
- * Convert a Claude Code tool name to OpenCode format.
7839
- * - Applies special mappings (AskUserQuestion -> question, etc.)
7840
- * - Converts to lowercase (except MCP tools which keep their format)
7625
+ * Get the global config directory, using the Claude adapter
7841
7626
  */
7842
- function convertToolName(claudeTool) {
7843
- if (claudeToOpencodeTools[claudeTool]) return claudeToOpencodeTools[claudeTool];
7844
- if (claudeTool.startsWith("mcp__")) return claudeTool;
7845
- return claudeTool.toLowerCase();
7627
+ function getGlobalDir(explicitDir = null) {
7628
+ return claudeAdapter.getGlobalDir(explicitDir);
7846
7629
  }
7847
7630
  /**
7848
- * Convert a Claude Code tool name to Gemini CLI format.
7849
- * - Applies Claude->Gemini mapping (Read->read_file, Bash->run_shell_command, etc.)
7850
- * - Filters out MCP tools (mcp__*) -- auto-discovered at runtime in Gemini
7851
- * - Filters out Task -- agents are auto-registered as tools in Gemini
7852
- * @returns Gemini tool name, or null if tool should be excluded
7631
+ * Get the config directory path relative to home for hook templating
7853
7632
  */
7854
- function convertGeminiToolName(claudeTool) {
7855
- if (claudeTool.startsWith("mcp__")) return null;
7856
- if (claudeTool === "Task") return null;
7857
- if (claudeToGeminiTools[claudeTool]) return claudeToGeminiTools[claudeTool];
7858
- return claudeTool.toLowerCase();
7633
+ function getConfigDirFromHome(isGlobal) {
7634
+ return claudeAdapter.getConfigDirFromHome(isGlobal);
7859
7635
  }
7860
-
7861
- //#endregion
7862
- //#region src/adapters/transforms/content.ts
7863
- /**
7864
- * @maxsim/adapters — Content transformation utilities
7865
- *
7866
- * Ported from bin/install.js lines ~423-564
7867
- */
7868
7636
  /**
7869
- * Convert /maxsim:command-name to $maxsim-command-name for Codex skill mentions.
7870
- * Ported from install.js line ~423
7637
+ * Get the local directory name
7871
7638
  */
7872
- function convertSlashCommandsToCodexSkillMentions(content) {
7873
- let converted = content.replace(/\/maxsim:([a-z0-9-]+)/gi, (_, commandName) => {
7874
- return `$maxsim-${String(commandName).toLowerCase()}`;
7875
- });
7876
- converted = converted.replace(/\/maxsim-help\b/g, "$maxsim-help");
7877
- return converted;
7639
+ function getDirName() {
7640
+ return claudeAdapter.dirName;
7878
7641
  }
7879
7642
  /**
7880
- * Convert Claude markdown to Codex markdown format.
7881
- * Replaces slash commands and $ARGUMENTS placeholder.
7882
- * Ported from install.js line ~431
7643
+ * Recursively remove a directory, handling Windows read-only file attributes.
7644
+ * fs-extra handles cross-platform edge cases (EPERM on Windows, symlinks, etc.)
7883
7645
  */
7884
- function convertClaudeToCodexMarkdown(content) {
7885
- let converted = convertSlashCommandsToCodexSkillMentions(content);
7886
- converted = converted.replace(/\$ARGUMENTS\b/g, "{{MAXSIM_ARGS}}");
7887
- return converted;
7646
+ function safeRmDir(dirPath) {
7647
+ import_lib.default.removeSync(dirPath);
7888
7648
  }
7889
7649
  /**
7890
- * Strip HTML <sub> tags for Gemini CLI output.
7891
- * Terminals don't support subscript -- converts <sub>text</sub> to italic *(text)*.
7892
- * Ported from install.js line ~474
7650
+ * Recursively copy a directory (dereferences symlinks)
7893
7651
  */
7894
- function stripSubTags(content) {
7895
- return content.replace(/<sub>(.*?)<\/sub>/g, "*($1)*");
7652
+ function copyDirRecursive(src, dest) {
7653
+ import_lib.default.copySync(src, dest, { dereference: true });
7896
7654
  }
7897
7655
  /**
7898
- * Convert Claude Code agent frontmatter to Gemini CLI format.
7899
- * - tools: must be a YAML array (not comma-separated string)
7900
- * - tool names: must use Gemini built-in names (read_file, not Read)
7901
- * - color: must be removed (causes validation error)
7902
- * - mcp__* tools: must be excluded (auto-discovered at runtime)
7903
- * - ${VAR} patterns: escaped to $VAR for Gemini template compatibility
7904
- *
7905
- * Ported from install.js line ~487
7656
+ * Verify a directory exists and contains files.
7657
+ * If expectedFiles is provided, also checks that those specific files exist inside the directory.
7906
7658
  */
7907
- function convertClaudeToGeminiAgent(content) {
7908
- if (!content.startsWith("---")) return content;
7909
- const endIndex = content.indexOf("---", 3);
7910
- if (endIndex === -1) return content;
7911
- const frontmatter = content.substring(3, endIndex).trim();
7912
- const body = content.substring(endIndex + 3);
7913
- const lines = frontmatter.split("\n");
7914
- const newLines = [];
7915
- let inAllowedTools = false;
7916
- const tools = [];
7917
- for (const line of lines) {
7918
- const trimmed = line.trim();
7919
- if (trimmed.startsWith("allowed-tools:")) {
7920
- inAllowedTools = true;
7921
- continue;
7922
- }
7923
- if (trimmed.startsWith("tools:")) {
7924
- const toolsValue = trimmed.substring(6).trim();
7925
- if (toolsValue) {
7926
- const parsed = toolsValue.split(",").map((t) => t.trim()).filter((t) => t);
7927
- for (const t of parsed) {
7928
- const mapped = convertGeminiToolName(t);
7929
- if (mapped) tools.push(mapped);
7930
- }
7931
- } else inAllowedTools = true;
7932
- continue;
7659
+ function verifyInstalled(dirPath, description, expectedFiles) {
7660
+ if (!node_fs.existsSync(dirPath)) {
7661
+ console.error(` \u2717 Failed to install ${description}: directory not created`);
7662
+ return false;
7663
+ }
7664
+ try {
7665
+ if (node_fs.readdirSync(dirPath).length === 0) {
7666
+ console.error(` \u2717 Failed to install ${description}: directory is empty`);
7667
+ return false;
7933
7668
  }
7934
- if (trimmed.startsWith("color:")) continue;
7935
- if (inAllowedTools) {
7936
- if (trimmed.startsWith("- ")) {
7937
- const mapped = convertGeminiToolName(trimmed.substring(2).trim());
7938
- if (mapped) tools.push(mapped);
7939
- continue;
7940
- } else if (trimmed && !trimmed.startsWith("-")) inAllowedTools = false;
7669
+ } catch (e) {
7670
+ console.error(` \u2717 Failed to install ${description}: ${e.message}`);
7671
+ return false;
7672
+ }
7673
+ if (expectedFiles && expectedFiles.length > 0) {
7674
+ const missing = expectedFiles.filter((f) => !node_fs.existsSync(node_path.join(dirPath, f)));
7675
+ if (missing.length > 0) {
7676
+ console.error(` \u2717 Failed to install ${description}: missing files: ${missing.join(", ")}`);
7677
+ return false;
7941
7678
  }
7942
- if (!inAllowedTools) newLines.push(line);
7943
7679
  }
7944
- if (tools.length > 0) {
7945
- newLines.push("tools:");
7946
- for (const tool of tools) newLines.push(` - ${tool}`);
7680
+ return true;
7681
+ }
7682
+ /**
7683
+ * Verify a file exists
7684
+ */
7685
+ function verifyFileInstalled(filePath, description) {
7686
+ if (!node_fs.existsSync(filePath)) {
7687
+ console.error(` \u2717 Failed to install ${description}: file not created`);
7688
+ return false;
7947
7689
  }
7948
- return `---\n${newLines.join("\n").trim()}\n---${stripSubTags(body.replace(/\$\{(\w+)\}/g, "$$$1"))}`;
7690
+ return true;
7949
7691
  }
7950
7692
  /**
7951
- * Replace path references in markdown content for a target runtime.
7952
- * Replaces ~/.claude/ with pathPrefix and ./.claude/ with ./dirName/.
7693
+ * Verify that all major install components are present. Uses the manifest
7694
+ * (if available) to check individual files; otherwise falls back to
7695
+ * directory-level checks.
7696
+ *
7697
+ * Returns an object with `complete` (boolean) and `missing` (list of
7698
+ * component names that are absent or incomplete).
7953
7699
  */
7954
- function replacePathReferences(content, pathPrefix, dirName) {
7955
- const globalClaudeRegex = /~\/\.claude\//g;
7956
- const localClaudeRegex = /\.\/\.claude\//g;
7957
- let result = content.replace(globalClaudeRegex, pathPrefix);
7958
- result = result.replace(localClaudeRegex, `./${dirName}/`);
7959
- return result;
7700
+ function verifyInstallComplete(configDir, _runtime, manifest = null) {
7701
+ const missing = [];
7702
+ if (manifest && manifest.files) {
7703
+ for (const relPath of Object.keys(manifest.files)) if (!node_fs.existsSync(node_path.join(configDir, relPath))) missing.push(relPath);
7704
+ return {
7705
+ complete: missing.length === 0,
7706
+ missing
7707
+ };
7708
+ }
7709
+ const components = [
7710
+ {
7711
+ dir: node_path.join(configDir, "maxsim"),
7712
+ label: "maxsim (workflows/templates)"
7713
+ },
7714
+ {
7715
+ dir: node_path.join(configDir, "agents"),
7716
+ label: "agents"
7717
+ },
7718
+ {
7719
+ dir: node_path.join(configDir, "commands", "maxsim"),
7720
+ label: "commands"
7721
+ },
7722
+ {
7723
+ dir: node_path.join(configDir, "hooks"),
7724
+ label: "hooks"
7725
+ }
7726
+ ];
7727
+ for (const { dir, label } of components) if (!node_fs.existsSync(dir)) missing.push(label);
7728
+ else try {
7729
+ if (node_fs.readdirSync(dir).length === 0) missing.push(label);
7730
+ } catch {
7731
+ missing.push(label);
7732
+ }
7733
+ return {
7734
+ complete: missing.length === 0,
7735
+ missing
7736
+ };
7960
7737
  }
7961
7738
 
7962
7739
  //#endregion
7963
- //#region src/adapters/transforms/frontmatter.ts
7740
+ //#region src/install/adapters.ts
7741
+ let attributionCached = false;
7742
+ let attributionValue;
7964
7743
  /**
7965
- * @maxsim/adapters Frontmatter conversion functions for opencode, gemini, codex
7966
- *
7967
- * Ported from bin/install.js lines ~308-711
7744
+ * Get commit attribution setting for Claude Code
7745
+ * @returns null = remove, undefined = keep default, string = custom
7968
7746
  */
7969
- /** Color name to hex mapping for opencode compatibility */
7970
- const colorNameToHex = {
7971
- cyan: "#00FFFF",
7972
- red: "#FF0000",
7973
- green: "#00FF00",
7974
- blue: "#0000FF",
7975
- yellow: "#FFFF00",
7976
- magenta: "#FF00FF",
7977
- orange: "#FFA500",
7978
- purple: "#800080",
7979
- pink: "#FFC0CB",
7980
- white: "#FFFFFF",
7981
- black: "#000000",
7982
- gray: "#808080",
7983
- grey: "#808080"
7984
- };
7985
- /** Collapse whitespace to single line */
7986
- function toSingleLine(value) {
7987
- return value.replace(/\s+/g, " ").trim();
7747
+ function getCommitAttribution(explicitConfigDir) {
7748
+ if (attributionCached) return attributionValue;
7749
+ const attr = readSettings(node_path.join(getGlobalDir(explicitConfigDir), "settings.json")).attribution;
7750
+ if (!attr || attr.commit === void 0) attributionValue = void 0;
7751
+ else if (attr.commit === "") attributionValue = null;
7752
+ else attributionValue = attr.commit;
7753
+ attributionCached = true;
7754
+ return attributionValue;
7988
7755
  }
7989
- /** Quote a value for YAML using JSON.stringify */
7990
- function yamlQuote(value) {
7991
- return JSON.stringify(value);
7992
- }
7993
- /** Extract a single-line field value from YAML frontmatter text */
7994
- function extractFrontmatterField(frontmatter, fieldName) {
7995
- const regex = new RegExp(`^${fieldName}:\\s*(.+)$`, "m");
7996
- const match = frontmatter.match(regex);
7997
- if (!match) return null;
7998
- return match[1].trim().replace(/^['"]|['"]$/g, "");
7756
+
7757
+ //#endregion
7758
+ //#region src/install/dashboard.ts
7759
+ /** Check whether the current process is running with admin/root privileges. */
7760
+ function isElevated() {
7761
+ if (process.platform === "win32") try {
7762
+ (0, node_child_process.execSync)("net session", { stdio: "pipe" });
7763
+ return true;
7764
+ } catch {
7765
+ return false;
7766
+ }
7767
+ return process.getuid?.() === 0;
7999
7768
  }
8000
7769
  /**
8001
- * Convert Claude Code frontmatter to OpenCode format.
8002
- * - Converts 'allowed-tools:' array to 'tools:' object with tool: true entries
8003
- * - Converts color names to hex
8004
- * - Removes name: field (opencode uses filename)
8005
- * - Replaces tool name references in body content
8006
- * - Replaces /maxsim: with /maxsim- (flat command structure)
8007
- * - Replaces ~/.claude with ~/.config/opencode
8008
- * - Replaces subagent_type="general-purpose" with "general"
8009
- *
8010
- * Ported from install.js line ~566
7770
+ * Add a firewall rule to allow inbound traffic on the given port.
7771
+ * Handles Windows (netsh), Linux (ufw / iptables), and macOS (no rule needed).
8011
7772
  */
8012
- function convertClaudeToOpencodeFrontmatter(content) {
8013
- let convertedContent = content;
8014
- convertedContent = convertedContent.replace(/\bAskUserQuestion\b/g, "question");
8015
- convertedContent = convertedContent.replace(/\bSlashCommand\b/g, "skill");
8016
- convertedContent = convertedContent.replace(/\bTodoWrite\b/g, "todowrite");
8017
- convertedContent = convertedContent.replace(/\/maxsim:/g, "/maxsim-");
8018
- convertedContent = convertedContent.replace(/~\/\.claude\b/g, "~/.config/opencode");
8019
- convertedContent = convertedContent.replace(/subagent_type="general-purpose"/g, "subagent_type=\"general\"");
8020
- if (!convertedContent.startsWith("---")) return convertedContent;
8021
- const endIndex = convertedContent.indexOf("---", 3);
8022
- if (endIndex === -1) return convertedContent;
8023
- const frontmatter = convertedContent.substring(3, endIndex).trim();
8024
- const body = convertedContent.substring(endIndex + 3);
8025
- const lines = frontmatter.split("\n");
8026
- const newLines = [];
8027
- let inAllowedTools = false;
8028
- const allowedTools = [];
8029
- for (const line of lines) {
8030
- const trimmed = line.trim();
8031
- if (trimmed.startsWith("allowed-tools:")) {
8032
- inAllowedTools = true;
8033
- continue;
8034
- }
8035
- if (trimmed.startsWith("tools:")) {
8036
- const toolsValue = trimmed.substring(6).trim();
8037
- if (toolsValue) {
8038
- const tools = toolsValue.split(",").map((t) => t.trim()).filter((t) => t);
8039
- allowedTools.push(...tools);
7773
+ function applyFirewallRule(port) {
7774
+ const platform = process.platform;
7775
+ try {
7776
+ if (platform === "win32") {
7777
+ const cmd = `netsh advfirewall firewall add rule name="MAXSIM Dashboard" dir=in action=allow protocol=TCP localport=${port}`;
7778
+ if (isElevated()) {
7779
+ (0, node_child_process.execSync)(cmd, { stdio: "pipe" });
7780
+ console.log(chalk.green(" ✓ Windows Firewall rule added for port " + port));
7781
+ } else {
7782
+ console.log(chalk.gray(" Requesting administrator privileges for firewall rule..."));
7783
+ (0, node_child_process.execSync)(`powershell -NoProfile -Command "${`Start-Process cmd -ArgumentList '/c ${cmd}' -Verb RunAs -Wait`}"`, { stdio: "pipe" });
7784
+ console.log(chalk.green(" ✓ Windows Firewall rule added for port " + port));
8040
7785
  }
8041
- continue;
8042
- }
8043
- if (trimmed.startsWith("name:")) continue;
8044
- if (trimmed.startsWith("color:")) {
8045
- const colorValue = trimmed.substring(6).trim().toLowerCase();
8046
- const hexColor = colorNameToHex[colorValue];
8047
- if (hexColor) newLines.push(`color: "${hexColor}"`);
8048
- else if (colorValue.startsWith("#")) {
8049
- if (/^#[0-9a-f]{3}$|^#[0-9a-f]{6}$/i.test(colorValue)) newLines.push(line);
7786
+ } else if (platform === "linux") {
7787
+ const sudoPrefix = isElevated() ? "" : "sudo ";
7788
+ try {
7789
+ (0, node_child_process.execSync)(`${sudoPrefix}ufw allow ${port}/tcp`, { stdio: "pipe" });
7790
+ console.log(chalk.green(" ✓ UFW rule added for port " + port));
7791
+ } catch {
7792
+ try {
7793
+ (0, node_child_process.execSync)(`${sudoPrefix}iptables -A INPUT -p tcp --dport ${port} -j ACCEPT`, { stdio: "pipe" });
7794
+ console.log(chalk.green(" ✓ iptables rule added for port " + port));
7795
+ } catch {
7796
+ console.log(chalk.yellow(` \u26a0 Could not add firewall rule automatically. Run: sudo ufw allow ${port}/tcp`));
7797
+ }
8050
7798
  }
8051
- continue;
8052
- }
8053
- if (inAllowedTools) {
8054
- if (trimmed.startsWith("- ")) {
8055
- allowedTools.push(trimmed.substring(2).trim());
8056
- continue;
8057
- } else if (trimmed && !trimmed.startsWith("-")) inAllowedTools = false;
8058
- }
8059
- if (!inAllowedTools) newLines.push(line);
8060
- }
8061
- if (allowedTools.length > 0) {
8062
- newLines.push("tools:");
8063
- for (const tool of allowedTools) newLines.push(` ${convertToolName(tool)}: true`);
7799
+ } else if (platform === "darwin") console.log(chalk.gray(" macOS: No firewall rule needed (inbound connections are allowed by default)"));
7800
+ } catch (err) {
7801
+ console.warn(chalk.yellow(` \u26a0 Firewall rule failed: ${err.message}`));
7802
+ console.warn(chalk.gray(` You may need to manually allow port ${port} through your firewall.`));
8064
7803
  }
8065
- return `---\n${newLines.join("\n").trim()}\n---${body}`;
8066
- }
8067
- /**
8068
- * Convert Claude Code markdown command to Gemini TOML format.
8069
- * Ported from install.js line ~677
8070
- */
8071
- function convertClaudeToGeminiToml(content) {
8072
- if (!content.startsWith("---")) return `prompt = ${JSON.stringify(content)}\n`;
8073
- const endIndex = content.indexOf("---", 3);
8074
- if (endIndex === -1) return `prompt = ${JSON.stringify(content)}\n`;
8075
- const frontmatter = content.substring(3, endIndex).trim();
8076
- const body = content.substring(endIndex + 3).trim();
8077
- let description = "";
8078
- const lines = frontmatter.split("\n");
8079
- for (const line of lines) {
8080
- const trimmed = line.trim();
8081
- if (trimmed.startsWith("description:")) {
8082
- description = trimmed.substring(12).trim();
8083
- break;
8084
- }
8085
- }
8086
- let toml = "";
8087
- if (description) toml += `description = ${JSON.stringify(description)}\n`;
8088
- toml += `prompt = ${JSON.stringify(body)}\n`;
8089
- return toml;
8090
- }
8091
- /**
8092
- * Convert Claude command to Codex skill format with adapter header.
8093
- * Ported from install.js line ~452
8094
- */
8095
- function convertClaudeCommandToCodexSkill(content, skillName) {
8096
- const { frontmatter, body } = extractFrontmatterAndBody(convertClaudeToCodexMarkdown(content));
8097
- let description = `Run MAXSIM workflow ${skillName}.`;
8098
- if (frontmatter) {
8099
- const maybeDescription = extractFrontmatterField(frontmatter, "description");
8100
- if (maybeDescription) description = maybeDescription;
8101
- }
8102
- description = toSingleLine(description);
8103
- const shortDescription = description.length > 180 ? `${description.slice(0, 177)}...` : description;
8104
- const adapter = getCodexSkillAdapterHeader(skillName);
8105
- return `---\nname: ${yamlQuote(skillName)}\ndescription: ${yamlQuote(description)}\nmetadata:\n short-description: ${yamlQuote(shortDescription)}\n---\n\n${adapter}\n\n${body.trimStart()}`;
8106
7804
  }
8107
7805
  /**
8108
- * Generate the Codex skill adapter header block.
8109
- * Ported from install.js line ~437
7806
+ * Handle the `dashboard` subcommand refresh assets, install node-pty, launch server
8110
7807
  */
8111
- function getCodexSkillAdapterHeader(skillName) {
8112
- const invocation = `$${skillName}`;
8113
- return `<codex_skill_adapter>
8114
- Codex skills-first mode:
8115
- - This skill is invoked by mentioning \`${invocation}\`.
8116
- - Treat all user text after \`${invocation}\` as \`{{MAXSIM_ARGS}}\`.
8117
- - If no arguments are present, treat \`{{MAXSIM_ARGS}}\` as empty.
8118
-
8119
- Legacy orchestration compatibility:
8120
- - Any \`Task(...)\` pattern in referenced workflow docs is legacy syntax.
8121
- - Implement equivalent behavior with Codex collaboration tools: \`spawn_agent\`, \`wait\`, \`send_input\`, and \`close_agent\`.
8122
- - Treat legacy \`subagent_type\` names as role hints in the spawned message.
8123
- </codex_skill_adapter>`;
7808
+ async function runDashboardSubcommand(argv) {
7809
+ const { spawn: spawnDash, execSync: execSyncDash } = await import("node:child_process");
7810
+ const dashboardAssetSrc = node_path.resolve(__dirname, "assets", "dashboard");
7811
+ const installDir = node_path.join(process.cwd(), ".claude");
7812
+ const installDashDir = node_path.join(installDir, "dashboard");
7813
+ if (node_fs.existsSync(dashboardAssetSrc)) {
7814
+ const nodeModulesDir = node_path.join(installDashDir, "node_modules");
7815
+ const nodeModulesTmp = node_path.join(installDir, "_dashboard_node_modules_tmp");
7816
+ const hadNodeModules = node_fs.existsSync(nodeModulesDir);
7817
+ if (hadNodeModules) node_fs.renameSync(nodeModulesDir, nodeModulesTmp);
7818
+ safeRmDir(installDashDir);
7819
+ node_fs.mkdirSync(installDashDir, { recursive: true });
7820
+ copyDirRecursive(dashboardAssetSrc, installDashDir);
7821
+ if (hadNodeModules && node_fs.existsSync(nodeModulesTmp)) node_fs.renameSync(nodeModulesTmp, nodeModulesDir);
7822
+ const dashConfigPath = node_path.join(installDir, "dashboard.json");
7823
+ if (!node_fs.existsSync(dashConfigPath)) node_fs.writeFileSync(dashConfigPath, JSON.stringify({ projectCwd: process.cwd() }, null, 2) + "\n");
7824
+ }
7825
+ const localDashboard = node_path.join(process.cwd(), ".claude", "dashboard", "server.js");
7826
+ const globalDashboard = node_path.join(node_os.homedir(), ".claude", "dashboard", "server.js");
7827
+ let serverPath = null;
7828
+ if (node_fs.existsSync(localDashboard)) serverPath = localDashboard;
7829
+ else if (node_fs.existsSync(globalDashboard)) serverPath = globalDashboard;
7830
+ if (!serverPath) {
7831
+ console.log(chalk.yellow("\n Dashboard not available.\n"));
7832
+ console.log(" Install MAXSIM first: " + chalk.cyan("npx maxsimcli@latest") + "\n");
7833
+ process.exit(0);
7834
+ }
7835
+ const forceNetwork = !!argv["network"];
7836
+ const dashboardDir = node_path.dirname(serverPath);
7837
+ const dashboardConfigPath = node_path.join(node_path.dirname(dashboardDir), "dashboard.json");
7838
+ let projectCwd = process.cwd();
7839
+ let networkMode = forceNetwork;
7840
+ if (node_fs.existsSync(dashboardConfigPath)) try {
7841
+ const config = JSON.parse(node_fs.readFileSync(dashboardConfigPath, "utf8"));
7842
+ if (config.projectCwd) projectCwd = config.projectCwd;
7843
+ if (!forceNetwork) networkMode = config.networkMode ?? false;
7844
+ } catch {}
7845
+ const dashDirForPty = node_path.dirname(serverPath);
7846
+ const ptyModulePath = node_path.join(dashDirForPty, "node_modules", "node-pty");
7847
+ if (!node_fs.existsSync(ptyModulePath)) {
7848
+ console.log(chalk.gray(" Installing node-pty for terminal support..."));
7849
+ try {
7850
+ const dashPkgPath = node_path.join(dashDirForPty, "package.json");
7851
+ if (!node_fs.existsSync(dashPkgPath)) node_fs.writeFileSync(dashPkgPath, "{\"private\":true}\n");
7852
+ execSyncDash("npm install node-pty --save-optional --no-audit --no-fund --loglevel=error", {
7853
+ cwd: dashDirForPty,
7854
+ stdio: "inherit",
7855
+ timeout: 12e4
7856
+ });
7857
+ } catch {
7858
+ console.warn(chalk.yellow(" node-pty installation failed — terminal will be unavailable."));
7859
+ }
7860
+ }
7861
+ console.log(chalk.blue("Starting dashboard..."));
7862
+ console.log(chalk.gray(` Project: ${projectCwd}`));
7863
+ console.log(chalk.gray(` Server: ${serverPath}`));
7864
+ if (networkMode) console.log(chalk.gray(" Network: enabled (local network access + QR code)"));
7865
+ console.log("");
7866
+ spawnDash(process.execPath, [serverPath], {
7867
+ cwd: dashboardDir,
7868
+ detached: true,
7869
+ stdio: "ignore",
7870
+ env: {
7871
+ ...process.env,
7872
+ MAXSIM_PROJECT_CWD: projectCwd,
7873
+ MAXSIM_NETWORK_MODE: networkMode ? "1" : "0",
7874
+ NODE_ENV: "production"
7875
+ }
7876
+ }).unref();
7877
+ const POLL_INTERVAL_MS = 500;
7878
+ const POLL_TIMEOUT_MS = 2e4;
7879
+ const HEALTH_TIMEOUT_MS = 1e3;
7880
+ const DEFAULT_PORT = 3333;
7881
+ const PORT_RANGE_END = 3343;
7882
+ let foundUrl = null;
7883
+ const deadline = Date.now() + POLL_TIMEOUT_MS;
7884
+ while (Date.now() < deadline) {
7885
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
7886
+ for (let p = DEFAULT_PORT; p <= PORT_RANGE_END; p++) try {
7887
+ const controller = new AbortController();
7888
+ const timer = setTimeout(() => controller.abort(), HEALTH_TIMEOUT_MS);
7889
+ const res = await fetch(`http://localhost:${p}/api/health`, { signal: controller.signal });
7890
+ clearTimeout(timer);
7891
+ if (res.ok) {
7892
+ if ((await res.json()).status === "ok") {
7893
+ foundUrl = `http://localhost:${p}`;
7894
+ break;
7895
+ }
7896
+ }
7897
+ } catch {}
7898
+ if (foundUrl) break;
7899
+ }
7900
+ if (foundUrl) console.log(chalk.green(` Dashboard ready at ${foundUrl}`));
7901
+ else console.log(chalk.yellow("\n Dashboard did not respond after 20s. The server may still be starting — check http://localhost:3333"));
7902
+ process.exit(0);
8124
7903
  }
8125
7904
 
8126
7905
  //#endregion
8127
- //#region src/adapters/opencode.ts
8128
- /**
8129
- * @maxsim/adapters — OpenCode adapter
8130
- *
8131
- * Ports the OpenCode-specific logic from bin/install.js:
8132
- * - getOpencodeGlobalDir() (lines 79-97)
8133
- * - getGlobalDir('opencode', ...) (lines 104-111)
8134
- * - getDirName('opencode') (line 46)
8135
- * - getConfigDirFromHome('opencode', isGlobal) (lines 58-68)
8136
- * - convertClaudeToOpencodeFrontmatter + path replacement
8137
- */
7906
+ //#region src/install/hooks.ts
8138
7907
  /**
8139
- * Get the global config directory for OpenCode.
8140
- * OpenCode follows XDG Base Directory spec and uses ~/.config/opencode/.
8141
- * Priority: OPENCODE_CONFIG_DIR > dirname(OPENCODE_CONFIG) > XDG_CONFIG_HOME/opencode > ~/.config/opencode
7908
+ * Clean up orphaned files from previous MAXSIM versions
8142
7909
  */
8143
- function getOpencodeGlobalDir$1() {
8144
- if (process.env.OPENCODE_CONFIG_DIR) return expandTilde(process.env.OPENCODE_CONFIG_DIR);
8145
- if (process.env.OPENCODE_CONFIG) return node_path.dirname(expandTilde(process.env.OPENCODE_CONFIG));
8146
- if (process.env.XDG_CONFIG_HOME) return node_path.join(expandTilde(process.env.XDG_CONFIG_HOME), "opencode");
8147
- return node_path.join(node_os.homedir(), ".config", "opencode");
7910
+ function cleanupOrphanedFiles(configDir) {
7911
+ for (const relPath of ["hooks/maxsim-notify.sh", "hooks/statusline.js"]) {
7912
+ const fullPath = node_path.join(configDir, relPath);
7913
+ if (node_fs.existsSync(fullPath)) {
7914
+ node_fs.unlinkSync(fullPath);
7915
+ console.log(` ${chalk.green("✓")} Removed orphaned ${relPath}`);
7916
+ }
7917
+ }
8148
7918
  }
8149
7919
  /**
8150
- * Get the global config directory for OpenCode.
8151
- * Priority: explicitDir > env vars (via getOpencodeGlobalDir)
7920
+ * Clean up orphaned hook registrations from settings.json
8152
7921
  */
8153
- function getGlobalDir$3(explicitDir) {
8154
- if (explicitDir) return expandTilde(explicitDir);
8155
- return getOpencodeGlobalDir$1();
8156
- }
8157
- /**
8158
- * Get the config directory path relative to home for hook templating.
8159
- */
8160
- function getConfigDirFromHome$3(isGlobal) {
8161
- if (!isGlobal) return "'.opencode'";
8162
- return "'.config', 'opencode'";
8163
- }
8164
- /**
8165
- * Transform markdown content for OpenCode installation.
8166
- * Applies frontmatter conversion and path replacement.
8167
- */
8168
- function transformContent$2(content, pathPrefix) {
8169
- let result = replacePathReferences(content, pathPrefix, ".opencode");
8170
- result = result.replace(/~\/\.opencode\//g, pathPrefix);
8171
- result = convertClaudeToOpencodeFrontmatter(result);
8172
- return result;
8173
- }
8174
- /**
8175
- * OpenCode adapter configuration.
8176
- * OpenCode uses flat command structure (command/maxsim-*.md).
8177
- */
8178
- const opencodeAdapter = {
8179
- runtime: "opencode",
8180
- dirName: ".opencode",
8181
- getGlobalDir: getGlobalDir$3,
8182
- getConfigDirFromHome: getConfigDirFromHome$3,
8183
- transformContent: transformContent$2,
8184
- commandStructure: "flat"
8185
- };
8186
-
8187
- //#endregion
8188
- //#region src/adapters/gemini.ts
8189
- /**
8190
- * @maxsim/adapters — Gemini adapter
8191
- *
8192
- * Ports the Gemini-specific logic from bin/install.js:
8193
- * - getGlobalDir('gemini', ...) (lines 113-122)
8194
- * - getDirName('gemini') (line 47)
8195
- * - getConfigDirFromHome('gemini', isGlobal) (line 69)
8196
- * - convertClaudeToGeminiToml + convertClaudeToGeminiAgent + stripSubTags
8197
- */
8198
- /**
8199
- * Get the global config directory for Gemini.
8200
- * Priority: explicitDir > GEMINI_CONFIG_DIR env > ~/.gemini
8201
- */
8202
- function getGlobalDir$2(explicitDir) {
8203
- if (explicitDir) return expandTilde(explicitDir);
8204
- if (process.env.GEMINI_CONFIG_DIR) return expandTilde(process.env.GEMINI_CONFIG_DIR);
8205
- return node_path.join(node_os.homedir(), ".gemini");
8206
- }
8207
- /**
8208
- * Get the config directory path relative to home for hook templating.
8209
- */
8210
- function getConfigDirFromHome$2(_isGlobal) {
8211
- return "'.gemini'";
8212
- }
8213
- /**
8214
- * Transform markdown content for Gemini installation.
8215
- * Applies TOML conversion for commands, agent conversion for agents,
8216
- * stripSubTags, and path replacement.
8217
- */
8218
- function transformContent$1(content, pathPrefix) {
8219
- let result = replacePathReferences(content, pathPrefix, ".gemini");
8220
- result = stripSubTags(result);
8221
- result = convertClaudeToGeminiToml(result);
8222
- return result;
8223
- }
8224
- /**
8225
- * Gemini adapter configuration.
8226
- * Gemini uses nested command structure (commands/maxsim/*.toml).
8227
- */
8228
- const geminiAdapter = {
8229
- runtime: "gemini",
8230
- dirName: ".gemini",
8231
- getGlobalDir: getGlobalDir$2,
8232
- getConfigDirFromHome: getConfigDirFromHome$2,
8233
- transformContent: transformContent$1,
8234
- commandStructure: "nested"
8235
- };
8236
-
8237
- //#endregion
8238
- //#region src/adapters/codex.ts
8239
- /**
8240
- * @maxsim/adapters — Codex adapter
8241
- *
8242
- * Ports the Codex-specific logic from bin/install.js:
8243
- * - getGlobalDir('codex', ...) (lines 124-133)
8244
- * - getDirName('codex') (line 48)
8245
- * - getConfigDirFromHome('codex', isGlobal) (line 70)
8246
- * - convertClaudeCommandToCodexSkill + convertClaudeToCodexMarkdown
8247
- */
8248
- /**
8249
- * Get the global config directory for Codex.
8250
- * Priority: explicitDir > CODEX_HOME env > ~/.codex
8251
- */
8252
- function getGlobalDir$1(explicitDir) {
8253
- if (explicitDir) return expandTilde(explicitDir);
8254
- if (process.env.CODEX_HOME) return expandTilde(process.env.CODEX_HOME);
8255
- return node_path.join(node_os.homedir(), ".codex");
8256
- }
8257
- /**
8258
- * Get the config directory path relative to home for hook templating.
8259
- */
8260
- function getConfigDirFromHome$1(_isGlobal) {
8261
- return "'.codex'";
8262
- }
8263
- /**
8264
- * Transform markdown content for Codex installation.
8265
- * Applies Codex markdown conversion and path replacement.
8266
- */
8267
- function transformContent(content, pathPrefix) {
8268
- let result = replacePathReferences(content, pathPrefix, ".codex");
8269
- result = result.replace(/~\/\.codex\//g, pathPrefix);
8270
- result = convertClaudeCommandToCodexSkill(result);
8271
- return result;
8272
- }
8273
- /**
8274
- * Codex adapter configuration.
8275
- * Codex uses skill-based command structure (skills/maxsim-star/SKILL.md).
8276
- */
8277
- const codexAdapter = {
8278
- runtime: "codex",
8279
- dirName: ".codex",
8280
- getGlobalDir: getGlobalDir$1,
8281
- getConfigDirFromHome: getConfigDirFromHome$1,
8282
- transformContent,
8283
- commandStructure: "skills"
8284
- };
8285
-
8286
- //#endregion
8287
- //#region src/install.ts
8288
- const pkg = JSON.parse(node_fs.readFileSync(node_path.resolve(__dirname, "..", "package.json"), "utf-8"));
8289
- const templatesRoot = node_path.resolve(__dirname, "assets", "templates");
8290
- const argv = (0, import_minimist.default)(process.argv.slice(2), {
8291
- boolean: [
8292
- "global",
8293
- "local",
8294
- "opencode",
8295
- "claude",
8296
- "gemini",
8297
- "codex",
8298
- "both",
8299
- "all",
8300
- "uninstall",
8301
- "help",
8302
- "version",
8303
- "force-statusline",
8304
- "network"
8305
- ],
8306
- string: ["config-dir"],
8307
- alias: {
8308
- g: "global",
8309
- l: "local",
8310
- u: "uninstall",
8311
- h: "help",
8312
- c: "config-dir"
8313
- }
8314
- });
8315
- const hasGlobal = !!argv["global"];
8316
- const hasLocal = !!argv["local"];
8317
- const hasOpencode = !!argv["opencode"];
8318
- const hasClaude = !!argv["claude"];
8319
- const hasGemini = !!argv["gemini"];
8320
- const hasCodex = !!argv["codex"];
8321
- const hasBoth = !!argv["both"];
8322
- const hasAll = !!argv["all"];
8323
- const hasUninstall = !!argv["uninstall"];
8324
- let selectedRuntimes = [];
8325
- if (hasAll) selectedRuntimes = [
8326
- "claude",
8327
- "opencode",
8328
- "gemini",
8329
- "codex"
8330
- ];
8331
- else if (hasBoth) selectedRuntimes = ["claude", "opencode"];
8332
- else {
8333
- if (hasOpencode) selectedRuntimes.push("opencode");
8334
- if (hasClaude) selectedRuntimes.push("claude");
8335
- if (hasGemini) selectedRuntimes.push("gemini");
8336
- if (hasCodex) selectedRuntimes.push("codex");
8337
- }
8338
- /**
8339
- * Add a firewall rule to allow inbound traffic on the given port.
8340
- * Handles Windows (netsh), Linux (ufw / iptables), and macOS (no rule needed).
8341
- */
8342
- /** Check whether the current process is running with admin/root privileges. */
8343
- function isElevated() {
8344
- if (process.platform === "win32") try {
8345
- (0, node_child_process.execSync)("net session", { stdio: "pipe" });
8346
- return true;
8347
- } catch {
8348
- return false;
8349
- }
8350
- return process.getuid?.() === 0;
8351
- }
8352
- function applyFirewallRule(port) {
8353
- const platform = process.platform;
8354
- try {
8355
- if (platform === "win32") {
8356
- const cmd = `netsh advfirewall firewall add rule name="MAXSIM Dashboard" dir=in action=allow protocol=TCP localport=${port}`;
8357
- if (isElevated()) {
8358
- (0, node_child_process.execSync)(cmd, { stdio: "pipe" });
8359
- console.log(chalk.green(" ✓ Windows Firewall rule added for port " + port));
8360
- } else {
8361
- console.log(chalk.gray(" Requesting administrator privileges for firewall rule..."));
8362
- (0, node_child_process.execSync)(`powershell -NoProfile -Command "${`Start-Process cmd -ArgumentList '/c ${cmd}' -Verb RunAs -Wait`}"`, { stdio: "pipe" });
8363
- console.log(chalk.green(" ✓ Windows Firewall rule added for port " + port));
8364
- }
8365
- } else if (platform === "linux") {
8366
- const sudoPrefix = isElevated() ? "" : "sudo ";
8367
- try {
8368
- (0, node_child_process.execSync)(`${sudoPrefix}ufw allow ${port}/tcp`, { stdio: "pipe" });
8369
- console.log(chalk.green(" ✓ UFW rule added for port " + port));
8370
- } catch {
8371
- try {
8372
- (0, node_child_process.execSync)(`${sudoPrefix}iptables -A INPUT -p tcp --dport ${port} -j ACCEPT`, { stdio: "pipe" });
8373
- console.log(chalk.green(" ✓ iptables rule added for port " + port));
8374
- } catch {
8375
- console.log(chalk.yellow(` ⚠ Could not add firewall rule automatically. Run: sudo ufw allow ${port}/tcp`));
8376
- }
8377
- }
8378
- } else if (platform === "darwin") console.log(chalk.gray(" macOS: No firewall rule needed (inbound connections are allowed by default)"));
8379
- } catch (err) {
8380
- console.warn(chalk.yellow(` ⚠ Firewall rule failed: ${err.message}`));
8381
- console.warn(chalk.gray(` You may need to manually allow port ${port} through your firewall.`));
8382
- }
8383
- }
8384
- /**
8385
- * Adapter registry keyed by runtime name
8386
- */
8387
- const adapterMap = {
8388
- claude: claudeAdapter,
8389
- opencode: opencodeAdapter,
8390
- gemini: geminiAdapter,
8391
- codex: codexAdapter
8392
- };
8393
- /**
8394
- * Get adapter for a runtime
8395
- */
8396
- function getAdapter(runtime) {
8397
- return adapterMap[runtime];
8398
- }
8399
- /**
8400
- * Get the global config directory for a runtime, using adapter
8401
- */
8402
- function getGlobalDir(runtime, explicitDir = null) {
8403
- return getAdapter(runtime).getGlobalDir(explicitDir);
8404
- }
8405
- /**
8406
- * Get the config directory path relative to home for hook templating
8407
- */
8408
- function getConfigDirFromHome(runtime, isGlobal) {
8409
- return getAdapter(runtime).getConfigDirFromHome(isGlobal);
8410
- }
8411
- /**
8412
- * Get the local directory name for a runtime
8413
- */
8414
- function getDirName(runtime) {
8415
- return getAdapter(runtime).dirName;
8416
- }
8417
- /**
8418
- * Recursively remove a directory, handling Windows read-only file attributes.
8419
- * fs-extra handles cross-platform edge cases (EPERM on Windows, symlinks, etc.)
8420
- */
8421
- function safeRmDir(dirPath) {
8422
- import_lib.default.removeSync(dirPath);
8423
- }
8424
- /**
8425
- * Recursively copy a directory (dereferences symlinks)
8426
- */
8427
- function copyDirRecursive(src, dest) {
8428
- import_lib.default.copySync(src, dest, { dereference: true });
8429
- }
8430
- /**
8431
- * Get the global config directory for OpenCode (for JSONC permissions)
8432
- * OpenCode follows XDG Base Directory spec
8433
- */
8434
- function getOpencodeGlobalDir() {
8435
- return opencodeAdapter.getGlobalDir();
8436
- }
8437
- const banner = "\n" + chalk.cyan(figlet.default.textSync("MAXSIM", { font: "ANSI Shadow" }).split("\n").map((line) => " " + line).join("\n")) + "\n\n MAXSIM " + chalk.dim("v" + pkg.version) + "\n A meta-prompting, context engineering and spec-driven\n development system for Claude Code, OpenCode, Gemini, and Codex.\n";
8438
- const explicitConfigDir = argv["config-dir"] || null;
8439
- const hasHelp = !!argv["help"];
8440
- const hasVersion = !!argv["version"];
8441
- const forceStatusline = !!argv["force-statusline"];
8442
- if (hasVersion) {
8443
- console.log(pkg.version);
8444
- process.exit(0);
8445
- }
8446
- console.log(banner);
8447
- if (hasHelp) {
8448
- console.log(` ${chalk.yellow("Usage:")} npx maxsimcli [options]\n\n ${chalk.yellow("Options:")}\n ${chalk.cyan("-g, --global")} Install globally (to config directory)\n ${chalk.cyan("-l, --local")} Install locally (to current directory)\n ${chalk.cyan("--claude")} Install for Claude Code only\n ${chalk.cyan("--opencode")} Install for OpenCode only\n ${chalk.cyan("--gemini")} Install for Gemini only\n ${chalk.cyan("--codex")} Install for Codex only\n ${chalk.cyan("--all")} Install for all runtimes\n ${chalk.cyan("-u, --uninstall")} Uninstall MAXSIM (remove all MAXSIM files)\n ${chalk.cyan("-c, --config-dir <path>")} Specify custom config directory\n ${chalk.cyan("-h, --help")} Show this help message\n ${chalk.cyan("--force-statusline")} Replace existing statusline config\n\n ${chalk.yellow("Examples:")}\n ${chalk.dim("# Interactive install (prompts for runtime and location)")}\n npx maxsimcli\n\n ${chalk.dim("# Install for Claude Code globally")}\n npx maxsimcli --claude --global\n\n ${chalk.dim("# Install for Gemini globally")}\n npx maxsimcli --gemini --global\n\n ${chalk.dim("# Install for Codex globally")}\n npx maxsimcli --codex --global\n\n ${chalk.dim("# Install for all runtimes globally")}\n npx maxsimcli --all --global\n\n ${chalk.dim("# Install to custom config directory")}\n npx maxsimcli --codex --global --config-dir ~/.codex-work\n\n ${chalk.dim("# Install to current project only")}\n npx maxsimcli --claude --local\n\n ${chalk.dim("# Uninstall MAXSIM from Codex globally")}\n npx maxsimcli --codex --global --uninstall\n\n ${chalk.yellow("Notes:")}\n The --config-dir option is useful when you have multiple configurations.\n It takes priority over CLAUDE_CONFIG_DIR / GEMINI_CONFIG_DIR / CODEX_HOME environment variables.\n`);
8449
- process.exit(0);
8450
- }
8451
- const attributionCache = /* @__PURE__ */ new Map();
8452
- /**
8453
- * Get commit attribution setting for a runtime
8454
- * @returns null = remove, undefined = keep default, string = custom
8455
- */
8456
- function getCommitAttribution(runtime) {
8457
- if (attributionCache.has(runtime)) return attributionCache.get(runtime);
8458
- let result;
8459
- if (runtime === "opencode") result = readSettings(node_path.join(getGlobalDir("opencode", null), "opencode.json")).disable_ai_attribution === true ? null : void 0;
8460
- else if (runtime === "gemini") {
8461
- const attr = readSettings(node_path.join(getGlobalDir("gemini", explicitConfigDir), "settings.json")).attribution;
8462
- if (!attr || attr.commit === void 0) result = void 0;
8463
- else if (attr.commit === "") result = null;
8464
- else result = attr.commit;
8465
- } else if (runtime === "claude") {
8466
- const attr = readSettings(node_path.join(getGlobalDir("claude", explicitConfigDir), "settings.json")).attribution;
8467
- if (!attr || attr.commit === void 0) result = void 0;
8468
- else if (attr.commit === "") result = null;
8469
- else result = attr.commit;
8470
- } else result = void 0;
8471
- attributionCache.set(runtime, result);
8472
- return result;
8473
- }
8474
- /**
8475
- * Copy commands to a flat structure for OpenCode
8476
- * OpenCode expects: command/maxsim-help.md (invoked as /maxsim-help)
8477
- * Source structure: commands/maxsim/help.md
8478
- */
8479
- function copyFlattenedCommands(srcDir, destDir, prefix, pathPrefix, runtime) {
8480
- if (!node_fs.existsSync(srcDir)) return;
8481
- if (node_fs.existsSync(destDir)) {
8482
- for (const file of node_fs.readdirSync(destDir)) if (file.startsWith(`${prefix}-`) && file.endsWith(".md")) node_fs.unlinkSync(node_path.join(destDir, file));
8483
- } else node_fs.mkdirSync(destDir, { recursive: true });
8484
- const entries = node_fs.readdirSync(srcDir, { withFileTypes: true });
8485
- for (const entry of entries) {
8486
- const srcPath = node_path.join(srcDir, entry.name);
8487
- if (entry.isDirectory()) copyFlattenedCommands(srcPath, destDir, `${prefix}-${entry.name}`, pathPrefix, runtime);
8488
- else if (entry.name.endsWith(".md")) {
8489
- const destName = `${prefix}-${entry.name.replace(".md", "")}.md`;
8490
- const destPath = node_path.join(destDir, destName);
8491
- let content = node_fs.readFileSync(srcPath, "utf8");
8492
- const globalClaudeRegex = /~\/\.claude\//g;
8493
- const localClaudeRegex = /\.\/\.claude\//g;
8494
- const opencodeDirRegex = /~\/\.opencode\//g;
8495
- content = content.replace(globalClaudeRegex, pathPrefix);
8496
- content = content.replace(localClaudeRegex, `./${getDirName(runtime)}/`);
8497
- content = content.replace(opencodeDirRegex, pathPrefix);
8498
- content = processAttribution(content, getCommitAttribution(runtime));
8499
- content = convertClaudeToOpencodeFrontmatter(content);
8500
- node_fs.writeFileSync(destPath, content);
8501
- }
8502
- }
8503
- }
8504
- function listCodexSkillNames(skillsDir, prefix = "maxsim-") {
8505
- if (!node_fs.existsSync(skillsDir)) return [];
8506
- return node_fs.readdirSync(skillsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.startsWith(prefix)).filter((entry) => node_fs.existsSync(node_path.join(skillsDir, entry.name, "SKILL.md"))).map((entry) => entry.name).sort();
8507
- }
8508
- function copyCommandsAsCodexSkills(srcDir, skillsDir, prefix, pathPrefix, runtime) {
8509
- if (!node_fs.existsSync(srcDir)) return;
8510
- node_fs.mkdirSync(skillsDir, { recursive: true });
8511
- const existing = node_fs.readdirSync(skillsDir, { withFileTypes: true });
8512
- for (const entry of existing) if (entry.isDirectory() && entry.name.startsWith(`${prefix}-`)) node_fs.rmSync(node_path.join(skillsDir, entry.name), { recursive: true });
8513
- function recurse(currentSrcDir, currentPrefix) {
8514
- const entries = node_fs.readdirSync(currentSrcDir, { withFileTypes: true });
8515
- for (const entry of entries) {
8516
- const srcPath = node_path.join(currentSrcDir, entry.name);
8517
- if (entry.isDirectory()) {
8518
- recurse(srcPath, `${currentPrefix}-${entry.name}`);
8519
- continue;
8520
- }
8521
- if (!entry.name.endsWith(".md")) continue;
8522
- const skillName = `${currentPrefix}-${entry.name.replace(".md", "")}`;
8523
- const skillDir = node_path.join(skillsDir, skillName);
8524
- node_fs.mkdirSync(skillDir, { recursive: true });
8525
- let content = node_fs.readFileSync(srcPath, "utf8");
8526
- const globalClaudeRegex = /~\/\.claude\//g;
8527
- const localClaudeRegex = /\.\/\.claude\//g;
8528
- const codexDirRegex = /~\/\.codex\//g;
8529
- content = content.replace(globalClaudeRegex, pathPrefix);
8530
- content = content.replace(localClaudeRegex, `./${getDirName(runtime)}/`);
8531
- content = content.replace(codexDirRegex, pathPrefix);
8532
- content = processAttribution(content, getCommitAttribution(runtime));
8533
- content = convertClaudeCommandToCodexSkill(content, skillName);
8534
- node_fs.writeFileSync(node_path.join(skillDir, "SKILL.md"), content);
8535
- }
8536
- }
8537
- recurse(srcDir, prefix);
8538
- }
8539
- /**
8540
- * Recursively copy directory, replacing paths in .md files
8541
- * Deletes existing destDir first to remove orphaned files from previous versions
8542
- */
8543
- function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime, isCommand = false) {
8544
- const isOpencode = runtime === "opencode";
8545
- const isCodex = runtime === "codex";
8546
- const dirName = getDirName(runtime);
8547
- if (node_fs.existsSync(destDir)) node_fs.rmSync(destDir, { recursive: true });
8548
- node_fs.mkdirSync(destDir, { recursive: true });
8549
- const entries = node_fs.readdirSync(srcDir, { withFileTypes: true });
8550
- for (const entry of entries) {
8551
- const srcPath = node_path.join(srcDir, entry.name);
8552
- const destPath = node_path.join(destDir, entry.name);
8553
- if (entry.isDirectory()) copyWithPathReplacement(srcPath, destPath, pathPrefix, runtime, isCommand);
8554
- else if (entry.name.endsWith(".md")) {
8555
- let content = node_fs.readFileSync(srcPath, "utf8");
8556
- const globalClaudeRegex = /~\/\.claude\//g;
8557
- const localClaudeRegex = /\.\/\.claude\//g;
8558
- content = content.replace(globalClaudeRegex, pathPrefix);
8559
- content = content.replace(localClaudeRegex, `./${dirName}/`);
8560
- content = processAttribution(content, getCommitAttribution(runtime));
8561
- if (isOpencode) {
8562
- content = convertClaudeToOpencodeFrontmatter(content);
8563
- node_fs.writeFileSync(destPath, content);
8564
- } else if (runtime === "gemini") if (isCommand) {
8565
- content = stripSubTags(content);
8566
- const tomlContent = convertClaudeToGeminiToml(content);
8567
- const tomlPath = destPath.replace(/\.md$/, ".toml");
8568
- node_fs.writeFileSync(tomlPath, tomlContent);
8569
- } else node_fs.writeFileSync(destPath, content);
8570
- else if (isCodex) {
8571
- content = convertClaudeToCodexMarkdown(content);
8572
- node_fs.writeFileSync(destPath, content);
8573
- } else node_fs.writeFileSync(destPath, content);
8574
- } else node_fs.copyFileSync(srcPath, destPath);
8575
- }
8576
- }
8577
- /**
8578
- * Clean up orphaned files from previous MAXSIM versions
8579
- */
8580
- function cleanupOrphanedFiles(configDir) {
8581
- for (const relPath of ["hooks/maxsim-notify.sh", "hooks/statusline.js"]) {
8582
- const fullPath = node_path.join(configDir, relPath);
8583
- if (node_fs.existsSync(fullPath)) {
8584
- node_fs.unlinkSync(fullPath);
8585
- console.log(` ${chalk.green("✓")} Removed orphaned ${relPath}`);
8586
- }
8587
- }
8588
- }
8589
- /**
8590
- * Clean up orphaned hook registrations from settings.json
8591
- */
8592
- function cleanupOrphanedHooks(settings) {
8593
- const orphanedHookPatterns = [
8594
- "maxsim-notify.sh",
8595
- "hooks/statusline.js",
8596
- "maxsim-intel-index.js",
8597
- "maxsim-intel-session.js",
8598
- "maxsim-intel-prune.js"
8599
- ];
8600
- let cleanedHooks = false;
8601
- const hooks = settings.hooks;
8602
- if (hooks) for (const eventType of Object.keys(hooks)) {
8603
- const hookEntries = hooks[eventType];
8604
- if (Array.isArray(hookEntries)) hooks[eventType] = hookEntries.filter((entry) => {
8605
- if (entry.hooks && Array.isArray(entry.hooks)) {
8606
- if (entry.hooks.some((h) => h.command && orphanedHookPatterns.some((pattern) => h.command.includes(pattern)))) {
8607
- cleanedHooks = true;
8608
- return false;
8609
- }
8610
- }
8611
- return true;
8612
- });
8613
- }
8614
- if (cleanedHooks) console.log(` ${chalk.green("✓")} Removed orphaned hook registrations`);
8615
- const statusLine = settings.statusLine;
8616
- if (statusLine && statusLine.command && statusLine.command.includes("statusline.js") && !statusLine.command.includes("maxsim-statusline.js")) {
8617
- statusLine.command = statusLine.command.replace(/statusline\.js/, "maxsim-statusline.js");
8618
- console.log(` ${chalk.green("✓")} Updated statusline path (statusline.js \u2192 maxsim-statusline.js)`);
8619
- }
8620
- return settings;
8621
- }
8622
- /**
8623
- * Uninstall MAXSIM from the specified directory for a specific runtime
8624
- */
8625
- function uninstall(isGlobal, runtime = "claude") {
8626
- const isOpencode = runtime === "opencode";
8627
- const isCodex = runtime === "codex";
8628
- const dirName = getDirName(runtime);
8629
- const targetDir = isGlobal ? getGlobalDir(runtime, explicitConfigDir) : node_path.join(process.cwd(), dirName);
8630
- const locationLabel = isGlobal ? targetDir.replace(node_os.homedir(), "~") : targetDir.replace(process.cwd(), ".");
8631
- let runtimeLabel = "Claude Code";
8632
- if (runtime === "opencode") runtimeLabel = "OpenCode";
8633
- if (runtime === "gemini") runtimeLabel = "Gemini";
8634
- if (runtime === "codex") runtimeLabel = "Codex";
8635
- console.log(` Uninstalling MAXSIM from ${chalk.cyan(runtimeLabel)} at ${chalk.cyan(locationLabel)}\n`);
8636
- if (!node_fs.existsSync(targetDir)) {
8637
- console.log(` ${chalk.yellow("⚠")} Directory does not exist: ${locationLabel}`);
8638
- console.log(` Nothing to uninstall.\n`);
8639
- return;
8640
- }
8641
- let removedCount = 0;
8642
- if (isOpencode) {
8643
- const commandDir = node_path.join(targetDir, "command");
8644
- if (node_fs.existsSync(commandDir)) {
8645
- const files = node_fs.readdirSync(commandDir);
8646
- for (const file of files) if (file.startsWith("maxsim-") && file.endsWith(".md")) {
8647
- node_fs.unlinkSync(node_path.join(commandDir, file));
8648
- removedCount++;
8649
- }
8650
- console.log(` ${chalk.green("✓")} Removed MAXSIM commands from command/`);
8651
- }
8652
- } else if (isCodex) {
8653
- const skillsDir = node_path.join(targetDir, "skills");
8654
- if (node_fs.existsSync(skillsDir)) {
8655
- let skillCount = 0;
8656
- const entries = node_fs.readdirSync(skillsDir, { withFileTypes: true });
8657
- for (const entry of entries) if (entry.isDirectory() && entry.name.startsWith("maxsim-")) {
8658
- node_fs.rmSync(node_path.join(skillsDir, entry.name), { recursive: true });
8659
- skillCount++;
8660
- }
8661
- if (skillCount > 0) {
8662
- removedCount++;
8663
- console.log(` ${chalk.green("✓")} Removed ${skillCount} Codex skills`);
8664
- }
8665
- }
8666
- } else {
8667
- const maxsimCommandsDir = node_path.join(targetDir, "commands", "maxsim");
8668
- if (node_fs.existsSync(maxsimCommandsDir)) {
8669
- node_fs.rmSync(maxsimCommandsDir, { recursive: true });
8670
- removedCount++;
8671
- console.log(` ${chalk.green("✓")} Removed commands/maxsim/`);
8672
- }
8673
- }
8674
- const maxsimDir = node_path.join(targetDir, "maxsim");
8675
- if (node_fs.existsSync(maxsimDir)) {
8676
- node_fs.rmSync(maxsimDir, { recursive: true });
8677
- removedCount++;
8678
- console.log(` ${chalk.green("✓")} Removed maxsim/`);
8679
- }
8680
- const agentsDir = node_path.join(targetDir, "agents");
8681
- if (node_fs.existsSync(agentsDir)) {
8682
- const files = node_fs.readdirSync(agentsDir);
8683
- let agentCount = 0;
8684
- for (const file of files) if (file.startsWith("maxsim-") && file.endsWith(".md")) {
8685
- node_fs.unlinkSync(node_path.join(agentsDir, file));
8686
- agentCount++;
8687
- }
8688
- if (agentCount > 0) {
8689
- removedCount++;
8690
- console.log(` ${chalk.green("✓")} Removed ${agentCount} MAXSIM agents`);
8691
- }
8692
- }
8693
- const hooksDir = node_path.join(targetDir, "hooks");
8694
- if (node_fs.existsSync(hooksDir)) {
8695
- const maxsimHooks = [
8696
- "maxsim-statusline.js",
8697
- "maxsim-check-update.js",
8698
- "maxsim-check-update.sh",
8699
- "maxsim-context-monitor.js"
8700
- ];
8701
- let hookCount = 0;
8702
- for (const hook of maxsimHooks) {
8703
- const hookPath = node_path.join(hooksDir, hook);
8704
- if (node_fs.existsSync(hookPath)) {
8705
- node_fs.unlinkSync(hookPath);
8706
- hookCount++;
8707
- }
8708
- }
8709
- if (hookCount > 0) {
8710
- removedCount++;
8711
- console.log(` ${chalk.green("✓")} Removed ${hookCount} MAXSIM hooks`);
8712
- }
8713
- }
8714
- const pkgJsonPath = node_path.join(targetDir, "package.json");
8715
- if (node_fs.existsSync(pkgJsonPath)) try {
8716
- if (node_fs.readFileSync(pkgJsonPath, "utf8").trim() === "{\"type\":\"commonjs\"}") {
8717
- node_fs.unlinkSync(pkgJsonPath);
8718
- removedCount++;
8719
- console.log(` ${chalk.green("✓")} Removed MAXSIM package.json`);
8720
- }
8721
- } catch {}
8722
- const settingsPath = node_path.join(targetDir, "settings.json");
8723
- if (node_fs.existsSync(settingsPath)) {
8724
- const settings = readSettings(settingsPath);
8725
- let settingsModified = false;
8726
- const statusLine = settings.statusLine;
8727
- if (statusLine && statusLine.command && statusLine.command.includes("maxsim-statusline")) {
8728
- delete settings.statusLine;
8729
- settingsModified = true;
8730
- console.log(` ${chalk.green("✓")} Removed MAXSIM statusline from settings`);
8731
- }
8732
- const settingsHooks = settings.hooks;
8733
- if (settingsHooks && settingsHooks.SessionStart) {
8734
- const before = settingsHooks.SessionStart.length;
8735
- settingsHooks.SessionStart = settingsHooks.SessionStart.filter((entry) => {
8736
- if (entry.hooks && Array.isArray(entry.hooks)) return !entry.hooks.some((h) => h.command && (h.command.includes("maxsim-check-update") || h.command.includes("maxsim-statusline")));
8737
- return true;
8738
- });
8739
- if (settingsHooks.SessionStart.length < before) {
8740
- settingsModified = true;
8741
- console.log(` ${chalk.green("✓")} Removed MAXSIM hooks from settings`);
8742
- }
8743
- if (settingsHooks.SessionStart.length === 0) delete settingsHooks.SessionStart;
8744
- }
8745
- if (settingsHooks && settingsHooks.PostToolUse) {
8746
- const before = settingsHooks.PostToolUse.length;
8747
- settingsHooks.PostToolUse = settingsHooks.PostToolUse.filter((entry) => {
8748
- if (entry.hooks && Array.isArray(entry.hooks)) return !entry.hooks.some((h) => h.command && h.command.includes("maxsim-context-monitor"));
8749
- return true;
8750
- });
8751
- if (settingsHooks.PostToolUse.length < before) {
8752
- settingsModified = true;
8753
- console.log(` ${chalk.green("✓")} Removed context monitor hook from settings`);
8754
- }
8755
- if (settingsHooks.PostToolUse.length === 0) delete settingsHooks.PostToolUse;
8756
- }
8757
- if (settingsHooks && Object.keys(settingsHooks).length === 0) delete settings.hooks;
8758
- if (settingsModified) {
8759
- writeSettings(settingsPath, settings);
8760
- removedCount++;
8761
- }
8762
- }
8763
- if (isOpencode) {
8764
- const opencodeConfigDir = isGlobal ? getOpencodeGlobalDir() : node_path.join(process.cwd(), ".opencode");
8765
- const configPath = node_path.join(opencodeConfigDir, "opencode.json");
8766
- if (node_fs.existsSync(configPath)) try {
8767
- const config = JSON.parse(node_fs.readFileSync(configPath, "utf8"));
8768
- let modified = false;
8769
- const permission = config.permission;
8770
- if (permission) {
8771
- for (const permType of ["read", "external_directory"]) if (permission[permType]) {
8772
- const keys = Object.keys(permission[permType]);
8773
- for (const key of keys) if (key.includes("maxsim")) {
8774
- delete permission[permType][key];
8775
- modified = true;
8776
- }
8777
- if (Object.keys(permission[permType]).length === 0) delete permission[permType];
7922
+ function cleanupOrphanedHooks(settings) {
7923
+ const orphanedHookPatterns = [
7924
+ "maxsim-notify.sh",
7925
+ "hooks/statusline.js",
7926
+ "maxsim-intel-index.js",
7927
+ "maxsim-intel-session.js",
7928
+ "maxsim-intel-prune.js"
7929
+ ];
7930
+ let cleanedHooks = false;
7931
+ const hooks = settings.hooks;
7932
+ if (hooks) for (const eventType of Object.keys(hooks)) {
7933
+ const hookEntries = hooks[eventType];
7934
+ if (Array.isArray(hookEntries)) hooks[eventType] = hookEntries.filter((entry) => {
7935
+ if (entry.hooks && Array.isArray(entry.hooks)) {
7936
+ if (entry.hooks.some((h) => h.command && orphanedHookPatterns.some((pattern) => h.command.includes(pattern)))) {
7937
+ cleanedHooks = true;
7938
+ return false;
8778
7939
  }
8779
- if (Object.keys(permission).length === 0) delete config.permission;
8780
- }
8781
- if (modified) {
8782
- node_fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
8783
- removedCount++;
8784
- console.log(` ${chalk.green("✓")} Removed MAXSIM permissions from opencode.json`);
8785
7940
  }
8786
- } catch {}
7941
+ return true;
7942
+ });
8787
7943
  }
8788
- if (removedCount === 0) console.log(` ${chalk.yellow("")} No MAXSIM files found to remove.`);
8789
- console.log(`
8790
- ${chalk.green("Done!")} MAXSIM has been uninstalled from ${runtimeLabel}.
8791
- Your other files and settings have been preserved.
8792
- `);
7944
+ if (cleanedHooks) console.log(` ${chalk.green("")} Removed orphaned hook registrations`);
7945
+ const statusLine = settings.statusLine;
7946
+ if (statusLine && statusLine.command && statusLine.command.includes("statusline.js") && !statusLine.command.includes("maxsim-statusline.js")) {
7947
+ statusLine.command = statusLine.command.replace(/statusline\.js/, "maxsim-statusline.js");
7948
+ console.log(` ${chalk.green("✓")} Updated statusline path (statusline.js \u2192 maxsim-statusline.js)`);
7949
+ }
7950
+ return settings;
8793
7951
  }
8794
7952
  /**
8795
- * Parse JSONC (JSON with Comments) by stripping comments and trailing commas.
7953
+ * Install hook files and configure settings.json
8796
7954
  */
8797
- function parseJsonc(content) {
8798
- if (content.charCodeAt(0) === 65279) content = content.slice(1);
8799
- let result = "";
8800
- let inString = false;
8801
- let i = 0;
8802
- while (i < content.length) {
8803
- const char = content[i];
8804
- const next = content[i + 1];
8805
- if (inString) {
8806
- result += char;
8807
- if (char === "\\" && i + 1 < content.length) {
8808
- result += next;
8809
- i += 2;
8810
- continue;
7955
+ function installHookFiles(targetDir, isGlobal, failures) {
7956
+ let hooksSrc = null;
7957
+ const bundledHooksDir = node_path.resolve(__dirname, "assets", "hooks");
7958
+ if (node_fs.existsSync(bundledHooksDir)) hooksSrc = bundledHooksDir;
7959
+ else console.warn(` ${chalk.yellow("!")} bundled hooks not found - hooks will not be installed`);
7960
+ if (hooksSrc) {
7961
+ const spinner = ora({
7962
+ text: "Installing hooks...",
7963
+ color: "cyan"
7964
+ }).start();
7965
+ const hooksDest = node_path.join(targetDir, "hooks");
7966
+ node_fs.mkdirSync(hooksDest, { recursive: true });
7967
+ const hookEntries = node_fs.readdirSync(hooksSrc);
7968
+ const configDirReplacement = getConfigDirFromHome(isGlobal);
7969
+ for (const entry of hookEntries) {
7970
+ const srcFile = node_path.join(hooksSrc, entry);
7971
+ if (node_fs.statSync(srcFile).isFile() && entry.endsWith(".cjs") && !entry.includes(".d.")) {
7972
+ const destName = entry.replace(/\.cjs$/, ".js");
7973
+ const destFile = node_path.join(hooksDest, destName);
7974
+ let content = node_fs.readFileSync(srcFile, "utf8");
7975
+ content = content.replace(/'\.claude'/g, configDirReplacement);
7976
+ node_fs.writeFileSync(destFile, content);
8811
7977
  }
8812
- if (char === "\"") inString = false;
8813
- i++;
8814
- } else if (char === "\"") {
8815
- inString = true;
8816
- result += char;
8817
- i++;
8818
- } else if (char === "/" && next === "/") while (i < content.length && content[i] !== "\n") i++;
8819
- else if (char === "/" && next === "*") {
8820
- i += 2;
8821
- while (i < content.length - 1 && !(content[i] === "*" && content[i + 1] === "/")) i++;
8822
- i += 2;
8823
- } else {
8824
- result += char;
8825
- i++;
7978
+ }
7979
+ if (verifyInstalled(hooksDest, "hooks")) spinner.succeed(chalk.green("✓") + " Installed hooks (bundled)");
7980
+ else {
7981
+ spinner.fail("Failed to install hooks");
7982
+ failures.push("hooks");
8826
7983
  }
8827
7984
  }
8828
- result = result.replace(/,(\s*[}\]])/g, "$1");
8829
- return JSON.parse(result);
8830
7985
  }
8831
7986
  /**
8832
- * Configure OpenCode permissions to allow reading MAXSIM reference docs
7987
+ * Configure hooks and statusline in settings.json
8833
7988
  */
8834
- function configureOpencodePermissions(isGlobal = true) {
8835
- const opencodeConfigDir = isGlobal ? getOpencodeGlobalDir() : node_path.join(process.cwd(), ".opencode");
8836
- const configPath = node_path.join(opencodeConfigDir, "opencode.json");
8837
- node_fs.mkdirSync(opencodeConfigDir, { recursive: true });
8838
- let config = {};
8839
- if (node_fs.existsSync(configPath)) try {
8840
- config = parseJsonc(node_fs.readFileSync(configPath, "utf8"));
8841
- } catch (e) {
8842
- console.log(` ${chalk.yellow("⚠")} Could not parse opencode.json - skipping permission config`);
8843
- console.log(` ${chalk.dim(`Reason: ${e.message}`)}`);
8844
- console.log(` ${chalk.dim("Your config was NOT modified. Fix the syntax manually if needed.")}`);
8845
- return;
7989
+ function configureSettingsHooks(targetDir, isGlobal) {
7990
+ const dirName = getDirName();
7991
+ const settingsPath = node_path.join(targetDir, "settings.json");
7992
+ const settings = cleanupOrphanedHooks(readSettings(settingsPath));
7993
+ const statuslineCommand = isGlobal ? buildHookCommand(targetDir, "maxsim-statusline.js") : "node " + dirName + "/hooks/maxsim-statusline.js";
7994
+ const updateCheckCommand = isGlobal ? buildHookCommand(targetDir, "maxsim-check-update.js") : "node " + dirName + "/hooks/maxsim-check-update.js";
7995
+ const contextMonitorCommand = isGlobal ? buildHookCommand(targetDir, "maxsim-context-monitor.js") : "node " + dirName + "/hooks/maxsim-context-monitor.js";
7996
+ if (!settings.hooks) settings.hooks = {};
7997
+ const installHooks = settings.hooks;
7998
+ if (!installHooks.SessionStart) installHooks.SessionStart = [];
7999
+ if (!installHooks.SessionStart.some((entry) => entry.hooks && entry.hooks.some((h) => h.command && h.command.includes("maxsim-check-update")))) {
8000
+ installHooks.SessionStart.push({ hooks: [{
8001
+ type: "command",
8002
+ command: updateCheckCommand
8003
+ }] });
8004
+ console.log(` ${chalk.green("✓")} Configured update check hook`);
8005
+ }
8006
+ if (!installHooks.PostToolUse) installHooks.PostToolUse = [];
8007
+ if (!installHooks.PostToolUse.some((entry) => entry.hooks && entry.hooks.some((h) => h.command && h.command.includes("maxsim-context-monitor")))) {
8008
+ installHooks.PostToolUse.push({ hooks: [{
8009
+ type: "command",
8010
+ command: contextMonitorCommand
8011
+ }] });
8012
+ console.log(` ${chalk.green("✓")} Configured context window monitor hook`);
8846
8013
  }
8847
- if (!config.permission) config.permission = {};
8848
- const permission = config.permission;
8849
- const maxsimPath = opencodeConfigDir === node_path.join(node_os.homedir(), ".config", "opencode") ? "~/.config/opencode/maxsim/*" : `${opencodeConfigDir.replace(/\\/g, "/")}/maxsim/*`;
8850
- let modified = false;
8851
- if (!permission.read || typeof permission.read !== "object") permission.read = {};
8852
- if (permission.read[maxsimPath] !== "allow") {
8853
- permission.read[maxsimPath] = "allow";
8854
- modified = true;
8855
- }
8856
- if (!permission.external_directory || typeof permission.external_directory !== "object") permission.external_directory = {};
8857
- if (permission.external_directory[maxsimPath] !== "allow") {
8858
- permission.external_directory[maxsimPath] = "allow";
8859
- modified = true;
8860
- }
8861
- if (!modified) return;
8862
- node_fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
8863
- console.log(` ${chalk.green("✓")} Configured read permission for MAXSIM docs`);
8014
+ return {
8015
+ settingsPath,
8016
+ settings,
8017
+ statuslineCommand,
8018
+ updateCheckCommand,
8019
+ contextMonitorCommand
8020
+ };
8864
8021
  }
8865
8022
  /**
8866
- * Verify a directory exists and contains files
8023
+ * Handle statusline configuration returns true if MAXSIM statusline should be installed
8867
8024
  */
8868
- function verifyInstalled(dirPath, description) {
8869
- if (!node_fs.existsSync(dirPath)) {
8870
- console.error(` ${chalk.yellow("✗")} Failed to install ${description}: directory not created`);
8871
- return false;
8872
- }
8873
- try {
8874
- if (node_fs.readdirSync(dirPath).length === 0) {
8875
- console.error(` ${chalk.yellow("✗")} Failed to install ${description}: directory is empty`);
8876
- return false;
8877
- }
8878
- } catch (e) {
8879
- console.error(` ${chalk.yellow("✗")} Failed to install ${description}: ${e.message}`);
8025
+ async function handleStatusline(settings, isInteractive, forceStatusline) {
8026
+ if (!(settings.statusLine != null)) return true;
8027
+ if (forceStatusline) return true;
8028
+ if (!isInteractive) {
8029
+ console.log(chalk.yellow("⚠") + " Skipping statusline (already configured)");
8030
+ console.log(" Use " + chalk.cyan("--force-statusline") + " to replace\n");
8880
8031
  return false;
8881
8032
  }
8882
- return true;
8033
+ const statusLine = settings.statusLine;
8034
+ const existingCmd = statusLine.command || statusLine.url || "(custom)";
8035
+ console.log();
8036
+ console.log(chalk.yellow("⚠ Existing statusline detected"));
8037
+ console.log();
8038
+ console.log(" Your current statusline:");
8039
+ console.log(" " + chalk.dim(`command: ${existingCmd}`));
8040
+ console.log();
8041
+ console.log(" MAXSIM includes a statusline showing:");
8042
+ console.log(" • Model name");
8043
+ console.log(" • Current task (from todo list)");
8044
+ console.log(" • Context window usage (color-coded)");
8045
+ console.log();
8046
+ return await dist_default$1({
8047
+ message: "Replace with MAXSIM statusline?",
8048
+ default: false
8049
+ });
8883
8050
  }
8884
8051
  /**
8885
- * Verify a file exists
8052
+ * Apply statusline config, then print completion message
8886
8053
  */
8887
- function verifyFileInstalled(filePath, description) {
8888
- if (!node_fs.existsSync(filePath)) {
8889
- console.error(` ${chalk.yellow("✗")} Failed to install ${description}: file not created`);
8890
- return false;
8054
+ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallStatusline, isGlobal = true) {
8055
+ if (shouldInstallStatusline) {
8056
+ settings.statusLine = {
8057
+ type: "command",
8058
+ command: statuslineCommand
8059
+ };
8060
+ console.log(` ${chalk.green("✓")} Configured statusline`);
8891
8061
  }
8892
- return true;
8062
+ if (settingsPath && settings) writeSettings(settingsPath, settings);
8063
+ console.log(`
8064
+ ${chalk.green("Done!")} Launch Claude Code and run ${chalk.cyan("/maxsim:help")}.
8065
+
8066
+ ${chalk.cyan("Join the community:")} https://discord.gg/5JJgD5svVS
8067
+ `);
8893
8068
  }
8894
- const PATCHES_DIR_NAME = "maxsim-local-patches";
8069
+
8070
+ //#endregion
8071
+ //#region src/install/manifest.ts
8895
8072
  const MANIFEST_NAME = "maxsim-file-manifest.json";
8896
8073
  /**
8897
8074
  * Compute SHA256 hash of file contents
@@ -8919,13 +8096,9 @@ function generateManifest(dir, baseDir) {
8919
8096
  /**
8920
8097
  * Write file manifest after installation for future modification detection
8921
8098
  */
8922
- function writeManifest(configDir, runtime = "claude") {
8923
- const isOpencode = runtime === "opencode";
8924
- const isCodex = runtime === "codex";
8099
+ function writeManifest(configDir) {
8925
8100
  const maxsimDir = node_path.join(configDir, "maxsim");
8926
8101
  const commandsDir = node_path.join(configDir, "commands", "maxsim");
8927
- const opencodeCommandDir = node_path.join(configDir, "command");
8928
- const codexSkillsDir = node_path.join(configDir, "skills");
8929
8102
  const agentsDir = node_path.join(configDir, "agents");
8930
8103
  const manifest = {
8931
8104
  version: pkg.version,
@@ -8934,40 +8107,43 @@ function writeManifest(configDir, runtime = "claude") {
8934
8107
  };
8935
8108
  const maxsimHashes = generateManifest(maxsimDir);
8936
8109
  for (const [rel, hash] of Object.entries(maxsimHashes)) manifest.files["maxsim/" + rel] = hash;
8937
- if (!isOpencode && !isCodex && node_fs.existsSync(commandsDir)) {
8110
+ if (node_fs.existsSync(commandsDir)) {
8938
8111
  const cmdHashes = generateManifest(commandsDir);
8939
8112
  for (const [rel, hash] of Object.entries(cmdHashes)) manifest.files["commands/maxsim/" + rel] = hash;
8940
8113
  }
8941
- if (isOpencode && node_fs.existsSync(opencodeCommandDir)) {
8942
- for (const file of node_fs.readdirSync(opencodeCommandDir)) if (file.startsWith("maxsim-") && file.endsWith(".md")) manifest.files["command/" + file] = fileHash(node_path.join(opencodeCommandDir, file));
8943
- }
8944
- if (isCodex && node_fs.existsSync(codexSkillsDir)) for (const skillName of listCodexSkillNames(codexSkillsDir)) {
8945
- const skillHashes = generateManifest(node_path.join(codexSkillsDir, skillName));
8946
- for (const [rel, hash] of Object.entries(skillHashes)) manifest.files[`skills/${skillName}/${rel}`] = hash;
8947
- }
8948
8114
  if (node_fs.existsSync(agentsDir)) {
8949
8115
  for (const file of node_fs.readdirSync(agentsDir)) if (file.startsWith("maxsim-") && file.endsWith(".md")) manifest.files["agents/" + file] = fileHash(node_path.join(agentsDir, file));
8950
8116
  }
8951
- const skillsManifestDir = node_path.join(agentsDir, "skills");
8117
+ const skillsManifestDir = node_path.join(configDir, "skills");
8952
8118
  if (node_fs.existsSync(skillsManifestDir)) {
8953
8119
  const skillHashes = generateManifest(skillsManifestDir);
8954
- for (const [rel, hash] of Object.entries(skillHashes)) manifest.files["agents/skills/" + rel] = hash;
8120
+ for (const [rel, hash] of Object.entries(skillHashes)) manifest.files["skills/" + rel] = hash;
8955
8121
  }
8956
8122
  node_fs.writeFileSync(node_path.join(configDir, MANIFEST_NAME), JSON.stringify(manifest, null, 2));
8957
8123
  return manifest;
8958
8124
  }
8959
8125
  /**
8960
- * Detect user-modified MAXSIM files by comparing against install manifest.
8126
+ * Read an existing manifest from the config directory, or return null if none exists / is invalid
8961
8127
  */
8962
- function saveLocalPatches(configDir) {
8128
+ function readManifest(configDir) {
8963
8129
  const manifestPath = node_path.join(configDir, MANIFEST_NAME);
8964
- if (!node_fs.existsSync(manifestPath)) return [];
8965
- let manifest;
8130
+ if (!node_fs.existsSync(manifestPath)) return null;
8966
8131
  try {
8967
- manifest = JSON.parse(node_fs.readFileSync(manifestPath, "utf8"));
8132
+ return JSON.parse(node_fs.readFileSync(manifestPath, "utf8"));
8968
8133
  } catch {
8969
- return [];
8134
+ return null;
8970
8135
  }
8136
+ }
8137
+
8138
+ //#endregion
8139
+ //#region src/install/patches.ts
8140
+ const PATCHES_DIR_NAME = "maxsim-local-patches";
8141
+ /**
8142
+ * Detect user-modified MAXSIM files by comparing against install manifest.
8143
+ */
8144
+ function saveLocalPatches(configDir) {
8145
+ const manifest = readManifest(configDir);
8146
+ if (!manifest) return [];
8971
8147
  const patchesDir = node_path.join(configDir, PATCHES_DIR_NAME);
8972
8148
  const modified = [];
8973
8149
  for (const [relPath, originalHash] of Object.entries(manifest.files || {})) {
@@ -8980,96 +8156,290 @@ function saveLocalPatches(configDir) {
8980
8156
  modified.push(relPath);
8981
8157
  }
8982
8158
  }
8983
- if (modified.length > 0) {
8984
- const meta = {
8985
- backed_up_at: (/* @__PURE__ */ new Date()).toISOString(),
8986
- from_version: manifest.version,
8987
- files: modified
8988
- };
8989
- node_fs.writeFileSync(node_path.join(patchesDir, "backup-meta.json"), JSON.stringify(meta, null, 2));
8990
- console.log(" " + chalk.yellow("i") + " Found " + modified.length + " locally modified MAXSIM file(s) — backed up to maxsim-local-patches/");
8991
- for (const f of modified) console.log(" " + chalk.dim(f));
8159
+ if (modified.length > 0) {
8160
+ const meta = {
8161
+ backed_up_at: (/* @__PURE__ */ new Date()).toISOString(),
8162
+ from_version: manifest.version,
8163
+ files: modified
8164
+ };
8165
+ node_fs.writeFileSync(node_path.join(patchesDir, "backup-meta.json"), JSON.stringify(meta, null, 2));
8166
+ console.log(" " + chalk.yellow("i") + " Found " + modified.length + " locally modified MAXSIM file(s) — backed up to maxsim-local-patches/");
8167
+ for (const f of modified) console.log(" " + chalk.dim(f));
8168
+ }
8169
+ return modified;
8170
+ }
8171
+ /**
8172
+ * After install, report backed-up patches for user to reapply.
8173
+ */
8174
+ function reportLocalPatches(configDir) {
8175
+ const patchesDir = node_path.join(configDir, PATCHES_DIR_NAME);
8176
+ const metaPath = node_path.join(patchesDir, "backup-meta.json");
8177
+ if (!node_fs.existsSync(metaPath)) return [];
8178
+ let meta;
8179
+ try {
8180
+ meta = JSON.parse(node_fs.readFileSync(metaPath, "utf8"));
8181
+ } catch {
8182
+ return [];
8183
+ }
8184
+ if (meta.files && meta.files.length > 0) {
8185
+ console.log("");
8186
+ console.log(" " + chalk.yellow("Local patches detected") + " (from v" + meta.from_version + "):");
8187
+ for (const f of meta.files) console.log(" " + chalk.cyan(f));
8188
+ console.log("");
8189
+ console.log(" Your modifications are saved in " + chalk.cyan(PATCHES_DIR_NAME + "/"));
8190
+ console.log(" Run " + chalk.cyan("/maxsim:reapply-patches") + " to merge them into the new version.");
8191
+ console.log(" Or manually compare and merge the files.");
8192
+ console.log("");
8193
+ }
8194
+ return meta.files || [];
8195
+ }
8196
+
8197
+ //#endregion
8198
+ //#region src/install/copy.ts
8199
+ /**
8200
+ * Recursively copy directory, replacing paths in .md files
8201
+ * Deletes existing destDir first to remove orphaned files from previous versions
8202
+ */
8203
+ function copyWithPathReplacement(srcDir, destDir, pathPrefix, explicitConfigDir, isCommand = false) {
8204
+ if (node_fs.existsSync(destDir)) node_fs.rmSync(destDir, { recursive: true });
8205
+ node_fs.mkdirSync(destDir, { recursive: true });
8206
+ const entries = node_fs.readdirSync(srcDir, { withFileTypes: true });
8207
+ for (const entry of entries) {
8208
+ const srcPath = node_path.join(srcDir, entry.name);
8209
+ const destPath = node_path.join(destDir, entry.name);
8210
+ if (entry.isDirectory()) copyWithPathReplacement(srcPath, destPath, pathPrefix, explicitConfigDir, isCommand);
8211
+ else if (entry.name.endsWith(".md")) {
8212
+ let content = node_fs.readFileSync(srcPath, "utf8");
8213
+ const globalClaudeRegex = /~\/\.claude\//g;
8214
+ const localClaudeRegex = /\.\/\.claude\//g;
8215
+ content = content.replace(globalClaudeRegex, pathPrefix);
8216
+ content = content.replace(localClaudeRegex, "./.claude/");
8217
+ content = processAttribution(content, getCommitAttribution(explicitConfigDir));
8218
+ node_fs.writeFileSync(destPath, content);
8219
+ } else node_fs.copyFileSync(srcPath, destPath);
8220
+ }
8221
+ }
8222
+
8223
+ //#endregion
8224
+ //#region src/install/uninstall.ts
8225
+ /**
8226
+ * Uninstall MAXSIM from the specified directory
8227
+ */
8228
+ function uninstall(isGlobal, explicitConfigDir = null) {
8229
+ const dirName = getDirName();
8230
+ const targetDir = isGlobal ? getGlobalDir(explicitConfigDir) : node_path.join(process.cwd(), dirName);
8231
+ const locationLabel = isGlobal ? targetDir.replace(node_os.homedir(), "~") : targetDir.replace(process.cwd(), ".");
8232
+ console.log(` Uninstalling MAXSIM from ${chalk.cyan("Claude Code")} at ${chalk.cyan(locationLabel)}\n`);
8233
+ if (!node_fs.existsSync(targetDir)) {
8234
+ console.log(` ${chalk.yellow("⚠")} Directory does not exist: ${locationLabel}`);
8235
+ console.log(` Nothing to uninstall.\n`);
8236
+ return;
8237
+ }
8238
+ let removedCount = 0;
8239
+ const maxsimCommandsDir = node_path.join(targetDir, "commands", "maxsim");
8240
+ if (node_fs.existsSync(maxsimCommandsDir)) {
8241
+ node_fs.rmSync(maxsimCommandsDir, { recursive: true });
8242
+ removedCount++;
8243
+ console.log(` ${chalk.green("✓")} Removed commands/maxsim/`);
8244
+ }
8245
+ {
8246
+ const skillsDir = node_path.join(targetDir, "skills");
8247
+ if (node_fs.existsSync(skillsDir)) {
8248
+ let skillCount = 0;
8249
+ for (const skill of builtInSkills) {
8250
+ const skillDir = node_path.join(skillsDir, skill);
8251
+ if (node_fs.existsSync(skillDir)) {
8252
+ node_fs.rmSync(skillDir, { recursive: true });
8253
+ skillCount++;
8254
+ }
8255
+ }
8256
+ if (skillCount > 0) {
8257
+ removedCount++;
8258
+ console.log(` ${chalk.green("✓")} Removed ${skillCount} MAXSIM skills`);
8259
+ }
8260
+ }
8261
+ const legacySkillsDir = node_path.join(targetDir, "agents", "skills");
8262
+ if (node_fs.existsSync(legacySkillsDir)) {
8263
+ node_fs.rmSync(legacySkillsDir, { recursive: true });
8264
+ console.log(` ${chalk.green("✓")} Removed legacy agents/skills/ directory`);
8265
+ }
8266
+ }
8267
+ const maxsimDir = node_path.join(targetDir, "maxsim");
8268
+ if (node_fs.existsSync(maxsimDir)) {
8269
+ node_fs.rmSync(maxsimDir, { recursive: true });
8270
+ removedCount++;
8271
+ console.log(` ${chalk.green("✓")} Removed maxsim/`);
8272
+ }
8273
+ const agentsDir = node_path.join(targetDir, "agents");
8274
+ if (node_fs.existsSync(agentsDir)) {
8275
+ const files = node_fs.readdirSync(agentsDir);
8276
+ let agentCount = 0;
8277
+ for (const file of files) if (file.startsWith("maxsim-") && file.endsWith(".md")) {
8278
+ node_fs.unlinkSync(node_path.join(agentsDir, file));
8279
+ agentCount++;
8280
+ }
8281
+ if (agentCount > 0) {
8282
+ removedCount++;
8283
+ console.log(` ${chalk.green("✓")} Removed ${agentCount} MAXSIM agents`);
8284
+ }
8285
+ }
8286
+ const hooksDir = node_path.join(targetDir, "hooks");
8287
+ if (node_fs.existsSync(hooksDir)) {
8288
+ const maxsimHooks = [
8289
+ "maxsim-statusline.js",
8290
+ "maxsim-check-update.js",
8291
+ "maxsim-check-update.sh",
8292
+ "maxsim-context-monitor.js"
8293
+ ];
8294
+ let hookCount = 0;
8295
+ for (const hook of maxsimHooks) {
8296
+ const hookPath = node_path.join(hooksDir, hook);
8297
+ if (node_fs.existsSync(hookPath)) {
8298
+ node_fs.unlinkSync(hookPath);
8299
+ hookCount++;
8300
+ }
8301
+ }
8302
+ if (hookCount > 0) {
8303
+ removedCount++;
8304
+ console.log(` ${chalk.green("✓")} Removed ${hookCount} MAXSIM hooks`);
8305
+ }
8992
8306
  }
8993
- return modified;
8994
- }
8995
- /**
8996
- * After install, report backed-up patches for user to reapply.
8997
- */
8998
- function reportLocalPatches(configDir, runtime = "claude") {
8999
- const patchesDir = node_path.join(configDir, PATCHES_DIR_NAME);
9000
- const metaPath = node_path.join(patchesDir, "backup-meta.json");
9001
- if (!node_fs.existsSync(metaPath)) return [];
9002
- let meta;
9003
- try {
9004
- meta = JSON.parse(node_fs.readFileSync(metaPath, "utf8"));
9005
- } catch {
9006
- return [];
8307
+ const pkgJsonPath = node_path.join(targetDir, "package.json");
8308
+ if (node_fs.existsSync(pkgJsonPath)) try {
8309
+ if (node_fs.readFileSync(pkgJsonPath, "utf8").trim() === "{\"type\":\"commonjs\"}") {
8310
+ node_fs.unlinkSync(pkgJsonPath);
8311
+ removedCount++;
8312
+ console.log(` ${chalk.green("✓")} Removed MAXSIM package.json`);
8313
+ }
8314
+ } catch {}
8315
+ const settingsPath = node_path.join(targetDir, "settings.json");
8316
+ if (node_fs.existsSync(settingsPath)) {
8317
+ const settings = readSettings(settingsPath);
8318
+ let settingsModified = false;
8319
+ const statusLine = settings.statusLine;
8320
+ if (statusLine && statusLine.command && statusLine.command.includes("maxsim-statusline")) {
8321
+ delete settings.statusLine;
8322
+ settingsModified = true;
8323
+ console.log(` ${chalk.green("✓")} Removed MAXSIM statusline from settings`);
8324
+ }
8325
+ const settingsHooks = settings.hooks;
8326
+ if (settingsHooks && settingsHooks.SessionStart) {
8327
+ const before = settingsHooks.SessionStart.length;
8328
+ settingsHooks.SessionStart = settingsHooks.SessionStart.filter((entry) => {
8329
+ if (entry.hooks && Array.isArray(entry.hooks)) return !entry.hooks.some((h) => h.command && (h.command.includes("maxsim-check-update") || h.command.includes("maxsim-statusline")));
8330
+ return true;
8331
+ });
8332
+ if (settingsHooks.SessionStart.length < before) {
8333
+ settingsModified = true;
8334
+ console.log(` ${chalk.green("✓")} Removed MAXSIM hooks from settings`);
8335
+ }
8336
+ if (settingsHooks.SessionStart.length === 0) delete settingsHooks.SessionStart;
8337
+ }
8338
+ if (settingsHooks && settingsHooks.PostToolUse) {
8339
+ const before = settingsHooks.PostToolUse.length;
8340
+ settingsHooks.PostToolUse = settingsHooks.PostToolUse.filter((entry) => {
8341
+ if (entry.hooks && Array.isArray(entry.hooks)) return !entry.hooks.some((h) => h.command && h.command.includes("maxsim-context-monitor"));
8342
+ return true;
8343
+ });
8344
+ if (settingsHooks.PostToolUse.length < before) {
8345
+ settingsModified = true;
8346
+ console.log(` ${chalk.green("✓")} Removed context monitor hook from settings`);
8347
+ }
8348
+ if (settingsHooks.PostToolUse.length === 0) delete settingsHooks.PostToolUse;
8349
+ }
8350
+ if (settingsHooks && Object.keys(settingsHooks).length === 0) delete settings.hooks;
8351
+ if (settingsModified) {
8352
+ writeSettings(settingsPath, settings);
8353
+ removedCount++;
8354
+ }
9007
8355
  }
9008
- if (meta.files && meta.files.length > 0) {
9009
- const reapplyCommand = runtime === "opencode" ? "/maxsim-reapply-patches" : runtime === "codex" ? "$maxsim-reapply-patches" : "/maxsim:reapply-patches";
9010
- console.log("");
9011
- console.log(" " + chalk.yellow("Local patches detected") + " (from v" + meta.from_version + "):");
9012
- for (const f of meta.files) console.log(" " + chalk.cyan(f));
9013
- console.log("");
9014
- console.log(" Your modifications are saved in " + chalk.cyan(PATCHES_DIR_NAME + "/"));
9015
- console.log(" Run " + chalk.cyan(reapplyCommand) + " to merge them into the new version.");
9016
- console.log(" Or manually compare and merge the files.");
9017
- console.log("");
8356
+ if (removedCount === 0) console.log(` ${chalk.yellow("⚠")} No MAXSIM files found to remove.`);
8357
+ console.log(`
8358
+ ${chalk.green("Done!")} MAXSIM has been uninstalled from Claude Code.
8359
+ Your other files and settings have been preserved.
8360
+ `);
8361
+ }
8362
+
8363
+ //#endregion
8364
+ //#region src/install/index.ts
8365
+ const argv = (0, import_minimist.default)(process.argv.slice(2), {
8366
+ boolean: [
8367
+ "global",
8368
+ "local",
8369
+ "claude",
8370
+ "uninstall",
8371
+ "help",
8372
+ "version",
8373
+ "force-statusline",
8374
+ "network"
8375
+ ],
8376
+ string: ["config-dir"],
8377
+ alias: {
8378
+ g: "global",
8379
+ l: "local",
8380
+ u: "uninstall",
8381
+ h: "help",
8382
+ c: "config-dir"
9018
8383
  }
9019
- return meta.files || [];
8384
+ });
8385
+ const hasGlobal = !!argv["global"];
8386
+ const hasLocal = !!argv["local"];
8387
+ const hasUninstall = !!argv["uninstall"];
8388
+ const banner = "\n" + chalk.cyan(figlet.default.textSync("MAXSIM", { font: "ANSI Shadow" }).split("\n").map((line) => " " + line).join("\n")) + "\n\n MAXSIM " + chalk.dim("v" + pkg.version) + "\n A meta-prompting, context engineering and spec-driven\n development system for Claude Code.\n";
8389
+ const explicitConfigDir = argv["config-dir"] || null;
8390
+ const hasHelp = !!argv["help"];
8391
+ const hasVersion = !!argv["version"];
8392
+ const forceStatusline = !!argv["force-statusline"];
8393
+ for (const flag of [
8394
+ "opencode",
8395
+ "gemini",
8396
+ "codex",
8397
+ "both",
8398
+ "all"
8399
+ ]) if (argv[flag]) {
8400
+ console.error(`Error: The --${flag} flag is no longer supported. MAXSIM v2.0 is Claude Code only.`);
8401
+ process.exit(1);
8402
+ }
8403
+ if (hasVersion) {
8404
+ console.log(pkg.version);
8405
+ process.exit(0);
8406
+ }
8407
+ console.log(banner);
8408
+ if (hasHelp) {
8409
+ console.log(` ${chalk.yellow("Usage:")} npx maxsimcli [options]\n\n ${chalk.yellow("Options:")}\n ${chalk.cyan("-g, --global")} Install globally (to config directory)\n ${chalk.cyan("-l, --local")} Install locally (to current directory)\n ${chalk.cyan("-u, --uninstall")} Uninstall MAXSIM (remove all MAXSIM files)\n ${chalk.cyan("-c, --config-dir <path>")} Specify custom config directory\n ${chalk.cyan("-h, --help")} Show this help message\n ${chalk.cyan("--force-statusline")} Replace existing statusline config\n\n ${chalk.yellow("Examples:")}\n ${chalk.dim("# Interactive install (prompts for location)")}\n npx maxsimcli\n\n ${chalk.dim("# Install globally")}\n npx maxsimcli --global\n\n ${chalk.dim("# Install to current project only")}\n npx maxsimcli --local\n\n ${chalk.dim("# Install to custom config directory")}\n npx maxsimcli --global --config-dir ~/.claude-work\n\n ${chalk.dim("# Uninstall MAXSIM globally")}\n npx maxsimcli --global --uninstall\n\n ${chalk.yellow("Notes:")}\n The --config-dir option is useful when you have multiple configurations.\n It takes priority over the CLAUDE_CONFIG_DIR environment variable.\n`);
8410
+ process.exit(0);
9020
8411
  }
9021
- async function install(isGlobal, runtime = "claude") {
9022
- const isOpencode = runtime === "opencode";
9023
- const isGemini = runtime === "gemini";
9024
- const isCodex = runtime === "codex";
9025
- const dirName = getDirName(runtime);
8412
+ async function install(isGlobal) {
8413
+ const runtime = "claude";
8414
+ const dirName = getDirName();
9026
8415
  const src = templatesRoot;
9027
- const targetDir = isGlobal ? getGlobalDir(runtime, explicitConfigDir) : node_path.join(process.cwd(), dirName);
8416
+ const targetDir = isGlobal ? getGlobalDir(explicitConfigDir) : node_path.join(process.cwd(), dirName);
9028
8417
  const locationLabel = isGlobal ? targetDir.replace(node_os.homedir(), "~") : targetDir.replace(process.cwd(), ".");
9029
8418
  const pathPrefix = isGlobal ? `${targetDir.replace(/\\/g, "/")}/` : `./${dirName}/`;
9030
- let runtimeLabel = "Claude Code";
9031
- if (isOpencode) runtimeLabel = "OpenCode";
9032
- if (isGemini) runtimeLabel = "Gemini";
9033
- if (isCodex) runtimeLabel = "Codex";
9034
- console.log(` Installing for ${chalk.cyan(runtimeLabel)} to ${chalk.cyan(locationLabel)}\n`);
8419
+ console.log(` Installing for ${chalk.cyan("Claude Code")} to ${chalk.cyan(locationLabel)}\n`);
9035
8420
  const failures = [];
8421
+ const existingManifest = readManifest(targetDir);
8422
+ const isAlreadyCurrent = existingManifest !== null && existingManifest.version === pkg.version;
8423
+ if (existingManifest !== null) {
8424
+ const { complete, missing } = verifyInstallComplete(targetDir, runtime, existingManifest);
8425
+ if (!complete) console.log(` ${chalk.yellow("!")} Previous install (v${existingManifest.version}) is incomplete — ${missing.length} missing file(s). Re-installing.`);
8426
+ else if (isAlreadyCurrent) console.log(` ${chalk.dim(`Version ${pkg.version} already installed — upgrading in place`)}`);
8427
+ }
9036
8428
  saveLocalPatches(targetDir);
9037
8429
  cleanupOrphanedFiles(targetDir);
9038
8430
  let spinner = ora({
9039
8431
  text: "Installing commands...",
9040
8432
  color: "cyan"
9041
8433
  }).start();
9042
- if (isOpencode) {
9043
- const commandDir = node_path.join(targetDir, "command");
9044
- node_fs.mkdirSync(commandDir, { recursive: true });
9045
- copyFlattenedCommands(node_path.join(src, "commands", "maxsim"), commandDir, "maxsim", pathPrefix, runtime);
9046
- if (verifyInstalled(commandDir, "command/maxsim-*")) {
9047
- const count = node_fs.readdirSync(commandDir).filter((f) => f.startsWith("maxsim-")).length;
9048
- spinner.succeed(chalk.green("✓") + ` Installed ${count} commands to command/`);
9049
- } else {
9050
- spinner.fail("Failed to install commands");
9051
- failures.push("command/maxsim-*");
9052
- }
9053
- } else if (isCodex) {
9054
- const skillsDir = node_path.join(targetDir, "skills");
9055
- copyCommandsAsCodexSkills(node_path.join(src, "commands", "maxsim"), skillsDir, "maxsim", pathPrefix, runtime);
9056
- const installedSkillNames = listCodexSkillNames(skillsDir);
9057
- if (installedSkillNames.length > 0) spinner.succeed(chalk.green("✓") + ` Installed ${installedSkillNames.length} skills to skills/`);
9058
- else {
9059
- spinner.fail("Failed to install skills");
9060
- failures.push("skills/maxsim-*");
9061
- }
9062
- } else {
9063
- const commandsDir = node_path.join(targetDir, "commands");
9064
- node_fs.mkdirSync(commandsDir, { recursive: true });
9065
- const maxsimSrc = node_path.join(src, "commands", "maxsim");
9066
- const maxsimDest = node_path.join(commandsDir, "maxsim");
9067
- copyWithPathReplacement(maxsimSrc, maxsimDest, pathPrefix, runtime, true);
9068
- if (verifyInstalled(maxsimDest, "commands/maxsim")) spinner.succeed(chalk.green("✓") + " Installed commands/maxsim");
9069
- else {
9070
- spinner.fail("Failed to install commands/maxsim");
9071
- failures.push("commands/maxsim");
9072
- }
8434
+ const commandsDir = node_path.join(targetDir, "commands");
8435
+ node_fs.mkdirSync(commandsDir, { recursive: true });
8436
+ const maxsimSrc = node_path.join(src, "commands", "maxsim");
8437
+ const maxsimDest = node_path.join(commandsDir, "maxsim");
8438
+ copyWithPathReplacement(maxsimSrc, maxsimDest, pathPrefix, explicitConfigDir, true);
8439
+ if (verifyInstalled(maxsimDest, "commands/maxsim")) spinner.succeed(chalk.green("✓") + " Installed commands/maxsim");
8440
+ else {
8441
+ spinner.fail("Failed to install commands/maxsim");
8442
+ failures.push("commands/maxsim");
9073
8443
  }
9074
8444
  spinner = ora({
9075
8445
  text: "Installing workflows and templates...",
@@ -9085,7 +8455,7 @@ async function install(isGlobal, runtime = "claude") {
9085
8455
  node_fs.mkdirSync(skillDest, { recursive: true });
9086
8456
  for (const subdir of maxsimSubdirs) {
9087
8457
  const subdirSrc = node_path.join(src, subdir);
9088
- if (node_fs.existsSync(subdirSrc)) copyWithPathReplacement(subdirSrc, node_path.join(skillDest, subdir), pathPrefix, runtime);
8458
+ if (node_fs.existsSync(subdirSrc)) copyWithPathReplacement(subdirSrc, node_path.join(skillDest, subdir), pathPrefix, explicitConfigDir);
9089
8459
  }
9090
8460
  if (verifyInstalled(skillDest, "maxsim")) spinner.succeed(chalk.green("✓") + " Installed maxsim");
9091
8461
  else {
@@ -9107,10 +8477,7 @@ async function install(isGlobal, runtime = "claude") {
9107
8477
  for (const entry of agentEntries) if (entry.isFile() && entry.name.endsWith(".md")) {
9108
8478
  let content = node_fs.readFileSync(node_path.join(agentsSrc, entry.name), "utf8");
9109
8479
  content = content.replace(/~\/\.claude\//g, pathPrefix);
9110
- content = processAttribution(content, getCommitAttribution(runtime));
9111
- if (isOpencode) content = convertClaudeToOpencodeFrontmatter(content);
9112
- else if (isGemini) content = convertClaudeToGeminiAgent(content);
9113
- else if (isCodex) content = convertClaudeToCodexMarkdown(content);
8480
+ content = processAttribution(content, getCommitAttribution(explicitConfigDir));
9114
8481
  node_fs.writeFileSync(node_path.join(agentsDest, entry.name), content);
9115
8482
  }
9116
8483
  if (verifyInstalled(agentsDest, "agents")) spinner.succeed(chalk.green("✓") + " Installed agents");
@@ -9119,18 +8486,19 @@ async function install(isGlobal, runtime = "claude") {
9119
8486
  failures.push("agents");
9120
8487
  }
9121
8488
  }
8489
+ const legacySkillsDir = node_path.join(targetDir, "agents", "skills");
8490
+ if (node_fs.existsSync(legacySkillsDir)) {
8491
+ node_fs.rmSync(legacySkillsDir, { recursive: true });
8492
+ console.log(` ${chalk.green("✓")} Removed legacy agents/skills/ directory`);
8493
+ }
9122
8494
  const skillsSrc = node_path.join(src, "skills");
9123
8495
  if (node_fs.existsSync(skillsSrc)) {
9124
8496
  spinner = ora({
9125
8497
  text: "Installing skills...",
9126
8498
  color: "cyan"
9127
8499
  }).start();
9128
- const skillsDest = node_path.join(targetDir, "agents", "skills");
9129
- if (node_fs.existsSync(skillsDest)) for (const skill of [
9130
- "tdd",
9131
- "systematic-debugging",
9132
- "verification-before-completion"
9133
- ]) {
8500
+ const skillsDest = node_path.join(targetDir, "skills");
8501
+ if (node_fs.existsSync(skillsDest)) for (const skill of builtInSkills) {
9134
8502
  const skillDir = node_path.join(skillsDest, skill);
9135
8503
  if (node_fs.existsSync(skillDir)) node_fs.rmSync(skillDir, { recursive: true });
9136
8504
  }
@@ -9141,15 +8509,15 @@ async function install(isGlobal, runtime = "claude") {
9141
8509
  if (node_fs.existsSync(skillMd)) {
9142
8510
  let content = node_fs.readFileSync(skillMd, "utf8");
9143
8511
  content = content.replace(/~\/\.claude\//g, pathPrefix);
9144
- content = processAttribution(content, getCommitAttribution(runtime));
8512
+ content = processAttribution(content, getCommitAttribution(explicitConfigDir));
9145
8513
  node_fs.writeFileSync(skillMd, content);
9146
8514
  }
9147
8515
  }
9148
8516
  const installedSkillDirs = node_fs.readdirSync(skillsDest, { withFileTypes: true }).filter((e) => e.isDirectory()).length;
9149
- if (installedSkillDirs > 0) spinner.succeed(chalk.green("✓") + ` Installed ${installedSkillDirs} skills to agents/skills/`);
8517
+ if (installedSkillDirs > 0) spinner.succeed(chalk.green("✓") + ` Installed ${installedSkillDirs} skills to skills/`);
9150
8518
  else {
9151
8519
  spinner.fail("Failed to install skills");
9152
- failures.push("agents/skills");
8520
+ failures.push("skills");
9153
8521
  }
9154
8522
  }
9155
8523
  const changelogSrc = node_path.join(src, "..", "CHANGELOG.md");
@@ -9184,58 +8552,28 @@ async function install(isGlobal, runtime = "claude") {
9184
8552
  node_fs.writeFileSync(versionDest, pkg.version);
9185
8553
  if (verifyFileInstalled(versionDest, "VERSION")) console.log(` ${chalk.green("✓")} Wrote VERSION (${pkg.version})`);
9186
8554
  else failures.push("VERSION");
9187
- if (!isCodex) {
9188
- const pkgJsonDest = node_path.join(targetDir, "package.json");
9189
- node_fs.writeFileSync(pkgJsonDest, "{\"type\":\"commonjs\"}\n");
9190
- console.log(` ${chalk.green("✓")} Wrote package.json (CommonJS mode)`);
9191
- const toolSrc = node_path.resolve(__dirname, "cli.cjs");
9192
- const binDir = node_path.join(targetDir, "maxsim", "bin");
9193
- const toolDest = node_path.join(binDir, "maxsim-tools.cjs");
9194
- if (node_fs.existsSync(toolSrc)) {
9195
- node_fs.mkdirSync(binDir, { recursive: true });
9196
- node_fs.copyFileSync(toolSrc, toolDest);
9197
- console.log(` ${chalk.green("✓")} Installed maxsim-tools.cjs`);
9198
- } else {
9199
- console.warn(` ${chalk.yellow("!")} cli.cjs not found at ${toolSrc} — maxsim-tools.cjs not installed`);
9200
- failures.push("maxsim-tools.cjs");
9201
- }
9202
- const mcpSrc = node_path.resolve(__dirname, "mcp-server.cjs");
9203
- const mcpDest = node_path.join(binDir, "mcp-server.cjs");
9204
- if (node_fs.existsSync(mcpSrc)) {
9205
- node_fs.mkdirSync(binDir, { recursive: true });
9206
- node_fs.copyFileSync(mcpSrc, mcpDest);
9207
- console.log(` ${chalk.green("")} Installed mcp-server.cjs`);
9208
- } else console.warn(` ${chalk.yellow("!")} mcp-server.cjs not found — MCP server not installed`);
9209
- let hooksSrc = null;
9210
- const bundledHooksDir = node_path.resolve(__dirname, "assets", "hooks");
9211
- if (node_fs.existsSync(bundledHooksDir)) hooksSrc = bundledHooksDir;
9212
- else console.warn(` ${chalk.yellow("!")} bundled hooks not found - hooks will not be installed`);
9213
- if (hooksSrc) {
9214
- spinner = ora({
9215
- text: "Installing hooks...",
9216
- color: "cyan"
9217
- }).start();
9218
- const hooksDest = node_path.join(targetDir, "hooks");
9219
- node_fs.mkdirSync(hooksDest, { recursive: true });
9220
- const hookEntries = node_fs.readdirSync(hooksSrc);
9221
- const configDirReplacement = getConfigDirFromHome(runtime, isGlobal);
9222
- for (const entry of hookEntries) {
9223
- const srcFile = node_path.join(hooksSrc, entry);
9224
- if (node_fs.statSync(srcFile).isFile() && entry.endsWith(".cjs") && !entry.includes(".d.")) {
9225
- const destName = entry.replace(/\.cjs$/, ".js");
9226
- const destFile = node_path.join(hooksDest, destName);
9227
- let content = node_fs.readFileSync(srcFile, "utf8");
9228
- content = content.replace(/'\.claude'/g, configDirReplacement);
9229
- node_fs.writeFileSync(destFile, content);
9230
- }
9231
- }
9232
- if (verifyInstalled(hooksDest, "hooks")) spinner.succeed(chalk.green("✓") + " Installed hooks (bundled)");
9233
- else {
9234
- spinner.fail("Failed to install hooks");
9235
- failures.push("hooks");
9236
- }
9237
- }
9238
- }
8555
+ const pkgJsonDest = node_path.join(targetDir, "package.json");
8556
+ node_fs.writeFileSync(pkgJsonDest, "{\"type\":\"commonjs\"}\n");
8557
+ console.log(` ${chalk.green("")} Wrote package.json (CommonJS mode)`);
8558
+ const toolSrc = node_path.resolve(__dirname, "cli.cjs");
8559
+ const binDir = node_path.join(targetDir, "maxsim", "bin");
8560
+ const toolDest = node_path.join(binDir, "maxsim-tools.cjs");
8561
+ if (node_fs.existsSync(toolSrc)) {
8562
+ node_fs.mkdirSync(binDir, { recursive: true });
8563
+ node_fs.copyFileSync(toolSrc, toolDest);
8564
+ console.log(` ${chalk.green("✓")} Installed maxsim-tools.cjs`);
8565
+ } else {
8566
+ console.warn(` ${chalk.yellow("!")} cli.cjs not found at ${toolSrc} — maxsim-tools.cjs not installed`);
8567
+ failures.push("maxsim-tools.cjs");
8568
+ }
8569
+ const mcpSrc = node_path.resolve(__dirname, "mcp-server.cjs");
8570
+ const mcpDest = node_path.join(binDir, "mcp-server.cjs");
8571
+ if (node_fs.existsSync(mcpSrc)) {
8572
+ node_fs.mkdirSync(binDir, { recursive: true });
8573
+ node_fs.copyFileSync(mcpSrc, mcpDest);
8574
+ console.log(` ${chalk.green("✓")} Installed mcp-server.cjs`);
8575
+ } else console.warn(` ${chalk.yellow("!")} mcp-server.cjs not found — MCP server not installed`);
8576
+ installHookFiles(targetDir, isGlobal, failures);
9239
8577
  const dashboardSrc = node_path.resolve(__dirname, "assets", "dashboard");
9240
8578
  if (node_fs.existsSync(dashboardSrc)) {
9241
8579
  let networkMode = false;
@@ -9262,12 +8600,29 @@ async function install(isGlobal, runtime = "claude") {
9262
8600
  else spinner.succeed(chalk.green("✓") + " Installed dashboard (server.js not found in bundle)");
9263
8601
  if (networkMode) applyFirewallRule(3333);
9264
8602
  }
9265
- if (!isOpencode && !isCodex && !isGemini) {
9266
- const mcpJsonPath = isGlobal ? node_path.join(targetDir, "..", ".mcp.json") : node_path.join(process.cwd(), ".mcp.json");
9267
- let mcpConfig = {};
9268
- if (node_fs.existsSync(mcpJsonPath)) try {
8603
+ const mcpJsonPath = isGlobal ? node_path.join(targetDir, "..", ".mcp.json") : node_path.join(process.cwd(), ".mcp.json");
8604
+ let mcpConfig = {};
8605
+ let skipMcpConfig = false;
8606
+ if (node_fs.existsSync(mcpJsonPath)) {
8607
+ node_fs.copyFileSync(mcpJsonPath, mcpJsonPath + ".bak");
8608
+ try {
9269
8609
  mcpConfig = JSON.parse(node_fs.readFileSync(mcpJsonPath, "utf-8"));
9270
- } catch {}
8610
+ } catch {
8611
+ console.warn(` ${chalk.yellow("!")} .mcp.json is corrupted (invalid JSON). Backup saved to .mcp.json.bak`);
8612
+ let startFresh = true;
8613
+ try {
8614
+ startFresh = await dist_default$1({
8615
+ message: ".mcp.json is corrupted. Start with a fresh config? (No = abort MCP setup)",
8616
+ default: true
8617
+ });
8618
+ } catch {}
8619
+ if (!startFresh) {
8620
+ console.log(` ${chalk.yellow("!")} Skipping .mcp.json configuration`);
8621
+ skipMcpConfig = true;
8622
+ }
8623
+ }
8624
+ }
8625
+ if (!skipMcpConfig) {
9271
8626
  const mcpServers = mcpConfig.mcpServers ?? {};
9272
8627
  mcpServers["maxsim"] = {
9273
8628
  command: "node",
@@ -9282,48 +8637,10 @@ async function install(isGlobal, runtime = "claude") {
9282
8637
  console.error(`\n ${chalk.yellow("Installation incomplete!")} Failed: ${failures.join(", ")}`);
9283
8638
  process.exit(1);
9284
8639
  }
9285
- writeManifest(targetDir, runtime);
8640
+ writeManifest(targetDir);
9286
8641
  console.log(` ${chalk.green("✓")} Wrote file manifest (${MANIFEST_NAME})`);
9287
- reportLocalPatches(targetDir, runtime);
9288
- if (isCodex) return {
9289
- settingsPath: null,
9290
- settings: null,
9291
- statuslineCommand: null,
9292
- runtime
9293
- };
9294
- const settingsPath = node_path.join(targetDir, "settings.json");
9295
- const settings = cleanupOrphanedHooks(readSettings(settingsPath));
9296
- const statuslineCommand = isGlobal ? buildHookCommand(targetDir, "maxsim-statusline.js") : "node " + dirName + "/hooks/maxsim-statusline.js";
9297
- const updateCheckCommand = isGlobal ? buildHookCommand(targetDir, "maxsim-check-update.js") : "node " + dirName + "/hooks/maxsim-check-update.js";
9298
- const contextMonitorCommand = isGlobal ? buildHookCommand(targetDir, "maxsim-context-monitor.js") : "node " + dirName + "/hooks/maxsim-context-monitor.js";
9299
- if (isGemini) {
9300
- if (!settings.experimental) settings.experimental = {};
9301
- const experimental = settings.experimental;
9302
- if (!experimental.enableAgents) {
9303
- experimental.enableAgents = true;
9304
- console.log(` ${chalk.green("✓")} Enabled experimental agents`);
9305
- }
9306
- }
9307
- if (!isOpencode) {
9308
- if (!settings.hooks) settings.hooks = {};
9309
- const installHooks = settings.hooks;
9310
- if (!installHooks.SessionStart) installHooks.SessionStart = [];
9311
- if (!installHooks.SessionStart.some((entry) => entry.hooks && entry.hooks.some((h) => h.command && h.command.includes("maxsim-check-update")))) {
9312
- installHooks.SessionStart.push({ hooks: [{
9313
- type: "command",
9314
- command: updateCheckCommand
9315
- }] });
9316
- console.log(` ${chalk.green("✓")} Configured update check hook`);
9317
- }
9318
- if (!installHooks.PostToolUse) installHooks.PostToolUse = [];
9319
- if (!installHooks.PostToolUse.some((entry) => entry.hooks && entry.hooks.some((h) => h.command && h.command.includes("maxsim-context-monitor")))) {
9320
- installHooks.PostToolUse.push({ hooks: [{
9321
- type: "command",
9322
- command: contextMonitorCommand
9323
- }] });
9324
- console.log(` ${chalk.green("✓")} Configured context window monitor hook`);
9325
- }
9326
- }
8642
+ reportLocalPatches(targetDir);
8643
+ const { settingsPath, settings, statuslineCommand } = configureSettingsHooks(targetDir, isGlobal);
9327
8644
  return {
9328
8645
  settingsPath,
9329
8646
  settings,
@@ -9332,113 +8649,27 @@ async function install(isGlobal, runtime = "claude") {
9332
8649
  };
9333
8650
  }
9334
8651
  /**
9335
- * Apply statusline config, then print completion message
9336
- */
9337
- function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallStatusline, runtime = "claude", isGlobal = true) {
9338
- const isOpencode = runtime === "opencode";
9339
- const isCodex = runtime === "codex";
9340
- if (shouldInstallStatusline && !isOpencode && !isCodex) {
9341
- settings.statusLine = {
9342
- type: "command",
9343
- command: statuslineCommand
9344
- };
9345
- console.log(` ${chalk.green("✓")} Configured statusline`);
9346
- }
9347
- if (!isCodex && settingsPath && settings) writeSettings(settingsPath, settings);
9348
- if (isOpencode) configureOpencodePermissions(isGlobal);
9349
- let program = "Claude Code";
9350
- if (runtime === "opencode") program = "OpenCode";
9351
- if (runtime === "gemini") program = "Gemini";
9352
- if (runtime === "codex") program = "Codex";
9353
- let command = "/maxsim:help";
9354
- if (runtime === "opencode") command = "/maxsim-help";
9355
- if (runtime === "codex") command = "$maxsim-help";
9356
- console.log(`
9357
- ${chalk.green("Done!")} Launch ${program} and run ${chalk.cyan(command)}.
9358
-
9359
- ${chalk.cyan("Join the community:")} https://discord.gg/5JJgD5svVS
9360
- `);
9361
- }
9362
- /**
9363
- * Handle statusline configuration — returns true if MAXSIM statusline should be installed
9364
- */
9365
- async function handleStatusline(settings, isInteractive) {
9366
- if (!(settings.statusLine != null)) return true;
9367
- if (forceStatusline) return true;
9368
- if (!isInteractive) {
9369
- console.log(chalk.yellow("⚠") + " Skipping statusline (already configured)");
9370
- console.log(" Use " + chalk.cyan("--force-statusline") + " to replace\n");
9371
- return false;
9372
- }
9373
- const statusLine = settings.statusLine;
9374
- const existingCmd = statusLine.command || statusLine.url || "(custom)";
9375
- console.log();
9376
- console.log(chalk.yellow("⚠ Existing statusline detected"));
9377
- console.log();
9378
- console.log(" Your current statusline:");
9379
- console.log(" " + chalk.dim(`command: ${existingCmd}`));
9380
- console.log();
9381
- console.log(" MAXSIM includes a statusline showing:");
9382
- console.log(" • Model name");
9383
- console.log(" • Current task (from todo list)");
9384
- console.log(" • Context window usage (color-coded)");
9385
- console.log();
9386
- return await dist_default$1({
9387
- message: "Replace with MAXSIM statusline?",
9388
- default: false
9389
- });
9390
- }
9391
- /**
9392
- * Prompt for runtime selection (multi-select)
9393
- */
9394
- async function promptRuntime() {
9395
- return await dist_default$2({
9396
- message: "Which runtime(s) would you like to install for?",
9397
- choices: [
9398
- {
9399
- name: "Claude Code " + chalk.dim("(~/.claude)"),
9400
- value: "claude",
9401
- checked: true
9402
- },
9403
- {
9404
- name: "OpenCode " + chalk.dim("(~/.config/opencode)") + " — open source, free models",
9405
- value: "opencode"
9406
- },
9407
- {
9408
- name: "Gemini " + chalk.dim("(~/.gemini)"),
9409
- value: "gemini"
9410
- },
9411
- {
9412
- name: "Codex " + chalk.dim("(~/.codex)"),
9413
- value: "codex"
9414
- }
9415
- ],
9416
- validate: (choices) => choices.length > 0 || "Please select at least one runtime"
9417
- });
9418
- }
9419
- /**
9420
8652
  * Prompt for install location
9421
8653
  */
9422
- async function promptLocation(runtimes) {
8654
+ async function promptLocation() {
9423
8655
  if (!process.stdin.isTTY) {
9424
8656
  console.log(chalk.yellow("Non-interactive terminal detected, defaulting to global install") + "\n");
9425
8657
  return true;
9426
8658
  }
9427
- const pathExamples = runtimes.map((r) => getGlobalDir(r, explicitConfigDir).replace(node_os.homedir(), "~")).join(", ");
9428
- const localExamples = runtimes.map((r) => `./${getDirName(r)}`).join(", ");
8659
+ const globalPath = getGlobalDir(explicitConfigDir).replace(node_os.homedir(), "~");
9429
8660
  return await dist_default({
9430
8661
  message: "Where would you like to install?",
9431
8662
  choices: [{
9432
- name: "Global " + chalk.dim(`(${pathExamples})`) + " — available in all projects",
8663
+ name: "Global " + chalk.dim(`(${globalPath})`) + " — available in all projects",
9433
8664
  value: "global"
9434
8665
  }, {
9435
- name: "Local " + chalk.dim(`(${localExamples})`) + " — this project only",
8666
+ name: "Local " + chalk.dim("(./.claude)") + " — this project only",
9436
8667
  value: "local"
9437
8668
  }]
9438
8669
  }) === "global";
9439
8670
  }
9440
8671
  /**
9441
- * Prompt whether to enable Agent Teams (Claude only, experimental feature)
8672
+ * Prompt whether to enable Agent Teams (experimental feature)
9442
8673
  */
9443
8674
  async function promptAgentTeams() {
9444
8675
  console.log();
@@ -9452,127 +8683,47 @@ async function promptAgentTeams() {
9452
8683
  });
9453
8684
  }
9454
8685
  /**
9455
- * Install MAXSIM for all selected runtimes
8686
+ * Install MAXSIM for Claude Code
9456
8687
  */
9457
- async function installAllRuntimes(runtimes, isGlobal, isInteractive) {
9458
- const results = [];
9459
- for (const runtime of runtimes) {
9460
- const result = await install(isGlobal, runtime);
9461
- results.push(result);
9462
- }
9463
- const statuslineRuntimes = ["claude", "gemini"];
9464
- const primaryStatuslineResult = results.find((r) => statuslineRuntimes.includes(r.runtime));
8688
+ async function installForClaude(isGlobal, isInteractive) {
8689
+ const result = await install(isGlobal);
9465
8690
  let shouldInstallStatusline = false;
9466
- if (primaryStatuslineResult && primaryStatuslineResult.settings) shouldInstallStatusline = await handleStatusline(primaryStatuslineResult.settings, isInteractive);
8691
+ if (result.settings) shouldInstallStatusline = await handleStatusline(result.settings, isInteractive, forceStatusline);
9467
8692
  let enableAgentTeams = false;
9468
- if (isInteractive && runtimes.includes("claude")) enableAgentTeams = await promptAgentTeams();
9469
- for (const result of results) {
9470
- const useStatusline = statuslineRuntimes.includes(result.runtime) && shouldInstallStatusline;
9471
- if (result.runtime === "claude" && enableAgentTeams && result.settings) {
9472
- const env = result.settings.env ?? {};
9473
- env["CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS"] = "1";
9474
- result.settings.env = env;
9475
- }
9476
- finishInstall(result.settingsPath, result.settings, result.statuslineCommand, useStatusline, result.runtime, isGlobal);
8693
+ if (isInteractive) enableAgentTeams = await promptAgentTeams();
8694
+ if (enableAgentTeams && result.settings) {
8695
+ const env = result.settings.env ?? {};
8696
+ env["CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS"] = "1";
8697
+ result.settings.env = env;
9477
8698
  }
8699
+ finishInstall(result.settingsPath, result.settings, result.statuslineCommand, shouldInstallStatusline, isGlobal);
9478
8700
  }
9479
8701
  const subcommand = argv._[0];
9480
8702
  (async () => {
9481
8703
  if (subcommand === "dashboard") {
9482
- const { spawn: spawnDash, execSync: execSyncDash } = await import("node:child_process");
9483
- const dashboardAssetSrc = node_path.resolve(__dirname, "assets", "dashboard");
9484
- const installDir = node_path.join(process.cwd(), ".claude");
9485
- const installDashDir = node_path.join(installDir, "dashboard");
9486
- if (node_fs.existsSync(dashboardAssetSrc)) {
9487
- const nodeModulesDir = node_path.join(installDashDir, "node_modules");
9488
- const nodeModulesTmp = node_path.join(installDir, "_dashboard_node_modules_tmp");
9489
- const hadNodeModules = node_fs.existsSync(nodeModulesDir);
9490
- if (hadNodeModules) node_fs.renameSync(nodeModulesDir, nodeModulesTmp);
9491
- safeRmDir(installDashDir);
9492
- node_fs.mkdirSync(installDashDir, { recursive: true });
9493
- copyDirRecursive(dashboardAssetSrc, installDashDir);
9494
- if (hadNodeModules && node_fs.existsSync(nodeModulesTmp)) node_fs.renameSync(nodeModulesTmp, nodeModulesDir);
9495
- const dashConfigPath = node_path.join(installDir, "dashboard.json");
9496
- if (!node_fs.existsSync(dashConfigPath)) node_fs.writeFileSync(dashConfigPath, JSON.stringify({ projectCwd: process.cwd() }, null, 2) + "\n");
9497
- }
9498
- const localDashboard = node_path.join(process.cwd(), ".claude", "dashboard", "server.js");
9499
- const globalDashboard = node_path.join(node_os.homedir(), ".claude", "dashboard", "server.js");
9500
- let serverPath = null;
9501
- if (node_fs.existsSync(localDashboard)) serverPath = localDashboard;
9502
- else if (node_fs.existsSync(globalDashboard)) serverPath = globalDashboard;
9503
- if (!serverPath) {
9504
- console.log(chalk.yellow("\n Dashboard not available.\n"));
9505
- console.log(" Install MAXSIM first: " + chalk.cyan("npx maxsimcli@latest") + "\n");
9506
- process.exit(0);
9507
- }
9508
- const forceNetwork = !!argv["network"];
9509
- const dashboardDir = node_path.dirname(serverPath);
9510
- const dashboardConfigPath = node_path.join(node_path.dirname(dashboardDir), "dashboard.json");
9511
- let projectCwd = process.cwd();
9512
- let networkMode = forceNetwork;
9513
- if (node_fs.existsSync(dashboardConfigPath)) try {
9514
- const config = JSON.parse(node_fs.readFileSync(dashboardConfigPath, "utf8"));
9515
- if (config.projectCwd) projectCwd = config.projectCwd;
9516
- if (!forceNetwork) networkMode = config.networkMode ?? false;
9517
- } catch {}
9518
- const dashDirForPty = node_path.dirname(serverPath);
9519
- const ptyModulePath = node_path.join(dashDirForPty, "node_modules", "node-pty");
9520
- if (!node_fs.existsSync(ptyModulePath)) {
9521
- console.log(chalk.gray(" Installing node-pty for terminal support..."));
9522
- try {
9523
- const dashPkgPath = node_path.join(dashDirForPty, "package.json");
9524
- if (!node_fs.existsSync(dashPkgPath)) node_fs.writeFileSync(dashPkgPath, "{\"private\":true}\n");
9525
- execSyncDash("npm install node-pty --save-optional --no-audit --no-fund --loglevel=error", {
9526
- cwd: dashDirForPty,
9527
- stdio: "inherit",
9528
- timeout: 12e4
9529
- });
9530
- } catch {
9531
- console.warn(chalk.yellow(" node-pty installation failed — terminal will be unavailable."));
8704
+ await runDashboardSubcommand(argv);
8705
+ return;
8706
+ }
8707
+ if (subcommand === "skill-list" || subcommand === "skill-install" || subcommand === "skill-update") {
8708
+ const { cmdSkillList, cmdSkillInstall, cmdSkillUpdate } = await Promise.resolve().then(() => require("./skills-BOSxYUzf.cjs"));
8709
+ const { CliOutput, writeOutput, CliError } = await Promise.resolve().then(() => require("./core-TFSlUjV1.cjs"));
8710
+ const cwd = process.cwd();
8711
+ try {
8712
+ if (subcommand === "skill-list") cmdSkillList(cwd, false);
8713
+ else if (subcommand === "skill-install") cmdSkillInstall(cwd, argv._[1], false);
8714
+ else if (subcommand === "skill-update") cmdSkillUpdate(cwd, argv._[1], false);
8715
+ } catch (thrown) {
8716
+ if (thrown instanceof CliOutput) {
8717
+ writeOutput(thrown);
8718
+ process.exit(0);
9532
8719
  }
9533
- }
9534
- console.log(chalk.blue("Starting dashboard..."));
9535
- console.log(chalk.gray(` Project: ${projectCwd}`));
9536
- console.log(chalk.gray(` Server: ${serverPath}`));
9537
- if (networkMode) console.log(chalk.gray(" Network: enabled (local network access + QR code)"));
9538
- console.log("");
9539
- spawnDash(process.execPath, [serverPath], {
9540
- cwd: dashboardDir,
9541
- detached: true,
9542
- stdio: "ignore",
9543
- env: {
9544
- ...process.env,
9545
- MAXSIM_PROJECT_CWD: projectCwd,
9546
- MAXSIM_NETWORK_MODE: networkMode ? "1" : "0",
9547
- NODE_ENV: "production"
8720
+ if (thrown instanceof CliError) {
8721
+ console.error("Error: " + thrown.message);
8722
+ process.exit(1);
9548
8723
  }
9549
- }).unref();
9550
- const POLL_INTERVAL_MS = 500;
9551
- const POLL_TIMEOUT_MS = 2e4;
9552
- const HEALTH_TIMEOUT_MS = 1e3;
9553
- const DEFAULT_PORT = 3333;
9554
- const PORT_RANGE_END = 3343;
9555
- let foundUrl = null;
9556
- const deadline = Date.now() + POLL_TIMEOUT_MS;
9557
- while (Date.now() < deadline) {
9558
- await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
9559
- for (let p = DEFAULT_PORT; p <= PORT_RANGE_END; p++) try {
9560
- const controller = new AbortController();
9561
- const timer = setTimeout(() => controller.abort(), HEALTH_TIMEOUT_MS);
9562
- const res = await fetch(`http://localhost:${p}/api/health`, { signal: controller.signal });
9563
- clearTimeout(timer);
9564
- if (res.ok) {
9565
- if ((await res.json()).status === "ok") {
9566
- foundUrl = `http://localhost:${p}`;
9567
- break;
9568
- }
9569
- }
9570
- } catch {}
9571
- if (foundUrl) break;
8724
+ throw thrown;
9572
8725
  }
9573
- if (foundUrl) console.log(chalk.green(` Dashboard ready at ${foundUrl}`));
9574
- else console.log(chalk.yellow("\n Dashboard did not respond after 20s. The server may still be starting — check http://localhost:3333"));
9575
- process.exit(0);
8726
+ return;
9576
8727
  }
9577
8728
  if (hasGlobal && hasLocal) {
9578
8729
  console.error(chalk.yellow("Cannot specify both --global and --local"));
@@ -9585,20 +8736,12 @@ const subcommand = argv._[0];
9585
8736
  console.error(chalk.yellow("--uninstall requires --global or --local"));
9586
8737
  process.exit(1);
9587
8738
  }
9588
- const runtimes = selectedRuntimes.length > 0 ? selectedRuntimes : ["claude"];
9589
- for (const runtime of runtimes) uninstall(hasGlobal, runtime);
9590
- } else if (selectedRuntimes.length > 0) if (!hasGlobal && !hasLocal) {
9591
- const isGlobal = await promptLocation(selectedRuntimes);
9592
- await installAllRuntimes(selectedRuntimes, isGlobal, true);
9593
- } else await installAllRuntimes(selectedRuntimes, hasGlobal, false);
9594
- else if (hasGlobal || hasLocal) await installAllRuntimes(["claude"], hasGlobal, false);
8739
+ uninstall(hasGlobal, explicitConfigDir);
8740
+ } else if (hasGlobal || hasLocal) await installForClaude(hasGlobal, false);
9595
8741
  else if (!process.stdin.isTTY) {
9596
- console.log(chalk.yellow("Non-interactive terminal detected, defaulting to Claude Code global install") + "\n");
9597
- await installAllRuntimes(["claude"], true, false);
9598
- } else {
9599
- const runtimes = await promptRuntime();
9600
- await installAllRuntimes(runtimes, await promptLocation(runtimes), true);
9601
- }
8742
+ console.log(chalk.yellow("Non-interactive terminal detected, defaulting to global install") + "\n");
8743
+ await installForClaude(true, false);
8744
+ } else await installForClaude(await promptLocation(), true);
9602
8745
  })().catch((err) => {
9603
8746
  if (err instanceof Error && err.message.includes("User force closed")) {
9604
8747
  console.log("\n" + chalk.yellow("Installation cancelled") + "\n");
@@ -9609,4 +8752,6 @@ const subcommand = argv._[0];
9609
8752
  });
9610
8753
 
9611
8754
  //#endregion
8755
+ exports.__commonJSMin = __commonJSMin;
8756
+ exports.__toESM = __toESM;
9612
8757
  //# sourceMappingURL=install.cjs.map