daemon/gather.bend
cswap-bend-cli@0.1.0.0 · 194 lines · 34 definitions
1import Base
2import cswap-bend-cli@0.1.0.0/json.bend as Json
3import cswap-bend-cli@0.1.0.0/fs.bend as Fs
4import cswap-bend-cli@0.1.0.0/act.bend as Act
5import cswap-bend-cli@0.1.0.0/store.bend as Store
6import cswap-bend-cli@0.1.0.0/store/base.bend as SBase
7import cswap-bend-cli@0.1.0.0/store/io.bend as SIo
8import cswap-bend-cli@0.1.0.0/store/ops.bend as SOps
9import cswap-bend-cli@0.1.0.0/store/seq.bend as SSeq
10import cswap-bend-cli@0.1.0.0/usage.bend as Usage
11import cswap-bend-cli@0.1.0.0/swlive.bend as L
12import cswap-bend-cli@0.1.0.0/swbase.bend as SwB
13import cswap-bend-cli@0.1.0.0/policy/types.bend as T
14import cswap-bend-cli@0.1.0.0/daemon/rows.bend as Rows
15import cswap-bend-cli@0.1.0.0/daemon/state.bend as St
16
17
18
19
20
21
22
23
24type Ent is Data:
25 Ent{slot: String, email: String, org: String, creds: String, acct: T.Account}
26
27type Snap is Data:
28 Snap{seq: Json.Json, live: L.Live, active: Maybe<&2, String>, state: Json.Json, rows: Json.Json, ents: List<&2, Ent>}
29
30def ActM(-A: Type) -> Type:
31 Act.of(A)
32
33def ActM.pure(-A: Type, x: A) -> Act.of(A):
34 Act.pure(A, x)
35
36def ActM.bind(-A: Type, -B: Type, m: Act.of(A), f: A -> Act.of(B)) -> Act.of(B):
37 Act.bind(A, B, m, f)
38
39def is_true(+m: Maybe<&2, Json.Json>) -> Bool:
40 match m:
41 case Some{Json.JBool{b}}:
42 b
43 case _:
44 False{}
45
46def refresh_of(+creds: String) -> String:
47 SSeq.field_str(SSeq.get_or("claudeAiOauth", Json.unwrap(Json.parse(creds)), Json.JNull{}), "refreshToken")
48
49def kind2(+api: Bool, +refresh: Bool) -> T.AcctKind:
50 match api refresh:
51 case True{} _:
52 T.ApiKey{}
53 case False{} True{}:
54 T.FullLogin{}
55 case False{} False{}:
56 T.SetupToken{}
57
58def kind_of(+api_rec: Bool, +creds: String) -> T.AcctKind:
59 kind2(api_rec || SBase.looks_like_api_key(creds), SBase.nonempty(refresh_of(creds)))
60
61def ent_of(+slot: String, +rec: Json.Json, +creds: String, +cfg_ok: Bool, +quarantined: Bool) -> Ent:
62 Ent{slot, SSeq.field_str(rec, "email"), SSeq.field_str(rec, "organizationUuid"), creds,
63 T.Account{U32.to_nat(SSeq.str_u32(slot)), SSeq.field_str(rec, "email"), kind_of(SSeq.is_api_key_rec(rec), creds),
64 is_true(Json.get("disabled", rec)) || quarantined || Bool.not(SBase.nonempty(creds) && cfg_ok)}}
65
66def mem_str(+x: String, +xs: List<&2, String>) -> Bool:
67 match xs:
68 case Nil{}:
69 False{}
70 case Con{h, t}:
71 String.eq(h, x) || mem_str(x, t)
72
73def has_config(+root: String, +seq: Json.Json, +slot: String) -> Act.of(Bool):
74 do ActM<Bool>:
75 cfg : Maybe<&2, String> <- L.read_text_opt(SBase.config_path(root, slot, SIo.slot_email(seq, slot)))
76 return SBase.nonempty(SwB.text_or(cfg))
77
78def cred_of(+root: String, +seq: Json.Json, +slot: String) -> Act.of(String):
79 SIo.read_cred(root, slot, SIo.slot_email(seq, slot))
80
81def ent_one(+root: String, +seq: Json.Json, +slot: String, +st: Json.Json) -> Act.of(Ent):
82 do ActM<Ent>:
83 creds : String <- cred_of(root, seq, slot)
84 ok : Bool <- has_config(root, seq, slot)
85 return ent_of(slot, SSeq.get_or(slot, SSeq.accounts_of(seq), Json.JNull{}), creds, ok, mem_str(slot, St.quarantined_slots(st)))
86
87def ents_of(+root: String, +seq: Json.Json, +slots: List<&2, String>, +st: Json.Json) -> Act.of(List<&2, Ent>):
88 match slots:
89 case Nil{}:
90 Act.pure(List<&2, Ent>, [])
91 case Con{s, t}:
92 do ActM<List<&2, Ent>>:
93 e : Ent <- ent_one(root, seq, s, st)
94 rest : List<&2, Ent> <- ents_of(root, seq, t, st)
95 return e <> rest
96
97def rows3(+v: String, +j: Json.Json) -> Json.Json:
98 Store.sel.j(String.eq(v, "2"), SSeq.accounts_of(j), Json.JObj{Json.JNil{}})
99
100def rows2(r: Result<&2, &2, String, Json.Json>) -> Json.Json:
101 match r:
102 case Fail{e}:
103 Json.JObj{Json.JNil{}}
104 case Done{+j}:
105 rows3(SOps.field_json(j, "schemaVersion"), j)
106
107
108def rows_of(r: Fs.Res(String)) -> Json.Json:
109 match r:
110 case Fail{e}:
111 SSeq.accounts_of(Json.JNull{})
112 case Done{t}:
113 rows2(Json.parse(t))
114
115def read_rows(+root: String) -> IO(Json.Json):
116 IO.bind(Fs.Res(String), Json.Json, Fs.read_file(root ++ "/cache/usage.json"), r => IO.pure(Json.Json, rows_of(r)))
117
118def ents_for(+ctx: SBase.Ctx, +seq: Json.Json, +st: Json.Json) -> Act.of(List<&2, Ent>):
119 ents_of(SBase.root_of(ctx), seq, SwB.slots_of(seq), st)
120
121def state_of(+ctx: SBase.Ctx) -> Act.of(Json.Json):
122 Act.lift(Json.Json, St.read_state(SBase.root_of(ctx)))
123
124def load(+ctx: SBase.Ctx, +seq: Json.Json) -> Act.of(Snap):
125 do ActM<Snap>:
126 +live : L.Live <- L.read_live(ctx)
127 +st : Json.Json <- state_of(ctx)
128 rows : Json.Json <- Act.lift(Json.Json, read_rows(SBase.root_of(ctx)))
129 ents : List<&2, Ent> <- ents_for(ctx, seq, st)
130 return Snap{seq, live, L.lv_slot(live, seq), st, rows, ents}
131
132def some_snap(s: Snap) -> Act.of(Maybe<&2, Snap>):
133 Act.pure(Maybe<&2, Snap>, Some{s})
134
135def snapshot2(+ctx: SBase.Ctx, +m: Maybe<&2, Json.Json>) -> Act.of(Maybe<&2, Snap>):
136 match m:
137 case None{}:
138 Act.pure(Maybe<&2, Snap>, None{})
139 case Some{seq}:
140 Act.bind(Snap, Maybe<&2, Snap>, load(ctx, seq), s => some_snap(s))
141
142
143def snapshot(+ctx: SBase.Ctx) -> Act.of(Maybe<&2, Snap>):
144 do ActM<Maybe<&2, Snap>>:
145 m : Maybe<&2, Json.Json> <- SIo.read_seq(SBase.root_of(ctx))
146 snapshot2(ctx, m)
147
148def snap_ents(+s: Snap) -> List<&2, Ent>:
149 match s:
150 case Snap{seq, live, active, st, rows, ents}:
151 ents
152
153def snap_active(+s: Snap) -> Maybe<&2, String>:
154 match s:
155 case Snap{seq, live, active, st, rows, ents}:
156 active
157
158def snap_state(+s: Snap) -> Json.Json:
159 match s:
160 case Snap{seq, live, active, st, rows, ents}:
161 st
162
163def snap_rows(+s: Snap) -> Json.Json:
164 match s:
165 case Snap{seq, live, active, st, rows, ents}:
166 rows
167
168def in_skip(+slot: String, +skip: List<&2, String>) -> Bool:
169 mem_str(slot, skip)
170
171def disable_ent(e: Ent, +skip: List<&2, String>) -> Ent:
172 match e:
173 case Ent{+slot, email, org, creds, T.Account{n, ae, k, d}}:
174 Ent{slot, email, org, creds, T.Account{n, ae, k, d || in_skip(slot, skip)}}
175
176def disable_ents(+es: List<&2, Ent>, +skip: List<&2, String>) -> List<&2, Ent>:
177 match es:
178 case Nil{}:
179 []
180 case Con{e, t}:
181 disable_ent(e, skip) <> disable_ents(t, skip)
182
183def skip_one(s: Snap, +skip: List<&2, String>) -> Snap:
184 match s:
185 case Snap{seq, live, active, st, rows, ents}:
186 Snap{seq, live, active, st, rows, disable_ents(ents, skip)}
187
188
189def skip_snap(+m: Maybe<&2, Snap>, +skip: List<&2, String>) -> Maybe<&2, Snap>:
190 match m:
191 case None{}:
192 None{}
193 case Some{s}:
194 Some{skip_one(s, skip)}