diff --git a/src/commands/add-dir/add-dir.tsx b/src/commands/add-dir/add-dir.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..b1a2b4de1f2d24185e389bd0982450de33cee68d
--- /dev/null
+++ b/src/commands/add-dir/add-dir.tsx
@@ -0,0 +1,126 @@
+import { c as _c } from "react/compiler-runtime";
+import chalk from 'chalk';
+import figures from 'figures';
+import React, { useEffect } from 'react';
+import { getAdditionalDirectoriesForClaudeMd, setAdditionalDirectoriesForClaudeMd } from '../../bootstrap/state.js';
+import type { LocalJSXCommandContext } from '../../commands.js';
+import { MessageResponse } from '../../components/MessageResponse.js';
+import { AddWorkspaceDirectory } from '../../components/permissions/rules/AddWorkspaceDirectory.js';
+import { Box, Text } from '../../ink.js';
+import type { LocalJSXCommandOnDone } from '../../types/command.js';
+import { applyPermissionUpdate, persistPermissionUpdate } from '../../utils/permissions/PermissionUpdate.js';
+import type { PermissionUpdateDestination } from '../../utils/permissions/PermissionUpdateSchema.js';
+import { SandboxManager } from '../../utils/sandbox/sandbox-adapter.js';
+import { addDirHelpMessage, validateDirectoryForWorkspace } from './validation.js';
+function AddDirError(t0) {
+ const $ = _c(10);
+ const {
+ message,
+ args,
+ onDone
+ } = t0;
+ let t1;
+ let t2;
+ if ($[0] !== onDone) {
+ t1 = () => {
+ const timer = setTimeout(onDone, 0);
+ return () => clearTimeout(timer);
+ };
+ t2 = [onDone];
+ $[0] = onDone;
+ $[1] = t1;
+ $[2] = t2;
+ } else {
+ t1 = $[1];
+ t2 = $[2];
+ }
+ useEffect(t1, t2);
+ let t3;
+ if ($[3] !== args) {
+ t3 = {figures.pointer} /add-dir {args};
+ $[3] = args;
+ $[4] = t3;
+ } else {
+ t3 = $[4];
+ }
+ let t4;
+ if ($[5] !== message) {
+ t4 = {message};
+ $[5] = message;
+ $[6] = t4;
+ } else {
+ t4 = $[6];
+ }
+ let t5;
+ if ($[7] !== t3 || $[8] !== t4) {
+ t5 = {t3}{t4};
+ $[7] = t3;
+ $[8] = t4;
+ $[9] = t5;
+ } else {
+ t5 = $[9];
+ }
+ return t5;
+}
+export async function call(onDone: LocalJSXCommandOnDone, context: LocalJSXCommandContext, args?: string): Promise {
+ const directoryPath = (args ?? '').trim();
+ const appState = context.getAppState();
+
+ // Helper to handle adding a directory (shared by both with-path and no-path cases)
+ const handleAddDirectory = async (path: string, remember = false) => {
+ const destination: PermissionUpdateDestination = remember ? 'localSettings' : 'session';
+ const permissionUpdate = {
+ type: 'addDirectories' as const,
+ directories: [path],
+ destination
+ };
+
+ // Apply to session context
+ const latestAppState = context.getAppState();
+ const updatedContext = applyPermissionUpdate(latestAppState.toolPermissionContext, permissionUpdate);
+ context.setAppState(prev => ({
+ ...prev,
+ toolPermissionContext: updatedContext
+ }));
+
+ // Update sandbox config so Bash commands can access the new directory.
+ // Bootstrap state is the source of truth for session-only dirs; persisted
+ // dirs are picked up via the settings subscription, but we refresh
+ // eagerly here to avoid a race when the user acts immediately.
+ const currentDirs = getAdditionalDirectoriesForClaudeMd();
+ if (!currentDirs.includes(path)) {
+ setAdditionalDirectoriesForClaudeMd([...currentDirs, path]);
+ }
+ SandboxManager.refreshConfig();
+ let message: string;
+ if (remember) {
+ try {
+ persistPermissionUpdate(permissionUpdate);
+ message = `Added ${chalk.bold(path)} as a working directory and saved to local settings`;
+ } catch (error) {
+ message = `Added ${chalk.bold(path)} as a working directory. Failed to save to local settings: ${error instanceof Error ? error.message : 'Unknown error'}`;
+ }
+ } else {
+ message = `Added ${chalk.bold(path)} as a working directory for this session`;
+ }
+ const messageWithHint = `${message} ${chalk.dim('· /permissions to manage')}`;
+ onDone(messageWithHint);
+ };
+
+ // When no path is provided, show AddWorkspaceDirectory input form directly
+ // and return to REPL after confirmation
+ if (!directoryPath) {
+ return {
+ onDone('Did not add a working directory.');
+ }} />;
+ }
+ const result = await validateDirectoryForWorkspace(directoryPath, appState.toolPermissionContext);
+ if (result.resultType !== 'success') {
+ const message = addDirHelpMessage(result);
+ return onDone(message)} />;
+ }
+ return {
+ onDone(`Did not add ${chalk.bold(result.absolutePath)} as a working directory.`);
+ }} />;
+}
+//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJjaGFsayIsImZpZ3VyZXMiLCJSZWFjdCIsInVzZUVmZmVjdCIsImdldEFkZGl0aW9uYWxEaXJlY3Rvcmllc0ZvckNsYXVkZU1kIiwic2V0QWRkaXRpb25hbERpcmVjdG9yaWVzRm9yQ2xhdWRlTWQiLCJMb2NhbEpTWENvbW1hbmRDb250ZXh0IiwiTWVzc2FnZVJlc3BvbnNlIiwiQWRkV29ya3NwYWNlRGlyZWN0b3J5IiwiQm94IiwiVGV4dCIsIkxvY2FsSlNYQ29tbWFuZE9uRG9uZSIsImFwcGx5UGVybWlzc2lvblVwZGF0ZSIsInBlcnNpc3RQZXJtaXNzaW9uVXBkYXRlIiwiUGVybWlzc2lvblVwZGF0ZURlc3RpbmF0aW9uIiwiU2FuZGJveE1hbmFnZXIiLCJhZGREaXJIZWxwTWVzc2FnZSIsInZhbGlkYXRlRGlyZWN0b3J5Rm9yV29ya3NwYWNlIiwiQWRkRGlyRXJyb3IiLCJ0MCIsIiQiLCJfYyIsIm1lc3NhZ2UiLCJhcmdzIiwib25Eb25lIiwidDEiLCJ0MiIsInRpbWVyIiwic2V0VGltZW91dCIsImNsZWFyVGltZW91dCIsInQzIiwicG9pbnRlciIsInQ0IiwidDUiLCJjYWxsIiwiY29udGV4dCIsIlByb21pc2UiLCJSZWFjdE5vZGUiLCJkaXJlY3RvcnlQYXRoIiwidHJpbSIsImFwcFN0YXRlIiwiZ2V0QXBwU3RhdGUiLCJoYW5kbGVBZGREaXJlY3RvcnkiLCJwYXRoIiwicmVtZW1iZXIiLCJkZXN0aW5hdGlvbiIsInBlcm1pc3Npb25VcGRhdGUiLCJ0eXBlIiwiY29uc3QiLCJkaXJlY3RvcmllcyIsImxhdGVzdEFwcFN0YXRlIiwidXBkYXRlZENvbnRleHQiLCJ0b29sUGVybWlzc2lvbkNvbnRleHQiLCJzZXRBcHBTdGF0ZSIsInByZXYiLCJjdXJyZW50RGlycyIsImluY2x1ZGVzIiwicmVmcmVzaENvbmZpZyIsImJvbGQiLCJlcnJvciIsIkVycm9yIiwibWVzc2FnZVdpdGhIaW50IiwiZGltIiwicmVzdWx0IiwicmVzdWx0VHlwZSIsImFic29sdXRlUGF0aCJdLCJzb3VyY2VzIjpbImFkZC1kaXIudHN4Il0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBjaGFsayBmcm9tICdjaGFsaydcbmltcG9ydCBmaWd1cmVzIGZyb20gJ2ZpZ3VyZXMnXG5pbXBvcnQgUmVhY3QsIHsgdXNlRWZmZWN0IH0gZnJvbSAncmVhY3QnXG5pbXBvcnQge1xuICBnZXRBZGRpdGlvbmFsRGlyZWN0b3JpZXNGb3JDbGF1ZGVNZCxcbiAgc2V0QWRkaXRpb25hbERpcmVjdG9yaWVzRm9yQ2xhdWRlTWQsXG59IGZyb20gJy4uLy4uL2Jvb3RzdHJhcC9zdGF0ZS5qcydcbmltcG9ydCB0eXBlIHsgTG9jYWxKU1hDb21tYW5kQ29udGV4dCB9IGZyb20gJy4uLy4uL2NvbW1hbmRzLmpzJ1xuaW1wb3J0IHsgTWVzc2FnZVJlc3BvbnNlIH0gZnJvbSAnLi4vLi4vY29tcG9uZW50cy9NZXNzYWdlUmVzcG9uc2UuanMnXG5pbXBvcnQgeyBBZGRXb3Jrc3BhY2VEaXJlY3RvcnkgfSBmcm9tICcuLi8uLi9jb21wb25lbnRzL3Blcm1pc3Npb25zL3J1bGVzL0FkZFdvcmtzcGFjZURpcmVjdG9yeS5qcydcbmltcG9ydCB7IEJveCwgVGV4dCB9IGZyb20gJy4uLy4uL2luay5qcydcbmltcG9ydCB0eXBlIHsgTG9jYWxKU1hDb21tYW5kT25Eb25lIH0gZnJvbSAnLi4vLi4vdHlwZXMvY29tbWFuZC5qcydcbmltcG9ydCB7XG4gIGFwcGx5UGVybWlzc2lvblVwZGF0ZSxcbiAgcGVyc2lzdFBlcm1pc3Npb25VcGRhdGUsXG59IGZyb20gJy4uLy4uL3V0aWxzL3Blcm1pc3Npb25zL1Blcm1pc3Npb25VcGRhdGUuanMnXG5pbXBvcnQgdHlwZSB7IFBlcm1pc3Npb25VcGRhdGVEZXN0aW5hdGlvbiB9IGZyb20gJy4uLy4uL3V0aWxzL3Blcm1pc3Npb25zL1Blcm1pc3Npb25VcGRhdGVTY2hlbWEuanMnXG5pbXBvcnQgeyBTYW5kYm94TWFuYWdlciB9IGZyb20gJy4uLy4uL3V0aWxzL3NhbmRib3gvc2FuZGJveC1hZGFwdGVyLmpzJ1xuaW1wb3J0IHtcbiAgYWRkRGlySGVscE1lc3NhZ2UsXG4gIHZhbGlkYXRlRGlyZWN0b3J5Rm9yV29ya3NwYWNlLFxufSBmcm9tICcuL3ZhbGlkYXRpb24uanMnXG5cbmZ1bmN0aW9uIEFkZERpckVycm9yKHtcbiAgbWVzc2FnZSxcbiAgYXJncyxcbiAgb25Eb25lLFxufToge1xuICBtZXNzYWdlOiBzdHJpbmdcbiAgYXJnczogc3RyaW5nXG4gIG9uRG9uZTogKCkgPT4gdm9pZFxufSk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gIHVzZUVmZmVjdCgoKSA9PiB7XG4gICAgLy8gV2UgbmVlZCB0byBkZWZlciBjYWxsaW5nIG9uRG9uZSB0byBhdm9pZCB0aGUgXCJyZXR1cm4gbnVsbFwiIGJ1ZyB3aGVyZVxuICAgIC8vIHRoZSBjb21wb25lbnQgdW5tb3VudHMgYmVmb3JlIFJlYWN0IGNhbiByZW5kZXIgdGhlIGVycm9yIG1lc3NhZ2UuXG4gICAgLy8gVXNpbmcgc2V0VGltZW91dCBlbnN1cmVzIHRoZSBlcnJvciBkaXNwbGF5cyBiZWZvcmUgdGhlIGNvbW1hbmQgZXhpdHMuXG4gICAgY29uc3QgdGltZXIgPSBzZXRUaW1lb3V0KG9uRG9uZSwgMClcbiAgICByZXR1cm4gKCkgPT4gY2xlYXJUaW1lb3V0KHRpbWVyKVxuICB9LCBbb25Eb25lXSlcblxuICByZXR1cm4gKFxuICAgIDxCb3ggZmxleERpcmVjdGlvbj1cImNvbHVtblwiPlxuICAgICAgPFRleHQgZGltQ29sb3I+XG4gICAgICAgIHtmaWd1cmVzLnBvaW50ZXJ9IC9hZGQtZGlyIHthcmdzfVxuICAgICAgPC9UZXh0PlxuICAgICAgPE1lc3NhZ2VSZXNwb25zZT5cbiAgICAgICAgPFRleHQ+e21lc3NhZ2V9PC9UZXh0PlxuICAgICAgPC9NZXNzYWdlUmVzcG9uc2U+XG4gICAgPC9Cb3g+XG4gIClcbn1cblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGNhbGwoXG4gIG9uRG9uZTogTG9jYWxKU1hDb21tYW5kT25Eb25lLFxuICBjb250ZXh0OiBMb2NhbEpTWENvbW1hbmRDb250ZXh0LFxuICBhcmdzPzogc3RyaW5nLFxuKTogUHJvbWlzZTxSZWFjdC5SZWFjdE5vZGU+IHtcbiAgY29uc3QgZGlyZWN0b3J5UGF0aCA9IChhcmdzID8/ICcnKS50cmltKClcbiAgY29uc3QgYXBwU3RhdGUgPSBjb250ZXh0LmdldEFwcFN0YXRlKClcblxuICAvLyBIZWxwZXIgdG8gaGFuZGxlIGFkZGluZyBhIGRpcmVjdG9yeSAoc2hhcmVkIGJ5IGJvdGggd2l0aC1wYXRoIGFuZCBuby1wYXRoIGNhc2VzKVxuICBjb25zdCBoYW5kbGVBZGREaXJlY3RvcnkgPSBhc3luYyAocGF0aDogc3RyaW5nLCByZW1lbWJlciA9IGZhbHNlKSA9PiB7XG4gICAgY29uc3QgZGVzdGluYXRpb246IFBlcm1pc3Npb25VcGRhdGVEZXN0aW5hdGlvbiA9IHJlbWVtYmVyXG4gICAgICA/ICdsb2NhbFNldHRpbmdzJ1xuICAgICAgOiAnc2Vzc2lvbidcblxuICAgIGNvbnN0IHBlcm1pc3Npb25VcGRhdGUgPSB7XG4gICAgICB0eXBlOiAnYWRkRGlyZWN0b3JpZXMnIGFzIGNvbnN0LFxuICAgICAgZGlyZWN0b3JpZXM6IFtwYXRoXSxcbiAgICAgIGRlc3RpbmF0aW9uLFxuICAgIH1cblxuICAgIC8vIEFwcGx5IHRvIHNlc3Npb24gY29udGV4dFxuICAgIGNvbnN0IGxhdGVzdEFwcFN0YXRlID0gY29udGV4dC5nZXRBcHBTdGF0ZSgpXG4gICAgY29uc3QgdXBkYXRlZENvbnRleHQgPSBhcHBseVBlcm1pc3Npb25VcGRhdGUoXG4gICAgICBsYXRlc3RBcHBTdGF0ZS50b29sUGVybWlzc2lvbkNvbnRleHQsXG4gICAgICBwZXJtaXNzaW9uVXBkYXRlLFxuICAgIClcbiAgICBjb250ZXh0LnNldEFwcFN0YXRlKHByZXYgPT4gKHtcbiAgICAgIC4uLnByZXYsXG4gICAgICB0b29sUGVybWlzc2lvbkNvbnRleHQ6IHVwZGF0ZWRDb250ZXh0LFxuICAgIH0pKVxuXG4gICAgLy8gVXBkYXRlIHNhbmRib3ggY29uZmlnIHNvIEJhc2ggY29tbWFuZHMgY2FuIGFjY2VzcyB0aGUgbmV3IGRpcmVjdG9yeS5cbiAgICAvLyBCb290c3RyYXAgc3RhdGUgaXMgdGhlIHNvdXJjZSBvZiB0cnV0aCBmb3Igc2Vzc2lvbi1vbmx5IGRpcnM7IHBlcnNpc3RlZFxuICAgIC8vIGRpcnMgYXJlIHBpY2tlZCB1cCB2aWEgdGhlIHNldHRpbmdzIHN1YnNjcmlwdGlvbiwgYnV0IHdlIHJlZnJlc2hcbiAgICAvLyBlYWdlcmx5IGhlcmUgdG8gYXZvaWQgYSByYWNlIHdoZW4gdGhlIHVzZXIgYWN0cyBpbW1lZGlhdGVseS5cbiAgICBjb25zdCBjdXJyZW50RGlycyA9IGdldEFkZGl0aW9uYWxEaXJlY3Rvcmllc0ZvckNsYXVkZU1kKClcbiAgICBpZiAoIWN1cnJlbnREaXJzLmluY2x1ZGVzKHBhdGgpKSB7XG4gICAgICBzZXRBZGRpdGlvbmFsRGlyZWN0b3JpZXNGb3JDbGF1ZGVNZChbLi4uY3VycmVudERpcnMsIHBhdGhdKVxuICAgIH1cbiAgICBTYW5kYm94TWFuYWdlci5yZWZyZXNoQ29uZmlnKClcblxuICAgIGxldCBtZXNzYWdlOiBzdHJpbmdcblxuICAgIGlmIChyZW1lbWJlcikge1xuICAgICAgdHJ5IHtcbiAgICAgICAgcGVyc2lzdFBlcm1pc3Npb25VcGRhdGUocGVybWlzc2lvblVwZGF0ZSlcbiAgICAgICAgbWVzc2FnZSA9IGBBZGRlZCAke2NoYWxrLmJvbGQocGF0aCl9IGFzIGEgd29ya2luZyBkaXJlY3RvcnkgYW5kIHNhdmVkIHRvIGxvY2FsIHNldHRpbmdzYFxuICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgbWVzc2FnZSA9IGBBZGRlZCAke2NoYWxrLmJvbGQocGF0aCl9IGFzIGEgd29ya2luZyBkaXJlY3RvcnkuIEZhaWxlZCB0byBzYXZlIHRvIGxvY2FsIHNldHRpbmdzOiAke2Vycm9yIGluc3RhbmNlb2YgRXJyb3IgPyBlcnJvci5tZXNzYWdlIDogJ1Vua25vd24gZXJyb3InfWBcbiAgICAgIH1cbiAgICB9IGVsc2Uge1xuICAgICAgbWVzc2FnZSA9IGBBZGRlZCAke2NoYWxrLmJvbGQocGF0aCl9IGFzIGEgd29ya2luZyBkaXJlY3RvcnkgZm9yIHRoaXMgc2Vzc2lvbmBcbiAgICB9XG5cbiAgICBjb25zdCBtZXNzYWdlV2l0aEhpbnQgPSBgJHttZXNzYWdlfSAke2NoYWxrLmRpbSgnwrcgL3Blcm1pc3Npb25zIHRvIG1hbmFnZScpfWBcbiAgICBvbkRvbmUobWVzc2FnZVdpdGhIaW50KVxuICB9XG5cbiAgLy8gV2hlbiBubyBwYXRoIGlzIHByb3ZpZGVkLCBzaG93IEFkZFdvcmtzcGFjZURpcmVjdG9yeSBpbnB1dCBmb3JtIGRpcmVjdGx5XG4gIC8vIGFuZCByZXR1cm4gdG8gUkVQTCBhZnRlciBjb25maXJtYXRpb25cbiAgaWYgKCFkaXJlY3RvcnlQYXRoKSB7XG4gICAgcmV0dXJuIChcbiAgICAgIDxBZGRXb3Jrc3BhY2VEaXJlY3RvcnlcbiAgICAgICAgcGVybWlzc2lvbkNvbnRleHQ9e2FwcFN0YXRlLnRvb2xQZXJtaXNzaW9uQ29udGV4dH1cbiAgICAgICAgb25BZGREaXJlY3Rvcnk9e2hhbmRsZUFkZERpcmVjdG9yeX1cbiAgICAgICAgb25DYW5jZWw9eygpID0+IHtcbiAgICAgICAgICBvbkRvbmUoJ0RpZCBub3QgYWRkIGEgd29ya2luZyBkaXJlY3RvcnkuJylcbiAgICAgICAgfX1cbiAgICAgIC8+XG4gICAgKVxuICB9XG5cbiAgY29uc3QgcmVzdWx0ID0gYXdhaXQgdmFsaWRhdGVEaXJlY3RvcnlGb3JXb3Jrc3BhY2UoXG4gICAgZGlyZWN0b3J5UGF0aCxcbiAgICBhcHBTdGF0ZS50b29sUGVybWlzc2lvbkNvbnRleHQsXG4gIClcblxuICBpZiAocmVzdWx0LnJlc3VsdFR5cGUgIT09ICdzdWNjZXNzJykge1xuICAgIGNvbnN0IG1lc3NhZ2UgPSBhZGREaXJIZWxwTWVzc2FnZShyZXN1bHQpXG5cbiAgICByZXR1cm4gKFxuICAgICAgPEFkZERpckVycm9yXG4gICAgICAgIG1lc3NhZ2U9e21lc3NhZ2V9XG4gICAgICAgIGFyZ3M9e2FyZ3MgPz8gJyd9XG4gICAgICAgIG9uRG9uZT17KCkgPT4gb25Eb25lKG1lc3NhZ2UpfVxuICAgICAgLz5cbiAgICApXG4gIH1cblxuICByZXR1cm4gKFxuICAgIDxBZGRXb3Jrc3BhY2VEaXJlY3RvcnlcbiAgICAgIGRpcmVjdG9yeVBhdGg9e3Jlc3VsdC5hYnNvbHV0ZVBhdGh9XG4gICAgICBwZXJtaXNzaW9uQ29udGV4dD17YXBwU3RhdGUudG9vbFBlcm1pc3Npb25Db250ZXh0fVxuICAgICAgb25BZGREaXJlY3Rvcnk9e2hhbmRsZUFkZERpcmVjdG9yeX1cbiAgICAgIG9uQ2FuY2VsPXsoKSA9PiB7XG4gICAgICAgIG9uRG9uZShcbiAgICAgICAgICBgRGlkIG5vdCBhZGQgJHtjaGFsay5ib2xkKHJlc3VsdC5hYnNvbHV0ZVBhdGgpfSBhcyBhIHdvcmtpbmcgZGlyZWN0b3J5LmAsXG4gICAgICAgIClcbiAgICAgIH19XG4gICAgLz5cbiAgKVxufVxuIl0sIm1hcHBpbmdzIjoiO0FBQUEsT0FBT0EsS0FBSyxNQUFNLE9BQU87QUFDekIsT0FBT0MsT0FBTyxNQUFNLFNBQVM7QUFDN0IsT0FBT0MsS0FBSyxJQUFJQyxTQUFTLFFBQVEsT0FBTztBQUN4QyxTQUNFQyxtQ0FBbUMsRUFDbkNDLG1DQUFtQyxRQUM5QiwwQkFBMEI7QUFDakMsY0FBY0Msc0JBQXNCLFFBQVEsbUJBQW1CO0FBQy9ELFNBQVNDLGVBQWUsUUFBUSxxQ0FBcUM7QUFDckUsU0FBU0MscUJBQXFCLFFBQVEsNkRBQTZEO0FBQ25HLFNBQVNDLEdBQUcsRUFBRUMsSUFBSSxRQUFRLGNBQWM7QUFDeEMsY0FBY0MscUJBQXFCLFFBQVEsd0JBQXdCO0FBQ25FLFNBQ0VDLHFCQUFxQixFQUNyQkMsdUJBQXVCLFFBQ2xCLDZDQUE2QztBQUNwRCxjQUFjQywyQkFBMkIsUUFBUSxtREFBbUQ7QUFDcEcsU0FBU0MsY0FBYyxRQUFRLHdDQUF3QztBQUN2RSxTQUNFQyxp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
\ No newline at end of file
diff --git a/src/commands/add-dir/index.ts b/src/commands/add-dir/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e3475492bc7531d262ad442e8a66aa24dfe36d93
--- /dev/null
+++ b/src/commands/add-dir/index.ts
@@ -0,0 +1,11 @@
+import type { Command } from '../../commands.js'
+
+const addDir = {
+ type: 'local-jsx',
+ name: 'add-dir',
+ description: 'Add a new working directory',
+ argumentHint: '',
+ load: () => import('./add-dir.js'),
+} satisfies Command
+
+export default addDir
diff --git a/src/commands/agents/agents.tsx b/src/commands/agents/agents.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..3af6273712ca291e7c30586a8031fb843b1912d1
--- /dev/null
+++ b/src/commands/agents/agents.tsx
@@ -0,0 +1,12 @@
+import * as React from 'react';
+import { AgentsMenu } from '../../components/agents/AgentsMenu.js';
+import type { ToolUseContext } from '../../Tool.js';
+import { getTools } from '../../tools.js';
+import type { LocalJSXCommandOnDone } from '../../types/command.js';
+export async function call(onDone: LocalJSXCommandOnDone, context: ToolUseContext): Promise {
+ const appState = context.getAppState();
+ const permissionContext = appState.toolPermissionContext;
+ const tools = getTools(permissionContext);
+ return ;
+}
+//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsIkFnZW50c01lbnUiLCJUb29sVXNlQ29udGV4dCIsImdldFRvb2xzIiwiTG9jYWxKU1hDb21tYW5kT25Eb25lIiwiY2FsbCIsIm9uRG9uZSIsImNvbnRleHQiLCJQcm9taXNlIiwiUmVhY3ROb2RlIiwiYXBwU3RhdGUiLCJnZXRBcHBTdGF0ZSIsInBlcm1pc3Npb25Db250ZXh0IiwidG9vbFBlcm1pc3Npb25Db250ZXh0IiwidG9vbHMiXSwic291cmNlcyI6WyJhZ2VudHMudHN4Il0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCAqIGFzIFJlYWN0IGZyb20gJ3JlYWN0J1xuaW1wb3J0IHsgQWdlbnRzTWVudSB9IGZyb20gJy4uLy4uL2NvbXBvbmVudHMvYWdlbnRzL0FnZW50c01lbnUuanMnXG5pbXBvcnQgdHlwZSB7IFRvb2xVc2VDb250ZXh0IH0gZnJvbSAnLi4vLi4vVG9vbC5qcydcbmltcG9ydCB7IGdldFRvb2xzIH0gZnJvbSAnLi4vLi4vdG9vbHMuanMnXG5pbXBvcnQgdHlwZSB7IExvY2FsSlNYQ29tbWFuZE9uRG9uZSB9IGZyb20gJy4uLy4uL3R5cGVzL2NvbW1hbmQuanMnXG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBjYWxsKFxuICBvbkRvbmU6IExvY2FsSlNYQ29tbWFuZE9uRG9uZSxcbiAgY29udGV4dDogVG9vbFVzZUNvbnRleHQsXG4pOiBQcm9taXNlPFJlYWN0LlJlYWN0Tm9kZT4ge1xuICBjb25zdCBhcHBTdGF0ZSA9IGNvbnRleHQuZ2V0QXBwU3RhdGUoKVxuICBjb25zdCBwZXJtaXNzaW9uQ29udGV4dCA9IGFwcFN0YXRlLnRvb2xQZXJtaXNzaW9uQ29udGV4dFxuICBjb25zdCB0b29scyA9IGdldFRvb2xzKHBlcm1pc3Npb25Db250ZXh0KVxuXG4gIHJldHVybiA8QWdlbnRzTWVudSB0b29scz17dG9vbHN9IG9uRXhpdD17b25Eb25lfSAvPlxufVxuIl0sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUtBLEtBQUssTUFBTSxPQUFPO0FBQzlCLFNBQVNDLFVBQVUsUUFBUSx1Q0FBdUM7QUFDbEUsY0FBY0MsY0FBYyxRQUFRLGVBQWU7QUFDbkQsU0FBU0MsUUFBUSxRQUFRLGdCQUFnQjtBQUN6QyxjQUFjQyxxQkFBcUIsUUFBUSx3QkFBd0I7QUFFbkUsT0FBTyxlQUFlQyxJQUFJQSxDQUN4QkMsTUFBTSxFQUFFRixxQkFBcUIsRUFDN0JHLE9BQU8sRUFBRUwsY0FBYyxDQUN4QixFQUFFTSxPQUFPLENBQUNSLEtBQUssQ0FBQ1MsU0FBUyxDQUFDLENBQUM7RUFDMUIsTUFBTUMsUUFBUSxHQUFHSCxPQUFPLENBQUNJLFdBQVcsQ0FBQyxDQUFDO0VBQ3RDLE1BQU1DLGlCQUFpQixHQUFHRixRQUFRLENBQUNHLHFCQUFxQjtFQUN4RCxNQUFNQyxLQUFLLEdBQUdYLFFBQVEsQ0FBQ1MsaUJBQWlCLENBQUM7RUFFekMsT0FBTyxDQUFDLFVBQVUsQ0FBQyxLQUFLLENBQUMsQ0FBQ0UsS0FBSyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUNSLE1BQU0sQ0FBQyxHQUFHO0FBQ3JEIiwiaWdub3JlTGlzdCI6W119
\ No newline at end of file
diff --git a/src/commands/agents/index.ts b/src/commands/agents/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ac43d2ec95258f2a7badafd9497c09e513e808b3
--- /dev/null
+++ b/src/commands/agents/index.ts
@@ -0,0 +1,10 @@
+import type { Command } from '../../commands.js'
+
+const agents = {
+ type: 'local-jsx',
+ name: 'agents',
+ description: 'Manage agent configurations',
+ load: () => import('./agents.js'),
+} satisfies Command
+
+export default agents
diff --git a/src/commands/ant-trace/index.js b/src/commands/ant-trace/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..e1a619d254332b7e0c662931b91aae3a9f9c3b0e
--- /dev/null
+++ b/src/commands/ant-trace/index.js
@@ -0,0 +1 @@
+export default { isEnabled: () => false, isHidden: true, name: 'stub' };
diff --git a/src/commands/autofix-pr/index.js b/src/commands/autofix-pr/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..e1a619d254332b7e0c662931b91aae3a9f9c3b0e
--- /dev/null
+++ b/src/commands/autofix-pr/index.js
@@ -0,0 +1 @@
+export default { isEnabled: () => false, isHidden: true, name: 'stub' };
diff --git a/src/commands/branch/branch.ts b/src/commands/branch/branch.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4a7c2779a408830c4fb5aa67463205fa03c342f9
--- /dev/null
+++ b/src/commands/branch/branch.ts
@@ -0,0 +1,296 @@
+import { randomUUID, type UUID } from 'crypto'
+import { mkdir, readFile, writeFile } from 'fs/promises'
+import { getOriginalCwd, getSessionId } from '../../bootstrap/state.js'
+import type { LocalJSXCommandContext } from '../../commands.js'
+import { logEvent } from '../../services/analytics/index.js'
+import type { LocalJSXCommandOnDone } from '../../types/command.js'
+import type {
+ ContentReplacementEntry,
+ Entry,
+ LogOption,
+ SerializedMessage,
+ TranscriptMessage,
+} from '../../types/logs.js'
+import { parseJSONL } from '../../utils/json.js'
+import {
+ getProjectDir,
+ getTranscriptPath,
+ getTranscriptPathForSession,
+ isTranscriptMessage,
+ saveCustomTitle,
+ searchSessionsByCustomTitle,
+} from '../../utils/sessionStorage.js'
+import { jsonStringify } from '../../utils/slowOperations.js'
+import { escapeRegExp } from '../../utils/stringUtils.js'
+
+type TranscriptEntry = TranscriptMessage & {
+ forkedFrom?: {
+ sessionId: string
+ messageUuid: UUID
+ }
+}
+
+/**
+ * Derive a single-line title base from the first user message.
+ * Collapses whitespace — multiline first messages (pasted stacks, code)
+ * otherwise flow into the saved title and break the resume hint.
+ */
+export function deriveFirstPrompt(
+ firstUserMessage: Extract | undefined,
+): string {
+ const content = firstUserMessage?.message?.content
+ if (!content) return 'Branched conversation'
+ const raw =
+ typeof content === 'string'
+ ? content
+ : content.find(
+ (block): block is { type: 'text'; text: string } =>
+ block.type === 'text',
+ )?.text
+ if (!raw) return 'Branched conversation'
+ return (
+ raw.replace(/\s+/g, ' ').trim().slice(0, 100) || 'Branched conversation'
+ )
+}
+
+/**
+ * Creates a fork of the current conversation by copying from the transcript file.
+ * Preserves all original metadata (timestamps, gitBranch, etc.) while updating
+ * sessionId and adding forkedFrom traceability.
+ */
+async function createFork(customTitle?: string): Promise<{
+ sessionId: UUID
+ title: string | undefined
+ forkPath: string
+ serializedMessages: SerializedMessage[]
+ contentReplacementRecords: ContentReplacementEntry['replacements']
+}> {
+ const forkSessionId = randomUUID() as UUID
+ const originalSessionId = getSessionId()
+ const projectDir = getProjectDir(getOriginalCwd())
+ const forkSessionPath = getTranscriptPathForSession(forkSessionId)
+ const currentTranscriptPath = getTranscriptPath()
+
+ // Ensure project directory exists
+ await mkdir(projectDir, { recursive: true, mode: 0o700 })
+
+ // Read current transcript file
+ let transcriptContent: Buffer
+ try {
+ transcriptContent = await readFile(currentTranscriptPath)
+ } catch {
+ throw new Error('No conversation to branch')
+ }
+
+ if (transcriptContent.length === 0) {
+ throw new Error('No conversation to branch')
+ }
+
+ // Parse all transcript entries (messages + metadata entries like content-replacement)
+ const entries = parseJSONL(transcriptContent)
+
+ // Filter to only main conversation messages (exclude sidechains and non-message entries)
+ const mainConversationEntries = entries.filter(
+ (entry): entry is TranscriptMessage =>
+ isTranscriptMessage(entry) && !entry.isSidechain,
+ )
+
+ // Content-replacement entries for the original session. These record which
+ // tool_result blocks were replaced with previews by the per-message budget.
+ // Without them in the fork JSONL, `claude -r {forkId}` reconstructs state
+ // with an empty replacements Map → previously-replaced results are classified
+ // as FROZEN and sent as full content (prompt cache miss + permanent overage).
+ // sessionId must be rewritten since loadTranscriptFile keys lookup by the
+ // session's messages' sessionId.
+ const contentReplacementRecords = entries
+ .filter(
+ (entry): entry is ContentReplacementEntry =>
+ entry.type === 'content-replacement' &&
+ entry.sessionId === originalSessionId,
+ )
+ .flatMap(entry => entry.replacements)
+
+ if (mainConversationEntries.length === 0) {
+ throw new Error('No messages to branch')
+ }
+
+ // Build forked entries with new sessionId and preserved metadata
+ let parentUuid: UUID | null = null
+ const lines: string[] = []
+ const serializedMessages: SerializedMessage[] = []
+
+ for (const entry of mainConversationEntries) {
+ // Create forked transcript entry preserving all original metadata
+ const forkedEntry: TranscriptEntry = {
+ ...entry,
+ sessionId: forkSessionId,
+ parentUuid,
+ isSidechain: false,
+ forkedFrom: {
+ sessionId: originalSessionId,
+ messageUuid: entry.uuid,
+ },
+ }
+
+ // Build serialized message for LogOption
+ const serialized: SerializedMessage = {
+ ...entry,
+ sessionId: forkSessionId,
+ }
+
+ serializedMessages.push(serialized)
+ lines.push(jsonStringify(forkedEntry))
+ if (entry.type !== 'progress') {
+ parentUuid = entry.uuid
+ }
+ }
+
+ // Append content-replacement entry (if any) with the fork's sessionId.
+ // Written as a SINGLE entry (same shape as insertContentReplacement) so
+ // loadTranscriptFile's content-replacement branch picks it up.
+ if (contentReplacementRecords.length > 0) {
+ const forkedReplacementEntry: ContentReplacementEntry = {
+ type: 'content-replacement',
+ sessionId: forkSessionId,
+ replacements: contentReplacementRecords,
+ }
+ lines.push(jsonStringify(forkedReplacementEntry))
+ }
+
+ // Write the fork session file
+ await writeFile(forkSessionPath, lines.join('\n') + '\n', {
+ encoding: 'utf8',
+ mode: 0o600,
+ })
+
+ return {
+ sessionId: forkSessionId,
+ title: customTitle,
+ forkPath: forkSessionPath,
+ serializedMessages,
+ contentReplacementRecords,
+ }
+}
+
+/**
+ * Generates a unique fork name by checking for collisions with existing session names.
+ * If "baseName (Branch)" already exists, tries "baseName (Branch 2)", "baseName (Branch 3)", etc.
+ */
+async function getUniqueForkName(baseName: string): Promise {
+ const candidateName = `${baseName} (Branch)`
+
+ // Check if this exact name already exists
+ const existingWithExactName = await searchSessionsByCustomTitle(
+ candidateName,
+ { exact: true },
+ )
+
+ if (existingWithExactName.length === 0) {
+ return candidateName
+ }
+
+ // Name collision - find a unique numbered suffix
+ // Search for all sessions that start with the base pattern
+ const existingForks = await searchSessionsByCustomTitle(`${baseName} (Branch`)
+
+ // Extract existing fork numbers to find the next available
+ const usedNumbers = new Set([1]) // Consider " (Branch)" as number 1
+ const forkNumberPattern = new RegExp(
+ `^${escapeRegExp(baseName)} \\(Branch(?: (\\d+))?\\)$`,
+ )
+
+ for (const session of existingForks) {
+ const match = session.customTitle?.match(forkNumberPattern)
+ if (match) {
+ if (match[1]) {
+ usedNumbers.add(parseInt(match[1], 10))
+ } else {
+ usedNumbers.add(1) // " (Branch)" without number is treated as 1
+ }
+ }
+ }
+
+ // Find the next available number
+ let nextNumber = 2
+ while (usedNumbers.has(nextNumber)) {
+ nextNumber++
+ }
+
+ return `${baseName} (Branch ${nextNumber})`
+}
+
+export async function call(
+ onDone: LocalJSXCommandOnDone,
+ context: LocalJSXCommandContext,
+ args: string,
+): Promise {
+ const customTitle = args?.trim() || undefined
+
+ const originalSessionId = getSessionId()
+
+ try {
+ const {
+ sessionId,
+ title,
+ forkPath,
+ serializedMessages,
+ contentReplacementRecords,
+ } = await createFork(customTitle)
+
+ // Build LogOption for resume
+ const now = new Date()
+ const firstPrompt = deriveFirstPrompt(
+ serializedMessages.find(m => m.type === 'user'),
+ )
+
+ // Save custom title - use provided title or firstPrompt as default
+ // This ensures /status and /resume show the same session name
+ // Always add " (Branch)" suffix to make it clear this is a branched session
+ // Handle collisions by adding a number suffix (e.g., " (Branch 2)", " (Branch 3)")
+ const baseName = title ?? firstPrompt
+ const effectiveTitle = await getUniqueForkName(baseName)
+ await saveCustomTitle(sessionId, effectiveTitle, forkPath)
+
+ logEvent('tengu_conversation_forked', {
+ message_count: serializedMessages.length,
+ has_custom_title: !!title,
+ })
+
+ const forkLog: LogOption = {
+ date: now.toISOString().split('T')[0]!,
+ messages: serializedMessages,
+ fullPath: forkPath,
+ value: now.getTime(),
+ created: now,
+ modified: now,
+ firstPrompt,
+ messageCount: serializedMessages.length,
+ isSidechain: false,
+ sessionId,
+ customTitle: effectiveTitle,
+ contentReplacements: contentReplacementRecords,
+ }
+
+ // Resume into the fork
+ const titleInfo = title ? ` "${title}"` : ''
+ const resumeHint = `\nTo resume the original: claude -r ${originalSessionId}`
+ const successMessage = `Branched conversation${titleInfo}. You are now in the branch.${resumeHint}`
+
+ if (context.resume) {
+ await context.resume(sessionId, forkLog, 'fork')
+ onDone(successMessage, { display: 'system' })
+ } else {
+ // Fallback if resume not available
+ onDone(
+ `Branched conversation${titleInfo}. Resume with: /resume ${sessionId}`,
+ )
+ }
+
+ return null
+ } catch (error) {
+ const message =
+ error instanceof Error ? error.message : 'Unknown error occurred'
+ onDone(`Failed to branch conversation: ${message}`)
+ return null
+ }
+}
diff --git a/src/commands/branch/index.ts b/src/commands/branch/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..731ff39fe572417053fceebe850fe118472b977f
--- /dev/null
+++ b/src/commands/branch/index.ts
@@ -0,0 +1,14 @@
+import { feature } from 'bun:bundle'
+import type { Command } from '../../commands.js'
+
+const branch = {
+ type: 'local-jsx',
+ name: 'branch',
+ // 'fork' alias only when /fork doesn't exist as its own command
+ aliases: feature('FORK_SUBAGENT') ? [] : ['fork'],
+ description: 'Create a branch of the current conversation at this point',
+ argumentHint: '[name]',
+ load: () => import('./branch.js'),
+} satisfies Command
+
+export default branch
diff --git a/src/commands/break-cache/index.js b/src/commands/break-cache/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..e1a619d254332b7e0c662931b91aae3a9f9c3b0e
--- /dev/null
+++ b/src/commands/break-cache/index.js
@@ -0,0 +1 @@
+export default { isEnabled: () => false, isHidden: true, name: 'stub' };
diff --git a/src/commands/btw/index.ts b/src/commands/btw/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d4888711d3b0779b7119e7d052b6b267998a6a39
--- /dev/null
+++ b/src/commands/btw/index.ts
@@ -0,0 +1,13 @@
+import type { Command } from '../../commands.js'
+
+const btw = {
+ type: 'local-jsx',
+ name: 'btw',
+ description:
+ 'Ask a quick side question without interrupting the main conversation',
+ immediate: true,
+ argumentHint: '',
+ load: () => import('./btw.js'),
+} satisfies Command
+
+export default btw
diff --git a/src/commands/chrome/index.ts b/src/commands/chrome/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0e9af5ff3c871e06879d217ec920b011a192f1cd
--- /dev/null
+++ b/src/commands/chrome/index.ts
@@ -0,0 +1,13 @@
+import { getIsNonInteractiveSession } from '../../bootstrap/state.js'
+import type { Command } from '../../commands.js'
+
+const command: Command = {
+ name: 'chrome',
+ description: 'Claude in Chrome (Beta) settings',
+ availability: ['claude-ai'],
+ isEnabled: () => !getIsNonInteractiveSession(),
+ type: 'local-jsx',
+ load: () => import('./chrome.js'),
+}
+
+export default command
diff --git a/src/commands/clear/clear.ts b/src/commands/clear/clear.ts
new file mode 100644
index 0000000000000000000000000000000000000000..24c6295a1d4e36283582eaef179eb00c3bdc3cd3
--- /dev/null
+++ b/src/commands/clear/clear.ts
@@ -0,0 +1,7 @@
+import type { LocalCommandCall } from '../../types/command.js'
+import { clearConversation } from './conversation.js'
+
+export const call: LocalCommandCall = async (_, context) => {
+ await clearConversation(context)
+ return { type: 'text', value: '' }
+}
diff --git a/src/commands/clear/index.ts b/src/commands/clear/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a426de6fa2048b3696ebfecf188e2431d7a39c10
--- /dev/null
+++ b/src/commands/clear/index.ts
@@ -0,0 +1,19 @@
+/**
+ * Clear command - minimal metadata only.
+ * Implementation is lazy-loaded from clear.ts to reduce startup time.
+ * Utility functions:
+ * - clearSessionCaches: import from './clear/caches.js'
+ * - clearConversation: import from './clear/conversation.js'
+ */
+import type { Command } from '../../commands.js'
+
+const clear = {
+ type: 'local',
+ name: 'clear',
+ description: 'Clear conversation history and free up context',
+ aliases: ['reset', 'new'],
+ supportsNonInteractive: false, // Should just create a new session
+ load: () => import('./clear.js'),
+} satisfies Command
+
+export default clear
diff --git a/src/commands/color/color.ts b/src/commands/color/color.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e4420b7f941e0b64e37347191b77632cff815641
--- /dev/null
+++ b/src/commands/color/color.ts
@@ -0,0 +1,93 @@
+import type { UUID } from 'crypto'
+import { getSessionId } from '../../bootstrap/state.js'
+import type { ToolUseContext } from '../../Tool.js'
+import {
+ AGENT_COLORS,
+ type AgentColorName,
+} from '../../tools/AgentTool/agentColorManager.js'
+import type {
+ LocalJSXCommandContext,
+ LocalJSXCommandOnDone,
+} from '../../types/command.js'
+import {
+ getTranscriptPath,
+ saveAgentColor,
+} from '../../utils/sessionStorage.js'
+import { isTeammate } from '../../utils/teammate.js'
+
+const RESET_ALIASES = ['default', 'reset', 'none', 'gray', 'grey'] as const
+
+export async function call(
+ onDone: LocalJSXCommandOnDone,
+ context: ToolUseContext & LocalJSXCommandContext,
+ args: string,
+): Promise {
+ // Teammates cannot set their own color
+ if (isTeammate()) {
+ onDone(
+ 'Cannot set color: This session is a swarm teammate. Teammate colors are assigned by the team leader.',
+ { display: 'system' },
+ )
+ return null
+ }
+
+ if (!args || args.trim() === '') {
+ const colorList = AGENT_COLORS.join(', ')
+ onDone(`Please provide a color. Available colors: ${colorList}, default`, {
+ display: 'system',
+ })
+ return null
+ }
+
+ const colorArg = args.trim().toLowerCase()
+
+ // Handle reset to default (gray)
+ if (RESET_ALIASES.includes(colorArg as (typeof RESET_ALIASES)[number])) {
+ const sessionId = getSessionId() as UUID
+ const fullPath = getTranscriptPath()
+
+ // Use "default" sentinel (not empty string) so truthiness guards
+ // in sessionStorage.ts persist the reset across session restarts
+ await saveAgentColor(sessionId, 'default', fullPath)
+
+ context.setAppState(prev => ({
+ ...prev,
+ standaloneAgentContext: {
+ ...prev.standaloneAgentContext,
+ name: prev.standaloneAgentContext?.name ?? '',
+ color: undefined,
+ },
+ }))
+
+ onDone('Session color reset to default', { display: 'system' })
+ return null
+ }
+
+ if (!AGENT_COLORS.includes(colorArg as AgentColorName)) {
+ const colorList = AGENT_COLORS.join(', ')
+ onDone(
+ `Invalid color "${colorArg}". Available colors: ${colorList}, default`,
+ { display: 'system' },
+ )
+ return null
+ }
+
+ const sessionId = getSessionId() as UUID
+ const fullPath = getTranscriptPath()
+
+ // Save to transcript for persistence across sessions
+ await saveAgentColor(sessionId, colorArg, fullPath)
+
+ // Update AppState for immediate effect
+ context.setAppState(prev => ({
+ ...prev,
+ standaloneAgentContext: {
+ ...prev.standaloneAgentContext,
+ name: prev.standaloneAgentContext?.name ?? '',
+ color: colorArg as AgentColorName,
+ },
+ }))
+
+ onDone(`Session color set to: ${colorArg}`, { display: 'system' })
+ return null
+}
diff --git a/src/commands/mcp/addCommand.ts b/src/commands/mcp/addCommand.ts
new file mode 100644
index 0000000000000000000000000000000000000000..aeb3bc0537a6f266845e2b0d28c9971d8fd733c9
--- /dev/null
+++ b/src/commands/mcp/addCommand.ts
@@ -0,0 +1,280 @@
+/**
+ * MCP add CLI subcommand
+ *
+ * Extracted from main.tsx to enable direct testing.
+ */
+import { type Command, Option } from '@commander-js/extra-typings'
+import { cliError, cliOk } from '../../cli/exit.js'
+import {
+ type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
+ logEvent,
+} from '../../services/analytics/index.js'
+import {
+ readClientSecret,
+ saveMcpClientSecret,
+} from '../../services/mcp/auth.js'
+import { addMcpConfig } from '../../services/mcp/config.js'
+import {
+ describeMcpConfigFilePath,
+ ensureConfigScope,
+ ensureTransport,
+ parseHeaders,
+} from '../../services/mcp/utils.js'
+import {
+ getXaaIdpSettings,
+ isXaaEnabled,
+} from '../../services/mcp/xaaIdpLogin.js'
+import { parseEnvVars } from '../../utils/envUtils.js'
+import { jsonStringify } from '../../utils/slowOperations.js'
+
+/**
+ * Registers the `mcp add` subcommand on the given Commander command.
+ */
+export function registerMcpAddCommand(mcp: Command): void {
+ mcp
+ .command('add [args...]')
+ .description(
+ 'Add an MCP server to Claude Code.\n\n' +
+ 'Examples:\n' +
+ ' # Add HTTP server:\n' +
+ ' claude mcp add --transport http sentry https://mcp.sentry.dev/mcp\n\n' +
+ ' # Add HTTP server with headers:\n' +
+ ' claude mcp add --transport http corridor https://app.corridor.dev/api/mcp --header "Authorization: Bearer ..."\n\n' +
+ ' # Add stdio server with environment variables:\n' +
+ ' claude mcp add -e API_KEY=xxx my-server -- npx my-mcp-server\n\n' +
+ ' # Add stdio server with subprocess flags:\n' +
+ ' claude mcp add my-server -- my-command --some-flag arg1',
+ )
+ .option(
+ '-s, --scope ',
+ 'Configuration scope (local, user, or project)',
+ 'local',
+ )
+ .option(
+ '-t, --transport ',
+ 'Transport type (stdio, sse, http). Defaults to stdio if not specified.',
+ )
+ .option(
+ '-e, --env ',
+ 'Set environment variables (e.g. -e KEY=value)',
+ )
+ .option(
+ '-H, --header ',
+ 'Set WebSocket headers (e.g. -H "X-Api-Key: abc123" -H "X-Custom: value")',
+ )
+ .option('--client-id ', 'OAuth client ID for HTTP/SSE servers')
+ .option(
+ '--client-secret',
+ 'Prompt for OAuth client secret (or set MCP_CLIENT_SECRET env var)',
+ )
+ .option(
+ '--callback-port ',
+ 'Fixed port for OAuth callback (for servers requiring pre-registered redirect URIs)',
+ )
+ .helpOption('-h, --help', 'Display help for command')
+ .addOption(
+ new Option(
+ '--xaa',
+ "Enable XAA (SEP-990) for this server. Requires 'claude mcp xaa setup' first. Also requires --client-id and --client-secret (for the MCP server's AS).",
+ ).hideHelp(!isXaaEnabled()),
+ )
+ .action(async (name, commandOrUrl, args, options) => {
+ // Commander.js handles -- natively: it consumes -- and everything after becomes args
+ const actualCommand = commandOrUrl
+ const actualArgs = args
+
+ // If no name is provided, error
+ if (!name) {
+ cliError(
+ 'Error: Server name is required.\n' +
+ 'Usage: claude mcp add [args...]',
+ )
+ } else if (!actualCommand) {
+ cliError(
+ 'Error: Command is required when server name is provided.\n' +
+ 'Usage: claude mcp add [args...]',
+ )
+ }
+
+ try {
+ const scope = ensureConfigScope(options.scope)
+ const transport = ensureTransport(options.transport)
+
+ // XAA fail-fast: validate at add-time, not auth-time.
+ if (options.xaa && !isXaaEnabled()) {
+ cliError(
+ 'Error: --xaa requires CLAUDE_CODE_ENABLE_XAA=1 in your environment',
+ )
+ }
+ const xaa = Boolean(options.xaa)
+ if (xaa) {
+ const missing: string[] = []
+ if (!options.clientId) missing.push('--client-id')
+ if (!options.clientSecret) missing.push('--client-secret')
+ if (!getXaaIdpSettings()) {
+ missing.push(
+ "'claude mcp xaa setup' (settings.xaaIdp not configured)",
+ )
+ }
+ if (missing.length) {
+ cliError(`Error: --xaa requires: ${missing.join(', ')}`)
+ }
+ }
+
+ // Check if transport was explicitly provided
+ const transportExplicit = options.transport !== undefined
+
+ // Check if the command looks like a URL (likely incorrect usage)
+ const looksLikeUrl =
+ actualCommand.startsWith('http://') ||
+ actualCommand.startsWith('https://') ||
+ actualCommand.startsWith('localhost') ||
+ actualCommand.endsWith('/sse') ||
+ actualCommand.endsWith('/mcp')
+
+ logEvent('tengu_mcp_add', {
+ type: transport as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
+ scope:
+ scope as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
+ source:
+ 'command' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
+ transport:
+ transport as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
+ transportExplicit: transportExplicit,
+ looksLikeUrl: looksLikeUrl,
+ })
+
+ if (transport === 'sse') {
+ if (!actualCommand) {
+ cliError('Error: URL is required for SSE transport.')
+ }
+
+ const headers = options.header
+ ? parseHeaders(options.header)
+ : undefined
+
+ const callbackPort = options.callbackPort
+ ? parseInt(options.callbackPort, 10)
+ : undefined
+ const oauth =
+ options.clientId || callbackPort || xaa
+ ? {
+ ...(options.clientId ? { clientId: options.clientId } : {}),
+ ...(callbackPort ? { callbackPort } : {}),
+ ...(xaa ? { xaa: true } : {}),
+ }
+ : undefined
+
+ const clientSecret =
+ options.clientSecret && options.clientId
+ ? await readClientSecret()
+ : undefined
+
+ const serverConfig = {
+ type: 'sse' as const,
+ url: actualCommand,
+ headers,
+ oauth,
+ }
+ await addMcpConfig(name, serverConfig, scope)
+
+ if (clientSecret) {
+ saveMcpClientSecret(name, serverConfig, clientSecret)
+ }
+
+ process.stdout.write(
+ `Added SSE MCP server ${name} with URL: ${actualCommand} to ${scope} config\n`,
+ )
+ if (headers) {
+ process.stdout.write(
+ `Headers: ${jsonStringify(headers, null, 2)}\n`,
+ )
+ }
+ } else if (transport === 'http') {
+ if (!actualCommand) {
+ cliError('Error: URL is required for HTTP transport.')
+ }
+
+ const headers = options.header
+ ? parseHeaders(options.header)
+ : undefined
+
+ const callbackPort = options.callbackPort
+ ? parseInt(options.callbackPort, 10)
+ : undefined
+ const oauth =
+ options.clientId || callbackPort || xaa
+ ? {
+ ...(options.clientId ? { clientId: options.clientId } : {}),
+ ...(callbackPort ? { callbackPort } : {}),
+ ...(xaa ? { xaa: true } : {}),
+ }
+ : undefined
+
+ const clientSecret =
+ options.clientSecret && options.clientId
+ ? await readClientSecret()
+ : undefined
+
+ const serverConfig = {
+ type: 'http' as const,
+ url: actualCommand,
+ headers,
+ oauth,
+ }
+ await addMcpConfig(name, serverConfig, scope)
+
+ if (clientSecret) {
+ saveMcpClientSecret(name, serverConfig, clientSecret)
+ }
+
+ process.stdout.write(
+ `Added HTTP MCP server ${name} with URL: ${actualCommand} to ${scope} config\n`,
+ )
+ if (headers) {
+ process.stdout.write(
+ `Headers: ${jsonStringify(headers, null, 2)}\n`,
+ )
+ }
+ } else {
+ if (
+ options.clientId ||
+ options.clientSecret ||
+ options.callbackPort ||
+ options.xaa
+ ) {
+ process.stderr.write(
+ `Warning: --client-id, --client-secret, --callback-port, and --xaa are only supported for HTTP/SSE transports and will be ignored for stdio.\n`,
+ )
+ }
+
+ // Warn if this looks like a URL but transport wasn't explicitly specified
+ if (!transportExplicit && looksLikeUrl) {
+ process.stderr.write(
+ `\nWarning: The command "${actualCommand}" looks like a URL, but is being interpreted as a stdio server as --transport was not specified.\n`,
+ )
+ process.stderr.write(
+ `If this is an HTTP server, use: claude mcp add --transport http ${name} ${actualCommand}\n`,
+ )
+ process.stderr.write(
+ `If this is an SSE server, use: claude mcp add --transport sse ${name} ${actualCommand}\n`,
+ )
+ }
+
+ const env = parseEnvVars(options.env)
+ await addMcpConfig(
+ name,
+ { type: 'stdio', command: actualCommand, args: actualArgs, env },
+ scope,
+ )
+
+ process.stdout.write(
+ `Added stdio MCP server ${name} with command: ${actualCommand} ${actualArgs.join(' ')} to ${scope} config\n`,
+ )
+ }
+ cliOk(`File modified: ${describeMcpConfigFilePath(scope)}`)
+ } catch (error) {
+ cliError((error as Error).message)
+ }
+ })
+}
diff --git a/src/commands/mcp/index.ts b/src/commands/mcp/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..153ac5b2c6393d695393b45d8b060be443bfc69a
--- /dev/null
+++ b/src/commands/mcp/index.ts
@@ -0,0 +1,12 @@
+import type { Command } from '../../commands.js'
+
+const mcp = {
+ type: 'local-jsx',
+ name: 'mcp',
+ description: 'Manage MCP servers',
+ immediate: true,
+ argumentHint: '[enable|disable [server-name]]',
+ load: () => import('./mcp.js'),
+} satisfies Command
+
+export default mcp
diff --git a/src/commands/mcp/mcp.tsx b/src/commands/mcp/mcp.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..f5cf40daf997e8f9530e519ebfb64a8a190c03a6
--- /dev/null
+++ b/src/commands/mcp/mcp.tsx
@@ -0,0 +1,85 @@
+import { c as _c } from "react/compiler-runtime";
+import React, { useEffect, useRef } from 'react';
+import { MCPSettings } from '../../components/mcp/index.js';
+import { MCPReconnect } from '../../components/mcp/MCPReconnect.js';
+import { useMcpToggleEnabled } from '../../services/mcp/MCPConnectionManager.js';
+import { useAppState } from '../../state/AppState.js';
+import type { LocalJSXCommandOnDone } from '../../types/command.js';
+import { PluginSettings } from '../plugin/PluginSettings.js';
+
+// TODO: This is a hack to get the context value from toggleMcpServer (useContext only works in a component)
+// Ideally, all MCP state and functions would be in global state.
+function MCPToggle(t0) {
+ const $ = _c(7);
+ const {
+ action,
+ target,
+ onComplete
+ } = t0;
+ const mcpClients = useAppState(_temp);
+ const toggleMcpServer = useMcpToggleEnabled();
+ const didRun = useRef(false);
+ let t1;
+ let t2;
+ if ($[0] !== action || $[1] !== mcpClients || $[2] !== onComplete || $[3] !== target || $[4] !== toggleMcpServer) {
+ t1 = () => {
+ if (didRun.current) {
+ return;
+ }
+ didRun.current = true;
+ const isEnabling = action === "enable";
+ const clients = mcpClients.filter(_temp2);
+ const toToggle = target === "all" ? clients.filter(c_0 => isEnabling ? c_0.type === "disabled" : c_0.type !== "disabled") : clients.filter(c_1 => c_1.name === target);
+ if (toToggle.length === 0) {
+ onComplete(target === "all" ? `All MCP servers are already ${isEnabling ? "enabled" : "disabled"}` : `MCP server "${target}" not found`);
+ return;
+ }
+ for (const s_0 of toToggle) {
+ toggleMcpServer(s_0.name);
+ }
+ onComplete(target === "all" ? `${isEnabling ? "Enabled" : "Disabled"} ${toToggle.length} MCP server(s)` : `MCP server "${target}" ${isEnabling ? "enabled" : "disabled"}`);
+ };
+ t2 = [action, target, mcpClients, toggleMcpServer, onComplete];
+ $[0] = action;
+ $[1] = mcpClients;
+ $[2] = onComplete;
+ $[3] = target;
+ $[4] = toggleMcpServer;
+ $[5] = t1;
+ $[6] = t2;
+ } else {
+ t1 = $[5];
+ t2 = $[6];
+ }
+ useEffect(t1, t2);
+ return null;
+}
+function _temp2(c) {
+ return c.name !== "ide";
+}
+function _temp(s) {
+ return s.mcp.clients;
+}
+export async function call(onDone: LocalJSXCommandOnDone, _context: unknown, args?: string): Promise {
+ if (args) {
+ const parts = args.trim().split(/\s+/);
+
+ // Allow /mcp no-redirect to bypass the redirect for testing
+ if (parts[0] === 'no-redirect') {
+ return ;
+ }
+ if (parts[0] === 'reconnect' && parts[1]) {
+ return ;
+ }
+ if (parts[0] === 'enable' || parts[0] === 'disable') {
+ return 1 ? parts.slice(1).join(' ') : 'all'} onComplete={onDone} />;
+ }
+ }
+
+ // Redirect base /mcp command to /plugins installed tab for ant users
+ if ("external" === 'ant') {
+ return ;
+ }
+ return ;
+}
+//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsInVzZUVmZmVjdCIsInVzZVJlZiIsIk1DUFNldHRpbmdzIiwiTUNQUmVjb25uZWN0IiwidXNlTWNwVG9nZ2xlRW5hYmxlZCIsInVzZUFwcFN0YXRlIiwiTG9jYWxKU1hDb21tYW5kT25Eb25lIiwiUGx1Z2luU2V0dGluZ3MiLCJNQ1BUb2dnbGUiLCJ0MCIsIiQiLCJfYyIsImFjdGlvbiIsInRhcmdldCIsIm9uQ29tcGxldGUiLCJtY3BDbGllbnRzIiwiX3RlbXAiLCJ0b2dnbGVNY3BTZXJ2ZXIiLCJkaWRSdW4iLCJ0MSIsInQyIiwiY3VycmVudCIsImlzRW5hYmxpbmciLCJjbGllbnRzIiwiZmlsdGVyIiwiX3RlbXAyIiwidG9Ub2dnbGUiLCJjXzAiLCJjIiwidHlwZSIsImNfMSIsIm5hbWUiLCJsZW5ndGgiLCJzXzAiLCJzIiwibWNwIiwiY2FsbCIsIm9uRG9uZSIsIl9jb250ZXh0IiwiYXJncyIsIlByb21pc2UiLCJSZWFjdE5vZGUiLCJwYXJ0cyIsInRyaW0iLCJzcGxpdCIsInNsaWNlIiwiam9pbiJdLCJzb3VyY2VzIjpbIm1jcC50c3giXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IFJlYWN0LCB7IHVzZUVmZmVjdCwgdXNlUmVmIH0gZnJvbSAncmVhY3QnXG5pbXBvcnQgeyBNQ1BTZXR0aW5ncyB9IGZyb20gJy4uLy4uL2NvbXBvbmVudHMvbWNwL2luZGV4LmpzJ1xuaW1wb3J0IHsgTUNQUmVjb25uZWN0IH0gZnJvbSAnLi4vLi4vY29tcG9uZW50cy9tY3AvTUNQUmVjb25uZWN0LmpzJ1xuaW1wb3J0IHsgdXNlTWNwVG9nZ2xlRW5hYmxlZCB9IGZyb20gJy4uLy4uL3NlcnZpY2VzL21jcC9NQ1BDb25uZWN0aW9uTWFuYWdlci5qcydcbmltcG9ydCB7IHVzZUFwcFN0YXRlIH0gZnJvbSAnLi4vLi4vc3RhdGUvQXBwU3RhdGUuanMnXG5pbXBvcnQgdHlwZSB7IExvY2FsSlNYQ29tbWFuZE9uRG9uZSB9IGZyb20gJy4uLy4uL3R5cGVzL2NvbW1hbmQuanMnXG5pbXBvcnQgeyBQbHVnaW5TZXR0aW5ncyB9IGZyb20gJy4uL3BsdWdpbi9QbHVnaW5TZXR0aW5ncy5qcydcblxuLy8gVE9ETzogVGhpcyBpcyBhIGhhY2sgdG8gZ2V0IHRoZSBjb250ZXh0IHZhbHVlIGZyb20gdG9nZ2xlTWNwU2VydmVyICh1c2VDb250ZXh0IG9ubHkgd29ya3MgaW4gYSBjb21wb25lbnQpXG4vLyBJZGVhbGx5LCBhbGwgTUNQIHN0YXRlIGFuZCBmdW5jdGlvbnMgd291bGQgYmUgaW4gZ2xvYmFsIHN0YXRlLlxuZnVuY3Rpb24gTUNQVG9nZ2xlKHtcbiAgYWN0aW9uLFxuICB0YXJnZXQsXG4gIG9uQ29tcGxldGUsXG59OiB7XG4gIGFjdGlvbjogJ2VuYWJsZScgfCAnZGlzYWJsZSdcbiAgdGFyZ2V0OiBzdHJpbmdcbiAgb25Db21wbGV0ZTogKHJlc3VsdDogc3RyaW5nKSA9PiB2b2lkXG59KTogbnVsbCB7XG4gIGNvbnN0IG1jcENsaWVudHMgPSB1c2VBcHBTdGF0ZShzID0+IHMubWNwLmNsaWVudHMpXG4gIGNvbnN0IHRvZ2dsZU1jcFNlcnZlciA9IHVzZU1jcFRvZ2dsZUVuYWJsZWQoKVxuICBjb25zdCBkaWRSdW4gPSB1c2VSZWYoZmFsc2UpXG5cbiAgdXNlRWZmZWN0KCgpID0+IHtcbiAgICBpZiAoZGlkUnVuLmN1cnJlbnQpIHJldHVyblxuICAgIGRpZFJ1bi5jdXJyZW50ID0gdHJ1ZVxuXG4gICAgY29uc3QgaXNFbmFibGluZyA9IGFjdGlvbiA9PT0gJ2VuYWJsZSdcbiAgICBjb25zdCBjbGllbnRzID0gbWNwQ2xpZW50cy5maWx0ZXIoYyA9PiBjLm5hbWUgIT09ICdpZGUnKVxuICAgIGNvbnN0IHRvVG9nZ2xlID1cbiAgICAgIHRhcmdldCA9PT0gJ2FsbCdcbiAgICAgICAgPyBjbGllbnRzLmZpbHRlcihjID0+XG4gICAgICAgICAgICBpc0VuYWJsaW5nID8gYy50eXBlID09PSAnZGlzYWJsZWQnIDogYy50eXBlICE9PSAnZGlzYWJsZWQnLFxuICAgICAgICAgIClcbiAgICAgICAgOiBjbGllbnRzLmZpbHRlcihjID0+IGMubmFtZSA9PT0gdGFyZ2V0KVxuXG4gICAgaWYgKHRvVG9nZ2xlLmxlbmd0aCA9PT0gMCkge1xuICAgICAgb25Db21wbGV0ZShcbiAgICAgICAgdGFyZ2V0ID09PSAnYWxsJ1xuICAgICAgICAgID8gYEFsbCBNQ1Agc2VydmVycyBhcmUgYWxyZWFkeSAke2lzRW5hYmxpbmcgPyAnZW5hYmxlZCcgOiAnZGlzYWJsZWQnfWBcbiAgICAgICAgICA6IGBNQ1Agc2VydmVyIFwiJHt0YXJnZXR9XCIgbm90IGZvdW5kYCxcbiAgICAgIClcbiAgICAgIHJldHVyblxuICAgIH1cblxuICAgIGZvciAoY29uc3QgcyBvZiB0b1RvZ2dsZSkge1xuICAgICAgdm9pZCB0b2dnbGVNY3BTZXJ2ZXIocy5uYW1lKVxuICAgIH1cblxuICAgIG9uQ29tcGxldGUoXG4gICAgICB0YXJnZXQgPT09ICdhbGwnXG4gICAgICAgID8gYCR7aXNFbmFibGluZyA/ICdFbmFibGVkJyA6ICdEaXNhYmxlZCd9ICR7dG9Ub2dnbGUubGVuZ3RofSBNQ1Agc2VydmVyKHMpYFxuICAgICAgICA6IGBNQ1Agc2VydmVyIFwiJHt0YXJnZXR9XCIgJHtpc0VuYWJsaW5nID8gJ2VuYWJsZWQnIDogJ2Rpc2FibGVkJ31gLFxuICAgIClcbiAgfSwgW2FjdGlvbiwgdGFyZ2V0LCBtY3BDbGllbnRzLCB0b2dnbGVNY3BTZXJ2ZXIsIG9uQ29tcGxldGVdKVxuXG4gIHJldHVybiBudWxsXG59XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBjYWxsKFxuICBvbkRvbmU6IExvY2FsSlNYQ29tbWFuZE9uRG9uZSxcbiAgX2NvbnRleHQ6IHVua25vd24sXG4gIGFyZ3M/OiBzdHJpbmcsXG4pOiBQcm9taXNlPFJlYWN0LlJlYWN0Tm9kZT4ge1xuICBpZiAoYXJncykge1xuICAgIGNvbnN0IHBhcnRzID0gYXJncy50cmltKCkuc3BsaXQoL1xccysvKVxuXG4gICAgLy8gQWxsb3cgL21jcCBuby1yZWRpcmVjdCB0byBieXBhc3MgdGhlIHJlZGlyZWN0IGZvciB0ZXN0aW5nXG4gICAgaWYgKHBhcnRzWzBdID09PSAnbm8tcmVkaXJlY3QnKSB7XG4gICAgICByZXR1cm4gPE1DUFNldHRpbmdzIG9uQ29tcGxldGU9e29uRG9uZX0gLz5cbiAgICB9XG5cbiAgICBpZiAocGFydHNbMF0gPT09ICdyZWNvbm5lY3QnICYmIHBhcnRzWzFdKSB7XG4gICAgICByZXR1cm4gKFxuICAgICAgICA8TUNQUmVjb25uZWN0XG4gICAgICAgICAgc2VydmVyTmFtZT17cGFydHMuc2xpY2UoMSkuam9pbignICcpfVxuICAgICAgICAgIG9uQ29tcGxldGU9e29uRG9uZX1cbiAgICAgICAgLz5cbiAgICAgIClcbiAgICB9XG5cbiAgICBpZiAocGFydHNbMF0gPT09ICdlbmFibGUnIHx8IHBhcnRzWzBdID09PSAnZGlzYWJsZScpIHtcbiAgICAgIHJldHVybiAoXG4gICAgICAgIDxNQ1BUb2dnbGVcbiAgICAgICAgICBhY3Rpb249e3BhcnRzWzBdfVxuICAgICAgICAgIHRhcmdldD17cGFydHMubGVuZ3RoID4gMSA/IHBhcnRzLnNsaWNlKDEpLmpvaW4oJyAnKSA6ICdhbGwnfVxuICAgICAgICAgIG9uQ29tcGxldGU9e29uRG9uZX1cbiAgICAgICAgLz5cbiAgICAgIClcbiAgICB9XG4gIH1cblxuICAvLyBSZWRpcmVjdCBiYXNlIC9tY3AgY29tbWFuZCB0byAvcGx1Z2lucyBpbnN0YWxsZWQgdGFiIGZvciBhbnQgdXNlcnNcbiAgaWYgKFwiZXh0ZXJuYWxcIiA9PT0gJ2FudCcpIHtcbiAgICByZXR1cm4gKFxuICAgICAgPFBsdWdpblNldHRpbmdzXG4gICAgICAgIG9uQ29tcGxldGU9e29uRG9uZX1cbiAgICAgICAgYXJncz1cIm1hbmFnZVwiXG4gICAgICAgIHNob3dNY3BSZWRpcmVjdE1lc3NhZ2VcbiAgICAgIC8+XG4gICAgKVxuICB9XG5cbiAgcmV0dXJuIDxNQ1BTZXR0aW5ncyBvbkNvbXBsZXRlPXtvbkRvbmV9IC8+XG59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
\ No newline at end of file
diff --git a/src/commands/mcp/xaaIdpCommand.ts b/src/commands/mcp/xaaIdpCommand.ts
new file mode 100644
index 0000000000000000000000000000000000000000..df8a8881bf6eecfdcbff5b373601da5239b5d720
--- /dev/null
+++ b/src/commands/mcp/xaaIdpCommand.ts
@@ -0,0 +1,266 @@
+/**
+ * `claude mcp xaa` — manage the XAA (SEP-990) IdP connection.
+ *
+ * The IdP connection is user-level: configure once, all XAA-enabled MCP
+ * servers reuse it. Lives in settings.xaaIdp (non-secret) + a keychain slot
+ * keyed by issuer (secret). Separate trust domain from per-server AS secrets.
+ */
+import type { Command } from '@commander-js/extra-typings'
+import { cliError, cliOk } from '../../cli/exit.js'
+import {
+ acquireIdpIdToken,
+ clearIdpClientSecret,
+ clearIdpIdToken,
+ getCachedIdpIdToken,
+ getIdpClientSecret,
+ getXaaIdpSettings,
+ issuerKey,
+ saveIdpClientSecret,
+ saveIdpIdTokenFromJwt,
+} from '../../services/mcp/xaaIdpLogin.js'
+import { errorMessage } from '../../utils/errors.js'
+import { updateSettingsForSource } from '../../utils/settings/settings.js'
+
+export function registerMcpXaaIdpCommand(mcp: Command): void {
+ const xaaIdp = mcp
+ .command('xaa')
+ .description('Manage the XAA (SEP-990) IdP connection')
+
+ xaaIdp
+ .command('setup')
+ .description(
+ 'Configure the IdP connection (one-time setup for all XAA-enabled servers)',
+ )
+ .requiredOption('--issuer ', 'IdP issuer URL (OIDC discovery)')
+ .requiredOption('--client-id ', "Claude Code's client_id at the IdP")
+ .option(
+ '--client-secret',
+ 'Read IdP client secret from MCP_XAA_IDP_CLIENT_SECRET env var',
+ )
+ .option(
+ '--callback-port ',
+ 'Fixed loopback callback port (only if IdP does not honor RFC 8252 port-any matching)',
+ )
+ .action(options => {
+ // Validate everything BEFORE any writes. An exit(1) mid-write leaves
+ // settings configured but keychain missing — confusing state.
+ // updateSettingsForSource doesn't schema-check on write; a non-URL
+ // issuer lands on disk and then poisons the whole userSettings source
+ // on next launch (SettingsSchema .url() fails → parseSettingsFile
+ // returns { settings: null }, dropping everything, not just xaaIdp).
+ let issuerUrl: URL
+ try {
+ issuerUrl = new URL(options.issuer)
+ } catch {
+ return cliError(
+ `Error: --issuer must be a valid URL (got "${options.issuer}")`,
+ )
+ }
+ // OIDC discovery + token exchange run against this host. Allow http://
+ // only for loopback (conformance harness mock IdP); anything else leaks
+ // the client secret and authorization code over plaintext.
+ if (
+ issuerUrl.protocol !== 'https:' &&
+ !(
+ issuerUrl.protocol === 'http:' &&
+ (issuerUrl.hostname === 'localhost' ||
+ issuerUrl.hostname === '127.0.0.1' ||
+ issuerUrl.hostname === '[::1]')
+ )
+ ) {
+ return cliError(
+ `Error: --issuer must use https:// (got "${issuerUrl.protocol}//${issuerUrl.host}")`,
+ )
+ }
+ const callbackPort = options.callbackPort
+ ? parseInt(options.callbackPort, 10)
+ : undefined
+ // callbackPort <= 0 fails Zod's .positive() on next launch — same
+ // settings-poisoning failure mode as the issuer check above.
+ if (
+ callbackPort !== undefined &&
+ (!Number.isInteger(callbackPort) || callbackPort <= 0)
+ ) {
+ return cliError('Error: --callback-port must be a positive integer')
+ }
+ const secret = options.clientSecret
+ ? process.env.MCP_XAA_IDP_CLIENT_SECRET
+ : undefined
+ if (options.clientSecret && !secret) {
+ return cliError(
+ 'Error: --client-secret requires MCP_XAA_IDP_CLIENT_SECRET env var',
+ )
+ }
+
+ // Read old config now (before settings overwrite) so we can clear stale
+ // keychain slots after a successful write. `clear` can't do this after
+ // the fact — it reads the *current* settings.xaaIdp, which by then is
+ // the new one.
+ const old = getXaaIdpSettings()
+ const oldIssuer = old?.issuer
+ const oldClientId = old?.clientId
+
+ // callbackPort MUST be present (even as undefined) — mergeWith deep-merges
+ // and only deletes on explicit `undefined`, not on absent key. A conditional
+ // spread would leak a prior fixed port into a new IdP's config.
+ const { error } = updateSettingsForSource('userSettings', {
+ xaaIdp: {
+ issuer: options.issuer,
+ clientId: options.clientId,
+ callbackPort,
+ },
+ })
+ if (error) {
+ return cliError(`Error writing settings: ${error.message}`)
+ }
+
+ // Clear stale keychain slots only after settings write succeeded —
+ // otherwise a write failure leaves settings pointing at oldIssuer with
+ // its secret already gone. Compare via issuerKey(): trailing-slash or
+ // host-case differences normalize to the same keychain slot.
+ if (oldIssuer) {
+ if (issuerKey(oldIssuer) !== issuerKey(options.issuer)) {
+ clearIdpIdToken(oldIssuer)
+ clearIdpClientSecret(oldIssuer)
+ } else if (oldClientId !== options.clientId) {
+ // Same issuer slot but different OAuth client registration — the
+ // cached id_token's aud claim and the stored secret are both for the
+ // old client. `xaa login` would send {new clientId, old secret} and
+ // fail with opaque `invalid_client`; downstream SEP-990 exchange
+ // would fail aud validation. Keep both when clientId is unchanged:
+ // re-setup without --client-secret means "tweak port, keep secret".
+ clearIdpIdToken(oldIssuer)
+ clearIdpClientSecret(oldIssuer)
+ }
+ }
+
+ if (secret) {
+ const { success, warning } = saveIdpClientSecret(options.issuer, secret)
+ if (!success) {
+ return cliError(
+ `Error: settings written but keychain save failed${warning ? ` — ${warning}` : ''}. ` +
+ `Re-run with --client-secret once keychain is available.`,
+ )
+ }
+ }
+
+ cliOk(`XAA IdP connection configured for ${options.issuer}`)
+ })
+
+ xaaIdp
+ .command('login')
+ .description(
+ 'Cache an IdP id_token so XAA-enabled MCP servers authenticate ' +
+ 'silently. Default: run the OIDC browser login. With --id-token: ' +
+ 'write a pre-obtained JWT directly (used by conformance/e2e tests ' +
+ 'where the mock IdP does not serve /authorize).',
+ )
+ .option(
+ '--force',
+ 'Ignore any cached id_token and re-login (useful after IdP-side revocation)',
+ )
+ // TODO(paulc): read the JWT from stdin instead of argv to keep it out of
+ // shell history. Fine for conformance (docker exec uses argv directly,
+ // no shell parser), but a real user would want `echo $TOKEN | ... --stdin`.
+ .option(
+ '--id-token ',
+ 'Write this pre-obtained id_token directly to cache, skipping the OIDC browser login',
+ )
+ .action(async options => {
+ const idp = getXaaIdpSettings()
+ if (!idp) {
+ return cliError(
+ "Error: no XAA IdP connection. Run 'claude mcp xaa setup' first.",
+ )
+ }
+
+ // Direct-inject path: skip cache check, skip OIDC. Writing IS the
+ // operation. Issuer comes from settings (single source of truth), not
+ // a separate flag — one less thing to desync.
+ if (options.idToken) {
+ const expiresAt = saveIdpIdTokenFromJwt(idp.issuer, options.idToken)
+ return cliOk(
+ `id_token cached for ${idp.issuer} (expires ${new Date(expiresAt).toISOString()})`,
+ )
+ }
+
+ if (options.force) {
+ clearIdpIdToken(idp.issuer)
+ }
+
+ const wasCached = getCachedIdpIdToken(idp.issuer) !== undefined
+ if (wasCached) {
+ return cliOk(
+ `Already logged in to ${idp.issuer} (cached id_token still valid). Use --force to re-login.`,
+ )
+ }
+
+ process.stdout.write(`Opening browser for IdP login at ${idp.issuer}…\n`)
+ try {
+ await acquireIdpIdToken({
+ idpIssuer: idp.issuer,
+ idpClientId: idp.clientId,
+ idpClientSecret: getIdpClientSecret(idp.issuer),
+ callbackPort: idp.callbackPort,
+ onAuthorizationUrl: url => {
+ process.stdout.write(
+ `If the browser did not open, visit:\n ${url}\n`,
+ )
+ },
+ })
+ cliOk(
+ `Logged in. MCP servers with --xaa will now authenticate silently.`,
+ )
+ } catch (e) {
+ cliError(`IdP login failed: ${errorMessage(e)}`)
+ }
+ })
+
+ xaaIdp
+ .command('show')
+ .description('Show the current IdP connection config')
+ .action(() => {
+ const idp = getXaaIdpSettings()
+ if (!idp) {
+ return cliOk('No XAA IdP connection configured.')
+ }
+ const hasSecret = getIdpClientSecret(idp.issuer) !== undefined
+ const hasIdToken = getCachedIdpIdToken(idp.issuer) !== undefined
+ process.stdout.write(`Issuer: ${idp.issuer}\n`)
+ process.stdout.write(`Client ID: ${idp.clientId}\n`)
+ if (idp.callbackPort !== undefined) {
+ process.stdout.write(`Callback port: ${idp.callbackPort}\n`)
+ }
+ process.stdout.write(
+ `Client secret: ${hasSecret ? '(stored in keychain)' : '(not set — PKCE-only)'}\n`,
+ )
+ process.stdout.write(
+ `Logged in: ${hasIdToken ? 'yes (id_token cached)' : "no — run 'claude mcp xaa login'"}\n`,
+ )
+ cliOk()
+ })
+
+ xaaIdp
+ .command('clear')
+ .description('Clear the IdP connection config and cached id_token')
+ .action(() => {
+ // Read issuer first so we can clear the right keychain slots.
+ const idp = getXaaIdpSettings()
+ // updateSettingsForSource uses mergeWith: set to undefined (not delete)
+ // to signal key removal.
+ const { error } = updateSettingsForSource('userSettings', {
+ xaaIdp: undefined,
+ })
+ if (error) {
+ return cliError(`Error writing settings: ${error.message}`)
+ }
+ // Clear keychain only after settings write succeeded — otherwise a
+ // write failure leaves settings pointing at the IdP with its secrets
+ // already gone (same pattern as `setup`'s old-issuer cleanup).
+ if (idp) {
+ clearIdpIdToken(idp.issuer)
+ clearIdpClientSecret(idp.issuer)
+ }
+ cliOk('XAA IdP connection cleared')
+ })
+}
diff --git a/src/commands/memory/index.ts b/src/commands/memory/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..31f254c95d63206431d3f1a72dbedcc2433c009c
--- /dev/null
+++ b/src/commands/memory/index.ts
@@ -0,0 +1,10 @@
+import type { Command } from '../../commands.js'
+
+const memory: Command = {
+ type: 'local-jsx',
+ name: 'memory',
+ description: 'Edit Claude memory files',
+ load: () => import('./memory.js'),
+}
+
+export default memory
diff --git a/src/commands/memory/memory.tsx b/src/commands/memory/memory.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..5ca53ae450e935bfdbf569a06c80321051fef42e
--- /dev/null
+++ b/src/commands/memory/memory.tsx
@@ -0,0 +1,90 @@
+import { mkdir, writeFile } from 'fs/promises';
+import * as React from 'react';
+import type { CommandResultDisplay } from '../../commands.js';
+import { Dialog } from '../../components/design-system/Dialog.js';
+import { MemoryFileSelector } from '../../components/memory/MemoryFileSelector.js';
+import { getRelativeMemoryPath } from '../../components/memory/MemoryUpdateNotification.js';
+import { Box, Link, Text } from '../../ink.js';
+import type { LocalJSXCommandCall } from '../../types/command.js';
+import { clearMemoryFileCaches, getMemoryFiles } from '../../utils/claudemd.js';
+import { getClaudeConfigHomeDir } from '../../utils/envUtils.js';
+import { getErrnoCode } from '../../utils/errors.js';
+import { logError } from '../../utils/log.js';
+import { editFileInEditor } from '../../utils/promptEditor.js';
+function MemoryCommand({
+ onDone
+}: {
+ onDone: (result?: string, options?: {
+ display?: CommandResultDisplay;
+ }) => void;
+}): React.ReactNode {
+ const handleSelectMemoryFile = async (memoryPath: string) => {
+ try {
+ // Create claude directory if it doesn't exist (idempotent with recursive)
+ if (memoryPath.includes(getClaudeConfigHomeDir())) {
+ await mkdir(getClaudeConfigHomeDir(), {
+ recursive: true
+ });
+ }
+
+ // Create file if it doesn't exist (wx flag fails if file exists,
+ // which we catch to preserve existing content)
+ try {
+ await writeFile(memoryPath, '', {
+ encoding: 'utf8',
+ flag: 'wx'
+ });
+ } catch (e: unknown) {
+ if (getErrnoCode(e) !== 'EEXIST') {
+ throw e;
+ }
+ }
+ await editFileInEditor(memoryPath);
+
+ // Determine which environment variable controls the editor
+ let editorSource = 'default';
+ let editorValue = '';
+ if (process.env.VISUAL) {
+ editorSource = '$VISUAL';
+ editorValue = process.env.VISUAL;
+ } else if (process.env.EDITOR) {
+ editorSource = '$EDITOR';
+ editorValue = process.env.EDITOR;
+ }
+ const editorInfo = editorSource !== 'default' ? `Using ${editorSource}="${editorValue}".` : '';
+ const editorHint = editorInfo ? `> ${editorInfo} To change editor, set $EDITOR or $VISUAL environment variable.` : `> To use a different editor, set the $EDITOR or $VISUAL environment variable.`;
+ onDone(`Opened memory file at ${getRelativeMemoryPath(memoryPath)}\n\n${editorHint}`, {
+ display: 'system'
+ });
+ } catch (error) {
+ logError(error);
+ onDone(`Error opening memory file: ${error}`);
+ }
+ };
+ const handleCancel = () => {
+ onDone('Cancelled memory editing', {
+ display: 'system'
+ });
+ };
+ return ;
+}
+export const call: LocalJSXCommandCall = async onDone => {
+ // Clear + prime before rendering — Suspense handles the unprimed case,
+ // but awaiting here avoids a fallback flash on initial open.
+ clearMemoryFileCaches();
+ await getMemoryFiles();
+ return ;
+};
+//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJta2RpciIsIndyaXRlRmlsZSIsIlJlYWN0IiwiQ29tbWFuZFJlc3VsdERpc3BsYXkiLCJEaWFsb2ciLCJNZW1vcnlGaWxlU2VsZWN0b3IiLCJnZXRSZWxhdGl2ZU1lbW9yeVBhdGgiLCJCb3giLCJMaW5rIiwiVGV4dCIsIkxvY2FsSlNYQ29tbWFuZENhbGwiLCJjbGVhck1lbW9yeUZpbGVDYWNoZXMiLCJnZXRNZW1vcnlGaWxlcyIsImdldENsYXVkZUNvbmZpZ0hvbWVEaXIiLCJnZXRFcnJub0NvZGUiLCJsb2dFcnJvciIsImVkaXRGaWxlSW5FZGl0b3IiLCJNZW1vcnlDb21tYW5kIiwib25Eb25lIiwicmVzdWx0Iiwib3B0aW9ucyIsImRpc3BsYXkiLCJSZWFjdE5vZGUiLCJoYW5kbGVTZWxlY3RNZW1vcnlGaWxlIiwibWVtb3J5UGF0aCIsImluY2x1ZGVzIiwicmVjdXJzaXZlIiwiZW5jb2RpbmciLCJmbGFnIiwiZSIsImVkaXRvclNvdXJjZSIsImVkaXRvclZhbHVlIiwicHJvY2VzcyIsImVudiIsIlZJU1VBTCIsIkVESVRPUiIsImVkaXRvckluZm8iLCJlZGl0b3JIaW50IiwiZXJyb3IiLCJoYW5kbGVDYW5jZWwiLCJjYWxsIl0sInNvdXJjZXMiOlsibWVtb3J5LnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBta2Rpciwgd3JpdGVGaWxlIH0gZnJvbSAnZnMvcHJvbWlzZXMnXG5pbXBvcnQgKiBhcyBSZWFjdCBmcm9tICdyZWFjdCdcbmltcG9ydCB0eXBlIHsgQ29tbWFuZFJlc3VsdERpc3BsYXkgfSBmcm9tICcuLi8uLi9jb21tYW5kcy5qcydcbmltcG9ydCB7IERpYWxvZyB9IGZyb20gJy4uLy4uL2NvbXBvbmVudHMvZGVzaWduLXN5c3RlbS9EaWFsb2cuanMnXG5pbXBvcnQgeyBNZW1vcnlGaWxlU2VsZWN0b3IgfSBmcm9tICcuLi8uLi9jb21wb25lbnRzL21lbW9yeS9NZW1vcnlGaWxlU2VsZWN0b3IuanMnXG5pbXBvcnQgeyBnZXRSZWxhdGl2ZU1lbW9yeVBhdGggfSBmcm9tICcuLi8uLi9jb21wb25lbnRzL21lbW9yeS9NZW1vcnlVcGRhdGVOb3RpZmljYXRpb24uanMnXG5pbXBvcnQgeyBCb3gsIExpbmssIFRleHQgfSBmcm9tICcuLi8uLi9pbmsuanMnXG5pbXBvcnQgdHlwZSB7IExvY2FsSlNYQ29tbWFuZENhbGwgfSBmcm9tICcuLi8uLi90eXBlcy9jb21tYW5kLmpzJ1xuaW1wb3J0IHsgY2xlYXJNZW1vcnlGaWxlQ2FjaGVzLCBnZXRNZW1vcnlGaWxlcyB9IGZyb20gJy4uLy4uL3V0aWxzL2NsYXVkZW1kLmpzJ1xuaW1wb3J0IHsgZ2V0Q2xhdWRlQ29uZmlnSG9tZURpciB9IGZyb20gJy4uLy4uL3V0aWxzL2VudlV0aWxzLmpzJ1xuaW1wb3J0IHsgZ2V0RXJybm9Db2RlIH0gZnJvbSAnLi4vLi4vdXRpbHMvZXJyb3JzLmpzJ1xuaW1wb3J0IHsgbG9nRXJyb3IgfSBmcm9tICcuLi8uLi91dGlscy9sb2cuanMnXG5pbXBvcnQgeyBlZGl0RmlsZUluRWRpdG9yIH0gZnJvbSAnLi4vLi4vdXRpbHMvcHJvbXB0RWRpdG9yLmpzJ1xuXG5mdW5jdGlvbiBNZW1vcnlDb21tYW5kKHtcbiAgb25Eb25lLFxufToge1xuICBvbkRvbmU6IChcbiAgICByZXN1bHQ/OiBzdHJpbmcsXG4gICAgb3B0aW9ucz86IHsgZGlzcGxheT86IENvbW1hbmRSZXN1bHREaXNwbGF5IH0sXG4gICkgPT4gdm9pZFxufSk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gIGNvbnN0IGhhbmRsZVNlbGVjdE1lbW9yeUZpbGUgPSBhc3luYyAobWVtb3J5UGF0aDogc3RyaW5nKSA9PiB7XG4gICAgdHJ5IHtcbiAgICAgIC8vIENyZWF0ZSBjbGF1ZGUgZGlyZWN0b3J5IGlmIGl0IGRvZXNuJ3QgZXhpc3QgKGlkZW1wb3RlbnQgd2l0aCByZWN1cnNpdmUpXG4gICAgICBpZiAobWVtb3J5UGF0aC5pbmNsdWRlcyhnZXRDbGF1ZGVDb25maWdIb21lRGlyKCkpKSB7XG4gICAgICAgIGF3YWl0IG1rZGlyKGdldENsYXVkZUNvbmZpZ0hvbWVEaXIoKSwgeyByZWN1cnNpdmU6IHRydWUgfSlcbiAgICAgIH1cblxuICAgICAgLy8gQ3JlYXRlIGZpbGUgaWYgaXQgZG9lc24ndCBleGlzdCAod3ggZmxhZyBmYWlscyBpZiBmaWxlIGV4aXN0cyxcbiAgICAgIC8vIHdoaWNoIHdlIGNhdGNoIHRvIHByZXNlcnZlIGV4aXN0aW5nIGNvbnRlbnQpXG4gICAgICB0cnkge1xuICAgICAgICBhd2FpdCB3cml0ZUZpbGUobWVtb3J5UGF0aCwgJycsIHsgZW5jb2Rpbmc6ICd1dGY4JywgZmxhZzogJ3d4JyB9KVxuICAgICAgfSBjYXRjaCAoZTogdW5rbm93bikge1xuICAgICAgICBpZiAoZ2V0RXJybm9Db2RlKGUpICE9PSAnRUVYSVNUJykge1xuICAgICAgICAgIHRocm93IGVcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICBhd2FpdCBlZGl0RmlsZUluRWRpdG9yKG1lbW9yeVBhdGgpXG5cbiAgICAgIC8vIERldGVybWluZSB3aGljaCBlbnZpcm9ubWVudCB2YXJpYWJsZSBjb250cm9scyB0aGUgZWRpdG9yXG4gICAgICBsZXQgZWRpdG9yU291cmNlID0gJ2RlZmF1bHQnXG4gICAgICBsZXQgZWRpdG9yVmFsdWUgPSAnJ1xuICAgICAgaWYgKHByb2Nlc3MuZW52LlZJU1VBTCkge1xuICAgICAgICBlZGl0b3JTb3VyY2UgPSAnJFZJU1VBTCdcbiAgICAgICAgZWRpdG9yVmFsdWUgPSBwcm9jZXNzLmVudi5WSVNVQUxcbiAgICAgIH0gZWxzZSBpZiAocHJvY2Vzcy5lbnYuRURJVE9SKSB7XG4gICAgICAgIGVkaXRvclNvdXJjZSA9ICckRURJVE9SJ1xuICAgICAgICBlZGl0b3JWYWx1ZSA9IHByb2Nlc3MuZW52LkVESVRPUlxuICAgICAgfVxuXG4gICAgICBjb25zdCBlZGl0b3JJbmZvID1cbiAgICAgICAgZWRpdG9yU291cmNlICE9PSAnZGVmYXVsdCdcbiAgICAgICAgICA/IGBVc2luZyAke2VkaXRvclNvdXJjZX09XCIke2VkaXRvclZhbHVlfVwiLmBcbiAgICAgICAgICA6ICcnXG5cbiAgICAgIGNvbnN0IGVkaXRvckhpbnQgPSBlZGl0b3JJbmZvXG4gICAgICAgID8gYD4gJHtlZGl0b3JJbmZvfSBUbyBjaGFuZ2UgZWRpdG9yLCBzZXQgJEVESVRPUiBvciAkVklTVUFMIGVudmlyb25tZW50IHZhcmlhYmxlLmBcbiAgICAgICAgOiBgPiBUbyB1c2UgYSBkaWZmZXJlbnQgZWRpdG9yLCBzZXQgdGhlICRFRElUT1Igb3IgJFZJU1VBTCBlbnZpcm9ubWVudCB2YXJpYWJsZS5gXG5cbiAgICAgIG9uRG9uZShcbiAgICAgICAgYE9wZW5lZCBtZW1vcnkgZmlsZSBhdCAke2dldFJlbGF0aXZlTWVtb3J5UGF0aChtZW1vcnlQYXRoKX1cXG5cXG4ke2VkaXRvckhpbnR9YCxcbiAgICAgICAgeyBkaXNwbGF5OiAnc3lzdGVtJyB9LFxuICAgICAgKVxuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICBsb2dFcnJvcihlcnJvcilcbiAgICAgIG9uRG9uZShgRXJyb3Igb3BlbmluZyBtZW1vcnkgZmlsZTogJHtlcnJvcn1gKVxuICAgIH1cbiAgfVxuXG4gIGNvbnN0IGhhbmRsZUNhbmNlbCA9ICgpID0+IHtcbiAgICBvbkRvbmUoJ0NhbmNlbGxlZCBtZW1vcnkgZWRpdGluZycsIHsgZGlzcGxheTogJ3N5c3RlbScgfSlcbiAgfVxuXG4gIHJldHVybiAoXG4gICAgPERpYWxvZyB0aXRsZT1cIk1lbW9yeVwiIG9uQ2FuY2VsPXtoYW5kbGVDYW5jZWx9IGNvbG9yPVwicmVtZW1iZXJcIj5cbiAgICAgIDxCb3ggZmxleERpcmVjdGlvbj1cImNvbHVtblwiPlxuICAgICAgICA8UmVhY3QuU3VzcGVuc2UgZmFsbGJhY2s9e251bGx9PlxuICAgICAgICAgIDxNZW1vcnlGaWxlU2VsZWN0b3JcbiAgICAgICAgICAgIG9uU2VsZWN0PXtoYW5kbGVTZWxlY3RNZW1vcnlGaWxlfVxuICAgICAgICAgICAgb25DYW5jZWw9e2hhbmRsZUNhbmNlbH1cbiAgICAgICAgICAvPlxuICAgICAgICA8L1JlYWN0LlN1c3BlbnNlPlxuXG4gICAgICAgIDxCb3ggbWFyZ2luVG9wPXsxfT5cbiAgICAgICAgICA8VGV4dCBkaW1Db2xvcj5cbiAgICAgICAgICAgIExlYXJuIG1vcmU6IDxMaW5rIHVybD1cImh0dHBzOi8vY29kZS5jbGF1ZGUuY29tL2RvY3MvZW4vbWVtb3J5XCIgLz5cbiAgICAgICAgICA8L1RleHQ+XG4gICAgICAgIDwvQm94PlxuICAgICAgPC9Cb3g+XG4gICAgPC9EaWFsb2c+XG4gIClcbn1cblxuZXhwb3J0IGNvbnN0IGNhbGw6IExvY2FsSlNYQ29tbWFuZENhbGwgPSBhc3luYyBvbkRvbmUgPT4ge1xuICAvLyBDbGVhciArIHByaW1lIGJlZm9yZSByZW5kZXJpbmcg4oCUIFN1c3BlbnNlIGhhbmRsZXMgdGhlIHVucHJpbWVkIGNhc2UsXG4gIC8vIGJ1dCBhd2FpdGluZyBoZXJlIGF2b2lkcyBhIGZhbGxiYWNrIGZsYXNoIG9uIGluaXRpYWwgb3Blbi5cbiAgY2xlYXJNZW1vcnlGaWxlQ2FjaGVzKClcbiAgYXdhaXQgZ2V0TWVtb3J5RmlsZXMoKVxuICByZXR1cm4gPE1lbW9yeUNvbW1hbmQgb25Eb25lPXtvbkRvbmV9IC8+XG59XG4iXSwibWFwcGluZ3MiOiJBQUFBLFNBQVNBLEtBQUssRUFBRUMsU0FBUyxRQUFRLGFBQWE7QUFDOUMsT0FBTyxLQUFLQyxLQUFLLE1BQU0sT0FBTztBQUM5QixjQUFjQyxvQkFBb0IsUUFBUSxtQkFBbUI7QUFDN0QsU0FBU0MsTUFBTSxRQUFRLDBDQUEwQztBQUNqRSxTQUFTQyxrQkFBa0IsUUFBUSwrQ0FBK0M7QUFDbEYsU0FBU0MscUJBQXFCLFFBQVEscURBQXFEO0FBQzNGLFNBQVNDLEdBQUcsRUFBRUMsSUFBSSxFQUFFQyxJQUFJLFFBQVEsY0FBYztBQUM5QyxjQUFjQyxtQkFBbUIsUUFBUSx3QkFBd0I7QUFDakUsU0FBU0MscUJBQXFCLEVBQUVDLGNBQWMsUUFBUSx5QkFBeUI7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
\ No newline at end of file
diff --git a/src/commands/mobile/index.ts b/src/commands/mobile/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2657aa8429532c00252cbf5f498e9117a0961f1e
--- /dev/null
+++ b/src/commands/mobile/index.ts
@@ -0,0 +1,11 @@
+import type { Command } from '../../commands.js'
+
+const mobile = {
+ type: 'local-jsx',
+ name: 'mobile',
+ aliases: ['ios', 'android'],
+ description: 'Show QR code to download the Claude mobile app',
+ load: () => import('./mobile.js'),
+} satisfies Command
+
+export default mobile
diff --git a/src/commands/mobile/mobile.tsx b/src/commands/mobile/mobile.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..40968c19ec83dad03a686c613dce3cfd517d7099
--- /dev/null
+++ b/src/commands/mobile/mobile.tsx
@@ -0,0 +1,274 @@
+import { c as _c } from "react/compiler-runtime";
+import { toString as qrToString } from 'qrcode';
+import * as React from 'react';
+import { useCallback, useEffect, useState } from 'react';
+import { Pane } from '../../components/design-system/Pane.js';
+import type { KeyboardEvent } from '../../ink/events/keyboard-event.js';
+import { Box, Text } from '../../ink.js';
+import { useKeybinding } from '../../keybindings/useKeybinding.js';
+import type { LocalJSXCommandOnDone } from '../../types/command.js';
+type Platform = 'ios' | 'android';
+type Props = {
+ onDone: () => void;
+};
+const PLATFORMS: Record = {
+ ios: {
+ url: 'https://apps.apple.com/app/claude-by-anthropic/id6473753684'
+ },
+ android: {
+ url: 'https://play.google.com/store/apps/details?id=com.anthropic.claude'
+ }
+};
+function MobileQRCode(t0) {
+ const $ = _c(52);
+ const {
+ onDone
+ } = t0;
+ const [platform, setPlatform] = useState("ios");
+ let t1;
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
+ t1 = {
+ ios: "",
+ android: ""
+ };
+ $[0] = t1;
+ } else {
+ t1 = $[0];
+ }
+ const [qrCodes, setQrCodes] = useState(t1);
+ const {
+ url
+ } = PLATFORMS[platform];
+ const qrCode = qrCodes[platform];
+ let t2;
+ let t3;
+ if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
+ t2 = () => {
+ const generateQRCodes = async function generateQRCodes() {
+ const [ios, android] = await Promise.all([qrToString(PLATFORMS.ios.url, {
+ type: "utf8",
+ errorCorrectionLevel: "L"
+ }), qrToString(PLATFORMS.android.url, {
+ type: "utf8",
+ errorCorrectionLevel: "L"
+ })]);
+ setQrCodes({
+ ios,
+ android
+ });
+ };
+ generateQRCodes().catch(_temp);
+ };
+ t3 = [];
+ $[1] = t2;
+ $[2] = t3;
+ } else {
+ t2 = $[1];
+ t3 = $[2];
+ }
+ useEffect(t2, t3);
+ let t4;
+ if ($[3] !== onDone) {
+ t4 = () => {
+ onDone();
+ };
+ $[3] = onDone;
+ $[4] = t4;
+ } else {
+ t4 = $[4];
+ }
+ const handleClose = t4;
+ let t5;
+ if ($[5] === Symbol.for("react.memo_cache_sentinel")) {
+ t5 = {
+ context: "Confirmation"
+ };
+ $[5] = t5;
+ } else {
+ t5 = $[5];
+ }
+ useKeybinding("confirm:no", handleClose, t5);
+ let t6;
+ if ($[6] !== onDone) {
+ t6 = function handleKeyDown(e) {
+ if (e.key === "q" || e.ctrl && e.key === "c") {
+ e.preventDefault();
+ onDone();
+ return;
+ }
+ if (e.key === "tab" || e.key === "left" || e.key === "right") {
+ e.preventDefault();
+ setPlatform(_temp2);
+ }
+ };
+ $[6] = onDone;
+ $[7] = t6;
+ } else {
+ t6 = $[7];
+ }
+ const handleKeyDown = t6;
+ let T0;
+ let T1;
+ let t10;
+ let t11;
+ let t12;
+ let t13;
+ let t7;
+ let t8;
+ let t9;
+ if ($[8] !== handleKeyDown || $[9] !== qrCode) {
+ const lines = qrCode.split("\n").filter(_temp3);
+ T1 = Pane;
+ T0 = Box;
+ t7 = "column";
+ t8 = 0;
+ t9 = true;
+ t10 = handleKeyDown;
+ if ($[19] === Symbol.for("react.memo_cache_sentinel")) {
+ t11 = ;
+ t12 = ;
+ $[19] = t11;
+ $[20] = t12;
+ } else {
+ t11 = $[19];
+ t12 = $[20];
+ }
+ t13 = lines.map(_temp4);
+ $[8] = handleKeyDown;
+ $[9] = qrCode;
+ $[10] = T0;
+ $[11] = T1;
+ $[12] = t10;
+ $[13] = t11;
+ $[14] = t12;
+ $[15] = t13;
+ $[16] = t7;
+ $[17] = t8;
+ $[18] = t9;
+ } else {
+ T0 = $[10];
+ T1 = $[11];
+ t10 = $[12];
+ t11 = $[13];
+ t12 = $[14];
+ t13 = $[15];
+ t7 = $[16];
+ t8 = $[17];
+ t9 = $[18];
+ }
+ let t14;
+ let t15;
+ if ($[21] === Symbol.for("react.memo_cache_sentinel")) {
+ t14 = ;
+ t15 = ;
+ $[21] = t14;
+ $[22] = t15;
+ } else {
+ t14 = $[21];
+ t15 = $[22];
+ }
+ const t16 = platform === "ios";
+ const t17 = platform === "ios";
+ let t18;
+ if ($[23] !== t16 || $[24] !== t17) {
+ t18 = iOS;
+ $[23] = t16;
+ $[24] = t17;
+ $[25] = t18;
+ } else {
+ t18 = $[25];
+ }
+ let t19;
+ if ($[26] === Symbol.for("react.memo_cache_sentinel")) {
+ t19 = {" / "};
+ $[26] = t19;
+ } else {
+ t19 = $[26];
+ }
+ const t20 = platform === "android";
+ const t21 = platform === "android";
+ let t22;
+ if ($[27] !== t20 || $[28] !== t21) {
+ t22 = Android;
+ $[27] = t20;
+ $[28] = t21;
+ $[29] = t22;
+ } else {
+ t22 = $[29];
+ }
+ let t23;
+ if ($[30] !== t18 || $[31] !== t22) {
+ t23 = {t18}{t19}{t22};
+ $[30] = t18;
+ $[31] = t22;
+ $[32] = t23;
+ } else {
+ t23 = $[32];
+ }
+ let t24;
+ if ($[33] === Symbol.for("react.memo_cache_sentinel")) {
+ t24 = (tab to switch, esc to close);
+ $[33] = t24;
+ } else {
+ t24 = $[33];
+ }
+ let t25;
+ if ($[34] !== t23) {
+ t25 = {t23}{t24};
+ $[34] = t23;
+ $[35] = t25;
+ } else {
+ t25 = $[35];
+ }
+ let t26;
+ if ($[36] !== url) {
+ t26 = {url};
+ $[36] = url;
+ $[37] = t26;
+ } else {
+ t26 = $[37];
+ }
+ let t27;
+ if ($[38] !== T0 || $[39] !== t10 || $[40] !== t11 || $[41] !== t12 || $[42] !== t13 || $[43] !== t25 || $[44] !== t26 || $[45] !== t7 || $[46] !== t8 || $[47] !== t9) {
+ t27 = {t11}{t12}{t13}{t14}{t15}{t25}{t26};
+ $[38] = T0;
+ $[39] = t10;
+ $[40] = t11;
+ $[41] = t12;
+ $[42] = t13;
+ $[43] = t25;
+ $[44] = t26;
+ $[45] = t7;
+ $[46] = t8;
+ $[47] = t9;
+ $[48] = t27;
+ } else {
+ t27 = $[48];
+ }
+ let t28;
+ if ($[49] !== T1 || $[50] !== t27) {
+ t28 = {t27};
+ $[49] = T1;
+ $[50] = t27;
+ $[51] = t28;
+ } else {
+ t28 = $[51];
+ }
+ return t28;
+}
+function _temp4(line_0, i) {
+ return {line_0};
+}
+function _temp3(line) {
+ return line.length > 0;
+}
+function _temp2(prev) {
+ return prev === "ios" ? "android" : "ios";
+}
+function _temp() {}
+export async function call(onDone: LocalJSXCommandOnDone): Promise {
+ return ;
+}
+//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJ0b1N0cmluZyIsInFyVG9TdHJpbmciLCJSZWFjdCIsInVzZUNhbGxiYWNrIiwidXNlRWZmZWN0IiwidXNlU3RhdGUiLCJQYW5lIiwiS2V5Ym9hcmRFdmVudCIsIkJveCIsIlRleHQiLCJ1c2VLZXliaW5kaW5nIiwiTG9jYWxKU1hDb21tYW5kT25Eb25lIiwiUGxhdGZvcm0iLCJQcm9wcyIsIm9uRG9uZSIsIlBMQVRGT1JNUyIsIlJlY29yZCIsInVybCIsImlvcyIsImFuZHJvaWQiLCJNb2JpbGVRUkNvZGUiLCJ0MCIsIiQiLCJfYyIsInBsYXRmb3JtIiwic2V0UGxhdGZvcm0iLCJ0MSIsIlN5bWJvbCIsImZvciIsInFyQ29kZXMiLCJzZXRRckNvZGVzIiwicXJDb2RlIiwidDIiLCJ0MyIsImdlbmVyYXRlUVJDb2RlcyIsIlByb21pc2UiLCJhbGwiLCJ0eXBlIiwiZXJyb3JDb3JyZWN0aW9uTGV2ZWwiLCJjYXRjaCIsIl90ZW1wIiwidDQiLCJoYW5kbGVDbG9zZSIsInQ1IiwiY29udGV4dCIsInQ2IiwiaGFuZGxlS2V5RG93biIsImUiLCJrZXkiLCJjdHJsIiwicHJldmVudERlZmF1bHQiLCJfdGVtcDIiLCJUMCIsIlQxIiwidDEwIiwidDExIiwidDEyIiwidDEzIiwidDciLCJ0OCIsInQ5IiwibGluZXMiLCJzcGxpdCIsImZpbHRlciIsIl90ZW1wMyIsIm1hcCIsIl90ZW1wNCIsInQxNCIsInQxNSIsInQxNiIsInQxNyIsInQxOCIsInQxOSIsInQyMCIsInQyMSIsInQyMiIsInQyMyIsInQyNCIsInQyNSIsInQyNiIsInQyNyIsInQyOCIsImxpbmVfMCIsImkiLCJsaW5lIiwibGVuZ3RoIiwicHJldiIsImNhbGwiLCJSZWFjdE5vZGUiXSwic291cmNlcyI6WyJtb2JpbGUudHN4Il0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IHRvU3RyaW5nIGFzIHFyVG9TdHJpbmcgfSBmcm9tICdxcmNvZGUnXG5pbXBvcnQgKiBhcyBSZWFjdCBmcm9tICdyZWFjdCdcbmltcG9ydCB7IHVzZUNhbGxiYWNrLCB1c2VFZmZlY3QsIHVzZVN0YXRlIH0gZnJvbSAncmVhY3QnXG5pbXBvcnQgeyBQYW5lIH0gZnJvbSAnLi4vLi4vY29tcG9uZW50cy9kZXNpZ24tc3lzdGVtL1BhbmUuanMnXG5pbXBvcnQgdHlwZSB7IEtleWJvYXJkRXZlbnQgfSBmcm9tICcuLi8uLi9pbmsvZXZlbnRzL2tleWJvYXJkLWV2ZW50LmpzJ1xuaW1wb3J0IHsgQm94LCBUZXh0IH0gZnJvbSAnLi4vLi4vaW5rLmpzJ1xuaW1wb3J0IHsgdXNlS2V5YmluZGluZyB9IGZyb20gJy4uLy4uL2tleWJpbmRpbmdzL3VzZUtleWJpbmRpbmcuanMnXG5pbXBvcnQgdHlwZSB7IExvY2FsSlNYQ29tbWFuZE9uRG9uZSB9IGZyb20gJy4uLy4uL3R5cGVzL2NvbW1hbmQuanMnXG5cbnR5cGUgUGxhdGZvcm0gPSAnaW9zJyB8ICdhbmRyb2lkJ1xuXG50eXBlIFByb3BzID0ge1xuICBvbkRvbmU6ICgpID0+IHZvaWRcbn1cblxuY29uc3QgUExBVEZPUk1TOiBSZWNvcmQ8UGxhdGZvcm0sIHsgdXJsOiBzdHJpbmcgfT4gPSB7XG4gIGlvczoge1xuICAgIHVybDogJ2h0dHBzOi8vYXBwcy5hcHBsZS5jb20vYXBwL2NsYXVkZS1ieS1hbnRocm9waWMvaWQ2NDczNzUzNjg0JyxcbiAgfSxcbiAgYW5kcm9pZDoge1xuICAgIHVybDogJ2h0dHBzOi8vcGxheS5nb29nbGUuY29tL3N0b3JlL2FwcHMvZGV0YWlscz9pZD1jb20uYW50aHJvcGljLmNsYXVkZScsXG4gIH0sXG59XG5cbmZ1bmN0aW9uIE1vYmlsZVFSQ29kZSh7IG9uRG9uZSB9OiBQcm9wcyk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gIGNvbnN0IFtwbGF0Zm9ybSwgc2V0UGxhdGZvcm1dID0gdXNlU3RhdGU8UGxhdGZvcm0+KCdpb3MnKVxuICBjb25zdCBbcXJDb2Rlcywgc2V0UXJDb2Rlc10gPSB1c2VTdGF0ZTxSZWNvcmQ8UGxhdGZvcm0sIHN0cmluZz4+KHtcbiAgICBpb3M6ICcnLFxuICAgIGFuZHJvaWQ6ICcnLFxuICB9KVxuXG4gIGNvbnN0IHsgdXJsIH0gPSBQTEFURk9STVNbcGxhdGZvcm1dXG4gIGNvbnN0IHFyQ29kZSA9IHFyQ29kZXNbcGxhdGZvcm1dXG5cbiAgLy8gR2VuZXJhdGUgYm90aCBRUiBjb2RlcyB1cGZyb250IHRvIGF2b2lkIGZsaWNrZXIgd2hlbiBzd2l0Y2hpbmdcbiAgdXNlRWZmZWN0KCgpID0+IHtcbiAgICBhc3luYyBmdW5jdGlvbiBnZW5lcmF0ZVFSQ29kZXMoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICBjb25zdCBbaW9zLCBhbmRyb2lkXSA9IGF3YWl0IFByb21pc2UuYWxsKFtcbiAgICAgICAgcXJUb1N0cmluZyhQTEFURk9STVMuaW9zLnVybCwge1xuICAgICAgICAgIHR5cGU6ICd1dGY4JyxcbiAgICAgICAgICBlcnJvckNvcnJlY3Rpb25MZXZlbDogJ0wnLFxuICAgICAgICB9KSxcbiAgICAgICAgcXJUb1N0cmluZyhQTEFURk9STVMuYW5kcm9pZC51cmwsIHtcbiAgICAgICAgICB0eXBlOiAndXRmOCcsXG4gICAgICAgICAgZXJyb3JDb3JyZWN0aW9uTGV2ZWw6ICdMJyxcbiAgICAgICAgfSksXG4gICAgICBdKVxuICAgICAgc2V0UXJDb2Rlcyh7IGlvcywgYW5kcm9pZCB9KVxuICAgIH1cbiAgICBnZW5lcmF0ZVFSQ29kZXMoKS5jYXRjaCgoKSA9PiB7XG4gICAgICAvLyBRUiBnZW5lcmF0aW9uIGZhaWxlZCwgbGVhdmUgZW1wdHlcbiAgICB9KVxuICB9LCBbXSlcblxuICBjb25zdCBoYW5kbGVDbG9zZSA9IHVzZUNhbGxiYWNrKCgpID0+IHtcbiAgICBvbkRvbmUoKVxuICB9LCBbb25Eb25lXSlcblxuICB1c2VLZXliaW5kaW5nKCdjb25maXJtOm5vJywgaGFuZGxlQ2xvc2UsIHsgY29udGV4dDogJ0NvbmZpcm1hdGlvbicgfSlcblxuICBmdW5jdGlvbiBoYW5kbGVLZXlEb3duKGU6IEtleWJvYXJkRXZlbnQpOiB2b2lkIHtcbiAgICBpZiAoZS5rZXkgPT09ICdxJyB8fCAoZS5jdHJsICYmIGUua2V5ID09PSAnYycpKSB7XG4gICAgICBlLnByZXZlbnREZWZhdWx0KClcbiAgICAgIG9uRG9uZSgpXG4gICAgICByZXR1cm5cbiAgICB9XG4gICAgaWYgKGUua2V5ID09PSAndGFiJyB8fCBlLmtleSA9PT0gJ2xlZnQnIHx8IGUua2V5ID09PSAncmlnaHQnKSB7XG4gICAgICBlLnByZXZlbnREZWZhdWx0KClcbiAgICAgIHNldFBsYXRmb3JtKHByZXYgPT4gKHByZXYgPT09ICdpb3MnID8gJ2FuZHJvaWQnIDogJ2lvcycpKVxuICAgIH1cbiAgfVxuXG4gIGNvbnN0IGxpbmVzID0gcXJDb2RlLnNwbGl0KCdcXG4nKS5maWx0ZXIobGluZSA9PiBsaW5lLmxlbmd0aCA+IDApXG5cbiAgcmV0dXJuIChcbiAgICA8UGFuZT5cbiAgICAgIDxCb3hcbiAgICAgICAgZmxleERpcmVjdGlvbj1cImNvbHVtblwiXG4gICAgICAgIHRhYkluZGV4PXswfVxuICAgICAgICBhdXRvRm9jdXNcbiAgICAgICAgb25LZXlEb3duPXtoYW5kbGVLZXlEb3dufVxuICAgICAgPlxuICAgICAgICA8VGV4dD4gPC9UZXh0PlxuICAgICAgICA8VGV4dD4gPC9UZXh0PlxuICAgICAgICB7bGluZXMubWFwKChsaW5lLCBpKSA9PiAoXG4gICAgICAgICAgPFRleHQga2V5PXtpfT57bGluZX08L1RleHQ+XG4gICAgICAgICkpfVxuICAgICAgICA8VGV4dD4gPC9UZXh0PlxuICAgICAgICA8VGV4dD4gPC9UZXh0PlxuXG4gICAgICAgIHsvKiBDb250cm9scyAqL31cbiAgICAgICAgPEJveCBmbGV4RGlyZWN0aW9uPVwicm93XCIgZ2FwPXsyfT5cbiAgICAgICAgICA8VGV4dD5cbiAgICAgICAgICAgIDxUZXh0IGJvbGQ9e3BsYXRmb3JtID09PSAnaW9zJ30gdW5kZXJsaW5lPXtwbGF0Zm9ybSA9PT0gJ2lvcyd9PlxuICAgICAgICAgICAgICBpT1NcbiAgICAgICAgICAgIDwvVGV4dD5cbiAgICAgICAgICAgIDxUZXh0IGRpbUNvbG9yPnsnIC8gJ308L1RleHQ+XG4gICAgICAgICAgICA8VGV4dFxuICAgICAgICAgICAgICBib2xkPXtwbGF0Zm9ybSA9PT0gJ2FuZHJvaWQnfVxuICAgICAgICAgICAgICB1bmRlcmxpbmU9e3BsYXRmb3JtID09PSAnYW5kcm9pZCd9XG4gICAgICAgICAgICA+XG4gICAgICAgICAgICAgIEFuZHJvaWRcbiAgICAgICAgICAgIDwvVGV4dD5cbiAgICAgICAgICA8L1RleHQ+XG4gICAgICAgICAgPFRleHQgZGltQ29sb3I+KHRhYiB0byBzd2l0Y2gsIGVzYyB0byBjbG9zZSk8L1RleHQ+XG4gICAgICAgIDwvQm94PlxuICAgICAgICA8VGV4dCBkaW1Db2xvcj57dXJsfTwvVGV4dD5cbiAgICAgIDwvQm94PlxuICAgIDwvUGFuZT5cbiAgKVxufVxuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gY2FsbChcbiAgb25Eb25lOiBMb2NhbEpTWENvbW1hbmRPbkRvbmUsXG4pOiBQcm9taXNlPFJlYWN0LlJlYWN0Tm9kZT4ge1xuICByZXR1cm4gPE1vYmlsZVFSQ29kZSBvbkRvbmU9e29uRG9uZX0gLz5cbn1cbiJdLCJtYXBwaW5ncyI6IjtBQUFBLFNBQVNBLFFBQVEsSUFBSUMsVUFBVSxRQUFRLFFBQVE7QUFDL0MsT0FBTyxLQUFLQyxLQUFLLE1BQU0sT0FBTztBQUM5QixTQUFTQyxXQUFXLEVBQUVDLFNBQVMsRUFBRUMsUUFBUSxRQUFRLE9BQU87QUFDeEQsU0FBU0MsSUFBSSxRQUFRLHdDQUF3QztBQUM3RCxjQUFjQyxhQUFhLFFBQVEsb0NBQW9DO0FBQ3ZFLFNBQVNDLEdBQUcsRUFBRUMsSUFBSSxRQUFRLGNBQWM7QUFDeEMsU0FBU0MsYUFBYSxRQUFRLG9DQUFvQztBQUNsRSxjQUFjQyxxQkFBcUIsUUFBUSx3QkFBd0I7QUFFbkUsS0FBS0MsUUFBUSxHQUFHLEtBQUssR0FBRyxTQUFTO0FBRWpDLEtBQUtDLEtBQUssR0FBRztFQUNYQyxNQUFN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
\ No newline at end of file
diff --git a/src/commands/mock-limits/index.js b/src/commands/mock-limits/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..e1a619d254332b7e0c662931b91aae3a9f9c3b0e
--- /dev/null
+++ b/src/commands/mock-limits/index.js
@@ -0,0 +1 @@
+export default { isEnabled: () => false, isHidden: true, name: 'stub' };
diff --git a/src/commands/model/index.ts b/src/commands/model/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5554906e65a01482ecba040a9d182cf8b6a513f8
--- /dev/null
+++ b/src/commands/model/index.ts
@@ -0,0 +1,16 @@
+import type { Command } from '../../commands.js'
+import { shouldInferenceConfigCommandBeImmediate } from '../../utils/immediateCommand.js'
+import { getMainLoopModel, renderModelName } from '../../utils/model/model.js'
+
+export default {
+ type: 'local-jsx',
+ name: 'model',
+ get description() {
+ return `Set the AI model for Claude Code (currently ${renderModelName(getMainLoopModel())})`
+ },
+ argumentHint: '[model]',
+ get immediate() {
+ return shouldInferenceConfigCommandBeImmediate()
+ },
+ load: () => import('./model.js'),
+} satisfies Command
diff --git a/src/commands/model/model.tsx b/src/commands/model/model.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..65c084d9fb959e43044d1806983b4a2fa6e9fb6a
--- /dev/null
+++ b/src/commands/model/model.tsx
@@ -0,0 +1,297 @@
+import { c as _c } from "react/compiler-runtime";
+import chalk from 'chalk';
+import * as React from 'react';
+import type { CommandResultDisplay } from '../../commands.js';
+import { ModelPicker } from '../../components/ModelPicker.js';
+import { COMMON_HELP_ARGS, COMMON_INFO_ARGS } from '../../constants/xml.js';
+import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, logEvent } from '../../services/analytics/index.js';
+import { useAppState, useSetAppState } from '../../state/AppState.js';
+import type { LocalJSXCommandCall } from '../../types/command.js';
+import type { EffortLevel } from '../../utils/effort.js';
+import { isBilledAsExtraUsage } from '../../utils/extraUsage.js';
+import { clearFastModeCooldown, isFastModeAvailable, isFastModeEnabled, isFastModeSupportedByModel } from '../../utils/fastMode.js';
+import { MODEL_ALIASES } from '../../utils/model/aliases.js';
+import { checkOpus1mAccess, checkSonnet1mAccess } from '../../utils/model/check1mAccess.js';
+import { getDefaultMainLoopModelSetting, isOpus1mMergeEnabled, renderDefaultModelSetting } from '../../utils/model/model.js';
+import { isModelAllowed } from '../../utils/model/modelAllowlist.js';
+import { validateModel } from '../../utils/model/validateModel.js';
+function ModelPickerWrapper(t0) {
+ const $ = _c(17);
+ const {
+ onDone
+ } = t0;
+ const mainLoopModel = useAppState(_temp);
+ const mainLoopModelForSession = useAppState(_temp2);
+ const isFastMode = useAppState(_temp3);
+ const setAppState = useSetAppState();
+ let t1;
+ if ($[0] !== mainLoopModel || $[1] !== onDone) {
+ t1 = function handleCancel() {
+ logEvent("tengu_model_command_menu", {
+ action: "cancel" as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
+ });
+ const displayModel = renderModelLabel(mainLoopModel);
+ onDone(`Kept model as ${chalk.bold(displayModel)}`, {
+ display: "system"
+ });
+ };
+ $[0] = mainLoopModel;
+ $[1] = onDone;
+ $[2] = t1;
+ } else {
+ t1 = $[2];
+ }
+ const handleCancel = t1;
+ let t2;
+ if ($[3] !== isFastMode || $[4] !== mainLoopModel || $[5] !== onDone || $[6] !== setAppState) {
+ t2 = function handleSelect(model, effort) {
+ logEvent("tengu_model_command_menu", {
+ action: model as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
+ from_model: mainLoopModel as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
+ to_model: model as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
+ });
+ setAppState(prev => ({
+ ...prev,
+ mainLoopModel: model,
+ mainLoopModelForSession: null
+ }));
+ let message = `Set model to ${chalk.bold(renderModelLabel(model))}`;
+ if (effort !== undefined) {
+ message = message + ` with ${chalk.bold(effort)} effort`;
+ }
+ let wasFastModeToggledOn = undefined;
+ if (isFastModeEnabled()) {
+ clearFastModeCooldown();
+ if (!isFastModeSupportedByModel(model) && isFastMode) {
+ setAppState(_temp4);
+ wasFastModeToggledOn = false;
+ } else {
+ if (isFastModeSupportedByModel(model) && isFastModeAvailable() && isFastMode) {
+ message = message + " \xB7 Fast mode ON";
+ wasFastModeToggledOn = true;
+ }
+ }
+ }
+ if (isBilledAsExtraUsage(model, wasFastModeToggledOn === true, isOpus1mMergeEnabled())) {
+ message = message + " \xB7 Billed as extra usage";
+ }
+ if (wasFastModeToggledOn === false) {
+ message = message + " \xB7 Fast mode OFF";
+ }
+ onDone(message);
+ };
+ $[3] = isFastMode;
+ $[4] = mainLoopModel;
+ $[5] = onDone;
+ $[6] = setAppState;
+ $[7] = t2;
+ } else {
+ t2 = $[7];
+ }
+ const handleSelect = t2;
+ let t3;
+ if ($[8] !== isFastMode || $[9] !== mainLoopModel) {
+ t3 = isFastModeEnabled() && isFastMode && isFastModeSupportedByModel(mainLoopModel) && isFastModeAvailable();
+ $[8] = isFastMode;
+ $[9] = mainLoopModel;
+ $[10] = t3;
+ } else {
+ t3 = $[10];
+ }
+ let t4;
+ if ($[11] !== handleCancel || $[12] !== handleSelect || $[13] !== mainLoopModel || $[14] !== mainLoopModelForSession || $[15] !== t3) {
+ t4 = ;
+ $[11] = handleCancel;
+ $[12] = handleSelect;
+ $[13] = mainLoopModel;
+ $[14] = mainLoopModelForSession;
+ $[15] = t3;
+ $[16] = t4;
+ } else {
+ t4 = $[16];
+ }
+ return t4;
+}
+function _temp4(prev_0) {
+ return {
+ ...prev_0,
+ fastMode: false
+ };
+}
+function _temp3(s_1) {
+ return s_1.fastMode;
+}
+function _temp2(s_0) {
+ return s_0.mainLoopModelForSession;
+}
+function _temp(s) {
+ return s.mainLoopModel;
+}
+function SetModelAndClose({
+ args,
+ onDone
+}: {
+ args: string;
+ onDone: (result?: string, options?: {
+ display?: CommandResultDisplay;
+ }) => void;
+}): React.ReactNode {
+ const isFastMode = useAppState(s => s.fastMode);
+ const setAppState = useSetAppState();
+ const model = args === 'default' ? null : args;
+ React.useEffect(() => {
+ async function handleModelChange(): Promise {
+ if (model && !isModelAllowed(model)) {
+ onDone(`Model '${model}' is not available. Your organization restricts model selection.`, {
+ display: 'system'
+ });
+ return;
+ }
+
+ // @[MODEL LAUNCH]: Update check for 1M access.
+ if (model && isOpus1mUnavailable(model)) {
+ onDone(`Opus 4.6 with 1M context is not available for your account. Learn more: https://code.claude.com/docs/en/model-config#extended-context-with-1m`, {
+ display: 'system'
+ });
+ return;
+ }
+ if (model && isSonnet1mUnavailable(model)) {
+ onDone(`Sonnet 4.6 with 1M context is not available for your account. Learn more: https://code.claude.com/docs/en/model-config#extended-context-with-1m`, {
+ display: 'system'
+ });
+ return;
+ }
+
+ // Skip validation for default model
+ if (!model) {
+ setModel(null);
+ return;
+ }
+
+ // Skip validation for known aliases - they're predefined and should work
+ if (isKnownAlias(model)) {
+ setModel(model);
+ return;
+ }
+
+ // Validate and set custom model
+ try {
+ // Don't use parseUserSpecifiedModel for non-aliases since it lowercases the input
+ // and model names are case-sensitive
+ const {
+ valid,
+ error: error_0
+ } = await validateModel(model);
+ if (valid) {
+ setModel(model);
+ } else {
+ onDone(error_0 || `Model '${model}' not found`, {
+ display: 'system'
+ });
+ }
+ } catch (error) {
+ onDone(`Failed to validate model: ${(error as Error).message}`, {
+ display: 'system'
+ });
+ }
+ }
+ function setModel(modelValue: string | null): void {
+ setAppState(prev => ({
+ ...prev,
+ mainLoopModel: modelValue,
+ mainLoopModelForSession: null
+ }));
+ let message = `Set model to ${chalk.bold(renderModelLabel(modelValue))}`;
+ let wasFastModeToggledOn = undefined;
+ if (isFastModeEnabled()) {
+ clearFastModeCooldown();
+ if (!isFastModeSupportedByModel(modelValue) && isFastMode) {
+ setAppState(prev_0 => ({
+ ...prev_0,
+ fastMode: false
+ }));
+ wasFastModeToggledOn = false;
+ // Do not update fast mode in settings since this is an automatic downgrade
+ } else if (isFastModeSupportedByModel(modelValue) && isFastMode) {
+ message += ` · Fast mode ON`;
+ wasFastModeToggledOn = true;
+ }
+ }
+ if (isBilledAsExtraUsage(modelValue, wasFastModeToggledOn === true, isOpus1mMergeEnabled())) {
+ message += ` · Billed as extra usage`;
+ }
+ if (wasFastModeToggledOn === false) {
+ // Fast mode was toggled off, show suffix after extra usage billing
+ message += ` · Fast mode OFF`;
+ }
+ onDone(message);
+ }
+ void handleModelChange();
+ }, [model, onDone, setAppState]);
+ return null;
+}
+function isKnownAlias(model: string): boolean {
+ return (MODEL_ALIASES as readonly string[]).includes(model.toLowerCase().trim());
+}
+function isOpus1mUnavailable(model: string): boolean {
+ const m = model.toLowerCase();
+ return !checkOpus1mAccess() && !isOpus1mMergeEnabled() && m.includes('opus') && m.includes('[1m]');
+}
+function isSonnet1mUnavailable(model: string): boolean {
+ const m = model.toLowerCase();
+ // Warn about Sonnet and Sonnet 4.6, but not Sonnet 4.5 since that had
+ // a different access criteria.
+ return !checkSonnet1mAccess() && (m.includes('sonnet[1m]') || m.includes('sonnet-4-6[1m]'));
+}
+function ShowModelAndClose(t0) {
+ const {
+ onDone
+ } = t0;
+ const mainLoopModel = useAppState(_temp7);
+ const mainLoopModelForSession = useAppState(_temp8);
+ const effortValue = useAppState(_temp9);
+ const displayModel = renderModelLabel(mainLoopModel);
+ const effortInfo = effortValue !== undefined ? ` (effort: ${effortValue})` : "";
+ if (mainLoopModelForSession) {
+ onDone(`Current model: ${chalk.bold(renderModelLabel(mainLoopModelForSession))} (session override from plan mode)\nBase model: ${displayModel}${effortInfo}`);
+ } else {
+ onDone(`Current model: ${displayModel}${effortInfo}`);
+ }
+ return null;
+}
+function _temp9(s_1) {
+ return s_1.effortValue;
+}
+function _temp8(s_0) {
+ return s_0.mainLoopModelForSession;
+}
+function _temp7(s) {
+ return s.mainLoopModel;
+}
+export const call: LocalJSXCommandCall = async (onDone, _context, args) => {
+ args = args?.trim() || '';
+ if (COMMON_INFO_ARGS.includes(args)) {
+ logEvent('tengu_model_command_inline_help', {
+ args: args as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
+ });
+ return ;
+ }
+ if (COMMON_HELP_ARGS.includes(args)) {
+ onDone('Run /model to open the model selection menu, or /model [modelName] to set the model.', {
+ display: 'system'
+ });
+ return;
+ }
+ if (args) {
+ logEvent('tengu_model_command_inline', {
+ args: args as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
+ });
+ return ;
+ }
+ return ;
+};
+function renderModelLabel(model: string | null): string {
+ const rendered = renderDefaultModelSetting(model ?? getDefaultMainLoopModelSetting());
+ return model === null ? `${rendered} (default)` : rendered;
+}
+//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJjaGFsayIsIlJlYWN0IiwiQ29tbWFuZFJlc3VsdERpc3BsYXkiLCJNb2RlbFBpY2tlciIsIkNPTU1PTl9IRUxQX0FSR1MiLCJDT01NT05fSU5GT19BUkdTIiwiQW5hbHl0aWNzTWV0YWRhdGFfSV9WRVJJRklFRF9USElTX0lTX05PVF9DT0RFX09SX0ZJTEVQQVRIUyIsImxvZ0V2ZW50IiwidXNlQXBwU3RhdGUiLCJ1c2VTZXRBcHBTdGF0ZSIsIkxvY2FsSlNYQ29tbWFuZENhbGwiLCJFZmZvcnRMZXZlbCIsImlzQmlsbGVkQXNFeHRyYVVzYWdlIiwiY2xlYXJGYXN0TW9kZUNvb2xkb3duIiwiaXNGYXN0TW9kZUF2YWlsYWJsZSIsImlzRmFzdE1vZGVFbmFibGVkIiwiaXNGYXN0TW9kZVN1cHBvcnRlZEJ5TW9kZWwiLCJNT0RFTF9BTElBU0VTIiwiY2hlY2tPcHVzMW1BY2Nlc3MiLCJjaGVja1Nvbm5ldDFtQWNjZXNzIiwiZ2V0RGVmYXVsdE1haW5Mb29wTW9kZWxTZXR0aW5nIiwiaXNPcHVzMW1NZXJnZUVuYWJsZWQiLCJyZW5kZXJEZWZhdWx0TW9kZWxTZXR0aW5nIiwiaXNNb2RlbEFsbG93ZWQiLCJ2YWxpZGF0ZU1vZGVsIiwiTW9kZWxQaWNrZXJXcmFwcGVyIiwidDAiLCIkIiwiX2MiLCJvbkRvbmUiLCJtYWluTG9vcE1vZGVsIiwiX3RlbXAiLCJtYWluTG9vcE1vZGVsRm9yU2Vzc2lvbiIsIl90ZW1wMiIsImlzRmFzdE1vZGUiLCJfdGVtcDMiLCJzZXRBcHBTdGF0ZSIsInQxIiwiaGFuZGxlQ2FuY2VsIiwiYWN0aW9uIiwiZGlzcGxheU1vZGVsIiwicmVuZGVyTW9kZWxMYWJlbCIsImJvbGQiLCJkaXNwbGF5IiwidDIiLCJoYW5kbGVTZWxlY3QiLCJtb2RlbCIsImVmZm9ydCIsImZyb21fbW9kZWwiLCJ0b19tb2RlbCIsInByZXYiLCJtZXNzYWdlIiwidW5kZWZpbmVkIiwid2FzRmFzdE1vZGVUb2dnbGVkT24iLCJfdGVtcDQiLCJ0MyIsInQ0IiwicHJldl8wIiwiZmFzdE1vZGUiLCJzXzEiLCJzIiwic18wIiwiU2V0TW9kZWxBbmRDbG9zZSIsImFyZ3MiLCJyZXN1bHQiLCJvcHRpb25zIiwiUmVhY3ROb2RlIiwidXNlRWZmZWN0IiwiaGFuZGxlTW9kZWxDaGFuZ2UiLCJQcm9taXNlIiwiaXNPcHVzMW1VbmF2YWlsYWJsZSIsImlzU29ubmV0MW1VbmF2YWlsYWJsZSIsInNldE1vZGVsIiwiaXNLbm93bkFsaWFzIiwidmFsaWQiLCJlcnJvciIsIkVycm9yIiwibW9kZWxWYWx1ZSIsImluY2x1ZGVzIiwidG9Mb3dlckNhc2UiLCJ0cmltIiwibSIsIlNob3dNb2RlbEFuZENsb3NlIiwiX3RlbXA3IiwiX3RlbXA4IiwiZWZmb3J0VmFsdWUiLCJfdGVtcDkiLCJlZmZvcnRJbmZvIiwiY2FsbCIsIl9jb250ZXh0IiwicmVuZGVyZWQiXSwic291cmNlcyI6WyJtb2RlbC50c3giXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IGNoYWxrIGZyb20gJ2NoYWxrJ1xuaW1wb3J0ICogYXMgUmVhY3QgZnJvbSAncmVhY3QnXG5pbXBvcnQgdHlwZSB7IENvbW1hbmRSZXN1bHREaXNwbGF5IH0gZnJvbSAnLi4vLi4vY29tbWFuZHMuanMnXG5pbXBvcnQgeyBNb2RlbFBpY2tlciB9IGZyb20gJy4uLy4uL2NvbXBvbmVudHMvTW9kZWxQaWNrZXIuanMnXG5pbXBvcnQgeyBDT01NT05fSEVMUF9BUkdTLCBDT01NT05fSU5GT19BUkdTIH0gZnJvbSAnLi4vLi4vY29uc3RhbnRzL3htbC5qcydcbmltcG9ydCB7XG4gIHR5cGUgQW5hbHl0aWNzTWV0YWRhdGFfSV9WRVJJRklFRF9USElTX0lTX05PVF9DT0RFX09SX0ZJTEVQQVRIUyxcbiAgbG9nRXZlbnQsXG59IGZyb20gJy4uLy4uL3NlcnZpY2VzL2FuYWx5dGljcy9pbmRleC5qcydcbmltcG9ydCB7IHVzZUFwcFN0YXRlLCB1c2VTZXRBcHBTdGF0ZSB9IGZyb20gJy4uLy4uL3N0YXRlL0FwcFN0YXRlLmpzJ1xuaW1wb3J0IHR5cGUgeyBMb2NhbEpTWENvbW1hbmRDYWxsIH0gZnJvbSAnLi4vLi4vdHlwZXMvY29tbWFuZC5qcydcbmltcG9ydCB0eXBlIHsgRWZmb3J0TGV2ZWwgfSBmcm9tICcuLi8uLi91dGlscy9lZmZvcnQuanMnXG5pbXBvcnQgeyBpc0JpbGxlZEFzRXh0cmFVc2FnZSB9IGZyb20gJy4uLy4uL3V0aWxzL2V4dHJhVXNhZ2UuanMnXG5pbXBvcnQge1xuICBjbGVhckZhc3RNb2RlQ29vbGRvd24sXG4gIGlzRmFzdE1vZGVBdmFpbGFibGUsXG4gIGlzRmFzdE1vZGVFbmFibGVkLFxuICBpc0Zhc3RNb2RlU3VwcG9ydGVkQnlNb2RlbCxcbn0gZnJvbSAnLi4vLi4vdXRpbHMvZmFzdE1vZGUuanMnXG5pbXBvcnQgeyBNT0RFTF9BTElBU0VTIH0gZnJvbSAnLi4vLi4vdXRpbHMvbW9kZWwvYWxpYXNlcy5qcydcbmltcG9ydCB7XG4gIGNoZWNrT3B1czFtQWNjZXNzLFxuICBjaGVja1Nvbm5ldDFtQWNjZXNzLFxufSBmcm9tICcuLi8uLi91dGlscy9tb2RlbC9jaGVjazFtQWNjZXNzLmpzJ1xuaW1wb3J0IHtcbiAgZ2V0RGVmYXVsdE1haW5Mb29wTW9kZWxTZXR0aW5nLFxuICBpc09wdXMxbU1lcmdlRW5hYmxlZCxcbiAgcmVuZGVyRGVmYXVsdE1vZGVsU2V0dGluZyxcbn0gZnJvbSAnLi4vLi4vdXRpbHMvbW9kZWwvbW9kZWwuanMnXG5pbXBvcnQgeyBpc01vZGVsQWxsb3dlZCB9IGZyb20gJy4uLy4uL3V0aWxzL21vZGVsL21vZGVsQWxsb3dsaXN0LmpzJ1xuaW1wb3J0IHsgdmFsaWRhdGVNb2RlbCB9IGZyb20gJy4uLy4uL3V0aWxzL21vZGVsL3ZhbGlkYXRlTW9kZWwuanMnXG5cbmZ1bmN0aW9uIE1vZGVsUGlja2VyV3JhcHBlcih7XG4gIG9uRG9uZSxcbn06IHtcbiAgb25Eb25lOiAoXG4gICAgcmVzdWx0Pzogc3RyaW5nLFxuICAgIG9wdGlvbnM/OiB7IGRpc3BsYXk/OiBDb21tYW5kUmVzdWx0RGlzcGxheSB9LFxuICApID0+IHZvaWRcbn0pOiBSZWFjdC5SZWFjdE5vZGUge1xuICBjb25zdCBtYWluTG9vcE1vZGVsID0gdXNlQXBwU3RhdGUocyA9PiBzLm1haW5Mb29wTW9kZWwpXG4gIGNvbnN0IG1haW5Mb29wTW9kZWxGb3JTZXNzaW9uID0gdXNlQXBwU3RhdGUocyA9PiBzLm1haW5Mb29wTW9kZWxGb3JTZXNzaW9uKVxuICBjb25zdCBpc0Zhc3RNb2RlID0gdXNlQXBwU3RhdGUocyA9PiBzLmZhc3RNb2RlKVxuICBjb25zdCBzZXRBcHBTdGF0ZSA9IHVzZVNldEFwcFN0YXRlKClcblxuICBmdW5jdGlvbiBoYW5kbGVDYW5jZWwoKTogdm9pZCB7XG4gICAgbG9nRXZlbnQoJ3Rlbmd1X21vZGVsX2NvbW1hbmRfbWVudScsIHtcbiAgICAgIGFjdGlvbjpcbiAgICAgICAgJ2NhbmNlbCcgYXMgQW5hbHl0aWNzTWV0YWRhdGFfSV9WRVJJRklFRF9USElTX0lTX05PVF9DT0RFX09SX0ZJTEVQQVRIUyxcbiAgICB9KVxuICAgIGNvbnN0IGRpc3BsYXlNb2RlbCA9IHJlbmRlck1vZGVsTGFiZWwobWFpbkxvb3BNb2RlbClcbiAgICBvbkRvbmUoYEtlcHQgbW9kZWwgYXMgJHtjaGFsay5ib2xkKGRpc3BsYXlNb2RlbCl9YCwge1xuICAgICAgZGlzcGxheTogJ3N5c3RlbScsXG4gICAgfSlcbiAgfVxuXG4gIGZ1bmN0aW9uIGhhbmRsZVNlbGVjdChcbiAgICBtb2RlbDogc3RyaW5nIHwgbnVsbCxcbiAgICBlZmZvcnQ6IEVmZm9ydExldmVsIHwgdW5kZWZpbmVkLFxuICApOiB2b2lkIHtcbiAgICBsb2dFdmVudCgndGVuZ3VfbW9kZWxfY29tbWFuZF9tZW51Jywge1xuICAgICAgYWN0aW9uOlxuICAgICAgICBtb2RlbCBhcyBBbmFseXRpY3NNZXRhZGF0YV9JX1ZFUklGSUVEX1RISVNfSVNfTk9UX0NPREVfT1JfRklMRVBBVEhTLFxuICAgICAgZnJvbV9tb2RlbDpcbiAgICAgICAgbWFpbkxvb3BNb2RlbCBhcyBBbmFseXRpY3NNZXRhZGF0YV9JX1ZFUklGSUVEX1RISVNfSVNfTk9UX0NPREVfT1JfRklMRVBBVEhTLFxuICAgICAgdG9fbW9kZWw6XG4gICAgICAgIG1vZGVsIGFzIEFuYWx5dGljc01ldGFkYXRhX0lfVkVSSUZJRURfVEhJU19JU19OT1RfQ09ERV9PUl9GSUxFUEFUSFMsXG4gICAgfSlcbiAgICBzZXRBcHBTdGF0ZShwcmV2ID0+ICh7XG4gICAgICAuLi5wcmV2LFxuICAgICAgbWFpbkxvb3BNb2RlbDogbW9kZWwsXG4gICAgICBtYWluTG9vcE1vZGVsRm9yU2Vzc2lvbjogbnVsbCxcbiAgICB9KSlcblxuICAgIGxldCBtZXNzYWdlID0gYFNldCBtb2RlbCB0byAke2NoYWxrLmJvbGQocmVuZGVyTW9kZWxMYWJlbChtb2RlbCkpfWBcbiAgICBpZiAoZWZmb3J0ICE9PSB1bmRlZmluZWQpIHtcbiAgICAgIG1lc3NhZ2UgKz0gYCB3aXRoICR7Y2hhbGsuYm9sZChlZmZvcnQpfSBlZmZvcnRgXG4gICAgfVxuXG4gICAgLy8gVHVybiBvZmYgZmFzdCBtb2RlIGlmIHN3aXRjaGluZyB0byB1bnN1cHBvcnRlZCBtb2RlbFxuICAgIGxldCB3YXNGYXN0TW9kZVRvZ2dsZWRPbiA9IHVuZGVmaW5lZFxuICAgIGlmIChpc0Zhc3RNb2RlRW5hYmxlZCgpKSB7XG4gICAgICBjbGVhckZhc3RNb2RlQ29vbGRvd24oKVxuICAgICAgaWYgKCFpc0Zhc3RNb2RlU3VwcG9ydGVkQnlNb2RlbChtb2RlbCkgJiYgaXNGYXN0TW9kZSkge1xuICAgICAgICBzZXRBcHBTdGF0ZShwcmV2ID0+ICh7XG4gICAgICAgICAgLi4ucHJldixcbiAgICAgICAgICBmYXN0TW9kZTogZmFsc2UsXG4gICAgICAgIH0pKVxuICAgICAgICB3YXNGYXN0TW9kZVRvZ2dsZWRPbiA9IGZhbHNlXG4gICAgICAgIC8vIERvIG5vdCB1cGRhdGUgZmFzdCBtb2RlIGluIHNldHRpbmdzIHNpbmNlIHRoaXMgaXMgYW4gYXV0b21hdGljIGRvd25ncmFkZVxuICAgICAgfSBlbHNlIGlmIChcbiAgICAgICAgaXNGYXN0TW9kZVN1cHBvcnRlZEJ5TW9kZWwobW9kZWwpICYmXG4gICAgICAgIGlzRmFzdE1vZGVBdmFpbGFibGUoKSAmJlxuICAgICAgICBpc0Zhc3RNb2RlXG4gICAgICApIHtcbiAgICAgICAgbWVzc2FnZSArPSBgIMK3IEZhc3QgbW9kZSBPTmBcbiAgICAgICAgd2FzRmFzdE1vZGVUb2dnbGVkT24gPSB0cnVlXG4gICAgICB9XG4gICAgfVxuXG4gICAgaWYgKFxuICAgICAgaXNCaWxsZWRBc0V4dHJhVXNhZ2UoXG4gICAgICAgIG1vZGVsLFxuICAgICAgICB3YXNGYXN0TW9kZVRvZ2dsZWRPbiA9PT0gdHJ1ZSxcbiAgICAgICAgaXNPcHVzMW1NZXJnZUVuYWJsZWQoKSxcbiAgICAgIClcbiAgICApIHtcbiAgICAgIG1lc3NhZ2UgKz0gYCDCtyBCaWxsZWQgYXMgZXh0cmEgdXNhZ2VgXG4gICAgfVxuXG4gICAgaWYgKHdhc0Zhc3RNb2RlVG9nZ2xlZE9uID09PSBmYWxzZSkge1xuICAgICAgLy8gRmFzdCBtb2RlIHdhcyB0b2dnbGVkIG9mZiwgc2hvdyBzdWZmaXggYWZ0ZXIgZXh0cmEgdXNhZ2UgYmlsbGluZ1xuICAgICAgbWVzc2FnZSArPSBgIMK3IEZhc3QgbW9kZSBPRkZgXG4gICAgfVxuXG4gICAgb25Eb25lKG1lc3NhZ2UpXG4gIH1cblxuICByZXR1cm4gKFxuICAgIDxNb2RlbFBpY2tlclxuICAgICAgaW5pdGlhbD17bWFpbkxvb3BNb2RlbH1cbiAgICAgIHNlc3Npb25Nb2RlbD17bWFpbkxvb3BNb2RlbEZvclNlc3Npb259XG4gICAgICBvblNlbGVjdD17aGFuZGxlU2VsZWN0fVxuICAgICAgb25DYW5jZWw9e2hhbmRsZUNhbmNlbH1cbiAgICAgIGlzU3RhbmRhbG9uZUNvbW1hbmRcbiAgICAgIHNob3dGYXN0TW9kZU5vdGljZT17XG4gICAgICAgIGlzRmFzdE1vZGVFbmFibGVkKCkgJiZcbiAgICAgICAgaXNGYXN0TW9kZSAmJlxuICAgICAgICBpc0Zhc3RNb2RlU3VwcG9ydGVkQnlNb2RlbChtYWluTG9vcE1vZGVsKSAmJlxuICAgICAgICBpc0Zhc3RNb2RlQXZhaWxhYmxlKClcbiAgICAgIH1cbiAgICAvPlxuICApXG59XG5cbmZ1bmN0aW9uIFNldE1vZGVsQW5kQ2xvc2Uoe1xuICBhcmdzLFxuICBvbkRvbmUsXG59OiB7XG4gIGFyZ3M6IHN0cmluZ1xuICBvbkRvbmU6IChcbiAgICByZXN1bHQ/OiBzdHJpbmcsXG4gICAgb3B0aW9ucz86IHsgZGlzcGxheT86IENvbW1hbmRSZXN1bHREaXNwbGF5IH0sXG4gICkgPT4gdm9pZFxufSk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gIGNvbnN0IGlzRmFzdE1vZGUgPSB1c2VBcHBTdGF0ZShzID0+IHMuZmFzdE1vZGUpXG4gIGNvbnN0IHNldEFwcFN0YXRlID0gdXNlU2V0QXBwU3RhdGUoKVxuICBjb25zdCBtb2RlbCA9IGFyZ3MgPT09ICdkZWZhdWx0JyA/IG51bGwgOiBhcmdzXG5cbiAgUmVhY3QudXNlRWZmZWN0KCgpID0+IHtcbiAgICBhc3luYyBmdW5jdGlvbiBoYW5kbGVNb2RlbENoYW5nZSgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgIGlmIChtb2RlbCAmJiAhaXNNb2RlbEFsbG93ZWQobW9kZWwpKSB7XG4gICAgICAgIG9uRG9uZShcbiAgICAgICAgICBgTW9kZWwgJyR7bW9kZWx9JyBpcyBub3QgYXZhaWxhYmxlLiBZb3VyIG9yZ2FuaXphdGlvbiByZXN0cmljdHMgbW9kZWwgc2VsZWN0aW9uLmAsXG4gICAgICAgICAgeyBkaXNwbGF5OiAnc3lzdGVtJyB9LFxuICAgICAgICApXG4gICAgICAgIHJldHVyblxuICAgICAgfVxuXG4gICAgICAvLyBAW01PREVMIExBVU5DSF06IFVwZGF0ZSBjaGVjayBmb3IgMU0gYWNjZXNzLlxuICAgICAgaWYgKG1vZGVsICYmIGlzT3B1czFtVW5hdmFpbGFibGUobW9kZWwpKSB7XG4gICAgICAgIG9uRG9uZShcbiAgICAgICAgICBgT3B1cyA0LjYgd2l0aCAxTSBjb250ZXh0IGlzIG5vdCBhdmFpbGFibGUgZm9yIHlvdXIgYWNjb3VudC4gTGVhcm4gbW9yZTogaHR0cHM6Ly9jb2RlLmNsYXVkZS5jb20vZG9jcy9lbi9tb2RlbC1jb25maWcjZXh0ZW5kZWQtY29udGV4dC13aXRoLTFtYCxcbiAgICAgICAgICB7IGRpc3BsYXk6ICdzeXN0ZW0nIH0sXG4gICAgICAgIClcbiAgICAgICAgcmV0dXJuXG4gICAgICB9XG5cbiAgICAgIGlmIChtb2RlbCAmJiBpc1Nvbm5ldDFtVW5hdmFpbGFibGUobW9kZWwpKSB7XG4gICAgICAgIG9uRG9uZShcbiAgICAgICAgICBgU29ubmV0IDQuNiB3aXRoIDFNIGNvbnRleHQgaXMgbm90IGF2YWlsYWJsZSBmb3IgeW91ciBhY2NvdW50LiBMZWFybiBtb3JlOiBodHRwczovL2NvZGUuY2xhdWRlLmNvbS9kb2NzL2VuL21vZGVsLWNvbmZpZyNleHRlbmRlZC1jb250ZXh0LXdpdGgtMW1gLFxuICAgICAgICAgIHsgZGlzcGxheTogJ3N5c3RlbScgfSxcbiAgICAgICAgKVxuICAgICAgICByZXR1cm5cbiAgICAgIH1cblxuICAgICAgLy8gU2tpcCB2YWxpZGF0aW9uIGZvciBkZWZhdWx0IG1vZGVsXG4gICAgICBpZiAoIW1vZGVsKSB7XG4gICAgICAgIHNldE1vZGVsKG51bGwpXG4gICAgICAgIHJldHVyblxuICAgICAgfVxuXG4gICAgICAvLyBTa2lwIHZhbGlkYXRpb24gZm9yIGtub3duIGFsaWFzZXMgLSB0aGV5J3JlIHByZWRlZmluZWQgYW5kIHNob3VsZCB3b3JrXG4gICAgICBpZiAoaXNLbm93bkFsaWFzKG1vZGVsKSkge1xuICAgICAgICBzZXRNb2RlbChtb2RlbClcbiAgICAgICAgcmV0dXJuXG4gICAgICB9XG5cbiAgICAgIC8vIFZhbGlkYXRlIGFuZCBzZXQgY3VzdG9tIG1vZGVsXG4gICAgICB0cnkge1xuICAgICAgICAvLyBEb24ndCB1c2UgcGFyc2VVc2VyU3BlY2lmaWVkTW9kZWwgZm9yIG5vbi1hbGlhc2VzIHNpbmNlIGl0IGxvd2VyY2FzZXMgdGhlIGlucHV0XG4gICAgICAgIC8vIGFuZCBtb2RlbCBuYW1lcyBhcmUgY2FzZS1zZW5zaXRpdmVcbiAgICAgICAgY29uc3QgeyB2YWxpZCwgZXJyb3IgfSA9IGF3YWl0IHZhbGlkYXRlTW9kZWwobW9kZWwpXG5cbiAgICAgICAgaWYgKHZhbGlkKSB7XG4gICAgICAgICAgc2V0TW9kZWwobW9kZWwpXG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgb25Eb25lKGVycm9yIHx8IGBNb2RlbCAnJHttb2RlbH0nIG5vdCBmb3VuZGAsIHtcbiAgICAgICAgICAgIGRpc3BsYXk6ICdzeXN0ZW0nLFxuICAgICAgICAgIH0pXG4gICAgICAgIH1cbiAgICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgIG9uRG9uZShgRmFpbGVkIHRvIHZhbGlkYXRlIG1vZGVsOiAkeyhlcnJvciBhcyBFcnJvcikubWVzc2FnZX1gLCB7XG4gICAgICAgICAgZGlzcGxheTogJ3N5c3RlbScsXG4gICAgICAgIH0pXG4gICAgICB9XG4gICAgfVxuXG4gICAgZnVuY3Rpb24gc2V0TW9kZWwobW9kZWxWYWx1ZTogc3RyaW5nIHwgbnVsbCk6IHZvaWQge1xuICAgICAgc2V0QXBwU3RhdGUocHJldiA9PiAoe1xuICAgICAgICAuLi5wcmV2LFxuICAgICAgICBtYWluTG9vcE1vZGVsOiBtb2RlbFZhbHVlLFxuICAgICAgICBtYWluTG9vcE1vZGVsRm9yU2Vzc2lvbjogbnVsbCxcbiAgICAgIH0pKVxuICAgICAgbGV0IG1lc3NhZ2UgPSBgU2V0IG1vZGVsIHRvICR7Y2hhbGsuYm9sZChyZW5kZXJNb2RlbExhYmVsKG1vZGVsVmFsdWUpKX1gXG5cbiAgICAgIGxldCB3YXNGYXN0TW9kZVRvZ2dsZWRPbiA9IHVuZGVmaW5lZFxuICAgICAgaWYgKGlzRmFzdE1vZGVFbmFibGVkKCkpIHtcbiAgICAgICAgY2xlYXJGYXN0TW9kZUNvb2xkb3duKClcbiAgICAgICAgaWYgKCFpc0Zhc3RNb2RlU3VwcG9ydGVkQnlNb2RlbChtb2RlbFZhbHVlKSAmJiBpc0Zhc3RNb2RlKSB7XG4gICAgICAgICAgc2V0QXBwU3RhdGUocHJldiA9PiAoe1xuICAgICAgICAgICAgLi4ucHJldixcbiAgICAgICAgICAgIGZhc3RNb2RlOiBmYWxzZSxcbiAgICAgICAgICB9KSlcbiAgICAgICAgICB3YXNGYXN0TW9kZVRvZ2dsZWRPbiA9IGZhbHNlXG4gICAgICAgICAgLy8gRG8gbm90IHVwZGF0ZSBmYXN0IG1vZGUgaW4gc2V0dGluZ3Mgc2luY2UgdGhpcyBpcyBhbiBhdXRvbWF0aWMgZG93bmdyYWRlXG4gICAgICAgIH0gZWxzZSBpZiAoaXNGYXN0TW9kZVN1cHBvcnRlZEJ5TW9kZWwobW9kZWxWYWx1ZSkgJiYgaXNGYXN0TW9kZSkge1xuICAgICAgICAgIG1lc3NhZ2UgKz0gYCDCtyBGYXN0IG1vZGUgT05gXG4gICAgICAgICAgd2FzRmFzdE1vZGVUb2dnbGVkT24gPSB0cnVlXG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgaWYgKFxuICAgICAgICBpc0JpbGxlZEFzRXh0cmFVc2FnZShcbiAgICAgICAgICBtb2RlbFZhbHVlLFxuICAgICAgICAgIHdhc0Zhc3RNb2RlVG9nZ2xlZE9uID09PSB0cnVlLFxuICAgICAgICAgIGlzT3B1czFtTWVyZ2VFbmFibGVkKCksXG4gICAgICAgIClcbiAgICAgICkge1xuICAgICAgICBtZXNzYWdlICs9IGAgwrcgQmlsbGVkIGFzIGV4dHJhIHVzYWdlYFxuICAgICAgfVxuXG4gICAgICBpZiAod2FzRmFzdE1vZGVUb2dnbGVkT24gPT09IGZhbHNlKSB7XG4gICAgICAgIC8vIEZhc3QgbW9kZSB3YXMgdG9nZ2xlZCBvZmYsIHNob3cgc3VmZml4IGFmdGVyIGV4dHJhIHVzYWdlIGJpbGxpbmdcbiAgICAgICAgbWVzc2FnZSArPSBgIMK3IEZhc3QgbW9kZSBPRkZgXG4gICAgICB9XG5cbiAgICAgIG9uRG9uZShtZXNzYWdlKVxuICAgIH1cblxuICAgIHZvaWQgaGFuZGxlTW9kZWxDaGFuZ2UoKVxuICB9LCBbbW9kZWwsIG9uRG9uZSwgc2V0QXBwU3RhdGVdKVxuXG4gIHJldHVybiBudWxsXG59XG5cbmZ1bmN0aW9uIGlzS25vd25BbGlhcyhtb2RlbDogc3RyaW5nKTogYm9vbGVhbiB7XG4gIHJldHVybiAoTU9ERUxfQUxJQVNFUyBhcyByZWFkb25seSBzdHJpbmdbXSkuaW5jbHVkZXMoXG4gICAgbW9kZWwudG9Mb3dlckNhc2UoKS50cmltKCksXG4gIClcbn1cblxuZnVuY3Rpb24gaXNPcHVzMW1VbmF2YWlsYWJsZShtb2RlbDogc3RyaW5nKTogYm9vbGVhbiB7XG4gIGNvbnN0IG0gPSBtb2RlbC50b0xvd2VyQ2FzZSgpXG4gIHJldHVybiAoXG4gICAgIWNoZWNrT3B1czFtQWNjZXNzKCkgJiZcbiAgICAhaXNPcHVzMW1NZXJnZUVuYWJsZWQoKSAmJlxuICAgIG0uaW5jbHVkZXMoJ29wdXMnKSAmJlxuICAgIG0uaW5jbHVkZXMoJ1sxbV0nKVxuICApXG59XG5cbmZ1bmN0aW9uIGlzU29ubmV0MW1VbmF2YWlsYWJsZShtb2RlbDogc3RyaW5nKTogYm9vbGVhbiB7XG4gIGNvbnN0IG0gPSBtb2RlbC50b0xvd2VyQ2FzZSgpXG4gIC8vIFdhcm4gYWJvdXQgU29ubmV0IGFuZCBTb25uZXQgNC42LCBidXQgbm90IFNvbm5ldCA0LjUgc2luY2UgdGhhdCBoYWRcbiAgLy8gYSBkaWZmZXJlbnQgYWNjZXNzIGNyaXRlcmlhLlxuICByZXR1cm4gKFxuICAgICFjaGVja1Nvbm5ldDFtQWNjZXNzKCkgJiZcbiAgICAobS5pbmNsdWRlcygnc29ubmV0WzFtXScpIHx8IG0uaW5jbHVkZXMoJ3Nvbm5ldC00LTZbMW1dJykpXG4gIClcbn1cblxuZnVuY3Rpb24gU2hvd01vZGVsQW5kQ2xvc2Uoe1xuICBvbkRvbmUsXG59OiB7XG4gIG9uRG9uZTogKHJlc3VsdD86IHN0cmluZykgPT4gdm9pZFxufSk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gIGNvbnN0IG1haW5Mb29wTW9kZWwgPSB1c2VBcHBTdGF0ZShzID0+IHMubWFpbkxvb3BNb2RlbClcbiAgY29uc3QgbWFpbkxvb3BNb2RlbEZvclNlc3Npb24gPSB1c2VBcHBTdGF0ZShzID0+IHMubWFpbkxvb3BNb2RlbEZvclNlc3Npb24pXG4gIGNvbnN0IGVmZm9ydFZhbHVlID0gdXNlQXBwU3RhdGUocyA9PiBzLmVmZm9ydFZhbHVlKVxuICBjb25zdCBkaXNwbGF5TW9kZWwgPSByZW5kZXJNb2RlbExhYmVsKG1haW5Mb29wTW9kZWwpXG4gIGNvbnN0IGVmZm9ydEluZm8gPVxuICAgIGVmZm9ydFZhbHVlICE9PSB1bmRlZmluZWQgPyBgIChlZmZvcnQ6ICR7ZWZmb3J0VmFsdWV9KWAgOiAnJ1xuXG4gIGlmIChtYWluTG9vcE1vZGVsRm9yU2Vzc2lvbikge1xuICAgIG9uRG9uZShcbiAgICAgIGBDdXJyZW50IG1vZGVsOiAke2NoYWxrLmJvbGQocmVuZGVyTW9kZWxMYWJlbChtYWluTG9vcE1vZGVsRm9yU2Vzc2lvbikpfSAoc2Vzc2lvbiBvdmVycmlkZSBmcm9tIHBsYW4gbW9kZSlcXG5CYXNlIG1vZGVsOiAke2Rpc3BsYXlNb2RlbH0ke2VmZm9ydEluZm99YCxcbiAgICApXG4gIH0gZWxzZSB7XG4gICAgb25Eb25lKGBDdXJyZW50IG1vZGVsOiAke2Rpc3BsYXlNb2RlbH0ke2VmZm9ydEluZm99YClcbiAgfVxuXG4gIHJldHVybiBudWxsXG59XG5cbmV4cG9ydCBjb25zdCBjYWxsOiBMb2NhbEpTWENvbW1hbmRDYWxsID0gYXN5bmMgKG9uRG9uZSwgX2NvbnRleHQsIGFyZ3MpID0+IHtcbiAgYXJncyA9IGFyZ3M/LnRyaW0oKSB8fCAnJ1xuICBpZiAoQ09NTU9OX0lORk9fQVJHUy5pbmNsdWRlcyhhcmdzKSkge1xuICAgIGxvZ0V2ZW50KCd0ZW5ndV9tb2RlbF9jb21tYW5kX2lubGluZV9oZWxwJywge1xuICAgICAgYXJnczogYXJncyBhcyBBbmFseXRpY3NNZXRhZGF0YV9JX1ZFUklGSUVEX1RISVNfSVNfTk9UX0NPREVfT1JfRklMRVBBVEhTLFxuICAgIH0pXG4gICAgcmV0dXJuIDxTaG93TW9kZWxBbmRDbG9zZSBvbkRvbmU9e29uRG9uZX0gLz5cbiAgfVxuICBpZiAoQ09NTU9OX0hFTFBfQVJHUy5pbmNsdWRlcyhhcmdzKSkge1xuICAgIG9uRG9uZShcbiAgICAgICdSdW4gL21vZGVsIHRvIG9wZW4gdGhlIG1vZGVsIHNlbGVjdGlvbiBtZW51LCBvciAvbW9kZWwgW21vZGVsTmFtZV0gdG8gc2V0IHRoZSBtb2RlbC4nLFxuICAgICAgeyBkaXNwbGF5OiAnc3lzdGVtJyB9LFxuICAgIClcbiAgICByZXR1cm5cbiAgfVxuXG4gIGlmIChhcmdzKSB7XG4gICAgbG9nRXZlbnQoJ3Rlbmd1X21vZGVsX2NvbW1hbmRfaW5saW5lJywge1xuICAgICAgYXJnczogYXJncyBhcyBBbmFseXRpY3NNZXRhZGF0YV9JX1ZFUklGSUVEX1RISVNfSVNfTk9UX0NPREVfT1JfRklMRVBBVEhTLFxuICAgIH0pXG4gICAgcmV0dXJuIDxTZXRNb2RlbEFuZENsb3NlIGFyZ3M9e2FyZ3N9IG9uRG9uZT17b25Eb25lfSAvPlxuICB9XG5cbiAgcmV0dXJuIDxNb2RlbFBpY2tlcldyYXBwZXIgb25Eb25lPXtvbkRvbmV9IC8+XG59XG5cbmZ1bmN0aW9uIHJlbmRlck1vZGVsTGFiZWwobW9kZWw6IHN0cmluZyB8IG51bGwpOiBzdHJpbmcge1xuICBjb25zdCByZW5kZXJlZCA9IHJlbmRlckRlZmF1bHRNb2RlbFNldHRpbmcoXG4gICAgbW9kZWwgPz8gZ2V0RGVmYXVsdE1haW5Mb29wTW9kZWxTZXR0aW5nKCksXG4gIClcbiAgcmV0dXJuIG1vZGVsID09PSBudWxsID8gYCR7cmVuZGVyZWR9IChkZWZhdWx0KWAgOiByZW5kZXJlZFxufVxuIl0sIm1hcHBpbmdzIjoiO0FBQUEsT0FBT0EsS0FBSyxNQUFNLE9BQU87QUFDekIsT0FBTyxLQUFLQyxLQUFLLE1BQU0sT0FBTztBQUM5QixjQUFjQyxvQkFBb0IsUUFBUSxtQkFBbUI7QUFDN0QsU0FBU0MsV0FBVyxRQUFRLGlDQUFpQztBQUM3RCxTQUFT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
\ No newline at end of file
diff --git a/src/commands/oauth-refresh/index.js b/src/commands/oauth-refresh/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..e1a619d254332b7e0c662931b91aae3a9f9c3b0e
--- /dev/null
+++ b/src/commands/oauth-refresh/index.js
@@ -0,0 +1 @@
+export default { isEnabled: () => false, isHidden: true, name: 'stub' };
diff --git a/src/commands/onboarding/index.js b/src/commands/onboarding/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..e1a619d254332b7e0c662931b91aae3a9f9c3b0e
--- /dev/null
+++ b/src/commands/onboarding/index.js
@@ -0,0 +1 @@
+export default { isEnabled: () => false, isHidden: true, name: 'stub' };
diff --git a/src/commands/output-style/index.ts b/src/commands/output-style/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5cb0e25e62094f477e8fe052c0a7db65842217cc
--- /dev/null
+++ b/src/commands/output-style/index.ts
@@ -0,0 +1,11 @@
+import type { Command } from '../../commands.js'
+
+const outputStyle = {
+ type: 'local-jsx',
+ name: 'output-style',
+ description: 'Deprecated: use /config to change output style',
+ isHidden: true,
+ load: () => import('./output-style.js'),
+} satisfies Command
+
+export default outputStyle
diff --git a/src/commands/output-style/output-style.tsx b/src/commands/output-style/output-style.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..9e26bf38271aa34831198486cd6a281b3837e7ed
--- /dev/null
+++ b/src/commands/output-style/output-style.tsx
@@ -0,0 +1,7 @@
+import type { LocalJSXCommandOnDone } from '../../types/command.js';
+export async function call(onDone: LocalJSXCommandOnDone): Promise {
+ onDone('/output-style has been deprecated. Use /config to change your output style, or set it in your settings file. Changes take effect on the next session.', {
+ display: 'system'
+ });
+}
+//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJMb2NhbEpTWENvbW1hbmRPbkRvbmUiLCJjYWxsIiwib25Eb25lIiwiUHJvbWlzZSIsImRpc3BsYXkiXSwic291cmNlcyI6WyJvdXRwdXQtc3R5bGUudHN4Il0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB0eXBlIHsgTG9jYWxKU1hDb21tYW5kT25Eb25lIH0gZnJvbSAnLi4vLi4vdHlwZXMvY29tbWFuZC5qcydcblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGNhbGwob25Eb25lOiBMb2NhbEpTWENvbW1hbmRPbkRvbmUpOiBQcm9taXNlPHVuZGVmaW5lZD4ge1xuICBvbkRvbmUoXG4gICAgJy9vdXRwdXQtc3R5bGUgaGFzIGJlZW4gZGVwcmVjYXRlZC4gVXNlIC9jb25maWcgdG8gY2hhbmdlIHlvdXIgb3V0cHV0IHN0eWxlLCBvciBzZXQgaXQgaW4geW91ciBzZXR0aW5ncyBmaWxlLiBDaGFuZ2VzIHRha2UgZWZmZWN0IG9uIHRoZSBuZXh0IHNlc3Npb24uJyxcbiAgICB7IGRpc3BsYXk6ICdzeXN0ZW0nIH0sXG4gIClcbn1cbiJdLCJtYXBwaW5ncyI6IkFBQUEsY0FBY0EscUJBQXFCLFFBQVEsd0JBQXdCO0FBRW5FLE9BQU8sZUFBZUMsSUFBSUEsQ0FBQ0MsTUFBTSxFQUFFRixxQkFBcUIsQ0FBQyxFQUFFRyxPQUFPLENBQUMsU0FBUyxDQUFDLENBQUM7RUFDNUVELE1BQU0sQ0FDSix1SkFBdUosRUFDdko7SUFBRUUsT0FBTyxFQUFFO0VBQVMsQ0FDdEIsQ0FBQztBQUNIIiwiaWdub3JlTGlzdCI6W119
\ No newline at end of file
diff --git a/src/commands/passes/index.ts b/src/commands/passes/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fc160132296f2fb58af3998a9caf8fd263cd0f0b
--- /dev/null
+++ b/src/commands/passes/index.ts
@@ -0,0 +1,22 @@
+import type { Command } from '../../commands.js'
+import {
+ checkCachedPassesEligibility,
+ getCachedReferrerReward,
+} from '../../services/api/referral.js'
+
+export default {
+ type: 'local-jsx',
+ name: 'passes',
+ get description() {
+ const reward = getCachedReferrerReward()
+ if (reward) {
+ return 'Share a free week of Claude Code with friends and earn extra usage'
+ }
+ return 'Share a free week of Claude Code with friends'
+ },
+ get isHidden() {
+ const { eligible, hasCache } = checkCachedPassesEligibility()
+ return !eligible || !hasCache
+ },
+ load: () => import('./passes.js'),
+} satisfies Command
diff --git a/src/commands/passes/passes.tsx b/src/commands/passes/passes.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..c17cb59471931801247123bf435029f4f2e94479
--- /dev/null
+++ b/src/commands/passes/passes.tsx
@@ -0,0 +1,24 @@
+import * as React from 'react';
+import { Passes } from '../../components/Passes/Passes.js';
+import { logEvent } from '../../services/analytics/index.js';
+import { getCachedRemainingPasses } from '../../services/api/referral.js';
+import type { LocalJSXCommandOnDone } from '../../types/command.js';
+import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js';
+export async function call(onDone: LocalJSXCommandOnDone): Promise {
+ // Mark that user has visited /passes so we stop showing the upsell
+ const config = getGlobalConfig();
+ const isFirstVisit = !config.hasVisitedPasses;
+ if (isFirstVisit) {
+ const remaining = getCachedRemainingPasses();
+ saveGlobalConfig(current => ({
+ ...current,
+ hasVisitedPasses: true,
+ passesLastSeenRemaining: remaining ?? current.passesLastSeenRemaining
+ }));
+ }
+ logEvent('tengu_guest_passes_visited', {
+ is_first_visit: isFirstVisit
+ });
+ return ;
+}
+//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsIlBhc3NlcyIsImxvZ0V2ZW50IiwiZ2V0Q2FjaGVkUmVtYWluaW5nUGFzc2VzIiwiTG9jYWxKU1hDb21tYW5kT25Eb25lIiwiZ2V0R2xvYmFsQ29uZmlnIiwic2F2ZUdsb2JhbENvbmZpZyIsImNhbGwiLCJvbkRvbmUiLCJQcm9taXNlIiwiUmVhY3ROb2RlIiwiY29uZmlnIiwiaXNGaXJzdFZpc2l0IiwiaGFzVmlzaXRlZFBhc3NlcyIsInJlbWFpbmluZyIsImN1cnJlbnQiLCJwYXNzZXNMYXN0U2VlblJlbWFpbmluZyIsImlzX2ZpcnN0X3Zpc2l0Il0sInNvdXJjZXMiOlsicGFzc2VzLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgKiBhcyBSZWFjdCBmcm9tICdyZWFjdCdcbmltcG9ydCB7IFBhc3NlcyB9IGZyb20gJy4uLy4uL2NvbXBvbmVudHMvUGFzc2VzL1Bhc3Nlcy5qcydcbmltcG9ydCB7IGxvZ0V2ZW50IH0gZnJvbSAnLi4vLi4vc2VydmljZXMvYW5hbHl0aWNzL2luZGV4LmpzJ1xuaW1wb3J0IHsgZ2V0Q2FjaGVkUmVtYWluaW5nUGFzc2VzIH0gZnJvbSAnLi4vLi4vc2VydmljZXMvYXBpL3JlZmVycmFsLmpzJ1xuaW1wb3J0IHR5cGUgeyBMb2NhbEpTWENvbW1hbmRPbkRvbmUgfSBmcm9tICcuLi8uLi90eXBlcy9jb21tYW5kLmpzJ1xuaW1wb3J0IHsgZ2V0R2xvYmFsQ29uZmlnLCBzYXZlR2xvYmFsQ29uZmlnIH0gZnJvbSAnLi4vLi4vdXRpbHMvY29uZmlnLmpzJ1xuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gY2FsbChcbiAgb25Eb25lOiBMb2NhbEpTWENvbW1hbmRPbkRvbmUsXG4pOiBQcm9taXNlPFJlYWN0LlJlYWN0Tm9kZT4ge1xuICAvLyBNYXJrIHRoYXQgdXNlciBoYXMgdmlzaXRlZCAvcGFzc2VzIHNvIHdlIHN0b3Agc2hvd2luZyB0aGUgdXBzZWxsXG4gIGNvbnN0IGNvbmZpZyA9IGdldEdsb2JhbENvbmZpZygpXG4gIGNvbnN0IGlzRmlyc3RWaXNpdCA9ICFjb25maWcuaGFzVmlzaXRlZFBhc3Nlc1xuICBpZiAoaXNGaXJzdFZpc2l0KSB7XG4gICAgY29uc3QgcmVtYWluaW5nID0gZ2V0Q2FjaGVkUmVtYWluaW5nUGFzc2VzKClcbiAgICBzYXZlR2xvYmFsQ29uZmlnKGN1cnJlbnQgPT4gKHtcbiAgICAgIC4uLmN1cnJlbnQsXG4gICAgICBoYXNWaXNpdGVkUGFzc2VzOiB0cnVlLFxuICAgICAgcGFzc2VzTGFzdFNlZW5SZW1haW5pbmc6IHJlbWFpbmluZyA/PyBjdXJyZW50LnBhc3Nlc0xhc3RTZWVuUmVtYWluaW5nLFxuICAgIH0pKVxuICB9XG4gIGxvZ0V2ZW50KCd0ZW5ndV9ndWVzdF9wYXNzZXNfdmlzaXRlZCcsIHsgaXNfZmlyc3RfdmlzaXQ6IGlzRmlyc3RWaXNpdCB9KVxuICByZXR1cm4gPFBhc3NlcyBvbkRvbmU9e29uRG9uZX0gLz5cbn1cbiJdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLQSxLQUFLLE1BQU0sT0FBTztBQUM5QixTQUFTQyxNQUFNLFFBQVEsbUNBQW1DO0FBQzFELFNBQVNDLFFBQVEsUUFBUSxtQ0FBbUM7QUFDNUQsU0FBU0Msd0JBQXdCLFFBQVEsZ0NBQWdDO0FBQ3pFLGNBQWNDLHFCQUFxQixRQUFRLHdCQUF3QjtBQUNuRSxTQUFTQyxlQUFlLEVBQUVDLGdCQUFnQixRQUFRLHVCQUF1QjtBQUV6RSxPQUFPLGVBQWVDLElBQUlBLENBQ3hCQyxNQUFNLEVBQUVKLHFCQUFxQixDQUM5QixFQUFFSyxPQUFPLENBQUNULEtBQUssQ0FBQ1UsU0FBUyxDQUFDLENBQUM7RUFDMUI7RUFDQSxNQUFNQyxNQUFNLEdBQUdOLGVBQWUsQ0FBQyxDQUFDO0VBQ2hDLE1BQU1PLFlBQVksR0FBRyxDQUFDRCxNQUFNLENBQUNFLGdCQUFnQjtFQUM3QyxJQUFJRCxZQUFZLEVBQUU7SUFDaEIsTUFBTUUsU0FBUyxHQUFHWCx3QkFBd0IsQ0FBQyxDQUFDO0lBQzVDRyxnQkFBZ0IsQ0FBQ1MsT0FBTyxLQUFLO01BQzNCLEdBQUdBLE9BQU87TUFDVkYsZ0JBQWdCLEVBQUUsSUFBSTtNQUN0QkcsdUJBQXVCLEVBQUVGLFNBQVMsSUFBSUMsT0FBTyxDQUFDQztJQUNoRCxDQUFDLENBQUMsQ0FBQztFQUNMO0VBQ0FkLFFBQVEsQ0FBQyw0QkFBNEIsRUFBRTtJQUFFZSxjQUFjLEVBQUVMO0VBQWEsQ0FBQyxDQUFDO0VBQ3hFLE9BQU8sQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLENBQUNKLE1BQU0sQ0FBQyxHQUFHO0FBQ25DIiwiaWdub3JlTGlzdCI6W119
\ No newline at end of file
diff --git a/src/commands/perf-issue/index.js b/src/commands/perf-issue/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..e1a619d254332b7e0c662931b91aae3a9f9c3b0e
--- /dev/null
+++ b/src/commands/perf-issue/index.js
@@ -0,0 +1 @@
+export default { isEnabled: () => false, isHidden: true, name: 'stub' };
diff --git a/src/commands/permissions/index.ts b/src/commands/permissions/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ef4641f0e342b2ff65ce92725648fab0ef9de14e
--- /dev/null
+++ b/src/commands/permissions/index.ts
@@ -0,0 +1,11 @@
+import type { Command } from '../../commands.js'
+
+const permissions = {
+ type: 'local-jsx',
+ name: 'permissions',
+ aliases: ['allowed-tools'],
+ description: 'Manage allow & deny tool permission rules',
+ load: () => import('./permissions.js'),
+} satisfies Command
+
+export default permissions
diff --git a/src/commands/permissions/permissions.tsx b/src/commands/permissions/permissions.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..6248fd96b8e10b7fc067855bdeed5bb52f09f042
--- /dev/null
+++ b/src/commands/permissions/permissions.tsx
@@ -0,0 +1,10 @@
+import * as React from 'react';
+import { PermissionRuleList } from '../../components/permissions/rules/PermissionRuleList.js';
+import type { LocalJSXCommandCall } from '../../types/command.js';
+import { createPermissionRetryMessage } from '../../utils/messages.js';
+export const call: LocalJSXCommandCall = async (onDone, context) => {
+ return {
+ context.setMessages(prev => [...prev, createPermissionRetryMessage(commands)]);
+ }} />;
+};
+//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsIlBlcm1pc3Npb25SdWxlTGlzdCIsIkxvY2FsSlNYQ29tbWFuZENhbGwiLCJjcmVhdGVQZXJtaXNzaW9uUmV0cnlNZXNzYWdlIiwiY2FsbCIsIm9uRG9uZSIsImNvbnRleHQiLCJjb21tYW5kcyIsInNldE1lc3NhZ2VzIiwicHJldiJdLCJzb3VyY2VzIjpbInBlcm1pc3Npb25zLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgKiBhcyBSZWFjdCBmcm9tICdyZWFjdCdcbmltcG9ydCB7IFBlcm1pc3Npb25SdWxlTGlzdCB9IGZyb20gJy4uLy4uL2NvbXBvbmVudHMvcGVybWlzc2lvbnMvcnVsZXMvUGVybWlzc2lvblJ1bGVMaXN0LmpzJ1xuaW1wb3J0IHR5cGUgeyBMb2NhbEpTWENvbW1hbmRDYWxsIH0gZnJvbSAnLi4vLi4vdHlwZXMvY29tbWFuZC5qcydcbmltcG9ydCB7IGNyZWF0ZVBlcm1pc3Npb25SZXRyeU1lc3NhZ2UgfSBmcm9tICcuLi8uLi91dGlscy9tZXNzYWdlcy5qcydcblxuZXhwb3J0IGNvbnN0IGNhbGw6IExvY2FsSlNYQ29tbWFuZENhbGwgPSBhc3luYyAob25Eb25lLCBjb250ZXh0KSA9PiB7XG4gIHJldHVybiAoXG4gICAgPFBlcm1pc3Npb25SdWxlTGlzdFxuICAgICAgb25FeGl0PXtvbkRvbmV9XG4gICAgICBvblJldHJ5RGVuaWFscz17Y29tbWFuZHMgPT4ge1xuICAgICAgICBjb250ZXh0LnNldE1lc3NhZ2VzKHByZXYgPT4gW1xuICAgICAgICAgIC4uLnByZXYsXG4gICAgICAgICAgY3JlYXRlUGVybWlzc2lvblJldHJ5TWVzc2FnZShjb21tYW5kcyksXG4gICAgICAgIF0pXG4gICAgICB9fVxuICAgIC8+XG4gIClcbn1cbiJdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLQSxLQUFLLE1BQU0sT0FBTztBQUM5QixTQUFTQyxrQkFBa0IsUUFBUSwwREFBMEQ7QUFDN0YsY0FBY0MsbUJBQW1CLFFBQVEsd0JBQXdCO0FBQ2pFLFNBQVNDLDRCQUE0QixRQUFRLHlCQUF5QjtBQUV0RSxPQUFPLE1BQU1DLElBQUksRUFBRUYsbUJBQW1CLEdBQUcsTUFBQUUsQ0FBT0MsTUFBTSxFQUFFQyxPQUFPLEtBQUs7RUFDbEUsT0FDRSxDQUFDLGtCQUFrQixDQUNqQixNQUFNLENBQUMsQ0FBQ0QsTUFBTSxDQUFDLENBQ2YsY0FBYyxDQUFDLENBQUNFLFFBQVEsSUFBSTtJQUMxQkQsT0FBTyxDQUFDRSxXQUFXLENBQUNDLElBQUksSUFBSSxDQUMxQixHQUFHQSxJQUFJLEVBQ1BOLDRCQUE0QixDQUFDSSxRQUFRLENBQUMsQ0FDdkMsQ0FBQztFQUNKLENBQUMsQ0FBQyxHQUNGO0FBRU4sQ0FBQyIsImlnbm9yZUxpc3QiOltdfQ==
\ No newline at end of file
diff --git a/src/commands/plan/index.ts b/src/commands/plan/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c710899c9f634c18dc6964a9340c968969325efa
--- /dev/null
+++ b/src/commands/plan/index.ts
@@ -0,0 +1,11 @@
+import type { Command } from '../../commands.js'
+
+const plan = {
+ type: 'local-jsx',
+ name: 'plan',
+ description: 'Enable plan mode or view the current session plan',
+ argumentHint: '[open|]',
+ load: () => import('./plan.js'),
+} satisfies Command
+
+export default plan
diff --git a/src/commands/plan/plan.tsx b/src/commands/plan/plan.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..c8597b0c2f6852f04a975cf2dffa74348b03f55a
--- /dev/null
+++ b/src/commands/plan/plan.tsx
@@ -0,0 +1,122 @@
+import { c as _c } from "react/compiler-runtime";
+import * as React from 'react';
+import { handlePlanModeTransition } from '../../bootstrap/state.js';
+import type { LocalJSXCommandContext } from '../../commands.js';
+import { Box, Text } from '../../ink.js';
+import type { LocalJSXCommandOnDone } from '../../types/command.js';
+import { getExternalEditor } from '../../utils/editor.js';
+import { toIDEDisplayName } from '../../utils/ide.js';
+import { applyPermissionUpdate } from '../../utils/permissions/PermissionUpdate.js';
+import { prepareContextForPlanMode } from '../../utils/permissions/permissionSetup.js';
+import { getPlan, getPlanFilePath } from '../../utils/plans.js';
+import { editFileInEditor } from '../../utils/promptEditor.js';
+import { renderToString } from '../../utils/staticRender.js';
+function PlanDisplay(t0) {
+ const $ = _c(11);
+ const {
+ planContent,
+ planPath,
+ editorName
+ } = t0;
+ let t1;
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
+ t1 = Current Plan;
+ $[0] = t1;
+ } else {
+ t1 = $[0];
+ }
+ let t2;
+ if ($[1] !== planPath) {
+ t2 = {planPath};
+ $[1] = planPath;
+ $[2] = t2;
+ } else {
+ t2 = $[2];
+ }
+ let t3;
+ if ($[3] !== planContent) {
+ t3 = {planContent};
+ $[3] = planContent;
+ $[4] = t3;
+ } else {
+ t3 = $[4];
+ }
+ let t4;
+ if ($[5] !== editorName) {
+ t4 = editorName && "/plan open" to edit this plan in {editorName};
+ $[5] = editorName;
+ $[6] = t4;
+ } else {
+ t4 = $[6];
+ }
+ let t5;
+ if ($[7] !== t2 || $[8] !== t3 || $[9] !== t4) {
+ t5 = {t1}{t2}{t3}{t4};
+ $[7] = t2;
+ $[8] = t3;
+ $[9] = t4;
+ $[10] = t5;
+ } else {
+ t5 = $[10];
+ }
+ return t5;
+}
+export async function call(onDone: LocalJSXCommandOnDone, context: LocalJSXCommandContext, args: string): Promise {
+ const {
+ getAppState,
+ setAppState
+ } = context;
+ const appState = getAppState();
+ const currentMode = appState.toolPermissionContext.mode;
+
+ // If not in plan mode, enable it
+ if (currentMode !== 'plan') {
+ handlePlanModeTransition(currentMode, 'plan');
+ setAppState(prev => ({
+ ...prev,
+ toolPermissionContext: applyPermissionUpdate(prepareContextForPlanMode(prev.toolPermissionContext), {
+ type: 'setMode',
+ mode: 'plan',
+ destination: 'session'
+ })
+ }));
+ const description = args.trim();
+ if (description && description !== 'open') {
+ onDone('Enabled plan mode', {
+ shouldQuery: true
+ });
+ } else {
+ onDone('Enabled plan mode');
+ }
+ return null;
+ }
+
+ // Already in plan mode - show the current plan
+ const planContent = getPlan();
+ const planPath = getPlanFilePath();
+ if (!planContent) {
+ onDone('Already in plan mode. No plan written yet.');
+ return null;
+ }
+
+ // If user typed "/plan open", open in editor
+ const argList = args.trim().split(/\s+/);
+ if (argList[0] === 'open') {
+ const result = await editFileInEditor(planPath);
+ if (result.error) {
+ onDone(`Failed to open plan in editor: ${result.error}`);
+ } else {
+ onDone(`Opened plan in editor: ${planPath}`);
+ }
+ return null;
+ }
+ const editor = getExternalEditor();
+ const editorName = editor ? toIDEDisplayName(editor) : undefined;
+ const display = ;
+
+ // Render to string and pass to onDone like local commands do
+ const output = await renderToString(display);
+ onDone(output);
+ return null;
+}
+//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsImhhbmRsZVBsYW5Nb2RlVHJhbnNpdGlvbiIsIkxvY2FsSlNYQ29tbWFuZENvbnRleHQiLCJCb3giLCJUZXh0IiwiTG9jYWxKU1hDb21tYW5kT25Eb25lIiwiZ2V0RXh0ZXJuYWxFZGl0b3IiLCJ0b0lERURpc3BsYXlOYW1lIiwiYXBwbHlQZXJtaXNzaW9uVXBkYXRlIiwicHJlcGFyZUNvbnRleHRGb3JQbGFuTW9kZSIsImdldFBsYW4iLCJnZXRQbGFuRmlsZVBhdGgiLCJlZGl0RmlsZUluRWRpdG9yIiwicmVuZGVyVG9TdHJpbmciLCJQbGFuRGlzcGxheSIsInQwIiwiJCIsIl9jIiwicGxhbkNvbnRlbnQiLCJwbGFuUGF0aCIsImVkaXRvck5hbWUiLCJ0MSIsIlN5bWJvbCIsImZvciIsInQyIiwidDMiLCJ0NCIsInQ1IiwiY2FsbCIsIm9uRG9uZSIsImNvbnRleHQiLCJhcmdzIiwiUHJvbWlzZSIsIlJlYWN0Tm9kZSIsImdldEFwcFN0YXRlIiwic2V0QXBwU3RhdGUiLCJhcHBTdGF0ZSIsImN1cnJlbnRNb2RlIiwidG9vbFBlcm1pc3Npb25Db250ZXh0IiwibW9kZSIsInByZXYiLCJ0eXBlIiwiZGVzdGluYXRpb24iLCJkZXNjcmlwdGlvbiIsInRyaW0iLCJzaG91bGRRdWVyeSIsImFyZ0xpc3QiLCJzcGxpdCIsInJlc3VsdCIsImVycm9yIiwiZWRpdG9yIiwidW5kZWZpbmVkIiwiZGlzcGxheSIsIm91dHB1dCJdLCJzb3VyY2VzIjpbInBsYW4udHN4Il0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCAqIGFzIFJlYWN0IGZyb20gJ3JlYWN0J1xuaW1wb3J0IHsgaGFuZGxlUGxhbk1vZGVUcmFuc2l0aW9uIH0gZnJvbSAnLi4vLi4vYm9vdHN0cmFwL3N0YXRlLmpzJ1xuaW1wb3J0IHR5cGUgeyBMb2NhbEpTWENvbW1hbmRDb250ZXh0IH0gZnJvbSAnLi4vLi4vY29tbWFuZHMuanMnXG5pbXBvcnQgeyBCb3gsIFRleHQgfSBmcm9tICcuLi8uLi9pbmsuanMnXG5pbXBvcnQgdHlwZSB7IExvY2FsSlNYQ29tbWFuZE9uRG9uZSB9IGZyb20gJy4uLy4uL3R5cGVzL2NvbW1hbmQuanMnXG5pbXBvcnQgeyBnZXRFeHRlcm5hbEVkaXRvciB9IGZyb20gJy4uLy4uL3V0aWxzL2VkaXRvci5qcydcbmltcG9ydCB7IHRvSURFRGlzcGxheU5hbWUgfSBmcm9tICcuLi8uLi91dGlscy9pZGUuanMnXG5pbXBvcnQgeyBhcHBseVBlcm1pc3Npb25VcGRhdGUgfSBmcm9tICcuLi8uLi91dGlscy9wZXJtaXNzaW9ucy9QZXJtaXNzaW9uVXBkYXRlLmpzJ1xuaW1wb3J0IHsgcHJlcGFyZUNvbnRleHRGb3JQbGFuTW9kZSB9IGZyb20gJy4uLy4uL3V0aWxzL3Blcm1pc3Npb25zL3Blcm1pc3Npb25TZXR1cC5qcydcbmltcG9ydCB7IGdldFBsYW4sIGdldFBsYW5GaWxlUGF0aCB9IGZyb20gJy4uLy4uL3V0aWxzL3BsYW5zLmpzJ1xuaW1wb3J0IHsgZWRpdEZpbGVJbkVkaXRvciB9IGZyb20gJy4uLy4uL3V0aWxzL3Byb21wdEVkaXRvci5qcydcbmltcG9ydCB7IHJlbmRlclRvU3RyaW5nIH0gZnJvbSAnLi4vLi4vdXRpbHMvc3RhdGljUmVuZGVyLmpzJ1xuXG5mdW5jdGlvbiBQbGFuRGlzcGxheSh7XG4gIHBsYW5Db250ZW50LFxuICBwbGFuUGF0aCxcbiAgZWRpdG9yTmFtZSxcbn06IHtcbiAgcGxhbkNvbnRlbnQ6IHN0cmluZ1xuICBwbGFuUGF0aDogc3RyaW5nXG4gIGVkaXRvck5hbWU6IHN0cmluZyB8IHVuZGVmaW5lZFxufSk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gIHJldHVybiAoXG4gICAgPEJveCBmbGV4RGlyZWN0aW9uPVwiY29sdW1uXCI+XG4gICAgICA8VGV4dCBib2xkPkN1cnJlbnQgUGxhbjwvVGV4dD5cbiAgICAgIDxUZXh0IGRpbUNvbG9yPntwbGFuUGF0aH08L1RleHQ+XG4gICAgICA8Qm94IG1hcmdpblRvcD17MX0+XG4gICAgICAgIDxUZXh0PntwbGFuQ29udGVudH08L1RleHQ+XG4gICAgICA8L0JveD5cbiAgICAgIHtlZGl0b3JOYW1lICYmIChcbiAgICAgICAgPEJveCBtYXJnaW5Ub3A9ezF9PlxuICAgICAgICAgIDxUZXh0IGRpbUNvbG9yPiZxdW90Oy9wbGFuIG9wZW4mcXVvdDs8L1RleHQ+XG4gICAgICAgICAgPFRleHQgZGltQ29sb3I+IHRvIGVkaXQgdGhpcyBwbGFuIGluIDwvVGV4dD5cbiAgICAgICAgICA8VGV4dCBib2xkIGRpbUNvbG9yPlxuICAgICAgICAgICAge2VkaXRvck5hbWV9XG4gICAgICAgICAgPC9UZXh0PlxuICAgICAgICA8L0JveD5cbiAgICAgICl9XG4gICAgPC9Cb3g+XG4gIClcbn1cblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGNhbGwoXG4gIG9uRG9uZTogTG9jYWxKU1hDb21tYW5kT25Eb25lLFxuICBjb250ZXh0OiBMb2NhbEpTWENvbW1hbmRDb250ZXh0LFxuICBhcmdzOiBzdHJpbmcsXG4pOiBQcm9taXNlPFJlYWN0LlJlYWN0Tm9kZT4ge1xuICBjb25zdCB7IGdldEFwcFN0YXRlLCBzZXRBcHBTdGF0ZSB9ID0gY29udGV4dFxuICBjb25zdCBhcHBTdGF0ZSA9IGdldEFwcFN0YXRlKClcbiAgY29uc3QgY3VycmVudE1vZGUgPSBhcHBTdGF0ZS50b29sUGVybWlzc2lvbkNvbnRleHQubW9kZVxuXG4gIC8vIElmIG5vdCBpbiBwbGFuIG1vZGUsIGVuYWJsZSBpdFxuICBpZiAoY3VycmVudE1vZGUgIT09ICdwbGFuJykge1xuICAgIGhhbmRsZVBsYW5Nb2RlVHJhbnNpdGlvbihjdXJyZW50TW9kZSwgJ3BsYW4nKVxuICAgIHNldEFwcFN0YXRlKHByZXYgPT4gKHtcbiAgICAgIC4uLnByZXYsXG4gICAgICB0b29sUGVybWlzc2lvbkNvbnRleHQ6IGFwcGx5UGVybWlzc2lvblVwZGF0ZShcbiAgICAgICAgcHJlcGFyZUNvbnRleHRGb3JQbGFuTW9kZShwcmV2LnRvb2xQZXJtaXNzaW9uQ29udGV4dCksXG4gICAgICAgIHsgdHlwZTogJ3NldE1vZGUnLCBtb2RlOiAncGxhbicsIGRlc3RpbmF0aW9uOiAnc2Vzc2lvbicgfSxcbiAgICAgICksXG4gICAgfSkpXG4gICAgY29uc3QgZGVzY3JpcHRpb24gPSBhcmdzLnRyaW0oKVxuICAgIGlmIChkZXNjcmlwdGlvbiAmJiBkZXNjcmlwdGlvbiAhPT0gJ29wZW4nKSB7XG4gICAgICBvbkRvbmUoJ0VuYWJsZWQgcGxhbiBtb2RlJywgeyBzaG91bGRRdWVyeTogdHJ1ZSB9KVxuICAgIH0gZWxzZSB7XG4gICAgICBvbkRvbmUoJ0VuYWJsZWQgcGxhbiBtb2RlJylcbiAgICB9XG4gICAgcmV0dXJuIG51bGxcbiAgfVxuXG4gIC8vIEFscmVhZHkgaW4gcGxhbiBtb2RlIC0gc2hvdyB0aGUgY3VycmVudCBwbGFuXG4gIGNvbnN0IHBsYW5Db250ZW50ID0gZ2V0UGxhbigpXG4gIGNvbnN0IHBsYW5QYXRoID0gZ2V0UGxhbkZpbGVQYXRoKClcblxuICBpZiAoIXBsYW5Db250ZW50KSB7XG4gICAgb25Eb25lKCdBbHJlYWR5IGluIHBsYW4gbW9kZS4gTm8gcGxhbiB3cml0dGVuIHlldC4nKVxuICAgIHJldHVybiBudWxsXG4gIH1cblxuICAvLyBJZiB1c2VyIHR5cGVkIFwiL3BsYW4gb3BlblwiLCBvcGVuIGluIGVkaXRvclxuICBjb25zdCBhcmdMaXN0ID0gYXJncy50cmltKCkuc3BsaXQoL1xccysvKVxuICBpZiAoYXJnTGlzdFswXSA9PT0gJ29wZW4nKSB7XG4gICAgY29uc3QgcmVzdWx0ID0gYXdhaXQgZWRpdEZpbGVJbkVkaXRvcihwbGFuUGF0aClcbiAgICBpZiAocmVzdWx0LmVycm9yKSB7XG4gICAgICBvbkRvbmUoYEZhaWxlZCB0byBvcGVuIHBsYW4gaW4gZWRpdG9yOiAke3Jlc3VsdC5lcnJvcn1gKVxuICAgIH0gZWxzZSB7XG4gICAgICBvbkRvbmUoYE9wZW5lZCBwbGFuIGluIGVkaXRvcjogJHtwbGFuUGF0aH1gKVxuICAgIH1cbiAgICByZXR1cm4gbnVsbFxuICB9XG5cbiAgY29uc3QgZWRpdG9yID0gZ2V0RXh0ZXJuYWxFZGl0b3IoKVxuICBjb25zdCBlZGl0b3JOYW1lID0gZWRpdG9yID8gdG9JREVEaXNwbGF5TmFtZShlZGl0b3IpIDogdW5kZWZpbmVkXG5cbiAgY29uc3QgZGlzcGxheSA9IChcbiAgICA8UGxhbkRpc3BsYXlcbiAgICAgIHBsYW5Db250ZW50PXtwbGFuQ29udGVudH1cbiAgICAgIHBsYW5QYXRoPXtwbGFuUGF0aH1cbiAgICAgIGVkaXRvck5hbWU9e2VkaXRvck5hbWV9XG4gICAgLz5cbiAgKVxuXG4gIC8vIFJlbmRlciB0byBzdHJpbmcgYW5kIHBhc3MgdG8gb25Eb25lIGxpa2UgbG9jYWwgY29tbWFuZHMgZG9cbiAgY29uc3Qgb3V0cHV0ID0gYXdhaXQgcmVuZGVyVG9TdHJpbmcoZGlzcGxheSlcbiAgb25Eb25lKG91dHB1dClcbiAgcmV0dXJuIG51bGxcbn1cbiJdLCJtYXBwaW5ncyI6IjtBQUFBLE9BQU8sS0FBS0EsS0FBSyxNQUFNLE9BQU87QUFDOUIsU0FBU0Msd0JBQXdCLFFBQVEsMEJBQTBCO0FBQ25FLGNBQWNDLHNCQUFzQixRQUFRLG1CQUFtQjtBQUMvRCxTQUFTQyxHQUFHLEVBQUVDLElBQUksUUFBUSxjQUFjO0FBQ3hDLGNBQWNDLHFCQUFxQixRQUFRLHdCQUF3QjtBQUNuRSxTQUFTQyxpQkFBaUIsUUFBUSx1QkFBdUI7QUFDekQsU0FBU0MsZ0JBQWdCLFFBQVEsb0JBQW9CO0FBQ3JELFNBQVNDLHFCQUFxQixRQUFRLDZDQUE2QztBQUNuRixTQUFTQyx5QkFBeUIsUUFBUSw0Q0FBNEM7QUFDdEYsU0FBU0MsT0FBTyxFQUFFQyxlQUFlLFFBQVEsc0JBQXNCO0FBQy9ELFNBQVNDLGdCQUFnQixRQUFRLDZCQUE2QjtBQUM5RCxTQUFTQyxjQUFjLFFBQVEsNkJBQTZCO0FBRTVELFNBQUFDLFlBQUFDLEVBQUE7RUFBQSxNQUFBQyxDQUFBLEdBQUFDLEVBQUE7RUFBcUI7SUFBQUMsV0FBQTtJQUFBQyxRQUFBO0lBQUFDO0VBQUEsSUFB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
\ No newline at end of file
diff --git a/src/commands/plugin/AddMarketplace.tsx b/src/commands/plugin/AddMarketplace.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..79b8188db376c397ac1df2030010d0f692fe67f9
--- /dev/null
+++ b/src/commands/plugin/AddMarketplace.tsx
@@ -0,0 +1,162 @@
+import * as React from 'react';
+import { useEffect, useRef, useState } from 'react';
+import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, logEvent } from 'src/services/analytics/index.js';
+import { ConfigurableShortcutHint } from '../../components/ConfigurableShortcutHint.js';
+import { Byline } from '../../components/design-system/Byline.js';
+import { KeyboardShortcutHint } from '../../components/design-system/KeyboardShortcutHint.js';
+import { Spinner } from '../../components/Spinner.js';
+import TextInput from '../../components/TextInput.js';
+import { Box, Text } from '../../ink.js';
+import { toError } from '../../utils/errors.js';
+import { logError } from '../../utils/log.js';
+import { clearAllCaches } from '../../utils/plugins/cacheUtils.js';
+import { addMarketplaceSource, saveMarketplaceToSettings } from '../../utils/plugins/marketplaceManager.js';
+import { parseMarketplaceInput } from '../../utils/plugins/parseMarketplaceInput.js';
+import type { ViewState } from './types.js';
+type Props = {
+ inputValue: string;
+ setInputValue: (value: string) => void;
+ cursorOffset: number;
+ setCursorOffset: (offset: number) => void;
+ error: string | null;
+ setError: (error: string | null) => void;
+ result: string | null;
+ setResult: (result: string | null) => void;
+ setViewState: (state: ViewState) => void;
+ onAddComplete?: () => void | Promise;
+ cliMode?: boolean;
+};
+export function AddMarketplace({
+ inputValue,
+ setInputValue,
+ cursorOffset,
+ setCursorOffset,
+ error,
+ setError,
+ result,
+ setResult,
+ setViewState,
+ onAddComplete,
+ cliMode = false
+}: Props): React.ReactNode {
+ const hasAttemptedAutoAdd = useRef(false);
+ const [isLoading, setLoading] = useState(false);
+ const [progressMessage, setProgressMessage] = useState('');
+ const handleAdd = async () => {
+ const input = inputValue.trim();
+ if (!input) {
+ setError('Please enter a marketplace source');
+ return;
+ }
+ const parsed = await parseMarketplaceInput(input);
+ if (!parsed) {
+ setError('Invalid marketplace source format. Try: owner/repo, https://..., or ./path');
+ return;
+ }
+
+ // Check if parseMarketplaceInput returned an error
+ if ('error' in parsed) {
+ setError(parsed.error);
+ return;
+ }
+ setError(null);
+ try {
+ setLoading(true);
+ setProgressMessage('');
+ const {
+ name,
+ resolvedSource
+ } = await addMarketplaceSource(parsed, message => {
+ setProgressMessage(message);
+ });
+ saveMarketplaceToSettings(name, {
+ source: resolvedSource
+ });
+ clearAllCaches();
+ let sourceType = parsed.source;
+ if (parsed.source === 'github') {
+ sourceType = parsed.repo as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS;
+ }
+ logEvent('tengu_marketplace_added', {
+ source_type: sourceType as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
+ });
+ if (onAddComplete) {
+ await onAddComplete();
+ }
+ setProgressMessage('');
+ setLoading(false);
+ if (cliMode) {
+ // In CLI mode, set result to trigger completion
+ setResult(`Successfully added marketplace: ${name}`);
+ } else {
+ // In interactive mode, switch to browse view
+ setViewState({
+ type: 'browse-marketplace',
+ targetMarketplace: name
+ });
+ }
+ } catch (err) {
+ const error = toError(err);
+ logError(error);
+ setError(error.message);
+ setProgressMessage('');
+ setLoading(false);
+ if (cliMode) {
+ // In CLI mode, set result with error to trigger completion
+ setResult(`Error: ${error.message}`);
+ } else {
+ setResult(null);
+ }
+ }
+ };
+
+ // Auto-add if inputValue is provided
+ useEffect(() => {
+ if (inputValue && !hasAttemptedAutoAdd.current && !error && !result) {
+ hasAttemptedAutoAdd.current = true;
+ void handleAdd();
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ // biome-ignore lint/correctness/useExhaustiveDependencies: intentional
+ }, []); // Only run once on mount
+
+ return
+
+
+ Add Marketplace
+
+
+ Enter marketplace source:
+ Examples:
+ · owner/repo (GitHub)
+ · git@github.com:owner/repo.git (SSH)
+ · https://example.com/marketplace.json
+ · ./path/to/marketplace
+
+
+
+
+ {isLoading &&
+
+
+ {progressMessage || 'Adding marketplace to configuration…'}
+
+ }
+ {error &&
+ {error}
+ }
+ {result &&
+ {result}
+ }
+
+
+
+
+
+
+
+
+
+ ;
+}
+//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsInVzZUVmZmVjdCIsInVzZVJlZiIsInVzZVN0YXRlIiwiQW5hbHl0aWNzTWV0YWRhdGFfSV9WRVJJRklFRF9USElTX0lTX05PVF9DT0RFX09SX0ZJTEVQQVRIUyIsImxvZ0V2ZW50IiwiQ29uZmlndXJhYmxlU2hvcnRjdXRIaW50IiwiQnlsaW5lIiwiS2V5Ym9hcmRTaG9ydGN1dEhpbnQiLCJTcGlubmVyIiwiVGV4dElucHV0IiwiQm94IiwiVGV4dCIsInRvRXJyb3IiLCJsb2dFcnJvciIsImNsZWFyQWxsQ2FjaGVzIiwiYWRkTWFya2V0cGxhY2VTb3VyY2UiLCJzYXZlTWFya2V0cGxhY2VUb1NldHRpbmdzIiwicGFyc2VNYXJrZXRwbGFjZUlucHV0IiwiVmlld1N0YXRlIiwiUHJvcHMiLCJpbnB1dFZhbHVlIiwic2V0SW5wdXRWYWx1ZSIsInZhbHVlIiwiY3Vyc29yT2Zmc2V0Iiwic2V0Q3Vyc29yT2Zmc2V0Iiwib2Zmc2V0IiwiZXJyb3IiLCJzZXRFcnJvciIsInJlc3VsdCIsInNldFJlc3VsdCIsInNldFZpZXdTdGF0ZSIsInN0YXRlIiwib25BZGRDb21wbGV0ZSIsIlByb21pc2UiLCJjbGlNb2RlIiwiQWRkTWFya2V0cGxhY2UiLCJSZWFjdE5vZGUiLCJoYXNBdHRlbXB0ZWRBdXRvQWRkIiwiaXNMb2FkaW5nIiwic2V0TG9hZGluZyIsInByb2dyZXNzTWVzc2FnZSIsInNldFByb2dyZXNzTWVzc2FnZSIsImhhbmRsZUFkZCIsImlucHV0IiwidHJpbSIsInBhcnNlZCIsIm5hbWUiLCJyZXNvbHZlZFNvdXJjZSIsIm1lc3NhZ2UiLCJzb3VyY2UiLCJzb3VyY2VUeXBlIiwicmVwbyIsInNvdXJjZV90eXBlIiwidHlwZSIsInRhcmdldE1hcmtldHBsYWNlIiwiZXJyIiwiY3VycmVudCJdLCJzb3VyY2VzIjpbIkFkZE1hcmtldHBsYWNlLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgKiBhcyBSZWFjdCBmcm9tICdyZWFjdCdcbmltcG9ydCB7IHVzZUVmZmVjdCwgdXNlUmVmLCB1c2VTdGF0ZSB9IGZyb20gJ3JlYWN0J1xuaW1wb3J0IHtcbiAgdHlwZSBBbmFseXRpY3NNZXRhZGF0YV9JX1ZFUklGSUVEX1RISVNfSVNfTk9UX0NPREVfT1JfRklMRVBBVEhTLFxuICBsb2dFdmVudCxcbn0gZnJvbSAnc3JjL3NlcnZpY2VzL2FuYWx5dGljcy9pbmRleC5qcydcbmltcG9ydCB7IENvbmZpZ3VyYWJsZVNob3J0Y3V0SGludCB9IGZyb20gJy4uLy4uL2NvbXBvbmVudHMvQ29uZmlndXJhYmxlU2hvcnRjdXRIaW50LmpzJ1xuaW1wb3J0IHsgQnlsaW5lIH0gZnJvbSAnLi4vLi4vY29tcG9uZW50cy9kZXNpZ24tc3lzdGVtL0J5bGluZS5qcydcbmltcG9ydCB7IEtleWJvYXJkU2hvcnRjdXRIaW50IH0gZnJvbSAnLi4vLi4vY29tcG9uZW50cy9kZXNpZ24tc3lzdGVtL0tleWJvYXJkU2hvcnRjdXRIaW50LmpzJ1xuaW1wb3J0IHsgU3Bpbm5lciB9IGZyb20gJy4uLy4uL2NvbXBvbmVudHMvU3Bpbm5lci5qcydcbmltcG9ydCBUZXh0SW5wdXQgZnJvbSAnLi4vLi4vY29tcG9uZW50cy9UZXh0SW5wdXQuanMnXG5pbXBvcnQgeyBCb3gsIFRleHQgfSBmcm9tICcuLi8uLi9pbmsuanMnXG5pbXBvcnQgeyB0b0Vycm9yIH0gZnJvbSAnLi4vLi4vdXRpbHMvZXJyb3JzLmpzJ1xuaW1wb3J0IHsgbG9nRXJyb3IgfSBmcm9tICcuLi8uLi91dGlscy9sb2cuanMnXG5pbXBvcnQgeyBjbGVhckFsbENhY2hlcyB9IGZyb20gJy4uLy4uL3V0aWxzL3BsdWdpbnMvY2FjaGVVdGlscy5qcydcbmltcG9ydCB7XG4gIGFkZE1hcmtldHBsYWNlU291cmNlLFxuICBzYXZlTWFya2V0cGxhY2VUb1NldHRpbmdzLFxufSBmcm9tICcuLi8uLi91dGlscy9wbHVnaW5zL21hcmtldHBsYWNlTWFuYWdlci5qcydcbmltcG9ydCB7IHBhcnNlTWFya2V0cGxhY2VJbnB1dCB9IGZyb20gJy4uLy4uL3V0aWxzL3BsdWdpbnMvcGFyc2VNYXJrZXRwbGFjZUlucHV0LmpzJ1xuaW1wb3J0IHR5cGUgeyBWaWV3U3RhdGUgfSBmcm9tICcuL3R5cGVzLmpzJ1xuXG50eXBlIFByb3BzID0ge1xuICBpbnB1dFZhbHVlOiBzdHJpbmdcbiAgc2V0SW5wdXRWYWx1ZTogKHZhbHVlOiBzdHJpbmcpID0+IHZvaWRcbiAgY3Vyc29yT2Zmc2V0OiBudW1iZXJcbiAgc2V0Q3Vyc29yT2Zmc2V0OiAob2Zmc2V0OiBudW1iZXIpID0+IHZvaWRcbiAgZXJyb3I6IHN0cmluZyB8IG51bGxcbiAgc2V0RXJyb3I6IChlcnJvcjogc3RyaW5nIHwgbnVsbCkgPT4gdm9pZFxuICByZXN1bHQ6IHN0cmluZyB8IG51bGxcbiAgc2V0UmVzdWx0OiAocmVzdWx0OiBzdHJpbmcgfCBudWxsKSA9PiB2b2lkXG4gIHNldFZpZXdTdGF0ZTogKHN0YXRlOiBWaWV3U3RhdGUpID0+IHZvaWRcbiAgb25BZGRDb21wbGV0ZT86ICgpID0+IHZvaWQgfCBQcm9taXNlPHZvaWQ+XG4gIGNsaU1vZGU/OiBib29sZWFuXG59XG5cbmV4cG9ydCBmdW5jdGlvbiBBZGRNYXJrZXRwbGFjZSh7XG4gIGlucHV0VmFsdWUsXG4gIHNldElucHV0VmFsdWUsXG4gIGN1cnNvck9mZnNldCxcbiAgc2V0Q3Vyc29yT2Zmc2V0LFxuICBlcnJvcixcbiAgc2V0RXJyb3IsXG4gIHJlc3VsdCxcbiAgc2V0UmVzdWx0LFxuICBzZXRWaWV3U3RhdGUsXG4gIG9uQWRkQ29tcGxldGUsXG4gIGNsaU1vZGUgPSBmYWxzZSxcbn06IFByb3BzKTogUmVhY3QuUmVhY3ROb2RlIHtcbiAgY29uc3QgaGFzQXR0ZW1wdGVkQXV0b0FkZCA9IHVzZVJlZihmYWxzZSlcbiAgY29uc3QgW2lzTG9hZGluZywgc2V0TG9hZGluZ10gPSB1c2VTdGF0ZShmYWxzZSlcbiAgY29uc3QgW3Byb2dyZXNzTWVzc2FnZSwgc2V0UHJvZ3Jlc3NNZXNzYWdlXSA9IHVzZVN0YXRlPHN0cmluZz4oJycpXG5cbiAgY29uc3QgaGFuZGxlQWRkID0gYXN5bmMgKCkgPT4ge1xuICAgIGNvbnN0IGlucHV0ID0gaW5wdXRWYWx1ZS50cmltKClcbiAgICBpZiAoIWlucHV0KSB7XG4gICAgICBzZXRFcnJvcignUGxlYXNlIGVudGVyIGEgbWFya2V0cGxhY2Ugc291cmNlJylcbiAgICAgIHJldHVyblxuICAgIH1cblxuICAgIGNvbnN0IHBhcnNlZCA9IGF3YWl0IHBhcnNlTWFya2V0cGxhY2VJbnB1dChpbnB1dClcbiAgICBpZiAoIXBhcnNlZCkge1xuICAgICAgc2V0RXJyb3IoXG4gICAgICAgICdJbnZhbGlkIG1hcmtldHBsYWNlIHNvdXJjZSBmb3JtYXQuIFRyeTogb3duZXIvcmVwbywgaHR0cHM6Ly8uLi4sIG9yIC4vcGF0aCcsXG4gICAgICApXG4gICAgICByZXR1cm5cbiAgICB9XG5cbiAgICAvLyBDaGVjayBpZiBwYXJzZU1hcmtldHBsYWNlSW5wdXQgcmV0dXJuZWQgYW4gZXJyb3JcbiAgICBpZiAoJ2Vycm9yJyBpbiBwYXJzZWQpIHtcbiAgICAgIHNldEVycm9yKHBhcnNlZC5lcnJvcilcbiAgICAgIHJldHVyblxuICAgIH1cblxuICAgIHNldEVycm9yKG51bGwpXG5cbiAgICB0cnkge1xuICAgICAgc2V0TG9hZGluZyh0cnVlKVxuICAgICAgc2V0UHJvZ3Jlc3NNZXNzYWdlKCcnKVxuICAgICAgY29uc3QgeyBuYW1lLCByZXNvbHZlZFNvdXJjZSB9ID0gYXdhaXQgYWRkTWFya2V0cGxhY2VTb3VyY2UoXG4gICAgICAgIHBhcnNlZCxcbiAgICAgICAgbWVzc2FnZSA9PiB7XG4gICAgICAgICAgc2V0UHJvZ3Jlc3NNZXNzYWdlKG1lc3NhZ2UpXG4gICAgICAgIH0sXG4gICAgICApXG4gICAgICBzYXZlTWFya2V0cGxhY2VUb1NldHRpbmdzKG5hbWUsIHsgc291cmNlOiByZXNvbHZlZFNvdXJjZSB9KVxuICAgICAgY2xlYXJBbGxDYWNoZXMoKVxuXG4gICAgICBsZXQgc291cmNlVHlwZSA9IHBhcnNlZC5zb3VyY2VcbiAgICAgIGlmIChwYXJzZWQuc291cmNlID09PSAnZ2l0aHViJykge1xuICAgICAgICBzb3VyY2VUeXBlID1cbiAgICAgICAgICBwYXJzZWQucmVwbyBhcyBBbmFseXRpY3NNZXRhZGF0YV9JX1ZFUklGSUVEX1RISVNfSVNfTk9UX0NPREVfT1JfRklMRVBBVEhTXG4gICAgICB9XG5cbiAgICAgIGxvZ0V2ZW50KCd0ZW5ndV9tYXJrZXRwbGFjZV9hZGRlZCcsIHtcbiAgICAgICAgc291cmNlX3R5cGU6XG4gICAgICAgICAgc291cmNlVHlwZSBhcyBBbmFseXRpY3NNZXRhZGF0YV9JX1ZFUklGSUVEX1RISVNfSVNfTk9UX0NPREVfT1JfRklMRVBBVEhTLFxuICAgICAgfSlcblxuICAgICAgaWYgKG9uQWRkQ29tcGxldGUpIHtcbiAgICAgICAgYXdhaXQgb25BZGRDb21wbGV0ZSgpXG4gICAgICB9XG5cbiAgICAgIHNldFByb2dyZXNzTWVzc2FnZSgnJylcbiAgICAgIHNldExvYWRpbmcoZmFsc2UpXG5cbiAgICAgIGlmIChjbGlNb2RlKSB7XG4gICAgICAgIC8vIEluIENMSSBtb2RlLCBzZXQgcmVzdWx0IHRvIHRyaWdnZXIgY29tcGxldGlvblxuICAgICAgICBzZXRSZXN1bHQoYFN1Y2Nlc3NmdWxseSBhZGRlZCBtYXJrZXRwbGFjZTogJHtuYW1lfWApXG4gICAgICB9IGVsc2Uge1xuICAgICAgICAvLyBJbiBpbnRlcmFjdGl2ZSBtb2RlLCBzd2l0Y2ggdG8gYnJvd3NlIHZpZXdcbiAgICAgICAgc2V0Vmlld1N0YXRlKHsgdHlwZTogJ2Jyb3dzZS1tYXJrZXRwbGFjZScsIHRhcmdldE1hcmtldHBsYWNlOiBuYW1lIH0pXG4gICAgICB9XG4gICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICBjb25zdCBlcnJvciA9IHRvRXJyb3IoZXJyKVxuICAgICAgbG9nRXJyb3IoZXJyb3IpXG4gICAgICBzZXRFcnJvcihlcnJvci5tZXNzYWdlKVxuICAgICAgc2V0UHJvZ3Jlc3NNZXNzYWdlKCcnKVxuICAgICAgc2V0TG9hZGluZyhmYWxzZSlcblxuICAgICAgaWYgKGNsaU1vZGUpIHtcbiAgICAgICAgLy8gSW4gQ0xJIG1vZGUsIHNldCByZXN1bHQgd2l0aCBlcnJvciB0byB0cmlnZ2VyIGNvbXBsZXRpb25cbiAgICAgICAgc2V0UmVzdWx0KGBFcnJvcjogJHtlcnJvci5tZXNzYWdlfWApXG4gICAgICB9IGVsc2Uge1xuICAgICAgICBzZXRSZXN1bHQobnVsbClcbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICAvLyBBdXRvLWFkZCBpZiBpbnB1dFZhbHVlIGlzIHByb3ZpZGVkXG4gIHVzZUVmZmVjdCgoKSA9PiB7XG4gICAgaWYgKGlucHV0VmFsdWUgJiYgIWhhc0F0dGVtcHRlZEF1dG9BZGQuY3VycmVudCAmJiAhZXJyb3IgJiYgIXJlc3VsdCkge1xuICAgICAgaGFzQXR0ZW1wdGVkQXV0b0FkZC5jdXJyZW50ID0gdHJ1ZVxuICAgICAgdm9pZCBoYW5kbGVBZGQoKVxuICAgIH1cbiAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgcmVhY3QtaG9va3MvZXhoYXVzdGl2ZS1kZXBzXG4gICAgLy8gYmlvbWUtaWdub3JlIGxpbnQvY29ycmVjdG5lc3MvdXNlRXhoYXVzdGl2ZURlcGVuZGVuY2llczogaW50ZW50aW9uYWxcbiAgfSwgW10pIC8vIE9ubHkgcnVuIG9uY2Ugb24gbW91bnRcblxuICByZXR1cm4gKFxuICAgIDxCb3ggZmxleERpcmVjdGlvbj1cImNvbHVtblwiPlxuICAgICAgPEJveCBmbGV4RGlyZWN0aW9uPVwiY29sdW1uXCIgcGFkZGluZ1g9ezF9IGJvcmRlclN0eWxlPVwicm91bmRcIj5cbiAgICAgICAgPEJveCBtYXJnaW5Cb3R0b209ezF9PlxuICAgICAgICAgIDxUZXh0IGJvbGQ+QWRkIE1hcmtldHBsYWNlPC9UZXh0PlxuICAgICAgICA8L0JveD5cbiAgICAgICAgPEJveCBmbGV4RGlyZWN0aW9uPVwiY29sdW1uXCI+XG4gICAgICAgICAgPFRleHQ+RW50ZXIgbWFya2V0cGxhY2Ugc291cmNlOjwvVGV4dD5cbiAgICAgICAgICA8VGV4dCBkaW1Db2xvcj5FeGFtcGxlczo8L1RleHQ+XG4gICAgICAgICAgPFRleHQgZGltQ29sb3I+IMK3IG93bmVyL3JlcG8gKEdpdEh1Yik8L1RleHQ+XG4gICAgICAgICAgPFRleHQgZGltQ29sb3I+IMK3IGdpdEBnaXRodWIuY29tOm93bmVyL3JlcG8uZ2l0IChTU0gpPC9UZXh0PlxuICAgICAgICAgIDxUZXh0IGRpbUNvbG9yPiDCtyBodHRwczovL2V4YW1wbGUuY29tL21hcmtldHBsYWNlLmpzb248L1RleHQ+XG4gICAgICAgICAgPFRleHQgZGltQ29sb3I+IMK3IC4vcGF0aC90by9tYXJrZXRwbGFjZTwvVGV4dD5cbiAgICAgICAgICA8Qm94IG1hcmdpblRvcD17MX0+XG4gICAgICAgICAgICA8VGV4dElucHV0XG4gICAgICAgICAgICAgIHZhbHVlPXtpbnB1dFZhbHVlfVxuICAgICAgICAgICAgICBvbkNoYW5nZT17c2V0SW5wdXRWYWx1ZX1cbiAgICAgICAgICAgICAgb25TdWJtaXQ9e2hhbmRsZUFkZH1cbiAgICAgICAgICAgICAgY29sdW1ucz17ODB9XG4gICAgICAgICAgICAgIGN1cnNvck9mZnNldD17Y3Vyc29yT2Zmc2V0fVxuICAgICAgICAgICAgICBvbkNoYW5nZUN1cnNvck9mZnNldD17c2V0Q3Vyc29yT2Zmc2V0fVxuICAgICAgICAgICAgICBmb2N1c1xuICAgICAgICAgICAgICBzaG93Q3Vyc29yXG4gICAgICAgICAgICAvPlxuICAgICAgICAgIDwvQm94PlxuICAgICAgICA8L0JveD5cbiAgICAgICAge2lzTG9hZGluZyAmJiAoXG4gICAgICAgICAgPEJveCBtYXJnaW5Ub3A9ezF9PlxuICAgICAgICAgICAgPFNwaW5uZXIgLz5cbiAgICAgICAgICAgIDxUZXh0PlxuICAgICAgICAgICAgICB7cHJvZ3Jlc3NNZXNzYWdlIHx8ICdBZGRpbmcgbWFya2V0cGxhY2UgdG8gY29uZmlndXJhdGlvbuKApid9XG4gICAgICAgICAgICA8L1RleHQ+XG4gICAgICAgICAgPC9Cb3g+XG4gICAgICAgICl9XG4gICAgICAgIHtlcnJvciAmJiAoXG4gICAgICAgICAgPEJveCBtYXJnaW5Ub3A9ezF9PlxuICAgICAgICAgICAgPFRleHQgY29sb3I9XCJlcnJvclwiPntlcnJvcn08L1RleHQ+XG4gICAgICAgICAgPC9Cb3g+XG4gICAgICAgICl9XG4gICAgICAgIHtyZXN1bHQgJiYgKFxuICAgICAgICAgIDxCb3ggbWFyZ2luVG9wPXsxfT5cbiAgICAgICAgICAgIDxUZXh0PntyZXN1bHR9PC9UZXh0PlxuICAgICAgICAgIDwvQm94PlxuICAgICAgICApfVxuICAgICAgPC9Cb3g+XG4gICAgICA8Qm94IG1hcmdpbkxlZnQ9ezN9PlxuICAgICAgICA8VGV4dCBkaW1Db2xvciBpdGFsaWM+XG4gICAgICAgICAgPEJ5bGluZT5cbiAgICAgICAgICAgIDxLZXlib2FyZFNob3J0Y3V0SGludCBzaG9ydGN1dD1cIkVudGVyXCIgYWN0aW9uPVwiYWRkXCIgLz5cbiAgICAgICAgICAgIDxDb25maWd1cmFibGVTaG9ydGN1dEhpbnRcbiAgICAgICAgICAgICAgYWN0aW9uPVwiY29uZmlybTpub1wiXG4gICAgICAgICAgICAgIGNvbnRleHQ9XCJTZXR0aW5nc1wiXG4gICAgICAgICAgICAgIGZhbGxiYWNrPVwiRXNjXCJcbiAgICAgICAgICAgICAgZGVzY3JpcHRpb249XCJjYW5jZWxcIlxuICAgICAgICAgICAgLz5cbiAgICAgICAgICA8L0J5bGluZT5cbiAgICAgICAgPC9UZXh0PlxuICAgICAgPC9Cb3g+XG4gICAgPC9Cb3g+XG4gIClcbn1cbiJdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLQSxLQUFLLE1BQU0sT0FBTztBQUM5QixTQUFTQyxTQUFTLEVBQUVDLE1BQU0sRUFBRUMsUUFBUSxRQUFRLE9BQU87QUFDbkQsU0FDRSxLQUFLQywwREFBMEQsRUFDL0RDLFFBQVEsUUFDSCxpQ0FBaUM7QUFDeEMsU0FBU0Msd0JBQXdCLFFBQVEsOENBQThDO0FBQ3ZGLFNBQVNDLE1BQU0sUUFBUSwwQ0FBMEM7QUFDakUsU0FBU0Msb0JBQW9CLFFBQVEsd0RBQXdEO0FBQzdGLFNBQVNDLE9BQU8sUUFBUSw2QkFBNkI7QUFDckQsT0FBT0MsU0FBUyxNQUFNLCtCQUErQjtBQUNyRCxTQUFTQyxHQUFHLEVBQUVDLElBQUksUUFBUSxjQUFjO0FBQ3hDLFNBQVNDLE9BQU8sUUFBUSx1QkFBdUI7QUFDL0MsU0FBU0MsUUFBUSxRQUFRLG9CQUFvQjtBQUM3QyxTQUFTQyxjQUFjLFFBQVEsbUNBQW1DO0FBQ2xFLFNBQ0VDLG9CQUFvQixFQUNwQkMseUJBQXlCLFFBQ3BCLDJDQUEyQztBQUNsRCxTQUFTQyxxQkFBcUIsUUFBUSw4Q0FBOEM7QUFDcEYsY0FBY0MsU0FBUyxRQUFRLFlBQVk7QUFFM0MsS0FBS0MsS0FBSyxHQUFHO0VBQ1hDLFVBQVUsRUFBRSxNQUFNO0VBQ2xCQyxhQUFhLEVBQUUsQ0FBQ0MsS0FBSyxFQUFFLE1BQU0sRUFBRSxHQUFHLElBQUk7RUFDdENDLFlBQVksRUFBRSxNQUFNO0VBQ3BCQyxlQUFlLEVBQUUsQ0FBQ0MsTUFBTSxFQUFFLE1BQU0sRUFBRSxHQUFHLElBQUk7RUFDekNDLEtBQUssRUFBRSxNQUFNLEdBQUcsSUFBSTtFQUNwQkMsUUFBUSxFQUFFLENBQUNELEtBQUssRUFBRSxNQUFNLEdBQUcsSUFBSSxFQUFFLEdBQUcsSUFBSTtFQUN4Q0UsTUFBTSxFQUFFLE1BQU0sR0FBRyxJQUFJO0VBQ3JCQyxTQUFTLEVBQUUsQ0FBQ0QsTUFBTSxFQUFFLE1BQU0sR0FBRyxJQUFJLEVBQUUsR0FBRyxJQUFJO0VBQzFDRSxZQUFZLEVBQUUsQ0FBQ0MsS0FBSyxFQUFFYixTQUFTLEVBQUUsR0FBRyxJQUFJO0VBQ3hDYyxhQUFhLENBQUMsRUFBRSxHQUFHLEdBQUcsSUFBSSxHQUFHQyxPQUFPLENBQUMsSUFBSSxDQUFDO0VBQzFDQyxPQUFPLENBQUMsRUFBRSxPQUFPO0FBQ25CLENBQUM7QUFFRCxPQUFPLFNBQVNDLGNBQWNBLENBQUM7RUFDN0JmLFVBQVU7RUFDVkMsYUFBYTtFQUNiRSxZQUFZO0VBQ1pDLGVBQWU7RUFDZkUsS0FBSztFQUNMQyxRQUFRO0VBQ1JDLE1BQU07RUFDTkMsU0FBUztFQUNUQyxZQUFZO0VBQ1pFLGFBQWE7RUFDYkUsT0FBTyxHQUFHO0FBQ0wsQ0FBTixFQUFFZixLQUFLLENBQUMsRUFBRXBCLEtBQUssQ0FBQ3FDLFNBQVMsQ0FBQztFQUN6QixNQUFNQyxtQkFBbUIsR0FBR3BDLE1BQU0sQ0FBQyxLQUFLLENBQUM7RUFDekMsTUFBTSxDQUFDcUMsU0FBUyxFQUFFQyxVQUFVLENBQUMsR0FBR3JDLFFBQVEsQ0FBQyxLQUFLLENBQUM7RUFDL0MsTUFBTSxDQUFDc0MsZUFBZSxFQUFFQyxrQkFBa0IsQ0FBQyxHQUFHdkMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxDQUFDLEVBQUUsQ0FBQztFQUVsRSxNQUFNd0MsU0FBUyxHQUFHLE1BQUFBLENBQUEsS0FBWTtJQUM1QixNQUFNQyxLQUFLLEdBQUd2QixVQUFVLENBQUN3QixJQUFJLENBQUMsQ0FBQztJQUMvQixJQUFJLENBQUNELEtBQUssRUFBRTtNQUNWaEIsUUFBUSxDQUFDLG1DQUFtQyxDQUFDO01BQzdDO0lBQ0Y7SUFFQSxNQUFNa0IsTUFBTSxHQUFHLE1BQU01QixxQkFBcUIsQ0FBQzBCLEtBQUssQ0FBQztJQUNqRCxJQUFJLENBQUNFLE1BQU0sRUFBRTtNQUNYbEIsUUFBUSxDQUNOLDRFQUNGLENBQUM7TUFDRDtJQUNGOztJQUVBO0lBQ0EsSUFBSSxPQUFPLElBQUlrQixNQUFNLEVBQUU7TUFDckJsQixRQUFRLENBQUNrQixNQUFNLENBQUNuQixLQUFLLENBQUM7TUFDdEI7SUFDRjtJQUVBQyxRQUFRLENBQUMsSUFBSSxDQUFDO0lBRWQsSUFBSTtNQUNGWSxVQUFVLENBQUMsSUFBSSxDQUFDO01BQ2hCRSxrQkFBa0IsQ0FBQyxFQUFFLENBQUM7TUFDdEIsTUFBTTtRQUFFSyxJQUFJO1FBQUVDO01BQWUsQ0FBQyxHQUFHLE1BQU1oQyxvQkFBb0IsQ0FDekQ4QixNQUFNLEVBQ05HLE9BQU8sSUFBSTtRQUNUUCxrQkFBa0IsQ0FBQ08sT0FBTyxDQUFDO01BQzdCLENBQ0YsQ0FBQztNQUNEaEMseUJBQXlCLENBQUM4QixJQUFJLEVBQUU7UUFBRUcsTUFBTSxFQUFFRjtNQUFlLENBQUMsQ0FBQztNQUMzRGpDLGNBQWMsQ0FBQyxDQUFDO01BRWhCLElBQUlvQyxVQUFVLEdBQUdMLE1BQU0sQ0FBQ0ksTUFBTTtNQUM5QixJQUFJSixNQUFNLENBQUNJLE1BQU0sS0FBSyxRQUFRLEVBQUU7UUFDOUJDLFVBQVUsR0FDUkwsTUFBTSxDQUFDTSxJQUFJLElBQUloRCwwREFBMEQ7TUFDN0U7TUFFQUMsUUFBUSxDQUFDLHlCQUF5QixFQUFFO1FBQ2xDZ0QsV0FBVyxFQUNURixVQUFVLElBQUkvQztNQUNsQixDQUFDLENBQUM7TUFFRixJQUFJNkIsYUFBYSxFQUFFO1FBQ2pCLE1BQU1BLGFBQWEsQ0FBQyxDQUFDO01BQ3ZCO01BRUFTLGtCQUFrQixDQUFDLEVBQUUsQ0FBQztNQUN0QkYsVUFBVSxDQUFDLEtBQUssQ0FBQztNQUVqQixJQUFJTCxPQUFPLEVBQUU7UUFDWDtRQUNBTCxTQUFTLENBQUMsbUNBQW1DaUIsSUFBSSxFQUFFLENBQUM7TUFDdEQsQ0FBQyxNQUFNO1FBQ0w7UUFDQWhCLFlBQVksQ0FBQztVQUFFdUIsSUFBSSxFQUFFLG9CQUFvQjtVQUFFQyxpQkFBaUIsRUFBRVI7UUFBSyxDQUFDLENBQUM7TUFDdkU7SUFDRixDQUFDLENBQUMsT0FBT1MsR0FBRyxFQUFFO01BQ1osTUFBTTdCLEtBQUssR0FBR2QsT0FBTyxDQUFDMkMsR0FBRyxDQUFDO01BQzFCMUMsUUFBUSxDQUFDYSxLQUFLLENBQUM7TUFDZkMsUUFBUSxDQUFDRCxLQUFLLENBQUNzQixPQUFPLENBQUM7TUFDdkJQLGtCQUFrQixDQUFDLEVBQUUsQ0FBQztNQUN0QkYsVUFBVSxDQUFDLEtBQUssQ0FBQztNQUVqQixJQUFJTCxPQUFPLEVBQUU7UUFDWDtRQUNBTCxTQUFTLENBQUMsVUFBVUgsS0FBSyxDQUFDc0IsT0FBTyxFQUFFLENBQUM7TUFDdEMsQ0FBQyxNQUFNO1FBQ0xuQixTQUFTLENBQUMsSUFBSSxDQUFDO01BQ2pCO0lBQ0Y7RUFDRixDQUFDOztFQUVEO0VBQ0E3QixTQUFTLENBQUMsTUFBTTtJQUNkLElBQUlvQixVQUFVLElBQUksQ0FBQ2lCLG1CQUFtQixDQUFDbUIsT0FBTyxJQUFJLENBQUM5QixLQUFLLElBQUksQ0FBQ0UsTUFBTSxFQUFFO01BQ25FUyxtQkFBbUIsQ0FBQ21CLE9BQU8sR0FBRyxJQUFJO01BQ2xDLEtBQUtkLFNBQVMsQ0FBQyxDQUFDO0lBQ2xCO0lBQ0E7SUFDQTtFQUNGLENBQUMsRUFBRSxFQUFFLENBQUMsRUFBQzs7RUFFUCxPQUNFLENBQUMsR0FBRyxDQUFDLGFBQWEsQ0FBQyxRQUFRO0FBQy9CLE1BQU0sQ0FBQyxHQUFHLENBQUMsYUFBYSxDQUFDLFFBQVEsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxXQUFXLENBQUMsT0FBTztBQUNsRSxRQUFRLENBQUMsR0FBRyxDQUFDLFlBQVksQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUM3QixVQUFVLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxlQUFlLEVBQUUsSUFBSTtBQUMxQyxRQUFRLEVBQUUsR0FBRztBQUNiLFFBQVEsQ0FBQyxHQUFHLENBQUMsYUFBYSxDQUFDLFFBQVE7QUFDbkMsVUFBVSxDQUFDLElBQUksQ0FBQyx5QkFBeUIsRUFBRSxJQUFJO0FBQy9DLFVBQVUsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLFNBQVMsRUFBRSxJQUFJO0FBQ3hDLFVBQVUsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLHNCQUFzQixFQUFFLElBQUk7QUFDckQsVUFBVSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsc0NBQXNDLEVBQUUsSUFBSTtBQUNyRSxVQUFVLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyx1Q0FBdUMsRUFBRSxJQUFJO0FBQ3RFLFVBQVUsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLHdCQUF3QixFQUFFLElBQUk7QUFDdkQsVUFBVSxDQUFDLEdBQUcsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDNUIsWUFBWSxDQUFDLFNBQVMsQ0FDUixLQUFLLENBQUMsQ0FBQ3RCLFVBQVUsQ0FBQyxDQUNsQixRQUFRLENBQUMsQ0FBQ0MsYUFBYSxDQUFDLENBQ3hCLFFBQVEsQ0FBQyxDQUFDcUIsU0FBUyxDQUFDLENBQ3BCLE9BQU8sQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUNaLFlBQVksQ0FBQyxDQUFDbkIsWUFBWSxDQUFDLENBQzNCLG9CQUFvQixDQUFDLENBQUNDLGVBQWUsQ0FBQyxDQUN0QyxLQUFLLENBQ0wsVUFBVTtBQUV4QixVQUFVLEVBQUUsR0FBRztBQUNmLFFBQVEsRUFBRSxHQUFHO0FBQ2IsUUFBUSxDQUFDYyxTQUFTLElBQ1IsQ0FBQyxHQUFHLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQzVCLFlBQVksQ0FBQyxPQUFPO0FBQ3BCLFlBQVksQ0FBQyxJQUFJO0FBQ2pCLGNBQWMsQ0FBQ0UsZUFBZSxJQUFJLHNDQUFzQztBQUN4RSxZQUFZLEVBQUUsSUFBSTtBQUNsQixVQUFVLEVBQUUsR0FBRyxDQUNOO0FBQ1QsUUFBUSxDQUFDZCxLQUFLLElBQ0osQ0FBQyxHQUFHLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQzVCLFlBQVksQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDQSxLQUFLLENBQUMsRUFBRSxJQUFJO0FBQzdDLFVBQVUsRUFBRSxHQUFHLENBQ047QUFDVCxRQUFRLENBQUNFLE1BQU0sSUFDTCxDQUFDLEdBQUcsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDNUIsWUFBWSxDQUFDLElBQUksQ0FBQyxDQUFDQSxNQUFNLENBQUMsRUFBRSxJQUFJO0FBQ2hDLFVBQVUsRUFBRSxHQUFHLENBQ047QUFDVCxNQUFNLEVBQUUsR0FBRztBQUNYLE1BQU0sQ0FBQyxHQUFHLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ3pCLFFBQVEsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLE1BQU07QUFDN0IsVUFBVSxDQUFDLE1BQU07QUFDakIsWUFBWSxDQUFDLG9CQUFvQixDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDLEtBQUs7QUFDL0QsWUFBWSxDQUFDLHdCQUF3QixDQUN2QixNQUFNLENBQUMsWUFBWSxDQUNuQixPQUFPLENBQUMsVUFBVSxDQUNsQixRQUFRLENBQUMsS0FBSyxDQUNkLFdBQVcsQ0FBQyxRQUFRO0FBRWxDLFVBQVUsRUFBRSxNQUFNO0FBQ2xCLFFBQVEsRUFBRSxJQUFJO0FBQ2QsTUFBTSxFQUFFLEdBQUc7QUFDWCxJQUFJLEVBQUUsR0FBRyxDQUFDO0FBRVYiLCJpZ25vcmVMaXN0IjpbXX0=
\ No newline at end of file
diff --git a/src/commands/plugin/BrowseMarketplace.tsx b/src/commands/plugin/BrowseMarketplace.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..4d1a24d42f5a02b172863c454ee4dcd36ab7b389
--- /dev/null
+++ b/src/commands/plugin/BrowseMarketplace.tsx
@@ -0,0 +1,802 @@
+import figures from 'figures';
+import * as React from 'react';
+import { useEffect, useState } from 'react';
+import { ConfigurableShortcutHint } from '../../components/ConfigurableShortcutHint.js';
+import { Byline } from '../../components/design-system/Byline.js';
+import { Box, Text } from '../../ink.js';
+import { useKeybinding, useKeybindings } from '../../keybindings/useKeybinding.js';
+import type { LoadedPlugin } from '../../types/plugin.js';
+import { count } from '../../utils/array.js';
+import { openBrowser } from '../../utils/browser.js';
+import { logForDebugging } from '../../utils/debug.js';
+import { errorMessage } from '../../utils/errors.js';
+import { clearAllCaches } from '../../utils/plugins/cacheUtils.js';
+import { formatInstallCount, getInstallCounts } from '../../utils/plugins/installCounts.js';
+import { isPluginGloballyInstalled, isPluginInstalled } from '../../utils/plugins/installedPluginsManager.js';
+import { createPluginId, formatFailureDetails, formatMarketplaceLoadingErrors, getMarketplaceSourceDisplay, loadMarketplacesWithGracefulDegradation } from '../../utils/plugins/marketplaceHelpers.js';
+import { getMarketplace, loadKnownMarketplacesConfig } from '../../utils/plugins/marketplaceManager.js';
+import { OFFICIAL_MARKETPLACE_NAME } from '../../utils/plugins/officialMarketplace.js';
+import { installPluginFromMarketplace } from '../../utils/plugins/pluginInstallationHelpers.js';
+import { isPluginBlockedByPolicy } from '../../utils/plugins/pluginPolicy.js';
+import { plural } from '../../utils/stringUtils.js';
+import { truncateToWidth } from '../../utils/truncate.js';
+import { findPluginOptionsTarget, PluginOptionsFlow } from './PluginOptionsFlow.js';
+import { PluginTrustWarning } from './PluginTrustWarning.js';
+import { buildPluginDetailsMenuOptions, extractGitHubRepo, type InstallablePlugin, PluginSelectionKeyHint } from './pluginDetailsHelpers.js';
+import type { ViewState as ParentViewState } from './types.js';
+import { usePagination } from './usePagination.js';
+type Props = {
+ error: string | null;
+ setError: (error: string | null) => void;
+ result: string | null;
+ setResult: (result: string | null) => void;
+ setViewState: (state: ParentViewState) => void;
+ onInstallComplete?: () => void | Promise;
+ targetMarketplace?: string;
+ targetPlugin?: string;
+};
+type ViewState = 'marketplace-list' | 'plugin-list' | 'plugin-details' | {
+ type: 'plugin-options';
+ plugin: LoadedPlugin;
+ pluginId: string;
+};
+type MarketplaceInfo = {
+ name: string;
+ totalPlugins: number;
+ installedCount: number;
+ source?: string;
+};
+export function BrowseMarketplace({
+ error,
+ setError,
+ result: _result,
+ setResult,
+ setViewState: setParentViewState,
+ onInstallComplete,
+ targetMarketplace,
+ targetPlugin
+}: Props): React.ReactNode {
+ // View state
+ const [viewState, setViewState] = useState('marketplace-list');
+ const [selectedMarketplace, setSelectedMarketplace] = useState(null);
+ const [selectedPlugin, setSelectedPlugin] = useState(null);
+
+ // Data state
+ const [marketplaces, setMarketplaces] = useState([]);
+ const [availablePlugins, setAvailablePlugins] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [installCounts, setInstallCounts] = useState