4 Commits

Author SHA1 Message Date
6dcf43ce41 feat(help): add knowledge-base leaf help (per-command usage)
The knowledge-base command family was registered in the help surface
(group + subcommands) but had NO leaf entries in internal/help/leaf.go, so
`hf knowledge-base add-fact --help` (and every other kb subcommand) printed
no usage. Weak agents then guessed flags (e.g. `add-fact --kb --topic-id
--title` instead of `--topic <id> --fact <text>`).

Add leaf help for all 18 kb subcommands (list/get/tree/topics/create/update/
delete/link/unlink/add-topic/update-topic/delete-topic/add-category/
update-category/delete-category/add-fact/update-fact/delete-fact), with the
authoritative usage strings copied from internal/commands/knowledge_base.go.
Now `hf knowledge-base <sub> --help` renders proper usage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 08:52:40 +01:00
729365ca46 Merge fix/security-audit: CLI credential hardening 2026-06-01 09:23:52 +01:00
ef8d4dbdad Merge feat/knowledge-base: KnowledgeBase CLI commands 2026-06-01 09:23:52 +01:00
4125a4c102 fix(security): keep credentials off argv and plaintext transports
- M7: ResolveToken accepts the token via the HF_TOKEN env var (so it need
  not appear in argv, where it's visible in ps/shell history); the HTTP
  client refuses to send a token / API key over plaintext http:// to a
  non-loopback host (use https://). Loopback http is still allowed for
  local dev.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 20:16:36 +01:00
3 changed files with 137 additions and 77 deletions

View File

@@ -5,7 +5,9 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"net"
"net/http" "net/http"
neturl "net/url"
"strings" "strings"
"time" "time"
) )
@@ -70,8 +72,39 @@ func (e *RequestError) Error() string {
return fmt.Sprintf("HTTP %d: %s", e.StatusCode, e.Body) return fmt.Sprintf("HTTP %d: %s", e.StatusCode, e.Body)
} }
// guardPlaintextCreds refuses to transmit a token / API key over a plaintext
// http:// connection to a non-loopback host (prevents credential interception).
func (c *Client) guardPlaintextCreds() error {
if c.Token == "" && c.APIKey == "" {
return nil
}
u, err := neturl.Parse(c.BaseURL)
if err != nil || u.Scheme != "http" {
return nil // parse errors and https:// are fine
}
if isLoopbackHost(u.Hostname()) {
return nil
}
return fmt.Errorf("refusing to send credentials over plaintext http:// to non-loopback host %q — use an https:// base URL (hf config set-url ...)", u.Hostname())
}
// isLoopbackHost reports whether h is a loopback address or localhost name.
func isLoopbackHost(h string) bool {
h = strings.ToLower(h)
if h == "localhost" || strings.HasSuffix(h, ".localhost") {
return true
}
if ip := net.ParseIP(h); ip != nil {
return ip.IsLoopback()
}
return false
}
// Do executes an HTTP request and returns the response body bytes. // Do executes an HTTP request and returns the response body bytes.
func (c *Client) Do(method, path string, body io.Reader) ([]byte, error) { func (c *Client) Do(method, path string, body io.Reader) ([]byte, error) {
if err := c.guardPlaintextCreds(); err != nil {
return nil, err
}
url := c.BaseURL + path url := c.BaseURL + path
req, err := http.NewRequest(method, url, body) req, err := http.NewRequest(method, url, body)
if err != nil { if err != nil {

View File

@@ -1,6 +1,9 @@
package commands package commands
import ( import (
"os"
"strings"
"git.hangman-lab.top/zhi/HarborForge.Cli/internal/mode" "git.hangman-lab.top/zhi/HarborForge.Cli/internal/mode"
"git.hangman-lab.top/zhi/HarborForge.Cli/internal/output" "git.hangman-lab.top/zhi/HarborForge.Cli/internal/output"
"git.hangman-lab.top/zhi/HarborForge.Cli/internal/passmgr" "git.hangman-lab.top/zhi/HarborForge.Cli/internal/passmgr"
@@ -20,11 +23,16 @@ func ResolveToken(tokenFlag string) string {
} }
return tok return tok
} }
// manual mode // manual mode — prefer the explicit flag, else fall back to the HF_TOKEN
if tokenFlag == "" { // env var so the token need not appear in argv (visible via `ps`/history).
output.Error("--token <token> required or execute this with pcexec") if tokenFlag != "" {
return tokenFlag
} }
return tokenFlag if env := strings.TrimSpace(os.Getenv("HF_TOKEN")); env != "" {
return env
}
output.Error("--token <token> or HF_TOKEN env required, or execute this with pcexec")
return ""
} }
// RejectTokenInPaddedCell checks if --token was passed in padded-cell mode // RejectTokenInPaddedCell checks if --token was passed in padded-cell mode

View File

@@ -108,57 +108,57 @@ func leafHelpSpec(group, cmd string) (leafHelp, bool) {
"In padded-cell mode, --acc-mgr-token is hidden and password generation can fall back to secret-mgr.", "In padded-cell mode, --acc-mgr-token is hidden and password generation can fall back to secret-mgr.",
}, },
}, },
"user/list": {Summary: "List users", Usage: []string{"hf user list"}, Flags: authFlagHelp()}, "user/list": {Summary: "List users", Usage: []string{"hf user list"}, Flags: authFlagHelp()},
"user/get": {Summary: "Show a user by username", Usage: []string{"hf user get <username>"}, Flags: authFlagHelp()}, "user/get": {Summary: "Show a user by username", Usage: []string{"hf user get <username>"}, Flags: authFlagHelp()},
"user/update": {Summary: "Update a user", Usage: []string{"hf user update <username> [--email <email>] [--full-name <name>] [--pass <password>] [--active <true|false>]"}, Flags: authFlagHelp()}, "user/update": {Summary: "Update a user", Usage: []string{"hf user update <username> [--email <email>] [--full-name <name>] [--pass <password>] [--active <true|false>]"}, Flags: authFlagHelp()},
"user/activate": {Summary: "Activate a user", Usage: []string{"hf user activate <username>"}, Flags: authFlagHelp()}, "user/activate": {Summary: "Activate a user", Usage: []string{"hf user activate <username>"}, Flags: authFlagHelp()},
"user/deactivate": {Summary: "Deactivate a user", Usage: []string{"hf user deactivate <username>"}, Flags: authFlagHelp()}, "user/deactivate": {Summary: "Deactivate a user", Usage: []string{"hf user deactivate <username>"}, Flags: authFlagHelp()},
"user/delete": {Summary: "Delete a user", Usage: []string{"hf user delete <username>"}, Flags: authFlagHelp()}, "user/delete": {Summary: "Delete a user", Usage: []string{"hf user delete <username>"}, Flags: authFlagHelp()},
"user/reset-apikey": {Summary: "Reset a user's API key", Usage: []string{"hf user reset-apikey <username>"}, Flags: authFlagHelp(), Notes: []string{"The new API key is shown once and cannot be retrieved again."}}, "user/reset-apikey": {Summary: "Reset a user's API key", Usage: []string{"hf user reset-apikey <username>"}, Flags: authFlagHelp(), Notes: []string{"The new API key is shown once and cannot be retrieved again."}},
"role/list": {Summary: "List roles", Usage: []string{"hf role list"}, Flags: authFlagHelp()}, "role/list": {Summary: "List roles", Usage: []string{"hf role list"}, Flags: authFlagHelp()},
"role/get": {Summary: "Show a role by name", Usage: []string{"hf role get <role-name>"}, Flags: authFlagHelp()}, "role/get": {Summary: "Show a role by name", Usage: []string{"hf role get <role-name>"}, Flags: authFlagHelp()},
"role/create": {Summary: "Create a role", Usage: []string{"hf role create --name <role-name> [--desc <desc>] [--global <true|false>]"}, Flags: authFlagHelp()}, "role/create": {Summary: "Create a role", Usage: []string{"hf role create --name <role-name> [--desc <desc>] [--global <true|false>]"}, Flags: authFlagHelp()},
"role/update": {Summary: "Update a role", Usage: []string{"hf role update <role-name> [--desc <desc>]"}, Flags: authFlagHelp()}, "role/update": {Summary: "Update a role", Usage: []string{"hf role update <role-name> [--desc <desc>]"}, Flags: authFlagHelp()},
"role/delete": {Summary: "Delete a role", Usage: []string{"hf role delete <role-name>"}, Flags: authFlagHelp()}, "role/delete": {Summary: "Delete a role", Usage: []string{"hf role delete <role-name>"}, Flags: authFlagHelp()},
"role/set-permissions": {Summary: "Replace role permissions", Usage: []string{"hf role set-permissions <role-name> --permission <perm> [--permission <perm> ...]"}, Flags: authFlagHelp()}, "role/set-permissions": {Summary: "Replace role permissions", Usage: []string{"hf role set-permissions <role-name> --permission <perm> [--permission <perm> ...]"}, Flags: authFlagHelp()},
"role/add-permissions": {Summary: "Add permissions to a role", Usage: []string{"hf role add-permissions <role-name> --permission <perm> [--permission <perm> ...]"}, Flags: authFlagHelp()}, "role/add-permissions": {Summary: "Add permissions to a role", Usage: []string{"hf role add-permissions <role-name> --permission <perm> [--permission <perm> ...]"}, Flags: authFlagHelp()},
"role/remove-permissions": {Summary: "Remove permissions from a role", Usage: []string{"hf role remove-permissions <role-name> --permission <perm> [--permission <perm> ...]"}, Flags: authFlagHelp()}, "role/remove-permissions": {Summary: "Remove permissions from a role", Usage: []string{"hf role remove-permissions <role-name> --permission <perm> [--permission <perm> ...]"}, Flags: authFlagHelp()},
"permission/list": {Summary: "List permissions", Usage: []string{"hf permission list"}, Flags: authFlagHelp()}, "permission/list": {Summary: "List permissions", Usage: []string{"hf permission list"}, Flags: authFlagHelp()},
"project/list": {Summary: "List projects", Usage: []string{"hf project list [--owner <username>] [--order-by <created|name>] [--order-by <...>]"}, Flags: authFlagHelp()}, "project/list": {Summary: "List projects", Usage: []string{"hf project list [--owner <username>] [--order-by <created|name>] [--order-by <...>]"}, Flags: authFlagHelp()},
"project/get": {Summary: "Show a project by code", Usage: []string{"hf project get <project-code>"}, Flags: authFlagHelp()}, "project/get": {Summary: "Show a project by code", Usage: []string{"hf project get <project-code>"}, Flags: authFlagHelp()},
"project/create": {Summary: "Create a project", Usage: []string{"hf project create --name <name> [--desc <desc>] [--repo <repo>]"}, Flags: authFlagHelp()}, "project/create": {Summary: "Create a project", Usage: []string{"hf project create --name <name> [--desc <desc>] [--repo <repo>]"}, Flags: authFlagHelp()},
"project/update": {Summary: "Update a project", Usage: []string{"hf project update <project-code> [--name <name>] [--desc <desc>] [--repo <repo>]"}, Flags: authFlagHelp()}, "project/update": {Summary: "Update a project", Usage: []string{"hf project update <project-code> [--name <name>] [--desc <desc>] [--repo <repo>]"}, Flags: authFlagHelp()},
"project/delete": {Summary: "Delete a project", Usage: []string{"hf project delete <project-code>"}, Flags: authFlagHelp()}, "project/delete": {Summary: "Delete a project", Usage: []string{"hf project delete <project-code>"}, Flags: authFlagHelp()},
"project/members": {Summary: "List project members", Usage: []string{"hf project members <project-code>"}, Flags: authFlagHelp()}, "project/members": {Summary: "List project members", Usage: []string{"hf project members <project-code>"}, Flags: authFlagHelp()},
"project/add-member": {Summary: "Add a member to a project", Usage: []string{"hf project add-member <project-code> --user <username> --role <role-name>"}, Flags: authFlagHelp()}, "project/add-member": {Summary: "Add a member to a project", Usage: []string{"hf project add-member <project-code> --user <username> --role <role-name>"}, Flags: authFlagHelp()},
"project/remove-member": {Summary: "Remove a member from a project", Usage: []string{"hf project remove-member <project-code> --user <username>"}, Flags: authFlagHelp()}, "project/remove-member": {Summary: "Remove a member from a project", Usage: []string{"hf project remove-member <project-code> --user <username>"}, Flags: authFlagHelp()},
"milestone/list": {Summary: "List milestones", Usage: []string{"hf milestone list --project <project-code> [--status <status>] [--order-by <due-date|created|name>] [--order-by <...>]"}, Flags: authFlagHelp()}, "milestone/list": {Summary: "List milestones", Usage: []string{"hf milestone list --project <project-code> [--status <status>] [--order-by <due-date|created|name>] [--order-by <...>]"}, Flags: authFlagHelp()},
"milestone/get": {Summary: "Show a milestone by code", Usage: []string{"hf milestone get <milestone-code>"}, Flags: authFlagHelp()}, "milestone/get": {Summary: "Show a milestone by code", Usage: []string{"hf milestone get <milestone-code>"}, Flags: authFlagHelp()},
"milestone/create": {Summary: "Create a milestone", Usage: []string{"hf milestone create --project <project-code> --title <title> [--desc <desc>] [--due <date>]"}, Flags: authFlagHelp()}, "milestone/create": {Summary: "Create a milestone", Usage: []string{"hf milestone create --project <project-code> --title <title> [--desc <desc>] [--due <date>]"}, Flags: authFlagHelp()},
"milestone/update": {Summary: "Update a milestone", Usage: []string{"hf milestone update <milestone-code> [--title <title>] [--desc <desc>] [--status <status>] [--due <date>]"}, Flags: authFlagHelp()}, "milestone/update": {Summary: "Update a milestone", Usage: []string{"hf milestone update <milestone-code> [--title <title>] [--desc <desc>] [--status <status>] [--due <date>]"}, Flags: authFlagHelp()},
"milestone/delete": {Summary: "Delete a milestone", Usage: []string{"hf milestone delete <milestone-code>"}, Flags: authFlagHelp()}, "milestone/delete": {Summary: "Delete a milestone", Usage: []string{"hf milestone delete <milestone-code>"}, Flags: authFlagHelp()},
"milestone/progress": {Summary: "Show milestone progress", Usage: []string{"hf milestone progress <milestone-code>"}, Flags: authFlagHelp()}, "milestone/progress": {Summary: "Show milestone progress", Usage: []string{"hf milestone progress <milestone-code>"}, Flags: authFlagHelp()},
"task/list": {Summary: "List tasks", Usage: []string{"hf task list [--project <project-code>] [--milestone <milestone-code>] [--status <status>] [--taken-by <me|null|username>] [--due-today <true|false>] [--order-by <due-date|priority|created|name>] [--order-by <...>]"}, Flags: authFlagHelp()}, "task/list": {Summary: "List tasks", Usage: []string{"hf task list [--project <project-code>] [--milestone <milestone-code>] [--status <status>] [--taken-by <me|null|username>] [--due-today <true|false>] [--order-by <due-date|priority|created|name>] [--order-by <...>]"}, Flags: authFlagHelp()},
"task/get": {Summary: "Show a task by code", Usage: []string{"hf task get <task-code>"}, Flags: authFlagHelp()}, "task/get": {Summary: "Show a task by code", Usage: []string{"hf task get <task-code>"}, Flags: authFlagHelp()},
"task/create": {Summary: "Create a task", Usage: []string{"hf task create --project <project-code> --title <title> [--milestone <milestone-code>] [--type <type>] [--priority <priority>] [--desc <desc>]"}, Flags: authFlagHelp()}, "task/create": {Summary: "Create a task", Usage: []string{"hf task create --project <project-code> --title <title> [--milestone <milestone-code>] [--type <type>] [--priority <priority>] [--desc <desc>]"}, Flags: authFlagHelp()},
"task/update": {Summary: "Update a task", Usage: []string{"hf task update <task-code> [--title <title>] [--desc <desc>] [--status <status>] [--priority <priority>] [--assignee <username|null>]"}, Flags: authFlagHelp()}, "task/update": {Summary: "Update a task", Usage: []string{"hf task update <task-code> [--title <title>] [--desc <desc>] [--status <status>] [--priority <priority>] [--assignee <username|null>]"}, Flags: authFlagHelp()},
"task/transition": {Summary: "Transition a task to a new status", Usage: []string{"hf task transition <task-code> <status>"}, Flags: authFlagHelp()}, "task/transition": {Summary: "Transition a task to a new status", Usage: []string{"hf task transition <task-code> <status>"}, Flags: authFlagHelp()},
"task/take": {Summary: "Assign a task to the current user", Usage: []string{"hf task take <task-code>"}, Flags: authFlagHelp()}, "task/take": {Summary: "Assign a task to the current user", Usage: []string{"hf task take <task-code>"}, Flags: authFlagHelp()},
"task/delete": {Summary: "Delete a task", Usage: []string{"hf task delete <task-code>"}, Flags: authFlagHelp()}, "task/delete": {Summary: "Delete a task", Usage: []string{"hf task delete <task-code>"}, Flags: authFlagHelp()},
"task/search": {Summary: "Search tasks", Usage: []string{"hf task search --query <text> [--project <project-code>] [--status <status>]"}, Flags: authFlagHelp()}, "task/search": {Summary: "Search tasks", Usage: []string{"hf task search --query <text> [--project <project-code>] [--status <status>]"}, Flags: authFlagHelp()},
"meeting/list": {Summary: "List meetings", Usage: []string{"hf meeting list [--project <project-code>] [--status <status>] [--order-by <created|due-date|name>] [--order-by <...>]"}, Flags: authFlagHelp()}, "meeting/list": {Summary: "List meetings", Usage: []string{"hf meeting list [--project <project-code>] [--status <status>] [--order-by <created|due-date|name>] [--order-by <...>]"}, Flags: authFlagHelp()},
"meeting/get": {Summary: "Show a meeting by code", Usage: []string{"hf meeting get <meeting-code>"}, Flags: authFlagHelp()}, "meeting/get": {Summary: "Show a meeting by code", Usage: []string{"hf meeting get <meeting-code>"}, Flags: authFlagHelp()},
"meeting/create": {Summary: "Create a meeting", Usage: []string{"hf meeting create --project <project-code> --title <title> [--milestone <milestone-code>] [--desc <desc>] [--time <datetime>]"}, Flags: authFlagHelp()}, "meeting/create": {Summary: "Create a meeting", Usage: []string{"hf meeting create --project <project-code> --title <title> [--milestone <milestone-code>] [--desc <desc>] [--time <datetime>]"}, Flags: authFlagHelp()},
"meeting/update": {Summary: "Update a meeting", Usage: []string{"hf meeting update <meeting-code> [--title <title>] [--desc <desc>] [--status <status>] [--time <datetime>]"}, Flags: authFlagHelp()}, "meeting/update": {Summary: "Update a meeting", Usage: []string{"hf meeting update <meeting-code> [--title <title>] [--desc <desc>] [--status <status>] [--time <datetime>]"}, Flags: authFlagHelp()},
"meeting/attend": {Summary: "Attend a meeting", Usage: []string{"hf meeting attend <meeting-code>"}, Flags: authFlagHelp()}, "meeting/attend": {Summary: "Attend a meeting", Usage: []string{"hf meeting attend <meeting-code>"}, Flags: authFlagHelp()},
"meeting/delete": {Summary: "Delete a meeting", Usage: []string{"hf meeting delete <meeting-code>"}, Flags: authFlagHelp()}, "meeting/delete": {Summary: "Delete a meeting", Usage: []string{"hf meeting delete <meeting-code>"}, Flags: authFlagHelp()},
"support/list": {Summary: "List support tickets", Usage: []string{"hf support list [--taken-by <me|null|username>] [--status <status>] [--order-by <due-date|priority|created|name>] [--order-by <...>]"}, Flags: authFlagHelp()}, "support/list": {Summary: "List support tickets", Usage: []string{"hf support list [--taken-by <me|null|username>] [--status <status>] [--order-by <due-date|priority|created|name>] [--order-by <...>]"}, Flags: authFlagHelp()},
"support/get": {Summary: "Show a support ticket by code", Usage: []string{"hf support get <support-code>"}, Flags: authFlagHelp()}, "support/get": {Summary: "Show a support ticket by code", Usage: []string{"hf support get <support-code>"}, Flags: authFlagHelp()},
"support/create": {Summary: "Create a support ticket", Usage: []string{"hf support create --title <title> [--project <project-code>] [--desc <desc>] [--priority <priority>]"}, Flags: authFlagHelp()}, "support/create": {Summary: "Create a support ticket", Usage: []string{"hf support create --title <title> [--project <project-code>] [--desc <desc>] [--priority <priority>]"}, Flags: authFlagHelp()},
"support/update": {Summary: "Update a support ticket", Usage: []string{"hf support update <support-code> [--title <title>] [--desc <desc>] [--status <status>] [--priority <priority>]"}, Flags: authFlagHelp()}, "support/update": {Summary: "Update a support ticket", Usage: []string{"hf support update <support-code> [--title <title>] [--desc <desc>] [--status <status>] [--priority <priority>]"}, Flags: authFlagHelp()},
"support/take": {Summary: "Assign a support ticket to the current user", Usage: []string{"hf support take <support-code>"}, Flags: authFlagHelp()}, "support/take": {Summary: "Assign a support ticket to the current user", Usage: []string{"hf support take <support-code>"}, Flags: authFlagHelp()},
"support/transition": {Summary: "Transition a support ticket to a new status", Usage: []string{"hf support transition <support-code> <status>"}, Flags: authFlagHelp()}, "support/transition": {Summary: "Transition a support ticket to a new status", Usage: []string{"hf support transition <support-code> <status>"}, Flags: authFlagHelp()},
"support/delete": {Summary: "Delete a support ticket", Usage: []string{"hf support delete <support-code>"}, Flags: authFlagHelp()}, "support/delete": {Summary: "Delete a support ticket", Usage: []string{"hf support delete <support-code>"}, Flags: authFlagHelp()},
"proposal/list": {Summary: "List proposals", Usage: []string{"hf proposal list --project <project-code> [--status <status>] [--order-by <created|name>] [--order-by <...>]"}, Flags: authFlagHelp()}, "proposal/list": {Summary: "List proposals", Usage: []string{"hf proposal list --project <project-code> [--status <status>] [--order-by <created|name>] [--order-by <...>]"}, Flags: authFlagHelp()},
"proposal/get": {Summary: "Show a proposal by code", Usage: []string{"hf proposal get <proposal-code>"}, Flags: authFlagHelp()}, "proposal/get": {Summary: "Show a proposal by code", Usage: []string{"hf proposal get <proposal-code>"}, Flags: authFlagHelp()},
"proposal/create": {Summary: "Create a proposal", Usage: []string{"hf proposal create --project <project-code> --title <title> --desc <desc>"}, Flags: authFlagHelp()}, "proposal/create": {Summary: "Create a proposal", Usage: []string{"hf proposal create --project <project-code> --title <title> --desc <desc>"}, Flags: authFlagHelp()},
@@ -171,28 +171,47 @@ func leafHelpSpec(group, cmd string) (leafHelp, bool) {
"proposal/essential/create": {Summary: "Create an essential", Usage: []string{"hf proposal essential create --proposal <proposal-code> --title <title> --type <feature|improvement|refactor> [--desc <desc>]"}, Flags: authFlagHelp()}, "proposal/essential/create": {Summary: "Create an essential", Usage: []string{"hf proposal essential create --proposal <proposal-code> --title <title> --type <feature|improvement|refactor> [--desc <desc>]"}, Flags: authFlagHelp()},
"proposal/essential/update": {Summary: "Update an essential", Usage: []string{"hf proposal essential update <essential-code> --proposal <proposal-code> [--title <title>] [--type <type>] [--desc <desc>]"}, Flags: authFlagHelp()}, "proposal/essential/update": {Summary: "Update an essential", Usage: []string{"hf proposal essential update <essential-code> --proposal <proposal-code> [--title <title>] [--type <type>] [--desc <desc>]"}, Flags: authFlagHelp()},
"proposal/essential/delete": {Summary: "Delete an essential", Usage: []string{"hf proposal essential delete <essential-code> --proposal <proposal-code>"}, Flags: authFlagHelp()}, "proposal/essential/delete": {Summary: "Delete an essential", Usage: []string{"hf proposal essential delete <essential-code> --proposal <proposal-code>"}, Flags: authFlagHelp()},
"calendar/schedule": {Summary: "Create a one-off calendar slot", Usage: []string{"hf calendar schedule <slot-type> <scheduled-at> <estimated-duration> [--job <code>] [--date <yyyy-mm-dd>] [--priority <0-99>]"}, Flags: authFlagHelp()}, "calendar/schedule": {Summary: "Create a one-off calendar slot", Usage: []string{"hf calendar schedule <slot-type> <scheduled-at> <estimated-duration> [--job <code>] [--date <yyyy-mm-dd>] [--priority <0-99>]"}, Flags: authFlagHelp()},
"calendar/show": {Summary: "Show slots for a day", Usage: []string{"hf calendar show [--date <yyyy-mm-dd>]", "hf calendar show --json [--date <yyyy-mm-dd>]"}, Flags: authFlagHelp()}, "calendar/show": {Summary: "Show slots for a day", Usage: []string{"hf calendar show [--date <yyyy-mm-dd>]", "hf calendar show --json [--date <yyyy-mm-dd>]"}, Flags: authFlagHelp()},
"calendar/edit": {Summary: "Edit a calendar slot", Usage: []string{"hf calendar edit <slot-id> [--date <yyyy-mm-dd>] [--slot-type <type>] [--estimated-duration <mins>] [--job <code>] [--scheduled-at <HH:mm>]"}, Flags: authFlagHelp(), Notes: []string{"For virtual plan slots, pass the virtual id like plan-12-2026-04-01."}}, "calendar/edit": {Summary: "Edit a calendar slot", Usage: []string{"hf calendar edit <slot-id> [--date <yyyy-mm-dd>] [--slot-type <type>] [--estimated-duration <mins>] [--job <code>] [--scheduled-at <HH:mm>]"}, Flags: authFlagHelp(), Notes: []string{"For virtual plan slots, pass the virtual id like plan-12-2026-04-01."}},
"calendar/cancel": {Summary: "Cancel a calendar slot", Usage: []string{"hf calendar cancel <slot-id> [--date <yyyy-mm-dd>]"}, Flags: authFlagHelp()}, "calendar/cancel": {Summary: "Cancel a calendar slot", Usage: []string{"hf calendar cancel <slot-id> [--date <yyyy-mm-dd>]"}, Flags: authFlagHelp()},
"calendar/date-list": {Summary: "List future dates with materialized slots", Usage: []string{"hf calendar date-list", "hf calendar date-list --json"}, Flags: authFlagHelp()}, "calendar/date-list": {Summary: "List future dates with materialized slots", Usage: []string{"hf calendar date-list", "hf calendar date-list --json"}, Flags: authFlagHelp()},
"calendar/plan-schedule": {Summary: "Create a recurring schedule plan", Usage: []string{"hf calendar plan-schedule <slot-type> <estimated-duration> --at <HH:mm> [--on-day <day>] [--on-week <1-4>] [--on-month <month>]"}, Flags: authFlagHelp()}, "calendar/plan-schedule": {Summary: "Create a recurring schedule plan", Usage: []string{"hf calendar plan-schedule <slot-type> <estimated-duration> --at <HH:mm> [--on-day <day>] [--on-week <1-4>] [--on-month <month>]"}, Flags: authFlagHelp()},
"calendar/plan-list": {Summary: "List recurring schedule plans", Usage: []string{"hf calendar plan-list", "hf calendar plan-list --json"}, Flags: authFlagHelp()}, "calendar/plan-list": {Summary: "List recurring schedule plans", Usage: []string{"hf calendar plan-list", "hf calendar plan-list --json"}, Flags: authFlagHelp()},
"calendar/plan-edit": {Summary: "Edit a recurring schedule plan", Usage: []string{"hf calendar plan-edit <plan-id> [--at <HH:mm>] [--on-day <day>] [--on-week <1-4>] [--on-month <month>] [--slot-type <type>] [--estimated-duration <mins>]"}, Flags: authFlagHelp()}, "calendar/plan-edit": {Summary: "Edit a recurring schedule plan", Usage: []string{"hf calendar plan-edit <plan-id> [--at <HH:mm>] [--on-day <day>] [--on-week <1-4>] [--on-month <month>] [--slot-type <type>] [--estimated-duration <mins>]"}, Flags: authFlagHelp()},
"calendar/plan-cancel": {Summary: "Cancel a recurring schedule plan", Usage: []string{"hf calendar plan-cancel <plan-id>"}, Flags: authFlagHelp()}, "calendar/plan-cancel": {Summary: "Cancel a recurring schedule plan", Usage: []string{"hf calendar plan-cancel <plan-id>"}, Flags: authFlagHelp()},
"comment/add": {Summary: "Add a comment to a task", Usage: []string{"hf comment add --task <task-code> --content <text>"}, Flags: authFlagHelp()}, "comment/add": {Summary: "Add a comment to a task", Usage: []string{"hf comment add --task <task-code> --content <text>"}, Flags: authFlagHelp()},
"comment/list": {Summary: "List comments for a task", Usage: []string{"hf comment list --task <task-code>"}, Flags: authFlagHelp()}, "comment/list": {Summary: "List comments for a task", Usage: []string{"hf comment list --task <task-code>"}, Flags: authFlagHelp()},
"worklog/add": {Summary: "Add a work log entry", Usage: []string{"hf worklog add --task <task-code> --hours <n> [--desc <text>] [--date <yyyy-mm-dd>]"}, Flags: authFlagHelp()}, "worklog/add": {Summary: "Add a work log entry", Usage: []string{"hf worklog add --task <task-code> --hours <n> [--desc <text>] [--date <yyyy-mm-dd>]"}, Flags: authFlagHelp()},
"worklog/list": {Summary: "List work logs by task or user", Usage: []string{"hf worklog list [--task <task-code>] [--user <username>]"}, Flags: authFlagHelp()}, "worklog/list": {Summary: "List work logs by task or user", Usage: []string{"hf worklog list [--task <task-code>] [--user <username>]"}, Flags: authFlagHelp()},
"monitor/overview": {Summary: "Show monitor overview", Usage: []string{"hf monitor overview"}, Flags: authFlagHelp()}, "monitor/overview": {Summary: "Show monitor overview", Usage: []string{"hf monitor overview"}, Flags: authFlagHelp()},
"monitor/server": {Summary: "Manage monitor servers", Usage: []string{"hf monitor server list", "hf monitor server get <identifier>", "hf monitor server create --identifier <identifier> [--name <display-name>]", "hf monitor server delete <identifier>"}, Flags: authFlagHelp()}, "monitor/server": {Summary: "Manage monitor servers", Usage: []string{"hf monitor server list", "hf monitor server get <identifier>", "hf monitor server create --identifier <identifier> [--name <display-name>]", "hf monitor server delete <identifier>"}, Flags: authFlagHelp()},
"monitor/server/list": {Summary: "List monitor servers", Usage: []string{"hf monitor server list"}, Flags: authFlagHelp()}, "monitor/server/list": {Summary: "List monitor servers", Usage: []string{"hf monitor server list"}, Flags: authFlagHelp()},
"monitor/server/get": {Summary: "Show a monitor server by identifier", Usage: []string{"hf monitor server get <identifier>"}, Flags: authFlagHelp()}, "monitor/server/get": {Summary: "Show a monitor server by identifier", Usage: []string{"hf monitor server get <identifier>"}, Flags: authFlagHelp()},
"monitor/server/create": {Summary: "Create a monitor server", Usage: []string{"hf monitor server create --identifier <identifier> [--name <display-name>]"}, Flags: authFlagHelp()}, "monitor/server/create": {Summary: "Create a monitor server", Usage: []string{"hf monitor server create --identifier <identifier> [--name <display-name>]"}, Flags: authFlagHelp()},
"monitor/server/delete": {Summary: "Delete a monitor server", Usage: []string{"hf monitor server delete <identifier>"}, Flags: authFlagHelp()}, "monitor/server/delete": {Summary: "Delete a monitor server", Usage: []string{"hf monitor server delete <identifier>"}, Flags: authFlagHelp()},
"monitor/api-key": {Summary: "Manage monitor API keys", Usage: []string{"hf monitor api-key generate <identifier>", "hf monitor api-key revoke <identifier>"}, Flags: authFlagHelp()}, "monitor/api-key": {Summary: "Manage monitor API keys", Usage: []string{"hf monitor api-key generate <identifier>", "hf monitor api-key revoke <identifier>"}, Flags: authFlagHelp()},
"monitor/api-key/generate": {Summary: "Generate a monitor API key", Usage: []string{"hf monitor api-key generate <identifier>"}, Flags: authFlagHelp()}, "monitor/api-key/generate": {Summary: "Generate a monitor API key", Usage: []string{"hf monitor api-key generate <identifier>"}, Flags: authFlagHelp()},
"monitor/api-key/revoke": {Summary: "Revoke a monitor API key", Usage: []string{"hf monitor api-key revoke <identifier>"}, Flags: authFlagHelp()}, "monitor/api-key/revoke": {Summary: "Revoke a monitor API key", Usage: []string{"hf monitor api-key revoke <identifier>"}, Flags: authFlagHelp()},
"knowledge-base/list": {Summary: "List knowledge bases", Usage: []string{"hf knowledge-base list [--project <project-code>]"}, Flags: authFlagHelp()},
"knowledge-base/get": {Summary: "Show a knowledge base by code", Usage: []string{"hf knowledge-base get <kb-code>"}, Flags: authFlagHelp()},
"knowledge-base/tree": {Summary: "Show the full topic/category/fact tree", Usage: []string{"hf knowledge-base tree <kb-code>"}, Flags: authFlagHelp()},
"knowledge-base/topics": {Summary: "List topics in a knowledge base", Usage: []string{"hf knowledge-base topics <kb-code>"}, Flags: authFlagHelp()},
"knowledge-base/create": {Summary: "Create a knowledge base", Usage: []string{"hf knowledge-base create --title <title> [--desc <description>]"}, Flags: authFlagHelp()},
"knowledge-base/update": {Summary: "Update a knowledge base", Usage: []string{"hf knowledge-base update <kb-code> [--title <title>] [--desc <description>]"}, Flags: authFlagHelp()},
"knowledge-base/delete": {Summary: "Delete a knowledge base", Usage: []string{"hf knowledge-base delete <kb-code>"}, Flags: authFlagHelp()},
"knowledge-base/link": {Summary: "Link a knowledge base to a project", Usage: []string{"hf knowledge-base link <kb-code> --project <project-code>"}, Flags: authFlagHelp()},
"knowledge-base/unlink": {Summary: "Unlink a knowledge base from a project", Usage: []string{"hf knowledge-base unlink <kb-code> --project <project-code>"}, Flags: authFlagHelp()},
"knowledge-base/add-topic": {Summary: "Add a topic to a knowledge base", Usage: []string{"hf knowledge-base add-topic <kb-code> --topic <name> [--desc <description>]"}, Flags: authFlagHelp()},
"knowledge-base/update-topic": {Summary: "Update a topic", Usage: []string{"hf knowledge-base update-topic <topic-id> [--topic <name>] [--desc <description>]"}, Flags: authFlagHelp()},
"knowledge-base/delete-topic": {Summary: "Delete a topic", Usage: []string{"hf knowledge-base delete-topic <topic-id>"}, Flags: authFlagHelp()},
"knowledge-base/add-category": {Summary: "Add a category under a topic", Usage: []string{"hf knowledge-base add-category --topic <topic-id> --name <name> [--parent <category-id>] [--desc <description>]"}, Flags: authFlagHelp()},
"knowledge-base/update-category": {Summary: "Update a category", Usage: []string{"hf knowledge-base update-category <category-id> [--name <name>] [--parent <category-id>] [--desc <description>]"}, Flags: authFlagHelp()},
"knowledge-base/delete-category": {Summary: "Delete a category", Usage: []string{"hf knowledge-base delete-category <category-id>"}, Flags: authFlagHelp()},
"knowledge-base/add-fact": {Summary: "Add a fact under a topic (optionally a category)", Usage: []string{"hf knowledge-base add-fact --topic <topic-id> [--category <category-id>] --fact <text>"}, Flags: authFlagHelp()},
"knowledge-base/update-fact": {Summary: "Update a fact", Usage: []string{"hf knowledge-base update-fact <fact-id> [--fact <text>] [--category <category-id>]"}, Flags: authFlagHelp()},
"knowledge-base/delete-fact": {Summary: "Delete a fact", Usage: []string{"hf knowledge-base delete-fact <fact-id>"}, Flags: authFlagHelp()},
} }
if group == "" { if group == "" {