diff --git a/CHANGELOG.md b/CHANGELOG.md index 61ec20e..4773d89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable user-visible changes are recorded here. Versions follow [Semantic Ve ## Unreleased +## [0.12.1] — 2026-09-12 + ### Added - Vault management on the Account page: what the vault is, the session @@ -15,12 +17,20 @@ All notable user-visible changes are recorded here. Versions follow [Semantic Ve ### Changed +- Give the accounts app a clear primary position in the landing-page header + and hero now that browser-started sessions are available. - Searching and exporting the audit log run in the browser, on the decrypted entries. - The service refuses unencrypted input entries for the audit log. ### Fixed +- Accept `--auto-close today`, which the CLI reference documents but which + never worked: it meant midnight, so it had already passed whenever the + command ran. It now means the end of today. +- Say that an unquoted `--auto-close` date has passed, instead of calling it + invalid. `--auto-close=2020-01-01` already said so; `--auto-close 2020-01-01` + reported a grammar error for a value it had understood perfectly well. - The sign-up page said typed input was not recorded. It is recorded, now end-to-end encrypted for your team. diff --git a/app/public/apple-touch-icon.png b/app/public/apple-touch-icon.png index 0979973..70d2e6b 100644 Binary files a/app/public/apple-touch-icon.png and b/app/public/apple-touch-icon.png differ diff --git a/app/public/favicon.svg b/app/public/favicon.svg index a6adf38..bd22146 100644 --- a/app/public/favicon.svg +++ b/app/public/favicon.svg @@ -1,15 +1 @@ - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/app/public/icon-192.png b/app/public/icon-192.png index bd33d44..323218e 100644 Binary files a/app/public/icon-192.png and b/app/public/icon-192.png differ diff --git a/app/public/icon-512.png b/app/public/icon-512.png index 7236ab2..b6f9ad3 100644 Binary files a/app/public/icon-512.png and b/app/public/icon-512.png differ diff --git a/app/public/icons/claude-code.svg b/app/public/icons/claude-code.svg index 879ad81..e52a457 100644 --- a/app/public/icons/claude-code.svg +++ b/app/public/icons/claude-code.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/app/public/icons/hermes.png b/app/public/icons/hermes.png index cbde251..6f41c09 100644 Binary files a/app/public/icons/hermes.png and b/app/public/icons/hermes.png differ diff --git a/app/public/icons/openclaw.png b/app/public/icons/openclaw.png index b04426d..2c0a04d 100644 Binary files a/app/public/icons/openclaw.png and b/app/public/icons/openclaw.png differ diff --git a/app/public/icons/terminal.png b/app/public/icons/terminal.png index 77b508b..4ff4db3 100644 Binary files a/app/public/icons/terminal.png and b/app/public/icons/terminal.png differ diff --git a/cmd/shell/autoclose.go b/cmd/shell/autoclose.go index c9cd5c4..52f382b 100644 --- a/cmd/shell/autoclose.go +++ b/cmd/shell/autoclose.go @@ -66,11 +66,18 @@ func normalizeAutoCloseArguments(arguments []string, now time.Time) ([]string, e } lastValid := -1 + // Why the first token's error is kept: when no prefix parses, the + // value the user actually wrote is the single token, and its own + // error says what is wrong with it. Reporting "invalid" for all of + // them turns "that time has passed" into a grammar complaint. + var firstError error for end := index + 1; end < len(arguments); end++ { candidate := strings.Join(arguments[index+1:end+1], " ") if _, err := parseCloseDeadline(candidate, now); err == nil { lastValid = end continue + } else if end == index+1 { + firstError = err } if lastValid >= 0 { break @@ -83,6 +90,9 @@ func normalizeAutoCloseArguments(arguments []string, now time.Time) ([]string, e break } if lastValid < 0 { + if firstError != nil { + return nil, firstError + } return nil, fmt.Errorf("invalid auto-close value %q", arguments[index+1]) } normalized = append(normalized, "--auto-close="+strings.Join(arguments[index+1:lastValid+1], " ")) @@ -197,15 +207,27 @@ func parseAbsoluteDeadline(value string, now time.Time) (time.Time, bool) { if prefix == "tomorrow" { day = day.AddDate(0, 0, 1) } + // A bare day keyword carries no time of day, so it has to stand + // for one. "tomorrow" takes the start of that day, which is still + // ahead. "today" cannot: midnight has already passed whenever the + // command runs, so the only reading of "close this today" that is + // ever a deadline is the end of it. The last second rather than + // the last minute, so that the form still works during 23:59. clock := "00:00" + second := 0 + if prefix == "today" { + clock = "23:59" + second = 59 + } if len(value) > len(prefix) { clock = strings.TrimSpace(value[len(prefix):]) + second = 0 } parsedClock, err := time.ParseInLocation("15:04", clock, now.Location()) if err != nil { return time.Time{}, false } - return time.Date(day.Year(), day.Month(), day.Day(), parsedClock.Hour(), parsedClock.Minute(), 0, 0, now.Location()), true + return time.Date(day.Year(), day.Month(), day.Day(), parsedClock.Hour(), parsedClock.Minute(), second, 0, now.Location()), true } } diff --git a/cmd/shell/autoclose_test.go b/cmd/shell/autoclose_test.go index f8d005a..36064e0 100644 --- a/cmd/shell/autoclose_test.go +++ b/cmd/shell/autoclose_test.go @@ -2,6 +2,7 @@ package main import ( "reflect" + "strings" "testing" "time" ) @@ -24,6 +25,9 @@ func TestParseCloseDeadline(t *testing.T) { {name: "years", value: "1y", want: now.AddDate(1, 0, 0)}, {name: "in prefix", value: "in 15m", want: now.Add(15 * time.Minute)}, {name: "tomorrow", value: "tomorrow 09:15", want: time.Date(2026, time.August, 20, 9, 15, 0, 0, location)}, + {name: "bare tomorrow", value: "tomorrow", want: time.Date(2026, time.August, 20, 0, 0, 0, 0, location)}, + {name: "bare today", value: "today", want: time.Date(2026, time.August, 19, 23, 59, 59, 0, location)}, + {name: "today with clock", value: "today 23:50", want: time.Date(2026, time.August, 19, 23, 50, 0, 0, location)}, {name: "clock rolls forward", value: "22:00", want: time.Date(2026, time.August, 20, 22, 0, 0, 0, location)}, {name: "local date", value: "2026-08-21 12:30", want: time.Date(2026, time.August, 21, 12, 30, 0, 0, location)}, } @@ -86,6 +90,7 @@ func TestNormalizeAutoCloseArguments(t *testing.T) { }{ {input: []string{"--auto-close", "5m", "sleep", "30"}, want: []string{"--auto-close=5m", "sleep", "30"}}, {input: []string{"--auto-close", "tomorrow", "09:00", "sleep", "30"}, want: []string{"--auto-close=tomorrow 09:00", "sleep", "30"}}, + {input: []string{"--auto-close", "today", "sleep", "30"}, want: []string{"--auto-close=today", "sleep", "30"}}, {input: []string{"--auto-close", "in", "15m", "sleep", "30"}, want: []string{"--auto-close=in 15m", "sleep", "30"}}, {input: []string{"--auto-close", "2d", "3h", "sleep", "30"}, want: []string{"--auto-close=2d 3h", "sleep", "30"}}, {input: []string{"--auto-close=2h", "echo"}, want: []string{"--auto-close=2h", "echo"}}, @@ -116,3 +121,37 @@ func TestNormalizeAutoCloseArgumentsRejectsMissingAndInvalidValues(t *testing.T) } } } + +// A bare "today" is only useful if it is a deadline at every hour it can be +// typed, including during the last minute of the day. +func TestBareTodayIsADeadlineAllDay(t *testing.T) { + location := time.FixedZone("test", 2*60*60) + for _, clock := range []struct{ hour, minute, second int }{ + {0, 0, 0}, {9, 15, 0}, {23, 58, 0}, {23, 59, 30}, + } { + now := time.Date(2026, time.August, 19, clock.hour, clock.minute, clock.second, 0, location) + deadline, err := parseCloseDeadline("today", now) + if err != nil { + t.Fatalf("parseCloseDeadline(\"today\") at %v: %v", now, err) + } + if !deadline.After(now) { + t.Fatalf("parseCloseDeadline(\"today\") at %v = %v, want a future deadline", now, deadline) + } + if deadline.Day() != now.Day() { + t.Fatalf("parseCloseDeadline(\"today\") at %v = %v, want the same day", now, deadline) + } + } +} + +// A date that has passed is a different mistake from one that cannot be read, +// and the unquoted form used to report both as "invalid". +func TestNormalizeAutoCloseArgumentsReportsPastDeadlines(t *testing.T) { + now := time.Date(2026, time.August, 19, 23, 40, 0, 0, time.UTC) + _, err := normalizeAutoCloseArguments([]string{"--auto-close", "2020-01-01", "sleep", "30"}, now) + if err == nil { + t.Fatal("normalizeAutoCloseArguments unexpectedly accepted a past date") + } + if !strings.Contains(err.Error(), "must be in the future") { + t.Fatalf("normalizeAutoCloseArguments error = %q, want it to say the deadline has passed", err) + } +} diff --git a/cmd/shell/help.go b/cmd/shell/help.go index e6a7871..c7cec95 100644 --- a/cmd/shell/help.go +++ b/cmd/shell/help.go @@ -296,7 +296,8 @@ START OPTIONS --auto-close Always close when the task exits; optionally add an earlier deadline. Units: ms, s, m, h, d, w, mo, y. Units may be combined, such as 1h30m. - Dates: RFC3339, YYYY-MM-DD[ HH:MM[:SS]], HH:MM, today, or tomorrow HH:MM. + Dates: RFC3339, YYYY-MM-DD[ HH:MM[:SS]], HH:MM, or today/tomorrow [HH:MM]. + Bare today means the end of today; bare tomorrow means 00:00 tomorrow. Multi-token dates may be written directly, for example: --auto-close tomorrow 09:00. A missing or invalid value returns status 2 and never becomes the command. --json diff --git a/docker-compose.yml b/docker-compose.yml index a24e4a9..abdf7b2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,10 +1,10 @@ services: shell-online: - image: ghcr.io/teoslayer/shell.online:0.12.0 + image: ghcr.io/teoslayer/shell.online:0.12.1 build: context: . args: - VERSION: "0.12.0" + VERSION: "0.12.1" restart: unless-stopped environment: SHELL_ONLINE_E2EE_PASSWORD: "${SHELL_ONLINE_E2EE_PASSWORD:-}" diff --git a/docs/content.json b/docs/content.json index 8a2e0f4..b9805f2 100644 --- a/docs/content.json +++ b/docs/content.json @@ -1,5 +1,5 @@ { - "version": "0.12.0", + "version": "0.12.1", "pages": { "docs": { "eyebrow": "Documentation", @@ -553,7 +553,7 @@ "cards": [ [ "Start once", - "Pull ghcr.io/teoslayer/shell.online:0.12.0 or run docker compose up --build -d, then docker compose logs shell-online. First launch prints the stable URL and a generated eight-character password. The tagged amd64/arm64 image includes an SBOM and build provenance." + "Pull ghcr.io/teoslayer/shell.online:0.12.1 or run docker compose up --build -d, then docker compose logs shell-online. First launch prints the stable URL and a generated eight-character password. The tagged amd64/arm64 image includes an SBOM and build provenance." ], [ "Two durable volumes", diff --git a/index.html b/index.html index 0a5c541..8930df5 100644 --- a/index.html +++ b/index.html @@ -70,7 +70,7 @@ }, "applicationCategory": "DeveloperApplication", "operatingSystem": "macOS, Windows, Linux, FreeBSD, OpenBSD, NetBSD, DragonFly BSD, Solaris", - "softwareVersion": "0.12.0", + "softwareVersion": "0.12.1", "softwareRequirements": "A supported architecture, outbound HTTPS and WebSockets, and a PTY or Windows ConPTY", "isAccessibleForFree": true, "downloadUrl": "https://shell.online/install", diff --git a/package-lock.json b/package-lock.json index 42738a2..ba7423b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "shell-online", - "version": "0.12.0", + "version": "0.12.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "shell-online", - "version": "0.12.0", + "version": "0.12.1", "license": "MIT", "dependencies": { "@xterm/xterm": "^6.0.0" diff --git a/package.json b/package.json index 985205c..e5604a7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "shell-online", - "version": "0.12.0", + "version": "0.12.1", "description": "Turn any terminal process into a collaborative browser link.", "private": true, "license": "MIT", diff --git a/public/app-icon.svg b/public/app-icon.svg index 48be5d2..82c5e7f 100644 --- a/public/app-icon.svg +++ b/public/app-icon.svg @@ -1,15 +1 @@ - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png index 0979973..f65789d 100644 Binary files a/public/apple-touch-icon.png and b/public/apple-touch-icon.png differ diff --git a/public/favicon.svg b/public/favicon.svg index a6adf38..bd22146 100644 --- a/public/favicon.svg +++ b/public/favicon.svg @@ -1,15 +1 @@ - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/public/icon-192.png b/public/icon-192.png index bd33d44..936ed82 100644 Binary files a/public/icon-192.png and b/public/icon-192.png differ diff --git a/public/icon-512.png b/public/icon-512.png index 7236ab2..c19be8d 100644 Binary files a/public/icon-512.png and b/public/icon-512.png differ diff --git a/public/llms.txt b/public/llms.txt index 7e441b3..926e508 100644 --- a/public/llms.txt +++ b/public/llms.txt @@ -24,9 +24,9 @@ The Homebrew formula builds locally: Brew fetches the checksum-pinned tagged sou The standalone installer uses a checksum-pinned release binary. To compile and run the tagged source outside either installation path: -git clone --depth 1 --branch v0.12.0 https://github.com/TeoSlayer/shell.online.git +git clone --depth 1 --branch v0.12.1 https://github.com/TeoSlayer/shell.online.git cd shell.online -go build -trimpath -ldflags="-X main.version=0.12.0" -o ./shell ./cmd/shell +go build -trimpath -ldflags="-X main.version=0.12.1" -o ./shell ./cmd/shell ./shell --version The source-build path requires Go 1.26.8 or newer, except Go 1.27.x is intentionally unsupported on MIPS64 because of go.dev/issue/80978. Keep using ./shell or move it to a directory on PATH. diff --git a/public/screenshots/codex-complete-mobile.png b/public/screenshots/codex-complete-mobile.png index 8529a69..8bd48ed 100644 Binary files a/public/screenshots/codex-complete-mobile.png and b/public/screenshots/codex-complete-mobile.png differ diff --git a/public/screenshots/codex-working-mobile.png b/public/screenshots/codex-working-mobile.png index 52f3483..f9f94bb 100644 Binary files a/public/screenshots/codex-working-mobile.png and b/public/screenshots/codex-working-mobile.png differ diff --git a/public/social-card.png b/public/social-card.png index 59dddce..2b6b2fb 100644 Binary files a/public/social-card.png and b/public/social-card.png differ diff --git a/public/social-card.svg b/public/social-card.svg index 188b022..202b935 100644 --- a/public/social-card.svg +++ b/public/social-card.svg @@ -1,75 +1 @@ - - shell.online — a live browser link for your terminal process - A cloudy interface showing one command becoming a collaborative browser terminal. - - - - - - - - - - - - - - - - - - - - - - - - - - - - shell.online - A live terminal. - One link. - Watch it. Type into it. Hand it off. - - - - - - shell <your-command> - - No account · Runs locally · TUI-ready - - - - - - - - - train.py - - 38 ms - - - epoch 38 / 100 - loss0.0821 - accuracy94.7% - - - saving checkpoint… - - - - - - - - Y - D - A - - - - +shell.online — a live browser link for your terminal processA cloudy interface showing one command becoming a collaborative browser terminal.shell.onlineA live terminal.One link.Watch it. Type into it. Hand it off.shell <your-command>No account · Runs locally · TUI-readytrain.py38 msepoch 38 / 100loss0.0821accuracy94.7%saving checkpoint…YDA \ No newline at end of file