Repository navigation
feat(status): add tdc status show, set and clear - #77
Conversation
|
@doistbot /review |
doistbot
left a comment
There was a problem hiding this comment.
Adds a tdc status command for showing, setting, and clearing your shared status, backed by the new users.setStatus()/users.clearStatus() SDK methods, with --emoji, --until/--for expiry options, notification pausing, and a Status: line in tdc user.
Few things worth tightening:
tdc status set "Focus" --for "" --pause-notificationscurrently slips past validation and pauses notifications indefinitely — check option presence with!== undefined(for--until,--for, and their mutual-exclusion check) so explicitly empty values reach validation and get rejected.
I also included a few optional follow-up notes in the details below.
Optional follow-up notes (7)
src/lib/duration.ts:1:
UNIT_MSredefines the same millisecond constants thatsrc/lib/dates.tsalready computes (MINUTE,HOUR,DAY). Export those constants fromdates.ts(or move them into a shared time-unit module) and reuse them here instead of maintaining duplicate values that can drift.src/commands/status/index.ts:28: The help text says "local time without an offset", but
parseDatefalls through tonew Date(dateStr), and ECMAScript parses a date-only string like--until 2026-10-20as UTC midnight, not local time — a user at UTC-8 gets an expiry 8 hours earlier than the docs imply. Either extend parsing so date-only values use local midnight, or adjust the wording to say date-only values are interpreted as UTC.src/index.ts:57:
statusnever renders markdown (show/set/clear only print plain text or JSON), but it isn't innoMarkdownCommands, so every plain-texttdc statusrun blocks onpreloadMarkdown()before parsing. Add'status'to thenoMarkdownCommandsset insrc/index.tsto skip that startup cost.src/commands/status/status.test.ts:43:
vi.useFakeTimers()is never undone. AddafterEach(() => vi.useRealTimers())(importingafterEach) so the suite doesn't leave fake timers enabled once it finishes.src/lib/dates.test.tsalready follows this cleanup pattern for the same setup.src/commands/status/status.test.ts:100: This test verifies the computed
activeUntilsent to the SDK, but no test assertstdc status set's human-readable output. The PR's test-plan case (--for 2h --pause-notifications) should print the expiry andnotifications paused; have thesetStatusmock return a status withactiveUntilandeffects, then assert the logged text containsuntilandnotifications paused.src/commands/user.ts:40: The new
tdc userStatus:line has no test coverage. Add a case insrc/commands/user.test.tswheregetSessionUserresolves a user withstatusand assert the text output includes the formattedStatus:line; optionally assert the line is omitted whenstatusis absent.src/commands/status/index.ts:33: Per AGENTS.md ("JSON output on mutating commands"), commands that return an object from the API (create/update) should also support
--full.status setandstatus clearreturn the updated user from the API but only register--json. Consider adding.option('--full', 'Include all fields in JSON output')and passing it through toformatJson(..., 'user', options.full)inset.ts/clear.ts, matching howtdc userandgroups createhandle it.
|
The P3 notes from the review body, all addressed in 182643f:
|
|
@doistbot /review |
doistbot
left a comment
There was a problem hiding this comment.
This PR adds tdc status with show/set/clear subcommands backed by users.setStatus()/users.clearStatus() in comms-sdk 3.4.0, plus a Status: line in tdc user. The implementation follows existing conventions well (MutationOptions, spinner messages, scope mapping, read-only guard) and addresses prior review feedback.
Few things worth tightening:
- Accept fractional seconds in
--untilISO timestamps — the current regex rejects2026-10-20T09:00:00.000Z(the exact formatDate.toISOString()produces), breaking scripts that pass generated timestamps; preserve milliseconds when constructing the date and add a test. status clear --jsondoesn't propagate a non-zero exit code on failure.- The user-status JSON rendering block is duplicated across the show, set, and clear handlers — consider moving it into the shared
helpers.ts. parseLocalDatesilently shifts local times that fall in a DST gap; worth a note or explicit handling.
I also included a few optional follow-up notes in the details below.
Optional follow-up notes (5)
src/commands/status/clear.ts:24:
--jsonbypasses the failure exit code. Theuser.statusre-check sits after the JSON return, so whenclearStatus()still returns a status,tdc status clear --jsonprints{ status: ... }(or the full user) and exits 0, while text mode exits 1. Move this check above the JSON branch, or throw aCliError, so scripted callers get a non-zero exit consistently.src/lib/dates.ts:85: Nonexistent local times during DST gaps are accepted with a shifted instant.
new Date(y, mo - 1, d, h, mi, s)normalizes e.g.2026-03-08T02:30in America/New_York to a neighboring hour, and the month/day check still passes, so--untilsilently sets an expiry one hour off instead of rejecting it. Addlocal.getHours() === h && local.getMinutes() === mi && local.getSeconds() === sto this branch to catch DST gaps.src/commands/status/set.ts:104: The
--json/--fullstatus rendering block is duplicated verbatim inshow.ts,set.ts, andclear.ts.status/helpers.tsalready exists as the shared home for status formatting, so add a helper there (e.g.formatStatusJson(user, full)) and call it from all three handlers instead of repeating theoptions.full ? formatJson(user, 'user', true) : formatJson({ status: user.status ?? null })branch.src/commands/status/set.ts:112: When
setStatus()returns no status, this path logs a caveat but leaves the exit code 0 in both text and JSON modes, so a script can't detect that the status wasn't actually set;clearalready exits 1 in the equivalent case. Throw aCliError(or otherwise set a non-zero exit) so the failure is explicit and consistent.src/lib/permissions.test.ts:86: These cases verify the
user:writescope mapping, but no test provesusers.setStatus/users.clearStatusare actually routed through the write guard and refused in read-only mode.isMutatingMethod's mutating assertions don't list either method, and the command tests mockgetCommsClient, bypassing the proxy guard entirely. Add both methods to theidentifies mutating methodsassertions (or add anapi.test.tsrouting test like thegroups.addUserscase) so a future addition toKNOWN_SAFE_API_METHODScan't silently allow status writes.
|
The P3 notes from the latest review body, addressed in 761dc90:
|
|
@doistbot /review |
doistbot
left a comment
There was a problem hiding this comment.
Adds tdc status with show, set, and clear subcommands on top of the new users.setStatus/users.clearStatus SDK methods, including duration/date-based expiry, notification pausing, and upfront validation — plus a status line in tdc user and a README cleanup. No inline issues were flagged.
I also included a few optional follow-up notes in the details below.
Optional follow-up notes (4)
src/commands/user.ts:14:
formatStatusis no longer just a status-subcommand helper now thattdc useruses it, so importing it from./status/helpers.jscouples two sibling command folders and makescommands/statusa shared dependency ofuser. Move the shared status formatting helper intosrc/lib(e.g.src/lib/output.tsnext to the other formatting utilities) and import it from both commands, keeping the command folders self-contained per thehelpers.tspattern in AGENTS.md.src/commands/status/set.ts:72: The hint assumes a date-only input, but this branch also fires for past datetimes:
tdc status set "x" --until 2020-01-01T00:00:00Zfails with "A date with no time means midnight at its start, so add a time or use a later date", and adding a time won't help there. Generalize the hint (e.g. "Use a future date or datetime") or emit the midnight-specific hint only when no time was given.src/commands/user.test.ts:181: The PR adds
statustoUSER_ESSENTIAL_FIELDSinsrc/lib/output.ts, sotdc user --jsonnow returns the status in curated output without--full, but no test asserts that contract. The newuser status lineblock only covers text output. Add a case wheregetSessionUserresolves a user withstatusand assert the parsed--jsonoutput includesstatus; this guards against the field silently dropping out ofUSER_ESSENTIAL_FIELDS.src/commands/status/status.test.ts:211: The
beforeEachintdc status set input guards(and the fake-timer parts oftdc status) duplicate the one intdc statusverbatim:clearAllMocks,useFakeTimers+setSystemTime(NOW),createUsersClient(), and thegetCommsClientmock. Extract a shared setup helper (e.g.function setupStatusFixture() { ... return users }) and call it from each describe so the client/timer wiring stays in one place when the fixture changes.
|
The P3 notes from the latest review body, addressed in c4053f7:
|
Needs a @doist/comms-sdk release that includes users.setStatus (Doist/comms-sdk-typescript#100); the dependency bump is not in this commit. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Brings in users.setStatus and users.clearStatus. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The command no longer exists. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… local Addresses doistbot review: validate --until/--for presence instead of truthiness, skip markdown preload for status, add --full to set/clear, reuse the dates.ts unit constants, and cover the user Status line, the set text output and fake-timer cleanup in tests. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…t what the server returned From an offline review of the whole PR: reject impossible dates, past expiries, out-of-range messages and empty emoji before the dry-run branch, require user:write through the scope table, print the server's status after set and clear, add --full to show, and fix the stale AGENTS.md pointer. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Commander reads the next token as the value, so --emoji --dry-run ran the real write. An emoji never starts with a dash. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…the server disagrees Addresses the second doistbot round: parseLocalDate takes what Date.toISOString() produces, a nonexistent local time in a daylight-saving gap is rejected, clear and set exit 1 whenever the server's status does not match the request (JSON output included), the JSON rendering lives in one helper, and the read-only routing is asserted for both status methods. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…and offset range, fail through CliError From a revision review of 761dc90: the DST-gap rule rejected a date-only --until on the one day a year when a zone's clocks skip midnight, years 0 to 99 round-tripped to 1900 to 1999, and offsets up to +23:59 were accepted. set and clear now throw INVALID_STATE when the server disagrees, so --json prints the usual stderr envelope and no process.exitCode state leaks. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…tus, cover the curated user JSON From the third doistbot round: the midnight hint now appears only for a date with no time, formatStatus moves to src/lib so tdc user no longer imports from the status folder, and a test pins status in the curated tdc user --json output. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…the no-op error From Scott's review: the help, README and skill text now say the pause holds back Comms notifications only, that each app has to read the status, and that held notifications are not sent later. An agent is told to pass the flag only when asked. The set error now says nothing changed, the usual cause, and what to run next. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The status commands print the user through formatJson, which since #78 drops token and cometChannel. This guards the call site. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
8ee5cb1 to
5ddd06f
Compare
## [3.6.0](v3.5.2...v3.6.0) (2026-10-14) ### Features * **status:** add tdc status show, set and clear ([#77](#77)) ([bdfcb16](bdfcb16)), closes [Doist/comms-sdk-typescript#100](Doist/comms-sdk-typescript#100) [#78](#78)
|
🎉 This PR is included in version 3.6.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
Overview
Adds
tdc statusto show, set and clear your shared status (message, emoji, optional expiry, optional pause notifications). It usesusers.setStatus()andusers.clearStatus()from@doist/comms-sdk3.4.0, which this bumps to.tdc useralso prints aStatus:line when one is set.setandclearneed theuser:writescope, so they needtdc auth login --full-access.tdc statusworks on a default login.--untiltakesYYYY-MM-DDorYYYY-MM-DDTHH:MM(local time, or addZor an offset) and--fortakes a duration such as30m,2hor1h30m. Impossible dates, past expiries and messages outside 1 to 80 characters are refused before anything is sent,--dry-runincluded.Also removes the
tdc awaylines from the README, since that command no longer exists.Reference
Adversarial review
8bce083265afeaDoistbot: filled in at merge.
Test plan
tdc auth login --full-accessand runtdc status.No status set.tdc status set "Deep work" --emoji 🎯 --for 2h --pause-notifications.notifications paused.tdc statusandtdc user.tdc status set "x" --until 2026-02-30, then--until 2020-01-01, then--for soon, each with and without--dry-run.tdc status set "x" --until 2030-01-01 --for 2h.Cannot use --until and --for together.tdc status clear, thentdc status.Status cleared., thenNo status set.tdc auth loginand runtdc status set "x".user:writeand--full-access, before a request is made.--read-onlyand runtdc status set "x".🤖 Generated with Claude Code