swbase.bend
cswap-bend-cli@0.1.0.0 · 255 lines · 50 definitions
1import Base
2import cswap-bend-cli@0.1.0.0/json.bend as Json
3import cswap-bend-cli@0.1.0.0/store.bend as Store
4import cswap-bend-cli@0.1.0.0/store/base.bend as SBase
5import cswap-bend-cli@0.1.0.0/store/ops.bend as SOps
6import cswap-bend-cli@0.1.0.0/store/seq.bend as SSeq
7
8
9
10
11
12def e_config() -> U32:
13 1
14
15def e_validation() -> U32:
16 2
17
18def e_not_found() -> U32:
19 3
20
21
22def e_overridden() -> U32:
23 20
24
25def error_type(+code: U32) -> String:
26 SBase.sel.s(U32.is_eq(code, 1), "ConfigError",
27 SBase.sel.s(U32.is_eq(code, 2), "ValidationError",
28 SBase.sel.s(U32.is_eq(code, 3), "AccountNotFoundError",
29 SBase.sel.s(U32.is_eq(code, 4), "SwitchError",
30 SBase.sel.s(U32.is_eq(code, 5), "CredentialReadError",
31 SBase.sel.s(U32.is_eq(code, 20), "live-login-overridden", "Error"))))))
32
33
34def merge_oauth(+cfg: Json.Json, +oauth: Json.Json) -> Json.Json:
35 Json.set("oauthAccount", oauth, cfg)
36
37def approved_form(+key: String) -> String:
38 String.drop(String.trim(key), Nat.sub(String.length(String.trim(key)), 20n))
39
40def has_text(+xs: Json.Json, +t: String) -> Bool:
41 match xs:
42 case Json.JCons{Json.JStr{s}, rest}:
43 String.eq(s, t) || has_text(rest, t)
44 case Json.JCons{h, rest}:
45 has_text(rest, t)
46 case _:
47 False{}
48
49def snoc(+xs: Json.Json, +x: Json.Json) -> Json.Json:
50 match xs:
51 case Json.JCons{h, t}:
52 Json.JCons{h, snoc(t, x)}
53 case _:
54 Json.JCons{x, Json.JNil{}}
55
56def approved_list(+responses: Json.Json) -> Json.Json:
57 SSeq.unwrap_arr(SSeq.get_or("approved", responses, Json.JNull{}))
58
59def with_rejected(+has: Bool, +r: Json.Json) -> Json.Json:
60 match has:
61 case True{}:
62 r
63 case False{}:
64 Json.set("rejected", Json.JArr{Json.JNil{}}, r)
65
66def responses2(+j: Json.Json) -> Json.Json:
67 match j:
68 case Json.JObj{f}:
69 Json.JObj{f}
70 case _:
71 Json.JObj{Json.JNil{}}
72
73def responses_of(+cfg: Json.Json) -> Json.Json:
74 responses2(SSeq.get_or("customApiKeyResponses", cfg, Json.JNull{}))
75
76def add_approved(+items: Json.Json, +key20: String) -> Json.Json:
77 Store.sel.j(has_text(items, key20), items, snoc(items, Json.JStr{key20}))
78
79def is_some_json(+m: Maybe<&2, Json.Json>) -> Bool:
80 match m:
81 case Some{x}:
82 True{}
83 case None{}:
84 False{}
85
86def managed_responses(+responses: Json.Json, +key20: String) -> Json.Json:
87 with_rejected(is_some_json(Json.get("rejected", responses)), Json.set("approved", Json.JArr{add_approved(approved_list(responses), key20)}, responses))
88
89
90
91def set_managed(+cfg: Json.Json, +key: String) -> Json.Json:
92 Json.set("primaryApiKey", Json.JStr{String.trim(key)},
93 Json.set("customApiKeyResponses", managed_responses(responses_of(cfg), approved_form(key)), cfg))
94
95def activate2(+api_key: Bool, +cfg: Json.Json, +oauth: Json.Json, +target_creds: String) -> Json.Json:
96 match api_key:
97 case True{}:
98 merge_oauth(set_managed(cfg, target_creds), oauth)
99 case False{}:
100 merge_oauth(Json.del("primaryApiKey", cfg), oauth)
101
102
103
104def activate_config(+cfg: Json.Json, +oauth: Json.Json, +target_creds: String) -> Json.Json:
105 activate2(SBase.looks_like_api_key(target_creds), cfg, oauth, target_creds)
106
107def num_json(+m: Maybe<&2, String>) -> Json.Json:
108 match m:
109 case Some{n}:
110 Json.JNum{n}
111 case None{}:
112 Json.JNull{}
113
114def ref(+num: Maybe<&2, String>, +email: String) -> Json.Json:
115 Json.JObj{Json.JField{"number", num_json(num), Json.JField{"email", Json.JStr{email}, Json.JNil{}}}}
116
117def strs_json(+xs: List<&2, String>) -> List<&2, Json.Json>:
118 match xs:
119 case Nil{}:
120 []
121 case Con{h, t}:
122 Json.JStr{h} <> strs_json(t)
123
124def result(+switched: Bool, +from: Json.Json, +to: Json.Json, +strategy: String, +reason: String, +message: String, +warnings: List<&2, String>) -> Json.Json:
125 Json.JObj{Json.JField{"schemaVersion", Json.JNum{"1"}, Json.JField{"switched", Json.JBool{switched}, Json.JField{"from", from,
126 Json.JField{"to", to, Json.JField{"strategy", Json.JStr{strategy}, Json.JField{"reason", Json.JStr{reason}, Json.JField{"message", Json.JStr{message},
127 Json.JField{"warnings", Json.arr(strs_json(warnings)), Json.JNil{}}}}}}}}}}
128
129
130def error_envelope(+code: U32, +msg: String) -> Json.Json:
131 Json.JObj{Json.JField{"schemaVersion", Json.JNum{"1"}, Json.JField{"error",
132 Json.JObj{Json.JField{"type", Json.JStr{error_type(code)}, Json.JField{"message", Json.JStr{msg}, Json.JNil{}}}}, Json.JNil{}}}}
133
134def ref_text(+r: Json.Json) -> String:
135 "Account-" ++ SOps.field_json(r, "number") ++ " (" ++ SSeq.field_str(r, "email") ++ ")"
136
137def same_ref(+a: Json.Json, +b: Json.Json) -> Bool:
138 String.eq(SOps.field_json(a, "number"), SOps.field_json(b, "number")) && String.eq(SSeq.field_str(a, "email"), SSeq.field_str(b, "email"))
139
140def switched_msg(+sw: Bool, +to: Json.Json) -> String:
141 SBase.sel.s(sw, "Switched to ", "Already on ") ++ ref_text(to)
142
143def result_of_op4(+sw: Bool, +f: Json.Json, +t: Json.Json, +strategy: String, +warns: List<&2, String>) -> Json.Json:
144 result(sw, f, t, strategy, SBase.sel.s(sw, "switched", "already-active"), switched_msg(sw, t), warns)
145
146def texts_chain(+items: Json.Json) -> List<&2, String>:
147 match items:
148 case Json.JCons{h, t}:
149 SSeq.str_or(Json.as_string(h), "") <> texts_chain(t)
150 case _:
151 []
152
153def texts_of(+w: Json.Json) -> List<&2, String>:
154 match w:
155 case Json.JArr{items}:
156 texts_chain(items)
157 case _:
158 []
159
160def result_of_op3(+f: Json.Json, +t: Json.Json, +strategy: String, +extra: List<&2, String>, +op: Json.Json) -> Json.Json:
161 result_of_op4(Bool.not(same_ref(f, t)), f, t, strategy, List.append(&2, String, extra, texts_of(SSeq.get_or("warnings", op, Json.JNull{}))))
162
163def result_of_op2(+from: Maybe<&2, Json.Json>, +to: Maybe<&2, Json.Json>, +strategy: String, +extra: List<&2, String>, +op: Json.Json) -> Json.Json:
164 result_of_op3(SSeq.get_or("from", op, Json.JNull{}), SSeq.get_or("to", op, Json.JNull{}), strategy, extra, op)
165
166
167def result_of_op(+op: Json.Json, +strategy: String, +extra: List<&2, String>) -> Json.Json:
168 result_of_op2(Json.get("from", op), Json.get("to", op), strategy, extra, op)
169
170def text_or(+m: Maybe<&2, String>) -> String:
171 SSeq.str_or(m, "")
172
173def unwrap_json(+m: Maybe<&2, Json.Json>) -> Json.Json:
174 match m:
175 case Some{j}:
176 j
177 case None{}:
178 Json.JNull{}
179
180def is_some_str(+m: Maybe<&2, String>) -> Bool:
181 match m:
182 case Some{x}:
183 True{}
184 case None{}:
185 False{}
186
187def from_ref(+email: String, +slot: Maybe<&2, String>) -> Json.Json:
188 Store.sel.j(String.is_empty(email), Json.JNull{}, ref(slot, email))
189
190def op_json(+from: Json.Json, +to: Json.Json, +warns: List<&2, String>, +org: String) -> Json.Json:
191 Json.JObj{Json.JField{"from", from, Json.JField{"to", to, Json.JField{"warnings", Json.arr(strs_json(warns)),
192 Json.JField{"targetOrg", Json.JStr{org}, Json.JNil{}}}}}}
193
194def slot_strs(+xs: List<&2, U32>) -> List<&2, String>:
195 match xs:
196 case Nil{}:
197 []
198 case Con{h, t}:
199 U32.show(h) <> slot_strs(t)
200
201
202def slots_of(+seq: Json.Json) -> List<&2, String>:
203 slot_strs(SSeq.seq_nums(seq))
204
205def sel_ms(+c: Bool, +x: Maybe<&2, String>, +y: Maybe<&2, String>) -> Maybe<&2, String>:
206 match c:
207 case True{}:
208 x
209 case False{}:
210 y
211
212def override_msg(+target: String, +temail: String, +observed: String) -> String:
213 "live-login-overridden: after switching to Account-" ++ target ++ " (" ++ temail ++ ") the live login reads "
214 ++ SBase.sel.s(String.is_empty(observed), "no account", observed)
215 ++ "; a Claude Code keychain login is overriding cswap; remove the 'Claude Code-credentials' keychain item"
216
217def noop(+strategy: String, +reason: String, +message: String, +to: Json.Json, +warns: List<&2, String>) -> Json.Json:
218 result(False{}, to, to, strategy, reason, message, warns)
219
220def forced2(+rewrite: Bool, +r: Json.Json) -> Json.Json:
221 Store.sel.j(rewrite, Json.set("message", Json.JStr{"Activated " ++ ref_text(SSeq.get_or("to", r, Json.JNull{})) ++ " from stored backup"},
222 Json.set("reason", Json.JStr{"activated"}, r)), r)
223
224def is_true(+m: Maybe<&2, Json.Json>) -> Bool:
225 match m:
226 case Some{Json.JBool{True{}}}:
227 True{}
228 case _:
229 False{}
230
231def forced_result(+force: Bool, +r: Json.Json) -> Json.Json:
232 forced2(force && Bool.not(is_true(Json.get("switched", r))), r)
233
234def sel_ls(+c: Bool, +x: List<&2, String>, +y: List<&2, String>) -> List<&2, String>:
235 match c:
236 case True{}:
237 x
238 case False{}:
239 y
240
241
242def num_str(+m: Maybe<&2, Json.Json>) -> Maybe<&2, String>:
243 match m:
244 case Some{Json.JNum{t}}:
245 sel_ms(String.eq(t, "0"), None{}, Some{t})
246 case _:
247 None{}
248
249def sel_s(+c: Bool, +x: String, +y: String) -> String:
250 SBase.sel.s(c, x, y)
251
252
253def with_models(+r: Json.Json, +models: List<&2, String>, +source: String) -> Json.Json:
254 Store.sel.j(List.is_empty(&2, String, models), r,
255 Json.set("modelSource", Json.JStr{source}, Json.set("models", Json.arr(strs_json(models)), r)))