loopable-cli 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.output/nitro.json +1 -1
- package/.output/public/assets/_connectorId-CDCuSgpN.js +1 -0
- package/.output/public/assets/_loopId-DOnIsVTW.js +1 -0
- package/.output/public/assets/_taskId-BO5MZnlZ.js +1 -0
- package/.output/public/assets/agents-CBozi7oi.js +1 -0
- package/.output/public/assets/alert-Bn_wjgTX.js +1 -0
- package/.output/public/assets/button-K3ypIhAZ.js +1 -0
- package/.output/public/assets/card-DiVnaTIE.js +1 -0
- package/.output/public/assets/chevron-right-BrctLtFK.js +1 -0
- package/.output/public/assets/connection-status-DKWelvLO.js +1 -0
- package/.output/public/assets/connector-icon-CEbdE71G.js +1 -0
- package/.output/public/assets/connectors-JyX3IzJj.js +1 -0
- package/.output/public/assets/gaps-PjmcbaWo.js +1 -0
- package/.output/public/assets/inbox-CujRR0Cz.js +1 -0
- package/.output/public/assets/index-C9SQ_Baq.js +16 -0
- package/.output/public/assets/loop-form-Dw3cuTzw.js +1 -0
- package/.output/public/assets/loops-DcEfZa2_.js +1 -0
- package/.output/public/assets/manifests-yJpevyye.js +1 -0
- package/.output/public/assets/new-CwsnNuRW.js +1 -0
- package/.output/public/assets/page-bvnfRMum.js +1 -0
- package/.output/public/assets/play-yHLs4DjK.js +1 -0
- package/.output/public/assets/plus-Ber25lrU.js +1 -0
- package/.output/public/assets/preload-helper-BoU_7EQC.js +3 -0
- package/.output/public/assets/{refresh-cw-C3xBnKfy.js → refresh-cw-BzSY7M8I.js} +1 -1
- package/.output/public/assets/rotate-ccw-DLv9EfCX.js +1 -0
- package/.output/public/assets/runners-DzX-RU3i.js +2 -0
- package/.output/public/assets/select-C9grIFwG.js +1 -0
- package/.output/public/assets/setting-fields-DUc3npgh.js +3 -0
- package/.output/public/assets/styles-B9B98zLk.css +2 -0
- package/.output/public/assets/switch-DwABVI6d.js +1 -0
- package/.output/public/assets/task-list-Ddk8-qt7.js +1 -0
- package/.output/public/assets/textarea-Ds-gIYUQ.js +1 -0
- package/.output/public/assets/time-goBWd-5J.js +1 -0
- package/.output/public/assets/use-live-tasks-DZNNuECy.js +1 -0
- package/.output/public/assets/useCompositeListItem-P4Nmmn7j.js +1 -0
- package/.output/public/assets/usePositioner-B7i9Ejzh.js +1 -0
- package/.output/server/_chunks/ssr-renderer.mjs +1 -1
- package/.output/server/{_connectorId-C9yydJrC.mjs → _connectorId-COuQN02a.mjs} +198 -188
- package/.output/server/_libs/@base-ui/react+[...].mjs +1410 -804
- package/.output/server/_libs/@tanstack/react-router+[...].mjs +1 -1
- package/.output/server/_libs/lucide-react.mjs +93 -84
- package/.output/server/_libs/next-themes.mjs +1 -1
- package/.output/server/_libs/sonner.mjs +1 -1
- package/.output/server/_loopId-CZh6_Yhr.mjs +310 -0
- package/.output/server/_ssr/{agents-DG643yoJ.mjs → agents-B_qo1gpL.mjs} +195 -6
- package/.output/server/_ssr/{agents-C7B6RJ6-.mjs → agents-Bz10gz_m.mjs} +8 -8
- package/.output/server/_ssr/agents-CjbOUp3Z.mjs +206 -0
- package/.output/server/_ssr/{alert-DQvzE7bu.mjs → alert-C2WBWfzo.mjs} +2 -2
- package/.output/server/_ssr/card-r1cV-PCN.mjs +36 -0
- package/.output/server/_ssr/{client-BtVRVhQ2.mjs → client-Dk3SD8Il.mjs} +5 -1
- package/.output/server/_ssr/connection-status-BogAL-Fj.mjs +32 -0
- package/.output/server/_ssr/{connections-Df-aDpfJ.mjs → connections-Cqvb6Gw2.mjs} +372 -325
- package/.output/server/_ssr/connector-icon-DrhVbhs6.mjs +190 -0
- package/.output/server/_ssr/connectors-BfS5tQKr.mjs +81 -0
- package/.output/server/_ssr/{connectors-e0mf2pMs.mjs → connectors-DJI-rzPU.mjs} +10 -10
- package/.output/server/_ssr/{createServerRpc-DPDrnFdH.mjs → createServerRpc-DDrp2nLT.mjs} +2 -2
- package/.output/server/_ssr/{gaps-Cz76ZicM.mjs → gaps-CcvHiLcj.mjs} +15 -7
- package/.output/server/_ssr/inbox-D1ef761v.mjs +178 -0
- package/.output/server/_ssr/loop-form-DWwOR05R.mjs +346 -0
- package/.output/server/_ssr/{loops-D8WuK7Ke.mjs → loops-6WhUe_lc.mjs} +37 -27
- package/.output/server/_ssr/loops-DHPjzG-t.mjs +217 -0
- package/.output/server/_ssr/new-ChKBdcXJ.mjs +245 -0
- package/.output/server/_ssr/page-DZVxKvdn.mjs +120 -0
- package/.output/server/_ssr/{router-54IDWTVz.mjs → router-DdwREiOf.mjs} +162 -489
- package/.output/server/_ssr/runners-CK9Cx9fS.mjs +217 -0
- package/.output/server/_ssr/runners-CrznFPHt.mjs +140 -0
- package/.output/server/_ssr/{runners-Cj6z9Mqh.mjs → runners-W-pgo5yo.mjs} +5 -5
- package/.output/server/_ssr/{secrets-CRnpwGBh.mjs → secrets-CV5scFlt.mjs} +2 -2
- package/.output/server/_ssr/select-BtndHhHb.mjs +76 -0
- package/.output/server/_ssr/{setting-fields-DzBlslan.mjs → setting-fields-LNgywAW7.mjs} +25 -20
- package/.output/server/_ssr/ssr.mjs +43 -42
- package/.output/server/_ssr/{start-Vq3Oan3v.mjs → start-C_j-e4vF.mjs} +9 -4
- package/.output/server/_ssr/{switch-1pnd_bPJ.mjs → switch-odxNO_S4.mjs} +3 -3
- package/.output/server/_ssr/task-list-Dnmq_WeE.mjs +150 -0
- package/.output/server/_ssr/{tasks-Qtml5TlK.mjs → tasks-D-kXLgVP.mjs} +13 -13
- package/.output/server/_ssr/{tasks-BDqznm57.mjs → tasks-D7aUvA33.mjs} +44 -17
- package/.output/server/_ssr/{textarea-CHJTKtsJ.mjs → textarea-D2c_vTIC.mjs} +2 -2
- package/.output/server/_ssr/time-Cy8TZP0Z.mjs +13 -0
- package/.output/server/_ssr/use-live-tasks-BzhTkEOL.mjs +40 -0
- package/.output/server/_tanstack-start-manifest_v-2HUdn-2E.mjs +166 -0
- package/.output/server/_taskId-D3KkNJW1.mjs +263 -0
- package/.output/server/index.mjs +199 -164
- package/README.md +36 -6
- package/dist/cli.mjs +161 -8
- package/dist/dispatcher.mjs +671 -285
- package/dist/runner.mjs +333 -46
- package/drizzle/0025_prompt_tasks_done.sql +2 -0
- package/drizzle/0026_guidance_into_prompt.sql +3 -0
- package/drizzle/0027_relative_folders.sql +5 -0
- package/drizzle/0028_runner_version.sql +5 -0
- package/drizzle/meta/_journal.json +29 -1
- package/package.json +4 -2
- package/.output/public/assets/_connectorId-CkygNLny.js +0 -1
- package/.output/public/assets/_loopId-Cshlc5Ja.js +0 -1
- package/.output/public/assets/_taskId-BKDIJ_Fg.js +0 -1
- package/.output/public/assets/alert-BT2EVMJP.js +0 -1
- package/.output/public/assets/arrow-left-CWUZt6k2.js +0 -1
- package/.output/public/assets/arrow-right-dy_oOg_c.js +0 -1
- package/.output/public/assets/connection-status-DCqqmY_6.js +0 -1
- package/.output/public/assets/connector-icon-BUoK6Cru.js +0 -1
- package/.output/public/assets/connectors-CeLBtEtg.js +0 -1
- package/.output/public/assets/external-link-DC11orFA.js +0 -1
- package/.output/public/assets/gaps-BtFoD7mF.js +0 -1
- package/.output/public/assets/inbox-BYNjkNDJ.js +0 -1
- package/.output/public/assets/index-0yP3Jn_G.js +0 -18
- package/.output/public/assets/loader-B4Epchm_.js +0 -1
- package/.output/public/assets/loop-form-DnSJNc1k.js +0 -1
- package/.output/public/assets/loops-1CvbqO6m.js +0 -1
- package/.output/public/assets/new-m5NlqYQT.js +0 -1
- package/.output/public/assets/play-DD4xQgvi.js +0 -1
- package/.output/public/assets/preload-helper-CfdAYKD7.js +0 -3
- package/.output/public/assets/rolldown-runtime-CbXtAM7H.js +0 -1
- package/.output/public/assets/runners-D8QVl-Y6.js +0 -1
- package/.output/public/assets/select-Cppu-hti.js +0 -1
- package/.output/public/assets/setting-fields-bJ_gTG7Q.js +0 -3
- package/.output/public/assets/styles-ClTGaXU0.css +0 -2
- package/.output/public/assets/switch-hRIbNGOj.js +0 -1
- package/.output/public/assets/task-list-BAxjcVgr.js +0 -1
- package/.output/public/assets/textarea-Fyuzi6Vp.js +0 -1
- package/.output/public/assets/trash-2-BXZzBsoV.js +0 -1
- package/.output/public/assets/useValueChanged-VVyq4hb9.js +0 -1
- package/.output/server/_loopId-B0VG-eBC.mjs +0 -301
- package/.output/server/_ssr/connection-status-BZr3Y-IC.mjs +0 -31
- package/.output/server/_ssr/connector-icon-WvxwZ5TF.mjs +0 -56
- package/.output/server/_ssr/connectors-BU0IiRge.mjs +0 -107
- package/.output/server/_ssr/inbox-CAUBa1I3.mjs +0 -114
- package/.output/server/_ssr/loop-form-FqSaXiHl.mjs +0 -413
- package/.output/server/_ssr/loops-Dqoebe8q.mjs +0 -222
- package/.output/server/_ssr/new-bBsVaWD6.mjs +0 -169
- package/.output/server/_ssr/runners-D2UAdLyj.mjs +0 -106
- package/.output/server/_ssr/runners-DGWaVvWi.mjs +0 -116
- package/.output/server/_ssr/task-list-_qgBS9LU.mjs +0 -187
- package/.output/server/_tanstack-start-manifest_v-CvheE7ZF.mjs +0 -131
- package/.output/server/_taskId-V4QLBO2N.mjs +0 -256
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { d as and, f as eq, h as lt, l as asc } from "../_libs/drizzle-orm.mjs";
|
|
2
|
-
import { a as connections, i as authAttempts, s as db } from "./client-
|
|
3
|
-
import { i as readCredential, s as writeCredential, t as deleteCredential } from "./secrets-
|
|
2
|
+
import { a as connections, i as authAttempts, s as db } from "./client-Dk3SD8Il.mjs";
|
|
3
|
+
import { i as readCredential, s as writeCredential, t as deleteCredential } from "./secrets-CV5scFlt.mjs";
|
|
4
4
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
5
5
|
import { existsSync, readFileSync } from "node:fs";
|
|
6
6
|
import { join } from "node:path";
|
|
7
|
-
//#region node_modules/.nitro/vite/services/ssr/assets/connections-
|
|
7
|
+
//#region node_modules/.nitro/vite/services/ssr/assets/connections-Cqvb6Gw2.js
|
|
8
8
|
function defineManifest(manifest) {
|
|
9
9
|
return manifest;
|
|
10
10
|
}
|
|
@@ -16,7 +16,7 @@ var repositories = {
|
|
|
16
16
|
key: "repositories",
|
|
17
17
|
kind: "string_list",
|
|
18
18
|
label: "Repositories",
|
|
19
|
-
help: "owner/name, one per line.
|
|
19
|
+
help: "owner/name, one per line. acme/* works. Empty means all.",
|
|
20
20
|
placeholder: "acme/web"
|
|
21
21
|
};
|
|
22
22
|
var ignoreDrafts = {
|
|
@@ -29,7 +29,7 @@ var ignoreBots = {
|
|
|
29
29
|
key: "ignoreBots",
|
|
30
30
|
kind: "boolean",
|
|
31
31
|
label: "Skip pull requests opened by bots",
|
|
32
|
-
help: "
|
|
32
|
+
help: "Such as Dependabot.",
|
|
33
33
|
default: true
|
|
34
34
|
};
|
|
35
35
|
/**
|
|
@@ -69,7 +69,7 @@ var issueTarget = {
|
|
|
69
69
|
key: "issue",
|
|
70
70
|
kind: "text",
|
|
71
71
|
label: "Issue or pull request",
|
|
72
|
-
help: "
|
|
72
|
+
help: "Empty replies where it was asked. Or a GitHub issue URL.",
|
|
73
73
|
placeholder: "https://github.com/acme/web/issues/12"
|
|
74
74
|
};
|
|
75
75
|
/**
|
|
@@ -91,115 +91,80 @@ var REVIEW_PROMPT = [
|
|
|
91
91
|
"Where you cannot see enough of the code to judge something, say so instead of",
|
|
92
92
|
"guessing."
|
|
93
93
|
].join("\n");
|
|
94
|
-
var
|
|
95
|
-
"
|
|
94
|
+
var REPLY_PROMPT = [
|
|
95
|
+
"Answer this issue in a comment, the way a teammate who knows this codebase would.",
|
|
96
96
|
"",
|
|
97
|
-
"
|
|
98
|
-
"
|
|
99
|
-
"
|
|
97
|
+
"Work out what the issue is asking for. If it is a question, answer it from the",
|
|
98
|
+
"code rather than from memory. If it reports a bug, find the likely cause and",
|
|
99
|
+
"point at the files and lines involved. If it asks for a change, say how you",
|
|
100
|
+
"would make it, where, and what to watch out for.",
|
|
100
101
|
"",
|
|
101
|
-
"
|
|
102
|
-
|
|
103
|
-
var IMPLEMENT_PROMPT = [
|
|
104
|
-
"Implement this issue in the checkout you are in.",
|
|
105
|
-
"",
|
|
106
|
-
"Read enough of the surrounding code first that what you write looks like it",
|
|
107
|
-
"belongs: the same patterns, the same names, the same way of handling errors.",
|
|
108
|
-
"Where the project has tests for work like this, add one.",
|
|
109
|
-
"",
|
|
110
|
-
"Stay inside what the issue asks for. A change that also tidies three other",
|
|
111
|
-
"things is a change nobody can review. If the issue is too vague to implement,",
|
|
112
|
-
"or doing it properly needs a decision that is not yours to make, reply with",
|
|
113
|
-
"exactly NOTHING_TO_DO rather than guessing: an unwanted pull request costs",
|
|
114
|
-
"more attention than a missing one.",
|
|
115
|
-
"",
|
|
116
|
-
"Run the project's tests if you can work out how, and say in your description",
|
|
117
|
-
"whether you did and what happened."
|
|
102
|
+
"Read the code; do not change it. If the issue is too vague to answer, say what",
|
|
103
|
+
"is missing rather than inventing it."
|
|
118
104
|
].join("\n");
|
|
119
105
|
var githubManifest = defineManifest({
|
|
120
106
|
id: "github",
|
|
121
107
|
name: "GitHub",
|
|
122
|
-
tagline: "Pick up
|
|
108
|
+
tagline: "Pick up reviews and issues sent to your team's shared GitHub account.",
|
|
123
109
|
docsUrl: "https://docs.github.com/rest",
|
|
124
110
|
icon: "Github",
|
|
125
111
|
accent: "bg-neutral-900 text-white",
|
|
126
112
|
auth: {
|
|
127
113
|
kind: "oauth_redirect",
|
|
128
114
|
scopes: GITHUB_SCOPES,
|
|
129
|
-
needsAppRegistration: true
|
|
115
|
+
needsAppRegistration: true,
|
|
116
|
+
note: "Connect the account the team will send work to. An account made for this is cleanest, but your own works too if you are happy to share it. Reviews and comments appear under its name, and it sees only the repositories it can access. Sign in to GitHub as that account first; GitHub authorizes whichever account the browser is signed in as."
|
|
130
117
|
},
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
{
|
|
182
|
-
id: "github.submit_review",
|
|
183
|
-
name: "Submit a review",
|
|
184
|
-
summary: "Post a review on a pull request, as a comment rather than an approval.",
|
|
185
|
-
target: [],
|
|
186
|
-
accepts: ["text", "review"]
|
|
187
|
-
},
|
|
188
|
-
{
|
|
189
|
-
id: "github.post_issue_comment",
|
|
190
|
-
name: "Comment on an issue",
|
|
191
|
-
summary: "Post a comment on an issue or pull request.",
|
|
192
|
-
target: [issueTarget],
|
|
193
|
-
accepts: ["text"]
|
|
194
|
-
},
|
|
195
|
-
{
|
|
196
|
-
id: "github.open_pull_request",
|
|
197
|
-
name: "Open a draft pull request",
|
|
198
|
-
summary: "Open a draft pull request for the branch the agent already pushed.",
|
|
199
|
-
target: [],
|
|
200
|
-
accepts: ["code"]
|
|
201
|
-
}
|
|
202
|
-
],
|
|
118
|
+
triggers: [{
|
|
119
|
+
id: "github.review_requested",
|
|
120
|
+
when: "A GitHub review is requested",
|
|
121
|
+
trigger: "someone requests a review from the connected account, directly or through its team",
|
|
122
|
+
watches: "is:open is:pr review-requested:@me",
|
|
123
|
+
settings: [
|
|
124
|
+
repositories,
|
|
125
|
+
ignoreDrafts,
|
|
126
|
+
ignoreBots,
|
|
127
|
+
sizeLimit
|
|
128
|
+
],
|
|
129
|
+
runsIn: "checkout",
|
|
130
|
+
answer: "review",
|
|
131
|
+
actionId: "github.submit_review"
|
|
132
|
+
}, {
|
|
133
|
+
id: "github.issue_assigned",
|
|
134
|
+
when: "A GitHub issue is assigned",
|
|
135
|
+
trigger: "an issue is assigned to the connected account",
|
|
136
|
+
watches: "is:open is:issue assignee:@me",
|
|
137
|
+
settings: [repositories],
|
|
138
|
+
runsIn: "checkout",
|
|
139
|
+
answer: "text",
|
|
140
|
+
actionId: "github.post_issue_comment"
|
|
141
|
+
}],
|
|
142
|
+
workflows: [{
|
|
143
|
+
id: "github.review_requested",
|
|
144
|
+
name: "Review pull requests",
|
|
145
|
+
summary: "Posts a review when someone requests one from the connected account.",
|
|
146
|
+
triggerId: "github.review_requested",
|
|
147
|
+
prompt: REVIEW_PROMPT
|
|
148
|
+
}, {
|
|
149
|
+
id: "github.issue_assigned",
|
|
150
|
+
name: "Reply to issues",
|
|
151
|
+
summary: "Answers an issue in a comment when it is assigned to the connected account.",
|
|
152
|
+
triggerId: "github.issue_assigned",
|
|
153
|
+
prompt: REPLY_PROMPT
|
|
154
|
+
}],
|
|
155
|
+
actions: [{
|
|
156
|
+
id: "github.submit_review",
|
|
157
|
+
name: "Submit a review",
|
|
158
|
+
summary: "Post a review on a pull request, as a comment rather than an approval.",
|
|
159
|
+
target: [],
|
|
160
|
+
accepts: ["text", "review"]
|
|
161
|
+
}, {
|
|
162
|
+
id: "github.post_issue_comment",
|
|
163
|
+
name: "Comment on an issue",
|
|
164
|
+
summary: "Post a comment on an issue or pull request.",
|
|
165
|
+
target: [issueTarget],
|
|
166
|
+
accepts: ["text"]
|
|
167
|
+
}],
|
|
203
168
|
settings: [],
|
|
204
169
|
allowsMultipleAccounts: false,
|
|
205
170
|
byHand: {
|
|
@@ -208,86 +173,14 @@ var githubManifest = defineManifest({
|
|
|
208
173
|
}
|
|
209
174
|
});
|
|
210
175
|
/**
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
* to agree to than one that reads your inbox and tells you about it.
|
|
214
|
-
*/
|
|
215
|
-
var GMAIL_SCOPES = ["https://www.googleapis.com/auth/gmail.readonly"];
|
|
216
|
-
/**
|
|
217
|
-
* Gmail's own search, appended to what the workflow already asks. This is the
|
|
218
|
-
* setting that makes the difference between a useful loop and a firehose, so
|
|
219
|
-
* it leads.
|
|
176
|
+
* Read, and send. Sending is only ever to the connected account itself, the
|
|
177
|
+
* way a bot answers whoever asked it; the agent never holds the token.
|
|
220
178
|
*/
|
|
221
|
-
var
|
|
222
|
-
key: "extraQuery",
|
|
223
|
-
kind: "text",
|
|
224
|
-
label: "Narrow it down",
|
|
225
|
-
help: "Gmail search terms, added to the query above. For example: from:bank.example.com, or subject:invoice, or -from:notifications@github.com.",
|
|
226
|
-
placeholder: "from:boss@example.com"
|
|
227
|
-
};
|
|
228
|
-
/**
|
|
229
|
-
* A cap on how many mails one look can turn into runs, which is a cost limit
|
|
230
|
-
* rather than a filter: every mail past it is still noticed on the next poll,
|
|
231
|
-
* because nothing has marked it as dealt with.
|
|
232
|
-
*/
|
|
233
|
-
var perPoll = {
|
|
234
|
-
key: "maxPerPoll",
|
|
235
|
-
kind: "select",
|
|
236
|
-
label: "At most, per look",
|
|
237
|
-
help: "Each mail costs a full agent run. A quiet mailbox never reaches this; a busy one should be narrowed above rather than raised here.",
|
|
238
|
-
options: [
|
|
239
|
-
{
|
|
240
|
-
value: "3",
|
|
241
|
-
label: "3 messages"
|
|
242
|
-
},
|
|
243
|
-
{
|
|
244
|
-
value: "10",
|
|
245
|
-
label: "10 messages"
|
|
246
|
-
},
|
|
247
|
-
{
|
|
248
|
-
value: "25",
|
|
249
|
-
label: "25 messages"
|
|
250
|
-
}
|
|
251
|
-
],
|
|
252
|
-
default: "10"
|
|
253
|
-
};
|
|
254
|
-
/**
|
|
255
|
-
* The setting that decides whether a quiet mailbox is quiet. Without it every
|
|
256
|
-
* receipt arrives as a notification saying a receipt arrived, which costs a
|
|
257
|
-
* run each and trains you to stop looking.
|
|
258
|
-
*/
|
|
259
|
-
var needsMeOnly = {
|
|
260
|
-
key: "needsMeOnly",
|
|
261
|
-
kind: "boolean",
|
|
262
|
-
label: "Only tell me when it needs me",
|
|
263
|
-
help: "One cheap look at everything that arrived decides which mails are actually asking something of you, from the sender, subject and first line. The rest wait in the backlog with a reason, so you can still run one if this got it wrong.",
|
|
264
|
-
default: true
|
|
265
|
-
};
|
|
266
|
-
var skipOwn = {
|
|
267
|
-
key: "skipOwn",
|
|
268
|
-
kind: "boolean",
|
|
269
|
-
label: "Skip mail I sent",
|
|
270
|
-
help: "Your own messages show up in threads you are part of, and being told about what you just sent is not useful.",
|
|
271
|
-
default: true
|
|
272
|
-
};
|
|
273
|
-
var TELL_ME_PROMPT = [
|
|
274
|
-
"Read the email in EMAIL.md and tell me what it says.",
|
|
275
|
-
"",
|
|
276
|
-
"You are writing a phone notification, not a summary document. Two or three",
|
|
277
|
-
"sentences. No headings, no bullet lists, no preamble like \"this email is\".",
|
|
278
|
-
"Lead with who it is from and what they want.",
|
|
279
|
-
"",
|
|
280
|
-
"Say plainly if it needs something from me and by when. If there is a",
|
|
281
|
-
"deadline, an amount of money, a date, or a link I have to act on, include it",
|
|
282
|
-
"exactly as written rather than describing it.",
|
|
283
|
-
"",
|
|
284
|
-
"If it is an automated notification, a newsletter, or otherwise needs nothing",
|
|
285
|
-
"from me, say so in one line and stop."
|
|
286
|
-
].join("\n");
|
|
179
|
+
var GMAIL_SCOPES = ["https://www.googleapis.com/auth/gmail.readonly", "https://www.googleapis.com/auth/gmail.send"];
|
|
287
180
|
var gmailManifest = defineManifest({
|
|
288
181
|
id: "gmail",
|
|
289
182
|
name: "Gmail",
|
|
290
|
-
tagline: "
|
|
183
|
+
tagline: "Start a loop when an email arrives, or email an answer to the connected account.",
|
|
291
184
|
docsUrl: "https://developers.google.com/gmail/api/guides",
|
|
292
185
|
icon: "Gmail",
|
|
293
186
|
accent: "bg-[#ea4335] text-white",
|
|
@@ -296,29 +189,67 @@ var gmailManifest = defineManifest({
|
|
|
296
189
|
scopes: GMAIL_SCOPES,
|
|
297
190
|
needsAppRegistration: true
|
|
298
191
|
},
|
|
299
|
-
|
|
192
|
+
triggers: [{
|
|
300
193
|
id: "gmail.new_mail",
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
trigger: "mail arrives in your inbox",
|
|
194
|
+
when: "An email arrives",
|
|
195
|
+
trigger: "an email arrives in the connected inbox",
|
|
304
196
|
watches: "in:inbox newer_than:1d",
|
|
305
|
-
writes: "a message telling you what arrived",
|
|
306
197
|
runsIn: "temp",
|
|
307
198
|
settings: [
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
199
|
+
{
|
|
200
|
+
key: "extraQuery",
|
|
201
|
+
kind: "text",
|
|
202
|
+
label: "Only mail matching",
|
|
203
|
+
help: "Gmail search terms, such as from:billing@vendor.com or subject:invoice.",
|
|
204
|
+
placeholder: "from:alerts@example.com"
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
key: "needsMeOnly",
|
|
208
|
+
kind: "boolean",
|
|
209
|
+
label: "Skip mail that needs nothing",
|
|
210
|
+
help: "A quick look first decides which mail asks for something. The rest is kept, not run.",
|
|
211
|
+
default: true
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
key: "maxPerPoll",
|
|
215
|
+
kind: "select",
|
|
216
|
+
label: "At most, per check",
|
|
217
|
+
options: [
|
|
218
|
+
{
|
|
219
|
+
value: "3",
|
|
220
|
+
label: "3 emails"
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
value: "10",
|
|
224
|
+
label: "10 emails"
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
value: "25",
|
|
228
|
+
label: "25 emails"
|
|
229
|
+
}
|
|
230
|
+
],
|
|
231
|
+
default: "10"
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
key: "skipOwn",
|
|
235
|
+
kind: "boolean",
|
|
236
|
+
label: "Skip mail sent from this account",
|
|
237
|
+
default: true
|
|
238
|
+
}
|
|
312
239
|
],
|
|
313
|
-
prompt: TELL_ME_PROMPT,
|
|
314
|
-
guidancePlaceholder: "What you care about in your mail. For example: I only need to know about anything involving money or a deadline.",
|
|
315
240
|
answer: "text",
|
|
316
|
-
actionConnectorId: "
|
|
317
|
-
actionId: "
|
|
318
|
-
actionTarget: { to: "me" },
|
|
241
|
+
actionConnectorId: "schedule",
|
|
242
|
+
actionId: "schedule.record",
|
|
319
243
|
pollEveryMs: 18e5
|
|
320
244
|
}],
|
|
321
|
-
|
|
245
|
+
workflows: [],
|
|
246
|
+
actions: [{
|
|
247
|
+
id: "gmail.send",
|
|
248
|
+
name: "Email this account",
|
|
249
|
+
summary: "Send the answer to the connected account's own inbox, in the same thread when it started from an email there.",
|
|
250
|
+
target: [],
|
|
251
|
+
accepts: ["text"]
|
|
252
|
+
}],
|
|
322
253
|
settings: [],
|
|
323
254
|
allowsMultipleAccounts: false,
|
|
324
255
|
byHand: { kind: "none" }
|
|
@@ -454,27 +385,20 @@ var at = {
|
|
|
454
385
|
key: "at",
|
|
455
386
|
kind: "text",
|
|
456
387
|
label: "At",
|
|
457
|
-
help: "
|
|
388
|
+
help: "24-hour time, in this host's time zone.",
|
|
458
389
|
placeholder: "09:00"
|
|
459
390
|
};
|
|
460
391
|
var weekday = {
|
|
461
392
|
key: "weekday",
|
|
462
393
|
kind: "select",
|
|
463
394
|
label: "On",
|
|
464
|
-
help: "
|
|
395
|
+
help: "For weekly loops.",
|
|
465
396
|
options: WEEKDAY_NAMES.map((name, index) => ({
|
|
466
397
|
value: String(index),
|
|
467
398
|
label: name
|
|
468
399
|
})),
|
|
469
400
|
default: "1"
|
|
470
401
|
};
|
|
471
|
-
var folder$3 = {
|
|
472
|
-
key: "folder",
|
|
473
|
-
kind: "text",
|
|
474
|
-
label: "Folder to work in",
|
|
475
|
-
help: "An absolute path. Nothing arrives to be looked at on a schedule, so this is the whole of what the agent is given.",
|
|
476
|
-
placeholder: "/Users/you/code/web"
|
|
477
|
-
};
|
|
478
402
|
/**
|
|
479
403
|
* Deliberately a frame rather than a job. Every other workflow ships a prompt
|
|
480
404
|
* because "review a pull request" is a problem worth solving once and getting
|
|
@@ -504,58 +428,34 @@ var scheduleManifest = defineManifest({
|
|
|
504
428
|
allowsMultipleAccounts: false,
|
|
505
429
|
byHand: { kind: "now" },
|
|
506
430
|
settings: [],
|
|
507
|
-
|
|
431
|
+
triggers: [{
|
|
508
432
|
id: "schedule.recurring",
|
|
509
|
-
|
|
510
|
-
summary: "Runs an agent in a folder you name, and keeps what it says.",
|
|
433
|
+
when: "On a schedule",
|
|
511
434
|
trigger: "the time you set comes round",
|
|
512
|
-
writes: "a note in the log",
|
|
513
435
|
runsIn: "folder",
|
|
514
436
|
settings: [
|
|
515
437
|
every,
|
|
516
438
|
at,
|
|
517
|
-
weekday
|
|
518
|
-
folder$3
|
|
439
|
+
weekday
|
|
519
440
|
],
|
|
520
|
-
prompt: RECURRING_PROMPT,
|
|
521
|
-
guidancePlaceholder: "Anything that holds every time this runs. For example: this repository releases on Thursdays, so say if anything is unmerged by Wednesday evening.",
|
|
522
441
|
answer: "text",
|
|
523
442
|
actionId: "schedule.record"
|
|
524
443
|
}],
|
|
444
|
+
workflows: [{
|
|
445
|
+
id: "schedule.recurring",
|
|
446
|
+
name: "Do something on a schedule",
|
|
447
|
+
summary: "Runs the agent at the times you set, in a folder you name. The answer stays in Inbox, or goes to Slack, Lark, or GitHub.",
|
|
448
|
+
triggerId: "schedule.recurring",
|
|
449
|
+
prompt: RECURRING_PROMPT
|
|
450
|
+
}],
|
|
525
451
|
actions: [{
|
|
526
452
|
id: "schedule.record",
|
|
527
|
-
name: "Keep
|
|
528
|
-
summary: "
|
|
453
|
+
name: "Keep in Inbox",
|
|
454
|
+
summary: "Keeps the answer in Inbox and sends it nowhere.",
|
|
529
455
|
target: [],
|
|
530
456
|
accepts: ["text", "review"]
|
|
531
457
|
}]
|
|
532
458
|
});
|
|
533
|
-
var folder$2 = {
|
|
534
|
-
key: "folder",
|
|
535
|
-
kind: "text",
|
|
536
|
-
label: "Folder to work in",
|
|
537
|
-
help: "An absolute path to a checkout on this host. The agent runs here, so it can read the code you are asking about.",
|
|
538
|
-
placeholder: "/Users/you/code/your-project"
|
|
539
|
-
};
|
|
540
|
-
var channel = {
|
|
541
|
-
key: "channel",
|
|
542
|
-
kind: "text",
|
|
543
|
-
label: "Channel",
|
|
544
|
-
help: "Leave empty to reply in the thread that asked. Set a channel ID when the work started somewhere else.",
|
|
545
|
-
placeholder: "C0123456789"
|
|
546
|
-
};
|
|
547
|
-
var ASK_PROMPT$2 = [
|
|
548
|
-
"Someone has sent you a message in Slack. Do what it asks, working in this directory,",
|
|
549
|
-
"and then answer them.",
|
|
550
|
-
"",
|
|
551
|
-
"You are answering into Slack. Be brief: a couple of short paragraphs at most,",
|
|
552
|
-
"no headings, no bullet lists unless you are genuinely listing things. Plain sentences.",
|
|
553
|
-
"",
|
|
554
|
-
"If the message asks a question about this code, read enough of it to answer",
|
|
555
|
-
"properly rather than guessing. If it asks for a change, make the change and",
|
|
556
|
-
"say in one line what you did. If the message is too vague to act on, say what",
|
|
557
|
-
"you need to know instead of assuming."
|
|
558
|
-
].join("\n");
|
|
559
459
|
var slackManifest = defineManifest({
|
|
560
460
|
id: "slack",
|
|
561
461
|
name: "Slack bot",
|
|
@@ -574,57 +474,51 @@ var slackManifest = defineManifest({
|
|
|
574
474
|
secret: true
|
|
575
475
|
}]
|
|
576
476
|
},
|
|
577
|
-
|
|
477
|
+
triggers: [{
|
|
578
478
|
id: "slack.ask",
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
trigger: "the bot is @mentioned, or someone DMs it",
|
|
582
|
-
writes: "a reply in the Slack thread",
|
|
479
|
+
when: "The Slack bot is mentioned",
|
|
480
|
+
trigger: "someone @mentions the Slack bot in a channel it is in, or DMs it",
|
|
583
481
|
runsIn: "folder",
|
|
584
|
-
settings: [
|
|
585
|
-
prompt: ASK_PROMPT$2,
|
|
586
|
-
guidancePlaceholder: "Anything the agent should know about this project before it starts. For example: run pnpm test before saying a change works.",
|
|
482
|
+
settings: [],
|
|
587
483
|
answer: "text",
|
|
588
484
|
actionId: "slack.reply",
|
|
589
485
|
pollEveryMs: 6e4
|
|
590
486
|
}],
|
|
487
|
+
workflows: [{
|
|
488
|
+
id: "slack.ask",
|
|
489
|
+
name: "Do what I ask the Slack bot",
|
|
490
|
+
summary: "Runs the agent when someone @mentions the Slack bot or DMs it, and answers in the thread.",
|
|
491
|
+
triggerId: "slack.ask",
|
|
492
|
+
prompt: [
|
|
493
|
+
"Someone has sent you a message in Slack. Do what it asks, working in this directory,",
|
|
494
|
+
"and then answer them.",
|
|
495
|
+
"",
|
|
496
|
+
"You are answering into Slack. Be brief: a couple of short paragraphs at most,",
|
|
497
|
+
"no headings, no bullet lists unless you are genuinely listing things. Plain sentences.",
|
|
498
|
+
"",
|
|
499
|
+
"If the message asks a question about this code, read enough of it to answer",
|
|
500
|
+
"properly rather than guessing. If it asks for a change, make the change and",
|
|
501
|
+
"say in one line what you did. If the message is too vague to act on, say what",
|
|
502
|
+
"you need to know instead of assuming."
|
|
503
|
+
].join("\n")
|
|
504
|
+
}],
|
|
591
505
|
actions: [{
|
|
592
506
|
id: "slack.reply",
|
|
593
507
|
name: "Reply in Slack",
|
|
594
508
|
summary: "Post a message back to the thread that asked, or to a channel you name.",
|
|
595
|
-
target: [
|
|
509
|
+
target: [{
|
|
510
|
+
key: "channel",
|
|
511
|
+
kind: "text",
|
|
512
|
+
label: "Channel",
|
|
513
|
+
help: "Empty replies where it was asked. Or a channel ID.",
|
|
514
|
+
placeholder: "C0123456789"
|
|
515
|
+
}],
|
|
596
516
|
accepts: ["text"]
|
|
597
517
|
}],
|
|
598
518
|
settings: [],
|
|
599
519
|
allowsMultipleAccounts: false,
|
|
600
520
|
byHand: { kind: "none" }
|
|
601
521
|
});
|
|
602
|
-
var folder$1 = {
|
|
603
|
-
key: "folder",
|
|
604
|
-
kind: "text",
|
|
605
|
-
label: "Folder to work in",
|
|
606
|
-
help: "An absolute path to a checkout on this host. The agent runs here, so it can read the code you are asking about.",
|
|
607
|
-
placeholder: "/Users/you/code/your-project"
|
|
608
|
-
};
|
|
609
|
-
var chat = {
|
|
610
|
-
key: "chat",
|
|
611
|
-
kind: "text",
|
|
612
|
-
label: "Chat ID",
|
|
613
|
-
help: "Leave empty to reply to the message that asked. Set a chat ID (oc_…) when the work started somewhere else.",
|
|
614
|
-
placeholder: "oc_..."
|
|
615
|
-
};
|
|
616
|
-
var ASK_PROMPT$1 = [
|
|
617
|
-
"Someone has sent you a message in Feishu. Do what it asks, working in this directory,",
|
|
618
|
-
"and then answer them.",
|
|
619
|
-
"",
|
|
620
|
-
"You are answering into Feishu. Be brief: a couple of short paragraphs at most,",
|
|
621
|
-
"no headings, no bullet lists unless you are genuinely listing things. Plain sentences.",
|
|
622
|
-
"",
|
|
623
|
-
"If the message asks a question about this code, read enough of it to answer",
|
|
624
|
-
"properly rather than guessing. If it asks for a change, make the change and",
|
|
625
|
-
"say in one line what you did. If the message is too vague to act on, say what",
|
|
626
|
-
"you need to know instead of assuming."
|
|
627
|
-
].join("\n");
|
|
628
522
|
var feishuManifest = defineManifest({
|
|
629
523
|
id: "feishu",
|
|
630
524
|
name: "Feishu / Lark bot",
|
|
@@ -658,25 +552,45 @@ var feishuManifest = defineManifest({
|
|
|
658
552
|
}
|
|
659
553
|
]
|
|
660
554
|
},
|
|
661
|
-
|
|
555
|
+
triggers: [{
|
|
662
556
|
id: "feishu.ask",
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
trigger: "the bot is @mentioned in a group it is in",
|
|
666
|
-
writes: "a reply in the Feishu thread",
|
|
557
|
+
when: "The Feishu / Lark bot is mentioned",
|
|
558
|
+
trigger: "someone @mentions the Feishu / Lark bot in a group it is in",
|
|
667
559
|
runsIn: "folder",
|
|
668
|
-
settings: [
|
|
669
|
-
prompt: ASK_PROMPT$1,
|
|
670
|
-
guidancePlaceholder: "Anything the agent should know about this project before it starts. For example: run pnpm test before saying a change works.",
|
|
560
|
+
settings: [],
|
|
671
561
|
answer: "text",
|
|
672
562
|
actionId: "feishu.reply",
|
|
673
563
|
pollEveryMs: 6e4
|
|
674
564
|
}],
|
|
565
|
+
workflows: [{
|
|
566
|
+
id: "feishu.ask",
|
|
567
|
+
name: "Do what I ask the Feishu / Lark bot",
|
|
568
|
+
summary: "Runs the agent when someone @mentions the Feishu / Lark bot in a group, and answers in the thread.",
|
|
569
|
+
triggerId: "feishu.ask",
|
|
570
|
+
prompt: [
|
|
571
|
+
"Someone has sent you a message in Feishu. Do what it asks, working in this directory,",
|
|
572
|
+
"and then answer them.",
|
|
573
|
+
"",
|
|
574
|
+
"You are answering into Feishu. Be brief: a couple of short paragraphs at most,",
|
|
575
|
+
"no headings, no bullet lists unless you are genuinely listing things. Plain sentences.",
|
|
576
|
+
"",
|
|
577
|
+
"If the message asks a question about this code, read enough of it to answer",
|
|
578
|
+
"properly rather than guessing. If it asks for a change, make the change and",
|
|
579
|
+
"say in one line what you did. If the message is too vague to act on, say what",
|
|
580
|
+
"you need to know instead of assuming."
|
|
581
|
+
].join("\n")
|
|
582
|
+
}],
|
|
675
583
|
actions: [{
|
|
676
584
|
id: "feishu.reply",
|
|
677
|
-
name: "Reply in Feishu",
|
|
585
|
+
name: "Reply in Feishu / Lark",
|
|
678
586
|
summary: "Post a message back to the thread that asked, or to a chat you name.",
|
|
679
|
-
target: [
|
|
587
|
+
target: [{
|
|
588
|
+
key: "chat",
|
|
589
|
+
kind: "text",
|
|
590
|
+
label: "Chat ID",
|
|
591
|
+
help: "Empty replies where it was asked. Or a chat ID (oc_…).",
|
|
592
|
+
placeholder: "oc_..."
|
|
593
|
+
}],
|
|
680
594
|
accepts: ["text"]
|
|
681
595
|
}],
|
|
682
596
|
settings: [],
|
|
@@ -684,18 +598,6 @@ var feishuManifest = defineManifest({
|
|
|
684
598
|
byHand: { kind: "none" }
|
|
685
599
|
});
|
|
686
600
|
/**
|
|
687
|
-
* Where the agent runs. Asking a phone "what broke the build" is only worth
|
|
688
|
-
* anything if the agent can read the build, so this workflow works in a real
|
|
689
|
-
* checkout rather than an empty directory.
|
|
690
|
-
*/
|
|
691
|
-
var folder = {
|
|
692
|
-
key: "folder",
|
|
693
|
-
kind: "text",
|
|
694
|
-
label: "Folder to work in",
|
|
695
|
-
help: "An absolute path to a checkout on this host. The agent runs here, so it can read the code you are asking about.",
|
|
696
|
-
placeholder: "/Users/you/code/your-project"
|
|
697
|
-
};
|
|
698
|
-
/**
|
|
699
601
|
* A bot bound to your account can be messaged by other people, and this
|
|
700
602
|
* workflow runs a coding agent on a runner. Answering only yourself is the
|
|
701
603
|
* default because the alternative hands strangers your terminal.
|
|
@@ -724,7 +626,7 @@ var recipient = {
|
|
|
724
626
|
key: "to",
|
|
725
627
|
kind: "select",
|
|
726
628
|
label: "Who to send it to",
|
|
727
|
-
help: "
|
|
629
|
+
help: "\"Me\" only arrives if you messaged the bot in the last half day.",
|
|
728
630
|
options: [{
|
|
729
631
|
value: "source",
|
|
730
632
|
label: "Whoever asked"
|
|
@@ -767,19 +669,22 @@ var CONNECTOR_MANIFESTS = [
|
|
|
767
669
|
kind: "qr_scan",
|
|
768
670
|
note: "Scanning binds a bot to your WeChat account. Loopable can then read what people send that bot and answer as it, and nothing else on your account."
|
|
769
671
|
},
|
|
770
|
-
|
|
672
|
+
triggers: [{
|
|
771
673
|
id: "wechat.ask",
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
trigger: "you send the bot a message",
|
|
775
|
-
writes: "a reply in the chat",
|
|
674
|
+
when: "The WeChat bot gets a message",
|
|
675
|
+
trigger: "someone sends the WeChat bot a message",
|
|
776
676
|
runsIn: "folder",
|
|
777
|
-
settings: [
|
|
778
|
-
prompt: ASK_PROMPT,
|
|
779
|
-
guidancePlaceholder: "Anything the agent should know about this project before it starts. For example: run pnpm test before saying a change works.",
|
|
677
|
+
settings: [askers],
|
|
780
678
|
answer: "text",
|
|
781
679
|
actionId: "wechat.reply"
|
|
782
680
|
}],
|
|
681
|
+
workflows: [{
|
|
682
|
+
id: "wechat.ask",
|
|
683
|
+
name: "Do what I ask the WeChat bot",
|
|
684
|
+
summary: "Runs the agent when someone messages the WeChat bot, and answers in the chat.",
|
|
685
|
+
triggerId: "wechat.ask",
|
|
686
|
+
prompt: ASK_PROMPT
|
|
687
|
+
}],
|
|
783
688
|
actions: [{
|
|
784
689
|
id: "wechat.reply",
|
|
785
690
|
name: "Reply in the chat",
|
|
@@ -813,17 +718,90 @@ function needsAccount(id) {
|
|
|
813
718
|
function connectionNoun(id) {
|
|
814
719
|
return connectorManifest(id)?.auth.kind === "token" ? "bot" : "account";
|
|
815
720
|
}
|
|
721
|
+
/** A trigger with nothing on top: what a custom loop runs. */
|
|
722
|
+
function customOn(trigger) {
|
|
723
|
+
return {
|
|
724
|
+
...trigger,
|
|
725
|
+
triggerId: trigger.id,
|
|
726
|
+
name: trigger.when,
|
|
727
|
+
summary: `When ${trigger.trigger}.`,
|
|
728
|
+
prompt: "",
|
|
729
|
+
custom: true
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* What a loop runs, by the id it stores: a workflow, merged with the trigger
|
|
734
|
+
* it sits on, or a trigger alone for a custom loop.
|
|
735
|
+
*/
|
|
816
736
|
function connectorWorkflow(connectorId, workflowId) {
|
|
817
|
-
|
|
737
|
+
const manifest = connectorManifest(connectorId);
|
|
738
|
+
if (!manifest) return void 0;
|
|
739
|
+
const defined = manifest.workflows.find((entry) => entry.id === workflowId);
|
|
740
|
+
if (defined) {
|
|
741
|
+
const trigger = manifest.triggers.find((entry) => entry.id === defined.triggerId);
|
|
742
|
+
if (!trigger) return void 0;
|
|
743
|
+
return {
|
|
744
|
+
...trigger,
|
|
745
|
+
...defined,
|
|
746
|
+
actionId: defined.actionId ?? trigger.actionId,
|
|
747
|
+
actionConnectorId: defined.actionConnectorId ?? trigger.actionConnectorId,
|
|
748
|
+
actionTarget: defined.actionTarget ?? trigger.actionTarget,
|
|
749
|
+
pollEveryMs: defined.pollEveryMs ?? trigger.pollEveryMs,
|
|
750
|
+
triggerId: trigger.id,
|
|
751
|
+
custom: false
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
const trigger = manifest.triggers.find((entry) => entry.id === workflowId);
|
|
755
|
+
return trigger ? customOn(trigger) : void 0;
|
|
818
756
|
}
|
|
819
757
|
function connectorAction(connectorId, actionId) {
|
|
820
758
|
return connectorManifest(connectorId)?.actions.find((entry) => entry.id === actionId);
|
|
821
759
|
}
|
|
822
|
-
/**
|
|
760
|
+
/**
|
|
761
|
+
* Keeping the answer in Inbox is the clock's action, because the clock is the
|
|
762
|
+
* one connector with nowhere of its own to write. To a person it is not a
|
|
763
|
+
* Schedule thing at all, so it is named for where the answer stays.
|
|
764
|
+
*/
|
|
765
|
+
var INBOX_ONLY = {
|
|
766
|
+
connectorId: "schedule",
|
|
767
|
+
actionId: "schedule.record"
|
|
768
|
+
};
|
|
769
|
+
function isInboxOnly(connectorId, actionId) {
|
|
770
|
+
return connectorId === INBOX_ONLY.connectorId && actionId === INBOX_ONLY.actionId;
|
|
771
|
+
}
|
|
772
|
+
/** What to call a connector as a place answers go. */
|
|
773
|
+
function writerName(connectorId) {
|
|
774
|
+
if (connectorId === INBOX_ONLY.connectorId) return "Inbox";
|
|
775
|
+
return connectorManifest(connectorId)?.name ?? connectorId;
|
|
776
|
+
}
|
|
777
|
+
/** Where a loop's answer goes, in a few words: "GitHub · Submit a review", or "Inbox only". */
|
|
778
|
+
function destinationLabel(connectorId, actionId) {
|
|
779
|
+
if (isInboxOnly(connectorId, actionId)) return "Inbox only";
|
|
780
|
+
const action = connectorAction(connectorId, actionId);
|
|
781
|
+
return action ? `${writerName(connectorId)} · ${action.name}` : "No destination";
|
|
782
|
+
}
|
|
783
|
+
/** Connectors that can write, Inbox first, for the question of where an answer goes. */
|
|
784
|
+
function writers() {
|
|
785
|
+
const all = CONNECTOR_MANIFESTS.filter((entry) => entry.actions.length > 0);
|
|
786
|
+
return [...all.filter((entry) => entry.id === INBOX_ONLY.connectorId), ...all.filter((entry) => entry.id !== INBOX_ONLY.connectorId)];
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Every workflow on offer, for the page that asks which one to turn on.
|
|
790
|
+
*
|
|
791
|
+
* The clock leads: it needs no account, and a scheduled job is the easiest
|
|
792
|
+
* first loop to try. The rest follow the connectors' own order.
|
|
793
|
+
*/
|
|
823
794
|
function allWorkflows() {
|
|
824
|
-
return CONNECTOR_MANIFESTS.flatMap((connector) => connector.workflows.map((
|
|
795
|
+
return [...CONNECTOR_MANIFESTS.filter((connector) => connector.id === "schedule"), ...CONNECTOR_MANIFESTS.filter((connector) => connector.id !== "schedule")].flatMap((connector) => connector.workflows.map((entry) => ({
|
|
825
796
|
connector,
|
|
826
|
-
workflow
|
|
797
|
+
workflow: connectorWorkflow(connector.id, entry.id)
|
|
798
|
+
})));
|
|
799
|
+
}
|
|
800
|
+
/** Every trigger on offer, the clock first, for choosing what starts a custom loop. */
|
|
801
|
+
function allTriggers() {
|
|
802
|
+
return [...CONNECTOR_MANIFESTS.filter((connector) => connector.id === "schedule"), ...CONNECTOR_MANIFESTS.filter((connector) => connector.id !== "schedule")].flatMap((connector) => connector.triggers.map((trigger) => ({
|
|
803
|
+
connector,
|
|
804
|
+
trigger
|
|
827
805
|
})));
|
|
828
806
|
}
|
|
829
807
|
/**
|
|
@@ -1286,15 +1264,15 @@ function candidates(items, repositories) {
|
|
|
1286
1264
|
});
|
|
1287
1265
|
}
|
|
1288
1266
|
/**
|
|
1289
|
-
* What the
|
|
1267
|
+
* What the trigger says it watches for, which is what gets asked. Taking the
|
|
1290
1268
|
* query from the manifest is the point: it is shown on the loop page, and a
|
|
1291
1269
|
* search that quietly differed from the one on screen would be the worst kind
|
|
1292
1270
|
* of wrong.
|
|
1293
1271
|
*/
|
|
1294
|
-
function query(
|
|
1295
|
-
const
|
|
1296
|
-
if (!
|
|
1297
|
-
return
|
|
1272
|
+
function query(triggerId) {
|
|
1273
|
+
const trigger = githubManifest.triggers.find((entry) => entry.id === triggerId);
|
|
1274
|
+
if (!trigger?.watches) throw new Error(`GitHub cannot watch for ${triggerId}.`);
|
|
1275
|
+
return trigger.watches;
|
|
1298
1276
|
}
|
|
1299
1277
|
async function pollGithub(input) {
|
|
1300
1278
|
const repositories = stringList(input.settings.repositories);
|
|
@@ -1567,13 +1545,18 @@ var githubRuntime = defineRuntime({
|
|
|
1567
1545
|
}
|
|
1568
1546
|
});
|
|
1569
1547
|
var BASE = "https://gmail.googleapis.com/gmail/v1/users/me";
|
|
1570
|
-
async function request(accessToken, path) {
|
|
1548
|
+
async function request(accessToken, path, init = {}) {
|
|
1571
1549
|
let res;
|
|
1572
1550
|
try {
|
|
1573
|
-
res = await fetch(`${BASE}${path}`, {
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1551
|
+
res = await fetch(`${BASE}${path}`, {
|
|
1552
|
+
method: init.method ?? "GET",
|
|
1553
|
+
headers: {
|
|
1554
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1555
|
+
Accept: "application/json",
|
|
1556
|
+
...init.body === void 0 ? {} : { "Content-Type": "application/json" }
|
|
1557
|
+
},
|
|
1558
|
+
...init.body === void 0 ? {} : { body: JSON.stringify(init.body) }
|
|
1559
|
+
});
|
|
1577
1560
|
} catch (error) {
|
|
1578
1561
|
throw new TransientError(`Gmail could not be reached: ${error instanceof Error ? error.message : String(error)}`);
|
|
1579
1562
|
}
|
|
@@ -1582,6 +1565,7 @@ async function request(accessToken, path) {
|
|
|
1582
1565
|
const message = `Gmail ${res.status} on ${path}: ${detail}`;
|
|
1583
1566
|
const backoff = res.status === 403 && /rateLimitExceeded|userRateLimitExceeded/i.test(detail);
|
|
1584
1567
|
if (res.status === 429 || res.status >= 500 || backoff) throw new TransientError(message);
|
|
1568
|
+
if (res.status === 403 && /insufficient.*scope|ACCESS_TOKEN_SCOPE_INSUFFICIENT/i.test(detail)) throw new Error("This Gmail connection cannot send mail. Reconnect Gmail to allow it.");
|
|
1585
1569
|
throw new Error(message);
|
|
1586
1570
|
}
|
|
1587
1571
|
return await res.json();
|
|
@@ -1618,6 +1602,16 @@ function getMessageHeaders(accessToken, id) {
|
|
|
1618
1602
|
function getMessage(accessToken, id) {
|
|
1619
1603
|
return request(accessToken, `/messages/${id}?format=full`);
|
|
1620
1604
|
}
|
|
1605
|
+
/** Send a message already written as RFC 2822 text, in a thread when one is given. */
|
|
1606
|
+
function sendMessage$2(accessToken, raw, threadId) {
|
|
1607
|
+
return request(accessToken, "/messages/send", {
|
|
1608
|
+
method: "POST",
|
|
1609
|
+
body: threadId ? {
|
|
1610
|
+
raw,
|
|
1611
|
+
threadId
|
|
1612
|
+
} : { raw }
|
|
1613
|
+
});
|
|
1614
|
+
}
|
|
1621
1615
|
/** A Gmail message id, as a ref: short, stable, and unique to the mailbox. */
|
|
1622
1616
|
function mailRef(id) {
|
|
1623
1617
|
return `mail#${id}`;
|
|
@@ -1729,6 +1723,30 @@ function mailContext(message) {
|
|
|
1729
1723
|
].join("\n")
|
|
1730
1724
|
};
|
|
1731
1725
|
}
|
|
1726
|
+
/** Base64url, as Gmail wants a raw message. */
|
|
1727
|
+
function base64url(text) {
|
|
1728
|
+
return Buffer.from(text, "utf8").toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
1729
|
+
}
|
|
1730
|
+
/** A header value that may hold any text, encoded so non-ASCII survives. */
|
|
1731
|
+
function encodedWord(text) {
|
|
1732
|
+
return /^[\x20-\x7e]*$/.test(text) ? text : `=?UTF-8?B?${Buffer.from(text, "utf8").toString("base64")}?=`;
|
|
1733
|
+
}
|
|
1734
|
+
/**
|
|
1735
|
+
* A plain-text email as Gmail's `raw`. The body is base64 so any line length
|
|
1736
|
+
* and any language go through untouched.
|
|
1737
|
+
*/
|
|
1738
|
+
function composeMail(input) {
|
|
1739
|
+
return base64url([
|
|
1740
|
+
`To: ${input.to}`,
|
|
1741
|
+
`Subject: ${encodedWord(input.subject)}`,
|
|
1742
|
+
...input.inReplyTo ? [`In-Reply-To: ${input.inReplyTo}`, `References: ${input.inReplyTo}`] : [],
|
|
1743
|
+
"MIME-Version: 1.0",
|
|
1744
|
+
"Content-Type: text/plain; charset=\"UTF-8\"",
|
|
1745
|
+
"Content-Transfer-Encoding: base64",
|
|
1746
|
+
"",
|
|
1747
|
+
Buffer.from(input.body, "utf8").toString("base64").replace(/.{76}/g, "$&\r\n")
|
|
1748
|
+
].join("\r\n"));
|
|
1749
|
+
}
|
|
1732
1750
|
/**
|
|
1733
1751
|
* What the workflow says it watches for, which is what gets asked. Taken from
|
|
1734
1752
|
* the manifest rather than written again here, because it is shown on the loop
|
|
@@ -1736,9 +1754,9 @@ function mailContext(message) {
|
|
|
1736
1754
|
* worst kind of wrong.
|
|
1737
1755
|
*/
|
|
1738
1756
|
function baseQuery(workflowId) {
|
|
1739
|
-
const
|
|
1740
|
-
if (!
|
|
1741
|
-
return
|
|
1757
|
+
const trigger = gmailManifest.triggers.find((entry) => entry.id === workflowId);
|
|
1758
|
+
if (!trigger?.watches) throw new Error(`Gmail cannot watch for ${workflowId}.`);
|
|
1759
|
+
return trigger.watches;
|
|
1742
1760
|
}
|
|
1743
1761
|
/**
|
|
1744
1762
|
* Missing means "the workflow's default", not "off". A loop saved before a
|
|
@@ -1906,7 +1924,7 @@ function accountOf$3(emailAddress) {
|
|
|
1906
1924
|
};
|
|
1907
1925
|
}
|
|
1908
1926
|
/**
|
|
1909
|
-
* Which message this task is about.
|
|
1927
|
+
* Which message this task is about. A task keeps the id, and a pasted link
|
|
1910
1928
|
* carries one too, so either is enough to fetch the mail again.
|
|
1911
1929
|
*/
|
|
1912
1930
|
function messageIdFrom(url, payload) {
|
|
@@ -1954,6 +1972,33 @@ var gmailRuntime = defineRuntime({
|
|
|
1954
1972
|
emailAddress: (await getProfile(accessToken)).emailAddress
|
|
1955
1973
|
}) };
|
|
1956
1974
|
},
|
|
1975
|
+
/**
|
|
1976
|
+
* Answers go to the connected account itself, the way a bot answers
|
|
1977
|
+
* whoever asked it. When the run started from an email in this inbox, the
|
|
1978
|
+
* answer joins that thread.
|
|
1979
|
+
*/
|
|
1980
|
+
async applyAction({ actionId, source, body, credential }) {
|
|
1981
|
+
if (actionId !== "gmail.send") throw new Error(`Gmail cannot ${actionId}.`);
|
|
1982
|
+
const { accessToken } = await usableToken(credential);
|
|
1983
|
+
const profile = await getProfile(accessToken);
|
|
1984
|
+
let subject = source.title || "From Loopable";
|
|
1985
|
+
let threadId;
|
|
1986
|
+
let inReplyTo;
|
|
1987
|
+
const sourceId = source.connectorId === "gmail" ? source.ref.replace(/^mail#/, "") : "";
|
|
1988
|
+
if (sourceId) {
|
|
1989
|
+
const original = await getMessage(accessToken, sourceId);
|
|
1990
|
+
threadId = original.threadId;
|
|
1991
|
+
inReplyTo = headerOf(original, "Message-ID") || void 0;
|
|
1992
|
+
const said = headerOf(original, "Subject");
|
|
1993
|
+
if (said) subject = /^re:/i.test(said) ? said : `Re: ${said}`;
|
|
1994
|
+
}
|
|
1995
|
+
return { url: permalink$2((await sendMessage$2(accessToken, composeMail({
|
|
1996
|
+
to: profile.emailAddress,
|
|
1997
|
+
subject,
|
|
1998
|
+
body,
|
|
1999
|
+
inReplyTo
|
|
2000
|
+
}), threadId)).id) };
|
|
2001
|
+
},
|
|
1957
2002
|
auth: {
|
|
1958
2003
|
async startAuthorization({ redirectUri, state }) {
|
|
1959
2004
|
const registration = requireRegistration();
|
|
@@ -2128,6 +2173,7 @@ async function authTest(botToken) {
|
|
|
2128
2173
|
const teamId = typeof body.team_id === "string" ? body.team_id : "";
|
|
2129
2174
|
const botUserId = typeof body.user_id === "string" ? body.user_id : "";
|
|
2130
2175
|
const botId = typeof body.bot_id === "string" ? body.bot_id : "";
|
|
2176
|
+
const botName = typeof body.user === "string" ? body.user : "";
|
|
2131
2177
|
if (!teamId || !botUserId) throw new Error("Slack did not return which workspace this bot belongs to.");
|
|
2132
2178
|
return {
|
|
2133
2179
|
botToken,
|
|
@@ -2135,7 +2181,8 @@ async function authTest(botToken) {
|
|
|
2135
2181
|
team,
|
|
2136
2182
|
teamUrl,
|
|
2137
2183
|
botUserId,
|
|
2138
|
-
botId
|
|
2184
|
+
botId,
|
|
2185
|
+
botName
|
|
2139
2186
|
};
|
|
2140
2187
|
}
|
|
2141
2188
|
async function listConversations(credential) {
|
|
@@ -2243,7 +2290,7 @@ function destination$1(target, source) {
|
|
|
2243
2290
|
function accountOf$2(credential) {
|
|
2244
2291
|
return {
|
|
2245
2292
|
id: credential.teamId,
|
|
2246
|
-
label: credential.team || credential.teamId,
|
|
2293
|
+
label: [credential.botName ? `@${credential.botName}` : null, credential.team || credential.teamId].filter(Boolean).join(" · "),
|
|
2247
2294
|
url: credential.teamUrl || void 0
|
|
2248
2295
|
};
|
|
2249
2296
|
}
|
|
@@ -3338,4 +3385,4 @@ async function checkConnection(id) {
|
|
|
3338
3385
|
return toView(await getConnection(id));
|
|
3339
3386
|
}
|
|
3340
3387
|
//#endregion
|
|
3341
|
-
export {
|
|
3388
|
+
export { startAuthAttempt as _, connectionNoun as a, writerName as b, connectorRuntime as c, destinationLabel as d, isInboxOnly as f, saveAuthorizedConnection as g, removeConnection as h, checkConnection as i, connectorWorkflow as l, needsAccount as m, allTriggers as n, connectorAction as o, listConnections as p, allWorkflows as r, connectorManifest as s, CONNECTOR_MANIFESTS as t, credentialForConnector as u, takeAuthAttempt as v, writers as x, updateConnectionSettings as y };
|