From 82d16fa01269ab865d65e7808c40dbaece46988c Mon Sep 17 00:00:00 2001 From: ujiro99 Date: Mon, 4 May 2026 16:01:58 +0900 Subject: [PATCH 1/9] Update: Count downloads on a per-user basis as much as possible. --- .../components/commandHub/DownloadButton.tsx | 41 +++++++++++++++++-- packages/extension/src/services/analytics.ts | 2 +- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/packages/extension/src/components/commandHub/DownloadButton.tsx b/packages/extension/src/components/commandHub/DownloadButton.tsx index f9561ac2..da1abb6d 100644 --- a/packages/extension/src/components/commandHub/DownloadButton.tsx +++ b/packages/extension/src/components/commandHub/DownloadButton.tsx @@ -5,7 +5,11 @@ import { Ipc, BgCommand } from "@/services/ipc" import { useSection } from "@/hooks/useSettings" import { useDetectUrlChanged } from "@/hooks/useDetectUrlChanged" import { CACHE_SECTIONS } from "@/services/settings/settingsCache" -import { sendEvent, ANALYTICS_EVENTS } from "@/services/analytics" +import { + sendEvent, + ANALYTICS_EVENTS, + getOrCreateClientId, +} from "@/services/analytics" import { Popover, PopoverContent, @@ -185,6 +189,19 @@ export const DownloadButton = (): JSX.Element => { * action: "DeleteCommand", * id: string // ID of the command to remove * } + * + * --- AddCommandAck (response) --- + * { + * action: "AddCommandAck", + * result: boolean, // true if the command was added successfully, false otherwise + * install_id: string // stable anonymous identifier per extension install (UUID, persisted in chrome.storage.local) + * } + * + * --- DeleteCommandAck (response) --- + * { + * action: "DeleteCommandAck", + * result: boolean // true if the command was removed successfully, false otherwise + * } */ useEffect(() => { const hubOrigin = new URL(HUB_URL).origin @@ -194,15 +211,25 @@ export const DownloadButton = (): JSX.Element => { if (action === "AddCommand") { if (typeof command !== "string") return Ipc.send(BgCommand.addCommand, { command }) - .then((res) => { + .then(async (res) => { if (res) { toast.success(t("commandHub_add_success")) } else { toast.error(t("commandHub_add_error")) } + const install_id = await getOrCreateClientId() + ;(event.source as WindowProxy)?.postMessage( + { action: "AddCommandAck", result: !!res, install_id }, + { targetOrigin: event.origin }, + ) }) - .catch(() => { + .catch(async () => { toast.error(t("commandHub_add_error")) + const install_id = await getOrCreateClientId() + ;(event.source as WindowProxy)?.postMessage( + { action: "AddCommandAck", result: false, install_id }, + { targetOrigin: event.origin }, + ) }) } else if (action === "DeleteCommand") { if (typeof id !== "string") return @@ -213,9 +240,17 @@ export const DownloadButton = (): JSX.Element => { } else { toast.error(t("commandHub_delete_error")) } + ;(event.source as WindowProxy)?.postMessage( + { action: "DeleteCommandAck", result: !!res }, + { targetOrigin: event.origin }, + ) }) .catch(() => { toast.error(t("commandHub_delete_error")) + ;(event.source as WindowProxy)?.postMessage( + { action: "DeleteCommandAck", result: false }, + { targetOrigin: event.origin }, + ) }) } } diff --git a/packages/extension/src/services/analytics.ts b/packages/extension/src/services/analytics.ts index 304efc25..a53eaa80 100644 --- a/packages/extension/src/services/analytics.ts +++ b/packages/extension/src/services/analytics.ts @@ -77,7 +77,7 @@ export async function sendEvent( } } -async function getOrCreateClientId() { +export async function getOrCreateClientId() { let clientId = await Storage.get(LOCAL_STORAGE_KEY.CLIENT_ID) if (!clientId) { clientId = crypto.randomUUID() From 5402321f980cada53366e5614e776dddec38a962 Mon Sep 17 00:00:00 2001 From: ujiro99 Date: Mon, 4 May 2026 18:05:07 +0900 Subject: [PATCH 2/9] Update: Centralize messages for command additions on the HUB side. --- packages/extension/public/_locales/de/messages.json | 6 ------ packages/extension/public/_locales/en/messages.json | 6 ------ packages/extension/public/_locales/es/messages.json | 6 ------ packages/extension/public/_locales/fr/messages.json | 6 ------ packages/extension/public/_locales/hi/messages.json | 6 ------ packages/extension/public/_locales/id/messages.json | 6 ------ packages/extension/public/_locales/it/messages.json | 6 ------ packages/extension/public/_locales/ja/messages.json | 6 ------ packages/extension/public/_locales/ko/messages.json | 6 ------ packages/extension/public/_locales/ms/messages.json | 6 ------ packages/extension/public/_locales/pt_BR/messages.json | 6 ------ packages/extension/public/_locales/pt_PT/messages.json | 6 ------ packages/extension/public/_locales/ru/messages.json | 6 ------ packages/extension/public/_locales/zh_CN/messages.json | 6 ------ .../extension/src/components/commandHub/DownloadButton.tsx | 6 ------ 15 files changed, 90 deletions(-) diff --git a/packages/extension/public/_locales/de/messages.json b/packages/extension/public/_locales/de/messages.json index 8d88d6d1..2582fdb4 100644 --- a/packages/extension/public/_locales/de/messages.json +++ b/packages/extension/public/_locales/de/messages.json @@ -1248,12 +1248,6 @@ "Menu_disabled_urlNotMatch": { "message": "Kann auf dieser Seite nicht ausgeführt werden (Seiten-URL stimmt nicht überein)" }, - "commandHub_add_success": { - "message": "Befehl hinzugefügt!" - }, - "commandHub_add_error": { - "message": "Fehler beim Hinzufügen des Befehls." - }, "commandHub_delete_success": { "message": "Befehl gelöscht." }, diff --git a/packages/extension/public/_locales/en/messages.json b/packages/extension/public/_locales/en/messages.json index 2245b928..175ff58d 100644 --- a/packages/extension/public/_locales/en/messages.json +++ b/packages/extension/public/_locales/en/messages.json @@ -1254,12 +1254,6 @@ "Menu_disabled_urlNotMatch": { "message": "Cannot run on this page (page URL does not match)" }, - "commandHub_add_success": { - "message": "Command added!" - }, - "commandHub_add_error": { - "message": "Failed to add command." - }, "commandHub_delete_success": { "message": "Command deleted." }, diff --git a/packages/extension/public/_locales/es/messages.json b/packages/extension/public/_locales/es/messages.json index 40fa7b2c..5d2bebfd 100644 --- a/packages/extension/public/_locales/es/messages.json +++ b/packages/extension/public/_locales/es/messages.json @@ -1248,12 +1248,6 @@ "Menu_disabled_urlNotMatch": { "message": "No se puede ejecutar en esta página (la URL de la página no coincide)" }, - "commandHub_add_success": { - "message": "¡Comando añadido!" - }, - "commandHub_add_error": { - "message": "Error al añadir el comando." - }, "commandHub_delete_success": { "message": "Comando eliminado." }, diff --git a/packages/extension/public/_locales/fr/messages.json b/packages/extension/public/_locales/fr/messages.json index 8cb7f21c..2eb5e1b5 100644 --- a/packages/extension/public/_locales/fr/messages.json +++ b/packages/extension/public/_locales/fr/messages.json @@ -1248,12 +1248,6 @@ "Menu_disabled_urlNotMatch": { "message": "Impossible d'exécuter sur cette page (l'URL de la page ne correspond pas)" }, - "commandHub_add_success": { - "message": "Commande ajoutée !" - }, - "commandHub_add_error": { - "message": "Échec de l'ajout de la commande." - }, "commandHub_delete_success": { "message": "Commande supprimée." }, diff --git a/packages/extension/public/_locales/hi/messages.json b/packages/extension/public/_locales/hi/messages.json index c12baec9..6af9bc97 100644 --- a/packages/extension/public/_locales/hi/messages.json +++ b/packages/extension/public/_locales/hi/messages.json @@ -1248,12 +1248,6 @@ "Menu_disabled_urlNotMatch": { "message": "इस पेज पर नहीं चला सकते (पृष्ठ URL मेल नहीं खाता)" }, - "commandHub_add_success": { - "message": "कमांड जोड़ा गया!" - }, - "commandHub_add_error": { - "message": "कमांड जोड़ने में विफल।" - }, "commandHub_delete_success": { "message": "कमांड हटाया गया।" }, diff --git a/packages/extension/public/_locales/id/messages.json b/packages/extension/public/_locales/id/messages.json index 0fc81d1c..68ea8987 100644 --- a/packages/extension/public/_locales/id/messages.json +++ b/packages/extension/public/_locales/id/messages.json @@ -1251,12 +1251,6 @@ "Menu_disabled_urlNotMatch": { "message": "Tidak dapat dijalankan di halaman ini (URL Halaman tidak cocok)" }, - "commandHub_add_success": { - "message": "Perintah ditambahkan!" - }, - "commandHub_add_error": { - "message": "Gagal menambahkan perintah." - }, "commandHub_delete_success": { "message": "Perintah dihapus." }, diff --git a/packages/extension/public/_locales/it/messages.json b/packages/extension/public/_locales/it/messages.json index 4a81f221..2ac9b205 100644 --- a/packages/extension/public/_locales/it/messages.json +++ b/packages/extension/public/_locales/it/messages.json @@ -1248,12 +1248,6 @@ "Menu_disabled_urlNotMatch": { "message": "Impossibile eseguire su questa pagina (l'URL della pagina non corrisponde)" }, - "commandHub_add_success": { - "message": "Comando aggiunto!" - }, - "commandHub_add_error": { - "message": "Impossibile aggiungere il comando." - }, "commandHub_delete_success": { "message": "Comando eliminato." }, diff --git a/packages/extension/public/_locales/ja/messages.json b/packages/extension/public/_locales/ja/messages.json index 0a85400d..a4705f5b 100644 --- a/packages/extension/public/_locales/ja/messages.json +++ b/packages/extension/public/_locales/ja/messages.json @@ -1245,12 +1245,6 @@ "Menu_disabled_urlNotMatch": { "message": "このページでは実行できません(ページURLが一致しません)" }, - "commandHub_add_success": { - "message": "コマンドを追加しました!" - }, - "commandHub_add_error": { - "message": "コマンドの追加に失敗しました。" - }, "commandHub_delete_success": { "message": "コマンドを削除しました。" }, diff --git a/packages/extension/public/_locales/ko/messages.json b/packages/extension/public/_locales/ko/messages.json index ff0d2e5d..2edd867f 100644 --- a/packages/extension/public/_locales/ko/messages.json +++ b/packages/extension/public/_locales/ko/messages.json @@ -1248,12 +1248,6 @@ "Menu_disabled_urlNotMatch": { "message": "이 페이지에서 실행할 수 없습니다 (페이지 URL이 일치하지 않습니다)" }, - "commandHub_add_success": { - "message": "명령이 추가되었습니다!" - }, - "commandHub_add_error": { - "message": "명령 추가에 실패했습니다." - }, "commandHub_delete_success": { "message": "명령이 삭제되었습니다." }, diff --git a/packages/extension/public/_locales/ms/messages.json b/packages/extension/public/_locales/ms/messages.json index 831d3d13..3a275d72 100644 --- a/packages/extension/public/_locales/ms/messages.json +++ b/packages/extension/public/_locales/ms/messages.json @@ -1251,12 +1251,6 @@ "Menu_disabled_urlNotMatch": { "message": "Tidak boleh dijalankan pada halaman ini (URL Halaman tidak sepadan)" }, - "commandHub_add_success": { - "message": "Arahan ditambahkan!" - }, - "commandHub_add_error": { - "message": "Gagal menambah arahan." - }, "commandHub_delete_success": { "message": "Arahan dipadam." }, diff --git a/packages/extension/public/_locales/pt_BR/messages.json b/packages/extension/public/_locales/pt_BR/messages.json index e5767ffc..2853f189 100644 --- a/packages/extension/public/_locales/pt_BR/messages.json +++ b/packages/extension/public/_locales/pt_BR/messages.json @@ -1251,12 +1251,6 @@ "Menu_disabled_urlNotMatch": { "message": "Não é possível executar nesta página (URL da página não corresponde)" }, - "commandHub_add_success": { - "message": "Comando adicionado!" - }, - "commandHub_add_error": { - "message": "Falha ao adicionar o comando." - }, "commandHub_delete_success": { "message": "Comando excluído." }, diff --git a/packages/extension/public/_locales/pt_PT/messages.json b/packages/extension/public/_locales/pt_PT/messages.json index a02df32b..61743228 100644 --- a/packages/extension/public/_locales/pt_PT/messages.json +++ b/packages/extension/public/_locales/pt_PT/messages.json @@ -1251,12 +1251,6 @@ "Menu_disabled_urlNotMatch": { "message": "Não é possível executar nesta página (URL da página não corresponde)" }, - "commandHub_add_success": { - "message": "Comando adicionado!" - }, - "commandHub_add_error": { - "message": "Falha ao adicionar o comando." - }, "commandHub_delete_success": { "message": "Comando eliminado." }, diff --git a/packages/extension/public/_locales/ru/messages.json b/packages/extension/public/_locales/ru/messages.json index caac6a56..efebd2fd 100644 --- a/packages/extension/public/_locales/ru/messages.json +++ b/packages/extension/public/_locales/ru/messages.json @@ -1248,12 +1248,6 @@ "Menu_disabled_urlNotMatch": { "message": "Невозможно выполнить на этой странице (URL страницы не совпадает)" }, - "commandHub_add_success": { - "message": "Команда добавлена!" - }, - "commandHub_add_error": { - "message": "Не удалось добавить команду." - }, "commandHub_delete_success": { "message": "Команда удалена." }, diff --git a/packages/extension/public/_locales/zh_CN/messages.json b/packages/extension/public/_locales/zh_CN/messages.json index 829d0086..58b5a984 100644 --- a/packages/extension/public/_locales/zh_CN/messages.json +++ b/packages/extension/public/_locales/zh_CN/messages.json @@ -1248,12 +1248,6 @@ "Menu_disabled_urlNotMatch": { "message": "无法在此页面运行(页面URL不匹配)" }, - "commandHub_add_success": { - "message": "命令已添加!" - }, - "commandHub_add_error": { - "message": "添加命令失败。" - }, "commandHub_delete_success": { "message": "命令已删除。" }, diff --git a/packages/extension/src/components/commandHub/DownloadButton.tsx b/packages/extension/src/components/commandHub/DownloadButton.tsx index da1abb6d..8b1a6209 100644 --- a/packages/extension/src/components/commandHub/DownloadButton.tsx +++ b/packages/extension/src/components/commandHub/DownloadButton.tsx @@ -212,11 +212,6 @@ export const DownloadButton = (): JSX.Element => { if (typeof command !== "string") return Ipc.send(BgCommand.addCommand, { command }) .then(async (res) => { - if (res) { - toast.success(t("commandHub_add_success")) - } else { - toast.error(t("commandHub_add_error")) - } const install_id = await getOrCreateClientId() ;(event.source as WindowProxy)?.postMessage( { action: "AddCommandAck", result: !!res, install_id }, @@ -224,7 +219,6 @@ export const DownloadButton = (): JSX.Element => { ) }) .catch(async () => { - toast.error(t("commandHub_add_error")) const install_id = await getOrCreateClientId() ;(event.source as WindowProxy)?.postMessage( { action: "AddCommandAck", result: false, install_id }, From 66c37cf24b31c31f4fcc63423835a5f141a3d1a2 Mon Sep 17 00:00:00 2001 From: ujiro99 Date: Tue, 5 May 2026 11:17:02 +0900 Subject: [PATCH 3/9] Update: Implement functionality to add and remove commands. --- .../components/commandHub/DownloadButton.tsx | 308 ++++++------------ .../src/components/commandHub/StarButton.tsx | 93 ------ .../src/hooks/useDetectUrlChanged.ts | 15 +- 3 files changed, 107 insertions(+), 309 deletions(-) delete mode 100644 packages/extension/src/components/commandHub/StarButton.tsx diff --git a/packages/extension/src/components/commandHub/DownloadButton.tsx b/packages/extension/src/components/commandHub/DownloadButton.tsx index 8b1a6209..a774b0d2 100644 --- a/packages/extension/src/components/commandHub/DownloadButton.tsx +++ b/packages/extension/src/components/commandHub/DownloadButton.tsx @@ -1,6 +1,4 @@ -import { useState, useEffect } from "react" -import clsx from "clsx" -import { toast, Toaster } from "sonner" +import { useEffect, useCallback } from "react" import { Ipc, BgCommand } from "@/services/ipc" import { useSection } from "@/hooks/useSettings" import { useDetectUrlChanged } from "@/hooks/useDetectUrlChanged" @@ -10,199 +8,113 @@ import { ANALYTICS_EVENTS, getOrCreateClientId, } from "@/services/analytics" -import { - Popover, - PopoverContent, - PopoverAnchor, - PopoverArrow, -} from "@/components/ui/popover" import { SCREEN, HUB_URL } from "@/const" -import { t } from "@/services/i18n" - -const TooltipDuration = 2000 +/** + * External postMessage API for adding/deleting commands from the Hub. + * + * This content script listens for messages from the Hub page (origin must match HUB_URL). + * The message object must have the following shape: + * + * --- AddCommand --- + * { + * action: "AddCommand", + * command: string // JSON-stringified command object (see below) + * } + * + * The `command` field is a JSON string representing a SearchCommand, an AiPromptCommand, or a PageActionCommand. + * + * SearchCommand (openMode is one of "popup" | "tab" | "window" | "backgroundTab" | "sidePanel"): + * { + * id: string, // Unique command identifier + * title: string, // Display name of the command + * searchUrl: string, // Search URL template (%s is replaced with selected text) + * iconUrl: string, // URL of the command icon + * openMode: string, // How to open the result: "popup" | "tab" | "window" | "backgroundTab" | "sidePanel" + * openModeSecondary?: string, // Secondary open mode (optional) + * spaceEncoding?: string, // Space encoding in URL: "plus" | "percent" (optional) + * sourceType?: string, // Origin of the command: "default" | "selfCreated" | "hubCommunity" | "unknown" (optional) + * sourceId?: string // Identifier of the source (optional) + * } + * + * AiPromptCommand (openMode is "aiPrompt"): + * { + * id: string, // Unique command identifier + * title: string, // Display name of the command + * iconUrl: string, // URL of the command icon + * openMode: "aiPrompt", // Must be "aiPrompt" for AI prompt commands + * aiPromptOption: { + * serviceId: string, // ID of the AI service to use (see hub/public/data/ai-services.json) + * prompt: string, // Prompt text sent to the AI service (supports variable placeholders) + * openMode: string // How to open the AI service result: "popup" | "tab" | "window" | etc. + * }, + * sourceType?: string, // Origin of the command: "default" | "selfCreated" | "hubCommunity" | "unknown" (optional) + * sourceId?: string // Identifier of the source (optional) + * } + * + * PageActionCommand (openMode is "pageAction"): + * { + * id: string, // Unique command identifier + * title: string, // Display name of the command + * iconUrl: string, // URL of the command icon + * openMode: "pageAction", // Must be "pageAction" for page action commands + * pageActionOption: { + * startUrl: string, // URL to open when executing the page action + * pageUrl?: string, // URL pattern for command enablement (currentTab mode only, optional) + * openMode: string, // How to open the page: "none" | "popup" | "tab" | "backgroundTab" | "window" | "currentTab" + * steps: Array, // Sequence of automation steps to execute + * userVariables?: Array<{ name: string, value: string }> // User-defined variables (optional) + * }, + * sourceType?: string, // Origin of the command: "default" | "selfCreated" | "hubCommunity" | "unknown" (optional) + * sourceId?: string // Identifier of the source (optional) + * } + * + * --- DeleteCommand --- + * { + * action: "DeleteCommand", + * id: string // ID of the command to remove + * } + * + * --- AddCommandAck (response) --- + * { + * action: "AddCommandAck", + * result: boolean, // true if the command was added successfully, false otherwise + * install_id: string // stable anonymous identifier per extension install (UUID, persisted in chrome.storage.local) + * } + * + * --- DeleteCommandAck (response) --- + * { + * action: "DeleteCommandAck", + * result: boolean // true if the command was removed successfully, false otherwise + * } + */ export const DownloadButton = (): JSX.Element => { - const [position, setPosition] = useState(null) const { data: commands } = useSection(CACHE_SECTIONS.COMMANDS) - const { addUrlChangeListener, removeUrlChangeListener } = - useDetectUrlChanged() - const [shouldRender, setShouldRender] = useState(false) - const open = position != null - - const setButtonClickListener = () => { - document.querySelectorAll("button[data-command]").forEach((button) => { - if (!(button instanceof HTMLButtonElement)) return - const command = button.dataset.command - const id = button.dataset.id - if (command == null) return - button.dataset.clickable = "true" + const { addUrlChangeListener } = useDetectUrlChanged() - // Deprecated: - // We will remove this in the future. - // Please use postMessage to communicate with the content script. - button.addEventListener("click", () => { - Ipc.send(BgCommand.addCommand, { command }).then((res) => { - if (res) { - sendEvent( - ANALYTICS_EVENTS.COMMAND_HUB_ADD, - { id }, - SCREEN.COMMAND_HUB, - ) - setPosition(button.parentElement) - } - }) - }) - }) - } - - const updateButtonVisibility = () => { + const updateInstalledState = useCallback(() => { const ids = commands?.map((c) => c.id) ?? [] - ids.forEach((id) => { - // hide installed buttons - const installed = document.querySelector( - `button[data-id="${id}"]`, - ) as HTMLElement - if (installed) installed.style.display = "none" - // show installed label - const p = document.querySelector(`p[data-id="${id}"]`) as HTMLElement - if (p) p.style.display = "block" - }) - } - - const updateCount = () => { - document.querySelectorAll("span[data-id]").forEach((span) => { - if (!(span instanceof HTMLElement)) return - const count = Number(span.dataset.downloadCount) - if (count == null || isNaN(count)) return - let reviced = 0 - const cmd = commands?.find((c) => c.id === span.dataset.id) - if (cmd != null) { - // There is a command. - reviced++ + const buttons = document.querySelectorAll( + `button[data-id]`, + ) as NodeListOf + buttons.forEach((button) => { + const id = button.dataset.id + if (id && ids.includes(id)) { + button.dataset.installed = "true" + } else { + delete button.dataset.installed } - span.textContent = (count + reviced).toLocaleString() }) - } - - useEffect(() => { - setButtonClickListener() - updateButtonVisibility() - addUrlChangeListener(setButtonClickListener) - addUrlChangeListener(updateButtonVisibility) - return () => { - removeUrlChangeListener(setButtonClickListener) - } - }, []) - - useEffect(() => { - updateButtonVisibility() - updateCount() - addUrlChangeListener(updateButtonVisibility) - addUrlChangeListener(updateCount) - return () => { - removeUrlChangeListener(updateButtonVisibility) - removeUrlChangeListener(updateCount) - } }, [commands]) useEffect(() => { - if (!open) return - const timer = setTimeout(() => setPosition(null), TooltipDuration) - return () => { - clearTimeout(timer) - } - }, [open]) + updateInstalledState() + }, [updateInstalledState]) useEffect(() => { - let timer: NodeJS.Timeout - if (open) { - timer = setTimeout(() => { - setShouldRender(true) - }, 100) - } else { - setShouldRender(false) - } - return () => clearTimeout(timer) - }, [open]) + return addUrlChangeListener(updateInstalledState) + }, [commands, addUrlChangeListener, updateInstalledState]) - /** - * External postMessage API for adding/deleting commands from the Hub. - * - * This content script listens for messages from the Hub page (origin must match HUB_URL). - * The message object must have the following shape: - * - * --- AddCommand --- - * { - * action: "AddCommand", - * command: string // JSON-stringified command object (see below) - * } - * - * The `command` field is a JSON string representing a SearchCommand, an AiPromptCommand, or a PageActionCommand. - * - * SearchCommand (openMode is one of "popup" | "tab" | "window" | "backgroundTab" | "sidePanel"): - * { - * id: string, // Unique command identifier - * title: string, // Display name of the command - * searchUrl: string, // Search URL template (%s is replaced with selected text) - * iconUrl: string, // URL of the command icon - * openMode: string, // How to open the result: "popup" | "tab" | "window" | "backgroundTab" | "sidePanel" - * openModeSecondary?: string, // Secondary open mode (optional) - * spaceEncoding?: string, // Space encoding in URL: "plus" | "percent" (optional) - * sourceType?: string, // Origin of the command: "default" | "selfCreated" | "hubCommunity" | "unknown" (optional) - * sourceId?: string // Identifier of the source (optional) - * } - * - * AiPromptCommand (openMode is "aiPrompt"): - * { - * id: string, // Unique command identifier - * title: string, // Display name of the command - * iconUrl: string, // URL of the command icon - * openMode: "aiPrompt", // Must be "aiPrompt" for AI prompt commands - * aiPromptOption: { - * serviceId: string, // ID of the AI service to use (see hub/public/data/ai-services.json) - * prompt: string, // Prompt text sent to the AI service (supports variable placeholders) - * openMode: string // How to open the AI service result: "popup" | "tab" | "window" | etc. - * }, - * sourceType?: string, // Origin of the command: "default" | "selfCreated" | "hubCommunity" | "unknown" (optional) - * sourceId?: string // Identifier of the source (optional) - * } - * - * PageActionCommand (openMode is "pageAction"): - * { - * id: string, // Unique command identifier - * title: string, // Display name of the command - * iconUrl: string, // URL of the command icon - * openMode: "pageAction", // Must be "pageAction" for page action commands - * pageActionOption: { - * startUrl: string, // URL to open when executing the page action - * pageUrl?: string, // URL pattern for command enablement (currentTab mode only, optional) - * openMode: string, // How to open the page: "none" | "popup" | "tab" | "backgroundTab" | "window" | "currentTab" - * steps: Array, // Sequence of automation steps to execute - * userVariables?: Array<{ name: string, value: string }> // User-defined variables (optional) - * }, - * sourceType?: string, // Origin of the command: "default" | "selfCreated" | "hubCommunity" | "unknown" (optional) - * sourceId?: string // Identifier of the source (optional) - * } - * - * --- DeleteCommand --- - * { - * action: "DeleteCommand", - * id: string // ID of the command to remove - * } - * - * --- AddCommandAck (response) --- - * { - * action: "AddCommandAck", - * result: boolean, // true if the command was added successfully, false otherwise - * install_id: string // stable anonymous identifier per extension install (UUID, persisted in chrome.storage.local) - * } - * - * --- DeleteCommandAck (response) --- - * { - * action: "DeleteCommandAck", - * result: boolean // true if the command was removed successfully, false otherwise - * } - */ useEffect(() => { const hubOrigin = new URL(HUB_URL).origin const handleMessage = (event: MessageEvent) => { @@ -217,6 +129,11 @@ export const DownloadButton = (): JSX.Element => { { action: "AddCommandAck", result: !!res, install_id }, { targetOrigin: event.origin }, ) + await sendEvent( + ANALYTICS_EVENTS.COMMAND_HUB_ADD, + { id }, + SCREEN.COMMAND_HUB, + ) }) .catch(async () => { const install_id = await getOrCreateClientId() @@ -229,18 +146,12 @@ export const DownloadButton = (): JSX.Element => { if (typeof id !== "string") return Ipc.send(BgCommand.removeCommand, { id }) .then((res) => { - if (res) { - toast.success(t("commandHub_delete_success")) - } else { - toast.error(t("commandHub_delete_error")) - } ;(event.source as WindowProxy)?.postMessage( { action: "DeleteCommandAck", result: !!res }, { targetOrigin: event.origin }, ) }) .catch(() => { - toast.error(t("commandHub_delete_error")) ;(event.source as WindowProxy)?.postMessage( { action: "DeleteCommandAck", result: false }, { targetOrigin: event.origin }, @@ -254,24 +165,5 @@ export const DownloadButton = (): JSX.Element => { } }, []) - return ( - <> - - - - {shouldRender && ( - - Command added! - - - )} - - - ) + return <> } diff --git a/packages/extension/src/components/commandHub/StarButton.tsx b/packages/extension/src/components/commandHub/StarButton.tsx deleted file mode 100644 index 3f172c25..00000000 --- a/packages/extension/src/components/commandHub/StarButton.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { useEffect, useCallback } from "react" -import { useSection } from "@/hooks/useSettings" -import { CACHE_SECTIONS } from "@/services/settings/settingsCache" -import { sendEvent, ANALYTICS_EVENTS } from "@/services/analytics" -import { SCREEN } from "@/const" -import { useDetectUrlChanged } from "@/hooks/useDetectUrlChanged" -import { Ipc, BgCommand } from "@/services/ipc" - -function isButtonElement(elm: Element): elm is HTMLButtonElement { - return elm.tagName?.toLowerCase() === "button" -} - -function findButtonElement(elm: Element): HTMLButtonElement | undefined { - if (elm == null) return undefined - if (elm.nodeName === "body") return undefined - if (isButtonElement(elm)) return elm - return findButtonElement(elm.parentElement as Element) -} - -export const StarButton = (): JSX.Element => { - const { data: stars } = useSection(CACHE_SECTIONS.STARS) - const { addUrlChangeListener, removeUrlChangeListener } = - useDetectUrlChanged() - - const updateStar = useCallback( - (e: MouseEvent) => { - const button = findButtonElement(e.target as Element) - const id = button?.dataset.starId - if (id == null) return - const found = stars?.some((s) => s.id === id) ?? false - sendEvent( - found - ? ANALYTICS_EVENTS.COMMAND_HUB_STAR_REMOVE - : ANALYTICS_EVENTS.COMMAND_HUB_STAR_ADD, - { id }, - SCREEN.COMMAND_HUB, - ) - Ipc.send(BgCommand.toggleStar, { id }) - }, - [stars], - ) - - const updateButton = useCallback(() => { - document.querySelectorAll("button[data-star-id]").forEach((button) => { - if (!(button instanceof HTMLButtonElement)) return - const id = button.dataset.starId - if (id == null) return - button.addEventListener("click", updateStar) - button.dataset.clickable = "true" - if (stars?.some((s) => s.id === id)) { - button.dataset.starred = "true" - } else { - button.dataset.starred = "false" - } - }) - }, [stars, updateStar]) - - const updateCount = useCallback(() => { - document.querySelectorAll("span[data-star-id]").forEach((span) => { - if (!(span instanceof HTMLElement)) return - const count = Number(span.dataset.starCount) - if (count == null || isNaN(count)) return - let reviced = 0 - const star = stars?.find((s) => s.id === span.dataset.starId) - if (star != null) { - // There is a new star. - reviced++ - } - span.textContent = (count + reviced).toLocaleString() - }) - }, [stars]) - - const removeButtonEvent = useCallback(() => { - document.querySelectorAll("button[data-star-id]").forEach((button) => { - if (!(button instanceof HTMLButtonElement)) return - button.removeEventListener("click", updateStar) - }) - }, [updateStar]) - - useEffect(() => { - updateButton() - updateCount() - addUrlChangeListener(updateButton) - addUrlChangeListener(updateCount) - return () => { - removeUrlChangeListener(updateButton) - removeUrlChangeListener(updateCount) - removeButtonEvent() - } - }, [stars]) - - return <> -} diff --git a/packages/extension/src/hooks/useDetectUrlChanged.ts b/packages/extension/src/hooks/useDetectUrlChanged.ts index 342571e6..6ccbd3c9 100644 --- a/packages/extension/src/hooks/useDetectUrlChanged.ts +++ b/packages/extension/src/hooks/useDetectUrlChanged.ts @@ -1,4 +1,4 @@ -import { useState, useEffect } from "react" +import { useState, useEffect, useCallback } from "react" type Listener = () => void type Listeners = Listener[] @@ -6,13 +6,12 @@ type Listeners = Listener[] export function useDetectUrlChanged() { const [listener, setListener] = useState([]) - const addUrlChangeListener = (l: Listener) => { + const addUrlChangeListener = useCallback((l: Listener) => { setListener((prev) => [...prev, l]) - } - - const removeUrlChangeListener = (l: Listener) => { - setListener((prev) => prev.filter((f) => f !== l)) - } + return () => { + setListener((prev) => prev.filter((f) => f !== l)) + } + }, []) useEffect(() => { const observeUrlChange = () => { @@ -31,5 +30,5 @@ export function useDetectUrlChanged() { return () => observer.disconnect() }, [listener]) - return { addUrlChangeListener, removeUrlChangeListener } + return { addUrlChangeListener } } From 2d7e9c7fdd0ff6c4c00982bd473b5731ad49be1c Mon Sep 17 00:00:00 2001 From: ujiro99 Date: Wed, 6 May 2026 11:22:18 +0900 Subject: [PATCH 4/9] Update: Convert CommandHub.tsx into a custom hook. --- .../src/components/commandHub/CommandHub.tsx | 11 +++-------- .../useCommandHubBridge.ts} | 8 ++++---- 2 files changed, 7 insertions(+), 12 deletions(-) rename packages/extension/src/{components/commandHub/DownloadButton.tsx => hooks/useCommandHubBridge.ts} (98%) diff --git a/packages/extension/src/components/commandHub/CommandHub.tsx b/packages/extension/src/components/commandHub/CommandHub.tsx index 0fcbbd2b..b730d975 100644 --- a/packages/extension/src/components/commandHub/CommandHub.tsx +++ b/packages/extension/src/components/commandHub/CommandHub.tsx @@ -1,11 +1,6 @@ -import { DownloadButton } from "@/components/commandHub/DownloadButton" -import { StarButton } from "@/components/commandHub/StarButton" +import { useCommandHubBridge } from "@/hooks/useCommandHubBridge" export const CommandHub = (): JSX.Element => { - return ( - <> - - - - ) + useCommandHubBridge() + return <> } diff --git a/packages/extension/src/components/commandHub/DownloadButton.tsx b/packages/extension/src/hooks/useCommandHubBridge.ts similarity index 98% rename from packages/extension/src/components/commandHub/DownloadButton.tsx rename to packages/extension/src/hooks/useCommandHubBridge.ts index a774b0d2..d3ebc0e4 100644 --- a/packages/extension/src/components/commandHub/DownloadButton.tsx +++ b/packages/extension/src/hooks/useCommandHubBridge.ts @@ -9,6 +9,7 @@ import { getOrCreateClientId, } from "@/services/analytics" import { SCREEN, HUB_URL } from "@/const" + /** * External postMessage API for adding/deleting commands from the Hub. * @@ -88,12 +89,13 @@ import { SCREEN, HUB_URL } from "@/const" * } */ -export const DownloadButton = (): JSX.Element => { +export function useCommandHubBridge() { const { data: commands } = useSection(CACHE_SECTIONS.COMMANDS) const { addUrlChangeListener } = useDetectUrlChanged() const updateInstalledState = useCallback(() => { const ids = commands?.map((c) => c.id) ?? [] + if (ids.length === 0) return const buttons = document.querySelectorAll( `button[data-id]`, ) as NodeListOf @@ -102,7 +104,7 @@ export const DownloadButton = (): JSX.Element => { if (id && ids.includes(id)) { button.dataset.installed = "true" } else { - delete button.dataset.installed + button.dataset.installed = "false" } }) }, [commands]) @@ -164,6 +166,4 @@ export const DownloadButton = (): JSX.Element => { window.removeEventListener("message", handleMessage) } }, []) - - return <> } From ae9e743d8058340ca52270cc884a4bcc6ca4bab4 Mon Sep 17 00:00:00 2001 From: ujiro99 Date: Wed, 6 May 2026 18:52:30 +0900 Subject: [PATCH 5/9] Update: Notify the installation status via postMessage instead of updating the DOM. --- .../src/hooks/useCommandHubBridge.ts | 56 +++++++++++-------- .../src/hooks/useDetectUrlChanged.ts | 34 ----------- 2 files changed, 33 insertions(+), 57 deletions(-) delete mode 100644 packages/extension/src/hooks/useDetectUrlChanged.ts diff --git a/packages/extension/src/hooks/useCommandHubBridge.ts b/packages/extension/src/hooks/useCommandHubBridge.ts index d3ebc0e4..3bec3d46 100644 --- a/packages/extension/src/hooks/useCommandHubBridge.ts +++ b/packages/extension/src/hooks/useCommandHubBridge.ts @@ -1,7 +1,6 @@ -import { useEffect, useCallback } from "react" +import { useEffect, useRef } from "react" import { Ipc, BgCommand } from "@/services/ipc" import { useSection } from "@/hooks/useSettings" -import { useDetectUrlChanged } from "@/hooks/useDetectUrlChanged" import { CACHE_SECTIONS } from "@/services/settings/settingsCache" import { sendEvent, @@ -87,35 +86,40 @@ import { SCREEN, HUB_URL } from "@/const" * action: "DeleteCommandAck", * result: boolean // true if the command was removed successfully, false otherwise * } + * + * --- RequestInstalledCommand (from Hub) --- + * { + * action: "RequestInstalledCommand" + * } + * + * --- SyncInstalledCommand (response / proactive push) --- + * { + * action: "SyncInstalledCommand", + * installedIds: string[] // IDs of all currently installed commands + * } */ export function useCommandHubBridge() { const { data: commands } = useSection(CACHE_SECTIONS.COMMANDS) - const { addUrlChangeListener } = useDetectUrlChanged() - - const updateInstalledState = useCallback(() => { - const ids = commands?.map((c) => c.id) ?? [] - if (ids.length === 0) return - const buttons = document.querySelectorAll( - `button[data-id]`, - ) as NodeListOf - buttons.forEach((button) => { - const id = button.dataset.id - if (id && ids.includes(id)) { - button.dataset.installed = "true" - } else { - button.dataset.installed = "false" - } - }) - }, [commands]) + // Ref keeps the message handler (empty deps) in sync with the latest commands + // without needing to recreate the listener on every change. + const commandsRef = useRef(commands) useEffect(() => { - updateInstalledState() - }, [updateInstalledState]) + commandsRef.current = commands + }, [commands]) + // Proactively push installed IDs to the Hub whenever the commands list changes. useEffect(() => { - return addUrlChangeListener(updateInstalledState) - }, [commands, addUrlChangeListener, updateInstalledState]) + if (commands == null) return + window.postMessage( + { + action: "SyncInstalledCommand", + installedIds: commands.map((c) => c.id), + }, + "*", + ) + }, [commands]) useEffect(() => { const hubOrigin = new URL(HUB_URL).origin @@ -159,6 +163,12 @@ export function useCommandHubBridge() { { targetOrigin: event.origin }, ) }) + } else if (action === "RequestInstalledCommand") { + const ids = commandsRef.current?.map((c) => c.id) ?? [] + window.postMessage( + { action: "SyncInstalledCommand", installedIds: ids }, + "*", + ) } } window.addEventListener("message", handleMessage) diff --git a/packages/extension/src/hooks/useDetectUrlChanged.ts b/packages/extension/src/hooks/useDetectUrlChanged.ts deleted file mode 100644 index 6ccbd3c9..00000000 --- a/packages/extension/src/hooks/useDetectUrlChanged.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { useState, useEffect, useCallback } from "react" - -type Listener = () => void -type Listeners = Listener[] - -export function useDetectUrlChanged() { - const [listener, setListener] = useState([]) - - const addUrlChangeListener = useCallback((l: Listener) => { - setListener((prev) => [...prev, l]) - return () => { - setListener((prev) => prev.filter((f) => f !== l)) - } - }, []) - - useEffect(() => { - const observeUrlChange = () => { - let oldHref = document.location.href - const body = document.querySelector("body") - const observer = new MutationObserver(() => { - if (oldHref !== document.location.href) { - oldHref = document.location.href - listener.forEach((l) => l()) - } - }) - observer.observe(body as Node, { childList: true, subtree: true }) - return observer - } - const observer = observeUrlChange() - return () => observer.disconnect() - }, [listener]) - - return { addUrlChangeListener } -} From d084c703e8b21b3184b0a87761922fe2859cb74d Mon Sep 17 00:00:00 2001 From: ujiro99 Date: Wed, 6 May 2026 21:48:38 +0900 Subject: [PATCH 6/9] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- packages/extension/src/hooks/useCommandHubBridge.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/extension/src/hooks/useCommandHubBridge.ts b/packages/extension/src/hooks/useCommandHubBridge.ts index 3bec3d46..29407bb7 100644 --- a/packages/extension/src/hooks/useCommandHubBridge.ts +++ b/packages/extension/src/hooks/useCommandHubBridge.ts @@ -165,9 +165,9 @@ export function useCommandHubBridge() { }) } else if (action === "RequestInstalledCommand") { const ids = commandsRef.current?.map((c) => c.id) ?? [] - window.postMessage( + ;(event.source as WindowProxy)?.postMessage( { action: "SyncInstalledCommand", installedIds: ids }, - "*", + { targetOrigin: event.origin }, ) } } From d8fb6c557c0fdbe22cc94592a3d7b3649926c05b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 6 May 2026 12:54:35 +0000 Subject: [PATCH 7/9] Fix postMessage security and analytics issues in useCommandHubBridge Agent-Logs-Url: https://github.com/ujiro99/selection-command/sessions/18e06b32-4e15-4ae5-b730-1c9caebcb9d2 Co-authored-by: ujiro99 <677231+ujiro99@users.noreply.github.com> --- .../src/hooks/useCommandHubBridge.ts | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/extension/src/hooks/useCommandHubBridge.ts b/packages/extension/src/hooks/useCommandHubBridge.ts index 29407bb7..83136985 100644 --- a/packages/extension/src/hooks/useCommandHubBridge.ts +++ b/packages/extension/src/hooks/useCommandHubBridge.ts @@ -7,12 +7,14 @@ import { ANALYTICS_EVENTS, getOrCreateClientId, } from "@/services/analytics" -import { SCREEN, HUB_URL } from "@/const" +import { SCREEN, NEW_HUB_URL } from "@/const" + +const hubOrigin = new URL(NEW_HUB_URL).origin /** * External postMessage API for adding/deleting commands from the Hub. * - * This content script listens for messages from the Hub page (origin must match HUB_URL). + * This content script listens for messages from the Hub page (origin must match NEW_HUB_URL). * The message object must have the following shape: * * --- AddCommand --- @@ -117,12 +119,11 @@ export function useCommandHubBridge() { action: "SyncInstalledCommand", installedIds: commands.map((c) => c.id), }, - "*", + hubOrigin, ) }, [commands]) useEffect(() => { - const hubOrigin = new URL(HUB_URL).origin const handleMessage = (event: MessageEvent) => { if (event.origin !== hubOrigin) return const { action, command, id } = event.data ?? {} @@ -135,11 +136,19 @@ export function useCommandHubBridge() { { action: "AddCommandAck", result: !!res, install_id }, { targetOrigin: event.origin }, ) - await sendEvent( - ANALYTICS_EVENTS.COMMAND_HUB_ADD, - { id }, - SCREEN.COMMAND_HUB, - ) + if (res) { + let commandId: string | undefined + try { + commandId = JSON.parse(command).id + } catch { + // Ignore parse errors; analytics will be sent without id + } + await sendEvent( + ANALYTICS_EVENTS.COMMAND_HUB_ADD, + { id: commandId }, + SCREEN.COMMAND_HUB, + ) + } }) .catch(async () => { const install_id = await getOrCreateClientId() From 8d001d974c4264398444b1637bfca8c96fc770a5 Mon Sep 17 00:00:00 2001 From: ujiro99 Date: Wed, 6 May 2026 21:55:56 +0900 Subject: [PATCH 8/9] fix: review comments. # Conflicts: # packages/extension/src/hooks/useCommandHubBridge.ts --- packages/extension/public/_locales/de/messages.json | 8 +------- packages/extension/public/_locales/en/messages.json | 6 ------ packages/extension/public/_locales/es/messages.json | 8 +------- packages/extension/public/_locales/fr/messages.json | 8 +------- packages/extension/public/_locales/hi/messages.json | 8 +------- packages/extension/public/_locales/id/messages.json | 8 +------- packages/extension/public/_locales/it/messages.json | 8 +------- packages/extension/public/_locales/ja/messages.json | 6 ------ packages/extension/public/_locales/ko/messages.json | 8 +------- packages/extension/public/_locales/ms/messages.json | 8 +------- packages/extension/public/_locales/pt_BR/messages.json | 8 +------- packages/extension/public/_locales/pt_PT/messages.json | 8 +------- packages/extension/public/_locales/ru/messages.json | 8 +------- packages/extension/public/_locales/zh_CN/messages.json | 8 +------- packages/extension/src/hooks/useCommandHubBridge.ts | 7 +++---- 15 files changed, 15 insertions(+), 100 deletions(-) diff --git a/packages/extension/public/_locales/de/messages.json b/packages/extension/public/_locales/de/messages.json index 2582fdb4..9f7c9a61 100644 --- a/packages/extension/public/_locales/de/messages.json +++ b/packages/extension/public/_locales/de/messages.json @@ -1247,11 +1247,5 @@ }, "Menu_disabled_urlNotMatch": { "message": "Kann auf dieser Seite nicht ausgeführt werden (Seiten-URL stimmt nicht überein)" - }, - "commandHub_delete_success": { - "message": "Befehl gelöscht." - }, - "commandHub_delete_error": { - "message": "Fehler beim Löschen des Befehls." } -} \ No newline at end of file +} diff --git a/packages/extension/public/_locales/en/messages.json b/packages/extension/public/_locales/en/messages.json index 175ff58d..b6a0352f 100644 --- a/packages/extension/public/_locales/en/messages.json +++ b/packages/extension/public/_locales/en/messages.json @@ -1253,11 +1253,5 @@ }, "Menu_disabled_urlNotMatch": { "message": "Cannot run on this page (page URL does not match)" - }, - "commandHub_delete_success": { - "message": "Command deleted." - }, - "commandHub_delete_error": { - "message": "Failed to delete command." } } diff --git a/packages/extension/public/_locales/es/messages.json b/packages/extension/public/_locales/es/messages.json index 5d2bebfd..7aadf686 100644 --- a/packages/extension/public/_locales/es/messages.json +++ b/packages/extension/public/_locales/es/messages.json @@ -1247,11 +1247,5 @@ }, "Menu_disabled_urlNotMatch": { "message": "No se puede ejecutar en esta página (la URL de la página no coincide)" - }, - "commandHub_delete_success": { - "message": "Comando eliminado." - }, - "commandHub_delete_error": { - "message": "Error al eliminar el comando." } -} \ No newline at end of file +} diff --git a/packages/extension/public/_locales/fr/messages.json b/packages/extension/public/_locales/fr/messages.json index 2eb5e1b5..ca4c32f1 100644 --- a/packages/extension/public/_locales/fr/messages.json +++ b/packages/extension/public/_locales/fr/messages.json @@ -1247,11 +1247,5 @@ }, "Menu_disabled_urlNotMatch": { "message": "Impossible d'exécuter sur cette page (l'URL de la page ne correspond pas)" - }, - "commandHub_delete_success": { - "message": "Commande supprimée." - }, - "commandHub_delete_error": { - "message": "Échec de la suppression de la commande." } -} \ No newline at end of file +} diff --git a/packages/extension/public/_locales/hi/messages.json b/packages/extension/public/_locales/hi/messages.json index 6af9bc97..abd92477 100644 --- a/packages/extension/public/_locales/hi/messages.json +++ b/packages/extension/public/_locales/hi/messages.json @@ -1247,11 +1247,5 @@ }, "Menu_disabled_urlNotMatch": { "message": "इस पेज पर नहीं चला सकते (पृष्ठ URL मेल नहीं खाता)" - }, - "commandHub_delete_success": { - "message": "कमांड हटाया गया।" - }, - "commandHub_delete_error": { - "message": "कमांड हटाने में विफल।" } -} \ No newline at end of file +} diff --git a/packages/extension/public/_locales/id/messages.json b/packages/extension/public/_locales/id/messages.json index 68ea8987..effb3a26 100644 --- a/packages/extension/public/_locales/id/messages.json +++ b/packages/extension/public/_locales/id/messages.json @@ -1250,11 +1250,5 @@ }, "Menu_disabled_urlNotMatch": { "message": "Tidak dapat dijalankan di halaman ini (URL Halaman tidak cocok)" - }, - "commandHub_delete_success": { - "message": "Perintah dihapus." - }, - "commandHub_delete_error": { - "message": "Gagal menghapus perintah." } -} \ No newline at end of file +} diff --git a/packages/extension/public/_locales/it/messages.json b/packages/extension/public/_locales/it/messages.json index 2ac9b205..ca533b2a 100644 --- a/packages/extension/public/_locales/it/messages.json +++ b/packages/extension/public/_locales/it/messages.json @@ -1247,11 +1247,5 @@ }, "Menu_disabled_urlNotMatch": { "message": "Impossibile eseguire su questa pagina (l'URL della pagina non corrisponde)" - }, - "commandHub_delete_success": { - "message": "Comando eliminato." - }, - "commandHub_delete_error": { - "message": "Impossibile eliminare il comando." } -} \ No newline at end of file +} diff --git a/packages/extension/public/_locales/ja/messages.json b/packages/extension/public/_locales/ja/messages.json index a4705f5b..3ac84d7e 100644 --- a/packages/extension/public/_locales/ja/messages.json +++ b/packages/extension/public/_locales/ja/messages.json @@ -1244,11 +1244,5 @@ }, "Menu_disabled_urlNotMatch": { "message": "このページでは実行できません(ページURLが一致しません)" - }, - "commandHub_delete_success": { - "message": "コマンドを削除しました。" - }, - "commandHub_delete_error": { - "message": "コマンドの削除に失敗しました。" } } diff --git a/packages/extension/public/_locales/ko/messages.json b/packages/extension/public/_locales/ko/messages.json index 2edd867f..75c61c9f 100644 --- a/packages/extension/public/_locales/ko/messages.json +++ b/packages/extension/public/_locales/ko/messages.json @@ -1247,11 +1247,5 @@ }, "Menu_disabled_urlNotMatch": { "message": "이 페이지에서 실행할 수 없습니다 (페이지 URL이 일치하지 않습니다)" - }, - "commandHub_delete_success": { - "message": "명령이 삭제되었습니다." - }, - "commandHub_delete_error": { - "message": "명령 삭제에 실패했습니다." } -} \ No newline at end of file +} diff --git a/packages/extension/public/_locales/ms/messages.json b/packages/extension/public/_locales/ms/messages.json index 3a275d72..dcaefb80 100644 --- a/packages/extension/public/_locales/ms/messages.json +++ b/packages/extension/public/_locales/ms/messages.json @@ -1250,11 +1250,5 @@ }, "Menu_disabled_urlNotMatch": { "message": "Tidak boleh dijalankan pada halaman ini (URL Halaman tidak sepadan)" - }, - "commandHub_delete_success": { - "message": "Arahan dipadam." - }, - "commandHub_delete_error": { - "message": "Gagal memadam arahan." } -} \ No newline at end of file +} diff --git a/packages/extension/public/_locales/pt_BR/messages.json b/packages/extension/public/_locales/pt_BR/messages.json index 2853f189..63f2af3b 100644 --- a/packages/extension/public/_locales/pt_BR/messages.json +++ b/packages/extension/public/_locales/pt_BR/messages.json @@ -1250,11 +1250,5 @@ }, "Menu_disabled_urlNotMatch": { "message": "Não é possível executar nesta página (URL da página não corresponde)" - }, - "commandHub_delete_success": { - "message": "Comando excluído." - }, - "commandHub_delete_error": { - "message": "Falha ao excluir o comando." } -} \ No newline at end of file +} diff --git a/packages/extension/public/_locales/pt_PT/messages.json b/packages/extension/public/_locales/pt_PT/messages.json index 61743228..419f1a3e 100644 --- a/packages/extension/public/_locales/pt_PT/messages.json +++ b/packages/extension/public/_locales/pt_PT/messages.json @@ -1250,11 +1250,5 @@ }, "Menu_disabled_urlNotMatch": { "message": "Não é possível executar nesta página (URL da página não corresponde)" - }, - "commandHub_delete_success": { - "message": "Comando eliminado." - }, - "commandHub_delete_error": { - "message": "Falha ao eliminar o comando." } -} \ No newline at end of file +} diff --git a/packages/extension/public/_locales/ru/messages.json b/packages/extension/public/_locales/ru/messages.json index efebd2fd..31c98caa 100644 --- a/packages/extension/public/_locales/ru/messages.json +++ b/packages/extension/public/_locales/ru/messages.json @@ -1247,11 +1247,5 @@ }, "Menu_disabled_urlNotMatch": { "message": "Невозможно выполнить на этой странице (URL страницы не совпадает)" - }, - "commandHub_delete_success": { - "message": "Команда удалена." - }, - "commandHub_delete_error": { - "message": "Не удалось удалить команду." } -} \ No newline at end of file +} diff --git a/packages/extension/public/_locales/zh_CN/messages.json b/packages/extension/public/_locales/zh_CN/messages.json index 58b5a984..8adb75f2 100644 --- a/packages/extension/public/_locales/zh_CN/messages.json +++ b/packages/extension/public/_locales/zh_CN/messages.json @@ -1247,11 +1247,5 @@ }, "Menu_disabled_urlNotMatch": { "message": "无法在此页面运行(页面URL不匹配)" - }, - "commandHub_delete_success": { - "message": "命令已删除。" - }, - "commandHub_delete_error": { - "message": "删除命令失败。" } -} \ No newline at end of file +} diff --git a/packages/extension/src/hooks/useCommandHubBridge.ts b/packages/extension/src/hooks/useCommandHubBridge.ts index 83136985..fd06a080 100644 --- a/packages/extension/src/hooks/useCommandHubBridge.ts +++ b/packages/extension/src/hooks/useCommandHubBridge.ts @@ -124,14 +124,14 @@ export function useCommandHubBridge() { }, [commands]) useEffect(() => { - const handleMessage = (event: MessageEvent) => { + const handleMessage = async (event: MessageEvent) => { if (event.origin !== hubOrigin) return const { action, command, id } = event.data ?? {} if (action === "AddCommand") { if (typeof command !== "string") return + const install_id = await getOrCreateClientId() Ipc.send(BgCommand.addCommand, { command }) .then(async (res) => { - const install_id = await getOrCreateClientId() ;(event.source as WindowProxy)?.postMessage( { action: "AddCommandAck", result: !!res, install_id }, { targetOrigin: event.origin }, @@ -150,8 +150,7 @@ export function useCommandHubBridge() { ) } }) - .catch(async () => { - const install_id = await getOrCreateClientId() + .catch(() => { ;(event.source as WindowProxy)?.postMessage( { action: "AddCommandAck", result: false, install_id }, { targetOrigin: event.origin }, From c4995526c59f5650641ee755e1a81789712a412c Mon Sep 17 00:00:00 2001 From: ujiro99 Date: Wed, 6 May 2026 21:58:58 +0900 Subject: [PATCH 9/9] Update: Ai settings --- .claude/settings.local.json | 4 +- .serena/project.yml | 113 ++++++++++++++++-------------------- 2 files changed, 52 insertions(+), 65 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index d9717718..e3ed0a5b 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -70,7 +70,9 @@ "mcp__serena__think_about_collected_information", "mcp__serena__think_about_task_adherence", "mcp__serena__think_about_whether_you_are_done", - "mcp__serena__write_memory" + "mcp__serena__write_memory", + "Bash(npx vitest *)", + "Bash(pnpm --filter extension tsc --noEmit)" ], "deny": [] } diff --git a/.serena/project.yml b/.serena/project.yml index b40c0729..f0fbf17e 100644 --- a/.serena/project.yml +++ b/.serena/project.yml @@ -1,10 +1,9 @@ -# whether to use the project's gitignore file to ignore files -# Added on 2025-04-07 +# whether to use project's .gitignore files to ignore files ignore_all_files_in_gitignore: true -# list of additional paths to ignore -# same syntax as gitignore, so you can use * and ** -# Was previously called `ignored_dirs`, please update your config if you are using that. -# Added (renamed)on 2025-04-07 + +# list of additional paths to ignore in this project. +# Same syntax as gitignore, so you can use * and **. +# Note: global ignored_paths from serena_config.yml are also applied additively. ignored_paths: [] # whether the project is in read-only mode @@ -12,45 +11,9 @@ ignored_paths: [] # Added on 2025-04-18 read_only: false -# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details. -# Below is the complete list of tools for convenience. -# To make sure you have the latest list of tools, and to view their descriptions, -# execute `uv run scripts/print_tool_overview.py`. -# -# * `activate_project`: Activates a project by name. -# * `check_onboarding_performed`: Checks whether project onboarding was already performed. -# * `create_text_file`: Creates/overwrites a file in the project directory. -# * `delete_lines`: Deletes a range of lines within a file. -# * `delete_memory`: Deletes a memory from Serena's project-specific memory store. -# * `execute_shell_command`: Executes a shell command. -# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced. -# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type). -# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type). -# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes. -# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file. -# * `initial_instructions`: Gets the initial instructions for the current project. -# Should only be used in settings where the system prompt cannot be set, -# e.g. in clients you have no control over, like Claude Desktop. -# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol. -# * `insert_at_line`: Inserts content at a given line in a file. -# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol. -# * `list_dir`: Lists files and directories in the given directory (optionally with recursion). -# * `list_memories`: Lists memories in Serena's project-specific memory store. -# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building). -# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context). -# * `read_file`: Reads a file within the project directory. -# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store. -# * `remove_project`: Removes a project from the Serena configuration. -# * `replace_lines`: Replaces a range of lines within a file with new content. -# * `replace_symbol_body`: Replaces the full definition of a symbol. -# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen. -# * `search_for_pattern`: Performs a search for a pattern in the project. -# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase. -# * `switch_modes`: Activates modes by providing a list of their names -# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information. -# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task. -# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed. -# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store. +# list of tool names to exclude. +# This extends the existing exclusions (e.g. from the global configuration) +# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html excluded_tools: [] # initial prompt for the project. It will always be given to the LLM upon activating the project @@ -67,18 +30,24 @@ project_name: "selection-command" # Set this to a list of mode names to always include the respective modes for this project. base_modes: -# list of mode names that are to be activated by default. -# The full set of modes to be activated is base_modes + default_modes. -# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply. +# list of mode names that are to be activated by default, overriding the setting in the global configuration. +# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes. +# If the setting is undefined/empty, the default_modes from the global configuration (serena_config.yml) apply. # Otherwise, this overrides the setting from the global configuration (serena_config.yml). +# Therefore, you can set this to [] if you do not want the default modes defined in the global config to apply +# for this project. # This setting can, in turn, be overridden by CLI parameters (--mode). +# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes default_modes: -# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default) +# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default). +# This extends the existing inclusions (e.g. from the global configuration). +# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html included_optional_tools: [] # fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools. # This cannot be combined with non-empty excluded_tools or included_optional_tools. +# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html fixed_tools: [] # the encoding used by text files in the project @@ -86,22 +55,33 @@ fixed_tools: [] encoding: utf-8 # list of languages for which language servers are started; choose from: -# al bash clojure cpp csharp -# csharp_omnisharp dart elixir elm erlang -# fortran fsharp go groovy haskell -# java julia kotlin lua markdown -# matlab nix pascal perl php -# php_phpactor powershell python python_jedi r -# rego ruby ruby_solargraph rust scala -# swift terraform toml typescript typescript_vts -# vue yaml zig -# powershell python python_jedi r rego -# ruby ruby_solargraph rust scala swift -# terraform toml typescript typescript_vts vue -# yaml zig -# +# al ansible bash clojure cpp +# cpp_ccls crystal csharp csharp_omnisharp dart +# elixir elm erlang fortran fsharp +# go groovy haskell haxe hlsl +# java json julia kotlin lean4 +# lua luau markdown matlab msl +# nix ocaml pascal perl php +# php_phpactor powershell python python_jedi python_ty +# r rego ruby ruby_solargraph rust +# scala solidity swift systemverilog terraform +# toml typescript typescript_vts vue yaml +# zig +# (This list may be outdated. For the current list, see values of Language enum here: +# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py +# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.) +# Note: +# - For C, use cpp +# - For JavaScript, use typescript +# - For Free Pascal/Lazarus, use pascal +# Special requirements: +# Some languages require additional setup/installations. +# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers +# When using multiple languages, the first language server that supports a given file will be used for that file. +# The first language is the default language and the respective language server will be used as a fallback. +# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored. languages: -- typescript + - typescript # time budget (seconds) per tool call for the retrieval of additional symbol information # such as docstrings or parameter information. @@ -138,3 +118,8 @@ ignored_memory_patterns: [] # Have a look at the docstring of the constructors of the LS implementations within solidlsp (e.g., for C# or PHP) to see which options are available. # No documentation on options means no options are available. ls_specific_settings: {} + +# list of mode names to be activated additionally for this project, e.g. ["query-projects"] +# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes. +# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes +added_modes: