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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/adapters/index.d.ts +0 -11
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +4 -40
- package/dist/adapters/index.js.map +1 -1
- package/dist/assets/CHANGELOG.md +36 -0
- package/dist/assets/dashboard/client/assets/{index-CZ8WC97G.js → index-C_eAetZJ.js} +66 -66
- package/dist/assets/dashboard/client/assets/index-CmiJKqOU.css +32 -0
- package/dist/assets/dashboard/client/index.html +2 -2
- package/dist/assets/dashboard/server.js +467 -271
- package/dist/assets/templates/agents/AGENTS.md +94 -0
- package/dist/assets/templates/agents/maxsim-debugger.md +2 -2
- package/dist/assets/templates/agents/maxsim-executor.md +5 -5
- package/dist/assets/templates/agents/maxsim-phase-researcher.md +2 -2
- package/dist/assets/templates/agents/maxsim-plan-checker.md +2 -2
- package/dist/assets/templates/agents/maxsim-planner.md +3 -3
- package/dist/assets/templates/commands/maxsim/add-todo.md +15 -5
- package/dist/assets/templates/commands/maxsim/discuss-phase.md +1 -0
- package/dist/assets/templates/commands/maxsim/init-existing.md +4 -0
- package/dist/assets/templates/commands/maxsim/new-project.md +4 -0
- package/dist/assets/templates/commands/maxsim/settings.md +1 -1
- package/dist/assets/templates/references/thinking-partner.md +41 -0
- package/dist/assets/templates/skills/batch-worktree/SKILL.md +137 -0
- package/dist/assets/templates/skills/brainstorming/SKILL.md +159 -0
- package/dist/assets/templates/skills/code-review/SKILL.md +151 -0
- package/dist/assets/templates/skills/memory-management/SKILL.md +174 -0
- package/dist/assets/templates/skills/roadmap-writing/SKILL.md +198 -0
- package/dist/assets/templates/skills/sdd/SKILL.md +175 -0
- package/dist/assets/templates/skills/simplify/SKILL.md +185 -0
- package/dist/assets/templates/skills/using-maxsim/SKILL.md +120 -0
- package/dist/assets/templates/templates/acceptance-criteria.md +10 -0
- package/dist/assets/templates/templates/config.json +1 -1
- package/dist/assets/templates/templates/decisions.md +10 -0
- package/dist/assets/templates/templates/no-gos.md +9 -0
- package/dist/assets/templates/workflows/add-tests.md +3 -3
- package/dist/assets/templates/workflows/add-todo.md +89 -0
- package/dist/assets/templates/workflows/complete-milestone.md +1 -1
- package/dist/assets/templates/workflows/discuss-phase.md +85 -1
- package/dist/assets/templates/workflows/execute-phase.md +26 -16
- package/dist/assets/templates/workflows/execute-plan.md +166 -0
- package/dist/assets/templates/workflows/init-existing.md +123 -3
- package/dist/assets/templates/workflows/new-milestone.md +4 -0
- package/dist/assets/templates/workflows/new-project.md +111 -3
- package/dist/assets/templates/workflows/plan-phase.md +5 -5
- package/dist/assets/templates/workflows/quick.md +2 -2
- package/dist/assets/templates/workflows/settings.md +8 -4
- package/dist/assets/templates/workflows/verify-work.md +1 -1
- package/dist/cli.cjs +1512 -1026
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +170 -278
- package/dist/cli.js.map +1 -1
- package/dist/core/artefakte.d.ts +12 -0
- package/dist/core/artefakte.d.ts.map +1 -0
- package/dist/core/artefakte.js +136 -0
- package/dist/core/artefakte.js.map +1 -0
- package/dist/core/commands.d.ts +13 -13
- package/dist/core/commands.d.ts.map +1 -1
- package/dist/core/commands.js +48 -58
- package/dist/core/commands.js.map +1 -1
- package/dist/core/config.d.ts +4 -3
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +14 -18
- package/dist/core/config.js.map +1 -1
- package/dist/core/context-loader.d.ts +20 -0
- package/dist/core/context-loader.d.ts.map +1 -0
- package/dist/core/context-loader.js +154 -0
- package/dist/core/context-loader.js.map +1 -0
- package/dist/core/core.d.ts +26 -2
- package/dist/core/core.d.ts.map +1 -1
- package/dist/core/core.js +90 -24
- package/dist/core/core.js.map +1 -1
- package/dist/core/dashboard-launcher.d.ts +56 -0
- package/dist/core/dashboard-launcher.d.ts.map +1 -0
- package/dist/core/dashboard-launcher.js +246 -0
- package/dist/core/dashboard-launcher.js.map +1 -0
- package/dist/core/frontmatter.d.ts +5 -5
- package/dist/core/frontmatter.d.ts.map +1 -1
- package/dist/core/frontmatter.js +21 -26
- package/dist/core/frontmatter.js.map +1 -1
- package/dist/core/index.d.ts +10 -3
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +40 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/init.d.ts +14 -15
- package/dist/core/init.d.ts.map +1 -1
- package/dist/core/init.js +93 -155
- package/dist/core/init.js.map +1 -1
- package/dist/core/milestone.d.ts +3 -3
- package/dist/core/milestone.d.ts.map +1 -1
- package/dist/core/milestone.js +9 -9
- package/dist/core/milestone.js.map +1 -1
- package/dist/core/phase.d.ts +9 -9
- package/dist/core/phase.d.ts.map +1 -1
- package/dist/core/phase.js +65 -63
- package/dist/core/phase.js.map +1 -1
- package/dist/core/roadmap.d.ts +4 -3
- package/dist/core/roadmap.d.ts.map +1 -1
- package/dist/core/roadmap.js +46 -108
- package/dist/core/roadmap.js.map +1 -1
- package/dist/core/skills.d.ts +19 -0
- package/dist/core/skills.d.ts.map +1 -0
- package/dist/core/skills.js +145 -0
- package/dist/core/skills.js.map +1 -0
- package/dist/core/start.d.ts +15 -0
- package/dist/core/start.d.ts.map +1 -0
- package/dist/core/start.js +80 -0
- package/dist/core/start.js.map +1 -0
- package/dist/core/state.d.ts +13 -13
- package/dist/core/state.d.ts.map +1 -1
- package/dist/core/state.js +125 -130
- package/dist/core/state.js.map +1 -1
- package/dist/core/template.d.ts +3 -3
- package/dist/core/template.d.ts.map +1 -1
- package/dist/core/template.js +12 -14
- package/dist/core/template.js.map +1 -1
- package/dist/core/types.d.ts +15 -4
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +9 -2
- package/dist/core/types.js.map +1 -1
- package/dist/core/verify.d.ts +10 -9
- package/dist/core/verify.d.ts.map +1 -1
- package/dist/core/verify.js +38 -48
- package/dist/core/verify.js.map +1 -1
- package/dist/core-TFSlUjV1.cjs +4312 -0
- package/dist/core-TFSlUjV1.cjs.map +1 -0
- package/dist/install/adapters.d.ts +6 -0
- package/dist/install/adapters.d.ts.map +1 -0
- package/dist/install/adapters.js +65 -0
- package/dist/install/adapters.js.map +1 -0
- package/dist/install/copy.d.ts +6 -0
- package/dist/install/copy.d.ts.map +1 -0
- package/dist/install/copy.js +71 -0
- package/dist/install/copy.js.map +1 -0
- package/dist/install/dashboard.d.ts +16 -0
- package/dist/install/dashboard.d.ts.map +1 -0
- package/dist/install/dashboard.js +273 -0
- package/dist/install/dashboard.js.map +1 -0
- package/dist/install/hooks.d.ts +31 -0
- package/dist/install/hooks.d.ts.map +1 -0
- package/dist/install/hooks.js +260 -0
- package/dist/install/hooks.js.map +1 -0
- package/dist/install/index.d.ts +2 -0
- package/dist/install/index.d.ts.map +1 -0
- package/dist/install/index.js +535 -0
- package/dist/install/index.js.map +1 -0
- package/dist/install/manifest.d.ts +23 -0
- package/dist/install/manifest.d.ts.map +1 -0
- package/dist/install/manifest.js +129 -0
- package/dist/install/manifest.js.map +1 -0
- package/dist/install/patches.d.ts +10 -0
- package/dist/install/patches.d.ts.map +1 -0
- package/dist/install/patches.js +124 -0
- package/dist/install/patches.js.map +1 -0
- package/dist/install/shared.d.ts +56 -0
- package/dist/install/shared.d.ts.map +1 -0
- package/dist/install/shared.js +172 -0
- package/dist/install/shared.js.map +1 -0
- package/dist/install/uninstall.d.ts +5 -0
- package/dist/install/uninstall.d.ts.map +1 -0
- package/dist/install/uninstall.js +222 -0
- package/dist/install/uninstall.js.map +1 -0
- package/dist/install.cjs +793 -1648
- package/dist/install.cjs.map +1 -1
- package/dist/mcp-server.cjs +38 -14
- package/dist/mcp-server.cjs.map +1 -1
- package/dist/skills-BOSxYUzf.cjs +6812 -0
- package/dist/skills-BOSxYUzf.cjs.map +1 -0
- package/package.json +1 -1
- package/dist/adapters/codex.d.ts +0 -19
- package/dist/adapters/codex.d.ts.map +0 -1
- package/dist/adapters/codex.js +0 -94
- package/dist/adapters/codex.js.map +0 -1
- package/dist/adapters/gemini.d.ts +0 -19
- package/dist/adapters/gemini.d.ts.map +0 -1
- package/dist/adapters/gemini.js +0 -96
- package/dist/adapters/gemini.js.map +0 -1
- package/dist/adapters/opencode.d.ts +0 -17
- package/dist/adapters/opencode.d.ts.map +0 -1
- package/dist/adapters/opencode.js +0 -111
- package/dist/adapters/opencode.js.map +0 -1
- package/dist/adapters/transforms/content.d.ts +0 -39
- package/dist/adapters/transforms/content.d.ts.map +0 -1
- package/dist/adapters/transforms/content.js +0 -125
- package/dist/adapters/transforms/content.js.map +0 -1
- package/dist/adapters/transforms/frontmatter.d.ts +0 -42
- package/dist/adapters/transforms/frontmatter.d.ts.map +0 -1
- package/dist/adapters/transforms/frontmatter.js +0 -204
- package/dist/adapters/transforms/frontmatter.js.map +0 -1
- package/dist/adapters/transforms/tool-maps.d.ts +0 -20
- package/dist/adapters/transforms/tool-maps.d.ts.map +0 -1
- package/dist/adapters/transforms/tool-maps.js +0 -64
- package/dist/adapters/transforms/tool-maps.js.map +0 -1
- package/dist/assets/dashboard/client/assets/index-DzJChB-D.css +0 -32
- package/dist/install.d.ts +0 -2
- package/dist/install.d.ts.map +0 -1
- package/dist/install.js +0 -1841
- 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$
|
|
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$
|
|
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
|
|
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$
|
|
7804
|
-
getConfigDirFromHome: getConfigDirFromHome$
|
|
7805
|
-
transformContent
|
|
7603
|
+
getGlobalDir: getGlobalDir$1,
|
|
7604
|
+
getConfigDirFromHome: getConfigDirFromHome$1,
|
|
7605
|
+
transformContent,
|
|
7806
7606
|
commandStructure: "nested"
|
|
7807
7607
|
};
|
|
7808
7608
|
|
|
7809
7609
|
//#endregion
|
|
7810
|
-
//#region src/
|
|
7811
|
-
|
|
7812
|
-
|
|
7813
|
-
|
|
7814
|
-
|
|
7815
|
-
|
|
7816
|
-
|
|
7817
|
-
|
|
7818
|
-
|
|
7819
|
-
|
|
7820
|
-
|
|
7821
|
-
|
|
7822
|
-
|
|
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
|
-
*
|
|
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
|
|
7843
|
-
|
|
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
|
-
*
|
|
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
|
|
7855
|
-
|
|
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
|
-
*
|
|
7870
|
-
* Ported from install.js line ~423
|
|
7637
|
+
* Get the local directory name
|
|
7871
7638
|
*/
|
|
7872
|
-
function
|
|
7873
|
-
|
|
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
|
-
*
|
|
7881
|
-
*
|
|
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
|
|
7885
|
-
|
|
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
|
-
*
|
|
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
|
|
7895
|
-
|
|
7652
|
+
function copyDirRecursive(src, dest) {
|
|
7653
|
+
import_lib.default.copySync(src, dest, { dereference: true });
|
|
7896
7654
|
}
|
|
7897
7655
|
/**
|
|
7898
|
-
*
|
|
7899
|
-
*
|
|
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
|
|
7908
|
-
if (!
|
|
7909
|
-
|
|
7910
|
-
|
|
7911
|
-
|
|
7912
|
-
|
|
7913
|
-
|
|
7914
|
-
|
|
7915
|
-
|
|
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
|
-
|
|
7935
|
-
|
|
7936
|
-
|
|
7937
|
-
|
|
7938
|
-
|
|
7939
|
-
|
|
7940
|
-
|
|
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
|
-
|
|
7945
|
-
|
|
7946
|
-
|
|
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
|
|
7690
|
+
return true;
|
|
7949
7691
|
}
|
|
7950
7692
|
/**
|
|
7951
|
-
*
|
|
7952
|
-
*
|
|
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
|
|
7955
|
-
const
|
|
7956
|
-
|
|
7957
|
-
|
|
7958
|
-
|
|
7959
|
-
|
|
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
|
|
7740
|
+
//#region src/install/adapters.ts
|
|
7741
|
+
let attributionCached = false;
|
|
7742
|
+
let attributionValue;
|
|
7964
7743
|
/**
|
|
7965
|
-
*
|
|
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
|
-
|
|
7970
|
-
|
|
7971
|
-
|
|
7972
|
-
|
|
7973
|
-
|
|
7974
|
-
|
|
7975
|
-
|
|
7976
|
-
|
|
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
|
-
|
|
7990
|
-
|
|
7991
|
-
|
|
7992
|
-
|
|
7993
|
-
|
|
7994
|
-
|
|
7995
|
-
|
|
7996
|
-
|
|
7997
|
-
|
|
7998
|
-
|
|
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
|
-
*
|
|
8002
|
-
*
|
|
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
|
|
8013
|
-
|
|
8014
|
-
|
|
8015
|
-
|
|
8016
|
-
|
|
8017
|
-
|
|
8018
|
-
|
|
8019
|
-
|
|
8020
|
-
|
|
8021
|
-
|
|
8022
|
-
|
|
8023
|
-
|
|
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
|
-
|
|
8042
|
-
|
|
8043
|
-
|
|
8044
|
-
|
|
8045
|
-
|
|
8046
|
-
|
|
8047
|
-
|
|
8048
|
-
|
|
8049
|
-
|
|
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
|
-
|
|
8052
|
-
|
|
8053
|
-
|
|
8054
|
-
|
|
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
|
-
*
|
|
8109
|
-
* Ported from install.js line ~437
|
|
7806
|
+
* Handle the `dashboard` subcommand — refresh assets, install node-pty, launch server
|
|
8110
7807
|
*/
|
|
8111
|
-
function
|
|
8112
|
-
const
|
|
8113
|
-
|
|
8114
|
-
|
|
8115
|
-
|
|
8116
|
-
|
|
8117
|
-
|
|
8118
|
-
|
|
8119
|
-
|
|
8120
|
-
|
|
8121
|
-
|
|
8122
|
-
|
|
8123
|
-
|
|
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/
|
|
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
|
-
*
|
|
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
|
|
8144
|
-
|
|
8145
|
-
|
|
8146
|
-
|
|
8147
|
-
|
|
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
|
-
*
|
|
8151
|
-
* Priority: explicitDir > env vars (via getOpencodeGlobalDir)
|
|
7920
|
+
* Clean up orphaned hook registrations from settings.json
|
|
8152
7921
|
*/
|
|
8153
|
-
function
|
|
8154
|
-
|
|
8155
|
-
|
|
8156
|
-
|
|
8157
|
-
|
|
8158
|
-
|
|
8159
|
-
|
|
8160
|
-
|
|
8161
|
-
|
|
8162
|
-
|
|
8163
|
-
|
|
8164
|
-
|
|
8165
|
-
|
|
8166
|
-
|
|
8167
|
-
|
|
8168
|
-
|
|
8169
|
-
|
|
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
|
-
|
|
7941
|
+
return true;
|
|
7942
|
+
});
|
|
8787
7943
|
}
|
|
8788
|
-
if (
|
|
8789
|
-
|
|
8790
|
-
|
|
8791
|
-
|
|
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
|
-
*
|
|
7953
|
+
* Install hook files and configure settings.json
|
|
8796
7954
|
*/
|
|
8797
|
-
function
|
|
8798
|
-
|
|
8799
|
-
|
|
8800
|
-
|
|
8801
|
-
|
|
8802
|
-
|
|
8803
|
-
const
|
|
8804
|
-
|
|
8805
|
-
|
|
8806
|
-
|
|
8807
|
-
|
|
8808
|
-
|
|
8809
|
-
|
|
8810
|
-
|
|
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
|
-
|
|
8813
|
-
|
|
8814
|
-
|
|
8815
|
-
|
|
8816
|
-
|
|
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
|
|
7987
|
+
* Configure hooks and statusline in settings.json
|
|
8833
7988
|
*/
|
|
8834
|
-
function
|
|
8835
|
-
const
|
|
8836
|
-
const
|
|
8837
|
-
|
|
8838
|
-
|
|
8839
|
-
|
|
8840
|
-
|
|
8841
|
-
|
|
8842
|
-
|
|
8843
|
-
|
|
8844
|
-
|
|
8845
|
-
|
|
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
|
-
|
|
8848
|
-
|
|
8849
|
-
|
|
8850
|
-
|
|
8851
|
-
|
|
8852
|
-
|
|
8853
|
-
|
|
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
|
-
*
|
|
8023
|
+
* Handle statusline configuration — returns true if MAXSIM statusline should be installed
|
|
8867
8024
|
*/
|
|
8868
|
-
function
|
|
8869
|
-
if (!
|
|
8870
|
-
|
|
8871
|
-
|
|
8872
|
-
|
|
8873
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
8052
|
+
* Apply statusline config, then print completion message
|
|
8886
8053
|
*/
|
|
8887
|
-
function
|
|
8888
|
-
if (
|
|
8889
|
-
|
|
8890
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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(
|
|
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["
|
|
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
|
-
*
|
|
8126
|
+
* Read an existing manifest from the config directory, or return null if none exists / is invalid
|
|
8961
8127
|
*/
|
|
8962
|
-
function
|
|
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
|
-
|
|
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
|
-
|
|
8994
|
-
|
|
8995
|
-
|
|
8996
|
-
|
|
8997
|
-
|
|
8998
|
-
|
|
8999
|
-
|
|
9000
|
-
|
|
9001
|
-
|
|
9002
|
-
|
|
9003
|
-
|
|
9004
|
-
|
|
9005
|
-
|
|
9006
|
-
|
|
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 (
|
|
9009
|
-
|
|
9010
|
-
|
|
9011
|
-
|
|
9012
|
-
|
|
9013
|
-
|
|
9014
|
-
|
|
9015
|
-
|
|
9016
|
-
|
|
9017
|
-
|
|
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
|
-
|
|
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
|
|
9022
|
-
const
|
|
9023
|
-
const
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
9043
|
-
|
|
9044
|
-
|
|
9045
|
-
|
|
9046
|
-
|
|
9047
|
-
|
|
9048
|
-
|
|
9049
|
-
|
|
9050
|
-
|
|
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,
|
|
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(
|
|
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, "
|
|
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(
|
|
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
|
|
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("
|
|
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
|
-
|
|
9188
|
-
|
|
9189
|
-
|
|
9190
|
-
|
|
9191
|
-
|
|
9192
|
-
|
|
9193
|
-
|
|
9194
|
-
|
|
9195
|
-
|
|
9196
|
-
|
|
9197
|
-
|
|
9198
|
-
}
|
|
9199
|
-
|
|
9200
|
-
|
|
9201
|
-
|
|
9202
|
-
|
|
9203
|
-
|
|
9204
|
-
|
|
9205
|
-
|
|
9206
|
-
|
|
9207
|
-
|
|
9208
|
-
|
|
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
|
-
|
|
9266
|
-
|
|
9267
|
-
|
|
9268
|
-
|
|
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
|
|
8640
|
+
writeManifest(targetDir);
|
|
9286
8641
|
console.log(` ${chalk.green("✓")} Wrote file manifest (${MANIFEST_NAME})`);
|
|
9287
|
-
reportLocalPatches(targetDir
|
|
9288
|
-
|
|
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(
|
|
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
|
|
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(`(${
|
|
8663
|
+
name: "Global " + chalk.dim(`(${globalPath})`) + " — available in all projects",
|
|
9433
8664
|
value: "global"
|
|
9434
8665
|
}, {
|
|
9435
|
-
name: "Local " + chalk.dim(
|
|
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 (
|
|
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
|
|
8686
|
+
* Install MAXSIM for Claude Code
|
|
9456
8687
|
*/
|
|
9457
|
-
async function
|
|
9458
|
-
const
|
|
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 (
|
|
8691
|
+
if (result.settings) shouldInstallStatusline = await handleStatusline(result.settings, isInteractive, forceStatusline);
|
|
9467
8692
|
let enableAgentTeams = false;
|
|
9468
|
-
if (isInteractive
|
|
9469
|
-
|
|
9470
|
-
const
|
|
9471
|
-
|
|
9472
|
-
|
|
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
|
-
|
|
9483
|
-
|
|
9484
|
-
|
|
9485
|
-
|
|
9486
|
-
|
|
9487
|
-
|
|
9488
|
-
|
|
9489
|
-
|
|
9490
|
-
if (
|
|
9491
|
-
|
|
9492
|
-
|
|
9493
|
-
|
|
9494
|
-
if (
|
|
9495
|
-
|
|
9496
|
-
|
|
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
|
-
|
|
9535
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
9589
|
-
|
|
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
|
|
9597
|
-
await
|
|
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
|