From 102b1f2cdcba14eb416a2daa7c25dffc4db64c15 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 10 Feb 2026 12:02:30 +0000 Subject: [PATCH 1/4] fix(ci): support windows time and tty Co-authored-by: Loongphy Wei --- src/cli.zig | 34 +++++++++++++++++++++++------ src/format.zig | 58 ++++++++++++++++++++++++++++++++++---------------- 2 files changed, 68 insertions(+), 24 deletions(-) diff --git a/src/cli.zig b/src/cli.zig index 6aa0cf6..bb87280 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const builtin = @import("builtin"); const registry = @import("registry.zig"); const io_util = @import("io_util.zig"); const timefmt = @import("timefmt.zig"); @@ -14,7 +15,7 @@ const ansi = struct { }; fn colorEnabled() bool { - return std.posix.isatty(std.posix.STDOUT_FILENO); + return std.fs.File.stdout().isTty(); } pub const OutputFormat = enum { table, json, csv, compact }; @@ -133,10 +134,16 @@ pub fn runCodexLogin(allocator: std.mem.Allocator) !void { } pub fn selectAccount(allocator: std.mem.Allocator, reg: *registry.Registry) !?[]const u8 { - return selectInteractive(allocator, reg) catch selectWithNumbers(reg); + return if (comptime builtin.os.tag == .windows) + selectWithNumbers(reg) + else + selectInteractive(allocator, reg) catch selectWithNumbers(reg); } pub fn selectAccountsToRemove(allocator: std.mem.Allocator, reg: *registry.Registry) !?[]usize { + if (comptime builtin.os.tag == .windows) { + return selectRemoveWithNumbers(allocator, reg); + } return selectRemoveInteractive(allocator, reg) catch selectRemoveWithNumbers(allocator, reg); } @@ -726,19 +733,34 @@ const ResetParts = struct { } }; +fn localtimeCompat(ts: i64, out_tm: *c.struct_tm) bool { + var t: c.time_t = @intCast(ts); + + if (comptime @hasDecl(c, "localtime_r")) { + return c.localtime_r(&t, out_tm) != null; + } + + if (comptime @hasDecl(c, "localtime")) { + const tm_ptr = c.localtime(&t); + if (tm_ptr == null) return false; + out_tm.* = tm_ptr.*; + return true; + } + + return false; +} + fn resetPartsAlloc(allocator: std.mem.Allocator, reset_at: i64, now: i64) !ResetParts { - var t: c.time_t = @intCast(reset_at); var tm: c.struct_tm = undefined; - if (c.localtime_r(&t, &tm) == null) { + if (!localtimeCompat(reset_at, &tm)) { return ResetParts{ .time = try std.fmt.allocPrint(allocator, "-", .{}), .date = try std.fmt.allocPrint(allocator, "-", .{}), .same_day = true, }; } - var now_t: c.time_t = @intCast(now); var now_tm: c.struct_tm = undefined; - if (c.localtime_r(&now_t, &now_tm) == null) { + if (!localtimeCompat(now, &now_tm)) { return ResetParts{ .time = try std.fmt.allocPrint(allocator, "-", .{}), .date = try std.fmt.allocPrint(allocator, "-", .{}), diff --git a/src/format.zig b/src/format.zig index e18e8f1..c8aa5fa 100644 --- a/src/format.zig +++ b/src/format.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const builtin = @import("builtin"); const registry = @import("registry.zig"); const cli = @import("cli.zig"); const io_util = @import("io_util.zig"); @@ -6,7 +7,6 @@ const timefmt = @import("timefmt.zig"); const c = @cImport({ @cInclude("time.h"); }); -const linux = std.os.linux; const ansi = struct { const reset = "\x1b[0m"; @@ -15,7 +15,7 @@ const ansi = struct { }; fn colorEnabled() bool { - return std.posix.isatty(std.posix.STDOUT_FILENO); + return std.fs.File.stdout().isTty(); } fn planDisplay(rec: *const registry.AccountRecord, missing: []const u8) []const u8 { @@ -260,19 +260,34 @@ const ResetParts = struct { } }; +fn localtimeCompat(ts: i64, out_tm: *c.struct_tm) bool { + var t: c.time_t = @intCast(ts); + + if (comptime @hasDecl(c, "localtime_r")) { + return c.localtime_r(&t, out_tm) != null; + } + + if (comptime @hasDecl(c, "localtime")) { + const tm_ptr = c.localtime(&t); + if (tm_ptr == null) return false; + out_tm.* = tm_ptr.*; + return true; + } + + return false; +} + fn resetPartsAlloc(reset_at: i64, now: i64) !ResetParts { - var t: c.time_t = @intCast(reset_at); var tm: c.struct_tm = undefined; - if (c.localtime_r(&t, &tm) == null) { + if (!localtimeCompat(reset_at, &tm)) { return ResetParts{ .time = try std.fmt.allocPrint(std.heap.page_allocator, "-", .{}), .date = try std.fmt.allocPrint(std.heap.page_allocator, "-", .{}), .same_day = true, }; } - var now_t: c.time_t = @intCast(now); var now_tm: c.struct_tm = undefined; - if (c.localtime_r(&now_t, &now_tm) == null) { + if (!localtimeCompat(now, &now_tm)) { return ResetParts{ .time = try std.fmt.allocPrint(std.heap.page_allocator, "-", .{}), .date = try std.fmt.allocPrint(std.heap.page_allocator, "-", .{}), @@ -370,14 +385,12 @@ fn remainingPercent(used: f64) i64 { } fn formatResetTimeAlloc(ts: i64, now: i64) ![]u8 { - var t: c.time_t = @intCast(ts); var tm: c.struct_tm = undefined; - if (c.localtime_r(&t, &tm) == null) { + if (!localtimeCompat(ts, &tm)) { return try std.fmt.allocPrint(std.heap.page_allocator, "-", .{}); } - var now_t: c.time_t = @intCast(now); var now_tm: c.struct_tm = undefined; - if (c.localtime_r(&now_t, &now_tm) == null) { + if (!localtimeCompat(now, &now_tm)) { return try std.fmt.allocPrint(std.heap.page_allocator, "-", .{}); } @@ -596,12 +609,22 @@ fn tableTotalWidth(widths: []const usize) usize { } fn terminalWidth() usize { - const fd = std.posix.STDOUT_FILENO; - if (!std.posix.isatty(fd)) return 0; - var wsz: std.posix.winsize = undefined; - const rc = linux.syscall3(.ioctl, @bitCast(@as(isize, fd)), linux.T.IOCGWINSZ, @intFromPtr(&wsz)); - if (linux.E.init(rc) != .SUCCESS) return 0; - return @as(usize, wsz.col); + const stdout_file = std.fs.File.stdout(); + if (!stdout_file.isTty()) return 0; + + if (comptime builtin.os.tag == .windows) { + return 0; + } else { + var wsz: std.posix.winsize = .{ + .row = 0, + .col = 0, + .xpixel = 0, + .ypixel = 0, + }; + const rc = std.posix.system.ioctl(stdout_file.handle, std.posix.T.IOCGWINSZ, @intFromPtr(&wsz)); + if (std.posix.errno(rc) != .SUCCESS) return 0; + return @as(usize, wsz.col); + } } fn truncateAlloc(value: []const u8, max_len: usize) ![]u8 { @@ -613,9 +636,8 @@ fn truncateAlloc(value: []const u8, max_len: usize) ![]u8 { fn formatTimestampAlloc(ts: i64) ![]u8 { if (ts < 0) return try std.fmt.allocPrint(std.heap.page_allocator, "-", .{}); - var t: c.time_t = @intCast(ts); var tm: c.struct_tm = undefined; - if (c.localtime_r(&t, &tm) == null) { + if (!localtimeCompat(ts, &tm)) { return try std.fmt.allocPrint(std.heap.page_allocator, "-", .{}); } From ae6f6b80a4c1df2680f0de8f5a9a8adbaae842e1 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 10 Feb 2026 13:28:11 +0000 Subject: [PATCH 2/4] fix(windows): prefer localtime_s and tty width Co-authored-by: Loongphy Wei --- src/cli.zig | 6 ++++++ src/format.zig | 14 +++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/cli.zig b/src/cli.zig index bb87280..ada01bd 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -736,6 +736,12 @@ const ResetParts = struct { fn localtimeCompat(ts: i64, out_tm: *c.struct_tm) bool { var t: c.time_t = @intCast(ts); + if (comptime builtin.os.tag == .windows) { + if (comptime @hasDecl(c, "localtime_s")) { + return c.localtime_s(out_tm, &t) == 0; + } + } + if (comptime @hasDecl(c, "localtime_r")) { return c.localtime_r(&t, out_tm) != null; } diff --git a/src/format.zig b/src/format.zig index c8aa5fa..5153e9a 100644 --- a/src/format.zig +++ b/src/format.zig @@ -263,6 +263,12 @@ const ResetParts = struct { fn localtimeCompat(ts: i64, out_tm: *c.struct_tm) bool { var t: c.time_t = @intCast(ts); + if (comptime builtin.os.tag == .windows) { + if (comptime @hasDecl(c, "localtime_s")) { + return c.localtime_s(out_tm, &t) == 0; + } + } + if (comptime @hasDecl(c, "localtime_r")) { return c.localtime_r(&t, out_tm) != null; } @@ -613,7 +619,13 @@ fn terminalWidth() usize { if (!stdout_file.isTty()) return 0; if (comptime builtin.os.tag == .windows) { - return 0; + var info: std.os.windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; + if (std.os.windows.kernel32.GetConsoleScreenBufferInfo(stdout_file.handle, &info) == std.os.windows.FALSE) { + return 0; + } + const width = @as(i32, info.srWindow.Right) - @as(i32, info.srWindow.Left) + 1; + if (width <= 0) return 0; + return @as(usize, @intCast(width)); } else { var wsz: std.posix.winsize = .{ .row = 0, From 72c19d381a692dc76e60b1ef1090cabf0cbe1347 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 10 Feb 2026 13:30:08 +0000 Subject: [PATCH 3/4] fix(windows): use _localtime64_s fallback Co-authored-by: Loongphy Wei --- src/cli.zig | 5 +++++ src/format.zig | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/cli.zig b/src/cli.zig index ada01bd..53313aa 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -737,6 +737,11 @@ fn localtimeCompat(ts: i64, out_tm: *c.struct_tm) bool { var t: c.time_t = @intCast(ts); if (comptime builtin.os.tag == .windows) { + // Use the exported CRT symbol first; localtime_s may be an inline wrapper. + if (comptime @hasDecl(c, "_localtime64_s") and @hasDecl(c, "__time64_t")) { + var t64: c.__time64_t = @intCast(ts); + return c._localtime64_s(out_tm, &t64) == 0; + } if (comptime @hasDecl(c, "localtime_s")) { return c.localtime_s(out_tm, &t) == 0; } diff --git a/src/format.zig b/src/format.zig index 5153e9a..2c8fabf 100644 --- a/src/format.zig +++ b/src/format.zig @@ -264,6 +264,11 @@ fn localtimeCompat(ts: i64, out_tm: *c.struct_tm) bool { var t: c.time_t = @intCast(ts); if (comptime builtin.os.tag == .windows) { + // Use the exported CRT symbol first; localtime_s may be an inline wrapper. + if (comptime @hasDecl(c, "_localtime64_s") and @hasDecl(c, "__time64_t")) { + var t64: c.__time64_t = @intCast(ts); + return c._localtime64_s(out_tm, &t64) == 0; + } if (comptime @hasDecl(c, "localtime_s")) { return c.localtime_s(out_tm, &t) == 0; } From 2a9c181ae4e595b4416a042c294c62b9d3b742e1 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 10 Feb 2026 13:49:03 +0000 Subject: [PATCH 4/4] fix(windows): bind _localtime64_s and guard cast Co-authored-by: Loongphy Wei --- src/cli.zig | 11 ++++------- src/format.zig | 11 ++++------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/cli.zig b/src/cli.zig index 53313aa..5fa3bf4 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -734,19 +734,16 @@ const ResetParts = struct { }; fn localtimeCompat(ts: i64, out_tm: *c.struct_tm) bool { - var t: c.time_t = @intCast(ts); - if (comptime builtin.os.tag == .windows) { - // Use the exported CRT symbol first; localtime_s may be an inline wrapper. + // Bind directly to the exported CRT symbol on Windows. if (comptime @hasDecl(c, "_localtime64_s") and @hasDecl(c, "__time64_t")) { - var t64: c.__time64_t = @intCast(ts); + var t64 = std.math.cast(c.__time64_t, ts) orelse return false; return c._localtime64_s(out_tm, &t64) == 0; } - if (comptime @hasDecl(c, "localtime_s")) { - return c.localtime_s(out_tm, &t) == 0; - } + return false; } + var t = std.math.cast(c.time_t, ts) orelse return false; if (comptime @hasDecl(c, "localtime_r")) { return c.localtime_r(&t, out_tm) != null; } diff --git a/src/format.zig b/src/format.zig index 2c8fabf..c3d4c4c 100644 --- a/src/format.zig +++ b/src/format.zig @@ -261,19 +261,16 @@ const ResetParts = struct { }; fn localtimeCompat(ts: i64, out_tm: *c.struct_tm) bool { - var t: c.time_t = @intCast(ts); - if (comptime builtin.os.tag == .windows) { - // Use the exported CRT symbol first; localtime_s may be an inline wrapper. + // Bind directly to the exported CRT symbol on Windows. if (comptime @hasDecl(c, "_localtime64_s") and @hasDecl(c, "__time64_t")) { - var t64: c.__time64_t = @intCast(ts); + var t64 = std.math.cast(c.__time64_t, ts) orelse return false; return c._localtime64_s(out_tm, &t64) == 0; } - if (comptime @hasDecl(c, "localtime_s")) { - return c.localtime_s(out_tm, &t) == 0; - } + return false; } + var t = std.math.cast(c.time_t, ts) orelse return false; if (comptime @hasDecl(c, "localtime_r")) { return c.localtime_r(&t, out_tm) != null; }