This page contains some of the small to medium-sized Goal programs that I've written, most of which depend on ari's extensions to Goal, shared here under the MIT license.

Many are incomplete, there are certainly mistakes and non-idiomatic constructions, and per the MIT license, I make no warranties. It's often helpful to see how other people did things, so I'm sharing how I have.

License

Copyright 2025 Daniel Gregoire

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

archive-stale.goal🔗

import"sc-client/sc"
/ Dates
staledur:*/(time.Hour;24;30;15); dtfmt:{time.format[time.RFC3339;x]}
now:time.utc time.now""; stale:time.add[now;-staledur]
/ Stories
eng:cache.workflows[1488]
qtmp:{..["archived":-0w;"updated_at_end":dtfmt[stale];"workflow_state_id":x]}
sss:story.search'qtmp'(..id)'eng"states"
toarchive:,/sss; #toarchive
ids:"i"$(..id)'toarchive; names:(..name)'toarchive
grade:>ids; sids:ids[grade]; snms:names[grade]; sarc:toarchive[grade]
"toarchive.csv"print","csv(sids;snms)
/ Updates
prts:(-500)$sarc; #prts
arcem:{[batch]n:#batch; say"Archiving batch of $n stories..."; story.update.bulk[..["story_ids":"i"$(..id)'batch;"archived":0w]]}
arcem'prts

bugslo.goal🔗

/ Goal tools
import "../go/goal/lib/fmt.goal"

/ Shortcut HTTP API
url:"https://api.app.shortcut.com/api/v3/";tv:"SHORTCUT_API_TOKEN";st:env tv;and["e"~@st;say "[ERR] Set the ${tv} env variable."]
urlrep:" ""%20"
urlenc:{[s]sub[urlrep] s}
gcmd:!"curl -s --fail-with-body -X GET -H 'Content-Type:application/json' -H 'Shortcut-Token:${st}' -L '${url}"
pcmd:("curl";"-s";"-X";"POST";"-d";"DATA";"-H";"'Content-Type: application/json'";"-H";"'Shortcut-Token: ${st}'";"-L";"${url}")
sc.get:{[p]ret:shell" "/@[gcmd;-1+#gcmd;+;p+"'"];?["e"~@ret;ret;json ret]}
sc.getq:{[p;querys]q:urlenc querys; ret:shell" "/@[gcmd;-1+#gcmd;+;p+"?$q'"];?["e"~@ret;ret;json ret]}
sc.pst:{[p;js]cmd:@[pcmd;&"DATA"=pcmd;:;"'${js}'"];ret:shell" "/@[cmd;-1+#cmd;+;p];?["e"~@ret;ret;json ret]}
sc.q:{[query]sc.pst["stories/search";""json sq]}
true:0w; false:-0w; null:0n
ppd:fmt.dict[;"%.1f"]

/ Shortcut data needed to resolve IDs
say "Fetching workflows..."; wfs:sc.get"workflows"; cwfs:#wfs; say "Fetched ${cwfs} workflows"
dwf:!/+(..id,name)'wfs / DICTIONARY: Workflow ID to name
stidnames:{[wf]wfid:wf"id"; sts:wf"states"; stids:(..id)'sts; stnames:(..name)'sts; (stids;stnames)}'wfs
dwfst:,/!/'idnames / DICTIONARY: Workflow State ID to name

/ Bug SLO
sq:..[archived:false;"story_type":"bug";"created_at_start":"2024-05-07T00:00:00Z"]
:ftsq:rq`type:bug !is:archived !is:done created:2024-05-07..*`
:sres:sc.getq["search/stories";"query=$ftsq"]
sres"total"
say "\n"/(..name)'sres"data"

say "Fetching bugs..."; b1bugs:sc.q[sq]; c:#b1bugs; say "Fetched $c bugs"
b1nd:sc.q[sq..["workflow_state_types":,"backlog""unstarted""started"]] / not done
b1dn:sc.q[sq..["workflow_state_types":,"done"]]                        / done
#b1dn
#b1bugs
sq
sq..["workflow_state_types":,"done"]

@[;$"workflow_state_id"]
(..x"workflow_state_id")'b1nd
(.."workflow_state_id")
(..name)

!*b1nd
#'(b1nd;b1dn;b1bugs)
#done
ppd@*done
#allbugs
22 % 63
"65% are done, 35% left to be squashed"

"%.1f"$100 * 1 - 17 % 68
68 % 17

csv-import-leads.goal🔗

fld:"csv-import-leads/"; jfiles:"\n"\-shell"ls $fld"
cols:^?,/!json read fld+*jfiles
msgs:json read fld+jfiles
xs:"\n"\-(..text)'msgs[&{\x;"bot_message"~x["subtype"]}'msgs]
xs[!10]

csvplay.goal🔗

a:{sql.q"select * from starwars.csv"}
b:{csv.tbl@csv 'read"starwars.csv"}
help"rt.time"
"%.2f"$math.avg 1_3{rt.time[a;0;1000]}\()
"%.2f"$math.avg 1_3{rt.time[b;0;1000]}\()
math.avg 1_3{rt.time[b;0;1000]}\()

deptree.goal🔗

say ARGS

pd:{[d]ks:!d;mk::|/#'"c"$'!d;say"\n"/{" "+((-mk)!y)+" : "+$x[y]}[d;]'ks}
url:"https://api.app.shortcut.com/api/v3/";tv:"SHORTCUT_API_TOKEN";st:env tv;and["e"~@st;say "[ERR] Set the ${tv} env variable."]
diag:"" / final dependency diagram
idstr:{[d]and[~"d"=@d;:error"${o} expects dictionary"]; "%0.f"$d"id"} / goal deserializes JSON numbers as floats
gcmd:!"curl -s --fail-with-body -X GET -H 'Content-Type:application/json' -H 'Shortcut-Token:${st}' -L '${url}"
sc.get:{[p]ret:shell" "/@[gcmd;-1+#gcmd;+;p+"'"];?["e"~@ret;ret;json ret]}
epid:259457 / TODO From ARGS
ss:sc.get"epics/${epid}/stories"
nodes:{[st]id:idstr[st];name:sub[rx/(\r\n|\r|\n)/;" "]st["name"];"${id}: ${name}"}'ss
say "\n"/nodes
:s:ss[2]
d:*s"story_links"

pd's"story_links"
pd[*s"story_links"]
:sl:st"story_links"
st"id"
{x"subject_id"}'sl
es:{[st]stlnks:st["story_links"];stlnks[{(st"id")=x["subject_id"]}]}'sts
(..type="subject")''es
:edges:{[st]stlnks:st["story_links"];subj:(~(..type="subject")'stlnks)^stlnks}'sts
stlnks:{x["story_links"]}'sts
sub[rx/(\r\n|\r|\n)/;" "]"foo\nbar"
"foo\nbar"sub
ids:(..id)'sts
dd:ids!(#ids)#()
dd
numlinks:#'stlnks
(numlinks;~numlinks)
+/~numdeps
{~~numdeps}^stlnks
{~~numdeps}[1]
@'stlnks
stlnks
:nodeps:(~numdeps)^stlks
whdeps
deps:stlks[whdeps]
*sts
(#!:)'sts
stlks[&#'stlks]
#'stlks
{#'stlks}#stlks


/
TODO Integrate d2 (written in Go) with cloned version of Goal,
     so a single executable can make a local SVG of the deps.
\



/pcmd:("curl";"-s";"-X";"POST";"-d";"DATA";"-H";"'Content-Type: application/json'";"-H";"'Shortcut-Token: ${st}'";"-L";"${url}")
/sc.pst:{[p;js]cmd:@[pcmd;&"DATA"=pcmd;:;"'${js}'"];ret:shell" "/@[cmd;-1+#cmd;+;p];?["e"~@ret;ret;json ret]}

/say "Fetching all members and saving in md..."
/ms:sc.get"members"
/mids:@[;"id"]'ms
/mnms:{x["profile";"name"]}'ms
/md:mids!mnms
/!md

/stdep:500165911 / workflow state 'Deployed'
/strqa:500165915 / workflow state 'Ready for QA'
/stqa:500165916  / workflow state 'In QA'
/say "Searching for Bug Bash deployed stories...";ss:sc.pst["stories/search";qq#{"archived": false, "label_name": "bugbash_2024_moved_deployed"}#]
/ls:#ss;say "History of ${ls} deployed stories...";sc.hist:{[id]sc.get"stories/${id}/history"}
/his:{[s]sc.hist id[s]}

/win:{[h]
/ macts:@[;["member_id","actions"]]'h
/ acts:{x[1]}'macts
/ ups:macts[&0<{+/|"update"=x}'@[;"action"]''acts] / includes an update
/ who:{md[x]}'*''macts[&{or[x~stdep;x~stqa]}'''{and["d"=@x;x"new"]}''{and["d"=@x;x"workflow_state_id"]}''@[;"changes"]''acts]
/ who
/}
/say win his[*ss]

/h:sc.hist[id[*|ss]]
/macts:@[;!"member_id actions"]'h
/acts:@[;1]'macts
/mups:macts[&0<{+/|"update"=x}'@[;"action"]''acts]
/"--------"
/upacts:@[;1]'mups
/upact:upacts[11]
/!'upact
/*upact
/@[;"changes"]'upact

/acts[&0<{+/|"update"=x}'@[;"action"]''acts]
/say @[cmd;-1+#cmd;+;"member"]
/say @[cmd;&"GET"=cmd;:;"PUT"]
/d:("archived")!("v"$"false")
/say cmd,("  "json d)
/say sub["TMP";qq# "wow": true,\n "awe": "some",#] jtmp
/say "Fetching workflows...";wfs:sc.get"workflows"

detach-eng-workflow.goal🔗

/ To detach the deprecated Engineering workflow, this script:
/  - Identifies stories assigned to teams in the Engineering workflow
/  - Labels those stories with `team-<mention-name-of-team>`
/  - Removes the team association
/
/ At that point, the Engineering workflow can be unassigned in
/ Shortcut settings.
/ gr.diamond gr.teamgold
""import"sc-client/sc"
/ Search
wf:cache.workflows[1488]
wfsts:(..id)'wf"states"
dsss:{story.search[..["group_id":x;"workflow_state_id":y]]}[gr.diamond"id"]'wfsts
gsss:{story.search[..["group_id":x;"workflow_state_id":y]]}[gr.teamgold"id"]'wfsts
/ Label
lblem:{[lblname;batch]n:#batch; say"Labeling batch of $n stories..."; story.update.bulk[..["story_ids":"i"$(..id)'batch;"labels_add":,..[name:lblname]]]}
lblem["team-diamond"]'(-500)$,/dsss
lblem["team-gold"]'(-500)$,/gsss
/ Remove team ownership
disownem:{[batch]n:#batch; say"Removing group/team from batch of $n stories..."; story.update.bulk[..["story_ids":"i"$(..id)'batch;"group_id":0n]]}
disownem'(-500)$,/dsss
disownem'(-500)$,/gsss

dos-story-arrival.goal🔗

""import"sc-client/sc"
doswf:(.cache.workflows)[2]
triage:doswf["states";0]
triage"name";"i"$triage"id" / 500179017
tss:story.search[..[archived:-0w;"workflow_state_id":"i"$triage"id"]]
s2:283816
h2:story.history[s2]
wchanges:{[story]
acts:,/(..actions)'story.history[story]
actions:(..action)'acts
entypes:(..x["entity_type"])'acts
wfstids:(..x["workflow_state_id"])'acts
/(!"a e w")!(actions;entypes;wfstids)
(actions;entypes;wfstids)
}

/ get value at k from d, if present, return fallback fb
getin:{.[{(x).(y)}[x];y;{y}[;z]]}
/Debug
:acts:,/(..actions)'h2
{say" "json x}acts
{say" "json x}'getin[;!"changes workflow_state_id";"none"]'acts
{say" "json x}'getin[;"changes";"none"]'acts
{say" "json x}'getin[;"workflow_state_id";()]'getin[;"changes";"none"]'acts
:changes:getin[;"workflow_state_id";()]'getin[;"changes";"none"]'acts
changes[&~{0=#x}'changes]


chwf:{changes:..x["changes"]; "d"~@changes}
@..[a:2]
chwf'acts
.[chwf;*acts;]
{.[(..x["changes";"workflow_state_id"]);z;:]}'acts
help rx/try/
help rx/error/
..changes
actions:(..action)'acts
entypes:(..x["entity_type"])'acts
wfstids:(..x["workflow_state_id"])'acts
fmt.pp t:(!"a e w")!(actions;entypes;wfstids)
fmt.pp (1;..a="create";..e="story")#t
fmt.pp (1;..a="update";..e="story")#t
{say" "json x}'acts
fmt.pp +thss
/ CSV of Epic Comments
(~2=#ARGS)and:error"USAGE: goal epic-comments.goal <epic-id>";(p;epicid):ARGS / epicid:170742
url:"https://api.app.shortcut.com/api/v3/";'st:env "SHORTCUT_API_TOKEN"
gcmd:!"curl -s --fail-with-body -X GET -H 'Content-Type:application/json' -H 'Shortcut-Token:${st}' -L '${url}"
scget:{[p]'ret:shell" "/@[gcmd;-1+#gcmd;+;p+"'"];?["e"~@ret;:ret;json ret]}
pid:"parent_id";kcs:"comments";cks:!"id author_id text ${pid}";ixpid:cks?pid
ac:{[cm;pcid]vs:cm[cks];vs[ixpid]:pcid;acc::cks!(.acc),'vs;o[;cm"id"]'(cm[kcs]);acc}
acs:{[epicid]cs:scget"epics/${epicid}/${kcs}";acc::cks!(#cks)#(());ac[;0n]'cs;acc}
t:acs[epicid];sid:"%0.f"$;t:@[t;!"id";sid];t:@[t;,pid;{{?[nan x;"";sid x]}'x}]
now:time "unixmilli";fn:"${kcs}-epic-${epicid}-ts-${now}.csv";say "Writing to ${fn}"
csvdata:","csv t;'csvfile:"w"open fn;csvfile print csvdata;close csvfile;say "ok"
/ CSV of Epic Comments
(~2=#ARGS)and:error"USAGE: goal epic-comments.goal <epic-id>";(p;epicid):ARGS / epicid:170742
url:"https://api.app.shortcut.com/api/v3/";'st:env "SHORTCUT_API_TOKEN"
gcmd:!"curl -s --fail-with-body -X GET -H 'Content-Type:application/json' -H 'Shortcut-Token:${st}' -L '${url}"
scget:{[p]'ret:shell" "/@[gcmd;-1+#gcmd;+;p+"'"];?["e"~@ret;:ret;json ret]}
pid:"parent_id";kcs:"comments";cks:!"id author_id text ${pid}";ixpid:cks?pid
/ Helper fn for acs; expects acc to be initialized
ac:{[cm;pcid]
 vs:cm[cks];vs[ixpid]:pcid / get values, set parent comment id
 acc::cks!(.acc),'vs       / append values as row
 o[;cm"id"]'(cm[kcs])      / recur: mutate acc for all nested comments
 acc}
/ All comments for epicid
acs:{[epicid]
 acc::cks!(#cks)#(())             / initialize acc table
 cs:scget"epics/${epicid}/${kcs}" / fetch Shortcut's "List Epic Comments"
 ac[;0n]'cs                       / process comments; top-level have parent_id of 0n
 acc}
/ Helper fn, format float as int string
sid:"%0.f"$
/ Flat table of nested comment data
t:acs[epicid]
t:@[t;!"id";sid]                    / string ids
t:@[t;,pid;{{?[nan x;"";sid x]}'x}] / string ids, empty string for 0n
/ Write CSV as file with epic id and timestamp in file name
now:time "unixmilli";fn:"${kcs}-epic-${epicid}-ts-${now}.csv";say "Writing to ${fn}"
csvdata:","csv t;'csvfile:"w"open fn;csvfile print csvdata;close csvfile;say "ok"

epics-on-roadmap.goal🔗

""import"sc-client/sc"
eps:epic""
t:json.tbl eps
where.match:{[tbl;col;val] (1;val~'(..x[p.col])tbl)#tbl}
where.in:{[tbl;col;val] (1;\¿[;val]'(..x[p.col])tbl)#tbl}

where.gr:where.match[t;"group_id"]
deps:where.gr[gr.diamond"id"]
/ All but Backlog; see bottom of file
/ states:500167369 500159124 500155207 500155209 500105052 500155208 500042484 500155210 500130130 500155211 500155212 500159479 500165183 500042485 500105047
states:500042484 500155211 500155212 500159479 500165183
dactive:where.in[deps;"epic_state_id";states]
fmt.pp dactive
+dactive[!"name app_url"]
/
(500159124 "👍 Prioritized"
 500155207 "📄 Planning"
 500155209 "✏️ In Design"
 500105052 "🚦 Ready For Dev"
 500155208 "🎟️ To Refine"
 500042484 "〜 In Development"
 500155210 "⚠️ Blocked"
 500130130 "⏸️ On Hold"
 500155211 "🏁 Internal Release"
 500155212 "% Release"
 500159479 "📊 Learning"
 500165183 "🚀 GA Release"
 500042485 "Done"
 500105047 "🚫 Canceled")
\

groups.goal🔗

/ # Fetch
server:- 'shell rq`aws-vault exec developer -- peer-server production | grep 'production analytics-server' | cut -d' ' -f3`
namewss:"active-workspaces"
namegroups:"groups-without-perms"
nameperms:"perms-in-groups"
nameepicswithgroups:"epics-with-groups"
presto:{"presto --server $server --catalog analytics --schema production_build -f ${x}.sql --output-format CSV_HEADER_UNQUOTED > ${x}.csv"}
say "Running query $namewss ..."; 'shell presto[namewss]
say "Running query $namegroups ..."; 'shell presto[namegroups]
say "Running query $nameperms ..."; 'shell presto[nameperms]
say "Running query $nameepicswithgroups ..."; 'shell presto[nameepicswithgroups]
say "Running query $nameepicswithoutgroups ..."; 'shell presto[nameepicswithoutgroups]
/ # Local DB
pp:fmt.tbl[;20;8;"%.1f"]; cleanplan:sub[":product.tier/" ""]
/ ## Teams/Groups
empgrp:{(*'x)!1_'x}@csv 'read"${namegroups}.csv"
empgrp:@[empgrp;!"plan";cleanplan]; empgrp:@[empgrp;!"nb np";"i"$]
fllgrp:{(*'x)!1_'x}@csv 'read"${nameperms}.csv" / row per permission
fllgrp:@[fllgrp;!"plan";cleanplan]; fllgrp:@[fllgrp;!"nb";"i"$]
freqs:k.freq[fllgrp"grp"]
idx::0; prog:{and[~1000!idx; print"$idx "]} / progress
catnp:{[grp] prog"";idx+:1; d:*'(1;..grp=p.grp)#fllgrp; d:(,"perm")^d; (.d),freqs[grp]}
nps:catnp'!freqs
allgrp:empgrp / start with empgrp, which has correct shape
allgrp:allgrp,'+nps / add non-empty groups
wss:{(*'x)!1_'x}@csv 'read"${namewss}.csv" / row per workspace
wss:@[wss;!"plan";cleanplan]; wss:@[wss;!"nb";"i"$]
ew:{(*'x)!1_'x}@csv 'read"${nameepicswithgroups}.csv"
ew:@[ew;!"plan";cleanplan]; ew:@[ew;!"nb";"i"$]
ewstarted:(1;..estate="in progress")#ew
ewfreqs:k.freq[ewstarted"ws"] / count of epics in each workspace, which is the rows in wss
wss0teamepicsks:(!ewfreqs)^wss"ws"
wss0teamepicsvs:(#wss0teamepicsks)#0
wss0teamepics:wss0teamepicsks!wss0teamepicsvs
w:wss0teamepics,ewfreqs
(!^w)~((wss@<wss..ws)"ws") / demonstrate ordering of ws ids
wss:(wss@<wss..ws),(,"epicswithteams")!,(.^w)
"_groups.csv"print ","csv allgrp
shell rq`duckdb_cli -c "DROP TABLE IF EXISTS grps;" groups.duckdb`
shell rq`duckdb_cli -c "CREATE TABLE grps AS SELECT * FROM '_groups.csv';" groups.duckdb`
"_wss.csv"print ","csv wss
shell rq`duckdb_cli -c "DROP TABLE IF EXISTS wss;" groups.duckdb`
shell rq`duckdb_cli -c "CREATE TABLE wss AS SELECT * FROM '_wss.csv';" groups.duckdb`

hex.goal🔗

numerals:,/!10,"c"$'97+!6; hex:{sub[" ";"0"]2!""/"s"$'numerals@16\x}; rtoh:{[rgb] "#"+""/hex'rgb}

ics.goal🔗

files:glob"*.ics"
f:"[email protected]"; s: 'read f
ls:=s
begs:&"BEGIN:VEVENT"=ls
ends:&"END:VEVENT"=ls
evts:ls[begs+!'1+ends-begs]

e:*evts
say'e
say'pevt e
pevt:{[ls]
 ctd:&(.." "~x[0;1])'ls
 dbg::ls[ctd]
 dbg.ctd::ctd
 \joined:(ctd-1)(..p.ls[x]+1_'p.ls[y])'ctd
 dbg.joined::joined
 / TODO START HERE vvv
 rem:ctd; is:!#ls
 ls@:&(#rem)=rem?is; ls[ctd-1]:joined}
pevt e
dbg
ctg:(«dbg.ctd)-dbg.ctd 
ctg[-1+#ctg]:-/dbg.ctd[-1 -2]
ctg

ctg
help rx/rot/
dbg.joined
1 rotate dbg.ctd
-1 rotate dbg.ctd
say'e
a:7 9 11 14 15 16 17 18 8 10 12 15 16 17 18 19
b:0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
{a}^b
help"v"
b@&((#a)=a?b)

a:8 10 12 15 16 17 18 19
(«a)-a
a-»a
help rx/shift/
-/a[-1 -2]
a:8 10 12 15 16 17 18 19
a
a-0i»a
{y-x}/2^a
-\1 -1_`a
2-/':a

ideas-channel.goal🔗

/ #server-errors channel; messages consist of attachments, first is workspace, rest are per log :tag
url:"https://slack.com/api/"; aj:"application/json"; tk: 'env"SLACK_USER_TOKEN"; hd:..[Accept:aj;"Content-Type":aj;"Authorization":"Bearer $tk"]
hc:http.client[..[Header:hd]]; hp:{[f]{[httpf;path]r: 'httpf[hc;url+path]; 'json r"bodystring"}[f]}
hpp:{[f]{[httpf;path;reqopts]r: 'httpf[hc;url+path;reqopts]; 'json r"bodystring"}[f]}
get:hp[http.get]; getq:hpp[http.get]; post:hpp[http.post]; jp:{say " "json x}
dt:{time.unix["i"$_"n"$x]}; int:"i"$_"n"$

/ convos:get"conversations.list"
/ chans:convos"channels"; ^(..name,id)'chans
/ channel:"C057UQVFA3S" / #ideas
channel:"C08HKS47HCH" / # csv-import-leads
link:{[ts]getq["chat.getPermalink";..[QueryParam:..[channel;"message_ts":ts]]]}

Day:time.Hour * 24; unow:time.utc time.now@0
:oldest:time.unix[time.add[unow;-3000 * Day]]
:latest:time.unix[unow]
msgs:{[acc;channel;oldest;latest] \latest / print progress
  hist:post["conversations.history";..[Body:""json..[channel;oldest;latest]]]
  ms:hist"messages"; newlatest:(..ts)@*|ms; acc,:ms
  ?[hist"has_more"
    o[acc;channel;oldest;newlatest]
    acc]}
allmsgs:msgs[();channel;oldest;latest]; "ok"
time.unix'"i"$"v"$(..ts)'allmsgs

mailchimp.goal🔗

libs import"math";libs import "fmt"
dc:"us1";tok: 'shell"cred mailchimp personal token";bu:"https://${dc}.api.mailchimp.com/3.0"
cl:http.client[..[Header:..[Authorization:"Bearer $tok"]]]
/ http.get[cl;bu+"/ping"]
campaigns:http.get[cl;bu+"/campaigns";..[QueryParam:..["count":"1000";"sort_field":"send_time";"sort_dir":"DESC"]]]
body:json campaigns"body"
cs:body"campaigns"
say'{sl:x["settings";"subject_line"];cnt:x["emails_sent"];sl,cnt}'cs
+/{rx/^\[Re-sent\]/x}'{x["settings";"title"]}'cs / 2 re-sent
/ Remove tiny sends (e.g., re-sends to newly-subscribed folks).
cs:cs[&5<(..x["emails_sent"])'cs]
math.avg@{rs:x["report_summary"];?[rs;rs["unique_opens"];0]}'cs / 102
math.med@{rs:x["report_summary"];?[rs;rs["unique_opens"];0]}'cs / 92
math.avg@{rs:x["report_summary"];?[rs;rs["subscriber_clicks"];0]}'cs / 23
math.med@{rs:x["report_summary"];?[rs;rs["subscriber_clicks"];0]}'cs / 25
math.avg@(..x["emails_sent"])'cs / 224
math.med@(..x["emails_sent"])'cs / 240

ks:!"long_archive_url emails_sent send_time"
subjectLine:{x["settings";"subject_line"]}'cs
(url;numberRecipients;sentAt):{[cs;k](..x@p.k)'cs}[cs]'ks
d:..[subjectLine;url;numberRecipients;sentAt]
"/Users/daniel/tmp/mailchimp_campaigns.csv"print csv d
shell "open " + "/Users/daniel/tmp/mailchimp_campaigns.csv"

members-without-teams.goal🔗

/import"sc-client/scapi"
mbs:member.list""
gps:group.list""
(#mbs;#gps)
ac"cache.*"
agps:gps[&-0w=(..archived)'gps]
ambs:mbs[&-0w=(..disabled)'mbs]
rp:,/'{(x["profile";!"email_address mention_name name"];#x["group_ids"])}'ambs
t:(!"email mentionname name numteams")!+rp
f:"user-teams.csv"
f print ","csv (..x@<numteams)(..x@<email)t

prs-without-stories.goal🔗

/ PRs without Shortcut Stories
ghurl:"https://api.github.com/"; ghtok: 'env"GITHUB_API_TOKEN"
year:2025;qtrs:("$year-01-01..$year-03-31";"$year-04-01..$year-06-30";"$year-07-01..$year-09-30";"$year-10-01..$year-12-31")
prsearch:{[repo;mergerange;page]
 q:"%20"/!"repo:useshortcut/$repo is:pull-request is:merged merged:$mergerange"
 "per_page=100&page=$page&q=$q"}
bot:"shortcut-integration[bot]"
nostory:{[fname]cs: 'json 'read fname;users:(..user..login)'cs;?[0=#cs;1;0=|/bot=users]}
clean:{f:sub["https://api.";"https://"];g:sub["github.com/repos/";"github.com/"];g f x}
curl:{[url]qq#curl -sS -H "Authorization: Bearer $ghtok" -H "Accept: application/vnd.github+json" "$url"#}
query:{[repo;qtr;page]qp:prsearch[repo;qtrs[qtr];page]
 url:"https://api.github.com/search/issues?$qp"
 finalurl:curl[url]
 json shell curl[url]}
commentfiles:-1_"\n"\shell "ls *-comments.json"
nostoryprs:commentfiles[&nostory'commentfiles]
prid:{[fname]m:rx/pr-(\d+)-comments.json/fname;m[1]}
nostoryprids:prid'nostoryprs
openpr:{[fname]m:rx/pr-(\d+)-comments.json/fname;id:m[1];j:json read "pr-${id}.json";url:clean j"url"; shell "open $url"}
/ openpr'sample
pr:{[prid]json read "pr-${prid}.json"}
prs:pr'nostoryprids
/ Only those in the nostoryprids
ghpeeps:..[] / REDACTED
dos:cache.workflows[(!cache.workflows)@2]
doscompleted:500179018
lbl:..[name:"audit-2025"]
storyforpr:{[prid]fname:"pr-${prid}.json";j:json read fname;(name;description;completedat;prurl):j@!"title body closed_at url";description:?[nan description;"";description];owner:ghpeeps[j["user";"login"]];..[name;description;"workflow_state_id":doscompleted;"owner_ids":,owner"id";"completed_at_override":completedat;labels:,lbl;"external_links":,prurl]}
ss:storyforpr'nostoryprids
batches:-100$ss
#'batches
curlp:{[url;body]qq#curl -L -XPOST -H "Authorization: Bearer $ghtok" -H "Accept: application/vnd.github+json" -d'{"body":"$body"}' "$url"#}
linktogh:{prurl:x["external_links";0];csurl:"$prurl/comments";storyid:"i"$x["id"];cstext:"[sc-$storyid]"; curlp[csurl;cstext]}
sss:story.search[..["label_name":"audit-2025";archived:-0w]]
fullsss:story.get'sss
/ Korey
addkorey:{[st]ids:(st"owner_ids"),(mb.korey"id");story.update[st"id";..["owner_ids":ids]]}
addkorey'sss
addkorey story.get[294843]
story.update.bulk[..["story_ids":"i"$(..id)'sss;"owner_ids_add":,(mb.korey)"id"]]
koreyproceed:{[st]cs:story.comments[st"id"];koreycs:(mb.korey"id")=@[;"author_id"]'cs;rootcs:nan'@[;"parent_id"]'cs;c:*cs[&koreycs&rootcs];pid:c"id";story.comments.create[story.get[st"id"];..[text:"Proceed.";"parent_id":pid]]}
haskoreycomment:{[st]cs:st"comments";koreycs:(mb.korey"id")=@[;"author_id"]'cs;(..x["created_at"])'cs[&koreycs]}
#'haskoreycomment'fullsss
haskoreycomment'fullsss
koreyproceed[story.get[294843]]
/ resp21@!"total_count incomplete_results"
/ items:,/@[;"items"]'(resp01;resp11;resp21)
/ {[i]fname:"pr-"+($"i"$i"id")+".json";fname print ""json i;}'items
/ {[i]id:$"i"$i"id";\id;fname:"pr-$id-comments.json";cs:json shell curl[i"comments_url"];fname print ""json cs;}'items
/ [sc-new-story] as GH PR comment
/ curlp:{[url]qq#curl -L -XPOST -H "Authorization: Bearer $ghtok" -H "Accept: application/vnd.github+json" -d'{"body":"[sc-new-story]"}' "$url"#}
/ shell curlp (json read"pr-2793685003.json")"comments_url"
/ {url:clean x"url";shell "open $url"}'sample / Open stories in browser
/ TODO Delete archived audit-2025 stories
/ {shell linktogh[x]}'1_b0
/ sample:-10?b0
/ shell'"open " + (..x["app_url"])'sample
/ [DONE] b0:story.create.bulk[..["stories":batches[0]]]
/ [DONE] b1:story.create.bulk[..["stories":batches[1]]]
/ [DONE] b2:story.create.bulk[..["stories":batches[2]]]
/ [DONE] b3:story.create.bulk[..["stories":batches[3]]]
/ {shell linktogh[x]}'b3
/ story.update.bulk[..["story_ids":"i"$(..id)'b0;"archived":0w]]
/ fullstories:story.get'sss
/ {shell "open "+x["app_url"]}@*fullstories[&~#'(..x["pull_requests"])'fullstories]
/ story.get[(*sss)]["pull_requests"]
1

sc.goal🔗

/ Shortcut API
prompt:"sc> "; ari.prompt:prompt
hoste:env "SHORTCUT_API_HOST";host:?["e"~@hoste;"api.app.shortcut.com";hoste]
url:"https://$host/api/v3/"; tok: 'env "SHORTCUT_API_TOKEN"; sc.debug:0
aj:"application/json"; hd:..["Shortcut-Token":tok;Accept:aj;"Content-Type":aj]; hc:http.client[..[Header:hd]]
hp:{[f]{[httpf;path]hc:?[sc.debug;..[Header:hd;Debug:1];hc]; r: 'httpf[hc;url+path]; 'json r"bodystring"}[f]}
hpp:{[f]{[httpf;path;reqopts]hc:?[sc.debug;..[Header:hd;Debug:1];hc]; r: 'httpf[hc;url+path;reqopts]; 'json r"bodystring"}[f]}
get:hp[http.get]; post:hpp[http.post]; put:hpp[http.put]; browse:{[ent]u:ent"app_url"; shell "open $u"}
sg:{or[..["stories":"story"]x;-1_x]}; sgmg:{[s]munge@sg s}; infi:1=sign; / singular, munge + singular, infinity-to-int
/ Strings
etk:"entity_type"; sc.ents:!"custom-fields epic-workflows epics groups iterations key-results labels members milestones objectives stories workflows"
stsfields:!"archived completed_at_end completed_at_start created_at_end created_at_start deadline_end deadline_start epic_id epic_ids estimate external_id group_id group_ids includes_description iteration_id iteration_ids label_ids label_name owner_id owner_ids project_id project_ids requested_by_id story_type updated_at_end updated_at_start workflow_state_id workflow_state_types"
idfs:(!"d s n")!({id:x"id";?["s"=@id;id;$"i"$id]};{x};{$"i"$x}); id:{tp:@x; ?[|/tp=!idfs;idfs[tp]x;$x]} / fmt ids
et:{x[etk]}
entn:{n:x"name"; ?[n="";x["profile";"name"];n]}
typeid:{et:x[etk]; type:munge[et]; i:id[x]; (type;i)}
epr:{(type;i):typeid x; :qq/$type["$i"]/}
/ TODO Brittle.
/ entprstr:{(type;id):typeid x; n:entn x; "$name[$i] / $n"}
/ ari.print:{and[ist x;:fmt.tbl[x;3;3;"%.1f"]]; and["d"=@x;:(?[|/etk=!x;(et:x[etk];say entprstr[x]);say x])]; and["e"=@x;:(say x; say"")]; say $x}
cd:{ari.c::x; ?[x;ari.prompt::epr[x]+"> ";ari.prompt::prompt]; 1}; jj:say" "json
san:{