where-to-make-changes
/where-to-make-changes
Wskazuje plik i symbol do edycji, gdy wiadomo, co zmienić, ale nie wiadomo, gdzie, według gotowej tabeli.
- Autor
- jamf-concepts ↗
- Repozytorium
- jamf-cli ↗
- Licencja
- MIT
- Ostatnia zmiana repozytorium
- 26.10.2026
Ocena i bezpieczeństwo
- Pliki w paczce
- 1
- Skrypty
- 0
- Komentarze HTML
- 0
- Adresy URL w SKILL.md
- 0
Brak sygnałów. Repozytorium zmienione 26.10.2026, bez odwołań do wycofanych modeli i API.
MIT, otwarta: można używać i zmieniać, z zachowaniem informacji o autorze.
Co robi i kiedy
Wskazuje plik i symbol do edycji, gdy wiadomo, co zmienić, ale nie wiadomo, gdzie, według gotowej tabeli.
Oryginalny opis (EN)
Use when you know what to change but not where — a lookup table mapping a desired change (privileges, 403 hints, generator behavior, classic/platform/security/protect commands, output, config, site) to the exact file and symbol to edit.
Jak działa
- Classic Scope
- Shared Command Helpers
- Cross-Product Dashboard and MCP
Podgląd SKILL.md
---
name: where-to-make-changes
description: Use when you know what to change but not where — a lookup table mapping a desired change (privileges, 403 hints, generator behavior, classic/platform/security/protect commands, output, config, site) to the exact file and symbol to edit.
---
# Where to Make Changes
| I want to... | Edit this file |
|---|---|
| Surface required privileges (annotation, catalog, 403 hint) | `generator/parser/generator.go` (`opAnnotations`) for the annotation; `internal/commands/root.go` for the `privileges` catalog field; `internal/commands/privilege_error.go` for the 403 hint |
| Change what the `commands` catalog says a gateway endpoint needs | `gatewayPrivilegesOf` / `gatewayPermissionsOf` (`internal/commands/gateway_coverage.go`); the annotation is stamped by `generator/parser/generator.go` (`opAnnotations`, plus `applyGatewayAnn` for the synthesized `apply`) and `generator/classic/generator.go` (`gatewayPrivAnn`, per method) |
| Change what a 403 says a permission is *called* (section + picker name) | `internal/privileges/catalogue.go` — a hand transcription of Jamf's permissions-map article, guarded by `TestCatalogueMatchesThePublishedMap` against the committed copy in `internal/privileges/permissions-map.md` (`make sync-permissions-map`) and by `TestCatalogueCoversEveryScopeThisCLISends`. Rendering is `internal/privileges/privileges.go` |
| Change the 403 hint for a Pro/Classic request **through the gateway** | `forbiddenHint` (`internal/client/client.go`), which reads `gateway.Scopes(method, path)` — the request knows the endpoint, the command annotation does not |
| Change what a platform 403 says when the *scope level* is wrong rather than the grants | `scopeMismatchHint` / `hasGatewayErrorCode` (`internal/commands/privilege_error.go`), branched on the gateway's `OWNERSHIP_FORBIDDEN` code; the declared levels it names come from `scopesOf` (`internal/commands/platform_scope.go`) |
| Change what is redacted from a `-vvv` body log | `redact.Body` (`internal/redact/redact.go`) — JSON, form-encoded and Classic XML. `RedactCredentialBody`, `RedactTokenBody` and `RedactBodyForLog` (`internal/client/client.go`) all delegate to it, and every body log goes through it |
| Change behavior of all modern API commands | `generator/parser/generator.go` (`resourceTemplate`) |
| Change behavior of all classic API commands | `generator/classic/generator.go` (`classicResourceTemplate`) |
| Change how OpenAPI specs are parsed | `generator/parser/parser.go` |
| Change singleton detection logic | `generator/parser/parser.go` → `detectSingleton()` |
| Change which schemas `detectNameField` may answer from | `representationSchemas` (`generator/parser/monolithparse.go`) — the per-resource `$ref` closure minus every `x-action` payload, because an action's body is a command and its fields are arguments. One components map means the closure reaches the shared `ExportField{fieldName}` body of every `/export` action, which cost `/v1/packages` and `/v2/jamf-remote-assist/session` their name field. `detectIDField` keeps the full closure |
| Change which side of a no-param GET collision keeps `get`/`list` | `isResourceRootPath` (`generator/parser/parser.go`), read against the `Resource.Root` the grouping derived — **not** against a root inferred from the colliding paths, which got three cases wrong (see `TestNoParamRootKeepsThePlainVerb`). A resource with no root endpoint keeps terminal-segment naming throughout (`pro ldap` has `groups`/`servers`/`ldap-servers` and no `list`, there being no `GET /v1/ldap`) |
| Change what makes a sub-path a nested command of its own | `subResourceWriteMethods` / `subResourceRoots` (`generator/parser/subresource.go`) — a sub-path carrying `PUT`/`PATCH`/`DELETE` **on the sub-path itself** is a separately-writable object, so `pro sso-settings cert delete` rather than `pro sso-settings delete` deleting the certificate. Derived, no override table; `TestSubResourcePartitionIsPinned` pins the 9 it admits and the composition of what it refuses |
| Rename a lone no-param GET that kept `list` on a non-collection path | `renameLoneNonCanonicalList` (`generator/parser/parser.go`) — **scoped to a resource the sub-resource split touched**, deliberately: six unrelated resources have the same shape (`pro m2m list` returns a tenant id, `pro jamf-protect list` the Protect plans) and renaming those is seven breaking renames this change is not |
| Change which tag names a resource whose root carries two | `groupTag` (`generator/parser/taggroup.go`) — the tag of the group's **own root collection**, then the most frequent tag. Eight roots in the 11.31.1 monolith hold two tags, which is the correct grouping; last-write-wins over sorted paths named three of them wrongly, shipping the `/v1/users` CRUD as `pro smart-user-groups` with an `apply` that deleted and recreated a **user record**. `TestMergedTagGroupsAreNamedByTheirRootCollection` is the guard |
| Change what happens when a path's methods disagree on their tag | `soleBaseTagOf` (`generator/parser/monolithparse.go`) — refused, naming the path and both tags. The tag bounds what may be grouped and a path joins exactly one group, so nothing downstream can express a path carrying two; taking the first was silent, and `TestParseMonolith_LosesNoEndpoint` counts endpoints rather than checking where they went. Zero live paths disagree, so it refuses nothing today |
| Change how an untagged path is bounded | `boundKey` (`generator/parser/taggroup.go`) — by its own root, not by the empty tag it shares with every other untagged path. Pooling them hands `resolveRootsWithinBound`'s no-survivor fallback a set of unrelated roots and folds them all into the largest: `/v1/health-check` and `/v1/health-status` would come out as one resource. No live path is untagged; `TestPathRootRule_TopLevelCollectionIsAlwaysARoot` is the guard |
| Point a retired `<resource> <verb>` invocation at where the operation went | `movedInvocations` (`internal/commands/moved_invocations.go`) — keyed on the command path **after** resource-alias resolution, so one entry covers every spelling. A resource alias resolves the first token only, so cobra answered the other 43 with `unknown command`, indistinguishable from a typo on the class that is hardest to self-diagnose |
| Change what a former leaf that became a command group answers | `formerLeafGroups` (`internal/commands/moved_invocations.go`) and `guardFormerLeafGroups` (wired in `root.go` **after** `guardUnknownSubcommands`, whose RunE it wraps) — a data request (`--output`, `--field`, `--select`, `--out-file`) is refused naming the leaf; a bare invocation and a typo are unchanged. `pro csas token -o json` used to pipe cobra's help text into `jq` at exit 0 |
| Change which of two colliding operations keeps a plain verb | `qualifyDuplicateVerbsOutsideTheRoot` (`generator/parser/parser.go`), run last in `buildResourceShell` — the resource's own root or root/`{id}` keeps `create`/`update`/`delete` and the loser is qualified by the last non-param segment of its own path (`ldap-create`, `dashboard-delete`). It touches writes only: a colliding GET is left to `dedupeOperations`, whose collection-path preference is what `resourceGetDetailPathOverrides` depends on. Before it, `dedupeOperations` **deleted** the loser with a warning and exit 0, which cost `pro enrollment-customization` its own `create`, `update` and `delete` and pointed its `apply` at an LDAP panel path |
| Separate two methods on the same path whose derived names collide | the fallback in `disambiguateSameTerminalOps` (`generator/parser/parser.go`) — `methodVerbPrefix` when the path-derived name is already taken by a sibling on the *same* path, which is what `buildDisambiguatedName` cannot express. Three methods on `/v2/computer-prestages/{id}/scope` all reduced to `scope-by-id` and two were dropped |
| Decide which of two colliding sub-path operations keeps a plain verb when **neither** addresses the root | `disambiguateSameTerminalOps` (`generator/parser/parser.go`), which now takes the resource root — when no member of the collision group is the resource's own collection or item, none has a claim on the plain verb and each is qualified by `owningNoun`. That helper returns "" for a *parameter*, which is what keeps `pro certificate-authority der`/`der-by-id` (`active/der` vs `{id}/der` — an item selector, not a noun) off the rule, and version-strips both sides, a `PathGroup` root keeping a leading `preview` where `stripVersionSegments` drops it |
| Stop an API version leaking into a command name | `buildDisambiguatedName` (`generator/parser/parser.go`) skips a version segment. Two paths differing *only* in version are folded by `deduplicateVersionedOps` long before, so a version segment here always sits beside a real difference. Three names carried one (`v-4-computers-inventory-erase`, `v-2-scope-delete-multiple`); `TestNoCommandNameCarriesAnAPIVersion` is the guard |
| Refuse to ingest a path at all | `droppedPaths` / `droppedTags` (`generator/parser/pathgroup.go`), read by `KeepPath`. Reach for a path when the endpoint is superseded at a *different path shape* — `deduplicateVersionedOps` keys on the shape, so it keeps both and the withdrawn one takes the plain verb (inventory-preload v1; `/v1/computer-inventory/{id}/erase`, where v4 spells the collection segment plurally). `droppedByPolicy` in `monolithparse_test.go` has to name it too |
| Change what a destructive command's confirmation, dry-run or bulk request says or sends | `actionMethod` / `actionPhrase` / `actionPhraseTitle` / `actionPhrasePast` / `actionNoun` in the `resourceTemplate` FuncMap (`generator/parser/generator.go`). The destructive bulk and confirmation blocks are shared by a plain `DELETE` and by a destructive `x-action`, and used to hardcode both: `pro mobile-device-groups erase --from-file` sent `DELETE` to a POST-only path and reported "Deleted", and the group prompt offered to "delete N computer-inventory" for a fleet wipe. An action quotes its own name rather than being conjugated |
| Make a duplicate command registration fail instead of shipping a resource twice | `CheckRegistryCollisions` — `generator/parser/generator.go` for the modern generator, `generator/classic/generator.go` for Classic. Called from each `GenerateRegistry` *and* ahead of `main.go`'s per-resource write loop, because a shared file name means the second `Generate` overwrites the first and the resource is already gone by the time the registry is emitted. Both symptoms are silent: a repeated `root.AddCommand(NewXCmd(ctx))` compiles, so the resource ships twice in `pro --help` and cobra resolves every path beneath it to the first copy, and `make verify-generated` reproduces the duplicate faithfully so the tree reads as up to date (#362). It refuses rather than collapsing the repeat — two resources deriving one identity means the grouping upstream is wrong |
| Make a stale `pro.go` wiring key fail instead of shipping an extra command | `findWiringParent` / `recordStaleProWiring` (`internal/commands/pro.go`), asserted by `TestProWiringResolvesEveryNameItUses`. All three helpers key on names the generator derives, and a stale key used to be a silent no-op — which for `removeSubcommand` leaves the generated command in place and for `replaceSubcommand` adds the replacement beside it. The child half was unguarded, and that is the half that ships a second destructive command |
| Change how the typed resource token is read for the deprecation warning or the moved-verb refusal | `resourceTokenAfter` / `visibleFlags` (`internal/commands/deprecated_names.go`) — flags are stripped by cobra's own rules, asking the assembled flag set whether one consumes the next argument. Assemble the set: `cmd.Flags()` holds only the command's *own* flags until cobra's unexported merge has run, so a root persistent flag looks unrecognised and eats the token |
| Record, or refuse, an operation the shipped surface loses to a name collision | `generator/parser/testdata/dropped-operations.tsv`, pinned by `TestTheDroppedOperationSetIsPinned`. It runs `dedupeOperations` — the pass `TestParseMonolith_LosesNoEndpoint` is one stage short of — and refuses outright a dropped operation that addresses its resource's own root while the winner does not |
| Refuse a verb whose *meaning* changed with the spelling of the resource it was typed against | `deprecatedNameVerbMoves` and `guardDeprecatedNameVerbMoves` (`internal/commands/moved_invocations.go`), wired in `root.go` **after** `guardStrayPositionals` — it fronts the leaf's `Args` as well as its `RunE`, and that walk installs a validator only on a leaf that has none. `movedInvocations` cannot cover this: it is keyed post-alias-resolution, and the two spellings differ only in the first token. The `Args` relaxation is what makes the refusal reachable from the arity the old command took |
| Redirect a retired resource name at a nested sub-resource | `nestedAliases` (`internal/commands/deprecated_names.go`) — a cobra alias is a name on one command and can only reach a direct child of `pro`, so these four register a hidden second instance of the nested subtree from `generated.NestedResourceCommands()`. Governed by the same `deprecatedNamesRemovedAfter` date |
| Mark a GET-only, no-`{id}` resource as a singleton (so it gets `get`, not `list`) | `generator/parser/parser.go` → `readOnlySingletonPaths` map |
| Let an endpoint's documented non-2xx status render instead of becoming an exit-code error | `generator/parser/parser.go` → `documentedStatusResults` map; plumbing is `registry.WithAllowedStatuses` |
| Change multi-family spec splitting | `generator/parser/parser.go` → `splitByPathFamilies()` |
| Add/change alternate lookup fields (--serial, --udid) — modern API | `generator/parser/parser.go` → `resourceLookupFields` map |
| Add a CLI flag alias for a classic lookup (e.g. `--serial` → `--serialnumber`) | `generator/classic/generator.go` → `lookupFlagAliases` map |
| Fix a resource name auto-pluralization issue | `generator/parser/parser.go` → `resourceNameOverrides` map |
| Change which API version a resource ships | nothing — `deduplicateVersionedOps` keeps the highest version per version-stripped path shape inside the resource, read from the paths and not from a name. Check `resourceGetDetailPathOverrides`, `resourceListDetailPathOverrides` and `internal/commands/pro_device_actions.go` for hand-pinned versions of the same resource |
| Fix wrong RSQL filter field for --name lookup | `generator/parser/parser.go` → `resourceNameFieldOverrides` map |
| Fix wrong ID field extracted from list response | `generator/parser/parser.go` → `resourceIDFieldOverrides` map |
| Change how classic YAML manifest is parsed | `generator/classic/parser.go` |
| Add a new resource to the classic API | `specs/classic/resources.yaml` |
| Refresh or re-route the App Installer specs | nothing by hand — `make sync-platform-specs-from-sdk` derives `specs/AppInstaller*.yaml` from the SDK's `pro_api.json`. Routes are `AppInstallerSpecs` / `AppInstallerSubtree` (`generator/monolith/overrides.go`); the walker is `generator/monolith/subtree.go`. **`PruneStaleSpecs` exempts every filename in `AppInstallerSpecs`** (`generator/monolith/document.go`), because a `make sync-spec` run derives them from nothing: App Installers sits under `hiddenapi/` in `jamf/jss`, so no instance `/api/schema/` carries it, and without the exemption the instance ingest deleted `specs/AppInstallers.yaml` and all four commands with it, reported as a routine prune. Derived from the route table rather than a second list, so a new family is protected without a matching edit |
| Change what a classic `--scaffold` prints, or its required/enum help | `generator/parser/scaffold_xml.go` (`ScaffoldXML`) for the XML; `generator/classic/schema.go` and `generator/classic/body_render.go` for the required/optional/enum tail |
| Change how a classic resource is bound to a request-body schema | `generator/classicschema/extract.go`; refresh with `make sync-gateway-coverage-from-sdk` |
| Change classic `--set` behaviour (grammar, coercion, refusals) | `generator/classic/generator.go` → `classicRegistryTemplate` (`buildClassicXMLFromSet`, `classicSetParentKind`, `classicSetValue`) |
| Mark a body field as a credential `--set` must refuse (any product) | `credentialNameMarkers` / `credentialNameSuffixes` (`generator/parser/credfield.go`) for a name; `credentialPathSuffixes` in the same file for one whose leaf name is too generic to substring-match (`institutional_recovery_key.key`, `.data`); `notCredentialFields` to exempt a false positive |
| Add server-side subset narrowing (`--subset`) to a classic `get` | `subsets:` list in `specs/classic/resources.yaml` (drives completion; non-id lookups auto-resolve to an id first for gateway compatibility) |
| Add/modify DDM component scaffolds | `internal/blueprintcomponents/scaffolds.go` — SDK-typed components via `example*()` funcs; raw JSON fallback in `rawScaffolds` for components not yet in SDK |
| Add a new legacy-to-DDM payload converter | `internal/profileconvert/ddm_<name>.go` (new converter + register in `ddm_converter.go` init) |
| Change when `import-profile` can take over an installed profile (identity, reasons, description, dropped keys) | `internal/profileconvert/takeover.go` (`ApplyTakeoverIdentity`, `ImportDescription`, `DroppedKeys`); the output lines are `reportTakeover`/`warnDroppedKeys` in `internal/commands/pro_blueprints.go` |
| Add/remove a resource in the `backup`/`diff` commands | `internal/commands/pro_resources.go` (curated allowlist; endpoints come from generated `backup_registry.go`). `TestBackupResourcePathsAreServed` fails if a key's list/get/scope path is refused by the gateway |
| Add a new Jamf Pro handwritten command | `internal/commands/pro_*.go` (new file + wire in `pro.go`) |
| Add a new Platform API endpoint (CRUD, actions, reports) | Drop/update spec in `specs/.platform-source/`, run `make sync-platform-specs && make generate`. Don't hand-write — generator owns CRUD/actions. |
| Add a new Platform business operation (apply, import-profile, clone, etc.) | Hand-write in the relevant `pro_<resource>.go`; CRUD primitives must come from `internal/commands/platform/generated/` |
| Add or change a generated Platform API resource | `make sync-platform-specs-from-sdk` — the SDK's `api/` is the canonical source, fetched from its `main` by default (`JAMFPLATFORM_SDK_REF` for a tag or SHA, `JAMFPLATFORM_SDK_PATH` for a local checkout) |
| Change behavior of all generated Platform commands | `generator/platform/template.go` (`resourceTemplate`) |
| Change what a generated Platform command's `Long` says | `firstParagraph` / `admonitionLabel` / `flattenHelpText` (`generator/platform/emitter.go`) — leading markdown admonitions are kept as a prefix to the first substantive paragraph, `**Required Permissions:**` is dropped wherever it appears (already the `jamf:privileges` annotation and the 403 hint), and `**bold**`/`_italic_` markers are stripped because cobra dumps `Long` verbatim to a terminal. Taking literally the first paragraph is what left all twelve AI Governance Longs saying only that the endpoint may change: SDK v1.1.0 prefixed every description with two preview banners. Recognising an admonition is a property of the markdown shape, not of that spec. Backticks stay, quoting a field or a value; an underscore is stripped only as a matched pair at word boundaries, so `snake_case` keeps its own |
| Mark a generated command as a preview endpoint | nothing by hand — `parser.Operation.Preview` reads the per-operation `x-preview` extension (`generator/parser/parser.go`), `opAnnotations` and `applyAnnotations` stamp `jamf:preview` (`generator/platform/emitter.go`), and the `commands -o json` catalog carries it as `preview` (`commandEntry` / `commandEntriesToMaps` in `internal/commands/root.go`). Positive-only, unlike `destructive`: most specs declare nothing, so `false` on every row would read as "this one is GA". The prose says it too — upstream renders a `Preview - ` prefix into the summary — which is exactly why the annotation exists: that is wording nobody here controls, on the surface most likely to move |
| Change Platform spec parsing (tenant prefix, tag grouping) | `generator/parser/platform.go` |
| Change Platform name-to-ID resolution | `internal/platform/resolve.go` (hand-written, typed); `internal/platform/resolve_generic.go` (generated, untyped) |
| Add a new Jamf Protect command | `internal/commands/protect_*.go` (new file + wire in `protect.go`) |
| Add/remove a resource in `protect backup`/`restore` | `internal/commands/protect_backup.go` → `protectResources()` (Export/Restore closures + `Order`) |
| Mark an object as a tenant default that `protect restore` skips | `internal/commands/protect_backup.go` → `protectDefaultObjects` |
| Change Protect name-to-ID resolution | `internal/protect/resolve.go` |
| Change Protect YAML import/export schemas | `internal/commands/protect_analytics.go`, `protect_ulf.go` |
| Add a new Jamf Security Cloud endpoint (Risk, Device Lifecycle, SSE) | Drop the spec into `specs/.security-source/`, run `make sync-security-specs && make generate`. Don't hand-write — `generator/parser/security.go`'s `securityOpsByFile` map owns every known operation. |
| Add a Jamf Security Cloud hand-written command (business logic, not a single HTTP call — currently only `setup`) | `internal/commands/security_*.go` (new file + wire in `security.go`) |
| Change behavior of all generated Security Cloud commands | `generator/security/template.go` (`resourceTemplate`) |
| Change how Security Cloud specs are parsed / which operations map to which resource | `generator/parser/security.go` (`securityOpsByFile`) |
| Add a gateway-served Security Cloud endpoint (DNS, ZTNA, categories, device groups, UEM Connect) | Publish the spec from `jamfplatform-go-sdk`'s `api/`, drop it in `specs/.platform-source/`, `make sync-platform-specs`, then wire the constructor in `internal/commands/security.go` |
| Rename a generated platform resource (collision or a bare-noun tag) | `generator/parser/platform.go` → `platformResourceNameOverrides` (keys tried most-specific first: `{namespace}/{name}`, then `{service}/{name}`, then a bare `{name}`) |
| Add a Jamf AI Governance command | Generated — the spec is `specs/platform/ai_governance_policies_api.json`; commands are wired under `platform`, not `pro` (`internal/commands/platform.go`) |
| Add a Jamf Account (licensing/partners/SSO) command | Generated from `specs/platform/account_*.json`; wired under `platform` by `newAccountCmds` (`internal/commands/platform_account.go`), which also applies the US-only guard and help |
| Add a platform audit command | Generated from `specs/platform/audit_api.json`; wired by `newPlatformAuditCmd` (`internal/commands/platform_audit.go`), which applies the environment-scope note |
| Change what a platform command says about an opaque gateway error | `annotateDistributorScopeError` (`internal/commands/platform_account.go`) — the platform analogue of `edgeBlockedNote` |
| Record a wire probe that the gateway does not route a Pro/Classic endpoint | `generator/gateway/verdict.go` → `probedUnserved` — **needs a corroborated probe**; one 403 is not one |
| Keep a command available despite being absent from the gateway's published API | `generator/gateway/verdict.go` → `forceServed` — asserts the published surface is *wrong*, not merely ahead of the wire |
| Refresh which Pro/Classic endpoints the gateway publishes | `make sync-platform-specs-from-sdk` (does it as part of the platform sync), or `make sync-gateway-coverage-from-sdk` alone — writes `specs/gateway/coverage.json` |
| Change how a gateway-coverage refusal or hint is worded | `internal/gateway/note.go` (`Note`, `Refusal`); the `--help` caveat is `gatewayCoverageHelp` and the group-level one `gatewayGroupCoverageHelp` (`internal/commands/gateway_coverage.go`) |
| Name a working replacement for a refused command | `successors` (`internal/gateway/note.go`) — a curated table read by the runtime refusal, the `--help` caveat and the `commands -o json` catalog's `gatewaySuccessor`, so the three cannot disagree. `TestGatewaySuccessorsNameCommandsTheBinaryShips` fails when a key or its replacement stops naming a shipped command |
| Change the exit code a policy refusal returns, or the opt-out | `exitcode.Unsupported` (8) and `JAMF_CLI_ALLOW_UNPUBLISHED` (`internal/commands/gateway_coverage.go`) |
| Change when a command is refused for the wrong credentials (either direction) | `checkAPIMatch` (`internal/commands/gateway_coverage.go`), called from `PersistentPreRunE` |
| Rename a generated platform operation whose path-derived verb reads badly | `generator/parser/platform.go` → `platformOperationNameOverrides` (keyed `{METHOD} {full path}`) |
| Stop enforcing a query param the spec marks required but the server ignores | `generator/platform/emitter.go` → `platformIgnoredRequiredParams` |
| Withhold a platform operation the spec declares but the gateway does not route | `generator/parser/platform.go` → `platformUnroutedOps` (keyed `{METHOD} {normalised path}`; needs a recorded probe **and** a working operation it would displace — the table is empty) |
| Change what a command says about the scope level its API needs | `internal/commands/platform_scope.go` — `AnnotateScopeLevelError` for the runtime note, `printScopeSummary` for `platform setup`'s closing summary, both reading the `jamf:scopes` annotation stamped by `generator/platform/emitter.go` (`opAnnotations`) from `x-scope-types` |
| Let a platform endpoint's documented non-2xx render instead of becoming an exit-code error | `generator/platform/emitter.go` → `platformDocumentedStatusResults`; runtime is `internal/platform/documented_status.go` |
| Suppress a platform `--name` flag that cannot work | `generator/platform/emitter.go` → `platformNoNameLookup` (keyed `{METHOD} {path}`) |
| Point a platform `--name` lookup at a property other than name/title/displayName | `generator/platform/emitter.go` → `platformNameLookupFields` (keyed `{namespace}/{name}`); runtime is `platform.ResolveIDByNameInField` |
| Change which columns a platform list renders in table/CSV output | `generator/platform/emitter.go` → `platformTableColumns` (keyed `{namespace}/{name}` via `namespaceFromPath` — the **whole** namespace) |
| Correct a platform path version or success status the spec gets wrong | the SDK's `tools/generate/config.json`, republished as `x-jamf-tenant-path-version` / `x-jamf-expected-status` — not in this repo |
| Change Security Cloud auth (token cache, per-scope credentials, error mapping) | `internal/security/client.go` |
| Change what `<product> open` opens, or add `open` to a new product | `internal/commands/open.go` — `radarURL` for Security Cloud (a constant; tenancy is in the JWT), `openTarget` for a product whose web UI is its configured base URL, `proWebURL` for Jamf Pro. Only `pro open` can need a request: a platform integration names a tenant and never a Jamf Pro host, so on a gateway profile the URL is read from `/v1/jamf-pro-server-url` (`jss-url:read`). The other three carry `jamf:no-auth` and make none. `TestEveryProductHasAnOpenCommand` fails when a product namespace ships without one |
| Add or rename a `pro open <section>` page | `proUISections` (`internal/commands/open_sections.go`) — pages derived from a Jamf Pro session's own route declarations: the Angular router's legacy-page table, the left-hand nav (`{text, href}`) and the Settings cards (`cardTitle`/`cardPagePath`). A route table is the only honest source, since an unauthenticated request answers 302 for every path real or invented, and the interface is a SPA whose routes never appear as requests. Settings names drop the interface's category segment (`settings/packages`, not `settings/computer-management/packages`); `TestProSectionNamesTrackTheCLIsOwnResourceNames` measures the overlap with this CLI's own resource names. Resolution and the host-escape refusal are `proSectionURL`, whose `..` guard decodes the argument first — `%2e%2e` is a different string from `..` and passed the literal segment walk (no host escape either way; the host/scheme re-parse is what contains that). `--list` refuses a section beside it rather than discarding it |
| Make a command auth-free under one of its own flags | `noAuthWhenFlagAnnotation` (`internal/commands/root.go`), read in `PersistentPreRunE` beside the `--scaffold` bypass. Auth is resolved per command, so a command that usually needs credentials and sometimes does not had no way to say so: `pro open --list` renders a table compiled into the binary and was refused for a missing server URL. An annotation rather than a second name in the flag check, so the bypass cannot be inherited by every other command that comes to declare a `--list` |
| Change when `<product> open` prints instead of launching | `wantsURLAsData` (`internal/commands/open.go`), read by `openOrPrint` beside `--print`, `--no-input` and `--dry-run`. **`--out-file` does not make stdout a non-terminal** — `PersistentPreRunE` points the formatter at the file and leaves stdout alone — so the launch branch ran, `printRows` never did, and the file the caller named was created and left at zero bytes, exit 0, no warning; `--field` and `--select` emitted nothing and launched a browser instead. A data request must not be answered by a side effect |
| Change which URL a `protect`/`school` `open` uses | `protectOpenTarget` / `schoolOpenTarget` (`internal/commands/open.go`). `envVars` are read **before** the profile and `fallbackEnvVars` **after** it, mirroring each resolver's order and not just its membership: `resolveProtectClient` reads `JAMFPROTECT_URL` before the profile and `JAMF_URL` only after, and a merged ladder sent `protect open` to the Jamf Pro instance under a "Jamf Protect" label in any shell exporting `JAMF_URL`. `TestOpenTargetLaddersMatchTheirProductsClient` is the guard |
| Change how a URL is handed to the OS browser | `internal/browser` — `Validate` refuses anything that is not absolute http/https, because the URL comes from a profile or an API response and a platform opener resolves a foreign scheme through the OS handler registry (macOS `open` launches an application for one). Every caller validates, printing included: the same value is wrong in both directions |
| Add a new cross-product command | `internal/commands/` (new file + wire in `root.go`) |
| Add a new product namespace | `internal/commands/` (e.g., `newproduct.go` + `newproduct_*.go` files); then update site (`index.html`, `style.css`, `catalog.js`) — `make verify-site` enforces |
| Modify auth behavior | `internal/auth/` |
| Change HTTP client / retry / exit codes | `internal/client/` |
| Add or change output formats | `internal/output/` |
| Add a short alias (e.g., `comp` for `computers`) | `internal/commands/aliases.go` |
| Add a command group for `--help` output | `internal/commands/groups.go` |
| Change global flags or root command behavior | `internal/commands/root.go` |
| Change config file handling | `internal/config/` |
| Change shared CLI interfaces (CLIContext, etc.) | `internal/registry/` |
| Modify the GitHub Pages showcase site | `docs/site/index.html`, `docs/site/style.css`, `docs/site/catalog.js`, `docs/site/palette.js`, `docs/site/terminal.js` |
| Change how commands.json is generated for the site | `generator/site/main.go` |
## Classic Scope
| I want to... | Edit this file |
|---|---|
| Change which scope categories a Classic resource accepts | `shapes` (`internal/scope/matrix.go`) — keyed on `Resource.SingularKey`, one entry per scopeable resource. Read the resource's own GET for the category set, then confirm anything doubtful with a write probe: `macapplications` returns an empty `<mobile_device_groups>` and 409s a member in it |
| Change how a Classic scope body is written | `PutScope` / `marshalScopeBody` (`internal/scope/scope.go`). **Do not re-order `ScopeXML`'s fields** — that order is the schema order the server's XML binding requires, and it silently ignores children out of order while answering 200 |
| Change what a Classic write's read-back check compares | `VerifyScopeWrite` (`internal/scope/scope.go`) — the **whole** scope sent, not just the item touched |
| Change what an all-flag conflict says | `CheckAllFlagConflict` (`internal/scope/`) |
| Change what a Classic HTTP error says | `classicHTMLErrorReason` (`internal/client/client.go`) |
| Add server-side subset narrowing (`--subset`) to a classic `get` | `subsets:` list in `specs/classic/resources.yaml` (drives completion; non-id lookups auto-resolve to an id first for gateway compatibility) |
## Shared Command Helpers
| I want to... | Edit this file |
|---|---|
| Change shared input reading, confirmation or export formatting (Protect + Platform) | `internal/commands/protect_helpers.go` — `readInput`, `unmarshalInput`, `printResult`, `printExport`, `confirmDelete`, `confirmReplace` |
| Change the Platform-only helpers | `internal/commands/pro_platform_helpers.go` — `requirePlatformClient`, `printScaffold`, `identityEncodingOnWrites`, `newPlatformSDKClient` |
| Change the Security Cloud helpers | `internal/security` — `ReadBody`, `ConfirmAction`. Duplicated from `internal/platform` rather than shared, since the products' generated packages cannot import each other's parent (`internal/commands`) without an import cycle |
## Cross-Product Dashboard and MCP
| I want to... | Edit this file |
|---|---|
| Add or change a dashboard section's data | `internal/commands/dashboard_pro.go` (Jamf Pro), `dashboard_protect.go`, `dashboard_platform.go`, `dashboard_security_cloud.go`; the shapes are in `dashboard_data.go` |
| Change how a dashboard section renders | `internal/commands/dashboard_html.go` (one `html/template`, no escape hatch anywhere — keep it that way) |
| Change the dashboard's exit code or banner | `finishDashboard` (`internal/commands/dashboard.go`) — the seam both are tested through |
| Change what each dashboard tier costs, or where a collector lives | `collectProDataFast` / `collectProDataFull` (`internal/commands/dashboard_pro.go`), and `fixedCostAuditChecks` / `fleetScaledAuditChecks` (`internal/commands/pro_audit.go`). State the cost through `dashboardCostNote` (`dashboard.go`) — it is the single source every surface quotes |
| Change which objects a category's item count covers | `classicCategorySources` / `proCategorySources` (`internal/commands/dashboard_pro_categories.go`) |
| Change what an MCP child may not do | `mcpRefusedCommands` / `mcpLocalPathFlags` / `mcpDirFlags` / `blockedChildFlagPrefixes` / `refuseReportThroughRunCommand` (`internal/commands/mcp.go`) — judged on the command and flags cobra resolves, server-side in `buildChildArgs` and again in the child by `refuseInMCPChild`; tree-walk tests fail on an unclassified path-shaped flag or a stale entry |
| Change what the MCP report tool returns | `runReportChild` (`internal/commands/mcp.go`) — exit 7 keeps the file, anything else removes it |
Wersje
W bazie nie ma innych skilli o nazwie where-to-make-changes.
Migawka bazy z 28.10.2026 · wersja paczki 2d142cfc9bf1 (skrót SHA-256 całej paczki)
Zgłoś problem z tym skillem: szkodliwa treść, błędny opis albo prośba autora o usunięcie.
Podobne skille
- 100context-search
Prowadzi semantyczne wyszukiwanie kodu według znaczenia: znajduje miejsca, o których lokalizacji nic nie wiadomo, gdy zadanie opisuje zachowanie lub intencję bez nazw symboli. - 100idea-generator
Generuje i szereguje pomysły na aplikacje iOS i macOS dopasowane do umiejętności dewelopera, gdy nie wiadomo, co budować. - 100regression-bisecting
Wskazuje commit, który wprowadził niedawny błąd, gdy nie wiadomo, która zmiana go spowodowała. - 100ownership
Wskazuje, który moduł jest właścicielem danego obszaru, tabeli lub trasy, i odmawia edycji sięgającej do cudzego modułu.