SLOPSHOPPER

file-view

Click a file path under a Read, Edit or Write row to show that file's contents in a pane beside the transcript.

newpanerows
★ 12v0.1.0no licenseupdated 2026-09-30ushironoko/dotfiles/claude/.claude/skills/file-view
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · file-view
│ ┃ file-view ✕ › fix the failing auth test and add an audit log call │ ┃ file-view │ ┃ Click a file path under a tool row to show ⏺ Read(src/auth.ts) │ ┃ it here. ⎿ Read 6 lines │ ⟨Claude Code's own drawing⟩ │ ⧉ /work/app/src/auth.ts │ ⏺ Bash(bun test) │ ⎿ 3 pass, 1 fail │ │ ● Done. refresh now rejects expired claims and logs an audit event. │ │ ✻ Worked for 42s · done 4:20 PM │ │ │ │ ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Pane · file-view
file-view Click a file path under a tool row to show it here.
Tool row
⟨Claude Code's own drawing⟩ ⧉ /work/app/src/auth.ts
README

Dotfiles

ushironoko's dotfiles management system.

Setup

# Initial setup
git clone https://github.com/ushironoko/dotfiles.git
cd dotfiles
./init.sh

# After setup, the dotfiles command is available:
dotfiles install    # Install symlinks
dotfiles list       # List managed files
dotfiles restore    # Restore from backup
dotfiles doctor     # Check environment

# Future updates with ghq (after initial setup):
ghq get ushironoko/dotfiles
cd $(ghq root)/github.com/ushironoko/dotfiles

Commands

dotfiles install

Creates symbolic links from repository to system locations.

  • -d, --dryRun: Preview changes
  • -f, --force: Force overwrite
  • -s, --select: Interactive selection
  • -v, --verbose: Detailed output

dotfiles list

Shows all managed dotfiles and their status.

  • -v, --verbose: Detailed information

dotfiles restore

Restores files from backup.

  • -b, --backup <timestamp>: Specific backup
  • -l, --list: List available backups
  • -v, --verbose: Detailed output

dotfiles doctor

Diagnoses environment issues.

  • -c, --check <categories>: Check specific areas (environment,conflicts,ghq,config,mcp)
  • -v, --verbose: Detailed diagnostics

Troubleshooting

Quick Diagnostics

If you're having issues with the initial setup, run the diagnostic tool:

./init.sh --check

This will show:

  • Binary locations (mise, bun, node)
  • Command availability in PATH
  • mise installation status
  • Installed tools
  • Recommendations for fixing issues

Common Issues

Run dotfiles doctor to diagnose issues. Common fixes:

# If mise commands are not found after installation
# Make sure ~/.local/bin is in your PATH and restart shell
export PATH="$HOME/.local/bin:$PATH"
exec $SHELL

# Remove conflicting Bun installation
rm -rf ~/.bun

# Fix PATH permanently
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(mise activate bash)"' >> ~/.bashrc
exec $SHELL

# Migrate old repos to ghq
./scripts/migrate-to-ghq.sh --dry-run
./scripts/migrate-to-ghq.sh --symlink

Configuration

Edit dotfiles.config.ts to manage your files:

  • file: Single file symlink
  • directory: Entire directory symlink
  • selective: Specific files with permissions

Codex harness

The versioned Codex harness installs global instructions, native custom agents, lifecycle hooks, command restrictions, and the shared Claude skills alongside ~/.codex/config.toml. Each component is linked selectively so Codex runtime state and bundled skills remain intact. After hook changes, review and trust them with /hooks in Codex.

Live config (~/.codex/config.toml)

~/.codex/config.toml is symlinked to codex/config.toml, but codex rewrites that file at runtime, filling it with machine-local state — [projects."<path>"] trust levels (absolute paths, including private/client repo names), [mcp_servers.*] tables wired to Codex.app paths, [marketplaces.*] sources, the notify helper path, per-repo [desktop...perPath] prefs, and [hooks.state] approval hashes. A git clean filter (codex-scrub, codex/scrub-config.awk) drops anything carrying a quoted absolute path, every [projects.*]/[mcp_servers.*] table, and the complete [hooks.state] tree at git add time. The path rule remains content-based so unknown machine state is scrubbed by default; URLs and relative paths are kept. The working tree keeps the live file untouched, so codex keeps functioning.

The filter driver lives in .git/config (never committed), so it must be registered once per clone:

bun run setup:git-filters   # also run automatically by init.sh and run-all

init.sh runs it on initial setup and bun run run-all runs it before every pre-commit check, so under normal use no manual step is needed. If you commit codex/config.toml on a fresh clone without it, trust state would leak — the filter is set to required = true to fail loudly if the scrubber ever errors.

Source 1 files
hooks/register.tsx 192 lines
1import type { ElementTable, On } from "claude-code";
2
3const PANE_ID = "file-view";
4const MAX_SOURCE_CHARS = 10000;
5const FILE_TOOLS: ReadonlySet<string> = new Set([
6  "Read",
7  "Edit",
8  "Write",
9  "MultiEdit",
10  "NotebookEdit",
11]);
12
13type Viewed =
14  | { kind: "loading"; path: string }
15  | { kind: "text"; path: string; source: string; isTruncated: boolean }
16  | { kind: "error"; path: string; message: string };
17
18interface Engine {
19  read: (path: string) => Promise<string>;
20  open: (title: string) => Promise<unknown>;
21  redraw: () => void;
22}
23
24type BodyKit = Pick<ElementTable<"terminal">, "Box" | "Text" | "Code">;
25
26const filePathOf = (input: unknown): string | null => {
27  if (typeof input !== "object" || input === null) {
28    return null;
29  }
30
31  const record = input as Record<string, unknown>;
32  const path = record.file_path ?? record.notebook_path;
33
34  return typeof path === "string" && path !== "" ? path : null;
35};
36
37const basenameOf = (path: string): string => {
38  const trimmed = path.endsWith("/") ? path.slice(0, -1) : path;
39
40  return trimmed.slice(trimmed.lastIndexOf("/") + 1) || path;
41};
42
43const isDrawable = (char: string): boolean => {
44  const code = char.codePointAt(0) ?? 0;
45
46  return code === 9 || code === 10 || (code >= 32 && code !== 127);
47};
48
49const drawableOf = (text: string): string =>
50  [...text.replaceAll("\r\n", "\n")].filter(isDrawable).join("");
51
52const boundedOf = (text: string): { source: string; isTruncated: boolean } => {
53  if (text.length <= MAX_SOURCE_CHARS) {
54    return { source: text, isTruncated: false };
55  }
56
57  const cut = text.lastIndexOf("\n", MAX_SOURCE_CHARS);
58
59  return {
60    source: text.slice(0, cut > 0 ? cut : MAX_SOURCE_CHARS - 1),
61    isTruncated: true,
62  };
63};
64
65const messageOf = (error: unknown): string =>
66  error instanceof Error ? error.message : String(error);
67
68const loadedOf = async (engine: Engine, path: string): Promise<Viewed> => {
69  try {
70    const raw = await engine.read(path);
71
72    if (raw.includes("\u0000")) {
73      return { kind: "error", path, message: "Binary file" };
74    }
75
76    return {
77      kind: "text",
78      path,
79      ...boundedOf(drawableOf(raw.slice(0, MAX_SOURCE_CHARS * 2))),
80    };
81  } catch (error) {
82    return { kind: "error", path, message: messageOf(error) };
83  }
84};
85
86const bodyOf = ({ Box, Text, Code }: BodyKit, shown: Viewed | null) => {
87  if (shown === null) {
88    return (
89      <Text dimColor>Click a file path under a tool row to show it here.</Text>
90    );
91  }
92
93  switch (shown.kind) {
94    case "loading": {
95      return <Text dimColor>Loading…</Text>;
96    }
97    case "error": {
98      return <Text color="red">{shown.message}</Text>;
99    }
100    case "text": {
101      return (
102        <Box flexDirection="column">
103          <Code source={shown.source} path={shown.path} startLine={1} />
104          {shown.isTruncated ? (
105            <Text dimColor>… truncated at {MAX_SOURCE_CHARS} characters</Text>
106          ) : null}
107        </Box>
108      );
109    }
110  }
111};
112
113export const register = (on: On) => {
114  let viewed: Viewed | null = null;
115  let generation = 0;
116
117  const show = async (engine: Engine, path: string) => {
118    generation += 1;
119    const current = generation;
120
121    viewed = { kind: "loading", path };
122    await engine.open(basenameOf(path));
123    engine.redraw();
124
125    const loaded = await loadedOf(engine, path);
126
127    if (current === generation) {
128      viewed = loaded;
129      engine.redraw();
130    }
131  };
132
133  on("ui.render", { component: "ToolUse" }, async ($, e, next) => {
134    const drawn = await next(e);
135    const path = FILE_TOOLS.has(e.props.tool)
136      ? filePathOf(e.props.input)
137      : null;
138
139    if (path === null || e.surface !== "terminal") {
140      return drawn;
141    }
142
143    const { Box, Button } = $.ui.resolve(e);
144
145    const engine: Engine = {
146      read: (file) => $.fs.read(file),
147      open: (title) => $.ui.open({ id: PANE_ID, title }),
148      redraw: () => $.ui.invalidate("ui.render"),
149    };
150
151    return (
152      <Box flexDirection="column">
153        {drawn}
154        <Box paddingLeft={2}>
155          <Button
156            key={`open:${e.requestId}`}
157            plain
158            dimColor
159            label={`⧉ ${path}`}
160            onPress={() => void show(engine, path).catch(() => undefined)}
161          />
162        </Box>
163      </Box>
164    );
165  });
166
167  on("ui.render", { component: "Pane" }, async ($, e, next) => {
168    if (e.requestId !== PANE_ID || e.surface !== "terminal") {
169      return next(e);
170    }
171
172    const { Box, Text, Code } = $.ui.resolve(e);
173    const shown = viewed;
174
175    return (
176      <Box flexDirection="column" paddingRight={1}>
177        <Text bold wrap="truncate-start">
178          {shown?.path ?? "file-view"}
179        </Text>
180        {bodyOf({ Box, Text, Code }, shown)}
181      </Box>
182    );
183  });
184
185  on("ui.close", { id: PANE_ID }, (_, e, next) => {
186    viewed = null;
187    generation += 1;
188
189    return next(e);
190  });
191};
192