6a4241b6957a

subcommand help: show usage when grouped command has no subcommand — output.subcommandHelp renders an aligned {invocation, desc} table; flake/service/store/channel/gen/pkg now print their subcommand list on empty sub or explicit 'help' (e.g. 'nina flake', 'nina flake help'); channel help shows before the flake note; real subcommands still dispatch unchanged — build+tests green, all six groups + explicit help verified

time
2026-06-10 01:11 UTC · 4 months ago
machine
koh2
agent
claude-opus-4-8
session
nina community improvements
parent
bbbdab1eedc3
2 files src/commands.zig+59 −2src/output.zig+10 −0
src/commands.zig+59 −2
@@ -124,7 +124,15 @@124124     for (gens) |g| output.historyRow(g);125125 }126126 127+const gen_help = [_][2][]const u8{128+    .{ "list", "list generations" },129+    .{ "current", "show current generation" },130+    .{ "delete <n>", "delete generation n" },131+    .{ "delete old", "delete old generations" },132+};133+127134 pub fn genCmd(ctx: Ctx) !void {135+    if (ctx.sub.len == 0 or std.mem.eql(u8, ctx.sub, "help")) return output.subcommandHelp("gen", &gen_help);128136     if (std.mem.eql(u8, ctx.sub, "list")) return history(ctx);129137     if (std.mem.eql(u8, ctx.sub, "current")) {130138         const n = try exec.getCurrentGeneration(ctx.gpa, ctx.io, &ctx.machine);@@ -825,8 +833,20 @@825833 826834 // --- Service commands ---827835 836+const service_help = [_][2][]const u8{837+    .{ "list", "list running services" },838+    .{ "logs <svc>", "show service logs" },839+    .{ "status <svc>", "service status" },840+    .{ "start <svc>", "start a service" },841+    .{ "stop <svc>", "stop a service" },842+    .{ "restart <svc>", "restart a service" },843+    .{ "enable <svc>", "enable at boot" },844+    .{ "disable <svc>", "disable at boot" },845+};846+828847 pub fn service(ctx: Ctx) !void {829-    if (std.mem.eql(u8, ctx.sub, "list") or ctx.sub.len == 0) {848+    if (ctx.sub.len == 0 or std.mem.eql(u8, ctx.sub, "help")) return output.subcommandHelp("service", &service_help);849+    if (std.mem.eql(u8, ctx.sub, "list")) {830850         output.serviceListHeader(ctx.machine.name);831851         const svcs = exec.serviceList(ctx.gpa, ctx.io, &ctx.machine) catch &.{};832852         defer ctx.gpa.free(svcs);@@ -858,7 +878,18 @@858878 859879 // --- Flake commands ---860880 881+const flake_help = [_][2][]const u8{882+    .{ "init", "create a new flake.nix" },883+    .{ "update", "update flake inputs" },884+    .{ "update <in>", "update a specific input" },885+    .{ "check", "validate the flake" },886+    .{ "show", "inspect flake outputs" },887+    .{ "lock", "regenerate flake.lock" },888+    .{ "clone <url>", "clone a flake repository" },889+};890+861891 pub fn flake(ctx: Ctx) !void {892+    if (ctx.sub.len == 0 or std.mem.eql(u8, ctx.sub, "help")) return output.subcommandHelp("flake", &flake_help);862893     if (std.mem.eql(u8, ctx.sub, "update")) {863894         output.flakeUpdateStart();864895         const input: ?[]const u8 = if (ctx.args.len > 0) ctx.args[0] else null;@@ -916,7 +947,16 @@916947 917948 // --- Store commands ---918949 950+const store_help = [_][2][]const u8{951+    .{ "info", "store size and counts" },952+    .{ "path <attr>", "store path of a package" },953+    .{ "gc", "collect garbage" },954+    .{ "verify", "verify store integrity" },955+    .{ "repair", "repair corrupted paths" },956+};957+919958 pub fn store(ctx: Ctx) !void {959+    if (ctx.sub.len == 0 or std.mem.eql(u8, ctx.sub, "help")) return output.subcommandHelp("store", &store_help);920960     if (std.mem.eql(u8, ctx.sub, "gc")) {921961         _ = try exec.storeGc(ctx.io, &ctx.machine, ctx.gpa);922962         return;@@ -951,10 +991,18 @@951991 952992 // --- Channel commands ---953993 994+const channel_help = [_][2][]const u8{995+    .{ "list", "list channels" },996+    .{ "add <url> [name]", "add a channel" },997+    .{ "remove <name>", "remove a channel" },998+    .{ "update", "update all channels" },999+};1000+9541001 pub fn channel(ctx: Ctx) !void {1002+    if (ctx.sub.len == 0 or std.mem.eql(u8, ctx.sub, "help")) return output.subcommandHelp("channel", &channel_help);9551003     // On a flake-managed system, nix-channel operations generally don't apply.9561004     if (ctx.cfg.flake) output.flakeChannelNote();957-    if (std.mem.eql(u8, ctx.sub, "list") or ctx.sub.len == 0) {1005+    if (std.mem.eql(u8, ctx.sub, "list")) {9581006         output.channelListHeader(ctx.machine.name);9591007         const chans = try exec.channelList(ctx.gpa, ctx.io, &ctx.machine);9601008         defer ctx.gpa.free(chans);@@ -1017,7 +1065,16 @@10171065 10181066 // --- Pkg commands ---10191067 1068+const pkg_help = [_][2][]const u8{1069+    .{ "why <pkg>", "what pulled it in" },1070+    .{ "deps <pkg>", "direct dependencies" },1071+    .{ "size <pkg>", "closure size" },1072+    .{ "path <pkg>", "store path" },1073+    .{ "closure <pkg>", "full closure" },1074+};1075+10201076 pub fn pkg(ctx: Ctx) !void {1077+    if (ctx.sub.len == 0 or std.mem.eql(u8, ctx.sub, "help")) return output.subcommandHelp("pkg", &pkg_help);10211078     const name = if (ctx.args.len > 0) ctx.args[0] else return error.PackageNotFound;10221079     if (std.mem.eql(u8, ctx.sub, "why")) {10231080         _ = try exec.pkgWhy(ctx.io, &ctx.machine, ctx.gpa, name);

view file

src/output.zig+10 −0
@@ -692,6 +692,16 @@692692     p("\n   {s}flags: --on <machine>  --dry  --check  --no-apply  --last <n>  --all  --json{s}\n", .{ c(DIM), c(RESET) });693693 }694694 695+// Render a grouped command's subcommand list, shown when the group is invoked696+// with no subcommand or an explicit `help`. Rows are {invocation, description}.697+pub fn subcommandHelp(group: []const u8, rows: []const [2][]const u8) void {698+    p("{s}{s} subcommands\n\n", .{ hdr(), group });699+    for (rows) |row| {700+        p("   {s:<16}  {s}{s}{s}\n", .{ row[0], c(DIM), row[1], c(RESET) });701+    }702+    p("\n   nina {s} help  {s}for this message{s}\n", .{ group, c(DIM), c(RESET) });703+}704+695705 pub fn version() void {696706     p("{s}nina  v{s}  zig 0.16  zero dependencies\n", .{ hdr(), nina_version.VERSION });697707 }

view file