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 @@
-
+
\ No newline at end of file