package main // The module's acting tools (design 39 bus-and-switch §38, §39, §42; design 39 parts §4): registered by the manager // bundle beside the seat's verbs, they start by hand what the manager does by itself. The tools that only read are the // tools bundle's (cmd/tools). import ( "context" "encoding/json" "errors" "fmt" "sort" "strings" stdio "git.novox.be/novox/mesh-sdk/go" . "claude-licence-manager/internal/licences" ) // tool is one of the module's own tools. func tool(name, description string, input map[string]any, run func(ctx context.Context, m *Manager, a map[string]any) (any, error)) stdio.Tool { return stdio.Tool{Name: name, Description: description, Input: input, Run: func(a map[string]any) (any, error) { m, err := manager() if err != nil { return nil, err } ctx := WithCause(context.Background(), Cause{Why: "the " + name + " tool", By: stdio.CallerOf(a)}) return run(ctx, m, a) }} } func boolean(description string) map[string]any { return map[string]any{"type": "boolean", "description": description} } func number(description string) map[string]any { return map[string]any{"type": "number", "description": description} } func moduleTools() []stdio.Tool { licence := str("a licence, as `licences` names it; absent for all") dry := boolean("answer what the rule would do, and do nothing") return []stdio.Tool{ tool("licences_read_usage", "Read usage now, for one subscription or every one, as the manager does on its cadence; then apply the switch rule unless dry. Answers each reading and what the rule did or would do.", map[string]any{"licence": licence, "dry": dry}, func(ctx context.Context, m *Manager, a map[string]any) (any, error) { l, _ := a["licence"].(string) d, _ := a["dry"].(bool) return m.ReadUsageOf(ctx, strings.TrimSpace(l), d) }), tool("licences_switch_now", "Apply the switch rule now from the latest readings, for one licence or all, even with switch_auto off since a person asked; with dry, answer the moves and make none.", map[string]any{"licence": licence, "dry": dry}, func(ctx context.Context, m *Manager, a map[string]any) (any, error) { l, _ := a["licence"].(string) d, _ := a["dry"].(bool) acts, err := m.ApplyRule(ctx, strings.TrimSpace(l), true, d) if acts == nil { acts = []Switch{} } return acts, err }), tool("licences_resync", "Recover: say binding.changed again for every binding, ask every node what it holds, say again each condition held, and hand each licence's access token to the vault again. Answers what it said.", nil, func(ctx context.Context, m *Manager, _ map[string]any) (any, error) { return m.Resync(ctx) }), } } // Resync says again what a consumer, the vault or a node may have missed (§42): binding.changed for every binding, // holdings.asked, each condition held, and licence-put of each licence's access token. Answers what it said. func (m *Manager) Resync(ctx context.Context) (map[string]any, error) { bindings, err := m.Store.Bindings(ctx) if err != nil { return nil, err } said := map[string]any{} consumers := []string{} for _, b := range bindings { m.changed(b, b.Licence) consumers = append(consumers, b.Consumer) } said["binding.changed"] = consumers if err := m.AskHoldings(CauseOf(ctx).By); err != nil { return said, fmt.Errorf("holdings.asked could not be said: %w", err) } said["holdings.asked"] = true if err := m.SayConditions(ctx, true); err != nil { return said, err } keys, err := m.Conditions.Keys() if err != nil { return said, err } sort.Strings(keys) said["conditions"] = keys licences, err := m.Store.Licences(ctx) if err != nil { return said, err } put := []string{} var errs []error for _, l := range licences { if l.Generation == 0 { continue } key := KeyFor(l.Name) // The access token is built from the grant, which the vault hands the manager; it hands a licence's access // token only to a machine a binding names (§17), and the manager's machine may have none. access, err := m.Grants.Get(ctx, key, PartGrant) if err == nil && l.Kind == "subscription" { var g FullGrant if err = json.Unmarshal([]byte(access), &g); err == nil { access = accessOf(g) } } if err == nil { err = m.Grants.Put(ctx, key, PartAccess, l.Generation, access) } access = "" if err != nil { errs = append(errs, fmt.Errorf("the access token of %s: %w", l.Name, err)) continue } put = append(put, l.Name) } said["licence-put"] = put return said, errors.Join(errs...) }