@fastagent-sh/fastagent 0.29.0 → 0.30.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.
Files changed (310) hide show
  1. package/README.md +9 -4
  2. package/dist/atomic-write.d.ts +4 -5
  3. package/dist/atomic-write.js +4 -5
  4. package/dist/authoring.d.ts +32 -15
  5. package/dist/authoring.js +147 -127
  6. package/dist/channels/agentcore-protocol.d.ts +8 -3
  7. package/dist/channels/agentcore-protocol.js +3 -1
  8. package/dist/channels/agentcore-service.d.ts +1 -0
  9. package/dist/channels/agentcore-service.js +14 -4
  10. package/dist/channels/agentcore.d.ts +9 -0
  11. package/dist/channels/agentcore.js +29 -4
  12. package/dist/channels/context.d.ts +2 -0
  13. package/dist/channels/context.js +5 -1
  14. package/dist/channels/feishu/feishu-api.d.ts +2 -1
  15. package/dist/channels/feishu/feishu.d.ts +0 -3
  16. package/dist/channels/feishu/feishu.js +10 -10
  17. package/dist/channels/feishu/register-app.js +3 -5
  18. package/dist/channels/feishu/scaffold/channel.ts +32 -34
  19. package/dist/channels/feishu/scaffold/feishu-send.ts +5 -5
  20. package/dist/channels/feishu/scaffold/feishu-threads.ts +3 -3
  21. package/dist/channels/feishu/setup-mode.d.ts +2 -12
  22. package/dist/channels/feishu/setup-mode.js +2 -22
  23. package/dist/channels/feishu/setup.d.ts +12 -0
  24. package/dist/channels/feishu/setup.js +197 -0
  25. package/dist/channels/kit/abortable.d.ts +8 -0
  26. package/dist/channels/kit/abortable.js +22 -0
  27. package/dist/channels/lark/lark.d.ts +0 -3
  28. package/dist/channels/lark/lark.js +0 -5
  29. package/dist/channels/lark/scaffold/channel.ts +33 -35
  30. package/dist/channels/lark/scaffold/lark-send.ts +5 -5
  31. package/dist/channels/lark/scaffold/lark-threads.ts +3 -3
  32. package/dist/channels/lark/setup.d.ts +9 -0
  33. package/dist/channels/lark/setup.js +57 -0
  34. package/dist/channels/setup.d.ts +112 -0
  35. package/dist/channels/setup.js +31 -0
  36. package/dist/channels/slack/history.js +1 -1
  37. package/dist/channels/slack/manifest.d.ts +17 -7
  38. package/dist/channels/slack/manifest.js +12 -16
  39. package/dist/channels/slack/scaffold/channel.ts +44 -30
  40. package/dist/channels/slack/scaffold/slack-send.ts +8 -8
  41. package/dist/channels/slack/scaffold/slack-threads.ts +4 -4
  42. package/dist/channels/slack/setup.d.ts +7 -0
  43. package/dist/channels/slack/setup.js +119 -0
  44. package/dist/channels/slack/slack-api.d.ts +2 -0
  45. package/dist/channels/slack/slack-api.js +4 -0
  46. package/dist/channels/slack/slack.d.ts +21 -2
  47. package/dist/channels/slack/slack.js +398 -338
  48. package/dist/channels/slack/socket.d.ts +57 -0
  49. package/dist/channels/slack/socket.js +305 -0
  50. package/dist/channels/telegram/polling.d.ts +27 -0
  51. package/dist/channels/telegram/polling.js +105 -0
  52. package/dist/channels/telegram/scaffold/channel.ts +52 -32
  53. package/dist/channels/telegram/scaffold/telegram-send.ts +9 -9
  54. package/dist/channels/telegram/setup.d.ts +2 -0
  55. package/dist/channels/telegram/setup.js +42 -0
  56. package/dist/channels/telegram/telegram-api.d.ts +27 -1
  57. package/dist/channels/telegram/telegram-api.js +7 -6
  58. package/dist/channels/telegram/telegram.d.ts +11 -3
  59. package/dist/channels/telegram/telegram.js +184 -157
  60. package/dist/channels/webhook-evidence.d.ts +11 -0
  61. package/dist/channels/webhook-evidence.js +71 -0
  62. package/dist/channels/webhooks.d.ts +37 -0
  63. package/dist/channels/webhooks.js +70 -0
  64. package/dist/child-env.d.ts +16 -0
  65. package/dist/child-env.js +30 -0
  66. package/dist/cli/box-login.js +4 -1
  67. package/dist/cli/channel-app.d.ts +3 -0
  68. package/dist/cli/channel-app.js +35 -0
  69. package/dist/cli/channel-setup.d.ts +38 -0
  70. package/dist/cli/channel-setup.js +195 -0
  71. package/dist/cli/commands/add.d.ts +6 -14
  72. package/dist/cli/commands/add.js +45 -303
  73. package/dist/cli/commands/auth.js +8 -2
  74. package/dist/cli/commands/build.d.ts +5 -0
  75. package/dist/cli/commands/build.js +16 -0
  76. package/dist/cli/commands/channels.d.ts +19 -0
  77. package/dist/cli/commands/channels.js +230 -0
  78. package/dist/cli/commands/content.d.ts +7 -3
  79. package/dist/cli/commands/content.js +16 -10
  80. package/dist/cli/commands/deploy/agentcore.js +55 -23
  81. package/dist/cli/commands/deploy/channel-apps.d.ts +17 -0
  82. package/dist/cli/commands/deploy/channel-apps.js +30 -0
  83. package/dist/cli/commands/deploy/docker.js +32 -65
  84. package/dist/cli/commands/deploy/fly.js +28 -12
  85. package/dist/cli/commands/deploy/railway.js +38 -13
  86. package/dist/cli/commands/deploy/secrets-step.d.ts +19 -0
  87. package/dist/cli/commands/deploy/secrets-step.js +66 -0
  88. package/dist/cli/commands/deploy/shared.d.ts +16 -5
  89. package/dist/cli/commands/deploy/shared.js +25 -8
  90. package/dist/cli/commands/deploy.js +53 -60
  91. package/dist/cli/commands/dev.d.ts +2 -0
  92. package/dist/cli/commands/dev.js +15 -3
  93. package/dist/cli/commands/env.js +1 -1
  94. package/dist/cli/commands/info.d.ts +2 -0
  95. package/dist/cli/commands/info.js +60 -25
  96. package/dist/cli/commands/init.js +3 -1
  97. package/dist/cli/commands/invoke.d.ts +2 -0
  98. package/dist/cli/commands/invoke.js +20 -7
  99. package/dist/cli/commands/login.d.ts +0 -7
  100. package/dist/cli/commands/login.js +22 -10
  101. package/dist/cli/commands/logout.js +1 -1
  102. package/dist/cli/commands/mcp.js +14 -6
  103. package/dist/cli/commands/models.js +3 -2
  104. package/dist/cli/commands/schedules.d.ts +4 -2
  105. package/dist/cli/commands/schedules.js +14 -10
  106. package/dist/cli/commands/secrets.d.ts +14 -0
  107. package/dist/cli/commands/secrets.js +213 -0
  108. package/dist/cli/commands/start.d.ts +14 -14
  109. package/dist/cli/commands/start.js +46 -60
  110. package/dist/cli/content-view.d.ts +4 -6
  111. package/dist/cli/content-view.js +5 -11
  112. package/dist/cli/first-run-channels.d.ts +3 -0
  113. package/dist/cli/first-run-channels.js +63 -0
  114. package/dist/cli/instance.d.ts +41 -0
  115. package/dist/cli/instance.js +155 -0
  116. package/dist/cli/invoke-stream.d.ts +5 -2
  117. package/dist/cli/invoke-stream.js +7 -2
  118. package/dist/cli/program.js +282 -110
  119. package/dist/cli/serve.d.ts +4 -5
  120. package/dist/cli/serve.js +4 -7
  121. package/dist/cli/shared.d.ts +11 -2
  122. package/dist/cli/shared.js +32 -19
  123. package/dist/cli/ticket.js +50 -6
  124. package/dist/content/declare.d.ts +31 -37
  125. package/dist/content/declare.js +59 -60
  126. package/dist/content/file.d.ts +23 -12
  127. package/dist/content/file.js +48 -35
  128. package/dist/content/git.d.ts +0 -10
  129. package/dist/content/git.js +5 -39
  130. package/dist/content/resolve.d.ts +18 -22
  131. package/dist/content/resolve.js +32 -66
  132. package/dist/content/source.d.ts +7 -8
  133. package/dist/content/source.js +10 -28
  134. package/dist/credentials-page.d.ts +26 -0
  135. package/dist/credentials-page.js +185 -0
  136. package/dist/declared-secrets.d.ts +10 -3
  137. package/dist/declared-secrets.js +8 -4
  138. package/dist/deploy/agentcore/destroy.d.ts +4 -1
  139. package/dist/deploy/agentcore/destroy.js +25 -1
  140. package/dist/deploy/agentcore/forwarder.js +1 -1
  141. package/dist/deploy/agentcore/plan.d.ts +7 -21
  142. package/dist/deploy/agentcore/plan.js +47 -49
  143. package/dist/deploy/agentcore/run.d.ts +18 -10
  144. package/dist/deploy/agentcore/run.js +93 -70
  145. package/dist/deploy/agentcore/runtime-secrets.d.ts +7 -0
  146. package/dist/deploy/agentcore/runtime-secrets.js +108 -0
  147. package/dist/deploy/agentcore/shell.js +12 -19
  148. package/dist/deploy/agentcore/sigv4.d.ts +7 -0
  149. package/dist/deploy/agentcore/sigv4.js +15 -0
  150. package/dist/deploy/agentcore/slr.d.ts +18 -0
  151. package/dist/deploy/agentcore/slr.js +27 -0
  152. package/dist/deploy/agentcore/store.d.ts +7 -0
  153. package/dist/deploy/agentcore/store.js +115 -0
  154. package/dist/deploy/api.d.ts +14 -14
  155. package/dist/deploy/api.js +26 -24
  156. package/dist/deploy/box-shell.d.ts +3 -1
  157. package/dist/deploy/build-context.d.ts +4 -4
  158. package/dist/deploy/build-context.js +11 -15
  159. package/dist/deploy/channel-ingress.d.ts +35 -9
  160. package/dist/deploy/channel-ingress.js +119 -22
  161. package/dist/deploy/container.d.ts +35 -10
  162. package/dist/deploy/container.js +79 -37
  163. package/dist/deploy/delivered-text.d.ts +13 -0
  164. package/dist/deploy/delivered-text.js +86 -0
  165. package/dist/deploy/docker/plan.d.ts +7 -8
  166. package/dist/deploy/docker/plan.js +31 -52
  167. package/dist/deploy/docker/run.d.ts +5 -43
  168. package/dist/deploy/docker/run.js +9 -84
  169. package/dist/deploy/file-store.d.ts +2 -0
  170. package/dist/deploy/file-store.js +17 -0
  171. package/dist/deploy/fly/plan.d.ts +3 -2
  172. package/dist/deploy/fly/plan.js +20 -9
  173. package/dist/deploy/fly/run.d.ts +18 -8
  174. package/dist/deploy/fly/run.js +24 -42
  175. package/dist/deploy/fly/store.d.ts +12 -0
  176. package/dist/deploy/fly/store.js +83 -0
  177. package/dist/deploy/hosts.d.ts +1 -2
  178. package/dist/deploy/hosts.js +1 -2
  179. package/dist/deploy/preflight.d.ts +18 -5
  180. package/dist/deploy/preflight.js +135 -63
  181. package/dist/deploy/railway/ignore.d.ts +8 -0
  182. package/dist/deploy/railway/ignore.js +57 -0
  183. package/dist/deploy/railway/plan.d.ts +1 -2
  184. package/dist/deploy/railway/plan.js +8 -6
  185. package/dist/deploy/railway/run.d.ts +21 -15
  186. package/dist/deploy/railway/run.js +32 -78
  187. package/dist/deploy/railway/store.d.ts +29 -0
  188. package/dist/deploy/railway/store.js +143 -0
  189. package/dist/deploy/runner.d.ts +5 -2
  190. package/dist/deploy/runner.js +4 -3
  191. package/dist/deploy/secret-store.d.ts +63 -0
  192. package/dist/deploy/secret-store.js +98 -0
  193. package/dist/deploy/secrets.d.ts +29 -46
  194. package/dist/deploy/secrets.js +49 -122
  195. package/dist/deploy/workspace.d.ts +6 -12
  196. package/dist/deploy/workspace.js +18 -94
  197. package/dist/dev-supervisor.js +6 -8
  198. package/dist/environment/declare.js +3 -2
  199. package/dist/environment/mise.d.ts +3 -1
  200. package/dist/environment/mise.js +4 -2
  201. package/dist/feishu.d.ts +1 -1
  202. package/dist/feishu.js +1 -1
  203. package/dist/harnesses/models/catalog.d.ts +1 -0
  204. package/dist/harnesses/models/catalog.js +1 -2
  205. package/dist/harnesses/models/environment.d.ts +7 -2
  206. package/dist/harnesses/models/environment.js +9 -5
  207. package/dist/harnesses/models/host.d.ts +0 -7
  208. package/dist/harnesses/models/host.js +10 -1
  209. package/dist/harnesses/models/login.d.ts +0 -8
  210. package/dist/harnesses/models/login.js +5 -4
  211. package/dist/harnesses/models/models.d.ts +7 -2
  212. package/dist/harnesses/models/models.js +28 -4
  213. package/dist/harnesses/pi-durable/agent.js +59 -38
  214. package/dist/harnesses/pi-durable/build.d.ts +12 -0
  215. package/dist/harnesses/pi-durable/build.js +270 -0
  216. package/dist/harnesses/pi-durable/built.d.ts +24 -0
  217. package/dist/harnesses/pi-durable/built.js +136 -0
  218. package/dist/harnesses/pi-durable/config.d.ts +81 -0
  219. package/dist/harnesses/pi-durable/config.js +266 -0
  220. package/dist/harnesses/pi-durable/define.d.ts +63 -13
  221. package/dist/harnesses/pi-durable/define.js +23 -2
  222. package/dist/harnesses/pi-durable/definition.d.ts +72 -0
  223. package/dist/harnesses/pi-durable/definition.js +143 -0
  224. package/dist/harnesses/pi-durable/events.d.ts +4 -3
  225. package/dist/harnesses/pi-durable/events.js +179 -136
  226. package/dist/harnesses/pi-durable/execution-env.d.ts +17 -0
  227. package/dist/harnesses/pi-durable/execution-env.js +11 -0
  228. package/dist/harnesses/pi-durable/mcp.js +1 -3
  229. package/dist/harnesses/pi-durable/open.d.ts +35 -37
  230. package/dist/harnesses/pi-durable/open.js +91 -153
  231. package/dist/harnesses/pi-durable/reads.d.ts +0 -1
  232. package/dist/harnesses/pi-durable/reads.js +1 -3
  233. package/dist/harnesses/pi-durable/registry.d.ts +4 -6
  234. package/dist/harnesses/pi-durable/registry.js +29 -26
  235. package/dist/harnesses/pi-durable/release.d.ts +2 -2
  236. package/dist/harnesses/pi-durable/release.js +10 -7
  237. package/dist/harnesses/pi-durable/serve.d.ts +91 -15
  238. package/dist/harnesses/pi-durable/serve.js +109 -47
  239. package/dist/harnesses/pi-durable/sessions.js +15 -12
  240. package/dist/harnesses/pi-durable/text-sources.d.ts +30 -0
  241. package/dist/harnesses/pi-durable/text-sources.js +55 -0
  242. package/dist/harnesses/pi-durable/text.d.ts +16 -4
  243. package/dist/harnesses/pi-durable/text.js +115 -59
  244. package/dist/harnesses/pi-durable/wake.js +8 -8
  245. package/dist/http/client.js +9 -3
  246. package/dist/http/contract.d.ts +3 -3
  247. package/dist/http/server.d.ts +1 -1
  248. package/dist/http/server.js +1 -1
  249. package/dist/index.d.ts +8 -5
  250. package/dist/index.js +4 -1
  251. package/dist/instance-name.d.ts +7 -0
  252. package/dist/instance-name.js +26 -0
  253. package/dist/lark.d.ts +1 -1
  254. package/dist/lark.js +1 -1
  255. package/dist/mcp/client.js +11 -2
  256. package/dist/mcp/config.d.ts +70 -18
  257. package/dist/mcp/config.js +136 -53
  258. package/dist/mcp/host.d.ts +5 -2
  259. package/dist/mcp/host.js +14 -6
  260. package/dist/mcp/sign-in.js +2 -2
  261. package/dist/mcp-api.d.ts +14 -0
  262. package/dist/paths.d.ts +22 -8
  263. package/dist/paths.js +44 -11
  264. package/dist/runtime-websocket.d.ts +14 -0
  265. package/dist/runtime-websocket.js +39 -0
  266. package/dist/scaffold/add-channel.d.ts +9 -16
  267. package/dist/scaffold/add-channel.js +13 -83
  268. package/dist/scaffold/templates/env.example +4 -5
  269. package/dist/scaffold/templates/fastagent.config.ts +20 -14
  270. package/dist/scaffold/templates/gitignore +9 -6
  271. package/dist/scaffold/templates.d.ts +6 -2
  272. package/dist/scaffold/templates.js +7 -2
  273. package/dist/scaffold/vendor-skill.js +4 -2
  274. package/dist/service.d.ts +44 -6
  275. package/dist/service.js +110 -10
  276. package/dist/serving-command.d.ts +10 -0
  277. package/dist/serving-command.js +1 -0
  278. package/dist/slack.d.ts +1 -1
  279. package/dist/slack.js +1 -1
  280. package/dist/strip-edges.d.ts +10 -0
  281. package/dist/strip-edges.js +22 -0
  282. package/dist/telegram.d.ts +1 -1
  283. package/dist/telegram.js +1 -1
  284. package/dist/tunnel.d.ts +0 -1
  285. package/dist/tunnel.js +2 -8
  286. package/dist/value-file.d.ts +11 -6
  287. package/dist/value-file.js +21 -9
  288. package/dist/version.d.ts +7 -1
  289. package/dist/version.js +9 -3
  290. package/package.json +6 -5
  291. package/dist/channels/lark/credentials-page.d.ts +0 -25
  292. package/dist/channels/lark/credentials-page.js +0 -206
  293. package/dist/channels/lark/onboard.d.ts +0 -52
  294. package/dist/channels/lark/onboard.js +0 -86
  295. package/dist/channels/slack/config-api.d.ts +0 -69
  296. package/dist/channels/slack/config-api.js +0 -162
  297. package/dist/channels/slack/onboard.d.ts +0 -40
  298. package/dist/channels/slack/onboard.js +0 -136
  299. package/dist/channels/slack/onboarding-state.d.ts +0 -31
  300. package/dist/channels/slack/onboarding-state.js +0 -64
  301. package/dist/channels/slack/register-webhook.d.ts +0 -12
  302. package/dist/channels/slack/register-webhook.js +0 -67
  303. package/dist/channels/slack/setup-server.d.ts +0 -32
  304. package/dist/channels/slack/setup-server.js +0 -246
  305. package/dist/cli/add-feishu.d.ts +0 -41
  306. package/dist/cli/add-feishu.js +0 -391
  307. package/dist/cli/add-slack.d.ts +0 -18
  308. package/dist/cli/add-slack.js +0 -216
  309. package/dist/harnesses/pi-durable/parked.d.ts +0 -18
  310. package/dist/harnesses/pi-durable/parked.js +0 -27
@@ -1,246 +0,0 @@
1
- import { randomBytes } from "node:crypto";
2
- import { createServer } from "node:http";
3
- import { field, formCard, openButton, privacy, prose, refusal, sendPage, setupRequestUrl, statusCard, step, submit, } from "../../setup-page.js";
4
- /** Where Slack's App Configuration tokens are generated: the page names the section, and links to the page it is on. */
5
- const SLACK_APPS_URL = "https://api.slack.com/apps";
6
- const TITLE = "Connect Slack — FastAgent";
7
- function tokensForm(error) {
8
- return {
9
- title: TITLE,
10
- card: formCard("Connect Slack", [
11
- step(1, "Generate two tokens in Slack", prose("Under <b>Your App Configuration Tokens</b>, choose <b>Generate Token</b> for your workspace.") +
12
- openButton("Open Slack", SLACK_APPS_URL)),
13
- step(2, "Paste them here", (error ? refusal("Those tokens were not accepted.", ` ${error}`) : "") +
14
- field({
15
- id: "access",
16
- label: "Access Token",
17
- placeholder: "xoxe.xoxp-…",
18
- secret: true,
19
- invalid: error !== undefined,
20
- autofocus: error !== undefined,
21
- }) +
22
- field({
23
- id: "refresh",
24
- label: "Refresh Token",
25
- placeholder: "xoxe-…",
26
- secret: true,
27
- invalid: error !== undefined,
28
- }) +
29
- submit("Create Slack app") +
30
- privacy(), { bare: error === undefined }),
31
- ].join("")),
32
- };
33
- }
34
- /** A page that says where the setup stands. */
35
- const status = (input, title = "FastAgent setup") => ({
36
- title,
37
- card: statusCard(input),
38
- });
39
- /** Temporary onboarding-only responder. */
40
- export async function startSlackSetupServer() {
41
- const nonce = randomBytes(24).toString("hex");
42
- const requestPath = `/slack/setup/${nonce}`;
43
- const redirectPath = `/slack/oauth/${nonce}`;
44
- const tokensPath = `/slack/tokens/${nonce}`;
45
- const nextPath = `/slack/next/${nonce}`;
46
- let takeTokens;
47
- let pasted = false;
48
- let after;
49
- let lastShown;
50
- /** The last page a browser waiting after the pair sees, and when it has seen it (bounded by one refresh window). */
51
- const lastPage = (said, failed) => {
52
- if (!pasted || after !== undefined)
53
- return Promise.resolve();
54
- after = { said, failed };
55
- return new Promise((resolve) => {
56
- const timer = setTimeout(resolve, 5_000);
57
- lastShown = () => {
58
- clearTimeout(timer);
59
- resolve();
60
- };
61
- });
62
- };
63
- let settleOAuth;
64
- const oauth = new Promise((resolve) => {
65
- settleOAuth = resolve;
66
- });
67
- const server = createServer((request, response) => {
68
- const url = setupRequestUrl(request, response);
69
- if (url === undefined)
70
- return;
71
- if (request.method === "GET" && url.pathname === "/health") {
72
- response.end("ok\n");
73
- return;
74
- }
75
- if (request.method === "GET" && url.pathname === redirectPath) {
76
- const callback = {
77
- code: url.searchParams.get("code") ?? undefined,
78
- state: url.searchParams.get("state") ?? undefined,
79
- error: url.searchParams.get("error") ?? undefined,
80
- };
81
- settleOAuth?.(callback);
82
- settleOAuth = undefined;
83
- // The onboarding flow still has to validate state and exchange the code.
84
- sendPage(response, 200, status(callback.error !== undefined
85
- ? {
86
- state: "failed",
87
- heading: "Slack authorization was not approved",
88
- lede: callback.error
89
- ? `OAuth error: ${callback.error}`
90
- : "The callback reported an authorization error.",
91
- next: "Go back to your coding agent to restart setup.",
92
- }
93
- : {
94
- state: "working",
95
- heading: "OAuth response received",
96
- lede: "FastAgent still needs to verify this response and finish the installation.",
97
- next: "Go back to your coding agent to check the result. You can close this tab.",
98
- }));
99
- return;
100
- }
101
- if (url.pathname === tokensPath && (request.method === "GET" || request.method === "POST")) {
102
- if (takeTokens === undefined) {
103
- // Taken already, or not asked for: the page is one-time.
104
- sendPage(response, 404, status({ state: "failed", heading: "This page is no longer in use", next: "Go back to your coding agent." }));
105
- return;
106
- }
107
- if (request.method === "GET") {
108
- sendPage(response, 200, tokensForm());
109
- return;
110
- }
111
- readBody(request, (body) => {
112
- if (body === undefined) {
113
- response.statusCode = 413;
114
- response.end("payload too large\n");
115
- return;
116
- }
117
- const form = new URLSearchParams(body);
118
- try {
119
- takeTokens?.({ access: (form.get("access") ?? "").trim(), refresh: (form.get("refresh") ?? "").trim() });
120
- }
121
- catch (error) {
122
- sendPage(response, 200, tokensForm(error.message));
123
- return;
124
- }
125
- response.statusCode = 303;
126
- response.setHeader("location", nextPath);
127
- response.end();
128
- });
129
- return;
130
- }
131
- if (request.method === "GET" && url.pathname === nextPath) {
132
- if (after === undefined) {
133
- // Says what comes next, so the redirect to Slack's page is no surprise.
134
- sendPage(response, 200, {
135
- ...status({
136
- state: "working",
137
- heading: "Creating your Slack app",
138
- lede: "In a moment Slack will ask you to click Allow.",
139
- }),
140
- head: '<meta http-equiv="refresh" content="2">',
141
- });
142
- }
143
- else if ("url" in after) {
144
- response.statusCode = 303;
145
- response.setHeader("location", after.url);
146
- response.end();
147
- }
148
- else {
149
- sendPage(response, 200, status({
150
- state: after.failed ? "failed" : "done",
151
- heading: after.failed ? "Setup stopped" : "Done",
152
- lede: after.said,
153
- next: "Go back to your coding agent.",
154
- }));
155
- lastShown?.();
156
- }
157
- return;
158
- }
159
- if (request.method === "POST" && url.pathname === requestPath) {
160
- readBody(request, (body) => {
161
- if (body === undefined) {
162
- response.statusCode = 413;
163
- response.end("payload too large\n");
164
- return;
165
- }
166
- try {
167
- const payload = JSON.parse(body);
168
- if (payload.type === "url_verification" && typeof payload.challenge === "string") {
169
- response.setHeader("content-type", "application/json");
170
- response.end(JSON.stringify({ challenge: payload.challenge }));
171
- return;
172
- }
173
- }
174
- catch {
175
- // Fall through: setup never acknowledges normal events or malformed traffic.
176
- }
177
- response.statusCode = 404;
178
- response.end("not found\n");
179
- });
180
- return;
181
- }
182
- response.statusCode = 404;
183
- response.end("not found\n");
184
- });
185
- await new Promise((resolve, reject) => {
186
- server.once("error", reject);
187
- server.listen(0, "127.0.0.1", () => {
188
- server.off("error", reject);
189
- resolve();
190
- });
191
- });
192
- const address = server.address();
193
- if (!address || typeof address === "string") {
194
- server.close();
195
- throw new Error("Slack setup server did not receive a local TCP port");
196
- }
197
- return {
198
- port: address.port,
199
- requestPath,
200
- redirectPath,
201
- tokensPath,
202
- waitForTokens: (check) => new Promise((resolve) => {
203
- takeTokens = (pair) => {
204
- const taken = check(pair);
205
- takeTokens = undefined;
206
- pasted = true;
207
- resolve(taken);
208
- };
209
- }),
210
- next(url) {
211
- after = { url };
212
- },
213
- failed: (message) => lastPage(message, true),
214
- done: (message) => lastPage(message, false),
215
- async waitForOAuth(timeoutMs = 10 * 60_000) {
216
- let timer;
217
- const timeout = new Promise((_, reject) => {
218
- timer = setTimeout(() => reject(new Error("Slack OAuth timed out — re-run `fastagent add slack`")), timeoutMs);
219
- timer.unref();
220
- });
221
- try {
222
- return await Promise.race([oauth, timeout]);
223
- }
224
- finally {
225
- if (timer)
226
- clearTimeout(timer);
227
- }
228
- },
229
- close: () => new Promise((resolve, reject) => {
230
- server.close((error) => (error ? reject(error) : resolve()));
231
- }),
232
- };
233
- }
234
- /** A request's body, or `undefined` past 64 KB: setup traffic is small, and anything larger is not ours. */
235
- function readBody(request, then) {
236
- let body = "";
237
- let tooLarge = false;
238
- request.on("data", (chunk) => {
239
- if (tooLarge)
240
- return;
241
- body += String(chunk);
242
- if (Buffer.byteLength(body) > 64 * 1024)
243
- tooLarge = true;
244
- });
245
- request.on("end", () => then(tooLarge ? undefined : body));
246
- }
@@ -1,41 +0,0 @@
1
- import { type FeishuSubscriptionMode } from "../channels/feishu/setup-mode.ts";
2
- import { type FeishuApi } from "../channels/feishu/feishu-api.ts";
3
- import type { DeclaredChannel } from "../channels/discover.ts";
4
- import { type Person } from "./person.ts";
5
- export interface AgentScopeCheck {
6
- /** Safe to proceed to version publishing now; false means Permissions still needs console or admin work. */
7
- publishReady: boolean;
8
- /** The scopes found missing; empty when the list could not be read (`publishReady` is false then). */
9
- missing: string[];
10
- }
11
- /**
12
- * Check that the app holds what an app of this ingress needs ({@link feishuAppScopes}). A created app asks for them on
13
- * its confirm page, and a tenant may still withhold some (its approval policy, or an app made by hand): those are
14
- * NAMED, with what each one costs, and the console page that requests them is opened pre-filled. Nothing is requested
15
- * through the config API — that needs a scope of its own, which a tenant can withhold just the same.
16
- */
17
- export declare function checkAgentScopes(input: {
18
- kind: "feishu" | "lark";
19
- appId: string;
20
- apiBase: string;
21
- ingress: FeishuSubscriptionMode;
22
- api: Pick<FeishuApi, "listAppScopes">;
23
- note?: (message: string) => void;
24
- /** Who is sent to the console page that requests a missing permission. */
25
- person?: Person;
26
- }): Promise<AgentScopeCheck>;
27
- /** Create or resume the platform app behind `add feishu` / `add lark`. */
28
- export declare function onboardFeishuCloudApp(target: string, kind: "feishu" | "lark", ingress: FeishuSubscriptionMode,
29
- /** The command that continues an interrupted setup: the one that started it. */
30
- rerun?: string,
31
- /** Who takes the steps only a person can: at this terminal, or a coding agent's person through a ticket. */
32
- person?: Person): Promise<Record<string, string> | undefined>;
33
- export declare function feishuAppSecretNames(kind: "feishu" | "lark", ingress: FeishuSubscriptionMode): string[];
34
- interface FeishuAppSetup {
35
- kind: "feishu" | "lark";
36
- ingress: FeishuSubscriptionMode;
37
- }
38
- export declare function unpreparedFeishuApps(channels: readonly DeclaredChannel[], values: ReadonlyMap<string, string>): FeishuAppSetup[];
39
- export declare function assertIndependentFeishuApps(agentDir: string, kinds: readonly ("feishu" | "lark")[], values: ReadonlyMap<string, string>): void;
40
- export declare function prepareFeishuApps(agentDir: string, apps: readonly FeishuAppSetup[], rerun: string, person: Person, onboard?: typeof onboardFeishuCloudApp): Promise<void>;
41
- export {};
@@ -1,391 +0,0 @@
1
- /**
2
- * `fastagent add feishu|lark` app onboarding — the cloud-facing half of runAdd, kept out of cli.ts (which
3
- * self-executes on import), mirroring models-view.ts/auth-view.ts.
4
- */
5
- import { readFile } from "node:fs/promises";
6
- import { isCancel, log as clackLog, password, text as clackText } from "@clack/prompts";
7
- import { bootstrapFeishuVerificationToken } from "../channels/feishu/bootstrap-token.js";
8
- import { FEISHU_APP_CONFIG_SCOPE, feishuAppAddons, feishuAppScopes, feishuScopeRequestUrl, scopeSatisfied, } from "../channels/feishu/setup-mode.js";
9
- import { cloudFor } from "../channels/feishu/cloud.js";
10
- import { createFeishuApi, isFeishuConfigApiMissing, isFeishuCredentialsRefused, isTransientFeishuRegistrationError, } from "../channels/feishu/feishu-api.js";
11
- import { registerFeishuApp } from "../channels/feishu/register-app.js";
12
- import { LarkCredentialsRefused, startLarkCredentialsPage, startLarkVerificationPage, } from "../channels/lark/credentials-page.js";
13
- import { onboardLarkApp } from "../channels/lark/onboard.js";
14
- import { dotEnvPath, loadEnvValues, parseEnvContent } from "../value-file.js";
15
- import { openExternalUrl } from "../open-url.js";
16
- import { terminalPerson } from "./person.js";
17
- import { AGENT_ENVIRONMENT_ENV } from "../paths.js";
18
- import { appendChannelDotEnv } from "../scaffold/add-channel.js";
19
- import { startCloudflareTunnel } from "../tunnel.js";
20
- /**
21
- * Check that the app holds what an app of this ingress needs ({@link feishuAppScopes}). A created app asks for them on
22
- * its confirm page, and a tenant may still withhold some (its approval policy, or an app made by hand): those are
23
- * NAMED, with what each one costs, and the console page that requests them is opened pre-filled. Nothing is requested
24
- * through the config API — that needs a scope of its own, which a tenant can withhold just the same.
25
- */
26
- export async function checkAgentScopes(input) {
27
- const { kind, appId, apiBase, api } = input;
28
- const note = input.note ?? ((message) => console.error(message));
29
- const person = input.person ?? terminalPerson;
30
- const needed = feishuAppScopes(input.ingress);
31
- const requested = needed.map((entry) => entry.request);
32
- let scopes;
33
- try {
34
- scopes = await api.listAppScopes();
35
- }
36
- catch (error) {
37
- const url = feishuScopeRequestUrl(apiBase, appId, requested);
38
- note(`[fastagent] warn: could not read the ${kind} app's permissions: ${String(error)}`);
39
- person.afterwards(`make sure the ${kind} app holds ${requested.join(", ")} before publishing`, url);
40
- return { publishReady: false, missing: [] };
41
- }
42
- // A user-type entry is not the tenant scope the app acts with.
43
- const tenant = scopes.filter((scope) => scope.type === undefined || scope.type === "tenant");
44
- const granted = (name) => tenant.some((scope) => scope.name === name && scope.grantStatus === 1);
45
- const missing = needed.filter((entry) => !scopeSatisfied(entry, granted));
46
- if (missing.length === 0) {
47
- note(`[fastagent] ${kind} app permissions: ${requested.join(", ")} granted`);
48
- return { publishReady: true, missing: [] };
49
- }
50
- // "Not granted", not "not requested": the list omits a scope still under review (measured), so the two look alike.
51
- const describe = (entry) => `\n ${entry.request}: without it ${entry.withoutIt}`;
52
- const url = feishuScopeRequestUrl(apiBase, appId, missing.map((entry) => entry.request));
53
- person.afterwards(`the ${kind} app is not granted:${missing.map(describe).join("")}\n` +
54
- `Tick and enable them on that page (one may already be awaiting a tenant admin's approval), before publishing`, url);
55
- return { publishReady: false, missing: missing.map((entry) => entry.request) };
56
- }
57
- /** Create or resume the platform app behind `add feishu` / `add lark`. */
58
- export async function onboardFeishuCloudApp(target, kind, ingress,
59
- /** The command that continues an interrupted setup: the one that started it. */
60
- rerun = `fastagent add ${kind}${ingress === "webhook" ? " --ingress webhook" : ""}`,
61
- /** Who takes the steps only a person can: at this terminal, or a coding agent's person through a ticket. */
62
- person = terminalPerson) {
63
- const env = dotEnvPath(target); // the file actually written — never the default spelling
64
- const { envPrefix, apiBase, capabilities } = cloudFor(kind);
65
- const requiredNames = feishuAppSecretNames(kind, ingress);
66
- const existing = await activeDotEnvValues(target, requiredNames);
67
- if (process.env[AGENT_ENVIRONMENT_ENV] === "production") {
68
- assertIndependentFeishuApps(target, [kind], new Map(Object.entries(existing)));
69
- }
70
- if (Object.keys(existing).length === requiredNames.length) {
71
- console.error(`[fastagent] ${requiredNames.join("/")} already set in ${env} — keeping them`);
72
- // WebSocket still needs its console mode/publish guidance.
73
- if (ingress === "webhook") {
74
- const appId = existing[`${envPrefix}_APP_ID`];
75
- const appSecret = existing[`${envPrefix}_APP_SECRET`];
76
- await checkAgentScopes({
77
- kind,
78
- appId,
79
- apiBase,
80
- ingress,
81
- api: createFeishuApi({ kind, baseUrl: apiBase, appId, appSecret }),
82
- person,
83
- });
84
- return undefined;
85
- }
86
- }
87
- if (capabilities.appCreation === "scan-to-create") {
88
- await createFeishuAppFlow(target, existing, ingress, rerun, person);
89
- return undefined;
90
- }
91
- // guided-console (lark): the intl cloud cannot complete the bound device flow — collect + validate.
92
- const credentials = await onboardLarkApp(person === terminalPerson ? larkAtTerminal : larkThrough(person, target), {
93
- existing,
94
- ingress,
95
- rerun,
96
- verifyCredentials: async (appId, appSecret) => {
97
- if (process.env[AGENT_ENVIRONMENT_ENV] === "production") {
98
- assertIndependentFeishuApps(target, ["lark"], new Map([["LARK_APP_ID", appId]]));
99
- }
100
- await createFeishuApi({ kind: "lark", baseUrl: apiBase, appId, appSecret }).verifyCredentials();
101
- console.error(`[fastagent] Lark App ID / Secret verified`);
102
- },
103
- // The same boundary `add feishu` keeps: a pair given just now is written before any step that can fail, so a
104
- // rerun finds it and carries on, instead of asking for it again.
105
- keep: async (appId, appSecret) => {
106
- const kept = {
107
- LARK_APP_ID: appId,
108
- LARK_APP_SECRET: appSecret,
109
- ...(ingress === "webhook" ? { LARK_VERIFICATION_TOKEN: "" } : {}),
110
- };
111
- await appendChannelDotEnv(target, "lark", kept, Object.keys(kept), ingress);
112
- console.error(`[fastagent] wrote LARK_APP_ID, LARK_APP_SECRET to ${env}`);
113
- },
114
- bootstrapWebhook: async (appId, appSecret) => {
115
- const api = createFeishuApi({ kind: "lark", baseUrl: apiBase, appId, appSecret });
116
- console.error(`[fastagent] trying Lark's webhook-mode + Verification-Token bootstrap (temporary tunnel)…`);
117
- try {
118
- const token = await bootstrapFeishuVerificationToken({
119
- api,
120
- appId,
121
- kind: "lark",
122
- startTunnel: (port) => startCloudflareTunnel(port),
123
- onTunnelReady: (url) => console.error(`[fastagent] temporary tunnel ready → ${url}; registering webhook mode now…`),
124
- onPatchRetry: ({ error, attempt, attempts, retryMs }) => console.error(`[fastagent] Lark could not validate the fresh tunnel yet (${String(error)}); retrying PATCH ${attempt + 1}/${attempts} in ${Math.round(retryMs / 1000)}s…`),
125
- // A route-level 404 is definitive, not edge weather: fall back immediately.
126
- shouldRetryPatch: (error) => !isFeishuConfigApiMissing(error) && isTransientFeishuRegistrationError(error),
127
- });
128
- console.error(`[fastagent] Lark Verification Token captured; Subscription mode changed to webhook in the app draft`);
129
- return { token };
130
- }
131
- catch (error) {
132
- if (!isFeishuConfigApiMissing(error))
133
- throw error;
134
- const manualReason = "This Lark app returned HTTP 404 for the application-config API, so automatic mode/token bootstrap is unavailable.";
135
- console.error(`[fastagent] ${manualReason}`);
136
- return { manualReason };
137
- }
138
- },
139
- });
140
- await checkAgentScopes({
141
- kind: "lark",
142
- appId: credentials.LARK_APP_ID,
143
- apiBase,
144
- ingress,
145
- person,
146
- api: createFeishuApi({
147
- kind: "lark",
148
- baseUrl: apiBase,
149
- appId: credentials.LARK_APP_ID,
150
- appSecret: credentials.LARK_APP_SECRET,
151
- }),
152
- });
153
- return Object.fromEntries(Object.entries(credentials).filter((entry) => typeof entry[1] === "string"));
154
- }
155
- /** Lark's guided console at a terminal: what to open, and the values asked for there. */
156
- const larkAtTerminal = {
157
- openUrl(url, action) {
158
- clackLog.info(`${action}. Opening ${url}`);
159
- openExternalUrl(url);
160
- },
161
- note: (message) => clackLog.info(message),
162
- async prompt(message, opts) {
163
- const result = opts?.hidden ? await password({ message }) : await clackText({ message });
164
- return isCancel(result) ? undefined : result;
165
- },
166
- };
167
- async function waitForLarkPage(person, page, action) {
168
- const port = new URL(page.url).port;
169
- try {
170
- person.waitsOn(page.url, action +
171
- " If the CLI runs remotely, forward this loopback port to the same local port " +
172
- `(SSH: ssh -N -L ${port}:127.0.0.1:${port} user@host), then open the same URL in your browser. ` +
173
- "Keep the forwarding session open; never paste secrets into the conversation.");
174
- const value = await page.credentials;
175
- person.carriesOn();
176
- return value;
177
- }
178
- finally {
179
- await page.close();
180
- }
181
- }
182
- function larkThrough(person, target) {
183
- return {
184
- note: (message) => console.error(`[fastagent] ${message}`),
185
- openUrl: (url, action) => person.afterwards(action, url),
186
- async prompt() {
187
- throw new Error("Lark setup must collect secrets on its page, not through terminal prompts");
188
- },
189
- async verificationToken(consoleUrl) {
190
- const page = await startLarkVerificationPage({
191
- consoleUrl,
192
- save: async (token) => {
193
- await appendChannelDotEnv(target, "lark", { LARK_VERIFICATION_TOKEN: token }, ["LARK_VERIFICATION_TOKEN"]);
194
- },
195
- });
196
- return waitForLarkPage(person, page, "Paste the Lark Verification Token on this page.");
197
- },
198
- async collect({ consoleUrl, check }) {
199
- const page = await startLarkCredentialsPage({
200
- consoleUrl,
201
- check: async ({ appId, appSecret }) => {
202
- try {
203
- await check(appId, appSecret);
204
- }
205
- catch (error) {
206
- // Only a known credential refusal is safe to retry on the form.
207
- if (!isFeishuCredentialsRefused(error))
208
- throw error;
209
- throw new LarkCredentialsRefused("Lark refused that App ID or Secret.", {
210
- fix: "Copy both again from Credentials & Basic Info.",
211
- why: `Lark said: ${error instanceof Error ? error.message : String(error)}`,
212
- });
213
- }
214
- },
215
- });
216
- return waitForLarkPage(person, page, "Create the Lark app, then paste its App ID and App Secret on this page.");
217
- },
218
- };
219
- }
220
- /** The scan-to-create flow `add feishu` runs by default. */
221
- async function createFeishuAppFlow(target, existing, ingress, rerun, person) {
222
- const env = dotEnvPath(target); // the file actually written — never the default spelling
223
- const { apiBase } = cloudFor("feishu");
224
- let appId = existing.FEISHU_APP_ID;
225
- let appSecret = existing.FEISHU_APP_SECRET;
226
- if (appId && appSecret) {
227
- console.error(ingress === "webhook"
228
- ? `[fastagent] resuming Feishu app ${appId} from ${env} to capture its missing Verification Token`
229
- : `[fastagent] reusing Feishu app ${appId} from ${env} for WebSocket ingress`);
230
- }
231
- else {
232
- console.error(`[fastagent] creating the Feishu app (confirm in the app)…`);
233
- const app = await registerFeishuApp({
234
- name: process.env[AGENT_ENVIRONMENT_ENV] === "production" ? "{user}'s production agent" : "{user}'s agent",
235
- desc: "Served by fastagent",
236
- // The agent template alone is not enough to SERVE, nor to hear a group (see feishuAppAddons).
237
- addons: feishuAppAddons(ingress),
238
- onVerificationUrl: ({ url, expiresInS }) => person.waitsOn(url, "confirm the new Feishu app (in a browser, or in Feishu, or as a QR code)", Date.now() + expiresInS * 1000),
239
- });
240
- person.carriesOn();
241
- console.error(`[fastagent] app created: ${app.appId}${app.tenantBrand ? ` (${app.tenantBrand} tenant)` : ""}`);
242
- // A cross-brand confirmation should be impossible (each confirm page refuses the other brand's code).
243
- if (app.tenantBrand && app.tenantBrand !== "feishu") {
244
- throw new Error(`the confirming account is a ${app.tenantBrand} tenant, but this is \`add feishu\` — run \`fastagent add ${app.tenantBrand}\` instead`);
245
- }
246
- appId = app.appId;
247
- appSecret = app.appSecret;
248
- // IRREVERSIBLE BOUNDARY: the remote app now exists and its one-time Secret is in memory.
249
- const staged = {
250
- FEISHU_APP_ID: appId,
251
- FEISHU_APP_SECRET: appSecret,
252
- ...(ingress === "webhook" ? { FEISHU_VERIFICATION_TOKEN: "" } : {}),
253
- };
254
- await appendChannelDotEnv(target, "feishu", staged, Object.keys(staged), ingress);
255
- console.error(ingress === "webhook"
256
- ? `[fastagent] wrote FEISHU_APP_ID, FEISHU_APP_SECRET to ${env} before Token bootstrap`
257
- : `[fastagent] wrote FEISHU_APP_ID, FEISHU_APP_SECRET to ${env}`);
258
- }
259
- const groupSetup = await checkAgentScopes({
260
- kind: "feishu",
261
- appId,
262
- apiBase,
263
- ingress,
264
- api: createFeishuApi({ kind: "feishu", baseUrl: apiBase, appId, appSecret }),
265
- person,
266
- });
267
- if (ingress === "websocket") {
268
- const versionUrl = `${apiBase}/app/${appId}/version`;
269
- const choose = "WebSocket ingress needs no Verification Token, Encrypt Key, Request URL, or tunnel: choose long connection in " +
270
- "Events & Callbacks";
271
- if (groupSetup.publishReady)
272
- person.afterwards(`${choose}, then CREATE + PUBLISH a version`, versionUrl);
273
- else
274
- person.afterwards(`${choose}, finish the permission work above, then CREATE + PUBLISH a version: ${versionUrl}`);
275
- return;
276
- }
277
- // The webhook channel authenticates plaintext events by the platform-generated Verification Token.
278
- const tokenVar = "FEISHU_VERIFICATION_TOKEN";
279
- const api = createFeishuApi({ baseUrl: apiBase, appId, appSecret });
280
- let token;
281
- let webhookModeChanged = false;
282
- try {
283
- const cfg = await api.getAppConfig(appId);
284
- token = cfg.verificationToken;
285
- }
286
- catch (error) {
287
- // Best-effort read; the bootstrap below is the real path. Said, because that path can take minutes and the reason
288
- // it was needed would otherwise be invisible.
289
- console.error(`[fastagent] could not read the app's Verification Token directly: ${error.message}`);
290
- }
291
- // The bootstrap registers a Request URL through the config scope; without it every attempt is refused, so it is not
292
- // tried, and the reason is the one the permission check above already named.
293
- const configWithheld = groupSetup.missing.includes(FEISHU_APP_CONFIG_SCOPE);
294
- if (!token && configWithheld) {
295
- console.error(`[fastagent] not capturing the Verification Token: the app is not granted ${FEISHU_APP_CONFIG_SCOPE}. Choose webhook ` +
296
- `in Events & Callbacks and set the Request URL there yourself (dev --tunnel and deploy --run cannot ` +
297
- `either), or have the permission approved and re-run \`${rerun}\`.`);
298
- }
299
- else if (!token) {
300
- console.error(`[fastagent] capturing the Verification Token — a throwaway webhook registration delivers it (spinning up a temporary tunnel; can take a few minutes on a slow edge)…`);
301
- try {
302
- token = await bootstrapFeishuVerificationToken({
303
- api,
304
- appId,
305
- startTunnel: (port) => startCloudflareTunnel(port),
306
- // Only edge weather is retried; a refusal (scope, auth, app under review) is reported as itself.
307
- shouldRetryPatch: isTransientFeishuRegistrationError,
308
- });
309
- webhookModeChanged = true;
310
- console.error(`[fastagent] Verification Token captured`);
311
- }
312
- catch (e) {
313
- console.error(`[fastagent] warn: could not capture the Verification Token: ${String(e)} — finish this app with the manual copy below`);
314
- }
315
- }
316
- if (token) {
317
- // Persist the second credential stage immediately too.
318
- const staged = await appendChannelDotEnv(target, "feishu", { [tokenVar]: token }, [tokenVar]);
319
- console.error(`[fastagent] wrote ${staged.written.join(", ")} to ${env}`);
320
- }
321
- else {
322
- person.afterwards(`copy the Verification Token: developer console → Events & Callbacks → Encryption Strategy → Verification ` +
323
- `Token → ${tokenVar} in ${env}`);
324
- }
325
- if (webhookModeChanged) {
326
- // The bootstrap's PATCH flipped event mode in the DRAFT.
327
- const versionUrl = `${apiBase}/app/${appId}/version`;
328
- const publish = "CREATE + PUBLISH a version (self-approved): the switch to webhook mode takes effect on publish";
329
- if (groupSetup.publishReady)
330
- person.afterwards(publish, versionUrl);
331
- else
332
- person.afterwards(`after the permission work above, ${publish}: ${versionUrl}`);
333
- }
334
- }
335
- /**
336
- * Active agent `.env` (`.secrets/.env`) values for the requested names — decided by THE .env parser, so this check can
337
- * never disagree with what `loadDotEnv` reads.
338
- */
339
- async function activeDotEnvValues(dir, names) {
340
- let content;
341
- try {
342
- content = await readFile(dotEnvPath(dir), "utf8");
343
- }
344
- catch (e) {
345
- if (e.code === "ENOENT")
346
- return {};
347
- throw e;
348
- }
349
- const parsed = parseEnvContent(content);
350
- return Object.fromEntries(names.flatMap((name) => {
351
- const value = parsed.get(name)?.trim();
352
- return value ? [[name, value]] : [];
353
- }));
354
- }
355
- export function feishuAppSecretNames(kind, ingress) {
356
- const prefix = cloudFor(kind).envPrefix;
357
- return [
358
- `${prefix}_APP_ID`,
359
- `${prefix}_APP_SECRET`,
360
- ...(ingress === "webhook" ? [`${prefix}_VERIFICATION_TOKEN`] : []),
361
- ];
362
- }
363
- export function unpreparedFeishuApps(channels, values) {
364
- return ["feishu", "lark"].flatMap((kind) => {
365
- const channel = channels.find((entry) => entry.name === kind);
366
- if (!channel)
367
- return [];
368
- const ingress = channel.ingress === "webhook" ? "webhook" : "websocket";
369
- return feishuAppSecretNames(kind, ingress).some((name) => !values.get(name)?.trim()) ? [{ kind, ingress }] : [];
370
- });
371
- }
372
- export function assertIndependentFeishuApps(agentDir, kinds, values) {
373
- const devFile = dotEnvPath(agentDir, {});
374
- const devValues = loadEnvValues(devFile);
375
- for (const kind of kinds) {
376
- const name = `${cloudFor(kind).envPrefix}_APP_ID`;
377
- const appId = values.get(name)?.trim();
378
- if (appId && appId === devValues.get(name)?.trim()) {
379
- throw new Error(`${kind}: dev and production use the same app (${name}) — create a separate production app in ` +
380
- `${dotEnvPath(agentDir)}; copying dev credentials would take its messages away`);
381
- }
382
- }
383
- }
384
- export async function prepareFeishuApps(agentDir, apps, rerun, person, onboard = onboardFeishuCloudApp) {
385
- for (const { kind, ingress } of apps) {
386
- console.error(`[fastagent] ${kind}: preparing the production app for ${ingress} in ${dotEnvPath(agentDir)}`);
387
- const created = await onboard(agentDir, kind, ingress, rerun, person);
388
- if (created)
389
- await appendChannelDotEnv(agentDir, kind, created, Object.keys(created), ingress);
390
- }
391
- }