SLOPSHOPPER

open-latest

Claude の最新の回答を latest.md に書き出し、プロンプト上のボタンでエディタで開く

bandtoastprocess
v0.2.0MITupdated 2026-10-06stakiran/open-latest
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · open-latest
› fix the failing auth test and add an audit log call ⏺ Read(src/auth.ts) ⎿ Read 6 lines ⏺ Update(src/auth.ts) ⎿ Added 2 lines, removed 1 line ⏺ 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 open copy ⟨Claude Code's own drawing⟩ ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Band
open copy ⟨Claude Code's own drawing⟩
README

open-latest

<img width="634" height="263" alt="Image" src="https://github.com/user-attachments/assets/695fd5d3-02a7-428b-85c9-b6ddf4392f84" />

Claude Code の最新の回答を ~/.claude/latest.md に書き出し、プロンプトの上に出る open ボタンからエディタで開けるようにするプラグインです。

長い回答をターミナルで読むのがつらいとき、使い慣れたエディタでじっくり読めます。

動作

  • ターンが終わるたびに、Claude の最終回答を %USERPROFILE%\.claude\latest.md に上書き保存します
  • サブエージェントの回答、中断されたターン、空の回答は保存しません
  • UTF-8 で保存します
  • latest.md があるとき、プロンプトの上に open と copy の 2 つのボタンが出ます
  • open ボタンを押すと、.md に関連付けられたアプリで latest.md を開きます
  • copy ボタンを押すと、最新の回答をクリップボードにコピーします

動作環境

  • Windows(rundll32 で関連付けられたアプリを起動するため)
  • Claude Code(ターミナル版)
  • .md ファイルが好きなエディタに関連付けられていること

導入方法

ステップ 1: マーケットプレイスを追加する

このリポジトリを「プラグインの入手元」として登録します。これだけではまだプラグインは動きません。最初の 1 回だけ必要です。入手元の指定方法は 3 通りあります。

GitHub のリポジトリ(owner/repo):

claude plugin marketplace add stakiran/open-latest

git の URL:

claude plugin marketplace add https://github.com/stakiran/open-latest.git

ローカルのディレクトリ(clone 済みのフォルダなど):

claude plugin marketplace add D:\path\to\open-latest

ステップ 2: プラグインをインストールする

3 通りあります。

~/.claude/settings.json:

claude plugin install open-latest@open-latest --scope user

そのディレクトリの .claude/settings.json:

claude plugin install open-latest@open-latest --scope project

そのディレクトリの .claude/settings.local.json:

claude plugin install open-latest@open-latest --scope local

アンインストールするときは /plugin のメニューから open-latest を削除してください。

導入はしないが試したい

インストールせずに、そのセッションだけ読み込んで試せます。Claude Code を閉じれば元に戻ります。

git clone https://github.com/stakiran/open-latest.git
claude --plugin-dir ./open-latest

起動したら何か質問して回答を待ち、プロンプトの上に open と copy のボタンが出ることを確かめてください。

ライセンス

MIT License

Source 1 files
hooks/register.js 72 lines
1// latest.md が存在するか(ボタンを出すかどうか)
2let hasLatest = false
3
4async function latestPath($) {
5  const home = await $.env.get('USERPROFILE')
6  return home + '\\.claude\\latest.md'
7}
8
9export function register(on) {
10  on('session.start', async ($, e, next) => {
11    hasLatest = await $.fs.exists(await latestPath($))
12    return next(e)
13  })
14
15  // ターン終了時に最終回答を latest.md に上書き保存
16  on('turn.complete', async ($, e, next) => {
17    // サブエージェントのターン、中断されたターン、空の回答は無視
18    if (e.agentId || e.isAborted || !e.answer) return next(e)
19    await $.fs.write(await latestPath($), e.answer)
20    // コピー用。$.fs には読み込みがないので、セッションをまたいで残る $.store にも持っておく
21    await $.store.set('latest', e.answer).catch(() => $.store.delete('latest'))
22    hasLatest = true
23    $.ui.invalidate('ui.render')
24    return next(e)
25  })
26
27  // プロンプト上の帯にボタンを描く
28  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
29    // 他の mod が帯に描いたものは残す
30    const others = await next(e)
31    if (!hasLatest) return others
32
33    const { Box, Button } = $.ui.resolve(e)
34    const openButton = Button({
35      key: 'open-latest',
36      label: 'open',
37      plain: true,
38      onPress: async () => {
39        try {
40          // 関連付けられたアプリ(ShellExecute)で開く。rundll32 は起動後すぐ終わるので待たされない
41          const file = await latestPath($)
42          const r = await $.process.run(['rundll32', 'url.dll,FileProtocolHandler', file])
43          if (r.exitCode !== 0) $.ui.toast('エディタを開けませんでした: ' + (r.stderr || r.exitCode))
44        } catch (err) {
45          $.ui.toast('エディタを開けませんでした: ' + err)
46        }
47      },
48    })
49    const copyButton = Button({
50      key: 'copy-latest',
51      label: 'copy',
52      plain: true,
53      onPress: async press => {
54        const text = await $.store.get('latest')
55        if (typeof text !== 'string') return $.ui.toast('コピーできる回答がありません')
56        const r = await $.ui.copy({ text, surface: press.surface })
57        $.ui.toast(r.isCopied ? '回答をコピーしました' : 'コピーできませんでした: ' + r.reason)
58      },
59    })
60    const buttons = Box({
61      flexDirection: 'row',
62      columnGap: 2,
63      children: [openButton, copyButton],
64    })
65
66    return Box({
67      flexDirection: 'column',
68      children: others ? [buttons, others] : [buttons],
69    })
70  })
71}
72