fix custom node path for Claude setup
This commit is contained in:
parent
64124659e6
commit
4bb55e1a22
8 changed files with 112 additions and 52 deletions
|
|
@ -175,6 +175,7 @@ async function renderAgentSetup(selectedPetId, commandMode) {
|
|||
const result = requireElement("claude-action-result");
|
||||
const devMode = requireInput("claude-dev-mode");
|
||||
const claudeCommandPath = requireInput("claude-command-path");
|
||||
const nodeCommandPath = requireInput("node-command-path");
|
||||
const hookStatus = requireElement("claude-hooks-status");
|
||||
const hookDetails = requireElement("claude-hooks-details");
|
||||
const hookPreview = requireElement("claude-hooks-preview");
|
||||
|
|
@ -191,6 +192,7 @@ async function renderAgentSetup(selectedPetId, commandMode) {
|
|||
devMode.checked = snapshot.commandMode === "local";
|
||||
devMode.disabled = !snapshot.localDevAvailable;
|
||||
claudeCommandPath.value = snapshot.commandPaths.claude || "";
|
||||
nodeCommandPath.value = snapshot.commandPaths.node || "";
|
||||
commandPreview.textContent = snapshot.preview.displayCommand;
|
||||
jsonPreview.textContent = JSON.stringify(snapshot.preview.mcpJson, null, 2);
|
||||
warning.textContent = createClaudeSetupWarning(snapshot);
|
||||
|
|
@ -203,6 +205,7 @@ async function renderAgentSetup(selectedPetId, commandMode) {
|
|||
memoryStatus.className = `agent-status-pill ${memoryStatusClassFor(snapshot.memoryStatus.status)}`;
|
||||
memoryDetails.textContent = `${snapshot.memoryStatus.message} Files: ${snapshot.memoryStatus.claudeMdPath}, ${snapshot.memoryStatus.openPetsMemoryPath}`;
|
||||
updateClaudeIntegrationCard(snapshot);
|
||||
updateClaudeCommandPathHelp(snapshot);
|
||||
updateOpenCodeIntegration(snapshot, selected);
|
||||
|
||||
select.onchange = () => { void renderAgentSetup(select.value, getCommandMode()); };
|
||||
|
|
@ -212,6 +215,7 @@ async function renderAgentSetup(selectedPetId, commandMode) {
|
|||
bindIntegrationHubButtons(snapshot, select);
|
||||
bindAgentSetupButton("claude-refresh", () => renderAgentSetup(select.value, getCommandMode()), snapshot.busy, "Refreshing…");
|
||||
bindAgentSetupButton("claude-command-path-save", () => saveAgentCommandPath("claude", claudeCommandPath.value, select.value, getCommandMode()), snapshot.busy, "Saving…");
|
||||
bindAgentSetupButton("node-command-path-save", () => saveAgentCommandPath("node", nodeCommandPath.value, select.value, getCommandMode()), snapshot.busy, "Saving…");
|
||||
bindAgentSetupButton("claude-copy-command", async () => copyText(snapshot.preview.displayCommand), false);
|
||||
bindAgentSetupButton("claude-configure", () => runAgentAction("configure", select.value, getCommandMode()), snapshot.busy || !snapshot.status.canConfigure, "Installing…");
|
||||
bindAgentSetupButton("claude-replace", () => runAgentAction("replace", select.value, getCommandMode()), snapshot.busy || !snapshot.status.canReplace, "Replacing…");
|
||||
|
|
@ -249,10 +253,13 @@ function updateOpenCodeIntegration(snapshot, selected) {
|
|||
if (title) title.textContent = opencode.state === "configured" ? "OpenCode global setup installed" : "Global setup available";
|
||||
const details = document.getElementById("opencode-details");
|
||||
if (details) details.textContent = opencode.details;
|
||||
updateOpenCodeCommandPathHelp(opencode);
|
||||
const select = document.getElementById("opencode-pet-select");
|
||||
if (select instanceof HTMLSelectElement) renderPetSelect(select, snapshot, selected);
|
||||
const opencodeCommandPath = document.getElementById("opencode-command-path");
|
||||
if (opencodeCommandPath instanceof HTMLInputElement) opencodeCommandPath.value = snapshot.commandPaths.opencode || "";
|
||||
const opencodeNodeCommandPath = document.getElementById("opencode-node-command-path");
|
||||
if (opencodeNodeCommandPath instanceof HTMLInputElement) opencodeNodeCommandPath.value = snapshot.commandPaths.node || "";
|
||||
const paths = document.getElementById("opencode-paths");
|
||||
if (paths) {
|
||||
const cleanup = Array.isArray(preview.cleanupConfigPaths) && preview.cleanupConfigPaths.length > 0 ? `. Cleanup: ${preview.cleanupConfigPaths.join(", ")}` : "";
|
||||
|
|
@ -266,6 +273,7 @@ function updateOpenCodeIntegration(snapshot, selected) {
|
|||
bindAgentSetupButton("opencode-remove", () => runAgentAction("opencode-remove", select instanceof HTMLSelectElement ? select.value : selected, getCommandMode()), snapshot.busy || !opencode.canRemove, "Removing…");
|
||||
bindAgentSetupButton("opencode-refresh", () => renderAgentSetup(select instanceof HTMLSelectElement ? select.value : selected, getCommandMode()), snapshot.busy, "Refreshing…");
|
||||
bindAgentSetupButton("opencode-command-path-save", () => saveAgentCommandPath("opencode", opencodeCommandPath instanceof HTMLInputElement ? opencodeCommandPath.value : "", select instanceof HTMLSelectElement ? select.value : selected, getCommandMode()), snapshot.busy, "Saving…");
|
||||
bindAgentSetupButton("opencode-node-command-path-save", () => saveAgentCommandPath("node", opencodeNodeCommandPath instanceof HTMLInputElement ? opencodeNodeCommandPath.value : "", select instanceof HTMLSelectElement ? select.value : selected, getCommandMode()), snapshot.busy, "Saving…");
|
||||
bindAgentSetupButton("opencode-copy-config", async () => copyText(requireElement("opencode-json-preview").textContent || "", "opencode-action-result", "Copied OpenCode config preview."), false);
|
||||
if (select instanceof HTMLSelectElement) select.onchange = () => { void renderAgentSetup(select.value, getCommandMode()); };
|
||||
}
|
||||
|
|
@ -304,6 +312,25 @@ function updateClaudeIntegrationCard(snapshot) {
|
|||
}
|
||||
}
|
||||
|
||||
function updateClaudeCommandPathHelp(snapshot) {
|
||||
const needsNode = snapshot.status.label === "Node required" || /Node\.js is required|set the Node\.js command path/i.test(snapshot.status.details || "");
|
||||
const details = document.querySelector("#claude-detail-view .agent-command-paths");
|
||||
const card = document.querySelector("#claude-detail-view .connection-card");
|
||||
if (details instanceof HTMLElement) {
|
||||
details.classList.toggle("needs-command-path", needsNode);
|
||||
}
|
||||
if (card instanceof HTMLElement) card.classList.toggle("needs-command-path", needsNode);
|
||||
if (needsNode) renderError("Node.js was not found. Open Claude configuration → Advanced detection, set the Node.js command path, then retry.");
|
||||
}
|
||||
|
||||
function updateOpenCodeCommandPathHelp(opencode) {
|
||||
const needsNode = /Node\.js is required|set the Node\.js command path/i.test(opencode.details || "");
|
||||
const paths = document.querySelector("#opencode-detail-view .agent-command-paths");
|
||||
const card = document.querySelector("#opencode-detail-view .connection-card");
|
||||
if (paths instanceof HTMLElement) paths.classList.toggle("needs-command-path", needsNode);
|
||||
if (card instanceof HTMLElement) card.classList.toggle("needs-command-path", needsNode);
|
||||
}
|
||||
|
||||
function cardStatusClassFor(state) {
|
||||
if (state === "not_detected" || state === "error") return "error";
|
||||
return statusClassFor(state);
|
||||
|
|
@ -431,12 +458,13 @@ function memoryStatusClassFor(status) {
|
|||
}
|
||||
|
||||
function decorateAgentSetupButtons() {
|
||||
for (const id of ["claude-configure", "claude-refresh", "claude-command-path-save", "claude-copy-command", "claude-replace", "claude-remove", "claude-memory-install", "claude-hooks-doctor", "claude-hooks-install", "claude-hooks-uninstall", "opencode-install", "opencode-remove", "opencode-refresh", "opencode-command-path-save", "opencode-copy-config"]) {
|
||||
for (const id of ["claude-configure", "claude-refresh", "claude-command-path-save", "node-command-path-save", "claude-copy-command", "claude-replace", "claude-remove", "claude-memory-install", "claude-hooks-doctor", "claude-hooks-install", "claude-hooks-uninstall", "opencode-install", "opencode-remove", "opencode-refresh", "opencode-command-path-save", "opencode-node-command-path-save", "opencode-copy-config"]) {
|
||||
delete requireButton(id).dataset.loading;
|
||||
}
|
||||
setIconButtonContent(requireButton("claude-configure"), "plug", "Install integration");
|
||||
setIconButtonContent(requireButton("claude-refresh"), "refresh", "Refresh");
|
||||
setIconButtonContent(requireButton("claude-command-path-save"), "check", "Save path");
|
||||
setIconButtonContent(requireButton("node-command-path-save"), "check", "Save path");
|
||||
setIconButtonContent(requireButton("claude-copy-command"), "copy", "Copy command");
|
||||
setIconButtonContent(requireButton("claude-replace"), "repeat", "Replace configuration");
|
||||
requireButton("claude-replace").className = "agent-action primary";
|
||||
|
|
@ -449,6 +477,7 @@ function decorateAgentSetupButtons() {
|
|||
setIconButtonContent(requireButton("opencode-remove"), "trash", "Remove global setup");
|
||||
setIconButtonContent(requireButton("opencode-refresh"), "refresh", "Refresh");
|
||||
setIconButtonContent(requireButton("opencode-command-path-save"), "check", "Save path");
|
||||
setIconButtonContent(requireButton("opencode-node-command-path-save"), "check", "Save path");
|
||||
setIconButtonContent(requireButton("opencode-copy-config"), "copy", "Copy config preview");
|
||||
}
|
||||
|
||||
|
|
@ -546,6 +575,7 @@ function setAgentSetupControlsBusy(busy) {
|
|||
"claude-memory-install",
|
||||
"claude-refresh",
|
||||
"claude-command-path-save",
|
||||
"node-command-path-save",
|
||||
"claude-copy-command",
|
||||
"claude-hooks-doctor",
|
||||
"claude-hooks-install",
|
||||
|
|
@ -554,6 +584,7 @@ function setAgentSetupControlsBusy(busy) {
|
|||
"opencode-remove",
|
||||
"opencode-refresh",
|
||||
"opencode-command-path-save",
|
||||
"opencode-node-command-path-save",
|
||||
"opencode-copy-config",
|
||||
];
|
||||
if (busy) {
|
||||
|
|
@ -595,10 +626,10 @@ async function runAgentAction(action, selectedPetId, commandMode) {
|
|||
}
|
||||
|
||||
async function saveAgentCommandPath(kind, path, selectedPetId, commandMode) {
|
||||
const patch = kind === "claude" ? { claude: path } : { opencode: path };
|
||||
const patch = kind === "claude" ? { claude: path } : kind === "node" ? { node: path } : { opencode: path };
|
||||
await agentSetupApi.updateCommandPaths(patch);
|
||||
await renderAgentSetup(selectedPetId || "", commandMode);
|
||||
const result = document.getElementById(kind === "claude" ? "claude-action-result" : "opencode-action-result");
|
||||
const result = document.getElementById(kind === "opencode" ? "opencode-action-result" : "claude-action-result");
|
||||
if (result) result.textContent = path.trim() ? "Saved command path. Refreshed detection using the saved path." : "Cleared command path. Refreshed automatic detection.";
|
||||
}
|
||||
|
||||
|
|
@ -1301,6 +1332,7 @@ function renderError(message) {
|
|||
const error = document.querySelector("[data-error]");
|
||||
if (error) {
|
||||
error.textContent = message;
|
||||
error.title = message;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1396,6 +1428,7 @@ function isAgentSetupSnapshot(value) {
|
|||
&& typeof value.memoryStatus.claudeMdPath === "string"
|
||||
&& typeof value.memoryStatus.openPetsMemoryPath === "string"
|
||||
&& typeof value.commandPaths.claude === "string"
|
||||
&& typeof value.commandPaths.node === "string"
|
||||
&& typeof value.commandPaths.opencode === "string";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ export interface AgentSetupSnapshot {
|
|||
|
||||
export interface AgentSetupCommandPaths {
|
||||
readonly claude: string;
|
||||
readonly node: string;
|
||||
readonly opencode: string;
|
||||
}
|
||||
|
||||
|
|
@ -136,10 +137,11 @@ export async function getAgentSetupSnapshot(selectedPetId?: unknown, commandMode
|
|||
export function updateAgentSetupCommandPaths(patch: unknown): AgentSetupCommandPaths {
|
||||
if (!isRecord(patch)) throw new Error("Invalid command path settings.");
|
||||
for (const key of Object.keys(patch)) {
|
||||
if (key !== "claude" && key !== "opencode") throw new Error("Invalid command path setting.");
|
||||
if (key !== "claude" && key !== "node" && key !== "opencode") throw new Error("Invalid command path setting.");
|
||||
}
|
||||
const updates: Writable<Partial<OpenPetsStateV1["preferences"]>> = {};
|
||||
if ("claude" in patch) updates.claudeCommandPath = normalizeOptionalCommandPath(patch.claude, "Claude");
|
||||
if ("node" in patch) updates.nodeCommandPath = normalizeOptionalCommandPath(patch.node, "Node.js");
|
||||
if ("opencode" in patch) updates.opencodeCommandPath = normalizeOptionalCommandPath(patch.opencode, "OpenCode");
|
||||
updatePreferences(updates);
|
||||
return getAgentSetupCommandPaths();
|
||||
|
|
@ -175,7 +177,7 @@ export function sanitizeAgentSetupOutput(value: string): string {
|
|||
|
||||
function safeBuildClaudeMcpPreview(selectedPetId: string | undefined, commandMode: OpenPetsCommandMode): { readonly preview: ClaudeMcpPreview; readonly error?: string } {
|
||||
try {
|
||||
return { preview: withPreferredClaudeCommand(buildClaudeMcpPreview(selectedPetId, commandMode)) };
|
||||
return { preview: withPreferredClaudeCommand(buildClaudeMcpPreview(selectedPetId, commandMode, getPreferredNodeCommand())) };
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Packaged OpenPets command resources are unavailable.";
|
||||
return { preview: createErrorPreview(commandMode, message), error: message };
|
||||
|
|
@ -184,7 +186,7 @@ function safeBuildClaudeMcpPreview(selectedPetId: string | undefined, commandMod
|
|||
|
||||
function safeDoctorClaudeHooks(commandMode: OpenPetsCommandMode, selectedPetId: string | undefined): ClaudeHookDoctorResult {
|
||||
try {
|
||||
return doctorClaudeHooks(undefined, commandMode, selectedPetId);
|
||||
return doctorClaudeHooks(undefined, commandMode, selectedPetId, getPreferredNodeCommand());
|
||||
} catch (error) {
|
||||
return createHookErrorStatus(error instanceof Error ? error.message : "Packaged OpenPets hook resources are unavailable.");
|
||||
}
|
||||
|
|
@ -247,8 +249,8 @@ async function runAction(action: AgentSetupAction, selectedPetId: string | undef
|
|||
return runRemove(createErrorPreview(commandMode, ""), selectedPetId, "Unknown", action);
|
||||
}
|
||||
if (commandMode === "bundled") {
|
||||
const node = await runCommand({ command: "node", args: ["--version"] });
|
||||
if (!node.ok) return { ok: false, action, message: `Packaged OpenPets Claude commands require node on Claude's PATH: ${summarizeCommandResult(node)}`, changed: false };
|
||||
const node = await runCommand({ command: getPreferredNodeCommand(), args: ["--version"] });
|
||||
if (!node.ok) return { ok: false, action, message: `Node.js is required for packaged OpenPets commands. Open Claude configuration, set the Node.js command path, then try again. ${summarizeCommandResult(node)}`, changed: false };
|
||||
}
|
||||
const previewResult = safeBuildClaudeMcpPreview(selectedPetId, commandMode);
|
||||
if (previewResult.error) return { ok: false, action, message: previewResult.error, changed: false };
|
||||
|
|
@ -256,7 +258,7 @@ async function runAction(action: AgentSetupAction, selectedPetId: string | undef
|
|||
if (action === "install-hooks") {
|
||||
let result;
|
||||
try {
|
||||
result = installClaudeHooks(undefined, commandMode, selectedPetId);
|
||||
result = installClaudeHooks(undefined, commandMode, selectedPetId, getPreferredNodeCommand());
|
||||
} catch (error) {
|
||||
return { ok: false, action, message: error instanceof Error ? error.message : "OpenPets hook install failed.", changed: false };
|
||||
}
|
||||
|
|
@ -340,6 +342,7 @@ function getAgentSetupCommandPaths(): AgentSetupCommandPaths {
|
|||
const preferences = getAppStateSnapshot().preferences;
|
||||
return {
|
||||
claude: preferences.claudeCommandPath ?? "",
|
||||
node: preferences.nodeCommandPath ?? "",
|
||||
opencode: preferences.opencodeCommandPath ?? "",
|
||||
};
|
||||
}
|
||||
|
|
@ -348,6 +351,10 @@ function getPreferredClaudeCommand(): string {
|
|||
return getAppStateSnapshot().preferences.claudeCommandPath || "claude";
|
||||
}
|
||||
|
||||
function getPreferredNodeCommand(): string {
|
||||
return getAppStateSnapshot().preferences.nodeCommandPath || "node";
|
||||
}
|
||||
|
||||
function getPreferredOpenCodeCommand(): string {
|
||||
return getAppStateSnapshot().preferences.opencodeCommandPath || (process.platform === "win32" ? "opencode.cmd" : "opencode");
|
||||
}
|
||||
|
|
@ -389,8 +396,8 @@ function safePrepareOpenCode(configDir: string, selectedPetId: string | undefine
|
|||
|
||||
async function installOpenCodeGlobal(selectedPetId: string | undefined, commandMode: OpenPetsCommandMode): Promise<AgentSetupActionResult> {
|
||||
if (commandMode === "bundled") {
|
||||
const node = await runCommand({ command: "node", args: ["--version"] });
|
||||
if (!node.ok) return { ok: false, action: "opencode-install", message: `Packaged OpenPets OpenCode setup requires node on OpenCode's PATH: ${summarizeCommandResult(node)}`, changed: false };
|
||||
const node = await runCommand({ command: getPreferredNodeCommand(), args: ["--version"] });
|
||||
if (!node.ok) return { ok: false, action: "opencode-install", message: `Node.js is required for packaged OpenPets commands. Open OpenCode configuration, set the Node.js command path, then try again. ${summarizeCommandResult(node)}`, changed: false };
|
||||
}
|
||||
try {
|
||||
const configDir = getGlobalOpenCodeConfigDir(process.env, app.getPath("home"), process.platform);
|
||||
|
|
@ -486,8 +493,8 @@ function safeUninstallClaudeMemory(): { readonly ok: true; readonly message: str
|
|||
|
||||
async function detectClaudeCodeStatus(selectedPetId: string | undefined, commandMode: OpenPetsCommandMode): Promise<ClaudeCodeStatus> {
|
||||
if (commandMode === "bundled") {
|
||||
const node = await runCommand({ command: "node", args: ["--version"] });
|
||||
if (!node.ok) return createStatus("error", "Node required", `Packaged OpenPets Claude commands require node on Claude's PATH: ${summarizeCommandResult(node)}`, undefined, node, { present: false, source: "none", verified: false, matchesExpected: false });
|
||||
const node = await runCommand({ command: getPreferredNodeCommand(), args: ["--version"] });
|
||||
if (!node.ok) return createStatus("error", "Node required", `Node.js is required for packaged OpenPets commands. Open Claude configuration, expand Advanced detection, set the Node.js command path, then try again. ${summarizeCommandResult(node)}`, undefined, node, { present: false, source: "none", verified: false, matchesExpected: false });
|
||||
}
|
||||
|
||||
const version = await runClaudeCommand({ command: "claude", args: ["--version"] });
|
||||
|
|
@ -501,11 +508,11 @@ async function detectClaudeCodeStatus(selectedPetId: string | undefined, command
|
|||
return createStatus("error", "Error / needs attention", `Claude Code was detected, but MCP status failed: ${summarizeCommandResult(list)}`, sanitizeAgentSetupOutput(version.stdout || version.stderr), list, { present: false, source: "none", verified: false, matchesExpected: false });
|
||||
}
|
||||
|
||||
const listed = classifyClaudeMcpStatus(list.stdout, undefined, selectedPetId, commandMode);
|
||||
const listed = classifyClaudeMcpStatus(list.stdout, undefined, selectedPetId, commandMode, getPreferredNodeCommand());
|
||||
let entry = listed;
|
||||
if (listed.present) {
|
||||
const get = await runClaudeCommand(buildClaudeMcpGetCommand());
|
||||
if (get.ok) entry = classifyClaudeMcpStatus(list.stdout, get.stdout, selectedPetId, commandMode);
|
||||
if (get.ok) entry = classifyClaudeMcpStatus(list.stdout, get.stdout, selectedPetId, commandMode, getPreferredNodeCommand());
|
||||
}
|
||||
|
||||
if (!entry.present) return createStatus("needs_setup", "Needs setup", "Claude Code is detected, but OpenPets MCP is not configured.", sanitizeAgentSetupOutput(version.stdout || version.stderr), list, entry);
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ export interface OpenPetsStateV1 {
|
|||
readonly petScale: number;
|
||||
readonly onboardingCompleted: boolean;
|
||||
readonly claudeCommandPath?: string;
|
||||
readonly nodeCommandPath?: string;
|
||||
readonly opencodeCommandPath?: string;
|
||||
};
|
||||
readonly pets: {
|
||||
|
|
@ -292,6 +293,7 @@ function normalizePreferences(value: Partial<OpenPetsStateV1["preferences"]>): O
|
|||
petScale: normalizePetScale(value.petScale),
|
||||
onboardingCompleted: normalizeOnboardingCompleted(value),
|
||||
claudeCommandPath: normalizeCommandPath(value.claudeCommandPath),
|
||||
nodeCommandPath: normalizeCommandPath(value.nodeCommandPath),
|
||||
opencodeCommandPath: normalizeCommandPath(value.opencodeCommandPath),
|
||||
};
|
||||
}
|
||||
|
|
@ -357,6 +359,7 @@ function createDefaultState(): OpenPetsStateV1 {
|
|||
petScale: defaultPetScale,
|
||||
onboardingCompleted: false,
|
||||
claudeCommandPath: undefined,
|
||||
nodeCommandPath: undefined,
|
||||
opencodeCommandPath: undefined,
|
||||
},
|
||||
pets: {
|
||||
|
|
|
|||
|
|
@ -594,11 +594,12 @@ function createAgentSetupHtml(definition: TaskWindowDefinition): string {
|
|||
<select id="claude-pet-select" class="agent-select"></select>
|
||||
</div>
|
||||
|
||||
<details class="agent-inline-details">
|
||||
<summary><span><small>Advanced detection</small><strong>Claude command path</strong></span></summary>
|
||||
<p class="agent-note">If Claude is not detected, paste the full path to the Claude executable or command shim. Leave blank for automatic PATH detection.</p>
|
||||
<div class="agent-path-row"><input id="claude-command-path" class="agent-text-input" type="text" spellcheck="false" placeholder="/Users/alvin/.local/bin/claude" /><button id="claude-command-path-save" class="agent-action secondary compact">Save path</button></div>
|
||||
</details>
|
||||
<section class="agent-command-paths" aria-labelledby="claude-command-paths-title">
|
||||
<div class="agent-command-paths-title"><small>Configuration</small><strong id="claude-command-paths-title">Command paths</strong></div>
|
||||
<p class="agent-note">If Claude or Node.js is not detected from the app, paste the full executable path. Leave blank for automatic PATH detection.</p>
|
||||
<label class="agent-subfield" for="claude-command-path"><span>Claude command</span><div class="agent-path-row"><input id="claude-command-path" class="agent-text-input" type="text" spellcheck="false" placeholder="/Users/alvin/.local/bin/claude" /><button id="claude-command-path-save" class="agent-action secondary compact">Save path</button></div></label>
|
||||
<label class="agent-subfield" for="node-command-path"><span>Node.js command</span><div class="agent-path-row"><input id="node-command-path" class="agent-text-input" type="text" spellcheck="false" placeholder="/Users/name/.nvm/versions/node/v22/bin/node" /><button id="node-command-path-save" class="agent-action secondary compact">Save path</button></div></label>
|
||||
</section>
|
||||
|
||||
<label class="agent-mode-row dev-mode-row" hidden>
|
||||
<span>
|
||||
|
|
@ -688,7 +689,7 @@ function createAgentSetupHtml(definition: TaskWindowDefinition): string {
|
|||
<div class="agent-section-header"><span><small>Global connection</small><strong id="opencode-status-title">Checking setup…</strong></span><span id="opencode-status" class="agent-status-pill">Checking</span></div>
|
||||
<p id="opencode-details" class="agent-note">Checking OpenCode…</p>
|
||||
<div class="agent-control-group"><label class="agent-field-label" for="opencode-pet-select">Pet routing</label><select id="opencode-pet-select" class="agent-select"></select></div>
|
||||
<details class="agent-inline-details"><summary><span><small>Advanced detection</small><strong>OpenCode command path</strong></span></summary><p class="agent-note">If OpenCode is not detected, paste the full path to the OpenCode executable or command shim. Leave blank for automatic PATH detection.</p><div class="agent-path-row"><input id="opencode-command-path" class="agent-text-input" type="text" spellcheck="false" placeholder="/Users/alvin/.opencode/bin/opencode" /><button id="opencode-command-path-save" class="agent-action secondary compact">Save path</button></div></details>
|
||||
<section class="agent-command-paths" aria-labelledby="opencode-command-paths-title"><div class="agent-command-paths-title"><small>Configuration</small><strong id="opencode-command-paths-title">Command paths</strong></div><p class="agent-note">If OpenCode or Node.js is not detected from the app, paste the full executable path. Leave blank for automatic PATH detection.</p><label class="agent-subfield" for="opencode-command-path"><span>OpenCode command</span><div class="agent-path-row"><input id="opencode-command-path" class="agent-text-input" type="text" spellcheck="false" placeholder="/Users/alvin/.opencode/bin/opencode" /><button id="opencode-command-path-save" class="agent-action secondary compact">Save path</button></div></label><label class="agent-subfield" for="opencode-node-command-path"><span>Node.js command</span><div class="agent-path-row"><input id="opencode-node-command-path" class="agent-text-input" type="text" spellcheck="false" placeholder="/Users/name/.nvm/versions/node/v22/bin/node" /><button id="opencode-node-command-path-save" class="agent-action secondary compact">Save path</button></div></label></section>
|
||||
<p class="agent-hook-warning warning">Desktop OpenCode setup is global and can affect every OpenCode project. For project-local setup, run <code>openpets configure --agent opencode --pet <id></code>. OpenCode may need npm/network access to load the published OpenPets plugin unless it is already cached or installed.</p>
|
||||
<div class="agent-actions agent-main-actions"><button id="opencode-install" class="agent-action primary">Install global setup</button><button id="opencode-remove" class="agent-action danger">Remove global setup</button><button id="opencode-refresh" class="agent-action secondary">Refresh</button></div>
|
||||
<details class="agent-inline-details" open><summary><span><small>Preview</small><strong>Global OpenCode config</strong></span></summary><p id="opencode-paths" class="agent-note"></p><div class="agent-actions advanced-actions"><button id="opencode-copy-config" class="agent-action secondary compact">Copy config preview</button></div><pre id="opencode-json-preview" class="agent-preview-code json-preview" aria-label="OpenCode config preview" aria-live="polite"></pre></details>
|
||||
|
|
@ -917,7 +918,13 @@ function createTaskWindowStyles(): string {
|
|||
body[data-openpets-view="agent-setup"] .agent-field-label { display: block; margin: 0 0 8px; color: #102149; font-weight: 900; }
|
||||
body[data-openpets-view="agent-setup"] .agent-select { width: 100%; box-sizing: border-box; min-height: 42px; border: 1px solid rgba(126, 161, 210, 0.54); border-radius: 12px; background: rgba(255,255,255,0.82); color: #17284f; padding: 0 12px; font: inherit; outline: none; }
|
||||
body[data-openpets-view="agent-setup"] .agent-select:focus { border-color: #3b82f6; box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15); }
|
||||
body[data-openpets-view="agent-setup"] .agent-command-paths { display: grid; gap: 10px; margin-top: 2px; padding: 14px; border: 1px solid rgba(126, 161, 210, 0.28); border-radius: 16px; background: rgba(239, 246, 255, 0.5); }
|
||||
body[data-openpets-view="agent-setup"] .agent-command-paths-title { display: grid; gap: 4px; }
|
||||
body[data-openpets-view="agent-setup"] .agent-command-paths-title small { color: #2478ff; font-size: 11px; font-weight: 900; letter-spacing: 0.1em; text-transform: uppercase; }
|
||||
body[data-openpets-view="agent-setup"] .agent-command-paths-title strong { color: #102149; font-size: 15px; }
|
||||
body[data-openpets-view="agent-setup"] .agent-path-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; align-items: center; }
|
||||
body[data-openpets-view="agent-setup"] .agent-subfield { display: grid; gap: 7px; margin-top: 10px; }
|
||||
body[data-openpets-view="agent-setup"] .agent-subfield span { color: #102149; font-size: 12px; font-weight: 900; }
|
||||
body[data-openpets-view="agent-setup"] .agent-text-input { width: 100%; box-sizing: border-box; min-height: 38px; border: 1px solid rgba(126, 161, 210, 0.54); border-radius: 11px; background: rgba(255,255,255,0.82); color: #17284f; padding: 0 11px; font: 12px ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; outline: none; }
|
||||
body[data-openpets-view="agent-setup"] .agent-text-input:focus { border-color: #3b82f6; box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15); }
|
||||
body[data-openpets-view="agent-setup"] .agent-mode-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-top: 0; color: #17284f; }
|
||||
|
|
@ -962,7 +969,10 @@ function createTaskWindowStyles(): string {
|
|||
body[data-openpets-view="agent-setup"] .hook-actions { margin-top: 12px; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
||||
body[data-openpets-view="agent-setup"] .memory-actions { margin-top: 12px; }
|
||||
body[data-openpets-view="agent-setup"] .advanced-actions { margin-top: 12px; }
|
||||
body[data-openpets-view="agent-setup"] [data-error] { position: fixed; left: 18px; right: 18px; bottom: 8px; margin: 0; color: #b91c1c; pointer-events: none; }
|
||||
body[data-openpets-view="agent-setup"] [data-error] { position: fixed; left: 18px; right: 18px; bottom: 8px; max-height: 3.4em; overflow: hidden; margin: 0; color: #b91c1c; pointer-events: none; line-height: 1.35; text-overflow: ellipsis; }
|
||||
body[data-openpets-view="agent-setup"] .connection-card.needs-command-path { border-color: rgba(239, 68, 68, 0.42); box-shadow: 0 16px 38px rgba(185, 28, 28, 0.12), 0 0 0 3px rgba(239, 68, 68, 0.08), inset 0 1px 0 rgba(255,255,255,0.94); }
|
||||
body[data-openpets-view="agent-setup"] .agent-command-paths.needs-command-path { border-color: rgba(239, 68, 68, 0.44); background: rgba(254, 242, 242, 0.58); }
|
||||
body[data-openpets-view="agent-setup"] .agent-command-paths.needs-command-path .agent-command-paths-title strong { color: #b91c1c; }
|
||||
@media (prefers-reduced-motion: reduce) { body[data-openpets-view="agent-setup"] .agent-action:hover:not(:disabled), body[data-openpets-view="agent-setup"] .agent-action:active:not(:disabled) { transform: none; } }
|
||||
@media (max-width: 980px) { body[data-openpets-view="agent-setup"] .integration-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
||||
@media (max-width: 860px) { body[data-openpets-view="agent-setup"] { overflow: auto; } body[data-openpets-view="agent-setup"] .agent-shell { height: auto; min-height: calc(100vh - 36px); overflow: visible; } body[data-openpets-view="agent-setup"] .integrations-view, body[data-openpets-view="agent-setup"] .claude-detail-view { height: auto; overflow: visible; } body[data-openpets-view="agent-setup"] .integration-grid { overflow: visible; } }
|
||||
|
|
|
|||
|
|
@ -39,6 +39,10 @@ assert.equal(bundledPreview.mcpJson.mcpServers.openpets.command, "node");
|
|||
assert.deepEqual(bundledPreview.mcpJson.mcpServers.openpets.args, [getBundledMcpEntryPath(), "--pet", "snoopy"]);
|
||||
const bundledGet = parseClaudeMcpGetOutput(JSON.stringify({ command: "node", args: [getBundledMcpEntryPath(), "--pet", "snoopy"] }), "snoopy", "bundled");
|
||||
assert.equal(bundledGet.matchesExpected, true);
|
||||
const customNode = "/Users/test/Library/Application Support/Herd/config/nvm/versions/node/v22.22.2/bin/node";
|
||||
const customNodePreview = buildClaudeMcpPreview("snoopy", "bundled", customNode);
|
||||
assert.equal(customNodePreview.mcpJson.mcpServers.openpets.command, customNode);
|
||||
assert.equal(parseClaudeMcpGetOutput(JSON.stringify({ command: customNode, args: [getBundledMcpEntryPath(), "--pet", "snoopy"] }), "snoopy", "bundled", customNode).matchesExpected, true);
|
||||
|
||||
const spacedPath = "/Applications/OpenPets Test.app/Contents/Resources/app/node_modules/@open-pets/mcp/dist/index.js";
|
||||
assert.equal(formatCommandForDisplay({ command: "node", args: [spacedPath, "--pet", "snoopy"] }), 'node "/Applications/OpenPets Test.app/Contents/Resources/app/node_modules/@open-pets/mcp/dist/index.js" --pet snoopy');
|
||||
|
|
|
|||
|
|
@ -97,6 +97,9 @@ const bundledPreview = createOpenPetsHookSettingsPreview("bundled");
|
|||
const bundledHook = (((bundledPreview.hooks as Record<string, unknown>).Stop as Array<{ hooks: Array<{ command: string }> }>)[0]?.hooks[0]);
|
||||
assert.ok(bundledHook?.command.includes(getBundledClaudeCliPath()));
|
||||
assert.ok(bundledHook?.command.includes(openPetsHookMarker));
|
||||
const customNodeHookCommand = createOpenPetsHookCommand("bundled", "fixer", "/Users/test/Library/Application Support/Herd/config/nvm/versions/node/v22.22.2/bin/node");
|
||||
assert.ok(customNodeHookCommand.startsWith('"/Users/test/Library/Application Support/Herd/config/nvm/versions/node/v22.22.2/bin/node"'));
|
||||
assert.ok(customNodeHookCommand.includes("--pet fixer"));
|
||||
assert.ok(createOpenPetsHookCommand("published", "fixer").endsWith("--openpets-managed --pet fixer"));
|
||||
const petPreview = createOpenPetsHookSettingsPreview("published", "fixer");
|
||||
const petHook = (((petPreview.hooks as Record<string, unknown>).UserPromptSubmit as Array<{ hooks: Array<{ command: string }> }>)[0]?.hooks[0]);
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export interface ClaudeMcpPreview {
|
|||
readonly mcpServers: {
|
||||
readonly openpets: {
|
||||
readonly type: "stdio";
|
||||
readonly command: "npx" | "node";
|
||||
readonly command: string;
|
||||
readonly args: readonly string[];
|
||||
};
|
||||
};
|
||||
|
|
@ -38,8 +38,8 @@ export interface ParsedClaudeMcpEntry {
|
|||
readonly matchesExpected: boolean;
|
||||
}
|
||||
|
||||
export function buildClaudeMcpPreview(selectedPetId?: string, commandMode: OpenPetsCommandMode = "published"): ClaudeMcpPreview {
|
||||
const server = buildOpenPetsMcpServerCommand(selectedPetId, commandMode);
|
||||
export function buildClaudeMcpPreview(selectedPetId?: string, commandMode: OpenPetsCommandMode = "published", nodeCommand = "node"): ClaudeMcpPreview {
|
||||
const server = buildOpenPetsMcpServerCommand(selectedPetId, commandMode, nodeCommand);
|
||||
const addArgs = ["mcp", "add", "--scope", "user", claudeMcpServerName, "--", server.command, ...server.args] as const;
|
||||
const removeArgs = ["mcp", "remove", "--scope", "user", claudeMcpServerName] as const;
|
||||
const add: ClaudeCommandSpec = { command: "claude", args: addArgs };
|
||||
|
|
@ -67,13 +67,13 @@ export function buildOpenPetsMcpArgs(selectedPetId?: string): readonly string[]
|
|||
return ["-y", openPetsMcpPackageName, "--pet", selectedPetId];
|
||||
}
|
||||
|
||||
export function buildOpenPetsMcpServerCommand(selectedPetId?: string, commandMode: OpenPetsCommandMode = "published"): { readonly command: "npx" | "node"; readonly args: readonly string[] } {
|
||||
export function buildOpenPetsMcpServerCommand(selectedPetId?: string, commandMode: OpenPetsCommandMode = "published", nodeCommand = "node"): { readonly command: string; readonly args: readonly string[] } {
|
||||
if (commandMode === "local" || commandMode === "bundled") {
|
||||
const entryPath = commandMode === "bundled" ? getBundledMcpEntryPath() : getLocalMcpEntryPath();
|
||||
commandMode === "bundled" ? assertBundledMcpEntryPath() : assertLocalMcpEntryPath();
|
||||
if (selectedPetId === undefined) return { command: "node", args: [entryPath] };
|
||||
if (selectedPetId === undefined) return { command: nodeCommand, args: [entryPath] };
|
||||
validateOpenPetsPetArg(selectedPetId);
|
||||
return { command: "node", args: [entryPath, "--pet", selectedPetId] };
|
||||
return { command: nodeCommand, args: [entryPath, "--pet", selectedPetId] };
|
||||
}
|
||||
return { command: "npx", args: buildOpenPetsMcpArgs(selectedPetId) };
|
||||
}
|
||||
|
|
@ -125,12 +125,12 @@ export function parseClaudeMcpListOutput(output: string): ParsedClaudeMcpEntry {
|
|||
};
|
||||
}
|
||||
|
||||
export function parseClaudeMcpGetOutput(output: string, expectedPetId?: string, commandMode: OpenPetsCommandMode = "published"): ParsedClaudeMcpEntry {
|
||||
export function parseClaudeMcpGetOutput(output: string, expectedPetId?: string, commandMode: OpenPetsCommandMode = "published", nodeCommand = "node"): ParsedClaudeMcpEntry {
|
||||
const text = output.trim();
|
||||
if (!text) return { present: false, source: "none", verified: false, matchesExpected: false };
|
||||
|
||||
const parsed = tryParseJson(text);
|
||||
const expected = buildOpenPetsMcpServerCommand(expectedPetId, commandMode);
|
||||
const expected = buildOpenPetsMcpServerCommand(expectedPetId, commandMode, nodeCommand);
|
||||
const jsonEntry = parsed ? extractJsonEntry(parsed) : null;
|
||||
if (jsonEntry) {
|
||||
const matchesExpected = jsonEntry.command === expected.command && arraysEqual(jsonEntry.args, expected.args);
|
||||
|
|
@ -151,9 +151,9 @@ export function parseClaudeMcpGetOutput(output: string, expectedPetId?: string,
|
|||
return { present: false, source: "none", verified: false, matchesExpected: false };
|
||||
}
|
||||
|
||||
export function classifyClaudeMcpStatus(listOutput: string, getOutput: string | undefined, expectedPetId?: string, commandMode: OpenPetsCommandMode = "published"): ParsedClaudeMcpEntry {
|
||||
export function classifyClaudeMcpStatus(listOutput: string, getOutput: string | undefined, expectedPetId?: string, commandMode: OpenPetsCommandMode = "published", nodeCommand = "node"): ParsedClaudeMcpEntry {
|
||||
if (getOutput) {
|
||||
const parsedGet = parseClaudeMcpGetOutput(getOutput, expectedPetId, commandMode);
|
||||
const parsedGet = parseClaudeMcpGetOutput(getOutput, expectedPetId, commandMode, nodeCommand);
|
||||
if (parsedGet.present) return parsedGet;
|
||||
}
|
||||
return parseClaudeMcpListOutput(listOutput);
|
||||
|
|
|
|||
|
|
@ -29,12 +29,12 @@ export function getClaudeUserSettingsPath(): string {
|
|||
return join(homedir(), ".claude", "settings.json");
|
||||
}
|
||||
|
||||
export function createOpenPetsHookCommand(commandMode: OpenPetsCommandMode = "published", selectedPetId?: string): string {
|
||||
export function createOpenPetsHookCommand(commandMode: OpenPetsCommandMode = "published", selectedPetId?: string, nodeCommand = "node"): string {
|
||||
const petArgs = selectedPetId === undefined ? "" : ` --pet ${shellQuote(validateOpenPetsPetArg(selectedPetId))}`;
|
||||
if (commandMode === "local" || commandMode === "bundled") {
|
||||
const cliPath = commandMode === "bundled" ? getBundledClaudeCliPath() : getLocalClaudeCliPath();
|
||||
commandMode === "bundled" ? assertBundledClaudeCliPath() : assertLocalClaudeCliPath();
|
||||
return `node ${shellQuote(cliPath)} hook ${openPetsHookMarker}${petArgs}`;
|
||||
return `${shellQuote(nodeCommand)} ${shellQuote(cliPath)} hook ${openPetsHookMarker}${petArgs}`;
|
||||
}
|
||||
return `npx -y @open-pets/claude hook ${openPetsHookMarker}${petArgs}`;
|
||||
}
|
||||
|
|
@ -72,20 +72,20 @@ function isTrueAsarPath(path: string): boolean {
|
|||
return /app\.asar(?:$|[\\/])/.test(path) && !/app\.asar\.unpacked(?:$|[\\/])/.test(path);
|
||||
}
|
||||
|
||||
export function createOpenPetsHookSettingsPreview(commandMode: OpenPetsCommandMode = "published", selectedPetId?: string): Record<string, unknown> {
|
||||
export function createOpenPetsHookSettingsPreview(commandMode: OpenPetsCommandMode = "published", selectedPetId?: string, nodeCommand = "node"): Record<string, unknown> {
|
||||
const hooks: Record<string, unknown> = {};
|
||||
for (const event of claudeHookEvents) {
|
||||
hooks[event] = [{ hooks: [createHookCommandEntry(commandMode, selectedPetId)] }];
|
||||
hooks[event] = [{ hooks: [createHookCommandEntry(commandMode, selectedPetId, nodeCommand)] }];
|
||||
}
|
||||
return { hooks };
|
||||
}
|
||||
|
||||
export function doctorClaudeHooks(settingsPath = getClaudeUserSettingsPath(), commandMode: OpenPetsCommandMode = "published", selectedPetId?: string): ClaudeHookDoctorResult {
|
||||
const preview = createOpenPetsHookSettingsPreview(commandMode, selectedPetId);
|
||||
export function doctorClaudeHooks(settingsPath = getClaudeUserSettingsPath(), commandMode: OpenPetsCommandMode = "published", selectedPetId?: string, nodeCommand = "node"): ClaudeHookDoctorResult {
|
||||
const preview = createOpenPetsHookSettingsPreview(commandMode, selectedPetId, nodeCommand);
|
||||
const asyncSupported = isClaudeHookAsyncSupported();
|
||||
try {
|
||||
const settings = readClaudeSettings(settingsPath);
|
||||
const status = getHookInstallStatus(settings, commandMode, selectedPetId);
|
||||
const status = getHookInstallStatus(settings, commandMode, selectedPetId, nodeCommand);
|
||||
return {
|
||||
status,
|
||||
settingsPath,
|
||||
|
|
@ -100,15 +100,15 @@ export function doctorClaudeHooks(settingsPath = getClaudeUserSettingsPath(), co
|
|||
}
|
||||
}
|
||||
|
||||
export function installClaudeHooks(settingsPath = getClaudeUserSettingsPath(), commandMode: OpenPetsCommandMode = "published", selectedPetId?: string): ClaudeHookWriteResult {
|
||||
export function installClaudeHooks(settingsPath = getClaudeUserSettingsPath(), commandMode: OpenPetsCommandMode = "published", selectedPetId?: string, nodeCommand = "node"): ClaudeHookWriteResult {
|
||||
if (!isClaudeHookAsyncSupported()) throw new Error("Claude async hook support is not enabled for this OpenPets build.");
|
||||
const settings = readClaudeSettings(settingsPath);
|
||||
const status = getHookInstallStatus(settings, commandMode, selectedPetId);
|
||||
if (status === "installed") return { ...doctorClaudeHooks(settingsPath, commandMode, selectedPetId), changed: false };
|
||||
const status = getHookInstallStatus(settings, commandMode, selectedPetId, nodeCommand);
|
||||
if (status === "installed") return { ...doctorClaudeHooks(settingsPath, commandMode, selectedPetId, nodeCommand), changed: false };
|
||||
const backupPath = backupSettings(settingsPath);
|
||||
const next = addOpenPetsHooks(removeOpenPetsHooks(settings), commandMode, selectedPetId);
|
||||
const next = addOpenPetsHooks(removeOpenPetsHooks(settings), commandMode, selectedPetId, nodeCommand);
|
||||
writeClaudeSettings(settingsPath, next);
|
||||
return { ...doctorClaudeHooks(settingsPath, commandMode, selectedPetId), backupPath, changed: true };
|
||||
return { ...doctorClaudeHooks(settingsPath, commandMode, selectedPetId, nodeCommand), backupPath, changed: true };
|
||||
}
|
||||
|
||||
export function uninstallClaudeHooks(settingsPath = getClaudeUserSettingsPath(), commandMode: OpenPetsCommandMode = "published"): ClaudeHookWriteResult {
|
||||
|
|
@ -121,13 +121,13 @@ export function uninstallClaudeHooks(settingsPath = getClaudeUserSettingsPath(),
|
|||
return { ...doctorClaudeHooks(settingsPath, commandMode), backupPath, changed: true };
|
||||
}
|
||||
|
||||
export function addOpenPetsHooks(settings: Record<string, unknown>, commandMode: OpenPetsCommandMode = "published", selectedPetId?: string): Record<string, unknown> {
|
||||
export function addOpenPetsHooks(settings: Record<string, unknown>, commandMode: OpenPetsCommandMode = "published", selectedPetId?: string, nodeCommand = "node"): Record<string, unknown> {
|
||||
const next = structuredClone(settings) as Record<string, unknown>;
|
||||
assertSelectedHookEventsAreArrays(next);
|
||||
const hooks = isRecord(next.hooks) ? { ...next.hooks } : {};
|
||||
for (const event of claudeHookEvents) {
|
||||
const existing = Array.isArray(hooks[event]) ? hooks[event].filter((entry) => !containsOpenPetsHook(entry)) : [];
|
||||
hooks[event] = [...existing, { hooks: [createHookCommandEntry(commandMode, selectedPetId)] }];
|
||||
hooks[event] = [...existing, { hooks: [createHookCommandEntry(commandMode, selectedPetId, nodeCommand)] }];
|
||||
}
|
||||
next.hooks = hooks;
|
||||
return next;
|
||||
|
|
@ -149,7 +149,7 @@ export function removeOpenPetsHooks(settings: Record<string, unknown>): Record<s
|
|||
return next;
|
||||
}
|
||||
|
||||
function getHookInstallStatus(settings: Record<string, unknown>, commandMode: OpenPetsCommandMode, selectedPetId?: string): ClaudeHookInstallStatus {
|
||||
function getHookInstallStatus(settings: Record<string, unknown>, commandMode: OpenPetsCommandMode, selectedPetId?: string, nodeCommand = "node"): ClaudeHookInstallStatus {
|
||||
if (settings.hooks !== undefined && !isRecord(settings.hooks)) throw new Error("Claude settings hooks field is not an object.");
|
||||
const hooks = isRecord(settings.hooks) ? settings.hooks : {};
|
||||
let foundAny = false;
|
||||
|
|
@ -157,7 +157,7 @@ function getHookInstallStatus(settings: Record<string, unknown>, commandMode: Op
|
|||
for (const event of claudeHookEvents) {
|
||||
const entries = hooks[event];
|
||||
if (!Array.isArray(entries)) return foundAny ? "needs_update" : "not_installed";
|
||||
const currentCount = entries.filter((entry) => containsCurrentOpenPetsHook(entry, commandMode, selectedPetId)).length;
|
||||
const currentCount = entries.filter((entry) => containsCurrentOpenPetsHook(entry, commandMode, selectedPetId, nodeCommand)).length;
|
||||
const managedCount = entries.filter((entry) => containsOpenPetsHook(entry)).length;
|
||||
const hasCurrent = currentCount === 1;
|
||||
if (managedCount > 0) foundAny = true;
|
||||
|
|
@ -168,13 +168,13 @@ function getHookInstallStatus(settings: Record<string, unknown>, commandMode: Op
|
|||
return staleManaged ? "needs_update" : "installed";
|
||||
}
|
||||
|
||||
function createHookCommandEntry(commandMode: OpenPetsCommandMode, selectedPetId?: string): Record<string, unknown> {
|
||||
return { type: "command", command: createOpenPetsHookCommand(commandMode, selectedPetId), timeout: 3, async: true, asyncRewake: false };
|
||||
function createHookCommandEntry(commandMode: OpenPetsCommandMode, selectedPetId?: string, nodeCommand = "node"): Record<string, unknown> {
|
||||
return { type: "command", command: createOpenPetsHookCommand(commandMode, selectedPetId, nodeCommand), timeout: 3, async: true, asyncRewake: false };
|
||||
}
|
||||
|
||||
function containsCurrentOpenPetsHook(value: unknown, commandMode: OpenPetsCommandMode, selectedPetId?: string): boolean {
|
||||
function containsCurrentOpenPetsHook(value: unknown, commandMode: OpenPetsCommandMode, selectedPetId?: string, nodeCommand = "node"): boolean {
|
||||
if (!isRecord(value) || !Array.isArray(value.hooks)) return false;
|
||||
const command = createOpenPetsHookCommand(commandMode, selectedPetId);
|
||||
const command = createOpenPetsHookCommand(commandMode, selectedPetId, nodeCommand);
|
||||
return value.hooks.some((hook) => isRecord(hook) && hook.type === "command" && hook.command === command && hook.timeout === 3 && hook.async === true && hook.asyncRewake === false);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue