From 005b7c4587b762d2113a59e23e73e8f469791bd2 Mon Sep 17 00:00:00 2001 From: Grady Zhuo Date: Mon, 6 Jul 2026 15:26:52 +0800 Subject: [PATCH] feat: orrery install reports the account and its workspace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The install success message now names both the target account and the workspace it is pinned to (e.g. 帳號「work」所在的 workspace「team」) — clarifying that shared add-ons (the statusline program) land in the workspace while the settings patch is per-account. Adds a claudeWorkspaceLabel helper (DRY'd with the account label) + a workspace param to L10n.Install.success across all locales. --- .../OrreryCore/Commands/InstallCommand.swift | 58 ++++++++++++------- .../OrreryCore/Resources/Localization/en.json | 2 +- .../OrreryCore/Resources/Localization/ja.json | 2 +- .../Localization/l10n-signatures.json | 3 +- .../Resources/Localization/zh-Hant.json | 2 +- Tests/OrreryTests/LocalizationTests.swift | 8 +++ 6 files changed, 50 insertions(+), 25 deletions(-) diff --git a/Sources/OrreryCore/Commands/InstallCommand.swift b/Sources/OrreryCore/Commands/InstallCommand.swift index e1d9872..82253c5 100644 --- a/Sources/OrreryCore/Commands/InstallCommand.swift +++ b/Sources/OrreryCore/Commands/InstallCommand.swift @@ -43,7 +43,8 @@ public struct InstallCommand: ParsableCommand { record.packageID, shortRef, record.copiedFiles.count, - claudeAccountLabel(forEnv: resolvedEnv) + claudeAccountLabel(forEnv: resolvedEnv), + claudeWorkspaceLabel(forEnv: resolvedEnv) )) } } @@ -54,27 +55,42 @@ func installCurrentEnvOrThrow() throws -> String { return env ?? Workspace.reservedOriginName } -/// The claude account add-ons are actually installed into / removed from (the -/// account dir, not the workspace). Resolves the active `CLAUDE_CONFIG_DIR`, else -/// the claude account pinned to `env`, and returns its display name. Falls back -/// to the env name when no account can be resolved. -func claudeAccountLabel(forEnv env: String) -> String { - let configDir: String? +/// Resolve the claude account dir add-ons install into / are removed from (the +/// account dir, not the workspace): the active `CLAUDE_CONFIG_DIR`, else the +/// claude account pinned to `env`. Nil when no account can be resolved. +func claudeAccountDirPath(forEnv env: String) -> String? { if let live = ProcessInfo.processInfo.environment["CLAUDE_CONFIG_DIR"], !live.isEmpty { - configDir = live - } else { - let store = EnvironmentStore.default - let pins = (env == Workspace.reservedOriginName) - ? store.loadOriginWorkspace().accounts - : ((try? store.load(named: env).accounts) ?? [:]) - configDir = pins[Tool.claude.rawValue] - .map { AccountStore.default.accountDir(id: $0, tool: .claude).path } + return live } - guard let configDir, - let data = try? Data(contentsOf: URL(fileURLWithPath: configDir) + let store = EnvironmentStore.default + let pins = (env == Workspace.reservedOriginName) + ? store.loadOriginWorkspace().accounts + : ((try? store.load(named: env).accounts) ?? [:]) + return pins[Tool.claude.rawValue] + .map { AccountStore.default.accountDir(id: $0, tool: .claude).path } +} + +/// The resolved account's `metadata.json` as a dict, if readable. +private func claudeAccountMetadata(forEnv env: String) -> [String: Any]? { + guard let dir = claudeAccountDirPath(forEnv: env), + let data = try? Data(contentsOf: URL(fileURLWithPath: dir) .appendingPathComponent("metadata.json")), - let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any], - let name = obj["displayName"] as? String - else { return env } - return name + let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any] + else { return nil } + return obj +} + +/// Display name of the account add-ons install into. Falls back to the env name. +func claudeAccountLabel(forEnv env: String) -> String { + claudeAccountMetadata(forEnv: env)?["displayName"] as? String ?? env +} + +/// The workspace the target account is pinned to (`metadata.json` `workspace`, +/// absent ⇒ origin) — where the shared add-on files (e.g. the statusline +/// program) actually land. +func claudeWorkspaceLabel(forEnv env: String) -> String { + guard let ws = claudeAccountMetadata(forEnv: env)?["workspace"] as? String, + !ws.isEmpty + else { return Workspace.reservedOriginName } + return ws } diff --git a/Sources/OrreryCore/Resources/Localization/en.json b/Sources/OrreryCore/Resources/Localization/en.json index 1a4f7a2..b269c0c 100644 --- a/Sources/OrreryCore/Resources/Localization/en.json +++ b/Sources/OrreryCore/Resources/Localization/en.json @@ -338,7 +338,7 @@ "install.urlHelp": "Override the manifest source URL (use a custom git repository)", "install.refHelp": "Override the manifest source ref (git branch/tag/sha)", "install.forceRefreshHelp": "Re-clone the source even if the cached copy already exists", - "install.success": "Installed {id} ({shortRef}) into account '{env}'. {fileCount} files copied, settings patched.", + "install.success": "Installed {id} ({shortRef}) into account '{env}' in workspace '{workspace}'. {fileCount} files copied, settings patched.", "thirdparty.abstract": "Manage installed third-party add-ons for an environment", "thirdparty.idHelp": "Package id (e.g. statusline)", "thirdparty.envHelp": "Target environment name", diff --git a/Sources/OrreryCore/Resources/Localization/ja.json b/Sources/OrreryCore/Resources/Localization/ja.json index 405ec82..2420aef 100644 --- a/Sources/OrreryCore/Resources/Localization/ja.json +++ b/Sources/OrreryCore/Resources/Localization/ja.json @@ -338,7 +338,7 @@ "install.urlHelp": "Override the manifest source URL (use a custom git repository)", "install.refHelp": "Override the manifest source ref (git branch/tag/sha)", "install.forceRefreshHelp": "Re-clone the source even if the cached copy already exists", - "install.success": "Installed {id} ({shortRef}) into account '{env}'. {fileCount} files copied, settings patched.", + "install.success": "Installed {id} ({shortRef}) into account '{env}' in workspace '{workspace}'. {fileCount} files copied, settings patched.", "thirdparty.abstract": "Manage installed third-party add-ons for an environment", "thirdparty.idHelp": "Package id (e.g. statusline)", "thirdparty.envHelp": "Target environment name", diff --git a/Sources/OrreryCore/Resources/Localization/l10n-signatures.json b/Sources/OrreryCore/Resources/Localization/l10n-signatures.json index 6b30149..4127cb7 100644 --- a/Sources/OrreryCore/Resources/Localization/l10n-signatures.json +++ b/Sources/OrreryCore/Resources/Localization/l10n-signatures.json @@ -2335,7 +2335,8 @@ { "label": "_", "name": "id", "type": "String" }, { "label": "_", "name": "shortRef", "type": "String" }, { "label": "_", "name": "fileCount", "type": "Int" }, - { "label": "_", "name": "env", "type": "String" } + { "label": "_", "name": "env", "type": "String" }, + { "label": "_", "name": "workspace", "type": "String" } ] }, { "path": ["Thirdparty", "abstract"], "kind": "var", "parameters": [] }, diff --git a/Sources/OrreryCore/Resources/Localization/zh-Hant.json b/Sources/OrreryCore/Resources/Localization/zh-Hant.json index 0ad0554..a59371a 100644 --- a/Sources/OrreryCore/Resources/Localization/zh-Hant.json +++ b/Sources/OrreryCore/Resources/Localization/zh-Hant.json @@ -338,7 +338,7 @@ "install.urlHelp": "覆寫 manifest 的來源 URL(改用自訂 git repo)", "install.refHelp": "覆寫 manifest 的來源 ref(git branch/tag/sha)", "install.forceRefreshHelp": "即使 cache 已存在也強制重新 clone", - "install.success": "已將 {id}({shortRef})安裝到帳號「{env}」,複製 {fileCount} 個檔案並更新 settings。", + "install.success": "已將 {id}({shortRef})安裝到帳號「{env}」所在的 workspace「{workspace}」,複製 {fileCount} 個檔案並更新 settings。", "thirdparty.abstract": "管理環境已安裝的第三方外掛", "thirdparty.idHelp": "套件 id(例如 statusline)", "thirdparty.envHelp": "目標環境名稱", diff --git a/Tests/OrreryTests/LocalizationTests.swift b/Tests/OrreryTests/LocalizationTests.swift index b039db6..2124d92 100644 --- a/Tests/OrreryTests/LocalizationTests.swift +++ b/Tests/OrreryTests/LocalizationTests.swift @@ -24,6 +24,14 @@ final class LocalizationTests: XCTestCase { XCTAssertTrue(value.contains("foo")) } + func testInstallSuccessNamesAccountAndWorkspace() { + // `orrery install` reports the account AND the workspace it's pinned to. + let msg = L10n.Install.success("statusline", "latest@v0.2.9", 1, "work", "team") + XCTAssertTrue(msg.contains("statusline")) + XCTAssertTrue(msg.contains("work"), "should name the account") + XCTAssertTrue(msg.contains("team"), "should name the workspace") + } + func testBothLocalesContainKnownKeys() { // Strings are compiled directly into the binary via codegen; the // generated `L10nData` holds the per-locale dictionaries.