Repository navigation
Expand file tree
/
Copy pathanycli.go
More file actions
109 lines (95 loc) · 4.53 KB
/
Copy pathanycli.go
File metadata and controls
109 lines (95 loc) · 4.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
// Package anycli is the embeddable core for "run an underlying CLI/API tool
// with injected credentials + middleware". A host (e.g. heliox) constructs an
// Engine, provides a CredentialResolver, and calls Engine.Execute; AnyCLI loads
// the embedded tool definition, resolves credentials through the resolver,
// injects them (env / arg / file), runs before/after middleware, and execs the
// underlying binary or built-in service.
//
// Tool definitions are internal to AnyCLI (embedded JSON under definitions/);
// the consumer never supplies them. The consumer supplies only a
// CredentialResolver and, optionally, a Cache.
//
// See docs/design/002-embeddable-core-and-credential-resolver.md.
package anycli
import (
"context"
"fmt"
"github.com/heliohq/anycli/internal/credential"
"github.com/heliohq/anycli/internal/exec"
)
// Tool identifies a tool by its definition name. It is a named type for
// type-safety + discoverability. Tool names come from the embedded definition
// set returned by ListTools; AnyCLI intentionally does not duplicate that set
// as exported constants. Pass a raw Tool("…") whose name matches an embedded
// definition. An unknown tool is an error from Execute, not a compile error.
type Tool = credential.Tool
// Credential holds the in-memory credential data a resolver returns for a tool.
// It is the only thing that crosses the resolver boundary into AnyCLI.
type Credential = credential.Credential
// CredentialResolver is the seam through which a host supplies credentials.
// The resolver returns in-memory data only; AnyCLI owns injection, caching, and
// lifecycle. The resolver never learns how the data is injected.
type CredentialResolver = credential.CredentialResolver
// Cache is the credential cache the engine uses to avoid re-resolving on every
// call. It is consumer-supplied so a host can back it with a per-process /
// per-assistant in-memory store instead of any on-disk cache. The cache stores
// entries keyed by (tool, account); the engine interprets freshness
// (CacheUntil / Stale), while the implementation only stores and retrieves.
type Cache = credential.Cache
// CacheEntry is one cached credential: the extracted fields plus the freshness
// metadata the engine uses to decide whether to re-resolve.
type CacheEntry = credential.CacheEntry
// Config carries the consumer-supplied initialization for an Engine. It carries
// only a Cache — tool definitions are internal to AnyCLI (embedded) and are
// never consumer-supplied.
type Config struct {
// Cache is the credential cache the engine uses. Optional: a nil Cache
// installs an in-memory default (see NewMemoryCache).
Cache Cache
}
// NewMemoryCache returns an empty in-memory Cache — the default the engine
// installs when Config.Cache is nil. Exposed so a consumer can construct one
// explicitly (e.g. one per assistant).
func NewMemoryCache() Cache {
return credential.NewMemoryCache()
}
// Engine is the embeddable AnyCLI core. Construct it with New, then call
// Execute. It is safe for concurrent use to the extent its Cache is.
type Engine struct {
inner *exec.Engine
}
// New constructs an Engine from cfg. A nil cfg.Cache installs the in-memory
// default cache.
func New(cfg Config) (*Engine, error) {
if _, err := ListTools(); err != nil {
return nil, fmt.Errorf("validate bundled tools: %w", err)
}
cache := cfg.Cache
if cache == nil {
cache = credential.NewMemoryCache()
}
inner, err := exec.NewEngine(cache)
if err != nil {
return nil, err
}
return &Engine{inner: inner}, nil
}
// ExecOptions carries per-invocation execution options (design 003).
type ExecOptions struct {
// Account selects which connected account's credential to resolve when
// the host has several for one tool. Empty = the resolver's default.
Account string
}
// ExecuteWith loads the embedded definition for tool, resolves and injects its
// credentials via resolver for the account selected by opts, runs middleware,
// and execs the underlying binary or built-in service.
//
// resolver must be non-nil; an unknown tool (no embedded definition) returns an
// error.
func (e *Engine) ExecuteWith(ctx context.Context, tool Tool, args []string, resolver CredentialResolver, opts ExecOptions) (exitCode int, err error) {
return e.inner.Execute(ctx, string(tool), args, resolver, opts.Account)
}
// Execute runs with the default account. Kept as the short form of ExecuteWith.
func (e *Engine) Execute(ctx context.Context, tool Tool, args []string, resolver CredentialResolver) (exitCode int, err error) {
return e.ExecuteWith(ctx, tool, args, resolver, ExecOptions{})
}