claude-code as channel model, container trust/permissions fixes, terminal cwd fix

- add claude-code as virtual model in channel messaging (telegram/discord/whatsapp)
- new send-claude-code.ts: docker exec claude -p with session resumption
- route claude-code model in sendAndAwait before Pi pipeline
- append claude-code to listPiModels output
- fix container .claude mount (rw for sub-mounts), hooks format (matcher-based)
- pre-seed hasTrustDialogAccepted and bypassPermissions in container settings
- git init in entrypoint to skip workspace trust prompt
- fix ~/~ double-tilde in CommandTerminalWrapper cwd resolution
- remove --continue from claude-code panel command

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-02 20:00:45 +00:00
co-authored by Claude Opus 4.6
parent 192ae63c0e
commit 0068244356
20 changed files with 1168 additions and 73 deletions
@@ -9,9 +9,10 @@ type CommandTerminalWrapperProps = {
panelId: string;
command: string;
statePrefix: string;
onPanelRefresh?: () => void;
};
export const CommandTerminalWrapper = ({ panelId, command, statePrefix }: CommandTerminalWrapperProps) => {
export const CommandTerminalWrapper = ({ panelId, command, statePrefix, onPanelRefresh }: CommandTerminalWrapperProps) => {
const { dashboardId, cwd } = useWorkspace();
const stateKey = dashboardId ? `ws-${statePrefix}-${dashboardId}` : `ws-${statePrefix}-default`;
const { value: terminals, setValue: setTerminals } = useDashboardState<Record<string, string>>(stateKey, EMPTY_TERMINALS);
@@ -37,8 +38,8 @@ export const CommandTerminalWrapper = ({ panelId, command, statePrefix }: Comman
if (!sessionId) return null;
const cwdPath = cwd && cwd !== '~' ? `~/${cwd.replace(/^\//, '')}` : null;
const cwdPath = cwd && cwd !== '~' ? (cwd.startsWith('~') ? cwd : `~/${cwd.replace(/^\//, '')}`) : null;
const fullCommand = cwdPath ? `cd ${cwdPath} && ${command}` : command;
return <TerminalView className="h-full w-full p-2" sessionId={sessionId} cwd={cwd} initialInput={fullCommand} />;
return <TerminalView className="h-full w-full p-2" sessionId={sessionId} cwd={cwd} initialInput={fullCommand} onPanelRefresh={onPanelRefresh} />;
};
@@ -1,4 +1,4 @@
import { TerminalSquare, Monitor, Columns2, PenLine } from 'lucide-react';
import { TerminalSquare, Monitor, Columns2, PenLine, Sparkles } from 'lucide-react';
import { useWorkspace } from '../../components/Workspace';
import { useAuth } from 'hooks/useAuth';
import { useTerminalMode } from './useTerminalMode';
@@ -61,3 +61,14 @@ export const NvimHeader = () => {
</>
);
};
export const ClaudeCodeHeader = () => {
const { cwd } = useWorkspace();
return (
<>
<Sparkles className="h-3.5 w-3.5 shrink-0" />
<span className="text-xs font-medium shrink-0">Claude Code</span>
<span className="text-[10px] font-mono truncate opacity-60">{cwd}</span>
</>
);
};
@@ -29,6 +29,7 @@ export type TerminalViewProps = {
onExit?: () => void;
onCommandDone?: (exitCode: number, output: string) => void;
onDisconnect?: () => void;
onPanelRefresh?: () => void;
};
const DEFAULT_THEME: Required<TerminalTheme> = {
@@ -69,6 +70,7 @@ export const TerminalView = ({
onExit,
onCommandDone,
onDisconnect,
onPanelRefresh,
}: TerminalViewProps) => {
const containerRef = useRef<HTMLDivElement>(null);
const termRef = useRef<XTerm | null>(null);
@@ -78,6 +80,7 @@ export const TerminalView = ({
const onExitRef = useRef<TerminalViewProps['onExit']>(onExit);
const onCommandDoneRef = useRef<TerminalViewProps['onCommandDone']>(onCommandDone);
const onDisconnectRef = useRef<TerminalViewProps['onDisconnect']>(onDisconnect);
const onPanelRefreshRef = useRef<TerminalViewProps['onPanelRefresh']>(onPanelRefresh);
const commandRef = useRef(command);
const initialInputRef = useRef(initialInput);
@@ -85,6 +88,7 @@ export const TerminalView = ({
onExitRef.current = onExit;
onCommandDoneRef.current = onCommandDone;
onDisconnectRef.current = onDisconnect;
onPanelRefreshRef.current = onPanelRefresh;
commandRef.current = command;
initialInputRef.current = initialInput;
@@ -188,6 +192,8 @@ export const TerminalView = ({
onExitRef.current?.();
} else if (msg.type === 'detached') {
term.write('\r\n[Session taken over]\r\n');
} else if (msg.type === 'panel-refresh') {
onPanelRefreshRef.current?.();
}
} catch {
// ignore
@@ -1,9 +1,11 @@
import { useCallback } from 'react';
import type { AppRegistryMeta } from '../../AppRegistry';
import { TerminalSquare, Monitor, Columns2, PenLine } from 'lucide-react';
import { TerminalSquare, Monitor, Columns2, PenLine, Sparkles } from 'lucide-react';
import { usePanelChannel } from 'hooks/usePanelChannel';
import { TerminalWrapper } from './TerminalWrapper';
import { HostTerminalWrapper } from './HostTerminalWrapper';
import { CommandTerminalWrapper } from './CommandTerminalWrapper';
import { TerminalHeader, HostTerminalHeader, TmuxHeader, NvimHeader } from './Headers';
import { TerminalHeader, HostTerminalHeader, TmuxHeader, NvimHeader, ClaudeCodeHeader } from './Headers';
export { TerminalView, type TerminalViewProps } from './Terminal';
@@ -15,6 +17,25 @@ const NvimWrapper = ({ panelId }: { panelId: string }) => (
<CommandTerminalWrapper panelId={panelId} command="nvim" statePrefix="nvim" />
);
const ClaudeCodeWrapper = ({ panelId }: { panelId: string }) => {
const [, setPreviewRefresh] = usePanelChannel<number>('preview:refresh', 0);
const [, setFilesRefresh] = usePanelChannel<number>('files:refresh-signal', 0);
const onPanelRefresh = useCallback(() => {
setPreviewRefresh(Date.now());
setFilesRefresh(Date.now());
}, [setPreviewRefresh, setFilesRefresh]);
return (
<CommandTerminalWrapper
panelId={panelId}
command="claude --dangerously-skip-permissions"
statePrefix="claude-code"
onPanelRefresh={onPanelRefresh}
/>
);
};
export const appRegistryMetas: AppRegistryMeta[] = [
{
key: 'officerdev/terminal',
@@ -45,4 +66,11 @@ export const appRegistryMetas: AppRegistryMeta[] = [
component: NvimWrapper,
header: NvimHeader,
},
{
key: 'officerdev/claude-code',
name: 'Claude Code',
icon: Sparkles,
component: ClaudeCodeWrapper,
header: ClaudeCodeHeader,
},
];