diff --git a/cmd/pad/main.go b/cmd/pad/main.go index 27bf3fde..0763d7ea 100644 --- a/cmd/pad/main.go +++ b/cmd/pad/main.go @@ -2799,6 +2799,42 @@ Usage: // v2 Commands: create, list, show, update, delete, search, status, next, collections // ============================================================================= +// parseFieldFlag parses a --field key=value flag value according to the +// field's declared schema type. JSON-typed and multi_select fields receive +// parsed JSON values, number-typed fields receive numbers, and checkbox +// fields receive booleans. Unknown fields (not in the schema) and string- +// typed fields (text, url, select, date, relation) fall back to the raw +// string. On parse failure, falls back to the raw string so the server +// validator returns a useful error rather than the CLI silently dropping +// data. See BUG-1125. +func parseFieldFlag(schema models.CollectionSchema, key, raw string) any { + for i := range schema.Fields { + def := schema.Fields[i] + if def.Key != key { + continue + } + switch def.Type { + case "json", "multi_select": + var v any + if err := json.Unmarshal([]byte(raw), &v); err == nil { + return v + } + case "number": + if f, err := strconv.ParseFloat(raw, 64); err == nil { + return f + } + case "checkbox": + if b, err := strconv.ParseBool(raw); err == nil { + return b + } + } + // All other types (text, url, select, date, relation) — string is correct. + return raw + } + // Unknown field — let the server decide. + return raw +} + // --- create --- func createCmd() *cobra.Command { @@ -2857,10 +2893,18 @@ Run with --help-collections to see available collections and their status values fields["category"] = category } - // Apply arbitrary --field key=value flags + // Apply arbitrary --field key=value flags. Fetch the collection + // schema so JSON / number / checkbox / multi_select fields parse + // to their declared types (BUG-1125). Schema-fetch failure + // degrades gracefully: all values stay as strings, matching + // pre-fix behavior. + var collSchema models.CollectionSchema + if coll, err := client.GetCollection(ws, collSlug); err == nil { + _ = json.Unmarshal([]byte(coll.Schema), &collSchema) + } for _, kv := range fieldFlags { if idx := strings.Index(kv, "="); idx > 0 { - fields[kv[:idx]] = kv[idx+1:] + fields[kv[:idx]] = parseFieldFlag(collSchema, kv[:idx], kv[idx+1:]) } } @@ -3424,10 +3468,20 @@ Examples: existingFields["category"] = category } - // Apply arbitrary --field key=value flags + // Apply arbitrary --field key=value flags. Fetch the + // collection schema (using the item's own collection slug) + // so JSON / number / checkbox / multi_select fields parse + // to their declared types (BUG-1125). Schema-fetch failure + // degrades gracefully: all values stay as strings. + var collSchema models.CollectionSchema + if item.CollectionSlug != "" { + if coll, err := client.GetCollection(ws, item.CollectionSlug); err == nil { + _ = json.Unmarshal([]byte(coll.Schema), &collSchema) + } + } for _, kv := range fieldFlags { if idx := strings.Index(kv, "="); idx > 0 { - existingFields[kv[:idx]] = kv[idx+1:] + existingFields[kv[:idx]] = parseFieldFlag(collSchema, kv[:idx], kv[idx+1:]) } } diff --git a/skills/pad/SKILL.md b/skills/pad/SKILL.md index b7f70469..653180a8 100644 --- a/skills/pad/SKILL.md +++ b/skills/pad/SKILL.md @@ -242,34 +242,34 @@ pad item create playbook "Release checklist" \ EOF ``` -**Authoring slug-invocable playbooks with arguments.** The `arguments` field on the playbooks collection is a `json` type, and `pad item create --field` only sets string values — so you can't set a structured `arguments` array directly from the CLI. Two working paths: +**Authoring slug-invocable playbooks with arguments.** As of BUG-1125's fix, `pad item create --field` is schema-aware — pass the structured `arguments` array directly as a JSON literal and the CLI parses it into the json-typed field. The full playbook (slug + arguments + body with `## Arguments` mirror) lands in one command: -- **Web UI playbook editor (recommended)** at `/{username}/{workspace}/playbooks` (click "+ New Playbook"). The editor lets the user (or the agent talking the user through it) declare each argument's `name / type / required / default / description / enum` in a structured form, validates the kebab-case slug + workspace uniqueness, and round-trips the spec into the body's `## Arguments` section. Open the URL with `pad server open` if the user isn't already there. This is the canonical path — the editor exists specifically for this case. +```bash +pad item create playbook "Cut a release" \ + --field invocation_slug=release \ + --field trigger=manual \ + --field status=active \ + --field 'arguments=[{"name":"version","type":"string","required":true,"description":"semver, e.g. 0.5.0"},{"name":"dry-run","type":"flag","default":false,"description":"Print what would happen, don'\''t push"}]' \ + --stdin <<'EOF' +Cut a Pad release. -- **CLI then web edit.** If the user wants the CLI flow, create the playbook with the body containing a `## Arguments` section and set everything BUT `arguments`: +## Arguments - ```bash - pad item create playbook "Cut a release" \ - --field invocation_slug=release \ - --field trigger=manual \ - --field status=active \ - --stdin <<'EOF' - Cut a Pad release. +- `version` (string, required) — semver, e.g. 0.5.0 +- `dry-run` (flag, default=false) — print what would happen, don't push - ## Arguments +## Steps - - `version` (string, required) — semver, e.g. 0.5.0 +1. Verify the tree is clean and on main +2. Run `make test` +3. Tag with `git tag v$VERSION && git push --tags` +4. Verify CI release workflow succeeded +EOF +``` - ## Steps +The `arguments` JSON and the body's `## Arguments` section are the same contract expressed two ways — the structured field is what the strict CLI/MCP arg parser reads; the markdown is the human-readable mirror. Keep them in sync. For long argument specs, write the JSON to a file and inline it: `--field "arguments=$(cat /tmp/args.json)"`. - 1. Verify the tree is clean and on main - 2. Run `make test` - 3. Tag with `git tag v$VERSION && git push --tags` - 4. Verify CI release workflow succeeded - EOF - ``` - - Then open the new playbook in the web editor's structured arguments builder to declare each argument (the markdown `## Arguments` section is the human-readable mirror; the structured field is what the strict CLI parser reads, so both need to be populated). The editor's two-way binding will keep them in sync from there. +**Web UI playbook editor** at `/{username}/{workspace}/playbooks` (click "+ New Playbook") is the alternative if the user prefers a form-based flow — kebab-case slug input with debounced uniqueness check, structured arguments builder, and two-way binding with the body's `## Arguments` section. Open with `pad server open`. Equally valid; pick whichever surface the user is already in. After creation, point the user at `/pad ` for the new invocation or, for trigger-only playbooks, the action that will auto-load it ("This will fire on the next `on-release` action").