@fastagent-sh/fastagent 0.28.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.
- package/README.md +10 -4
- package/dist/atomic-write.d.ts +4 -5
- package/dist/atomic-write.js +4 -5
- package/dist/authoring.d.ts +32 -15
- package/dist/authoring.js +147 -127
- package/dist/channels/agentcore-protocol.d.ts +8 -3
- package/dist/channels/agentcore-protocol.js +3 -1
- package/dist/channels/agentcore-service.d.ts +1 -0
- package/dist/channels/agentcore-service.js +14 -4
- package/dist/channels/agentcore.d.ts +9 -0
- package/dist/channels/agentcore.js +29 -4
- package/dist/channels/context.d.ts +2 -0
- package/dist/channels/context.js +5 -1
- package/dist/channels/feishu/feishu-api.d.ts +3 -1
- package/dist/channels/feishu/feishu-api.js +4 -0
- package/dist/channels/feishu/feishu.d.ts +0 -3
- package/dist/channels/feishu/feishu.js +10 -10
- package/dist/channels/feishu/register-app.d.ts +2 -0
- package/dist/channels/feishu/register-app.js +4 -6
- package/dist/channels/feishu/scaffold/channel.ts +32 -34
- package/dist/channels/feishu/scaffold/feishu-send.ts +5 -5
- package/dist/channels/feishu/scaffold/feishu-threads.ts +3 -3
- package/dist/channels/feishu/setup-mode.d.ts +2 -12
- package/dist/channels/feishu/setup-mode.js +2 -22
- package/dist/channels/feishu/setup.d.ts +12 -0
- package/dist/channels/feishu/setup.js +197 -0
- package/dist/channels/kit/abortable.d.ts +8 -0
- package/dist/channels/kit/abortable.js +22 -0
- package/dist/channels/lark/lark.d.ts +0 -3
- package/dist/channels/lark/lark.js +0 -5
- package/dist/channels/lark/scaffold/channel.ts +33 -35
- package/dist/channels/lark/scaffold/lark-send.ts +5 -5
- package/dist/channels/lark/scaffold/lark-threads.ts +3 -3
- package/dist/channels/lark/setup.d.ts +9 -0
- package/dist/channels/lark/setup.js +57 -0
- package/dist/channels/setup.d.ts +112 -0
- package/dist/channels/setup.js +31 -0
- package/dist/channels/slack/history.js +1 -1
- package/dist/channels/slack/manifest.d.ts +17 -7
- package/dist/channels/slack/manifest.js +12 -16
- package/dist/channels/slack/scaffold/channel.ts +44 -30
- package/dist/channels/slack/scaffold/slack-send.ts +8 -8
- package/dist/channels/slack/scaffold/slack-threads.ts +4 -4
- package/dist/channels/slack/setup.d.ts +7 -0
- package/dist/channels/slack/setup.js +119 -0
- package/dist/channels/slack/slack-api.d.ts +2 -0
- package/dist/channels/slack/slack-api.js +4 -0
- package/dist/channels/slack/slack.d.ts +21 -2
- package/dist/channels/slack/slack.js +398 -338
- package/dist/channels/slack/socket.d.ts +57 -0
- package/dist/channels/slack/socket.js +305 -0
- package/dist/channels/telegram/polling.d.ts +27 -0
- package/dist/channels/telegram/polling.js +105 -0
- package/dist/channels/telegram/scaffold/channel.ts +52 -32
- package/dist/channels/telegram/scaffold/telegram-send.ts +9 -9
- package/dist/channels/telegram/setup.d.ts +2 -0
- package/dist/channels/telegram/setup.js +42 -0
- package/dist/channels/telegram/telegram-api.d.ts +27 -1
- package/dist/channels/telegram/telegram-api.js +7 -6
- package/dist/channels/telegram/telegram.d.ts +11 -3
- package/dist/channels/telegram/telegram.js +184 -157
- package/dist/channels/wait-health.js +1 -1
- package/dist/channels/webhook-evidence.d.ts +11 -0
- package/dist/channels/webhook-evidence.js +71 -0
- package/dist/channels/webhooks.d.ts +37 -0
- package/dist/channels/webhooks.js +70 -0
- package/dist/child-env.d.ts +16 -0
- package/dist/child-env.js +30 -0
- package/dist/cli/box-login.js +4 -1
- package/dist/cli/channel-app.d.ts +3 -0
- package/dist/cli/channel-app.js +35 -0
- package/dist/cli/channel-setup.d.ts +38 -0
- package/dist/cli/channel-setup.js +195 -0
- package/dist/cli/commands/add.d.ts +6 -14
- package/dist/cli/commands/add.js +45 -310
- package/dist/cli/commands/auth.js +8 -2
- package/dist/cli/commands/build.d.ts +5 -0
- package/dist/cli/commands/build.js +16 -0
- package/dist/cli/commands/channels.d.ts +19 -0
- package/dist/cli/commands/channels.js +230 -0
- package/dist/cli/commands/content.d.ts +7 -3
- package/dist/cli/commands/content.js +16 -10
- package/dist/cli/commands/deploy/agentcore.js +55 -23
- package/dist/cli/commands/deploy/channel-apps.d.ts +17 -0
- package/dist/cli/commands/deploy/channel-apps.js +30 -0
- package/dist/cli/commands/deploy/docker.js +32 -65
- package/dist/cli/commands/deploy/fly.js +28 -12
- package/dist/cli/commands/deploy/railway.js +38 -13
- package/dist/cli/commands/deploy/secrets-step.d.ts +19 -0
- package/dist/cli/commands/deploy/secrets-step.js +66 -0
- package/dist/cli/commands/deploy/shared.d.ts +16 -5
- package/dist/cli/commands/deploy/shared.js +25 -8
- package/dist/cli/commands/deploy.js +53 -67
- package/dist/cli/commands/dev.d.ts +2 -0
- package/dist/cli/commands/dev.js +15 -3
- package/dist/cli/commands/env.js +1 -1
- package/dist/cli/commands/info.d.ts +2 -0
- package/dist/cli/commands/info.js +63 -24
- package/dist/cli/commands/init.js +3 -1
- package/dist/cli/commands/invoke.d.ts +2 -0
- package/dist/cli/commands/invoke.js +20 -7
- package/dist/cli/commands/login.d.ts +0 -7
- package/dist/cli/commands/login.js +22 -10
- package/dist/cli/commands/logout.js +1 -1
- package/dist/cli/commands/mcp.d.ts +12 -0
- package/dist/cli/commands/mcp.js +164 -0
- package/dist/cli/commands/models.js +3 -2
- package/dist/cli/commands/schedules.d.ts +4 -2
- package/dist/cli/commands/schedules.js +14 -10
- package/dist/cli/commands/secrets.d.ts +14 -0
- package/dist/cli/commands/secrets.js +213 -0
- package/dist/cli/commands/start.d.ts +14 -14
- package/dist/cli/commands/start.js +46 -60
- package/dist/cli/content-view.d.ts +4 -6
- package/dist/cli/content-view.js +5 -11
- package/dist/cli/first-run-channels.d.ts +3 -0
- package/dist/cli/first-run-channels.js +63 -0
- package/dist/cli/instance.d.ts +41 -0
- package/dist/cli/instance.js +155 -0
- package/dist/cli/invoke-stream.d.ts +5 -2
- package/dist/cli/invoke-stream.js +7 -2
- package/dist/cli/program.js +339 -108
- package/dist/cli/serve.d.ts +4 -5
- package/dist/cli/serve.js +4 -7
- package/dist/cli/shared.d.ts +11 -2
- package/dist/cli/shared.js +32 -19
- package/dist/cli/ticket.js +50 -6
- package/dist/content/declare.d.ts +31 -37
- package/dist/content/declare.js +59 -60
- package/dist/content/file.d.ts +23 -12
- package/dist/content/file.js +48 -35
- package/dist/content/git.d.ts +0 -10
- package/dist/content/git.js +5 -39
- package/dist/content/resolve.d.ts +18 -22
- package/dist/content/resolve.js +32 -66
- package/dist/content/source.d.ts +7 -8
- package/dist/content/source.js +10 -28
- package/dist/credentials-page.d.ts +26 -0
- package/dist/credentials-page.js +185 -0
- package/dist/declared-secrets.d.ts +10 -3
- package/dist/declared-secrets.js +8 -4
- package/dist/deploy/agentcore/destroy.d.ts +4 -1
- package/dist/deploy/agentcore/destroy.js +26 -2
- package/dist/deploy/agentcore/forwarder.js +1 -1
- package/dist/deploy/agentcore/plan.d.ts +7 -21
- package/dist/deploy/agentcore/plan.js +47 -49
- package/dist/deploy/agentcore/run.d.ts +18 -10
- package/dist/deploy/agentcore/run.js +93 -70
- package/dist/deploy/agentcore/runtime-secrets.d.ts +7 -0
- package/dist/deploy/agentcore/runtime-secrets.js +108 -0
- package/dist/deploy/agentcore/shell.js +12 -19
- package/dist/deploy/agentcore/sigv4.d.ts +7 -0
- package/dist/deploy/agentcore/sigv4.js +15 -0
- package/dist/deploy/agentcore/slr.d.ts +18 -0
- package/dist/deploy/agentcore/slr.js +27 -0
- package/dist/deploy/agentcore/store.d.ts +7 -0
- package/dist/deploy/agentcore/store.js +115 -0
- package/dist/deploy/api.d.ts +14 -14
- package/dist/deploy/api.js +26 -24
- package/dist/deploy/box-shell.d.ts +3 -1
- package/dist/deploy/build-context.d.ts +4 -4
- package/dist/deploy/build-context.js +11 -11
- package/dist/deploy/channel-ingress.d.ts +35 -9
- package/dist/deploy/channel-ingress.js +119 -22
- package/dist/deploy/container.d.ts +35 -10
- package/dist/deploy/container.js +80 -39
- package/dist/deploy/delivered-text.d.ts +13 -0
- package/dist/deploy/delivered-text.js +86 -0
- package/dist/deploy/docker/plan.d.ts +7 -8
- package/dist/deploy/docker/plan.js +31 -52
- package/dist/deploy/docker/run.d.ts +18 -43
- package/dist/deploy/docker/run.js +57 -79
- package/dist/deploy/file-store.d.ts +2 -0
- package/dist/deploy/file-store.js +17 -0
- package/dist/deploy/fly/plan.d.ts +3 -2
- package/dist/deploy/fly/plan.js +20 -9
- package/dist/deploy/fly/run.d.ts +18 -22
- package/dist/deploy/fly/run.js +25 -44
- package/dist/deploy/fly/store.d.ts +12 -0
- package/dist/deploy/fly/store.js +83 -0
- package/dist/deploy/hosts.d.ts +1 -2
- package/dist/deploy/hosts.js +1 -2
- package/dist/deploy/preflight.d.ts +18 -5
- package/dist/deploy/preflight.js +135 -63
- package/dist/deploy/railway/ignore.d.ts +8 -0
- package/dist/deploy/railway/ignore.js +57 -0
- package/dist/deploy/railway/plan.d.ts +1 -2
- package/dist/deploy/railway/plan.js +8 -6
- package/dist/deploy/railway/run.d.ts +21 -15
- package/dist/deploy/railway/run.js +32 -78
- package/dist/deploy/railway/store.d.ts +29 -0
- package/dist/deploy/railway/store.js +143 -0
- package/dist/deploy/runner.d.ts +5 -2
- package/dist/deploy/runner.js +4 -3
- package/dist/deploy/secret-store.d.ts +63 -0
- package/dist/deploy/secret-store.js +98 -0
- package/dist/deploy/secrets.d.ts +29 -46
- package/dist/deploy/secrets.js +49 -122
- package/dist/deploy/workspace.d.ts +6 -12
- package/dist/deploy/workspace.js +18 -94
- package/dist/dev-supervisor.js +6 -7
- package/dist/environment/declare.js +3 -2
- package/dist/environment/mise.d.ts +3 -1
- package/dist/environment/mise.js +4 -2
- package/dist/feishu.d.ts +1 -1
- package/dist/feishu.js +1 -1
- package/dist/harnesses/models/catalog.d.ts +1 -0
- package/dist/harnesses/models/catalog.js +1 -2
- package/dist/harnesses/models/environment.d.ts +7 -2
- package/dist/harnesses/models/environment.js +9 -5
- package/dist/harnesses/models/host.d.ts +0 -7
- package/dist/harnesses/models/host.js +10 -1
- package/dist/harnesses/models/login.d.ts +0 -8
- package/dist/harnesses/models/login.js +5 -4
- package/dist/harnesses/models/models.d.ts +7 -2
- package/dist/harnesses/models/models.js +28 -4
- package/dist/harnesses/pi-durable/agent.js +59 -38
- package/dist/harnesses/pi-durable/build.d.ts +12 -0
- package/dist/harnesses/pi-durable/build.js +270 -0
- package/dist/harnesses/pi-durable/built.d.ts +24 -0
- package/dist/harnesses/pi-durable/built.js +136 -0
- package/dist/harnesses/pi-durable/config.d.ts +81 -0
- package/dist/harnesses/pi-durable/config.js +266 -0
- package/dist/harnesses/pi-durable/define.d.ts +63 -13
- package/dist/harnesses/pi-durable/define.js +23 -2
- package/dist/harnesses/pi-durable/definition.d.ts +72 -0
- package/dist/harnesses/pi-durable/definition.js +143 -0
- package/dist/harnesses/pi-durable/events.d.ts +4 -3
- package/dist/harnesses/pi-durable/events.js +179 -136
- package/dist/harnesses/pi-durable/execution-env.d.ts +17 -0
- package/dist/harnesses/pi-durable/execution-env.js +11 -0
- package/dist/harnesses/pi-durable/mcp.d.ts +3 -0
- package/dist/harnesses/pi-durable/mcp.js +32 -0
- package/dist/harnesses/pi-durable/open.d.ts +36 -38
- package/dist/harnesses/pi-durable/open.js +97 -150
- package/dist/harnesses/pi-durable/reads.d.ts +0 -1
- package/dist/harnesses/pi-durable/reads.js +1 -3
- package/dist/harnesses/pi-durable/registry.d.ts +4 -6
- package/dist/harnesses/pi-durable/registry.js +29 -26
- package/dist/harnesses/pi-durable/release.d.ts +2 -2
- package/dist/harnesses/pi-durable/release.js +10 -7
- package/dist/harnesses/pi-durable/serve.d.ts +95 -15
- package/dist/harnesses/pi-durable/serve.js +151 -59
- package/dist/harnesses/pi-durable/sessions.js +15 -12
- package/dist/harnesses/pi-durable/text-sources.d.ts +30 -0
- package/dist/harnesses/pi-durable/text-sources.js +55 -0
- package/dist/harnesses/pi-durable/text.d.ts +16 -4
- package/dist/harnesses/pi-durable/text.js +115 -59
- package/dist/harnesses/pi-durable/wake.js +8 -8
- package/dist/http/client.js +9 -3
- package/dist/http/server.d.ts +1 -1
- package/dist/http/server.js +1 -1
- package/dist/index.d.ts +11 -4
- package/dist/index.js +8 -1
- package/dist/instance-name.d.ts +7 -0
- package/dist/instance-name.js +26 -0
- package/dist/lark.d.ts +1 -1
- package/dist/lark.js +1 -1
- package/dist/locked-file.d.ts +2 -2
- package/dist/locked-file.js +8 -3
- package/dist/mcp/auth.d.ts +22 -0
- package/dist/mcp/auth.js +180 -0
- package/dist/mcp/client.d.ts +15 -0
- package/dist/mcp/client.js +179 -0
- package/dist/mcp/config.d.ts +86 -0
- package/dist/mcp/config.js +225 -0
- package/dist/mcp/grants.d.ts +4 -0
- package/dist/mcp/grants.js +132 -0
- package/dist/mcp/host.d.ts +19 -0
- package/dist/mcp/host.js +128 -0
- package/dist/mcp/network.d.ts +12 -0
- package/dist/mcp/network.js +150 -0
- package/dist/mcp/sign-in.d.ts +2 -0
- package/dist/mcp/sign-in.js +204 -0
- package/dist/mcp-api.d.ts +107 -0
- package/dist/mcp-api.js +10 -0
- package/dist/paths.d.ts +22 -8
- package/dist/paths.js +44 -11
- package/dist/runtime-websocket.d.ts +14 -0
- package/dist/runtime-websocket.js +39 -0
- package/dist/scaffold/add-channel.d.ts +9 -16
- package/dist/scaffold/add-channel.js +13 -83
- package/dist/scaffold/templates/env.example +4 -5
- package/dist/scaffold/templates/fastagent.config.ts +20 -14
- package/dist/scaffold/templates/gitignore +9 -6
- package/dist/scaffold/templates.d.ts +6 -2
- package/dist/scaffold/templates.js +7 -2
- package/dist/scaffold/vendor-skill.js +4 -2
- package/dist/service.d.ts +44 -6
- package/dist/service.js +110 -10
- package/dist/serving-command.d.ts +10 -0
- package/dist/serving-command.js +1 -0
- package/dist/setup-page.d.ts +51 -0
- package/dist/setup-page.js +149 -0
- package/dist/slack.d.ts +1 -1
- package/dist/slack.js +1 -1
- package/dist/strip-edges.d.ts +10 -0
- package/dist/strip-edges.js +22 -0
- package/dist/telegram.d.ts +1 -1
- package/dist/telegram.js +1 -1
- package/dist/tunnel.d.ts +0 -1
- package/dist/tunnel.js +2 -8
- package/dist/value-file.d.ts +12 -6
- package/dist/value-file.js +36 -25
- package/dist/version.d.ts +7 -1
- package/dist/version.js +9 -3
- package/package.json +8 -6
- package/dist/channels/lark/onboard.d.ts +0 -36
- package/dist/channels/lark/onboard.js +0 -56
- package/dist/channels/slack/config-api.d.ts +0 -69
- package/dist/channels/slack/config-api.js +0 -162
- package/dist/channels/slack/onboard.d.ts +0 -40
- package/dist/channels/slack/onboard.js +0 -136
- package/dist/channels/slack/onboarding-state.d.ts +0 -31
- package/dist/channels/slack/onboarding-state.js +0 -64
- package/dist/channels/slack/register-webhook.d.ts +0 -12
- package/dist/channels/slack/register-webhook.js +0 -67
- package/dist/channels/slack/setup-server.d.ts +0 -32
- package/dist/channels/slack/setup-server.js +0 -201
- package/dist/cli/add-feishu.d.ts +0 -50
- package/dist/cli/add-feishu.js +0 -367
- package/dist/cli/add-slack.d.ts +0 -18
- package/dist/cli/add-slack.js +0 -216
- package/dist/harnesses/pi-durable/parked.d.ts +0 -18
- package/dist/harnesses/pi-durable/parked.js +0 -27
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
export declare function setupRequestUrl(request: IncomingMessage, response: ServerResponse): URL | undefined;
|
|
3
|
+
/** `1 Title`: the number and the title on one line, so the step's content below shares the card's one left edge. */
|
|
4
|
+
export declare function step(number: number, title: string, content: string, options?: {
|
|
5
|
+
bare?: boolean;
|
|
6
|
+
}): string;
|
|
7
|
+
/** A sentence under a step's title. `html` is trusted markup: use `<b>` for a name the person must find on another site. */
|
|
8
|
+
export declare const prose: (html: string) => string;
|
|
9
|
+
/** A button that opens another site in a new tab (secondary: the step's real action is the primary button below). */
|
|
10
|
+
export declare function openButton(label: string, href: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* A labelled input. `label` is the name the other site gives the value, so the person never has to map one to the other.
|
|
13
|
+
* A secret gets a Show/Hide toggle; nothing is ever put back in a secret field after a refusal.
|
|
14
|
+
*/
|
|
15
|
+
export declare function field(input: {
|
|
16
|
+
id: string;
|
|
17
|
+
label: string;
|
|
18
|
+
placeholder?: string;
|
|
19
|
+
secret?: boolean;
|
|
20
|
+
value?: string;
|
|
21
|
+
invalid?: boolean;
|
|
22
|
+
autofocus?: boolean;
|
|
23
|
+
}): string;
|
|
24
|
+
/**
|
|
25
|
+
* What was refused, in bold, and what to do about it. Sits above the field it is about. `why` is the other side's own
|
|
26
|
+
* words, for someone who has to look it up: small and set apart, so the sentence the person reads stays one they can act on.
|
|
27
|
+
*/
|
|
28
|
+
export declare function refusal(headline: string, detail?: string, why?: string): string;
|
|
29
|
+
export declare const submit: (label: string) => string;
|
|
30
|
+
/** The promise about where a pasted value goes. Said once, the same way, on every page that asks for one. */
|
|
31
|
+
export declare const privacy: () => string;
|
|
32
|
+
/** A page that asks for something: a heading, then the steps, in one form posting back to this address. */
|
|
33
|
+
export declare function formCard(heading: string, steps: string): string;
|
|
34
|
+
/** A page that says where things stand: working (it moves on by itself), done, or stopped. */
|
|
35
|
+
export declare function statusCard(input: {
|
|
36
|
+
state: "working" | "done" | "failed";
|
|
37
|
+
heading: string;
|
|
38
|
+
lede?: string;
|
|
39
|
+
/** What the person does now, set apart under the status. */
|
|
40
|
+
next?: string;
|
|
41
|
+
}): string;
|
|
42
|
+
/**
|
|
43
|
+
* Send a page. The headers are those of a page that takes a secret: nothing cached, nothing framed, nothing loaded from
|
|
44
|
+
* elsewhere (a nonce admits only this page's own style and script). No `form-action`: browsers apply it to the redirects a
|
|
45
|
+
* submission is answered with, and Slack's page answers one by sending the browser on to Slack.
|
|
46
|
+
*/
|
|
47
|
+
export declare function sendPage(response: ServerResponse, status: number, page: {
|
|
48
|
+
title: string;
|
|
49
|
+
card: string;
|
|
50
|
+
head?: string;
|
|
51
|
+
}): void;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pages a person sees while FastAgent sets an app up with them: where they paste a credential, and what happens next.
|
|
3
|
+
* One design for every channel (Slack's tokens, Lark's credentials), so a second page is a repetition of the first.
|
|
4
|
+
*
|
|
5
|
+
* The page is self-contained: no font, script or image comes from anywhere else, and it follows the system's light or
|
|
6
|
+
* dark. It is served to a browser on this machine (or through a tunnel), so a secret pasted into it goes from the browser
|
|
7
|
+
* to this process and nowhere else — never through a coding agent's conversation or a file the person writes.
|
|
8
|
+
*
|
|
9
|
+
* Design rules it keeps, so a page added later keeps them too: one left edge; one primary button per page; a label is the
|
|
10
|
+
* name the other site uses for the value; an error sits above the field it is about; red is for an error and green for
|
|
11
|
+
* success only; every text is at least 4.5:1 against its background and an input's edge 3:1.
|
|
12
|
+
*/
|
|
13
|
+
import { randomBytes } from "node:crypto";
|
|
14
|
+
export function setupRequestUrl(request, response) {
|
|
15
|
+
const url = URL.parse(request.url ?? "/", "http://127.0.0.1");
|
|
16
|
+
if (url === null) {
|
|
17
|
+
response.writeHead(400).end("invalid request URL\n");
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
return url;
|
|
21
|
+
}
|
|
22
|
+
const escapeHtml = (text) => text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
23
|
+
const CSS = `
|
|
24
|
+
:root{color-scheme:light dark;--bg:#f4f5f7;--card:#fff;--ink:#11151a;--muted:#4f5a67;--line:#d7dce2;--field:#fff;--edge:#727c88;
|
|
25
|
+
--accent:#0a6a7d;--accent-hover:#085666;--on-accent:#fff;--accent-tint:#e2f2f5;--ok:#17713f;--ok-tint:#e2f4ea;--bad:#b3261e;--bad-tint:#fdecea}
|
|
26
|
+
@media (prefers-color-scheme:dark){:root{--bg:#0d1014;--card:#161a20;--ink:#f2f4f7;--muted:#a3adba;--line:#2c333d;--field:#0d1014;--edge:#6b7684;
|
|
27
|
+
--accent:#46bfd4;--accent-hover:#6bd0e1;--on-accent:#06222a;--accent-tint:#123039;--ok:#5ccd91;--ok-tint:#11301f;--bad:#ff948b;--bad-tint:#3a1815}}
|
|
28
|
+
*{box-sizing:border-box}
|
|
29
|
+
body{margin:0;background:var(--bg);color:var(--ink);font:16px/1.5 ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
|
|
30
|
+
-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}
|
|
31
|
+
h1,h2{text-wrap:balance}p{text-wrap:pretty}
|
|
32
|
+
main{max-width:520px;margin:48px auto;padding:0 16px}
|
|
33
|
+
.brand{margin:0 0 16px;font-weight:700;letter-spacing:-.01em}
|
|
34
|
+
.card{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:32px}
|
|
35
|
+
h1{font-size:28px;line-height:1.2;font-weight:700;letter-spacing:-.011em;margin:0 0 32px}
|
|
36
|
+
.step+.step{margin-top:40px}
|
|
37
|
+
.step header{display:flex;align-items:center;gap:12px;margin:0 0 8px}
|
|
38
|
+
.step header.bare{margin-bottom:16px}
|
|
39
|
+
.num{flex:none;width:28px;height:28px;border-radius:50%;display:grid;place-items:center;background:var(--accent-tint);color:var(--accent);font-weight:700;font-size:14px}
|
|
40
|
+
.step h2{font-size:18px;line-height:28px;font-weight:700;margin:0}
|
|
41
|
+
.step p{margin:0 0 16px;color:var(--muted)}
|
|
42
|
+
.step p b{color:var(--ink);font-weight:600;white-space:nowrap}
|
|
43
|
+
label{display:block;font-weight:600;font-size:14px;margin:0 0 4px}
|
|
44
|
+
.field{position:relative;margin-bottom:16px}
|
|
45
|
+
input{width:100%;height:48px;padding:0 72px 0 16px;font:15px ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--ink);background:var(--field);border:1px solid var(--edge);border-radius:8px}
|
|
46
|
+
input.plain{padding-right:16px}
|
|
47
|
+
input[aria-invalid=true]{border-color:var(--bad)}
|
|
48
|
+
input:focus-visible,button:focus-visible,a:focus-visible{outline:3px solid var(--accent);outline-offset:2px}
|
|
49
|
+
.toggle{position:absolute;right:4px;bottom:4px;height:40px;padding:0 12px;border:0;border-radius:6px;background:none;color:var(--accent);font:inherit;font-size:14px;font-weight:600;cursor:pointer}
|
|
50
|
+
.btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;height:48px;padding:0 24px;border-radius:8px;font:inherit;font-size:16px;font-weight:700;line-height:1;text-decoration:none;cursor:pointer;border:2px solid var(--accent)}
|
|
51
|
+
.btn.primary{background:var(--accent);color:var(--on-accent)}
|
|
52
|
+
.btn.primary:hover{background:var(--accent-hover);border-color:var(--accent-hover)}
|
|
53
|
+
.btn.secondary{background:transparent;color:var(--accent)}
|
|
54
|
+
.btn.secondary:hover{background:var(--accent-tint)}
|
|
55
|
+
.btn[disabled]{opacity:.65;cursor:progress}
|
|
56
|
+
.btn.block{width:100%;margin-top:8px}
|
|
57
|
+
.step .privacy{display:flex;align-items:flex-start;gap:8px;margin:12px 0 0;color:var(--muted);font-size:14px;line-height:20px}
|
|
58
|
+
.privacy svg{flex:none;margin-top:2px}
|
|
59
|
+
.error{margin:0 0 16px;padding:12px 16px;border-left:4px solid var(--bad);background:var(--bad-tint);border-radius:0 8px 8px 0;font-size:15px}
|
|
60
|
+
.error b{display:block}
|
|
61
|
+
.error .why{display:block;margin-top:4px;font-size:13px;color:var(--muted);overflow-wrap:anywhere}
|
|
62
|
+
.state{display:flex;align-items:center;gap:16px;margin:0 0 16px}
|
|
63
|
+
.state h1{margin:0}
|
|
64
|
+
.glyph{flex:none;width:40px;height:40px;border-radius:50%;display:grid;place-items:center}
|
|
65
|
+
.glyph.ok{background:var(--ok-tint);color:var(--ok)}
|
|
66
|
+
.glyph.bad{background:var(--bad-tint);color:var(--bad)}
|
|
67
|
+
.glyph.wait{border:4px solid var(--accent-tint);border-top-color:var(--accent);animation:r 1s linear infinite}
|
|
68
|
+
@keyframes r{to{transform:rotate(360deg)}}
|
|
69
|
+
@media (prefers-reduced-motion:reduce){.glyph.wait{animation:none}}
|
|
70
|
+
.lede{margin:0;color:var(--muted)}
|
|
71
|
+
.next{margin:32px 0 0;padding-top:24px;border-top:1px solid var(--line);font-weight:600}
|
|
72
|
+
@media (max-width:480px){.card{padding:24px 20px}h1{font-size:24px}}
|
|
73
|
+
`;
|
|
74
|
+
/** Show/hide for a secret, and a busy button so a second click cannot submit twice. */
|
|
75
|
+
const SCRIPT = `
|
|
76
|
+
document.querySelectorAll(".toggle").forEach(function(b){b.addEventListener("click",function(){
|
|
77
|
+
var i=document.getElementById(b.dataset.for),s=i.type==="password";i.type=s?"text":"password";b.textContent=s?"Hide":"Show"})});
|
|
78
|
+
document.querySelectorAll("form").forEach(function(f){f.addEventListener("submit",function(){
|
|
79
|
+
var g=f.querySelector(".btn.primary");if(g){g.disabled=true;g.textContent="Checking\\u2026"}})});
|
|
80
|
+
`;
|
|
81
|
+
const LOCK = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="4" y="11" width="16" height="10" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg>`;
|
|
82
|
+
const OUT = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 4h6v6M20 4l-9 9M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/></svg>`;
|
|
83
|
+
const TICK = `<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12.5l4.5 4.5L19 7.5"/></svg>`;
|
|
84
|
+
const CROSS = `<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg>`;
|
|
85
|
+
/** `1 Title`: the number and the title on one line, so the step's content below shares the card's one left edge. */
|
|
86
|
+
export function step(number, title, content, options = {}) {
|
|
87
|
+
return (`<section class="step"><header${options.bare ? ' class="bare"' : ""}><span class="num" aria-hidden="true">${number}</span>` +
|
|
88
|
+
`<h2>${escapeHtml(title)}</h2></header>${content}</section>`);
|
|
89
|
+
}
|
|
90
|
+
/** A sentence under a step's title. `html` is trusted markup: use `<b>` for a name the person must find on another site. */
|
|
91
|
+
export const prose = (html) => `<p>${html}</p>`;
|
|
92
|
+
/** A button that opens another site in a new tab (secondary: the step's real action is the primary button below). */
|
|
93
|
+
export function openButton(label, href) {
|
|
94
|
+
return `<a class="btn secondary" href="${escapeHtml(href)}" target="_blank" rel="noopener noreferrer">${escapeHtml(label)} ${OUT}</a>`;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* A labelled input. `label` is the name the other site gives the value, so the person never has to map one to the other.
|
|
98
|
+
* A secret gets a Show/Hide toggle; nothing is ever put back in a secret field after a refusal.
|
|
99
|
+
*/
|
|
100
|
+
export function field(input) {
|
|
101
|
+
const { id, label, placeholder, secret, value, invalid, autofocus } = input;
|
|
102
|
+
return (`<div class="field"><label for="${id}">${escapeHtml(label)}</label>` +
|
|
103
|
+
`<input id="${id}" name="${id}" class="${secret ? "" : "plain"}" type="${secret ? "password" : "text"}" ` +
|
|
104
|
+
`${placeholder ? `placeholder="${escapeHtml(placeholder)}" ` : ""}${value ? `value="${escapeHtml(value)}" ` : ""}` +
|
|
105
|
+
`${invalid ? 'aria-invalid="true" ' : ""}${autofocus ? "autofocus " : ""}` +
|
|
106
|
+
`autocomplete="off" autocapitalize="off" spellcheck="false" required>` +
|
|
107
|
+
`${secret ? `<button class="toggle" type="button" data-for="${id}">Show</button>` : ""}</div>`);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* What was refused, in bold, and what to do about it. Sits above the field it is about. `why` is the other side's own
|
|
111
|
+
* words, for someone who has to look it up: small and set apart, so the sentence the person reads stays one they can act on.
|
|
112
|
+
*/
|
|
113
|
+
export function refusal(headline, detail, why) {
|
|
114
|
+
return (`<div class="error" role="alert"><b>${escapeHtml(headline)}</b>${detail ? escapeHtml(detail) : ""}` +
|
|
115
|
+
`${why ? `<span class="why">${escapeHtml(why)}</span>` : ""}</div>`);
|
|
116
|
+
}
|
|
117
|
+
export const submit = (label) => `<button class="btn primary block" type="submit">${escapeHtml(label)}</button>`;
|
|
118
|
+
/** The promise about where a pasted value goes. Said once, the same way, on every page that asks for one. */
|
|
119
|
+
export const privacy = () => `<p class="privacy">${LOCK}<span>Stays on this computer. Never shared with your coding agent.</span></p>`;
|
|
120
|
+
/** A page that asks for something: a heading, then the steps, in one form posting back to this address. */
|
|
121
|
+
export function formCard(heading, steps) {
|
|
122
|
+
return `<form class="card" method="post"><h1>${escapeHtml(heading)}</h1>${steps}</form>`;
|
|
123
|
+
}
|
|
124
|
+
/** A page that says where things stand: working (it moves on by itself), done, or stopped. */
|
|
125
|
+
export function statusCard(input) {
|
|
126
|
+
const glyph = input.state === "working"
|
|
127
|
+
? `<span class="glyph wait" role="status" aria-label="Working"></span>`
|
|
128
|
+
: `<span class="glyph ${input.state === "done" ? "ok" : "bad"}">${input.state === "done" ? TICK : CROSS}</span>`;
|
|
129
|
+
return (`<div class="card"><div class="state">${glyph}<h1>${escapeHtml(input.heading)}</h1></div>` +
|
|
130
|
+
`${input.lede ? `<p class="lede">${escapeHtml(input.lede)}</p>` : ""}` +
|
|
131
|
+
`${input.next ? `<p class="next">${escapeHtml(input.next)}</p>` : ""}</div>`);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Send a page. The headers are those of a page that takes a secret: nothing cached, nothing framed, nothing loaded from
|
|
135
|
+
* elsewhere (a nonce admits only this page's own style and script). No `form-action`: browsers apply it to the redirects a
|
|
136
|
+
* submission is answered with, and Slack's page answers one by sending the browser on to Slack.
|
|
137
|
+
*/
|
|
138
|
+
export function sendPage(response, status, page) {
|
|
139
|
+
const nonce = randomBytes(16).toString("base64");
|
|
140
|
+
response.statusCode = status;
|
|
141
|
+
response.setHeader("content-type", "text/html; charset=utf-8");
|
|
142
|
+
response.setHeader("cache-control", "no-store");
|
|
143
|
+
response.setHeader("referrer-policy", "no-referrer");
|
|
144
|
+
response.setHeader("x-content-type-options", "nosniff");
|
|
145
|
+
response.setHeader("content-security-policy", `default-src 'none'; style-src 'nonce-${nonce}'; script-src 'nonce-${nonce}'; base-uri 'none'; frame-ancestors 'none'`);
|
|
146
|
+
response.end(`<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">` +
|
|
147
|
+
`${page.head ?? ""}<title>${escapeHtml(page.title)}</title><style nonce="${nonce}">${CSS}</style>` +
|
|
148
|
+
`<main><p class="brand">FastAgent</p>${page.card}</main><script nonce="${nonce}">${SCRIPT}</script>`);
|
|
149
|
+
}
|
package/dist/slack.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/** `@fastagent-sh/fastagent/slack` — the first-party Slack Events API bot-channel surface. */
|
|
2
|
-
export { slackChannel, defaultSlackRoute, slackEnvelope, verifySlackSignature, type SlackChannelOptions, type SlackEventEnvelope, type SlackFile, type SlackMessageEvent, type SlackRendering, type SlackRoute, type SlackFailure, } from "./channels/slack/slack.ts";
|
|
2
|
+
export { slackChannel, slackSocketChannel, defaultSlackRoute, slackEnvelope, verifySlackSignature, type SlackChannelOptions, type SlackSocketChannelOptions, type SlackEventEnvelope, type SlackFile, type SlackMessageEvent, type SlackRendering, type SlackRoute, type SlackFailure, } from "./channels/slack/slack.ts";
|
|
3
3
|
export { slackThreads, slackTransport, type SlackTransport } from "./channels/slack/shared-api.ts";
|
|
4
4
|
export type { SentSlackMessage, SlackTarget, UploadedSlackFile } from "./channels/slack/slack-api.ts";
|
package/dist/slack.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
/** `@fastagent-sh/fastagent/slack` — the first-party Slack Events API bot-channel surface. */
|
|
2
|
-
export { slackChannel, defaultSlackRoute, slackEnvelope, verifySlackSignature, } from "./channels/slack/slack.js";
|
|
2
|
+
export { slackChannel, slackSocketChannel, defaultSlackRoute, slackEnvelope, verifySlackSignature, } from "./channels/slack/slack.js";
|
|
3
3
|
export { slackThreads, slackTransport } from "./channels/slack/shared-api.js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text without the characters at its ends that `isEdge` names, found by index. A regex for it (`/^-+|-+$/`, `/\/+$/`)
|
|
3
|
+
* backtracks quadratically on a long run that is not at the end, and what is trimmed here is often a name or a URL
|
|
4
|
+
* somebody else wrote.
|
|
5
|
+
*/
|
|
6
|
+
export declare function stripEdges(text: string, isEdge: (char: string) => boolean): string;
|
|
7
|
+
/** `text` without the characters at its end that `isEdge` names. */
|
|
8
|
+
export declare function stripEnd(text: string, isEdge: (char: string) => boolean): string;
|
|
9
|
+
/** A URL or path without the `/` that ends it. */
|
|
10
|
+
export declare function stripTrailingSlashes(text: string): string;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text without the characters at its ends that `isEdge` names, found by index. A regex for it (`/^-+|-+$/`, `/\/+$/`)
|
|
3
|
+
* backtracks quadratically on a long run that is not at the end, and what is trimmed here is often a name or a URL
|
|
4
|
+
* somebody else wrote.
|
|
5
|
+
*/
|
|
6
|
+
export function stripEdges(text, isEdge) {
|
|
7
|
+
let start = 0;
|
|
8
|
+
while (start < text.length && isEdge(text[start]))
|
|
9
|
+
start++;
|
|
10
|
+
return stripEnd(text.slice(start), isEdge);
|
|
11
|
+
}
|
|
12
|
+
/** `text` without the characters at its end that `isEdge` names. */
|
|
13
|
+
export function stripEnd(text, isEdge) {
|
|
14
|
+
let end = text.length;
|
|
15
|
+
while (end > 0 && isEdge(text[end - 1]))
|
|
16
|
+
end--;
|
|
17
|
+
return text.slice(0, end);
|
|
18
|
+
}
|
|
19
|
+
/** A URL or path without the `/` that ends it. */
|
|
20
|
+
export function stripTrailingSlashes(text) {
|
|
21
|
+
return stripEnd(text, (char) => char === "/");
|
|
22
|
+
}
|
package/dist/telegram.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
/** `@fastagent-sh/fastagent/telegram` — the Telegram bot channel subpath export, kept off the root surface. */
|
|
2
|
-
export { telegramChannel, defaultTelegramRoute, telegramEnvelope, telegramStop, type TelegramChannelOptions, type TelegramUpdate, type TelegramMessage, type TelegramRoute, type TelegramFailure, } from "./channels/telegram/telegram.ts";
|
|
2
|
+
export { telegramChannel, telegramPollingChannel, defaultTelegramRoute, telegramEnvelope, telegramStop, type TelegramChannelOptions, type TelegramPollingChannelOptions, type TelegramUpdate, type TelegramMessage, type TelegramRoute, type TelegramFailure, } from "./channels/telegram/telegram.ts";
|
|
3
3
|
export { telegramTransport, type TelegramSendTarget, type TelegramSent, type TelegramTransport, } from "./channels/telegram/shared-api.ts";
|
package/dist/telegram.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
/** `@fastagent-sh/fastagent/telegram` — the Telegram bot channel subpath export, kept off the root surface. */
|
|
2
|
-
export { telegramChannel, defaultTelegramRoute, telegramEnvelope, telegramStop, } from "./channels/telegram/telegram.js";
|
|
2
|
+
export { telegramChannel, telegramPollingChannel, defaultTelegramRoute, telegramEnvelope, telegramStop, } from "./channels/telegram/telegram.js";
|
|
3
3
|
export { telegramTransport, } from "./channels/telegram/shared-api.js";
|
package/dist/tunnel.d.ts
CHANGED
|
@@ -26,7 +26,6 @@ export declare function startCloudflareTunnel(port: number, spawnFn?: SpawnCloud
|
|
|
26
26
|
/** Print the public URL and wire up first-party webhook channels found under `dir` (the agent ROOT). */
|
|
27
27
|
export declare function announceWebhooks(dir: string, baseUrl: string, channels: readonly DeclaredChannel[], opts?: {
|
|
28
28
|
openUrl?: (url: string) => void;
|
|
29
|
-
stateRoot?: string;
|
|
30
29
|
env?: NodeJS.ProcessEnv;
|
|
31
30
|
/** {@link Registrars.leave}. */
|
|
32
31
|
leave?: (step: string) => void;
|
package/dist/tunnel.js
CHANGED
|
@@ -5,13 +5,12 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { spawn } from "node:child_process";
|
|
7
7
|
import { registerFeishuWebhook } from "./channels/feishu/register-webhook.js";
|
|
8
|
-
import { registerSlackWebhook } from "./channels/slack/register-webhook.js";
|
|
9
8
|
import { registerTelegramWebhook } from "./channels/telegram/register-webhook.js";
|
|
10
|
-
import { pointChannelsAt
|
|
9
|
+
import { pointChannelsAt } from "./deploy/channel-ingress.js";
|
|
10
|
+
import { webhookKinds } from "./channels/webhooks.js";
|
|
11
11
|
import { loadDotEnv } from "./env.js";
|
|
12
12
|
import { dotEnvPath } from "./value-file.js";
|
|
13
13
|
import { installProxyFetch } from "./proxy.js";
|
|
14
|
-
import { AGENT_ENVIRONMENT_ENV, resolveStateRoot } from "./paths.js";
|
|
15
14
|
import { log } from "./log.js";
|
|
16
15
|
// `(?!api\.)`: cloudflared's ERROR lines mention its request endpoint (`https://api.trycloudflare.com/tunnel`, e.g.
|
|
17
16
|
// "failed to request quick Tunnel: Post ... timeout" under a flaky proxy) — without the exclusion a transient error
|
|
@@ -157,11 +156,6 @@ export async function announceWebhooks(dir, baseUrl, channels, opts = {}) {
|
|
|
157
156
|
registrars: {
|
|
158
157
|
...(opts.leave === undefined ? {} : { leave: opts.leave }),
|
|
159
158
|
telegram: (url) => registerTelegramWebhook(url, { env: opts.env }),
|
|
160
|
-
slack: (url) => registerSlackWebhook(url, {
|
|
161
|
-
stateRoot: opts.stateRoot ?? resolveStateRoot(dir),
|
|
162
|
-
environment: process.env[AGENT_ENVIRONMENT_ENV] === "production" ? "production" : "dev",
|
|
163
|
-
log: (message) => log.info(message),
|
|
164
|
-
}),
|
|
165
159
|
feishu: (url, kind) => registerFeishuWebhook(url, kind, feishuOptions),
|
|
166
160
|
},
|
|
167
161
|
});
|
package/dist/value-file.d.ts
CHANGED
|
@@ -7,7 +7,9 @@ export declare function parseEnvContent(content: string): Map<string, string>;
|
|
|
7
7
|
* is no values, which is normal.
|
|
8
8
|
*/
|
|
9
9
|
export declare function loadEnvValues(file: string): Map<string, string>;
|
|
10
|
-
/**
|
|
10
|
+
/** Use `path` as the agent's value file from here on, or the default again. */
|
|
11
|
+
export declare function useValueFile(path: string | undefined): void;
|
|
12
|
+
/** The agent's `.env` file: the one `--env-file` named, else `<resolved secrets dir>/.env`. */
|
|
11
13
|
export declare function dotEnvPath(agentDir: string, env?: NodeJS.ProcessEnv): string;
|
|
12
14
|
/** The committable template: `<agentDir>/.secrets/.env.example`. */
|
|
13
15
|
export declare function envExamplePath(agentDir: string): string;
|
|
@@ -31,10 +33,14 @@ export interface ValueWriteResult {
|
|
|
31
33
|
/** Set when the secrets dir is an operator-chosen one (`FASTAGENT_SECRETS_DIR`) carrying no `.gitignore`. */
|
|
32
34
|
unprotectedSecretsDir?: string;
|
|
33
35
|
}
|
|
34
|
-
/** What a command says when it wrote a secret into a secrets directory fastagent does not own and git may track. */
|
|
35
|
-
export declare function unprotectedSecretsNote(dir: string): string;
|
|
36
36
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
37
|
+
* What a command says when it wrote a secret into a secrets directory fastagent does not own and git may track; `chosenBy`
|
|
38
|
+
* is what put the secrets there (the operator's `FASTAGENT_SECRETS_DIR`, unless the caller knows better).
|
|
39
39
|
*/
|
|
40
|
-
export declare function
|
|
40
|
+
export declare function unprotectedSecretsNote(dir: string, chosenBy?: string): string;
|
|
41
|
+
export declare function prepareSecretsDirectory(dir: string, secretsDir: string): Promise<string | undefined>;
|
|
42
|
+
/**
|
|
43
|
+
* Write `entries` into `file` (default: the selected environment's value file), under a `# --- <section> ---` line. A
|
|
44
|
+
* name with a non-empty active value is kept unless `overwrite` names it.
|
|
45
|
+
*/
|
|
46
|
+
export declare function writeValues(dir: string, section: string, entries: readonly ValueEntry[], overwrite?: readonly string[], file?: string): Promise<ValueWriteResult>;
|
package/dist/value-file.js
CHANGED
|
@@ -44,9 +44,19 @@ export function loadEnvValues(file) {
|
|
|
44
44
|
throw error;
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* The value file this process's command was told to use (`--env-file`). Process state set at the command's entry, not
|
|
49
|
+
* an environment variable: a variable would be read from a shell, by commands that never took the flag, and by what
|
|
50
|
+
* the agent runs. A `dev` worker is spawned with its supervisor's arguments and takes the flag itself.
|
|
51
|
+
*/
|
|
52
|
+
let selectedValueFile;
|
|
53
|
+
/** Use `path` as the agent's value file from here on, or the default again. */
|
|
54
|
+
export function useValueFile(path) {
|
|
55
|
+
selectedValueFile = path;
|
|
56
|
+
}
|
|
57
|
+
/** The agent's `.env` file: the one `--env-file` named, else `<resolved secrets dir>/.env`. */
|
|
48
58
|
export function dotEnvPath(agentDir, env = process.env) {
|
|
49
|
-
return join(resolveSecretsDir(agentDir, env), ".env");
|
|
59
|
+
return selectedValueFile ?? join(resolveSecretsDir(agentDir, env), ".env");
|
|
50
60
|
}
|
|
51
61
|
/** The committable template: `<agentDir>/.secrets/.env.example`. */
|
|
52
62
|
export function envExamplePath(agentDir) {
|
|
@@ -64,11 +74,28 @@ export function agentValues(agentDir) {
|
|
|
64
74
|
values[key] = value;
|
|
65
75
|
return values;
|
|
66
76
|
}
|
|
67
|
-
/**
|
|
68
|
-
|
|
69
|
-
|
|
77
|
+
/**
|
|
78
|
+
* What a command says when it wrote a secret into a secrets directory fastagent does not own and git may track; `chosenBy`
|
|
79
|
+
* is what put the secrets there (the operator's `FASTAGENT_SECRETS_DIR`, unless the caller knows better).
|
|
80
|
+
*/
|
|
81
|
+
export function unprotectedSecretsNote(dir, chosenBy = "FASTAGENT_SECRETS_DIR") {
|
|
82
|
+
return (`${dir} (${chosenBy}) now holds a secret and has no .gitignore — that directory is yours, so ` +
|
|
70
83
|
"fastagent will not write one into it. Make sure git does not track what is in there.");
|
|
71
84
|
}
|
|
85
|
+
export async function prepareSecretsDirectory(dir, secretsDir) {
|
|
86
|
+
await mkdir(secretsDir, { recursive: true });
|
|
87
|
+
const owned = secretsDir === join(dir, SECRETS_DIRNAME) || secretsDir === join(dir, SECRETS_DIRNAME, "production");
|
|
88
|
+
if (owned) {
|
|
89
|
+
// EEXIST preserves an existing ignore file, including one created by a concurrent writer.
|
|
90
|
+
await writeFile(join(secretsDir, ".gitignore"), baseTemplate("secrets.gitignore"), { flag: "wx" }).catch((error) => {
|
|
91
|
+
if (error.code !== "EEXIST")
|
|
92
|
+
throw error;
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
else if (!(await exists(join(secretsDir, ".gitignore")))) {
|
|
96
|
+
return secretsDir;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
72
99
|
/** Whether `.env` content carries a non-empty ACTIVE value for `name`. */
|
|
73
100
|
function hasActiveEnvValue(content, name) {
|
|
74
101
|
return (parseEnvContent(content).get(name)?.trim() ?? "") !== "";
|
|
@@ -77,27 +104,11 @@ function mentionsEnvName(content, name) {
|
|
|
77
104
|
return content.split("\n").some((line) => new RegExp(`^\\s*#?\\s*${name}\\s*=`).test(line));
|
|
78
105
|
}
|
|
79
106
|
/**
|
|
80
|
-
* Write `entries` into the selected environment's value file, under a `# --- <section> ---` line. A
|
|
81
|
-
* non-empty active value is kept unless `overwrite` names it.
|
|
107
|
+
* Write `entries` into `file` (default: the selected environment's value file), under a `# --- <section> ---` line. A
|
|
108
|
+
* name with a non-empty active value is kept unless `overwrite` names it.
|
|
82
109
|
*/
|
|
83
|
-
export async function writeValues(dir, section, entries, overwrite = []) {
|
|
84
|
-
const
|
|
85
|
-
const secretsDir = dirname(file);
|
|
86
|
-
await mkdir(secretsDir, { recursive: true });
|
|
87
|
-
// THE one exception to "fastagent has no opinion about git": the directory it writes secrets into carries its own
|
|
88
|
-
// `.gitignore`.
|
|
89
|
-
const owned = secretsDir === join(dir, SECRETS_DIRNAME) || secretsDir === join(dir, SECRETS_DIRNAME, "production");
|
|
90
|
-
let unprotectedSecretsDir;
|
|
91
|
-
if (owned) {
|
|
92
|
-
// Only EEXIST is tolerable (already protected, or a concurrent writer).
|
|
93
|
-
await writeFile(join(secretsDir, ".gitignore"), baseTemplate("secrets.gitignore"), { flag: "wx" }).catch((e) => {
|
|
94
|
-
if (e.code !== "EEXIST")
|
|
95
|
-
throw e;
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
else if (!(await exists(join(secretsDir, ".gitignore")))) {
|
|
99
|
-
unprotectedSecretsDir = secretsDir;
|
|
100
|
-
}
|
|
110
|
+
export async function writeValues(dir, section, entries, overwrite = [], file = dotEnvPath(dir)) {
|
|
111
|
+
const unprotectedSecretsDir = await prepareSecretsDirectory(dir, dirname(file));
|
|
101
112
|
let current = "";
|
|
102
113
|
try {
|
|
103
114
|
current = await readFile(file, "utf8");
|
package/dist/version.d.ts
CHANGED
|
@@ -3,8 +3,14 @@
|
|
|
3
3
|
* scaffolded dependency range (`^${version}`), so `init` must never fall back and scaffold an uninstallable `^0.0.0`.
|
|
4
4
|
*/
|
|
5
5
|
export declare function fastagentVersion(): Promise<string>;
|
|
6
|
-
/**
|
|
6
|
+
/** The packages an agent's code imports besides fastagent: the harness its tools and providers are written for. */
|
|
7
|
+
export declare const HARNESS_PACKAGES: string[];
|
|
8
|
+
/**
|
|
9
|
+
* This build's version, the exact versions of the harness packages it runs, and the TypeScript `fastagent build`
|
|
10
|
+
* compiles with (FastAgent's own), for a new agent to depend on.
|
|
11
|
+
*/
|
|
7
12
|
export declare function fastagentBuild(): Promise<{
|
|
8
13
|
version: string;
|
|
9
14
|
harness: Record<string, string>;
|
|
15
|
+
typescript: string;
|
|
10
16
|
}>;
|
package/dist/version.js
CHANGED
|
@@ -8,8 +8,11 @@ export async function fastagentVersion() {
|
|
|
8
8
|
return (await fastagentBuild()).version;
|
|
9
9
|
}
|
|
10
10
|
/** The packages an agent's code imports besides fastagent: the harness its tools and providers are written for. */
|
|
11
|
-
const HARNESS_PACKAGES = ["@earendil-works/pi-durable", "@earendil-works/pi-ai"];
|
|
12
|
-
/**
|
|
11
|
+
export const HARNESS_PACKAGES = ["@earendil-works/pi-durable", "@earendil-works/pi-ai", "@earendil-works/pi-schema"];
|
|
12
|
+
/**
|
|
13
|
+
* This build's version, the exact versions of the harness packages it runs, and the TypeScript `fastagent build`
|
|
14
|
+
* compiles with (FastAgent's own), for a new agent to depend on.
|
|
15
|
+
*/
|
|
13
16
|
export async function fastagentBuild() {
|
|
14
17
|
const pkgPath = fileURLToPath(new URL("../package.json", import.meta.url));
|
|
15
18
|
const pkg = JSON.parse(await readFile(pkgPath, "utf8"));
|
|
@@ -22,5 +25,8 @@ export async function fastagentBuild() {
|
|
|
22
25
|
throw new Error(`${pkgPath} does not depend on ${name}`);
|
|
23
26
|
harness[name] = range;
|
|
24
27
|
}
|
|
25
|
-
|
|
28
|
+
const typescript = pkg.devDependencies?.typescript;
|
|
29
|
+
if (typescript === undefined)
|
|
30
|
+
throw new Error(`${pkgPath} does not develop with typescript`);
|
|
31
|
+
return { version: pkg.version, harness, typescript };
|
|
26
32
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fastagent-sh/fastagent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.0",
|
|
4
4
|
"description": "Vibe first. Then FastAgent: turn a local agent directory into a live service in your app, on Telegram, Slack, Feishu, or behind any channel.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
@@ -77,7 +77,6 @@
|
|
|
77
77
|
"build": "tsc -p tsconfig.build.json",
|
|
78
78
|
"prepack": "npm run build",
|
|
79
79
|
"test": "vitest --run",
|
|
80
|
-
"test:live": "vitest --run --config vitest.live.config.ts",
|
|
81
80
|
"typecheck": "tsc --noEmit",
|
|
82
81
|
"lint": "biome check src test && node scripts/check-doc-links.mjs && knip",
|
|
83
82
|
"format": "biome check --write src test",
|
|
@@ -89,12 +88,14 @@
|
|
|
89
88
|
},
|
|
90
89
|
"dependencies": {
|
|
91
90
|
"@clack/prompts": "^1.6.0",
|
|
92
|
-
"@earendil-works/chord": "1.
|
|
93
|
-
"@earendil-works/pi-ai": "1.
|
|
94
|
-
"@earendil-works/pi-durable": "1.
|
|
91
|
+
"@earendil-works/chord": "1.5.0",
|
|
92
|
+
"@earendil-works/pi-ai": "1.5.0",
|
|
93
|
+
"@earendil-works/pi-durable": "1.5.0",
|
|
94
|
+
"@earendil-works/pi-schema": "1.5.0",
|
|
95
95
|
"@effect/platform-bun": "^4.0.7",
|
|
96
96
|
"@effect/platform-node": "^4.0.7",
|
|
97
97
|
"@larksuiteoapi/node-sdk": "^1.76.0",
|
|
98
|
+
"@modelcontextprotocol/client": "2.4.0",
|
|
98
99
|
"@smithy/signature-v4": "^5.7.4",
|
|
99
100
|
"chokidar": "^6.0.0",
|
|
100
101
|
"commander": "^15.0.0",
|
|
@@ -108,8 +109,9 @@
|
|
|
108
109
|
},
|
|
109
110
|
"devDependencies": {
|
|
110
111
|
"@biomejs/biome": "^2.6.4",
|
|
111
|
-
"@earendil-works/pi-coding-agent": "1.
|
|
112
|
+
"@earendil-works/pi-coding-agent": "1.5.0",
|
|
112
113
|
"@hono/node-server": "^2.1.5",
|
|
114
|
+
"@modelcontextprotocol/server": "2.4.0",
|
|
113
115
|
"@types/express": "^5.0.6",
|
|
114
116
|
"@types/proper-lockfile": "^4.1.4",
|
|
115
117
|
"express": "^5.3.0",
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import type { FeishuSubscriptionMode } from "../feishu/setup-mode.ts";
|
|
2
|
-
/** Guided Lark-international onboarding. */
|
|
3
|
-
export declare const LARK_CONSOLE_URL = "https://open.larksuite.com/page/launcher?from=backend_oneclick";
|
|
4
|
-
/** Directly where the fallback token lives (and where the successful automatic mode change is visible). */
|
|
5
|
-
export declare function larkEventSecurityUrl(appId: string): string;
|
|
6
|
-
export interface LarkOnboardIO {
|
|
7
|
-
/** A page for the person, and what they do there. */
|
|
8
|
-
openUrl(url: string, action: string): void;
|
|
9
|
-
note(message: string): void;
|
|
10
|
-
prompt(message: string, opts?: {
|
|
11
|
-
hidden?: boolean;
|
|
12
|
-
}): Promise<string | undefined>;
|
|
13
|
-
}
|
|
14
|
-
interface LarkBootstrapResult {
|
|
15
|
-
/** Challenge-captured token: the PATCH also switched Subscription mode to webhook. */
|
|
16
|
-
token?: string;
|
|
17
|
-
/** Present only for a definitive config-route 404; tells the user why the manual path is active. */
|
|
18
|
-
manualReason?: string;
|
|
19
|
-
}
|
|
20
|
-
export interface LarkOnboardOptions {
|
|
21
|
-
/** Existing active .env values. */
|
|
22
|
-
existing?: Readonly<Record<string, string | undefined>>;
|
|
23
|
-
ingress?: FeishuSubscriptionMode;
|
|
24
|
-
verifyCredentials(appId: string, appSecret: string): Promise<void>;
|
|
25
|
-
bootstrapWebhook?(appId: string, appSecret: string): Promise<LarkBootstrapResult>;
|
|
26
|
-
/** The command that continues an interrupted setup: the one that started it (`add lark`, `deploy <host> --run`). */
|
|
27
|
-
rerun?: string;
|
|
28
|
-
}
|
|
29
|
-
export interface LarkOnboardCredentials {
|
|
30
|
-
LARK_APP_ID: string;
|
|
31
|
-
LARK_APP_SECRET: string;
|
|
32
|
-
LARK_VERIFICATION_TOKEN?: string;
|
|
33
|
-
}
|
|
34
|
-
/** Open the stable app console and collect everything the runtime needs. */
|
|
35
|
-
export declare function onboardLarkApp(io: LarkOnboardIO, opts: LarkOnboardOptions): Promise<LarkOnboardCredentials>;
|
|
36
|
-
export {};
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/** Guided Lark-international onboarding. */
|
|
2
|
-
export const LARK_CONSOLE_URL = "https://open.larksuite.com/page/launcher?from=backend_oneclick";
|
|
3
|
-
/** Directly where the fallback token lives (and where the successful automatic mode change is visible). */
|
|
4
|
-
export function larkEventSecurityUrl(appId) {
|
|
5
|
-
return `https://open.larksuite.com/app/${encodeURIComponent(appId)}/event?tab=safe`;
|
|
6
|
-
}
|
|
7
|
-
function required(value, name, rerun) {
|
|
8
|
-
const trimmed = value?.trim();
|
|
9
|
-
if (!trimmed)
|
|
10
|
-
throw new Error(`${name} is required — re-run \`${rerun}\` to continue setup`);
|
|
11
|
-
return trimmed;
|
|
12
|
-
}
|
|
13
|
-
/** Open the stable app console and collect everything the runtime needs. */
|
|
14
|
-
export async function onboardLarkApp(io, opts) {
|
|
15
|
-
const rerun = opts.rerun ?? "fastagent add lark";
|
|
16
|
-
const existingId = opts.existing?.LARK_APP_ID?.trim();
|
|
17
|
-
const existingSecret = opts.existing?.LARK_APP_SECRET?.trim();
|
|
18
|
-
const reuseExistingApp = Boolean(existingId && existingSecret);
|
|
19
|
-
// Credentials are one App-scoped set.
|
|
20
|
-
if (reuseExistingApp) {
|
|
21
|
-
io.note(`Reusing Lark app ${existingId}.`);
|
|
22
|
-
}
|
|
23
|
-
else {
|
|
24
|
-
io.openUrl(LARK_CONSOLE_URL, "create the app on Lark's one-click launcher; LARK_APP_ID and LARK_APP_SECRET are under Credentials & Basic Info");
|
|
25
|
-
}
|
|
26
|
-
const appId = required(reuseExistingApp ? existingId : await io.prompt("LARK_APP_ID (Credentials & Basic Info)", { hidden: false }), "LARK_APP_ID", rerun);
|
|
27
|
-
const appSecret = required(reuseExistingApp ? existingSecret : await io.prompt("LARK_APP_SECRET (Credentials & Basic Info)", { hidden: true }), "LARK_APP_SECRET", rerun);
|
|
28
|
-
await opts.verifyCredentials(appId, appSecret);
|
|
29
|
-
const eventSecurityUrl = larkEventSecurityUrl(appId);
|
|
30
|
-
io.note("App ID / Secret verified.");
|
|
31
|
-
if (opts.ingress === "websocket") {
|
|
32
|
-
io.openUrl(eventSecurityUrl, "in Events & Callbacks, choose long connection and subscribe im.message.receive_v1, but do not publish yet: " +
|
|
33
|
-
"the permission check follows (no Verification Token or Request URL is needed)");
|
|
34
|
-
return { LARK_APP_ID: appId, LARK_APP_SECRET: appSecret };
|
|
35
|
-
}
|
|
36
|
-
io.note("Trying automatic webhook-mode + Verification-Token bootstrap…");
|
|
37
|
-
if (!opts.bootstrapWebhook)
|
|
38
|
-
throw new Error("Lark webhook onboarding requires bootstrapWebhook");
|
|
39
|
-
const bootstrap = await opts.bootstrapWebhook(appId, appSecret);
|
|
40
|
-
if (bootstrap.token) {
|
|
41
|
-
io.note("Verification Token captured; Subscription mode changed to webhook in the app draft.");
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
io.note(bootstrap.manualReason ?? "Automatic bootstrap unavailable.");
|
|
45
|
-
io.openUrl(eventSecurityUrl, "in Events & Callbacks → Encryption Strategy, copy the Verification Token, and switch Subscription mode to " +
|
|
46
|
-
"webhook when setting the Request URL");
|
|
47
|
-
}
|
|
48
|
-
const verificationToken = required(bootstrap.token ||
|
|
49
|
-
(reuseExistingApp ? opts.existing?.LARK_VERIFICATION_TOKEN?.trim() : undefined) ||
|
|
50
|
-
(await io.prompt("LARK_VERIFICATION_TOKEN (Events & Callbacks → Encryption Strategy)", { hidden: true })), "LARK_VERIFICATION_TOKEN", rerun);
|
|
51
|
-
return {
|
|
52
|
-
LARK_APP_ID: appId,
|
|
53
|
-
LARK_APP_SECRET: appSecret,
|
|
54
|
-
LARK_VERIFICATION_TOKEN: verificationToken,
|
|
55
|
-
};
|
|
56
|
-
}
|