Files
larksuite-cli/internal/binding/json_pointer.go
evandance ce80b3bc46 feat(config): add 'config bind' for per-Agent credential isolation (#515)
Give each AI Agent (OpenClaw, Hermes) its own lark-cli workspace so
its Feishu calls don't overwrite the developer's local config or
collide with other Agents.

    lark-cli config bind [--source openclaw|hermes] [--app-id <id>]
                         [--identity bot-only|user-default] [--force]

Key capabilities:

- Source auto-detected from OPENCLAW_* / HERMES_* env signals; config
  written to ~/.lark-cli/<agent>/, isolated per Agent.
- Two identity presets: 'bot-only' (flag-mode default) and
  'user-default'. Flag mode rejects silent bot→user escalation
  without --force; TUI prompts are exempt.
- Agent-friendly stdout JSON with 'identity' + 'message' for
  next-step branching.
- 'config show' and 'doctor' expose the bound 'workspace'.
- OpenClaw SecretRef resolution: plain / ${VAR} / file:+JSON Pointer
  / exec:.
2026-04-23 19:51:36 +08:00

56 lines
1.6 KiB
Go

// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package binding
import (
"fmt"
"strings"
)
// ReadJSONPointer navigates a parsed JSON value (typically the result of
// json.Unmarshal into interface{}) using an RFC 6901 JSON Pointer string.
//
// Supported pointer format: "/key/subkey/subsubkey".
// An empty pointer ("") returns data as-is.
// RFC 6901 escape sequences: ~1 → /, ~0 → ~.
//
// Limitation: only object (map) traversal is supported. Array index segments
// (e.g., "/channels/0/appId") are not implemented because OpenClaw's
// SecretRef file provider uses object-only paths in practice.
func ReadJSONPointer(data interface{}, pointer string) (interface{}, error) {
if pointer == "" {
return data, nil
}
if !strings.HasPrefix(pointer, "/") {
return nil, fmt.Errorf("json pointer must start with '/' or be empty, got %q", pointer)
}
// Split after the leading "/" and decode each segment.
segments := strings.Split(pointer[1:], "/")
current := data
for i, raw := range segments {
// RFC 6901 unescaping: ~1 → /, ~0 → ~ (order matters).
key := strings.ReplaceAll(raw, "~1", "/")
key = strings.ReplaceAll(key, "~0", "~")
m, ok := current.(map[string]interface{})
if !ok {
traversed := "/" + strings.Join(segments[:i], "/")
return nil, fmt.Errorf("json pointer %q: value at %q is %T, not an object",
pointer, traversed, current)
}
val, exists := m[key]
if !exists {
return nil, fmt.Errorf("json pointer %q: key %q not found", pointer, key)
}
current = val
}
return current, nil
}