Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
4104020
feat(smartgroup): introduce Template + ParamSpec foundation types
ktn-jamf May 13, 2026
eca67d9
fix(smartgroup): reject partial int parses, tighten error-content tests
ktn-jamf May 13, 2026
ab5d681
feat(smartgroup): add JSS-verified criterion-name constants
ktn-jamf May 13, 2026
28be92f
feat(smartgroup): add template Library registry with Register/Lookup/…
ktn-jamf May 13, 2026
654edb9
feat(smartgroup): add 6 encryption templates
ktn-jamf May 13, 2026
1844443
feat(smartgroup): add 4 software-update templates
ktn-jamf May 13, 2026
305919c
feat(smartgroup): add 5 MDM-health templates
ktn-jamf May 13, 2026
e7b2707
feat(smartgroup): add 4 compliance-basics templates
ktn-jamf May 13, 2026
a875bda
feat(smartgroup): add 4 lifecycle-hygiene templates
ktn-jamf May 13, 2026
9b126ec
test(smartgroup): add full-library integration tests (23 templates, 5…
ktn-jamf May 13, 2026
2bc5d95
feat(smartgroup): add CountMembers helper for post-apply membership c…
ktn-jamf May 13, 2026
12f413b
feat(commands): scaffold pro smart-group namespace (alias sg) with st…
ktn-jamf May 13, 2026
cd861e3
feat(commands): implement pro smart-group templates (list with --cate…
ktn-jamf May 13, 2026
8c71e6a
feat(commands): implement pro smart-group preview with per-template p…
ktn-jamf May 13, 2026
cc969d9
feat(commands): implement pro smart-group apply with idempotent name-…
ktn-jamf May 13, 2026
45ccbcf
feat(commands): implement pro smart-group verify-templates with live-…
ktn-jamf May 13, 2026
6cf4a11
fix(smartgroup): resolve errcheck and staticcheck lint failures
ktn-jamf May 13, 2026
dde2a61
fix(smartgroup): check recalcGroup HTTP status; add --yes guard regre…
ktn-jamf May 13, 2026
7e57af0
fix(smartgroup): encryption/not-encrypted uses 'No Partitions Encrypt…
ktn-jamf May 13, 2026
438e945
fix(smartgroup): correct 4 JSS-verified strings — JAMF caps, Gatekeep…
ktn-jamf May 13, 2026
9d66718
fix(smartgroup): jamf-binary-outdated uses 'not current'; ade-enrolle…
ktn-jamf May 13, 2026
477d591
docs(design-patterns): add spec + plan for pro smart-group templates
ktn-jamf May 13, 2026
ca65088
Merge branch 'main' into claude/vigorous-davinci-88fbc0
neilmartin83 May 13, 2026
bba258f
fix(smartgroup): address #205 review — input validation, cleanup erro…
ktn-jamf May 13, 2026
c2ed951
Merge branch 'main' into claude/vigorous-davinci-88fbc0
ktn-jamf May 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3,418 changes: 3,418 additions & 0 deletions docs/plans/pro-smart-group-templates-plan-2026-05-12.md

Large diffs are not rendered by default.

573 changes: 573 additions & 0 deletions docs/plans/pro-smart-group-templates-spec-2026-05-12.md

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion generator/monolith/split.go
Original file line number Diff line number Diff line change
Expand Up @@ -769,7 +769,8 @@ func toNode(v any) *yaml.Node {
sort.Strings(keys)
n := &yaml.Node{Kind: yaml.MappingNode}
for _, k := range keys {
n.Content = append(n.Content,
n.Content = append(
n.Content,
&yaml.Node{Kind: yaml.ScalarNode, Value: k},
toNode(x[k]),
)
Expand Down
1 change: 1 addition & 0 deletions internal/commands/aliases.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ var commandAliases = map[string][]string{
// so it isn't detected as a singleton and retains the plural generated name.
"jamf-protect": {"jp"},
"jamf-connects": {"jamf-connect"},
"smart-group": {"sg"},
}

// rootAliases maps root-level command names to short aliases.
Expand Down
3 changes: 2 additions & 1 deletion internal/commands/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,8 @@ func zshCompletionCandidates(home string) []string {
}
}

candidates = append(candidates,
candidates = append(
candidates,
filepath.Join(home, ".zsh", "completions"),
filepath.Join(home, ".local", "share", "zsh", "site-functions"),
"/usr/local/share/zsh/site-functions",
Expand Down
1 change: 1 addition & 0 deletions internal/commands/groups.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ var proGroupMap = map[string]string{
"computer-extension-attributes": groupComputers,
"computer-inventory-collection-settings": groupComputers,
"smart-computer-groups": groupComputers,
"smart-group": groupComputers,
"static-computer-groups": groupComputers,
"computers-inventory": groupComputers,
// macOS-only Dock customization (Pro: Settings > Computer management > Dock items)
Expand Down
1 change: 1 addition & 0 deletions internal/commands/pro.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ func newProCmd(cliCtx *registry.CLIContext) *cobra.Command {
cmd.AddCommand(newDiffCmd())
cmd.AddCommand(newGroupToolsCmd(cliCtx))
cmd.AddCommand(newDeviceCmd(cliCtx))
cmd.AddCommand(newSmartGroupCmd(cliCtx))
// Platform API commands (require platform gateway auth)
cmd.AddCommand(newBlueprintsCmd(cliCtx))
cmd.AddCommand(newComplianceBenchmarksCmd(cliCtx))
Expand Down
39 changes: 26 additions & 13 deletions internal/commands/pro_bulk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -640,7 +640,8 @@ func TestAddToGroup_DryRunDefault(t *testing.T) {
cliCtx := newBulkCLIContext(mock)

cmd := newBulkCmd(cliCtx)
_, stderr, err := runCobraCmd(t, cmd, "add-to-group",
_, stderr, err := runCobraCmd(
t, cmd, "add-to-group",
"--target-group", "Quarantine",
"--group", "Lab Macs",
)
Expand Down Expand Up @@ -673,7 +674,8 @@ func TestAddToGroup_YesDispatches(t *testing.T) {
cliCtx := newBulkCLIContext(mock)

cmd := newBulkCmd(cliCtx)
_, _, err := runCobraCmd(t, cmd, "add-to-group",
_, _, err := runCobraCmd(
t, cmd, "add-to-group",
"--target-group", "Quarantine",
"--group", "Lab Macs",
"--yes",
Expand Down Expand Up @@ -705,7 +707,8 @@ func TestRemoveFromGroup_YesDispatches(t *testing.T) {
cliCtx := newBulkCLIContext(mock)

cmd := newBulkCmd(cliCtx)
_, _, err := runCobraCmd(t, cmd, "remove-from-group",
_, _, err := runCobraCmd(
t, cmd, "remove-from-group",
"--target-group", "Quarantine",
"--group", "Lab Macs",
"--yes",
Expand Down Expand Up @@ -737,7 +740,8 @@ func TestAddToGroup_SmartGroupRejected(t *testing.T) {
cliCtx := newBulkCLIContext(mock)

cmd := newBulkCmd(cliCtx)
_, _, err := runCobraCmd(t, cmd, "add-to-group",
_, _, err := runCobraCmd(
t, cmd, "add-to-group",
"--target-group", "SmartTarget",
"--group", "Lab Macs",
"--yes",
Expand Down Expand Up @@ -779,7 +783,8 @@ func TestAddToGroup_FromFile(t *testing.T) {
cliCtx := newBulkCLIContext(mock)

cmd := newBulkCmd(cliCtx)
_, _, err := runCobraCmd(t, cmd, "add-to-group",
_, _, err := runCobraCmd(
t, cmd, "add-to-group",
"--target-group", "Quarantine",
"--from-file", filePath,
"--yes",
Expand Down Expand Up @@ -824,7 +829,8 @@ func TestFromFileMutualExclusion(t *testing.T) {
cliCtx := newBulkCLIContext(mock)

cmd := newBulkCmd(cliCtx)
_, _, err := runCobraCmd(t, cmd, "add-to-group",
_, _, err := runCobraCmd(
t, cmd, "add-to-group",
"--target-group", "Quarantine",
"--from-file", filePath,
"--group", "Lab Macs",
Expand Down Expand Up @@ -856,7 +862,8 @@ func TestSendCommand_DryRunDefault(t *testing.T) {
cliCtx := newBulkCLIContext(mock)

cmd := newBulkCmd(cliCtx)
_, stderr, err := runCobraCmd(t, cmd, "send-command",
_, stderr, err := runCobraCmd(
t, cmd, "send-command",
"--command", "BlankPush",
"--group", "Lab Macs",
)
Expand Down Expand Up @@ -888,7 +895,8 @@ func TestSendCommand_YesDispatches(t *testing.T) {
cliCtx := newBulkCLIContext(mock)

cmd := newBulkCmd(cliCtx)
_, _, err := runCobraCmd(t, cmd, "send-command",
_, _, err := runCobraCmd(
t, cmd, "send-command",
"--command", "BlankPush",
"--group", "Lab Macs",
"--yes",
Expand All @@ -910,7 +918,8 @@ func TestSendCommand_DestructiveRequiresConfirm(t *testing.T) {
for _, cmd2 := range []string{"EraseDevice", "DeviceLock"} {
t.Run(cmd2, func(t *testing.T) {
cmd := newBulkCmd(cliCtx)
_, _, err := runCobraCmd(t, cmd, "send-command",
_, _, err := runCobraCmd(
t, cmd, "send-command",
"--command", cmd2,
"--from-file", "/dev/null",
"--yes",
Expand All @@ -935,7 +944,8 @@ func TestSendCommand_DestructiveWithBothFlags(t *testing.T) {
cliCtx := newBulkCLIContext(mock)

cmd := newBulkCmd(cliCtx)
_, _, err := runCobraCmd(t, cmd, "send-command",
_, _, err := runCobraCmd(
t, cmd, "send-command",
"--command", "EraseDevice",
"--from-file", "/dev/null",
"--yes",
Expand All @@ -952,7 +962,8 @@ func TestSendCommand_DestructiveRequiresYesToo(t *testing.T) {
cliCtx := newBulkCLIContext(mock)

cmd := newBulkCmd(cliCtx)
_, _, err := runCobraCmd(t, cmd, "send-command",
_, _, err := runCobraCmd(
t, cmd, "send-command",
"--command", "EraseDevice",
"--from-file", "/dev/null",
// --yes is NOT set, --confirm-destructive IS set
Expand All @@ -972,7 +983,8 @@ func TestSendCommand_InvalidCommandName(t *testing.T) {
cliCtx := newBulkCLIContext(mock)

cmd := newBulkCmd(cliCtx)
_, _, err := runCobraCmd(t, cmd, "send-command",
_, _, err := runCobraCmd(
t, cmd, "send-command",
"--command", "SelfDestruct",
"--from-file", "/dev/null",
"--yes",
Expand Down Expand Up @@ -1040,7 +1052,8 @@ func TestSendCommand_PartialFailure(t *testing.T) {
cliCtx := newBulkCLIContext(mock)

cmd := newBulkCmd(cliCtx)
_, stderr, err := runCobraCmd(t, cmd, "send-command",
_, stderr, err := runCobraCmd(
t, cmd, "send-command",
"--command", "BlankPush",
"--group", "Lab Macs",
"--yes",
Expand Down
6 changes: 4 additions & 2 deletions internal/commands/pro_overview.go
Original file line number Diff line number Diff line change
Expand Up @@ -1278,7 +1278,8 @@ func buildEnrollmentItems(
items = append(items, item("ADE Token Expires", "None configured"))
}

items = append(items,
items = append(
items,
item("ADE Sync Status", get("ade_sync_status")),
item("Computer Prestages", get("computer_prestages")),
item("Mobile Device Prestages", get("md_prestages")),
Expand All @@ -1295,7 +1296,8 @@ func buildEnrollmentItems(
items = append(items, overviewItem{label, display, t.Color})
}

items = append(items,
items = append(
items,
overviewItem{}, // blank separator
getItem("APNs Certificate", "apns_cert"),
getItem("Built-in CA Expires", "ca_expires"),
Expand Down
3 changes: 2 additions & 1 deletion internal/commands/pro_platform_devices.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ import (

// uuidPattern matches the standard UUID format (8-4-4-4-12 hex digits).
var uuidPattern = regexp.MustCompile(
`^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`)
`^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`,
)

func newPlatformDevicesCmd(cliCtx *registry.CLIContext) *cobra.Command {
cmd := &cobra.Command{
Expand Down
Loading