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# One usage pass over `infos` with the models and threshold of the settings.
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))