clilist.bend
cswap-bend-cli@0.1.0.0 · 281 lines · 45 definitions
1import Base
2import cswap-bend-cli@0.1.0.0/setload.bend as Setload
3import cswap-bend-cli@0.1.0.0/collectrun.bend as Collectrun
4import cswap-bend-cli@0.1.0.0/pollrow.bend as Pollrow
5import cswap-bend-cli@0.1.0.0/store/io.bend as SIo
6import cswap-bend-cli@0.1.0.0/store/base.bend as SBase
7import cswap-bend-cli@0.1.0.0/store/seq.bend as SSeq
8import cswap-bend-cli@0.1.0.0/json.bend as Json
9import cswap-bend-cli@0.1.0.0/clock.bend as Clock
10import cswap-bend-cli@0.1.0.0/act.bend as Act
11import cswap-bend-cli@0.1.0.0/cmd.bend as Cmd
12import cswap-bend-cli@0.1.0.0/view.bend as View
13import cswap-bend-cli@0.1.0.0/collect.bend as Collect
14import cswap-bend-cli@0.1.0.0/store.bend as Store
15import cswap-bend-cli@0.1.0.0/render.bend as Render
16import cswap-bend-cli@0.1.0.0/warn.bend as Warn
17import cswap-bend-cli@0.1.0.0/clibase.bend as Clibase
18
19def rows_json(as: List<&2, View.Acct>, +models: List<&2, String>, +now: U32, +off: U32) -> List<&2, Json.Json>:
20 match as:
21 case Nil{}:
22 []
23 case Con{h, t}:
24 View.account_row(h, models, now, off) <> rows_json(t, models, now, off)
25
26def active.pick(active: Bool, +num: U32, rest: Json.Json) -> Json.Json:
27 match active:
28 case True{}:
29 Json.JNum{U32.show(num)}
30 case False{}:
31 rest
32
33def active_num(as: List<&2, View.Acct>) -> Json.Json:
34 match as:
35 case Nil{}:
36 Json.JNull{}
37 case Con{View.Acct{num, e, on, ou, active, al, dis, lg, sen, row}, t}:
38 active.pick(active, num, active_num(t))
39
40def strs_json(xs: List<&2, String>) -> List<&2, Json.Json>:
41 match xs:
42 case Nil{}:
43 []
44 case Con{h, t}:
45 Json.JStr{h} <> strs_json(t)
46
47def with_warn(+k: String, xs: List<&2, String>, j: Json.Json) -> Json.Json:
48 match xs:
49 case Nil{}:
50 j
51 case Con{h, t}:
52 Json.set(k, Json.arr(strs_json(h <> t)), j)
53
54def list_payload(+as: List<&2, View.Acct>, models: List<&2, String>, +now: U32, +off: U32, dups: List<&2, String>, locks: List<&2, String>) -> Json.Json:
55 with_warn("lockstepUsageWarnings", locks, with_warn("duplicateAccountWarnings", dups,
56 Json.of_pairs([("schemaVersion", Json.JNum{"1"}), ("activeAccountNumber", active_num(as)), ("accounts", Json.arr(rows_json(as, models, now, off)))])))
57
58def lines.measured(has: Bool, +lg: Json.Json, +fetched: Maybe<&2, U32>, +now: U32, +off: U32, le: Maybe<&2, String>) -> List<&2, String>:
59 match has:
60 case True{}:
61 Render.measured(lg, fetched, Pollrow.age_of(fetched, now), now, off)
62 case False{}:
63 Render.unavailable(le)
64
65def acct_lines2(sentinel: Maybe<&2, String>, +row: Pollrow.Row, +now: U32, +off: U32) -> List<&2, String>:
66 match sentinel row:
67 case Some{s} Pollrow.Row{fetched, lg, la, fl, le, bu, np, iv, l429, st, sfp, cu, hu}:
68 Render.sentinel_lines(s, lg, fetched, Pollrow.age_of(fetched, now), off)
69 case None{} Pollrow.Row{+fetched, lg, la, fl, le, bu, np, iv, l429, st, sfp, cu, hu}:
70 lines.measured(View.is_obj(lg), lg, fetched, now, off, le)
71
72def acct_lines(+a: View.Acct, +now: U32, +off: U32) -> List<&2, String>:
73 match a:
74 case View.Acct{num, email, org_name, org_uuid, active, alias, disabled, login, sentinel, row}:
75 acct_lines2(sentinel, row, now, off)
76
77def tag_of(+org_name: String) -> String:
78 Clock.sel.s(String.is_empty(org_name), "personal", org_name)
79
80def head_line(+a: View.Acct) -> String:
81 match a:
82 case View.Acct{num, email, org_name, org_uuid, active, alias, disabled, login, sentinel, row}:
83 " " ++ U32.show(num) ++ ": " ++ Clock.sel.s(String.is_empty(alias), email, alias ++ " (" ++ email ++ ")") ++ " [" ++ tag_of(org_name) ++ "]"
84 ++ Clock.sel.s(active, " (active)", "") ++ Clock.sel.s(disabled, " (disabled)", "")
85
86def indent5(ls: List<&2, String>) -> String:
87 match ls:
88 case Nil{}:
89 ""
90 case Con{h, t}:
91 " " ++ h ++ "\n" ++ indent5(t)
92
93def acct_block(+a: View.Acct, +now: U32, +off: U32) -> String:
94 head_line(a) ++ "\n" ++ indent5(acct_lines(a, now, off))
95
96def blocks(as: List<&2, View.Acct>, +now: U32, +off: U32) -> String:
97 match as:
98 case Nil{}:
99 ""
100 case Con{h, Nil{}}:
101 acct_block(h, now, off)
102 case Con{h, Con{h2, t2}}:
103 acct_block(h, now, off) ++ "\n" ++ blocks(h2 <> t2, now, off)
104
105def warn_lines(xs: List<&2, String>) -> String:
106 match xs:
107 case Nil{}:
108 ""
109 case Con{h, t}:
110 "Warning: " ++ h ++ "\n" ++ warn_lines(t)
111
112def warn_gap(xs: List<&2, String>) -> String:
113 match xs:
114 case Nil{}:
115 ""
116 case Con{h, t}:
117 "\n" ++ warn_lines(h <> t)
118
119def warn_block(dups: List<&2, String>, locks: List<&2, String>) -> String:
120 warn_gap(List.append(&2, String, dups, locks))
121
122def list_text(as: List<&2, View.Acct>, +now: U32, +off: U32, dups: List<&2, String>, locks: List<&2, String>) -> String:
123 "Accounts:\n" ++ blocks(as, now, off) ++ warn_block(dups, locks)
124
125def list_out(+json: Bool, as: List<&2, View.Acct>, models: List<&2, String>, +now: U32, +off: U32, dups: List<&2, String>, locks: List<&2, String>) -> Cmd.Out:
126 match json:
127 case True{}:
128 Cmd.doc(list_payload(as, models, now, off, dups, locks))
129 case False{}:
130 Cmd.Out{0, list_text(as, now, off, dups, locks), ""}
131
132def empty_list(+json: Bool) -> Cmd.Out:
133 match json:
134 case True{}:
135 Cmd.doc(Json.of_pairs([("schemaVersion", Json.JNum{"1"}), ("activeAccountNumber", Json.JNull{}), ("accounts", Json.arr([]))]))
136 case False{}:
137 Cmd.ok("No accounts are managed yet.")
138
139def settings_of(+ctx: SBase.Ctx) -> Act.of(Json.Json):
140 Act.lift(Json.Json, Setload.load(SBase.root_of(ctx)))
141
142
143def usage_pass(+ctx: SBase.Ctx, infos: List<&2, Collect.Info>, tm: Clibase.Tm, +cfg: Json.Json) -> Act.of(List<&2, View.Acct>):
144 match tm:
145 case Clibase.Tm{now, off, tick}:
146 Collectrun.collect(ctx, infos, now, Setload.model(cfg), Setload.threshold_tenths(cfg), tick)
147
148def list_finish(+json: Bool, +infos: List<&2, Collect.Info>, +accts: List<&2, View.Acct>, +seq: Json.Json, +cfg: Json.Json, tm: Clibase.Tm) -> Cmd.Out:
149 match tm:
150 case Clibase.Tm{+now, off, tick}:
151 list_out(json, accts, Setload.model(cfg), now, off, Warn.duplicates(infos, seq), Warn.lockstep(accts, Setload.model(cfg), now))
152
153def list_run(+ctx: SBase.Ctx, +seq: Json.Json, +json: Bool) -> Act.of(Cmd.Out):
154 do Clibase.ActM<Cmd.Out>:
155 +tm : Clibase.Tm <- Clibase.times()
156 +cfg : Json.Json <- settings_of(ctx)
157 +infos : List<&2, Collect.Info> <- Collect.gather(ctx, seq)
158 +accts : List<&2, View.Acct> <- usage_pass(ctx, infos, tm, cfg)
159 return list_finish(json, infos, accts, seq, cfg, tm)
160
161def list_seq(m: Maybe<&2, Json.Json>, +ctx: SBase.Ctx, +json: Bool) -> Act.of(Cmd.Out):
162 match m:
163 case None{}:
164 Clibase.done(empty_list(json))
165 case Some{seq}:
166 list_run(ctx, seq, json)
167
168def list_cmd(+ctx: SBase.Ctx, +json: Bool) -> Act.of(Cmd.Out):
169 Act.bind(Maybe<&2, Json.Json>, Cmd.Out, SIo.read_seq(SBase.root_of(ctx)), m => list_seq(m, ctx, json))
170
171def unmanaged(+json: Bool, +email: String) -> Cmd.Out:
172 match json:
173 case True{}:
174 Cmd.doc(Json.of_pairs([("schemaVersion", Json.JNum{"1"}), ("active", Json.of_pairs([("email", Json.JStr{email}), ("managed", Json.JBool{False{}})]))]))
175 case False{}:
176 Cmd.ok("Status: " ++ email ++ " (not managed)")
177
178def logged_out(+json: Bool) -> Cmd.Out:
179 match json:
180 case True{}:
181 Cmd.doc(Json.of_pairs([("schemaVersion", Json.JNum{"1"}), ("active", Json.JNull{})]))
182 case False{}:
183 Cmd.ok("Status: No active Claude account")
184
185def only.pick(a: Bool, i: Collect.Info, rest: List<&2, Collect.Info>) -> List<&2, Collect.Info>:
186 match a:
187 case True{}:
188 i <> rest
189 case False{}:
190 rest
191
192def info_active(+i: Collect.Info) -> Bool:
193 match i:
194 case Collect.Info{num, email, org_name, org_uuid, alias, disabled, active, creds}:
195 active
196
197def active_only(xs: List<&2, Collect.Info>) -> List<&2, Collect.Info>:
198 match xs:
199 case Nil{}:
200 []
201 case Con{+i, t}:
202 only.pick(info_active(i), i, active_only(t))
203
204def status_row(+a: View.Acct, models: List<&2, String>, +now: U32, +off: U32) -> Json.Json:
205 Json.set("managed", Json.JBool{True{}}, Json.del("loginExpiresAt", Json.del("disabled", Json.del("active", View.account_row(a, models, now, off)))))
206
207def total_of(+seq: Json.Json) -> Json.Json:
208 Json.JNum{Nat.show(List.length(&2, String, Json.keys(Maybe.default(&2, Json.Json, Json.get("accounts", seq), Json.JNull{}))))}
209
210def total_text(j: Json.Json) -> String:
211 match j:
212 case Json.JNum{t}:
213 t
214 case _:
215 "0"
216
217def indent2(ls: List<&2, String>) -> String:
218 match ls:
219 case Nil{}:
220 ""
221 case Con{h, t}:
222 " " ++ h ++ "\n" ++ indent2(t)
223
224def status_text(+a: View.Acct, +total: Json.Json, +now: U32, +off: U32) -> String:
225 match a:
226 case View.Acct{num, email, org_name, org_uuid, active, alias, disabled, login, sentinel, row}:
227 "Status: Account-" ++ U32.show(num) ++ " (" ++ email ++ " [" ++ tag_of(org_name) ++ "])\n Total managed accounts: " ++ total_text(total) ++ "\n"
228 ++ indent2(acct_lines(a, now, off))
229
230def status_out2(+json: Bool, +a: View.Acct, models: List<&2, String>, +seq: Json.Json, +now: U32, +off: U32) -> Cmd.Out:
231 match json:
232 case True{}:
233 Cmd.doc(Json.of_pairs([("schemaVersion", Json.JNum{"1"}), ("active", status_row(a, models, now, off)), ("totalManagedAccounts", total_of(seq))]))
234 case False{}:
235 Cmd.Out{0, status_text(a, total_of(seq), now, off), ""}
236
237def status_out(+json: Bool, as: List<&2, View.Acct>, models: List<&2, String>, +seq: Json.Json, +now: U32, +off: U32) -> Cmd.Out:
238 match as:
239 case Nil{}:
240 Cmd.fail("the active account has no record")
241 case Con{+a, t}:
242 status_out2(json, a, models, seq, now, off)
243
244def status_finish(+json: Bool, accts: List<&2, View.Acct>, +seq: Json.Json, +cfg: Json.Json, tm: Clibase.Tm) -> Cmd.Out:
245 match tm:
246 case Clibase.Tm{now, off, tick}:
247 status_out(json, accts, Setload.model(cfg), seq, now, off)
248
249def status_run(+ctx: SBase.Ctx, +seq: Json.Json, +json: Bool) -> Act.of(Cmd.Out):
250 do Clibase.ActM<Cmd.Out>:
251 +tm : Clibase.Tm <- Clibase.times()
252 +cfg : Json.Json <- settings_of(ctx)
253 infos : List<&2, Collect.Info> <- Collect.gather(ctx, seq)
254 accts : List<&2, View.Acct> <- usage_pass(ctx, active_only(infos), tm, cfg)
255 return status_finish(json, accts, seq, cfg, tm)
256
257def status_slot(m: Maybe<&2, String>, +ctx: SBase.Ctx, +seq: Json.Json, +json: Bool, +email: String) -> Act.of(Cmd.Out):
258 match m:
259 case None{}:
260 Clibase.done(unmanaged(json, email))
261 case Some{slot}:
262 status_run(ctx, seq, json)
263
264def status_seq(m: Maybe<&2, Json.Json>, +ctx: SBase.Ctx, +json: Bool, +id: Store.Ident) -> Act.of(Cmd.Out):
265 match m:
266 case None{}:
267 Clibase.done(unmanaged(json, Store.id_email(id)))
268 case Some{+seq}:
269 status_slot(SSeq.find_slot(seq, Store.id_email(id), Store.id_org(id)), ctx, seq, json, Store.id_email(id))
270
271def status_ident.m(c: Bool, +id: Store.Ident, +ctx: SBase.Ctx, +json: Bool) -> Act.of(Cmd.Out):
272 match c:
273 case True{}:
274 Clibase.done(logged_out(json))
275 case False{}:
276 Act.bind(Maybe<&2, Json.Json>, Cmd.Out, SIo.read_seq(SBase.root_of(ctx)), m => status_seq(m, ctx, json, id))
277
278def status_ident(+id: Store.Ident, +ctx: SBase.Ctx, +json: Bool) -> Act.of(Cmd.Out):
279 status_ident.m(String.is_empty(Store.id_email(id)), id, ctx, json)
280def status_cmd(+ctx: SBase.Ctx, +json: Bool) -> Act.of(Cmd.Out):
281 Act.bind(Store.Ident, Cmd.Out, Act.lift(Store.Ident, Collect.live_ident(ctx)), id => status_ident(id, ctx, json))