Repository navigation
Add inbox control and read-only mode - #27
Merged
Merged
Conversation
`line --read-only COMMAND` or LINE_CLI_READ_ONLY=1 refuses send, react, unsend, login, logout, auth migrate, and update installs. The refusal comes right after option parsing, so help still works, and before any prompt, stdin, attachment, lock, credential access, request sequence, or network request. It uses the new JSON error code read_only at stage preparation, and message actions report not_attempted. Global options are now parsed by one shared function in any order, each at most once. An unrecognized LINE_CLI_READ_ONLY value fails every command instead of being ignored, and --read-only=false cannot turn the environment switch off. As defense in depth, session.Manager.ReadOnly makes MutateContext, ReserveSequence, and both login paths fail with the same typed error, so a command that forgets its gate still cannot change LINE.
`line read CHAT [--message ID]` marks a chat as read; others in the chat see "Read". It is the only command that calls sendChatChecked. History, watch, download, members, chats, and pickers stay receipt-free, and an AST regression test keeps it that way. Static evidence from the public LINE Chrome extension 3.7.2 (recorded in docs/protocol/endpoints/read-receipts.md) shows Chrome sends a nonzero incrementing request sequence with [reqSeq, chatMid, lastMessageId, 0]. The CLI reserves a persisted sequence, attempts once, and records a typed outcome. The old helper that hardcoded sequence 0 now requires one. Without --message the CLI picks the newest message from someone else among the latest 50, skipping SKIP_BADGE_COUNT messages like Chrome. It neither decrypts nor fetches keys. With --message it verifies the ID with the bounded action lookup and refuses your own messages. Guided mode lists unread chats first and asks before sending. --read-only blocks it. Also records the static evidence for group invitations used next.
`line chats --unread` keeps chats whose unread count, as reported by LINE, is above zero. The filter runs on the client after fetching, because the server's unreadOnly option has no protocol evidence yet. Missing or malformed counts are excluded rather than treated as unread. It combines with --search, --limit, --all, --show-ids, --json, and --details. --limit applies after filtering, and the legacy JSON row shape is unchanged. Listing unread chats never sends a read receipt.
`line invites` lists pending group invitations from getAllChatMids with best-effort names from getChats. If names can't be loaded, invitations are still listed, with the name unavailable. `line invites accept|reject GROUP_ID` takes a full group ID and confirms it is pending before reserving a persisted request sequence. Each change is attempted once and records a typed accept_invitation or reject_invitation outcome. In a terminal, `line invites` lets you choose an invitation, then accept, reject, or skip. Accepting never registers or fetches a group key and never sends a read receipt, matching the Chrome extension call sites. The inviter isn't shown because no request the CLI makes returns it. --read-only blocks accept and reject but still lists invitations. Adds strict context variants for getAllChatMids, acceptChatInvitation, and rejectChatInvitation to pkg/line and session.API.
`line doctor [--offline] [--json]` runs read-only checks with stable IDs: version (and latest release unless --offline), install (every line on PATH and which one runs), storage, session, line_auth (one profile read, never a new login), e2ee_keys (count only), clock (skew against the LINE response Date header), and watch (lock held or free, checkpoint present). Each check reports pass, warn, fail, or skipped, and every non-pass result has a next step. It exits 1 only when a check fails. The report is meant for GitHub issues. It never includes tokens, keys, MIDs, names, email addresses, messages, or server responses, and it shows paths inside the home folder as ~/... It works logged out, with headless storage, and in --read-only mode. Supporting changes: pkg/line records the gateway Date header for clock checks, session.ErrWatchBusy identifies a running watcher (same message), and update.Service.Inspect reports the installation method offline.
`line completion bash|zsh|fish|powershell` prints a static script for commands, subcommands, options, and fixed values such as reactions and --error-format. All four scripts come from one spec in Go, and the command reads no session and makes no network requests. Drift tests compare the spec with every command's --help (both directions) and with the command list in `line help`, and check that each script covers each flag. The bash and zsh scripts are also run in tests on macOS and Linux.
--help, -h, and the new `line help COMMAND` print to stdout and exit 0, so `line send --help | less` works. A shared usage printer replaces Go's flag dump. It spells flags --flag, groups them by purpose (for send: message, target, delivery, output), and adds examples to every command. logout and auth now explain what they do. Errors are one line on stderr with a pointer back to help instead of a usage dump. Unknown commands and options suggest the closest match (`line chat` -> line chats, --limt -> --limit), global options given after the command get a hint to move them, and option values are never echoed. JSON error codes and exit codes are unchanged.
In a terminal, a chat name that matches no chat exactly, or several, now opens a chooser of chats and contacts whose names contain it (case-insensitive). The user's pick is the explicit selection. The chooser runs without the session lock, and the account is checked again before the command continues. Without a terminal (scripts, --json, --stdin), the command still fails, but the error lists up to five similar names with full IDs. A single similar match is never chosen automatically, so sends and other changes still need a full ID, a unique exact name, or a choice the user made. The JSON error code is unchanged.
When stdout is a terminal and --json is not given, `line watch` prints one line per message: time, chat, sender, and text or attachment type. Chat and sender names are looked up once and cached, with IDs as the fallback, and remote text is sanitized. Other events are hidden unless --verbose is given. Hidden events still advance the resume position but don't count toward --limit. Piped or redirected output and --json keep the existing NDJSON stream byte for byte. The watcher gains an optional Emit hook. When it is set, session state is reloaded after output, so a token refresh during a name lookup is never overwritten by the checkpoint save.
Move Unreleased into [0.6.0] with upgrade notes for --read-only and LINE_CLI_READ_ONLY, explicit read receipts through `line read`, readable watch output in a terminal, and help on stdout. Add the new commands to the README command tables and automation sections. Align the Japanese, Thai, and Traditional Chinese READMEs and guides: new sections for read-only mode, marking chats read, group invitations, doctor, and shell completion, with the English anchors kept. Update the unread filter, chat chooser, readable watch, help, timeout table, JSON tables, troubleshooting, and limitations. The release checklist and test notes now describe this candidate.
The test passed --timeout 1ns and expected the deadline to fire before the command finished. On Windows CI the coarse timer let the command complete first, so it failed. Now one case uses an already-expired context, and the other checks the --timeout flag with a lock hook that waits until the deadline has fired. Both still assert that no read receipt is sent.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The v0.6.0 "inbox control and agent safety" release: commands for everyday inbox work, plus a read-only switch so scripts and agents can't change anything.
--read-onlybefore the command, orLINE_CLI_READ_ONLY=1, refuses send, read, react, unsend, invites accept/reject, login, logout, auth migrate, and update installs. It stops before any prompt, stdin, lock, credential access, request sequence, or network call, using the new JSON coderead_only.session.Manager.ReadOnlyadds a second check on mutations, sequence reservation, and both login paths, so a command that forgets its own check still can't change LINE. An unrecognized env value fails every command instead of being ignored.line read CHAT [--message ID]sends a read receipt. It is now the only path tosendChatChecked, and an AST test enforces that. Without--message, it marks read up to the newest message from someone else. Your own messages are refused, matching Chrome.line chats --unreadfilters on your machine. Chats with unknown or malformed counts are excluded, and the legacy JSON shape is unchanged.line invites [accept|reject GROUP_ID]lists pending group invitations and changes one after confirming it is pending. Accepting never registers or fetches a group key.line doctor [--offline] [--json]runs eight read-only checks: version, install, storage, session, line_auth, e2ee_keys, clock, and watch. Each non-passing check has a next step. Output contains no tokens, keys, MIDs, names, email addresses, or messages, and home paths are shown as~/….line completion bash|zsh|fish|powershellprints static scripts generated from one spec.line help COMMANDworks, and help uses grouped--flaglists with examples. Typos get one-line "did you mean" errors. In a terminal, a name that doesn't match exactly opens a chooser; scripts get an error listing up to five candidate IDs.watchprints readable lines in a terminal.[0.6.0]with upgrade notes. The JSON reference is updated, and the ja/th/zh-TW READMEs and guides are aligned with English anchors kept.Protocol evidence
docs/protocol/endpoints/read-receipts.mdandchat-invitations.mdrecord a static read of the public LINE Chrome extension 3.7.2. Its SHA-256 matches the package cited in the existing protocol docs. No login, capture, or live call was involved.sendChatCheckedas[reqSeq, chatMid, lastMessageId, 0].reqSeqcomes from a nonzero, incrementing 31-bit counter, so the persisted-sequence rule applies as is.{reqSeq, chatMid}struct. Neither is followed by a key or receipt call in Chrome.getChatsdoesn't return who sent an invitation, soline invitesdoesn't show an inviter.For reviewers
watchattached to a terminal is now readable text; piped output and--jsonare unchanged byte for byte. Scripts that runwatchattached to a terminal need--json. The changelog has upgrade notes for both.operation_failed; only its message now lists candidates. Switching that toinvalid_argumentsis an open question.fault.Mutationoutcome. Nothing retries a mutation automatically.pkg/line.Client.ResponseClockrecords the gatewayDateheader.session.ErrWatchBusyreplaces the plain watch-busy error, with the same message.update.Service.Inspectis the offline part ofCheck.SendChatCheckedthat hardcoded sequence 0 now requires a sequence.Validation
./build.sh,go test -race ./internal/... ./cmd/line ./pkg/line/... ./pkg/e2ee,goimports,go vet,staticcheck, andgit diff --check, before every commit.go test -count=1 -timeout=20m ./pkg ./pkg/ltsm../scripts/check-toolchain.sh../scripts/security-check.sh ./bin/line: govulncheck v1.9.0 found 0 reachable vulnerabilities in source or binary.go.mod/go.sumare unchanged from main.sh -n scripts/install-release.sh.line doctorwas not run against a real Keychain.