@fastagent-sh/fastagent 0.13.0 → 0.15.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/README.md +5 -5
- package/dist/agent.d.ts +20 -0
- package/dist/agent.js +9 -0
- package/dist/channels/context-buffer.d.ts +30 -0
- package/dist/channels/context-buffer.js +98 -0
- package/dist/channels/control.d.ts +28 -0
- package/dist/channels/control.js +214 -0
- package/dist/channels/feishu/card.js +1 -1
- package/dist/channels/feishu/context-buffer.d.ts +43 -0
- package/dist/channels/feishu/context-buffer.js +72 -0
- package/dist/channels/feishu/crypto.d.ts +4 -2
- package/dist/channels/feishu/crypto.js +4 -2
- package/dist/channels/feishu/feishu-api.d.ts +15 -7
- package/dist/channels/feishu/feishu-api.js +22 -4
- package/dist/channels/feishu/feishu.d.ts +38 -16
- package/dist/channels/feishu/feishu.js +286 -151
- package/dist/channels/feishu/invoke-turn.d.ts +24 -31
- package/dist/channels/feishu/invoke-turn.js +61 -62
- package/dist/channels/feishu/model.d.ts +98 -0
- package/dist/channels/feishu/model.js +9 -0
- package/dist/channels/feishu/normalize.d.ts +23 -0
- package/dist/channels/feishu/normalize.js +132 -0
- package/dist/channels/feishu/owned-threads.d.ts +7 -0
- package/dist/channels/feishu/owned-threads.js +47 -0
- package/dist/channels/feishu/parse.d.ts +21 -103
- package/dist/channels/feishu/parse.js +35 -145
- package/dist/channels/feishu/preview.d.ts +4 -7
- package/dist/channels/feishu/preview.js +26 -142
- package/dist/channels/feishu/register-app.d.ts +2 -1
- package/dist/channels/feishu/register-webhook.d.ts +2 -1
- package/dist/channels/feishu/scaffold/channel.ts +10 -3
- package/dist/channels/feishu/setup-mode.d.ts +8 -0
- package/dist/channels/feishu/setup-mode.js +2 -0
- package/dist/channels/feishu/ws-ingress.d.ts +28 -0
- package/dist/channels/feishu/ws-ingress.js +136 -0
- package/dist/channels/github/github.js +8 -6
- package/dist/channels/http.d.ts +14 -0
- package/dist/channels/http.js +35 -2
- package/dist/channels/invoke-turn-kit.d.ts +65 -0
- package/dist/channels/invoke-turn-kit.js +87 -0
- package/dist/channels/lark/lark.d.ts +4 -2
- package/dist/channels/lark/lark.js +4 -1
- package/dist/channels/lark/onboard.d.ts +8 -4
- package/dist/channels/lark/onboard.js +8 -0
- package/dist/channels/lark/scaffold/channel.ts +10 -3
- package/dist/channels/preview-kit.d.ts +109 -0
- package/dist/channels/preview-kit.js +183 -0
- package/dist/channels/seen.d.ts +5 -0
- package/dist/channels/seen.js +35 -0
- package/dist/channels/slack/bot-auth.d.ts +15 -0
- package/dist/channels/slack/bot-auth.js +146 -0
- package/dist/channels/slack/config-api.d.ts +60 -0
- package/dist/channels/slack/config-api.js +149 -0
- package/dist/channels/slack/context-buffer.d.ts +24 -0
- package/dist/channels/slack/context-buffer.js +37 -0
- package/dist/channels/slack/invoke-turn.d.ts +19 -0
- package/dist/channels/slack/invoke-turn.js +63 -0
- package/dist/channels/slack/manifest.d.ts +49 -0
- package/dist/channels/slack/manifest.js +69 -0
- package/dist/channels/slack/model.d.ts +67 -0
- package/dist/channels/slack/model.js +2 -0
- package/dist/channels/slack/onboard.d.ts +41 -0
- package/dist/channels/slack/onboard.js +120 -0
- package/dist/channels/slack/onboarding-state.d.ts +31 -0
- package/dist/channels/slack/onboarding-state.js +69 -0
- package/dist/channels/slack/owned-threads.d.ts +6 -0
- package/dist/channels/slack/owned-threads.js +43 -0
- package/dist/channels/slack/parse.d.ts +23 -0
- package/dist/channels/slack/parse.js +81 -0
- package/dist/channels/slack/preview.d.ts +24 -0
- package/dist/channels/slack/preview.js +359 -0
- package/dist/channels/slack/reaction.d.ts +24 -0
- package/dist/channels/slack/reaction.js +62 -0
- package/dist/channels/slack/register-webhook.d.ts +10 -0
- package/dist/channels/slack/register-webhook.js +49 -0
- package/dist/channels/slack/scaffold/channel.ts +33 -0
- package/dist/channels/slack/scaffold/slack-send.ts +171 -0
- package/dist/channels/slack/setup-server.d.ts +17 -0
- package/dist/channels/slack/setup-server.js +103 -0
- package/dist/channels/slack/slack-api.d.ts +77 -0
- package/dist/channels/slack/slack-api.js +415 -0
- package/dist/channels/slack/slack.d.ts +58 -0
- package/dist/channels/slack/slack.js +451 -0
- package/dist/channels/slack/welcomed.d.ts +5 -0
- package/dist/channels/slack/welcomed.js +32 -0
- package/dist/channels/state.js +3 -3
- package/dist/channels/stop-command.d.ts +6 -0
- package/dist/channels/stop-command.js +36 -0
- package/dist/channels/tasks.d.ts +13 -0
- package/dist/channels/tasks.js +10 -0
- package/dist/channels/telegram/context-buffer.d.ts +8 -17
- package/dist/channels/telegram/context-buffer.js +6 -85
- package/dist/channels/telegram/invoke-turn.d.ts +5 -22
- package/dist/channels/telegram/invoke-turn.js +11 -58
- package/dist/channels/telegram/preview.d.ts +4 -7
- package/dist/channels/telegram/preview.js +24 -142
- package/dist/channels/telegram/telegram.js +23 -9
- package/dist/channels/{feishu/text.d.ts → text.d.ts} +1 -1
- package/dist/channels/{feishu/text.js → text.js} +1 -1
- package/dist/channels/turn-queue.js +1 -1
- package/dist/channels/turn-store.d.ts +1 -1
- package/dist/channels/turn-store.js +2 -3
- package/dist/cli/add-feishu.d.ts +27 -0
- package/dist/{cli-add-feishu.js → cli/add-feishu.js} +167 -44
- package/dist/cli/add-slack.d.ts +10 -0
- package/dist/cli/add-slack.js +204 -0
- package/dist/{cli-auth.d.ts → cli/auth-view.d.ts} +1 -1
- package/dist/{cli-auth.js → cli/auth-view.js} +1 -1
- package/dist/cli/commands/add.d.ts +13 -0
- package/dist/cli/commands/add.js +274 -0
- package/dist/cli/commands/attach.d.ts +82 -0
- package/dist/cli/commands/attach.js +559 -0
- package/dist/cli/commands/chat.d.ts +4 -0
- package/dist/cli/commands/chat.js +21 -0
- package/dist/cli/commands/deploy.d.ts +15 -0
- package/dist/cli/commands/deploy.js +394 -0
- package/dist/cli/commands/dev.d.ts +11 -0
- package/dist/cli/commands/dev.js +82 -0
- package/dist/cli/commands/fire.d.ts +7 -0
- package/dist/cli/commands/fire.js +45 -0
- package/dist/cli/commands/info.d.ts +7 -0
- package/dist/cli/commands/info.js +108 -0
- package/dist/cli/commands/init.d.ts +8 -0
- package/dist/cli/commands/init.js +81 -0
- package/dist/cli/commands/invoke.d.ts +7 -0
- package/dist/cli/commands/invoke.js +28 -0
- package/dist/cli/commands/login.d.ts +6 -0
- package/dist/cli/commands/login.js +52 -0
- package/dist/cli/commands/models.d.ts +1 -0
- package/dist/cli/commands/models.js +15 -0
- package/dist/cli/commands/schedule.d.ts +12 -0
- package/dist/cli/commands/schedule.js +89 -0
- package/dist/cli/commands/start.d.ts +10 -0
- package/dist/cli/commands/start.js +97 -0
- package/dist/cli/commands/tool.d.ts +1 -0
- package/dist/cli/commands/tool.js +38 -0
- package/dist/cli/fail.d.ts +17 -0
- package/dist/cli/fail.js +32 -0
- package/dist/{invoke-stream.d.ts → cli/invoke-stream.d.ts} +1 -1
- package/dist/{invoke-stream.js → cli/invoke-stream.js} +4 -0
- package/dist/cli/kernel.d.ts +90 -0
- package/dist/cli/kernel.js +190 -0
- package/dist/cli/models-view.d.ts +21 -0
- package/dist/cli/models-view.js +66 -0
- package/dist/cli/program.d.ts +11 -0
- package/dist/cli/program.js +479 -0
- package/dist/cli/serve.d.ts +48 -0
- package/dist/cli/serve.js +248 -0
- package/dist/cli/shared.d.ts +44 -0
- package/dist/cli/shared.js +246 -0
- package/dist/cli.js +8 -1329
- package/dist/collect.d.ts +14 -3
- package/dist/collect.js +24 -0
- package/dist/core.d.ts +3 -1
- package/dist/core.js +2 -0
- package/dist/deploy/docker/plan.d.ts +45 -0
- package/dist/deploy/docker/plan.js +141 -0
- package/dist/deploy/docker/run.d.ts +40 -0
- package/dist/deploy/docker/run.js +126 -0
- package/dist/deploy/fly/plan.d.ts +3 -1
- package/dist/deploy/fly/plan.js +12 -7
- package/dist/deploy/fly/run.d.ts +5 -4
- package/dist/deploy/fly/run.js +16 -5
- package/dist/deploy/preflight.d.ts +10 -4
- package/dist/deploy/preflight.js +57 -12
- package/dist/deploy/railway/plan.d.ts +3 -1
- package/dist/deploy/railway/plan.js +12 -4
- package/dist/deploy/railway/run.d.ts +5 -4
- package/dist/deploy/railway/run.js +15 -5
- package/dist/deploy/runner.d.ts +6 -2
- package/dist/deploy/runner.js +1 -0
- package/dist/deploy/secrets.d.ts +2 -1
- package/dist/deploy/secrets.js +23 -3
- package/dist/dev-supervisor.d.ts +0 -2
- package/dist/dev-supervisor.js +7 -3
- package/dist/engines/pi/auth.js +160 -46
- package/dist/engines/pi/channel.d.ts +22 -16
- package/dist/engines/pi/channel.js +90 -60
- package/dist/engines/pi/chat.d.ts +4 -16
- package/dist/engines/pi/chat.js +8 -188
- package/dist/engines/pi/config.d.ts +23 -9
- package/dist/engines/pi/config.js +35 -5
- package/dist/engines/pi/create.d.ts +36 -7
- package/dist/engines/pi/create.js +63 -22
- package/dist/engines/pi/harness.d.ts +65 -1
- package/dist/engines/pi/harness.js +166 -2
- package/dist/engines/pi/invoke.d.ts +56 -3
- package/dist/engines/pi/invoke.js +340 -20
- package/dist/engines/pi/login.d.ts +11 -0
- package/dist/engines/pi/login.js +17 -5
- package/dist/engines/pi/models.d.ts +56 -10
- package/dist/engines/pi/models.js +61 -23
- package/dist/engines/pi/search-tools.d.ts +10 -0
- package/dist/engines/pi/search-tools.js +138 -0
- package/dist/engines/pi/session-builder.d.ts +16 -0
- package/dist/engines/pi/session-builder.js +308 -0
- package/dist/engines/pi/session-control.d.ts +50 -0
- package/dist/engines/pi/session-control.js +604 -0
- package/dist/engines/pi/sessions.d.ts +17 -2
- package/dist/engines/pi/sessions.js +9 -0
- package/dist/engines/pi/tool-context.d.ts +46 -11
- package/dist/engines/pi/tool-context.js +11 -9
- package/dist/engines/pi/tool.d.ts +35 -6
- package/dist/engines/pi/tool.js +47 -1
- package/dist/engines/pi/wake-tool.d.ts +0 -3
- package/dist/engines/pi/wake-tool.js +9 -7
- package/dist/engines/pi/workspace.d.ts +56 -1
- package/dist/engines/pi/workspace.js +75 -16
- package/dist/feishu.d.ts +1 -1
- package/dist/feishu.js +1 -1
- package/dist/host/node.d.ts +23 -6
- package/dist/host/node.js +5 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lark.d.ts +1 -1
- package/dist/lark.js +1 -1
- package/dist/observe.js +3 -0
- package/dist/pi.d.ts +6 -4
- package/dist/pi.js +2 -1
- package/dist/scaffold/add-channel.d.ts +19 -6
- package/dist/scaffold/add-channel.js +127 -16
- package/dist/scaffold/templates/fastagent.config.mjs +5 -3
- package/dist/schedule/wakeups.d.ts +0 -3
- package/dist/schedule/wakeups.js +1 -1
- package/dist/session-remote.d.ts +53 -0
- package/dist/session-remote.js +336 -0
- package/dist/session.d.ts +265 -0
- package/dist/session.js +37 -0
- package/dist/slack.d.ts +2 -0
- package/dist/slack.js +2 -0
- package/dist/tunnel.d.ts +7 -4
- package/dist/tunnel.js +21 -10
- package/package.json +22 -7
- package/dist/channels/feishu/seen.d.ts +0 -5
- package/dist/channels/feishu/seen.js +0 -47
- package/dist/cli-add-feishu.d.ts +0 -8
- package/dist/cli-models.d.ts +0 -11
- package/dist/cli-models.js +0 -20
|
@@ -1,27 +1,88 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `fastagent add feishu|lark` app onboarding — the cloud-facing half of runAdd, kept out of cli.ts
|
|
3
|
-
* (which self-executes on import), mirroring
|
|
3
|
+
* (which self-executes on import), mirroring models-view.ts/auth-view.ts. The POLICY already lives in
|
|
4
4
|
* testable modules (register-app.ts, bootstrap-token.ts, lark/onboard.ts); this layer is the terminal
|
|
5
5
|
* wiring: clack prompts, .env staging, browser opens, progress lines.
|
|
6
6
|
*
|
|
7
|
-
* Feishu (scan-to-create): the device flow creates the app
|
|
8
|
-
* irreversible
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* webhook/token bootstrap, and fall back to a manual token prompt on the definitive config-route 404.
|
|
7
|
+
* Feishu (scan-to-create): the device flow creates the app and persists App ID/Secret at the
|
|
8
|
+
* irreversible boundary. WebSocket stops there; webhook additionally captures the Verification Token
|
|
9
|
+
* over a throwaway tunnel. Lark uses the unbound launcher + credential validation, then either stops
|
|
10
|
+
* for WebSocket or probes the same webhook/token bootstrap with the config-route-404 manual fallback.
|
|
12
11
|
*/
|
|
13
12
|
import { readFile } from "node:fs/promises";
|
|
14
13
|
import { join } from "node:path";
|
|
15
14
|
import { isCancel, log as clackLog, password, text as clackText } from "@clack/prompts";
|
|
16
|
-
import { bootstrapFeishuVerificationToken } from "
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
15
|
+
import { bootstrapFeishuVerificationToken } from "../channels/feishu/bootstrap-token.js";
|
|
16
|
+
import { FEISHU_GROUP_CONTEXT_SCOPE, } from "../channels/feishu/setup-mode.js";
|
|
17
|
+
import { cloudFor } from "../channels/feishu/cloud.js";
|
|
18
|
+
import { createFeishuApi, isFeishuConfigApiMissing, isTransientFeishuRegistrationError, } from "../channels/feishu/feishu-api.js";
|
|
19
|
+
import { registerFeishuApp } from "../channels/feishu/register-app.js";
|
|
20
|
+
import { onboardLarkApp } from "../channels/lark/onboard.js";
|
|
21
|
+
import { parseEnvContent } from "../env.js";
|
|
22
|
+
import { openExternalUrl } from "../open-url.js";
|
|
23
|
+
import { appendChannelDotEnv } from "../scaffold/add-channel.js";
|
|
24
|
+
import { startCloudflareTunnel } from "../tunnel.js";
|
|
25
|
+
export async function configureGroupBehavior(input) {
|
|
26
|
+
const { kind, appId, apiBase, api, behavior, explicit } = input;
|
|
27
|
+
const note = input.note ?? ((message) => console.error(message));
|
|
28
|
+
const openUrl = input.openUrl ?? openExternalUrl;
|
|
29
|
+
let scopes;
|
|
30
|
+
let inspected = true;
|
|
31
|
+
try {
|
|
32
|
+
scopes = await api.listAppScopes();
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
note(`[fastagent] warn: could not inspect ${kind} group-message permission: ${String(error)} — check Permissions & Scopes manually`);
|
|
36
|
+
inspected = false;
|
|
37
|
+
scopes = [];
|
|
38
|
+
}
|
|
39
|
+
const groupScope = scopes.find((scope) => scope.name === FEISHU_GROUP_CONTEXT_SCOPE && (scope.type === undefined || scope.type === "tenant"));
|
|
40
|
+
if (behavior === "mentions") {
|
|
41
|
+
if (!inspected) {
|
|
42
|
+
const permissionUrl = `${apiBase}/app/${encodeURIComponent(appId)}/permission`;
|
|
43
|
+
note(`[fastagent] mention-only scope state could not be verified — check Permissions before publishing. Opening ${permissionUrl}`);
|
|
44
|
+
openUrl(permissionUrl);
|
|
45
|
+
return { publishReady: false };
|
|
46
|
+
}
|
|
47
|
+
if (groupScope?.grantStatus === 1) {
|
|
48
|
+
const permissionUrl = `${apiBase}/app/${encodeURIComponent(appId)}/permission`;
|
|
49
|
+
note(`[fastagent] warn: mention-only was selected, but ${FEISHU_GROUP_CONTEXT_SCOPE} is already granted — remove it before publishing a new version to restore least-privilege platform delivery. Opening ${permissionUrl}`);
|
|
50
|
+
openUrl(permissionUrl);
|
|
51
|
+
return { publishReady: false };
|
|
52
|
+
}
|
|
53
|
+
note(`[fastagent] group behavior: mention-only — bare managed-thread replies and group context buffering are disabled`);
|
|
54
|
+
return { publishReady: true };
|
|
55
|
+
}
|
|
56
|
+
note(`[fastagent] group behavior: context-aware (recommended) — ${kind} will deliver all group messages; ` +
|
|
57
|
+
`FastAgent invokes @Agent + bare managed-thread replies and durably buffers other discussion`);
|
|
58
|
+
if (groupScope?.grantStatus === 1) {
|
|
59
|
+
note(`[fastagent] ${FEISHU_GROUP_CONTEXT_SCOPE} is already granted`);
|
|
60
|
+
return { publishReady: true };
|
|
61
|
+
}
|
|
62
|
+
const permissionUrl = `${apiBase}/app/${encodeURIComponent(appId)}/permission`;
|
|
63
|
+
if (groupScope) {
|
|
64
|
+
note(`[fastagent] ${FEISHU_GROUP_CONTEXT_SCOPE} is awaiting approval — complete tenant-admin approval before publishing. Opening ${permissionUrl}`);
|
|
65
|
+
openUrl(permissionUrl);
|
|
66
|
+
return { publishReady: false };
|
|
67
|
+
}
|
|
68
|
+
if (!explicit) {
|
|
69
|
+
// Defaulted, not chosen: report the gap and how to opt in, but leave the app's requested
|
|
70
|
+
// permission set untouched (a scripted re-run must not silently escalate a mention-only app).
|
|
71
|
+
note(`[fastagent] ${FEISHU_GROUP_CONTEXT_SCOPE} is not granted (or could not be verified) — group behavior was ` +
|
|
72
|
+
`defaulted, so it was not requested. Re-run with --group-behavior context to add it to the app draft, ` +
|
|
73
|
+
`or --group-behavior mentions to stay least-privilege: ${permissionUrl}`);
|
|
74
|
+
return { publishReady: false };
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
await api.addAppScopes(appId, [FEISHU_GROUP_CONTEXT_SCOPE]);
|
|
78
|
+
note(`[fastagent] added ${FEISHU_GROUP_CONTEXT_SCOPE} to the app draft — complete tenant-admin approval before publishing. Opening ${permissionUrl}`);
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
note(`[fastagent] warn: could not add ${FEISHU_GROUP_CONTEXT_SCOPE} automatically: ${String(error)} — add it manually before publishing. Opening ${permissionUrl}`);
|
|
82
|
+
}
|
|
83
|
+
openUrl(permissionUrl);
|
|
84
|
+
return { publishReady: false };
|
|
85
|
+
}
|
|
25
86
|
/**
|
|
26
87
|
* Create or resume the platform app behind `add feishu` / `add lark`. Returns credentials for the
|
|
27
88
|
* caller's generic .env write (the guided Lark path), or undefined when nothing remains to write —
|
|
@@ -29,7 +90,7 @@ import { startCloudflareTunnel } from "./tunnel.js";
|
|
|
29
90
|
* irreversible and must not wait for the caller). Throws on refusal (a committable .env, a
|
|
30
91
|
* non-interactive lark run); the caller surfaces that as a startup failure.
|
|
31
92
|
*/
|
|
32
|
-
export async function onboardFeishuCloudApp(target, kind, envIgnored) {
|
|
93
|
+
export async function onboardFeishuCloudApp(target, kind, envIgnored, ingress = "webhook", groupBehavior = { behavior: "context", explicit: false }) {
|
|
33
94
|
const { envPrefix, apiBase, capabilities } = cloudFor(kind);
|
|
34
95
|
// The CLI must never materialize a real credential into a committable file — refuse, don't warn.
|
|
35
96
|
if (!envIgnored) {
|
|
@@ -37,24 +98,40 @@ export async function onboardFeishuCloudApp(target, kind, envIgnored) {
|
|
|
37
98
|
? "`add feishu` creates an app and writes real credentials to .env — add .env to .gitignore/.fastagentignore first, then re-run"
|
|
38
99
|
: "`add lark` writes real app credentials to .env — add .env to .gitignore/.fastagentignore first, then re-run");
|
|
39
100
|
}
|
|
40
|
-
const
|
|
101
|
+
const requiredNames = [
|
|
41
102
|
`${envPrefix}_APP_ID`,
|
|
42
103
|
`${envPrefix}_APP_SECRET`,
|
|
43
|
-
`${envPrefix}_VERIFICATION_TOKEN
|
|
44
|
-
]
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
104
|
+
...(ingress === "webhook" ? [`${envPrefix}_VERIFICATION_TOKEN`] : []),
|
|
105
|
+
];
|
|
106
|
+
const existing = await activeDotEnvValues(target, requiredNames);
|
|
107
|
+
if (Object.keys(existing).length === requiredNames.length) {
|
|
108
|
+
console.error(`[fastagent] ${requiredNames.join("/")} already set in .env — keeping them`);
|
|
109
|
+
// WebSocket still needs its console mode/publish guidance. A complete webhook can skip the rest of
|
|
110
|
+
// onboarding, but group visibility must still be inspected/configured on every explicit re-run.
|
|
111
|
+
if (ingress === "webhook") {
|
|
112
|
+
const appId = existing[`${envPrefix}_APP_ID`];
|
|
113
|
+
const appSecret = existing[`${envPrefix}_APP_SECRET`];
|
|
114
|
+
await configureGroupBehavior({
|
|
115
|
+
kind,
|
|
116
|
+
appId,
|
|
117
|
+
apiBase,
|
|
118
|
+
api: createFeishuApi({ kind, baseUrl: apiBase, appId, appSecret }),
|
|
119
|
+
behavior: groupBehavior.behavior,
|
|
120
|
+
explicit: groupBehavior.explicit,
|
|
121
|
+
});
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
48
124
|
}
|
|
49
125
|
if (capabilities.appCreation === "scan-to-create") {
|
|
50
|
-
await createFeishuAppFlow(target, existing);
|
|
126
|
+
await createFeishuAppFlow(target, existing, ingress, groupBehavior);
|
|
51
127
|
return undefined;
|
|
52
128
|
}
|
|
53
129
|
// guided-console (lark): the intl cloud cannot complete the bound device flow — collect + validate.
|
|
54
|
-
if (!(process.stdin.isTTY && process.stdout.isTTY)
|
|
130
|
+
if (!(process.stdin.isTTY && process.stdout.isTTY) &&
|
|
131
|
+
!(existing[`${envPrefix}_APP_ID`] && existing[`${envPrefix}_APP_SECRET`])) {
|
|
55
132
|
throw new Error("`add lark` needs an interactive terminal to onboard the Lark app credentials — re-run it in a terminal");
|
|
56
133
|
}
|
|
57
|
-
|
|
134
|
+
const credentials = await onboardLarkApp({
|
|
58
135
|
openUrl: openExternalUrl,
|
|
59
136
|
note: (message) => clackLog.info(message),
|
|
60
137
|
async prompt(message, opts) {
|
|
@@ -63,6 +140,8 @@ export async function onboardFeishuCloudApp(target, kind, envIgnored) {
|
|
|
63
140
|
},
|
|
64
141
|
}, {
|
|
65
142
|
existing,
|
|
143
|
+
ingress,
|
|
144
|
+
groupBehavior: groupBehavior.behavior,
|
|
66
145
|
verifyCredentials: async (appId, appSecret) => {
|
|
67
146
|
await createFeishuApi({ kind: "lark", baseUrl: apiBase, appId, appSecret }).verifyCredentials();
|
|
68
147
|
console.error(`[fastagent] Lark App ID / Secret verified`);
|
|
@@ -94,37 +173,54 @@ export async function onboardFeishuCloudApp(target, kind, envIgnored) {
|
|
|
94
173
|
}
|
|
95
174
|
},
|
|
96
175
|
});
|
|
176
|
+
await configureGroupBehavior({
|
|
177
|
+
kind: "lark",
|
|
178
|
+
appId: credentials.LARK_APP_ID,
|
|
179
|
+
apiBase,
|
|
180
|
+
api: createFeishuApi({
|
|
181
|
+
kind: "lark",
|
|
182
|
+
baseUrl: apiBase,
|
|
183
|
+
appId: credentials.LARK_APP_ID,
|
|
184
|
+
appSecret: credentials.LARK_APP_SECRET,
|
|
185
|
+
}),
|
|
186
|
+
behavior: groupBehavior.behavior,
|
|
187
|
+
explicit: groupBehavior.explicit,
|
|
188
|
+
});
|
|
189
|
+
return Object.fromEntries(Object.entries(credentials).filter((entry) => typeof entry[1] === "string"));
|
|
97
190
|
}
|
|
98
191
|
/**
|
|
99
192
|
* The scan-to-create flow `add feishu` runs by default. The device-authorization grant
|
|
100
193
|
* creates a pre-configured agent app (bot capability, messaging scopes, event subscriptions) when the
|
|
101
|
-
* user confirms a link
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
* complete before the one remaining version-publish action. The event Request URL is NOT left pointing at the throwaway
|
|
105
|
-
* tunnel for long: `dev --tunnel` / `deploy --run` re-register it against the live URL.
|
|
194
|
+
* user confirms a link, then persists App ID/Secret at the irreversible boundary. That completes
|
|
195
|
+
* WebSocket credentials; webhook continues through challenge-captured Token persistence. The throwaway
|
|
196
|
+
* Request URL is later replaced by `dev --tunnel` / `deploy --run`.
|
|
106
197
|
*
|
|
107
198
|
* Feishu is the reference cloud and the only kind that runs this BOUND device flow. Lark is an explicit
|
|
108
199
|
* compatibility profile: its lagging control plane uses the unbound launcher + guided credentials,
|
|
109
200
|
* then probes the canonical token/mode bootstrap with a manual fallback.
|
|
110
201
|
*/
|
|
111
|
-
async function createFeishuAppFlow(target, existing) {
|
|
202
|
+
async function createFeishuAppFlow(target, existing, ingress, groupBehavior) {
|
|
112
203
|
const { apiBase } = cloudFor("feishu");
|
|
113
204
|
let appId = existing.FEISHU_APP_ID;
|
|
114
205
|
let appSecret = existing.FEISHU_APP_SECRET;
|
|
115
206
|
if (appId && appSecret) {
|
|
116
|
-
console.error(
|
|
207
|
+
console.error(ingress === "webhook"
|
|
208
|
+
? `[fastagent] resuming Feishu app ${appId} from .env to capture its missing Verification Token`
|
|
209
|
+
: `[fastagent] reusing Feishu app ${appId} from .env for WebSocket ingress`);
|
|
117
210
|
}
|
|
118
211
|
else {
|
|
119
212
|
console.error(`[fastagent] creating the Feishu app (confirm in the app)…`);
|
|
120
213
|
const app = await registerFeishuApp({
|
|
121
214
|
name: "{user}'s agent", // the platform expands {user} to the confirming user's name; editable on the page
|
|
122
215
|
desc: "Served by fastagent",
|
|
123
|
-
// The agent template alone is not enough to SERVE:
|
|
124
|
-
//
|
|
125
|
-
//
|
|
216
|
+
// The agent template alone is not enough to SERVE: v7 config PATCHes (webhook registration and
|
|
217
|
+
// context-aware group scope setup) demand application:application:patch, and the app must subscribe
|
|
218
|
+
// the receive event. Addons merge those BASE capabilities onto the confirm page; sensitive group
|
|
219
|
+
// permission approval and version publishing remain explicit console work.
|
|
126
220
|
addons: {
|
|
127
|
-
|
|
221
|
+
...(ingress === "webhook" || (groupBehavior.behavior === "context" && groupBehavior.explicit)
|
|
222
|
+
? { scopes: { tenant: ["application:application:patch"] } }
|
|
223
|
+
: {}),
|
|
128
224
|
events: { items: { tenant: ["im.message.receive_v1"] } },
|
|
129
225
|
},
|
|
130
226
|
onVerificationUrl: ({ url, expiresInS }) => {
|
|
@@ -144,14 +240,36 @@ async function createFeishuAppFlow(target, existing) {
|
|
|
144
240
|
// IRREVERSIBLE BOUNDARY: the remote app now exists and its one-time Secret is in memory. Persist
|
|
145
241
|
// both before any config read, temporary tunnel, or Token bootstrap can be interrupted. Partial old
|
|
146
242
|
// lines are overwritten because these newly-minted credentials are authoritative as one pair.
|
|
147
|
-
|
|
243
|
+
const staged = {
|
|
148
244
|
FEISHU_APP_ID: appId,
|
|
149
245
|
FEISHU_APP_SECRET: appSecret,
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
246
|
+
...(ingress === "webhook" ? { FEISHU_VERIFICATION_TOKEN: "" } : {}),
|
|
247
|
+
};
|
|
248
|
+
await appendChannelDotEnv(target, "feishu", staged, Object.keys(staged), ingress);
|
|
249
|
+
console.error(ingress === "webhook"
|
|
250
|
+
? `[fastagent] wrote FEISHU_APP_ID, FEISHU_APP_SECRET to .env before Token bootstrap`
|
|
251
|
+
: `[fastagent] wrote FEISHU_APP_ID, FEISHU_APP_SECRET to .env`);
|
|
252
|
+
}
|
|
253
|
+
const groupSetup = await configureGroupBehavior({
|
|
254
|
+
kind: "feishu",
|
|
255
|
+
appId,
|
|
256
|
+
apiBase,
|
|
257
|
+
api: createFeishuApi({ kind: "feishu", baseUrl: apiBase, appId, appSecret }),
|
|
258
|
+
behavior: groupBehavior.behavior,
|
|
259
|
+
explicit: groupBehavior.explicit,
|
|
260
|
+
});
|
|
261
|
+
if (ingress === "websocket") {
|
|
262
|
+
const versionUrl = `${apiBase}/app/${appId}/version`;
|
|
263
|
+
if (groupSetup.publishReady) {
|
|
264
|
+
console.error(`[fastagent] WebSocket ingress needs no Verification Token, Encrypt Key, Request URL, or tunnel. ` +
|
|
265
|
+
`Choose long connection in Events & Callbacks, then CREATE + PUBLISH a version. Opening ${versionUrl}`);
|
|
266
|
+
openExternalUrl(versionUrl);
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
console.error(`[fastagent] WebSocket ingress needs no Verification Token, Encrypt Key, Request URL, or tunnel. ` +
|
|
270
|
+
`Choose long connection in Events & Callbacks, finish the permission work opened above, then CREATE + PUBLISH: ${versionUrl}`);
|
|
271
|
+
}
|
|
272
|
+
return;
|
|
155
273
|
}
|
|
156
274
|
// The webhook channel authenticates plaintext events by the platform-generated Verification Token.
|
|
157
275
|
// Try the cheap read first (the v6 detail MAY someday return `encryption`), then the real path: the
|
|
@@ -199,8 +317,13 @@ async function createFeishuAppFlow(target, existing) {
|
|
|
199
317
|
// The bootstrap's PATCH flipped event mode in the DRAFT. It takes effect only after a version
|
|
200
318
|
// publish, which has no API; later dev/deploy runs change only the Request URL immediately.
|
|
201
319
|
const versionUrl = `${apiBase}/app/${appId}/version`;
|
|
202
|
-
|
|
203
|
-
|
|
320
|
+
if (groupSetup.publishReady) {
|
|
321
|
+
console.error(`[fastagent] one console click remains: CREATE + PUBLISH a version (self-approved) — the switch to webhook mode takes effect on publish. Opening ${versionUrl}`);
|
|
322
|
+
openExternalUrl(versionUrl);
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
console.error(`[fastagent] after the permission work opened above, CREATE + PUBLISH a version — the switch to webhook mode takes effect on publish: ${versionUrl}`);
|
|
326
|
+
}
|
|
204
327
|
}
|
|
205
328
|
}
|
|
206
329
|
/** Active run-root `.env` values for the requested names — decided by THE .env parser, so this
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type GroupBehaviorChoice } from "../scaffold/add-channel.ts";
|
|
2
|
+
/** Interactive single-workspace internal-app creation + installation. Safe to re-run after interruption. */
|
|
3
|
+
export declare function onboardSlackInternalApp(input: {
|
|
4
|
+
target: string;
|
|
5
|
+
stateRoot: string;
|
|
6
|
+
envIgnored: boolean;
|
|
7
|
+
groupBehavior: GroupBehaviorChoice;
|
|
8
|
+
/** `--replace-config`: go straight to replacing the local App Configuration token pair. */
|
|
9
|
+
replaceConfig?: boolean;
|
|
10
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { basename, join } from "node:path";
|
|
3
|
+
import { isCancel, log as clackLog, password, select, text as clackText } from "@clack/prompts";
|
|
4
|
+
import { waitForHealth } from "../channels/wait-health.js";
|
|
5
|
+
import { ensureStateRootSelfIgnored } from "../engines/pi/definition.js";
|
|
6
|
+
import { parseEnvContent } from "../env.js";
|
|
7
|
+
import { openExternalUrl } from "../open-url.js";
|
|
8
|
+
import { installProxyFetch } from "../proxy.js";
|
|
9
|
+
import { appendChannelDotEnv } from "../scaffold/add-channel.js";
|
|
10
|
+
import { newSlackOnboardingState, onboardSlackApp } from "../channels/slack/onboard.js";
|
|
11
|
+
import { readSlackOnboardingState, writeSlackOnboardingState } from "../channels/slack/onboarding-state.js";
|
|
12
|
+
import { startSlackSetupServer } from "../channels/slack/setup-server.js";
|
|
13
|
+
import { startCloudflareTunnel } from "../tunnel.js";
|
|
14
|
+
const CONFIG_TOKEN_URL = "https://api.slack.com/apps";
|
|
15
|
+
async function promptValue(message, hidden = false, initialValue) {
|
|
16
|
+
const result = hidden ? await password({ message }) : await clackText({ message, initialValue });
|
|
17
|
+
if (isCancel(result))
|
|
18
|
+
throw new Error("Slack onboarding cancelled");
|
|
19
|
+
const value = String(result).trim();
|
|
20
|
+
if (!value)
|
|
21
|
+
throw new Error(`${message}: value is required`);
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
/** Interactive single-workspace internal-app creation + installation. Safe to re-run after interruption. */
|
|
25
|
+
export async function onboardSlackInternalApp(input) {
|
|
26
|
+
installProxyFetch();
|
|
27
|
+
if (!input.envIgnored) {
|
|
28
|
+
throw new Error("`add slack` onboarding creates an app and writes real credentials to .env — " +
|
|
29
|
+
"add .env to .gitignore/.fastagentignore first, then re-run");
|
|
30
|
+
}
|
|
31
|
+
if (!(process.stdin.isTTY && process.stdout.isTTY)) {
|
|
32
|
+
throw new Error("`add slack` needs an interactive terminal for internal-app creation and OAuth — " +
|
|
33
|
+
"re-run in a terminal, or pass --no-onboard to scaffold only");
|
|
34
|
+
}
|
|
35
|
+
await ensureStateRootSelfIgnored(input.target, input.stateRoot);
|
|
36
|
+
let state = await readSlackOnboardingState(input.stateRoot);
|
|
37
|
+
const resumed = state !== undefined;
|
|
38
|
+
if (input.replaceConfig && !state) {
|
|
39
|
+
throw new Error("--replace-config found no local Slack onboarding state on this machine — nothing to replace. " +
|
|
40
|
+
"Run `fastagent add slack` to onboard, or update the Request URL manually in the Slack console");
|
|
41
|
+
}
|
|
42
|
+
if (state?.installedAt) {
|
|
43
|
+
const env = await readFile(join(input.target, ".env"), "utf8")
|
|
44
|
+
.then(parseEnvContent)
|
|
45
|
+
.catch((error) => {
|
|
46
|
+
if (error.code === "ENOENT")
|
|
47
|
+
return new Map();
|
|
48
|
+
throw error;
|
|
49
|
+
});
|
|
50
|
+
const missingRuntime = [
|
|
51
|
+
"SLACK_BOT_TOKEN",
|
|
52
|
+
"SLACK_BOT_REFRESH_TOKEN",
|
|
53
|
+
"SLACK_BOT_TOKEN_EXPIRES_AT",
|
|
54
|
+
"SLACK_CLIENT_ID",
|
|
55
|
+
"SLACK_CLIENT_SECRET",
|
|
56
|
+
"SLACK_SIGNING_SECRET",
|
|
57
|
+
].filter((name) => !((process.env[name] ?? env.get(name))?.trim() ?? ""));
|
|
58
|
+
if (missingRuntime.length > 0) {
|
|
59
|
+
throw new Error(`Slack app ${state.appId ?? "(unknown)"} is installed but .env is missing ${missingRuntime.join(", ")} — ` +
|
|
60
|
+
"restore them from the Slack app console, or delete the app + onboarding state and create a new one");
|
|
61
|
+
}
|
|
62
|
+
if (input.groupBehavior.explicit && state.groupBehavior !== input.groupBehavior.behavior) {
|
|
63
|
+
throw new Error(`the onboarded Slack app uses group behavior ${state.groupBehavior}; changing an installed app's ` +
|
|
64
|
+
"OAuth scopes is a migration. Keep the existing choice, or remove the app + Slack onboarding state and create a new app");
|
|
65
|
+
}
|
|
66
|
+
let action = "replace-config";
|
|
67
|
+
if (!input.replaceConfig) {
|
|
68
|
+
const answer = await select({
|
|
69
|
+
message: `Slack app ${state.appId ?? "(unknown)"} is already installed${state.teamName ? ` in ${state.teamName}` : ""}`,
|
|
70
|
+
initialValue: "keep",
|
|
71
|
+
options: [
|
|
72
|
+
{ value: "keep", label: "Keep the installed app" },
|
|
73
|
+
{
|
|
74
|
+
value: "replace-config",
|
|
75
|
+
label: "Replace App Configuration tokens",
|
|
76
|
+
hint: "repair automatic dev/deploy Request URL updates",
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
});
|
|
80
|
+
if (isCancel(answer))
|
|
81
|
+
throw new Error("Slack onboarding cancelled");
|
|
82
|
+
action = answer;
|
|
83
|
+
}
|
|
84
|
+
if (action === "replace-config") {
|
|
85
|
+
console.error(`[fastagent] generate a fresh App Configuration Token pair at ${CONFIG_TOKEN_URL}`);
|
|
86
|
+
openExternalUrl(CONFIG_TOKEN_URL);
|
|
87
|
+
const configToken = await promptValue("Slack configuration access token (xoxe.xoxp-…)", true);
|
|
88
|
+
const configRefreshToken = await promptValue("Slack configuration refresh token (xoxe-…)", true);
|
|
89
|
+
if (!configToken.startsWith("xoxe.") || !configRefreshToken.startsWith("xoxe-")) {
|
|
90
|
+
throw new Error("invalid Slack configuration token prefix (expected xoxe. access + xoxe- refresh)");
|
|
91
|
+
}
|
|
92
|
+
await writeSlackOnboardingState(input.stateRoot, {
|
|
93
|
+
...state,
|
|
94
|
+
configToken,
|
|
95
|
+
configRefreshToken,
|
|
96
|
+
configTokenExpiresAt: Date.now() + 11 * 60 * 60_000,
|
|
97
|
+
});
|
|
98
|
+
console.error("[fastagent] replaced local Slack App Configuration tokens; runtime app credentials are unchanged");
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
console.error("[fastagent] keeping the installed Slack app and local configuration tokens");
|
|
102
|
+
}
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (!state) {
|
|
106
|
+
const appName = await promptValue("Slack app name", false, `FastAgent ${basename(input.target)}`);
|
|
107
|
+
clackLog.info("Slack's configuration refresh token can manage apps owned by your user in this workspace. " +
|
|
108
|
+
"FastAgent stores it only in owner-readable local state; it is never deployed.");
|
|
109
|
+
console.error(`[fastagent] generate an App Configuration Token at ${CONFIG_TOKEN_URL}`);
|
|
110
|
+
openExternalUrl(CONFIG_TOKEN_URL);
|
|
111
|
+
const configToken = await promptValue("Slack configuration access token (xoxe.xoxp-…)", true);
|
|
112
|
+
const configRefreshToken = await promptValue("Slack configuration refresh token (xoxe-…)", true);
|
|
113
|
+
if (!configToken.startsWith("xoxe."))
|
|
114
|
+
throw new Error("Slack configuration access token must start with xoxe.");
|
|
115
|
+
if (!configRefreshToken.startsWith("xoxe-")) {
|
|
116
|
+
throw new Error("Slack configuration refresh token must start with xoxe-");
|
|
117
|
+
}
|
|
118
|
+
state = newSlackOnboardingState({
|
|
119
|
+
appName,
|
|
120
|
+
groupBehavior: input.groupBehavior.behavior,
|
|
121
|
+
configToken,
|
|
122
|
+
configRefreshToken,
|
|
123
|
+
});
|
|
124
|
+
await writeSlackOnboardingState(input.stateRoot, state);
|
|
125
|
+
}
|
|
126
|
+
else if (input.groupBehavior.explicit && !state.appId) {
|
|
127
|
+
state = { ...state, groupBehavior: input.groupBehavior.behavior };
|
|
128
|
+
await writeSlackOnboardingState(input.stateRoot, state);
|
|
129
|
+
}
|
|
130
|
+
if (state.createAttemptedAt && !state.appId) {
|
|
131
|
+
throw new Error(`a prior Slack app creation attempt at ${state.createAttemptedAt} returned no app ID — ` +
|
|
132
|
+
`inspect ${CONFIG_TOKEN_URL}; delete any incomplete app and ${input.stateRoot}/channels/slack/onboarding.json before retrying`);
|
|
133
|
+
}
|
|
134
|
+
// `--replace-config` also covers the created-but-not-installed state, where a revoked token would
|
|
135
|
+
// otherwise strand the resume (rotation fails and no menu offers replacement).
|
|
136
|
+
if (resumed && (!state.appId || input.replaceConfig)) {
|
|
137
|
+
let action = "replace-config";
|
|
138
|
+
if (!input.replaceConfig) {
|
|
139
|
+
const answer = await select({
|
|
140
|
+
message: "Resume Slack onboarding with which App Configuration tokens?",
|
|
141
|
+
initialValue: "keep",
|
|
142
|
+
options: [
|
|
143
|
+
{ value: "keep", label: "Use the saved token pair" },
|
|
144
|
+
{ value: "replace-config", label: "Paste a fresh token pair" },
|
|
145
|
+
],
|
|
146
|
+
});
|
|
147
|
+
if (isCancel(answer))
|
|
148
|
+
throw new Error("Slack onboarding cancelled");
|
|
149
|
+
action = answer;
|
|
150
|
+
}
|
|
151
|
+
if (action === "replace-config") {
|
|
152
|
+
openExternalUrl(CONFIG_TOKEN_URL);
|
|
153
|
+
state = {
|
|
154
|
+
...state,
|
|
155
|
+
configToken: await promptValue("Slack configuration access token (xoxe.xoxp-…)", true),
|
|
156
|
+
configRefreshToken: await promptValue("Slack configuration refresh token (xoxe-…)", true),
|
|
157
|
+
configTokenExpiresAt: Date.now() + 11 * 60 * 60_000,
|
|
158
|
+
};
|
|
159
|
+
if (!state.configToken.startsWith("xoxe.") || !state.configRefreshToken.startsWith("xoxe-")) {
|
|
160
|
+
throw new Error("invalid Slack configuration token prefix (expected xoxe. access + xoxe- refresh)");
|
|
161
|
+
}
|
|
162
|
+
await writeSlackOnboardingState(input.stateRoot, state);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const server = await startSlackSetupServer();
|
|
166
|
+
const tunnel = await startCloudflareTunnel(server.port);
|
|
167
|
+
if (!tunnel) {
|
|
168
|
+
await server.close();
|
|
169
|
+
throw new Error("Slack onboarding needs a temporary HTTPS tunnel — install cloudflared and re-run");
|
|
170
|
+
}
|
|
171
|
+
const requestUrl = `${tunnel.url}${server.requestPath}`;
|
|
172
|
+
const redirectUrl = `${tunnel.url}${server.redirectPath}`;
|
|
173
|
+
console.error(`[fastagent] temporary Slack setup tunnel ready → ${tunnel.url}`);
|
|
174
|
+
try {
|
|
175
|
+
if (!(await waitForHealth(`${tunnel.url}/health`, 45_000, 500))) {
|
|
176
|
+
throw new Error("the temporary Slack setup tunnel did not become reachable; no app was created");
|
|
177
|
+
}
|
|
178
|
+
await onboardSlackApp({ stateRoot: input.stateRoot, state, requestUrl, redirectUrl }, {
|
|
179
|
+
note: (message) => clackLog.info(message),
|
|
180
|
+
openUrl: openExternalUrl,
|
|
181
|
+
waitForOAuth: () => server.waitForOAuth(),
|
|
182
|
+
writeRuntimeSecrets: async ({ botToken, botRefreshToken, botTokenExpiresAt, clientId, clientSecret, signingSecret, }) => {
|
|
183
|
+
const values = {
|
|
184
|
+
...(botToken ? { SLACK_BOT_TOKEN: botToken } : {}),
|
|
185
|
+
...(botRefreshToken ? { SLACK_BOT_REFRESH_TOKEN: botRefreshToken } : {}),
|
|
186
|
+
...(botTokenExpiresAt ? { SLACK_BOT_TOKEN_EXPIRES_AT: String(botTokenExpiresAt) } : {}),
|
|
187
|
+
...(clientId ? { SLACK_CLIENT_ID: clientId } : {}),
|
|
188
|
+
...(clientSecret ? { SLACK_CLIENT_SECRET: clientSecret } : {}),
|
|
189
|
+
...(signingSecret ? { SLACK_SIGNING_SECRET: signingSecret } : {}),
|
|
190
|
+
};
|
|
191
|
+
if (Object.keys(values).length > 0) {
|
|
192
|
+
await appendChannelDotEnv(input.target, "slack", values, Object.keys(values));
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
console.error("[fastagent] Slack app installed; rotating bot credentials and Signing Secret written to .env");
|
|
197
|
+
console.error(`[fastagent] run \`fastagent dev --tunnel\` next — FastAgent will rotate the config token and ` +
|
|
198
|
+
"replace the temporary Events API URL automatically");
|
|
199
|
+
}
|
|
200
|
+
finally {
|
|
201
|
+
tunnel.close();
|
|
202
|
+
await server.close().catch(() => { });
|
|
203
|
+
}
|
|
204
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CLI presenter for the auth-status line `reportAuth` prints (invoke/dev/start). Kept out of cli.ts —
|
|
3
3
|
* which self-executes on import — so the expired-vs-missing DECISION is unit-testable without a real
|
|
4
|
-
* credential round-trip, mirroring
|
|
4
|
+
* credential round-trip, mirroring models-view.ts.
|
|
5
5
|
*/
|
|
6
6
|
/** A stored credential as `reportAuth` needs it: just its kind, for the status line. */
|
|
7
7
|
export interface StoredCredentialInfo {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CLI presenter for the auth-status line `reportAuth` prints (invoke/dev/start). Kept out of cli.ts —
|
|
3
3
|
* which self-executes on import — so the expired-vs-missing DECISION is unit-testable without a real
|
|
4
|
-
* credential round-trip, mirroring
|
|
4
|
+
* credential round-trip, mirroring models-view.ts.
|
|
5
5
|
*/
|
|
6
6
|
/**
|
|
7
7
|
* Format the auth status for `spec`'s provider. `source` is {@link probeAuthSource}'s label (an env-var
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type ChannelKind } from "../../scaffold/add-channel.ts";
|
|
2
|
+
/** `fastagent add <kind> [dir]`: scaffold `channels/<kind>.ts` — the adapter import plus a starter `on()`. */
|
|
3
|
+
export declare function runAddChannel(channelKind: ChannelKind, dirArg: string, opts: {
|
|
4
|
+
createApp?: boolean;
|
|
5
|
+
ingress?: string;
|
|
6
|
+
groupBehavior?: string;
|
|
7
|
+
onboard?: boolean;
|
|
8
|
+
replaceConfig?: boolean;
|
|
9
|
+
}): Promise<void>;
|
|
10
|
+
/** `fastagent add skill <source> [dir]`: vendor an Agent Skills skill into <dir>/skills/<name>/. */
|
|
11
|
+
export declare function runAddSkill(source: string | undefined, dirArg: string, opts: {
|
|
12
|
+
update?: boolean;
|
|
13
|
+
}): Promise<void>;
|