bf9395e022
CI / results (push) Blocked by required conditions
CI / license-header (push) Has been skipped
CI / e2e-dry-run (push) Has been skipped
CI / e2e-live (push) Waiting to run
CI / fast-gate (push) Failing after 0s
Test PR Label Logic / test-pr-labels (push) Failing after 1s
Skill Format Check / check-format (push) Failing after 2s
CI / security (push) Failing after 5s
CI / unit-test (push) Has been skipped
CI / lint (push) Has been skipped
CI / script-test (push) Has been skipped
CI / deterministic-gate (push) Has been skipped
CI / coverage (push) Has been skipped
CI / deadcode (push) Waiting to run
129 lines
4.2 KiB
Go
129 lines
4.2 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package sheets
|
|
|
|
import (
|
|
_ "embed"
|
|
"encoding/json"
|
|
"fmt"
|
|
"sort"
|
|
"sync"
|
|
|
|
"github.com/larksuite/cli/errs"
|
|
)
|
|
|
|
// ─── --print-schema runtime introspection ─────────────────────────────
|
|
//
|
|
// Composite JSON flags (--cells, --properties, --operations, --border-styles,
|
|
// --sort-keys) carry non-trivial structured payloads. Reference docs cover
|
|
// the top-level fields but agents often need the full JSON Schema to
|
|
// generate valid input.
|
|
//
|
|
// To serve that need without forcing every caller to fetch external docs,
|
|
// the spec repo ships a compact `flag-schemas.json` that extracts just the
|
|
// schema subtree corresponding to each (shortcut, flag) pair. We embed
|
|
// that artifact at compile time so `lark-cli sheets <shortcut>
|
|
// --print-schema --flag-name <name>` runs entirely locally.
|
|
//
|
|
// The artifact is generated by sheet-skill-spec's
|
|
// scripts/sync_to_consumers.mjs from canonical-spec/cli-flag-schema-map.json
|
|
// + tool-schemas/mcp-tools.json. Do not hand-edit data/flag-schemas.json;
|
|
// regenerate via the sync script.
|
|
|
|
//go:embed data/flag-schemas.json
|
|
var flagSchemasJSON []byte
|
|
|
|
// flagSchemaIndex parses lazily on first access; failures are surfaced
|
|
// as errors from the lookup helper rather than panicking at init time.
|
|
type flagSchemaIndex struct {
|
|
SchemaVersion string `json:"schema_version"`
|
|
Flags map[string]map[string]json.RawMessage `json:"flags"`
|
|
}
|
|
|
|
// loadFlagSchemas is sync.Once-guarded so concurrent first access from
|
|
// parallel goroutines (e.g. parallel unit tests, parallel shortcut
|
|
// invocations) doesn't race on the lazy parse.
|
|
var (
|
|
flagSchemasOnce sync.Once
|
|
parsedFlagSchemas *flagSchemaIndex
|
|
parseFlagErr error
|
|
)
|
|
|
|
func loadFlagSchemas() (*flagSchemaIndex, error) {
|
|
flagSchemasOnce.Do(func() {
|
|
var idx flagSchemaIndex
|
|
if err := json.Unmarshal(flagSchemasJSON, &idx); err != nil {
|
|
parseFlagErr = fmt.Errorf("flag-schemas.json: %w", err)
|
|
return
|
|
}
|
|
if idx.Flags == nil {
|
|
idx.Flags = map[string]map[string]json.RawMessage{}
|
|
}
|
|
parsedFlagSchemas = &idx
|
|
})
|
|
return parsedFlagSchemas, parseFlagErr
|
|
}
|
|
|
|
// commandsWithFlagSchema returns the set of shortcut commands that have
|
|
// at least one introspectable flag. Used by Shortcuts() to decide which
|
|
// shortcuts to wire PrintFlagSchema into.
|
|
func commandsWithFlagSchema() map[string]struct{} {
|
|
idx, err := loadFlagSchemas()
|
|
if err != nil || idx == nil {
|
|
return nil
|
|
}
|
|
out := make(map[string]struct{}, len(idx.Flags))
|
|
for cmd := range idx.Flags {
|
|
out[cmd] = struct{}{}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// printFlagSchemaFor returns a PrintFlagSchema closure bound to the given
|
|
// shortcut command. When flagName == "" the closure returns a JSON
|
|
// listing of introspectable flags; otherwise it returns the schema
|
|
// subtree JSON for the named flag, or an error if the flag is not
|
|
// registered.
|
|
func printFlagSchemaFor(command string) func(flagName string) ([]byte, error) {
|
|
return func(flagName string) ([]byte, error) {
|
|
idx, err := loadFlagSchemas()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
entry, ok := idx.Flags[command]
|
|
if !ok || len(entry) == 0 {
|
|
return nil, errs.NewValidationError(errs.SubtypeInvalidArgument, "no JSON Schema registered for %s", command)
|
|
}
|
|
if flagName == "" {
|
|
flags := make([]string, 0, len(entry))
|
|
for f := range entry {
|
|
flags = append(flags, f)
|
|
}
|
|
sort.Strings(flags)
|
|
return json.MarshalIndent(map[string]interface{}{
|
|
"shortcut": command,
|
|
"introspectable_flags": flags,
|
|
"hint": "run again with --flag-name <name> to dump the JSON Schema for that flag",
|
|
}, "", " ")
|
|
}
|
|
schema, ok := entry[flagName]
|
|
if !ok {
|
|
flags := make([]string, 0, len(entry))
|
|
for f := range entry {
|
|
flags = append(flags, f)
|
|
}
|
|
sort.Strings(flags)
|
|
return nil, errs.NewValidationError(errs.SubtypeInvalidArgument,
|
|
"no JSON Schema registered for %s --%s; available: %v", command, flagName, flags).
|
|
WithParam("--flag-name")
|
|
}
|
|
// Reformat for readability — schema files store compact JSON.
|
|
var pretty interface{}
|
|
if err := json.Unmarshal(schema, &pretty); err != nil {
|
|
return nil, err
|
|
}
|
|
return json.MarshalIndent(pretty, "", " ")
|
|
}
|
|
}
|