domain/architecture/system/release/plugin/ops.bend

near-v-framework@0.7.0.0 · 94 lines · 0 definitions

1import Base
2import bend-net-json@0.3.0.0/json.bend as Json
3import near-v-framework@0.7.0.0/domain/architecture/system/release/plugin/json.bend as J
4import near-v-framework@0.7.0.0/domain/architecture/system/release/plugin/type.bend as Pl
5import near-v-framework@0.7.0.0/domain/architecture/system/release/plugin/claude/ops.bend as ClO
6import near-v-framework@0.7.0.0/domain/architecture/system/release/plugin/codex/ops.bend as CxO
7
8# A plugin's two manifests: the root plugin.json (Agent Plugins 1.0, which Codex reads) and
9# .claude-plugin/plugin.json (which Claude Code reads).
10
11def schema() -> String:
12  "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json"
13
14def version_show(v: Pl.Version) -> String:
15  match v:
16    case Pl.Version{major, minor, patch}:
17      Nat.show(major) ++ "." ++ Nat.show(minor) ++ "." ++ Nat.show(patch)
18
19def author(a: Pl.Author) -> Json.Val:
20  match a:
21    case Pl.Author{name, email, url}:
22      J.cat([[("name", Json.Str{name})], J.opt("email", J.text(email)), J.opt("url", J.text(url))])
23
24def keywords(xs: List<&2, String>) -> List<&2, Sigma<&2, &2, String, _ => Json.Val>>:
25  match xs:
26    case []:
27      []
28    case x <> rest:
29      [("keywords", J.texts(x <> rest))]
30
31def core_keys(c: Pl.Core) -> List<&2, Sigma<&2, &2, String, _ => Json.Val>>:
32  match c:
33    case Pl.Core{name, v, description, a, homepage, repository, license, ks}:
34      List.concat(&2, Sigma<&2, &2, String, _ => Json.Val>, [
35        [("name", Json.Str{name}), ("version", Json.Str{version_show(v)}), ("description", Json.Str{description}), ("author", author(a))],
36        J.opt("homepage", J.text(homepage)), J.opt("repository", J.text(repository)), J.opt("license", J.text(license)), keywords(ks)])
37
38# Codex's extension, or nothing when Codex adds nothing.
39def extensions(kvs: List<&2, Sigma<&2, &2, String, _ => Json.Val>>) -> List<&2, Sigma<&2, &2, String, _ => Json.Val>>:
40  match kvs:
41    case []:
42      []
43    case kv <> rest:
44      [("extensions", J.obj([("com.openai", J.obj(kv <> rest))]))]
45
46# The root plugin.json's text.
47def agent(p: Pl.Package) -> String:
48  match p:
49    case Pl.Package{c, _, cx}:
50      Json.encode(J.cat([[("$schema", Json.Str{schema()})], core_keys(c), extensions(CxO.keys(cx))])) ++ "\n"
51
52# .claude-plugin/plugin.json's text.
53def claude(p: Pl.Package) -> String:
54  match p:
55    case Pl.Package{c, cl, _}:
56      Json.encode(J.cat([core_keys(c), ClO.keys(cl)])) ++ "\n"
57
58def name_of(p: Pl.Package) -> String:
59  match p:
60    case Pl.Package{Pl.Core{n, _, _, _, _, _, _, _}, _, _}:
61      n
62
63# ---- the standard's rule for a name ----
64def name_char(+c: Char) -> Bool:
65  Char.is_lower(c) || Char.is_digit(c) || Char.is_eq(c, '.') || Char.is_eq(c, '-')
66
67def edge_char(+c: Char) -> Bool:
68  Char.is_lower(c) || Char.is_digit(c)
69
70def repeats(cs: List<&2, Char>, +prev: Char) -> Bool:
71  match cs:
72    case []:
73      False{}
74    case +c <> rest:
75      ((Char.is_eq(c, '-') || Char.is_eq(c, '.')) && Char.is_eq(c, prev)) || repeats(rest, c)
76
77def all_name_chars(cs: List<&2, Char>) -> Bool:
78  match cs:
79    case []:
80      True{}
81    case +c <> rest:
82      name_char(c) && all_name_chars(rest)
83
84def first_ok(cs: List<&2, Char>) -> Bool:
85  match cs:
86    case []:
87      False{}
88    case +c <> _:
89      edge_char(c)
90
91# 1 to 64 of a-z, 0-9, . and -, starting and ending with a letter or digit, with no -- or ..
92def name_ok(+n: String) -> Bool:
93  Nat.is_ge(String.length(n), 1n) && Nat.is_le(String.length(n), 64n) && all_name_chars(String.to_list(n)) &&
94  first_ok(String.to_list(n)) && first_ok(String.to_list(String.reverse(n))) && Bool.not(repeats(String.to_list(n), ' '))