Commit Graph
141 Commits
Author SHA1 Message Date
l0ng-ai 27483e893d feat(ui): collapse the New Tab menu's shell list to three by frecency (#960)
The + menu listed every shell the machine reports (nine on a stock macOS box)
above the SSH hosts. The Local section now names the default shell, always
first, then only shells that have actually been opened, by frecency, three rows
at most, the same way the SSH section caps its hosts.

Shell usage is recorded in a new `shell_frecency` config map, keyed by the
inventory label, bumped from both the menu row and the palette. Every shell is
now a palette command titled "Shell: {label}" (same word in every locale), and
an "Other Shells…" row opens the palette pre-filtered to them. That row is
hidden when the menu already names the whole inventory. Running a shell command
from the palette respects the ⌥/Alt split modifier like the menu row.
2026-09-25 16:44:32 +08:00
l0ng-ai bd0dd22bfa feat(tabs): hibernate a tab to free its memory and wake it later (#954)
A tab can be put to sleep from its context menu or the command palette:
its panes are stopped (screens kept on disk), the tab keeps its place in
the sidebar, and selecting it wakes it through the same restore a reboot
runs, resuming a supported agent's session. The sleep mark lives on the
machine tree, so it survives app and daemon restarts.

Closes #762
2026-09-25 16:41:12 +08:00
l0ng-ai 9f034558c0 feat(ssh): switch a port forward off without losing its rule (#953)
A forward could only be removed, so pointing one local port at another
remote target meant deleting the rule and retyping the other one (#439).

Forward rules gain an `enabled` flag (serde default on, so saved profiles
and older peers keep every rule live) and the daemon a SetForwardEnabled
op for panes and workspaces. Off releases the listener and keeps the
entry; on rebinds it from the rule it was made from, and is refused by
name when another switched-on forward of the same owner holds the port.
The Ports panel and the Settings rules editor each get a switch; a
switch flipped in the panel on a rule from a saved host is written back
to that host.

Closes #439
2026-09-25 16:38:00 +08:00
l0ng-ai de15f9b0ab feat(ssh): keep saved hosts in servers.json and add an SSH tab title setting (#952)
Saved SSH hosts and their usage counts move out of config.json into
servers.json beside it, so config.json can be synced between machines
without carrying a server list (#911). An older config.json is split on
first load: servers.json is written first (0600), then only the two keys
are removed from config.json, leaving every other key as it was. When
both files hold hosts, servers.json wins and the stale copy falls out of
config.json at its next save. A servers.json that does not parse is kept
aside and blocks saves, as config.json does; hand edits hot-reload.

Settings -> Window & Tabs -> SSH tab title (`ssh_tab_title`) pins an SSH
tab to the profile name (saved host name, ~/.ssh/config alias, or the
address typed for a quick connect) or the hostname, on the OSC title's
rung of the existing label ladder: a renamed tab still wins, OSC titles
are still tracked, local panes are untouched (#726).
2026-09-25 16:34:38 +08:00
l0ng-ai 458c923aaa docs: lead with the tour video instead of the old hero screenshot
The README now opens on an animated WebP of the one-minute tour (GitHub
won't play an mp4 inline), linking the full-quality mp4. The docs home
page autoplays the mp4 in the hero slot. The old hero screenshot and the
separate tour poster block are gone.
2026-09-24 08:28:23 +08:00
l0ng-ai fed4aec170 docs: replace placeholder screenshots and add a one-minute tour video
Every placeholder frame in docs/ now shows a real capture of the current
build: 20 screenshots plus two short looping clips (prompt editor, pane
drag). The README and the docs home page link a one-minute tour covering
agent status across repos, one agent driving another through the CLI,
the prompt editor, diffs, pane dragging, and sessions surviving a quit.
2026-09-24 08:18:14 +08:00
l0ng-ai 0988c57d5c fix(ui): restore the sidebar's rounded-rectangle corners
The v5 restyle turned the tab rows, the search field and the workspace
switcher into full pills, which read too round at this density. Put
back CARD_RADIUS on the rows, rounded_lg on the search field and
rounded_md on the switcher (and its rename field), keeping the 28px
row height. PILL_RADIUS has no users left and goes with them.
2026-09-23 22:02:45 +08:00
l0ng-ai f1648117b7 feat(ui): restyle the workspace after the v5 design
- Rails share the window fill and are separated by a hairline only;
  the current tab is a neutral selected step plus a semibold title
  instead of the blue navigation wash (the unused role is removed).
- Default Light theme takes ink #0F1419 and accent #1F6BF0; the caret
  follows the accent.
- Sidebar: 28px pill rows with 18px avatars (two-line rows keep card
  corners), pill search field and workspace switcher, group headings
  in body ink with the chevron shown only when folded.
- Right panel: word tabs (Info / Changes / Files) with an underline
  bar on a closing hairline. The Changes count is dropped before any
  label is cut, and the panel floor grows with the measured labels so
  the chrome tiles are never pushed off the edge.
- Headings are no longer uppercased; popovers use a 10px radius.
- docs/design-system.md updated to match.
2026-09-23 21:35:28 +08:00
l0ng-ai 800c6bf724 Merge pull request #933 from l0ng-ai/feat/comfortable-ui-surfaces
feat(ui): refine panel readability and workspace surfaces
2026-09-23 16:59:54 +08:00
l0ng-ai 7666e2fd83 Merge pull request #941 from l0ng-ai/feat/852-portable-mode
feat(windows): portable mode keeps data beside the executable
2026-09-23 16:58:12 +08:00
l0ng-ai 0b53ff16ef Merge pull request #940 from l0ng-ai/feat/767-prompt-input-new-line
feat(prompt-editor): start input on its own row when the prompt leaves too little room
2026-09-23 16:58:08 +08:00
l0ng-ai 386ccc3d81 Merge pull request #939 from l0ng-ai/feat/720-font-smoothing
feat(fonts): make macOS stroke thickening configurable
2026-09-23 16:58:03 +08:00
l0ng-ai 296a8a3c3f Merge pull request #938 from l0ng-ai/feat/760-stable-workspace-numbers
feat(workspaces): number workspaces in a stable order (#760)
2026-09-23 16:57:59 +08:00
l0ng-ai da6c8624de Merge pull request #937 from l0ng-ai/feat/723-remote-tree-download
feat(file-tree): download a remote file from the tree's context menu
2026-09-23 16:57:54 +08:00
l0ng-ai a012321542 Merge pull request #936 from l0ng-ai/feat/892-741-codebuddy-cursor-hooks
feat(agents): CodeBuddy CLI support and Cursor CLI hooks
2026-09-23 16:57:50 +08:00
l0ng-ai 2eb1a79d76 Merge pull request #935 from l0ng-ai/feat/754-dock-preview-wrap-actions
feat(editor): add ToggleDocumentPreview / ToggleDocumentWrap actions
2026-09-23 16:57:45 +08:00
l0ng-ai 2aa28d3982 Merge pull request #934 from l0ng-ai/feat/867-direct-tab-cycling
feat(tabs): step to the next/previous tab without the switcher (#867)
2026-09-23 16:57:41 +08:00
l0ng-ai 3d4c158278 fix(agents): open Cursor turns on beforeSubmitPrompt, count tool calls as activity
The interactive cursor-agent TUI fires beforeSubmitPrompt and stop; only
print mode (-p) lacks them. Mapping postToolUse to prompt-submit froze the
activity counter (opportunistic git refresh, agents --changed), shortened
turn timing and hid tool-less turns. Map it to tool-complete instead.
2026-09-23 16:54:06 +08:00
l0ng-ai 75480cac05 fix(ui): improve panel readability at narrow sizes 2026-09-23 16:51:59 +08:00
l0ng-ai f5a97ed68c feat(windows): portable mode keeps data beside the executable (#852)
A portable ZIP install whose folder has both the .tty7-portable marker
and a data\ folder next to tty7-app.exe now uses <exe dir>\data as its
config directory, so settings, sessions, scrollback and the daemon's
socket travel with the program. --config-dir and TTY7_CONFIG_DIR still
win. The folder is an explicit opt-in because the marker has shipped in
every portable ZIP since the in-app updater arrived: keying on it alone
would move existing users onto an empty directory at their next update.

The portable directory is not the machine's config dir, so a portable
copy never adopts the installed tty7's legacy tree. An updater test pins
that data\ is not a managed root and survives an in-place update.
2026-09-23 15:44:27 +08:00
l0ng-ai 9a01a12852 docs(prompt): describe the input moving below a long prompt (#767) 2026-09-23 15:43:05 +08:00
l0ng-ai 57ca21dd68 feat(workspaces): number workspaces in a stable order (#760)
SelectWorkspace1-9 and the Window menu numbered workspaces by most
recent use, so switching to one moved it to slot 1 and reshuffled the
rest. Number them by the order this client first had them instead
(the append-only views list), skipping synced remote references until
they are opened; opening one moves it to the end so it takes the next
free number. The switcher keeps its MRU list and shows each row's
number.
2026-09-23 15:38:28 +08:00
l0ng-ai 592058acc7 feat(fonts): make macOS stroke thickening configurable (#720)
Add a macOS-only `font_thicken` key (default true) and a Settings row
under Appearance > Terminal text. When off, AppleFontSmoothing is pinned
to 0 in this process's NSArgumentDomain before gpui's text system first
reads it, so glyphs render at the face's own weight. The volatile domain
is in-memory only: nothing is persisted and no other app is affected.
gpui caches the preference in a OnceLock, so a change applies after a
restart; no gpui fork change is needed.
2026-09-23 15:23:10 +08:00
l0ng-ai 43d2b902bb feat(file-tree): download a remote file from the tree's context menu
A remote workspace's Files tree took uploads by drag-and-drop but had no
way to bring a file back. Right-click a file -> Download now reads it
through Host::read_file and saves it into this machine's Downloads
folder, named and numbered the way the SFTP panel's Download does.

Capped at the same ~63 MB as uploads (one control frame); an oversized
file is refused up front with the limit and nothing is transferred.
Local trees keep Reveal in that slot; folders are not offered.

Closes #723
2026-09-23 15:22:57 +08:00
l0ng-ai fe7b6e0b76 feat(agents): wire Cursor CLI (cursor-agent) into agent hooks (#741)
Cursor's ~/.cursor/hooks.json is a flat hook map — `command` directly on
each entry under `hooks.<event>` — so it reuses the flat writer Crush
introduced. Two things are Cursor-specific:

- The file needs `"version": 1` at its root or Cursor ignores it. tty7
  now writes it when it creates the file or finds it missing, never
  overwrites a version the user set, and reports a versionless file that
  holds our hooks as Outdated so refresh repairs it.
- Payloads name the session `conversation_id` (only sessionStart repeats
  it as `session_id`), and most events carry `workspace_roots` instead of
  `cwd`. Both are now read as aliases, which is what Copy Session ID and
  resume were missing.

Events: sessionStart, beforeSubmitPrompt, postToolUse, stop, sessionEnd.
cursor-agent does not fire beforeSubmitPrompt today (a known gap on
Cursor's side), so postToolUse also reports prompt-submit: the first tool
call opens the turn and Cursor's stop, which the CLI does fire, closes it.
A turn with no tool calls never shows as working. None of Cursor's
permission hooks are installed, since those would block on the empty
stdout tty7's hook prints.
2026-09-23 15:21:34 +08:00
l0ng-ai 962fd87ea5 feat(tabs): step to the next/previous tab without the switcher (#867)
Add SelectNextTab / SelectPrevTab, which move to the neighbouring tab in
the order the strip or sidebar shows them, wrapping, with no popup.
Defaults: Cmd+Shift+] / Cmd+Shift+[ on macOS, Ctrl+PgDn / Ctrl+PgUp
elsewhere. The tmux preset's prefix n / p now bind these (tmux
next-window semantics) instead of the MRU switcher, which never
auto-committed there.

NextTab / PrevTab keep their config names and Ctrl+Tab, but are labelled
Recent Tab Switcher on the Keybindings page; the palette's Next/Previous
Tab entries now show the SelectNextTab chord they actually run.
2026-09-23 15:20:34 +08:00
l0ng-ai 9f842975ce feat(editor): add ToggleDocumentPreview / ToggleDocumentWrap actions (#754)
The code panel's Preview/Edit and Wrap buttons only toggled state in their
click handlers, so there was no way to reach them from the keyboard. Register
both as actions, unbound by default like ToggleDocumentFill and the
DocumentWidth* actions, list them in the command palette and the Keybindings
page, and route the buttons through the same methods.

Instead of config keys for the initial state, the last-used state is
remembered (editor_soft_wrap / editor_markdown_preview), the way diff_view
and scm_graph_expanded already are. A file opened at a line target always
opens as source so the cursor is visible.
2026-09-23 15:18:58 +08:00
l0ng-ai 0d0dc597ee feat(agents): add CodeBuddy CLI integration (#892)
CodeBuddy Code takes Claude Code's hooks as-is: the same nested
`hooks.<Event>[].hooks[{type, command}]` shape in ~/.codebuddy/settings.json
(or $CODEBUDDY_CONFIG_DIR/settings.json), the same event names, and the same
session_id/cwd payload fields. So it rides the shared hook-map installer and
needs no payload aliases.

The event table follows Qoder rather than Claude: CodeBuddy has a
first-class PermissionRequest and Elicitation, so it gets no Notification
hook, and StopFailure ends a turn like Stop. CodeBuddy also emits
SessionStart with source "compact" mid-turn, which is filtered out the same
way Qoder's is so the pane does not drop back to idle.

Detection covers all three npm bins (codebuddy, codebuddy-code, cbc).
Resume is `codebuddy --resume <id>` and fork appends --fork-session; stale
session and worktree flags are dropped from the replayed launch argv, and
--no-session-persistence disables both commands.

Icon, en/ja/zh names and search keywords, and docs are updated.
2026-09-23 15:04:06 +08:00
l0ng-ai d474e4ade3 feat(sftp): open the Files panel at the shell's current directory
Reopening the SFTP panel went back to the last browsed folder; a fresh
open now prefers the pane shell's cwd, falling back to the last browsed
folder, then the login directory. Following a pane switch with the panel
still open keeps each pane's last browsed folder.

Also pin the test config dir in the sftp gpui harness so filtered runs
do not trip the real-config guard, and fix docs/remote/sftp.mdx, which
said the panel opens on the remote home directory.

Closes #826
2026-09-23 15:03:04 +08:00
l0ng-ai ae89ff0110 Merge main and preserve SSH credential saving in settings workflows 2026-09-23 08:54:49 +08:00
l0ng-ai a26dab532a Align settings terminology and documentation navigation 2026-09-23 08:47:15 +08:00
l0ng-ai acb8075c64 Unify tty7 server terminology across settings and restart flows 2026-09-23 08:35:45 +08:00
l0ng-ai ec08949625 Keep About focused on app information and update status 2026-09-23 08:30:53 +08:00
l0ng-ai fd9f589c80 Merge pull request #914 from l0ng-ai/feat/right-panel-refresh
style(ui): unify native colors, icons, and sidebar hierarchy
2026-09-23 00:39:03 +08:00
l0ng-ai 4ebc1423c1 Merge pull request #906 from l0ng-ai/fix/901-alt-digit-bindings
fix(keybindings): give Alt+1..9 a way out, and stop one bad line resetting the file (#901)
2026-09-23 00:34:23 +08:00
l0ng-ai a4dbdc7bf1 Redesign settings navigation, search, and editing workflows 2026-09-23 00:24:38 +08:00
l0ng-ai 770f19151a docs(windows): every release through 26.9.2 needed the VC++ redistributable (#902)
The install guide said only releases up to 26.8.2 needed it, but 26.8.3
through 26.9.2 were built the same way and import VCRUNTIME140.dll too.
Also correct the CI comment: cc-built code follows +crt-static by itself;
what can bring the import back is a prebuilt native library or RUSTFLAGS.
2026-09-22 23:40:34 +08:00
l0ng-ai 6846571142 Refine native UI hierarchy, icons, and sidebar chrome 2026-09-22 23:20:31 +08:00
l0ng-ai a645fd33f5 fix(windows): link the Visual C++ runtime statically (#902)
Every Windows binary tty7 has ever shipped imports `VCRUNTIME140.dll`. That
file is not part of Windows; it arrives with the "Visual C++ 2015-2022
Redistributable", which Visual Studio, the GitHub runners and most developer
machines install as a side effect of something else. On a machine that has
never installed it the loader fails before `main` — no window, no log, no
crash report, just

    The code execution cannot proceed because VCRUNTIME140.dll was not found.

which is how 26.8.2 failed winget's install validation
(microsoft/winget-pkgs#415841).

Confirmed against the installed 26.x `tty7-app.exe` with
`dumpbin /dependents`: `VCRUNTIME140.dll` plus nine `api-ms-win-crt-*`
entries, and the same in `tty7.exe` and `tty7-updater.exe`. The UCRT half is
in-box from Windows 10 on and never was the problem; VCRUNTIME140 is the one
piece that has to come from the redistributable. The bundled ConPTY pair is
already CRT-static, so it was never implicated — only our own three binaries
are.

`-C target-feature=+crt-static` for the MSVC targets, in `.cargo/config.toml`
rather than in the release workflow, so CI's Windows `build & test` job
compiles under the same flag a release does and a dependency that cannot link
statically fails a pull request instead of a tag. The alternative — declaring
`Microsoft.VCRedist.2015+.x64` in the winget manifest, or shipping the DLLs
beside the exe — was rejected: it leaves the portable zip, the GitHub release
and every non-winget install path broken, this repository publishes no winget
manifest to carry the declaration, and the winget PR shows the declared
dependency did not actually resolve the failure. Static linking removes the
requirement instead of documenting it.

The regression is invisible to everyone who could catch it, because every
machine that builds tty7 has the redistributable, so
`assert-no-vcruntime.ps1` reads the PE import and delay-load tables directly
(no `dumpbin`, which would re-introduce the same "my machine has Visual
Studio" assumption) and fails on any VC++ redistributable import. It runs in
CI on the Windows debug build and, via `verify-windows-package.ps1`, over
both shipped payloads in release and nightly.

Verified on Windows 11 x86_64 with MSVC 14.44: a full
`cargo build --release --locked --target x86_64-pc-windows-msvc` links
cleanly, and `dumpbin /dependents` on the resulting `tty7-app.exe` shows 29
imports, all in-box — no `VCRUNTIME140.dll` and no `api-ms-win-crt-*` at all.
Same for `tty7.exe` and the `--features updater` `tty7-updater.exe`.
`cargo test --release -p tty7-core` under the flag: 1223 passed, 4 failed,
those four being the `remote_link`/`router` tests that fail on a clean tree
on this machine too.

Fixes #902

Claude-Session: https://claude.ai/code/session_01JRqYZ9E153WpSHGS2AW3BM
2026-09-21 22:44:15 +08:00
l0ng-ai a723e71c9a fix(keybindings): give Alt+1..9 a way out, and stop one bad line resetting the file (#901)
Alt+1..9 are vim's tab keys, and tty7 takes all nine for Go to Tab.
Two things stood between the reporter and getting them back.

**Nothing in the app could leave an action unbound.** Backspace on a
Keybindings row that has recorded nothing *reset* the row — dropped the
override so the action gets its shipped chord back. On a row nobody had
overridden, which is every row the first time it is looked at, that is a
no-op: pressing it over Alt+1 left Alt+1 sitting exactly where it was,
which reads as the default restoring itself. `config.json` has spelled
"no chord" as `[]` since #868, but no gesture wrote it.

Backspace now writes that empty list. The row falls to `—` and grows the
**Reset** button every overridden row has, which is the way back to the
default. The capture hint names the key, and the docs say what it is for.

**A keybinding line serde could not read failed the whole `Config`.**
`keybindings` is a hand-edited map and was strict, so `"ActivateTab1":
null` — or a number, or an object — quarantined `config.json` and started
the app on built-in defaults. Every rebinding in the file then read as
its shipped default, and the next settings write persisted those
defaults over what the user had written. It now reads one entry at a
time, like every other hand-edited nested key here: the lines that name
a shortcut bind, a line that does not is logged and skipped.

Tests, each failing on the unfixed code:

- `ui::app::keybinding_gpui_tests::backspace_on_a_row_unbinds_the_action_rather_than_restoring_its_default`
- `core::config::tests::a_keybinding_line_that_cannot_be_read_does_not_take_the_config_with_it`

and `ui::keymap::gpui_tests::alt_digits_can_be_moved_off_the_tab_actions_for_good`
pins the merge and a save/reload round trip: a list replaces the shipped
Alt+1, `[]` leaves nothing, and neither comes back after a restart.

Fixes #901

Claude-Session: https://claude.ai/code/session_01JRqYZ9E153WpSHGS2AW3BM
2026-09-21 22:42:45 +08:00
l0ng-ai 7c0e339be4 docs(keymap): terminal shortcuts keep their chord; upgrading configs (#868)
A chord added in config wins a tie only against another app-wide action.
Copy, paste, find, clear scrollback and insert newline are bound in the
Terminal context, which is deeper, so they keep their chord while a
terminal is focused. Also tells people upgrading that a shortcut recorded
in Settings up to 26.9.2 was saved as a string. It now adds instead of
replacing, and a list brings back the old meaning.

Claude-Session: https://claude.ai/code/session_01JRqYZ9E153WpSHGS2AW3BM
2026-09-15 23:01:51 +08:00
l0ng-ai 436ea04f33 fix(keymap): a keybinding in config adds a chord instead of replacing the default (#868)
effective_bindings kept one chord per action and set_binding overwrote that
slot, so "NextTab": "cmd-shift-]" silently took Ctrl+Tab away.

A string in keybindings now adds a chord beside the action's default (or
preset) chord; "" still unbinds, as configs and the docs already rely on; a
list is the exact chord set, [] unbinds. Configured chords are installed after
every shipped one, so a chord the user names wins a tie with another action's
default. The Settings page lists every chord of an action, and recording a
shortcut writes the list shape (it sets the binding) and takes only the stolen
chord from the action that had it.

Claude-Session: https://claude.ai/code/session_01JRqYZ9E153WpSHGS2AW3BM
2026-09-14 21:48:19 +08:00
l0ng-ai 85b4450756 fix(agents): keep Crush's PreToolUse from opening a turn it can never close
Crush fires only PreToolUse, and it mapped to prompt-submit. With no Stop
behind it the pane stayed on Working until Crush exited: every close asked
whether to cut Crush's work short, the tray and dot stayed on working, and
fork warned mid-turn. Map it to tool-complete, which still records
session_id/cwd for resume and bumps activity without moving the status.
2026-09-14 18:31:59 +08:00
Fabrice Aneche c2d2db2cfc added support for Crush agent 2026-09-11 21:56:22 -04:00
l0ng-ai 2ef652436b Merge pull request #856 from l0ng-ai/fix/wait-free-remote-panes
fix(cli): answer `wait --until free` on remote and SSH panes (#840)
2026-09-10 15:14:22 +08:00
l0ng-ai a88c49904c Merge branch 'main' into fix/wait-free-remote-panes
#731's `PortProbe` and #774's `TerminalModes` both landed on main while this was
open, each adding a field beside one this branch adds — `PaneProcs.probe` next to
`context`, `PaneState.modes` next to `remote_prompt_seen` — so every struct
literal for the two conflicted without either side being wrong. Both fields are
kept everywhere. `procinfo::snapshot` is #731's rewrite, with `context: None`
moved onto the `finish` helper that now builds the reply; the comment saying why
only the pane can fill it comes along.

Claude-Session: https://claude.ai/code/session_01JRqYZ9E153WpSHGS2AW3BM
2026-09-10 15:01:01 +08:00
l0ng-ai a70d58b05f Merge pull request #825 from l0ng-ai/docs/nested-shell-cwd-698
docs: why a nested shell stops reporting its cwd (#698)
2026-09-10 14:47:44 +08:00
l0ng-ai 76c607a8a9 Merge branch 'main' into fix/wait-free-remote-panes
#711 landed on main while this was open and added two `ShellState` literals to
pane.rs's tests, which `mark_at_prompt` makes incomplete — the merge did not
compile on any target even though both sides did. The marks those two tests
stand up say "at a prompt", which is exactly the reading the new field carries,
so both get `mark_at_prompt: true` and the replay gate they exercise is
untouched. `cli_e2e`'s test table took both sides' new entries.

Claude-Session: https://claude.ai/code/session_01JRqYZ9E153WpSHGS2AW3BM
2026-09-10 14:25:06 +08:00
l0ng-ai 2ec515c206 Merge pull request #831 from l0ng-ai/fix/716-remaining-three
fix: the three remaining items of #716 — the misdirected rename, the missing adopt verb, the absent tree backup
2026-09-10 14:19:07 +08:00
l0ng-ai ecd5c31d39 docs: name the settings field that picks a local pane's shell
The local remedy sent the reader to `config.json` for something the app has
a field for — **Settings → Terminal → Shell → Program**, with the
**Arguments** box beside it that turns the injection off when filled.

Claude-Session: https://claude.ai/code/session_01JRqYZ9E153WpSHGS2AW3BM
2026-09-10 14:00:53 +08:00