← back to Cli Printing Press
fix(cli): preserve manifest fields during dogfood sync (#844)
2d9a304a509f427226d08cbf567f0a7aea5c5df3 · 2026-05-09 13:09:12 -0700 · Trevin Chow
Files touched
M internal/pipeline/climanifest.goM internal/pipeline/climanifest_test.go
Diff
commit 2d9a304a509f427226d08cbf567f0a7aea5c5df3
Author: Trevin Chow <trevin@trevinchow.com>
Date: Sat May 9 13:09:12 2026 -0700
fix(cli): preserve manifest fields during dogfood sync (#844)
---
internal/pipeline/climanifest.go | 127 +++++++++++++++++++++++++++++++++-
internal/pipeline/climanifest_test.go | 63 +++++++++++++++++
2 files changed, 189 insertions(+), 1 deletion(-)
diff --git a/internal/pipeline/climanifest.go b/internal/pipeline/climanifest.go
index 637fa33c..00473c5d 100644
--- a/internal/pipeline/climanifest.go
+++ b/internal/pipeline/climanifest.go
@@ -1,11 +1,13 @@
package pipeline
import (
+ "bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
+ "maps"
"os"
"path/filepath"
"reflect"
@@ -213,7 +215,130 @@ func SyncCLIManifestNovelFeatures(dir string, features []NovelFeature) (bool, er
}
m.NovelFeatures = updated
- return true, WriteCLIManifest(dir, m)
+ var raw map[string]json.RawMessage
+ if err := json.Unmarshal(data, &raw); err != nil {
+ return false, fmt.Errorf("parsing CLI manifest for raw update: %w", err)
+ }
+ if raw == nil {
+ return false, fmt.Errorf("parsing CLI manifest for raw update: expected JSON object")
+ }
+ known, err := marshalCLIManifestFields(m)
+ if err != nil {
+ return false, err
+ }
+ maps.Copy(raw, known)
+ rendered, err := marshalCLIManifestObject(raw)
+ if err != nil {
+ return false, err
+ }
+ if err := os.WriteFile(manifestPath, rendered, 0o644); err != nil {
+ return false, fmt.Errorf("writing CLI manifest: %w", err)
+ }
+
+ return true, nil
+}
+
+func marshalCLIManifestFields(m CLIManifest) (map[string]json.RawMessage, error) {
+ data, err := json.Marshal(m)
+ if err != nil {
+ return nil, fmt.Errorf("marshaling CLI manifest fields: %w", err)
+ }
+ var raw map[string]json.RawMessage
+ if err := json.Unmarshal(data, &raw); err != nil {
+ return nil, fmt.Errorf("parsing CLI manifest fields: %w", err)
+ }
+ return raw, nil
+}
+
+func marshalCLIManifestObject(raw map[string]json.RawMessage) ([]byte, error) {
+ keys := orderedCLIManifestKeys(raw)
+ var b strings.Builder
+ b.WriteString("{\n")
+ for i, key := range keys {
+ name, err := json.Marshal(key)
+ if err != nil {
+ return nil, fmt.Errorf("marshaling CLI manifest key: %w", err)
+ }
+ value, err := formatRawJSONValue(raw[key])
+ if err != nil {
+ return nil, fmt.Errorf("formatting CLI manifest field %q: %w", key, err)
+ }
+ b.WriteString(" ")
+ b.WriteString(string(name))
+ b.WriteString(": ")
+ b.WriteString(value)
+ if i < len(keys)-1 {
+ b.WriteByte(',')
+ }
+ b.WriteByte('\n')
+ }
+ b.WriteString("}\n")
+ return []byte(b.String()), nil
+}
+
+func orderedCLIManifestKeys(raw map[string]json.RawMessage) []string {
+ known := []string{
+ "schema_version",
+ "generated_at",
+ "printing_press_version",
+ "api_name",
+ "display_name",
+ "cli_name",
+ "owner",
+ "printer",
+ "printer_name",
+ "spec_url",
+ "spec_path",
+ "spec_format",
+ "spec_checksum",
+ "run_id",
+ "catalog_entry",
+ "category",
+ "description",
+ "mcp_binary",
+ "mcp_tool_count",
+ "mcp_public_tool_count",
+ "mcp_ready",
+ "api_version",
+ "auth_type",
+ "auth_env_vars",
+ "auth_env_var_specs",
+ "endpoint_template_vars",
+ "auth_key_url",
+ "auth_title",
+ "auth_description",
+ "auth_optional",
+ "novel_features",
+ }
+
+ keys := make([]string, 0, len(raw))
+ seen := make(map[string]bool, len(raw))
+ for _, key := range known {
+ if _, ok := raw[key]; ok {
+ keys = append(keys, key)
+ seen[key] = true
+ }
+ }
+ var unknown []string
+ for key := range raw {
+ if !seen[key] {
+ unknown = append(unknown, key)
+ }
+ }
+ sort.Strings(unknown)
+ return append(keys, unknown...)
+}
+
+func formatRawJSONValue(raw json.RawMessage) (string, error) {
+ var buf bytes.Buffer
+ if err := json.Indent(&buf, raw, "", " "); err != nil {
+ return "", err
+ }
+ lines := strings.Split(buf.String(), "\n")
+ for i := 1; i < len(lines); i++ {
+ lines[i] = " " + lines[i]
+ }
+ return strings.Join(lines, "\n"), nil
}
// findArchivedSpec looks for a spec file archived alongside a generated CLI.
diff --git a/internal/pipeline/climanifest_test.go b/internal/pipeline/climanifest_test.go
index b15b1613..fce21ea7 100644
--- a/internal/pipeline/climanifest_test.go
+++ b/internal/pipeline/climanifest_test.go
@@ -114,6 +114,69 @@ func TestWriteCLIManifestNonexistentDir(t *testing.T) {
assert.Error(t, err)
}
+func TestSyncCLIManifestNovelFeaturesPreservesManifestContract(t *testing.T) {
+ dir := t.TempDir()
+ manifest := []byte(`{
+ "schema_version": 1,
+ "generated_at": "2026-05-09T17:28:02Z",
+ "printing_press_version": "4.2.0",
+ "api_name": "openrouter",
+ "display_name": "OpenRouter",
+ "cli_name": "openrouter-pp-cli",
+ "printer": "rvdlaar",
+ "printer_name": "Rick van de Laar",
+ "spec_url": "https://example.com/openapi.json",
+ "category": "ai",
+ "description": "Access OpenRouter models.",
+ "x_future_manifest_field": {
+ "keep": true
+ },
+ "novel_features": [
+ {
+ "name": "Old",
+ "command": "old",
+ "description": "Old feature."
+ }
+ ]
+}
+`)
+ require.NoError(t, os.WriteFile(filepath.Join(dir, CLIManifestFilename), manifest, 0o644))
+
+ changed, err := SyncCLIManifestNovelFeatures(dir, []NovelFeature{
+ {Name: "Model finder", Command: "models find", Description: "Find a model for a prompt."},
+ })
+ require.NoError(t, err)
+ assert.True(t, changed)
+
+ data, err := os.ReadFile(filepath.Join(dir, CLIManifestFilename))
+ require.NoError(t, err)
+
+ var got map[string]any
+ require.NoError(t, json.Unmarshal(data, &got))
+ assert.Equal(t, float64(1), got["schema_version"])
+ assert.Equal(t, "4.2.0", got["printing_press_version"])
+ assert.Equal(t, "openrouter", got["api_name"])
+ assert.Equal(t, "openrouter-pp-cli", got["cli_name"])
+ assert.Equal(t, "rvdlaar", got["printer"])
+ assert.Equal(t, "Rick van de Laar", got["printer_name"])
+ assert.Equal(t, "https://example.com/openapi.json", got["spec_url"])
+ assert.Equal(t, "ai", got["category"])
+ assert.Equal(t, "Access OpenRouter models.", got["description"])
+
+ future, ok := got["x_future_manifest_field"].(map[string]any)
+ require.True(t, ok)
+ assert.Equal(t, true, future["keep"])
+
+ features, ok := got["novel_features"].([]any)
+ require.True(t, ok)
+ require.Len(t, features, 1)
+ feature, ok := features[0].(map[string]any)
+ require.True(t, ok)
+ assert.Equal(t, "Model finder", feature["name"])
+ assert.Equal(t, "models find", feature["command"])
+ assert.Equal(t, "Find a model for a prompt.", feature["description"])
+}
+
func TestSpecChecksum(t *testing.T) {
dir := t.TempDir()
content := []byte(`{"openapi": "3.0.0"}`)
← 117544d0 Ignore .omx workspace folders (#841)
·
back to Cli Printing Press
·
docs(cli): add Cursor guide for printed CLIs (#833) f34645a8 →