@fastagent-sh/fastagent 0.25.0 → 0.26.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 +3 -2
- package/dist/agent-api.d.ts +10 -2
- package/dist/agent-api.js +10 -2
- package/dist/authoring.d.ts +12 -8
- package/dist/authoring.js +14 -7
- package/dist/cli/add-feishu.js +1 -1
- package/dist/cli/add-slack.js +1 -1
- package/dist/cli/box-login.d.ts +3 -2
- package/dist/cli/commands/add.js +2 -1
- package/dist/cli/commands/deploy.js +1 -1
- package/dist/cli/commands/env.js +5 -4
- package/dist/cli/commands/init.js +3 -3
- package/dist/cli/commands/invoke.js +2 -1
- package/dist/cli/commands/login.js +6 -4
- package/dist/cli/login-relay.d.ts +4 -3
- package/dist/cli/login-relay.js +1 -1
- package/dist/cli/shared.js +5 -3
- package/dist/content/git.d.ts +1 -1
- package/dist/content/git.js +7 -4
- package/dist/content/resolve.d.ts +1 -1
- package/dist/content/resolve.js +2 -2
- package/dist/deploy/preflight.js +1 -1
- package/dist/dev-supervisor.js +1 -1
- package/dist/env.d.ts +0 -13
- package/dist/env.js +1 -44
- package/dist/environment/mise.d.ts +8 -7
- package/dist/environment/mise.js +24 -20
- package/dist/git.d.ts +2 -2
- package/dist/git.js +5 -5
- package/dist/harnesses/models/environment.d.ts +7 -2
- package/dist/harnesses/models/environment.js +16 -10
- package/dist/harnesses/models/host.d.ts +30 -0
- package/dist/harnesses/models/host.js +97 -0
- package/dist/harnesses/models/login.d.ts +38 -73
- package/dist/harnesses/models/login.js +50 -50
- package/dist/harnesses/models/models.d.ts +4 -3
- package/dist/harnesses/models/models.js +51 -12
- package/dist/harnesses/pi-durable/agent.js +2 -0
- package/dist/harnesses/pi-durable/open.d.ts +7 -0
- package/dist/harnesses/pi-durable/open.js +4 -3
- package/dist/harnesses/pi-durable/serve.d.ts +11 -1
- package/dist/harnesses/pi-durable/serve.js +42 -12
- package/dist/harnesses/pi-durable/wake.d.ts +8 -1
- package/dist/harnesses/pi-durable/wake.js +10 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +3 -0
- package/dist/scaffold/add-channel.js +1 -1
- package/dist/sign-in-api.d.ts +147 -0
- package/dist/sign-in-api.js +9 -0
- package/dist/tunnel.js +2 -1
- package/dist/value-file.d.ts +19 -0
- package/dist/value-file.js +61 -5
- package/package.json +1 -1
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model credentials and provider sign-in as an embedding host sees them: FastAgent's own types, so a host keeps the
|
|
3
|
+
* credentials where it chooses and runs a sign-in in its own UI without knowing the harness. Imports nothing; the
|
|
4
|
+
* functions over these types are in harnesses/models/host.ts.
|
|
5
|
+
*/
|
|
6
|
+
/** One provider's credential, kept by a {@link CredentialStore} whole, as it is given. */
|
|
7
|
+
export type Credential = {
|
|
8
|
+
type: "api_key";
|
|
9
|
+
key?: string;
|
|
10
|
+
env?: Record<string, string>;
|
|
11
|
+
} | {
|
|
12
|
+
type: "oauth";
|
|
13
|
+
access: string;
|
|
14
|
+
refresh: string;
|
|
15
|
+
expires: number;
|
|
16
|
+
[field: string]: unknown;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Where model credentials are kept, one per provider. `modify` is the only write, and it runs one at a time per
|
|
20
|
+
* provider (across processes, where the store can): an OAuth refresh happens inside it, and a rotated refresh token
|
|
21
|
+
* must never be used twice. A store it cannot read rejects; answering it as empty would let the next write replace
|
|
22
|
+
* every provider's credential.
|
|
23
|
+
*/
|
|
24
|
+
export interface CredentialStore {
|
|
25
|
+
/** The stored credential, possibly expired; undefined when the provider has none. */
|
|
26
|
+
read(provider: string): Promise<Credential | undefined>;
|
|
27
|
+
/** Which providers have a credential, and of what kind; never the secrets. */
|
|
28
|
+
list(): Promise<readonly {
|
|
29
|
+
provider: string;
|
|
30
|
+
type: Credential["type"];
|
|
31
|
+
}[]>;
|
|
32
|
+
/** `change` returns the new credential, or undefined to leave it as it is. Resolves with what is stored after. */
|
|
33
|
+
modify(provider: string, change: (current: Credential | undefined) => Promise<Credential | undefined>): Promise<Credential | undefined>;
|
|
34
|
+
delete(provider: string): Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
export type SignInMethod = Credential["type"];
|
|
37
|
+
/** One way to sign in to one provider, with what authenticates the provider now. */
|
|
38
|
+
export interface SignInOption {
|
|
39
|
+
provider: string;
|
|
40
|
+
/** The provider's name: "OpenAI", "OpenAI Codex (legacy)". */
|
|
41
|
+
name: string;
|
|
42
|
+
method: SignInMethod;
|
|
43
|
+
/** What signing in this way is called: the provider's own label ("Sign in with ChatGPT"), else a generic one. */
|
|
44
|
+
label: string;
|
|
45
|
+
/** An account sign-in backed by a provider subscription. */
|
|
46
|
+
subscription: boolean;
|
|
47
|
+
/** Its key comes from the environment only: there is no sign-in to run. */
|
|
48
|
+
external: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* What authenticates the provider now: a stored credential (`OAuth`, `stored credential`), else the environment
|
|
51
|
+
* (a variable such as `OPENAI_API_KEY`). One credential is stored per provider, so signing in with the other method
|
|
52
|
+
* replaces a stored one.
|
|
53
|
+
*/
|
|
54
|
+
status?: {
|
|
55
|
+
type: SignInMethod;
|
|
56
|
+
source: string;
|
|
57
|
+
};
|
|
58
|
+
/** What in the environment authenticates the provider with nothing stored: what still answers after a sign-out. */
|
|
59
|
+
environment?: string;
|
|
60
|
+
/**
|
|
61
|
+
* Why checking the environment for the provider failed: `environment` is then unknown, and so is `status` when
|
|
62
|
+
* nothing is stored.
|
|
63
|
+
*/
|
|
64
|
+
problem?: string;
|
|
65
|
+
}
|
|
66
|
+
/** A question a sign-in asks. `manual_code`: the address the browser ended on after signing in, or the code its page showed. */
|
|
67
|
+
export type SignInPrompt = {
|
|
68
|
+
signal?: AbortSignal;
|
|
69
|
+
} & ({
|
|
70
|
+
type: "text" | "secret" | "manual_code";
|
|
71
|
+
message: string;
|
|
72
|
+
placeholder?: string;
|
|
73
|
+
} | {
|
|
74
|
+
type: "select";
|
|
75
|
+
message: string;
|
|
76
|
+
options: readonly {
|
|
77
|
+
id: string;
|
|
78
|
+
label: string;
|
|
79
|
+
description?: string;
|
|
80
|
+
}[];
|
|
81
|
+
});
|
|
82
|
+
/** What a sign-in tells the person: a page to open, a code to enter there, progress. */
|
|
83
|
+
export type SignInEvent = {
|
|
84
|
+
type: "info";
|
|
85
|
+
message: string;
|
|
86
|
+
links?: readonly {
|
|
87
|
+
url: string;
|
|
88
|
+
label?: string;
|
|
89
|
+
}[];
|
|
90
|
+
} | {
|
|
91
|
+
type: "progress";
|
|
92
|
+
message: string;
|
|
93
|
+
} | {
|
|
94
|
+
type: "auth_url";
|
|
95
|
+
url: string;
|
|
96
|
+
instructions?: string;
|
|
97
|
+
} | {
|
|
98
|
+
type: "device_code";
|
|
99
|
+
userCode: string;
|
|
100
|
+
verificationUri: string;
|
|
101
|
+
intervalSeconds?: number;
|
|
102
|
+
expiresInSeconds?: number;
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* How a sign-in reaches the person. `prompt` resolves with the answer (a select's option `id`); it rejects with
|
|
106
|
+
* {@link SignInCancelled} when the person backs out, which ends the sign-in with that error, and with any other error
|
|
107
|
+
* when asking failed, which fails the sign-in with it. A prompt's `signal` aborts when the sign-in no longer needs the
|
|
108
|
+
* answer, such as a `manual_code` question a browser callback answered first.
|
|
109
|
+
*/
|
|
110
|
+
export interface SignInInteraction {
|
|
111
|
+
signal?: AbortSignal;
|
|
112
|
+
prompt(prompt: SignInPrompt): Promise<string>;
|
|
113
|
+
notify(event: SignInEvent): void;
|
|
114
|
+
}
|
|
115
|
+
export interface SignInRequest {
|
|
116
|
+
credentials: CredentialStore;
|
|
117
|
+
provider: string;
|
|
118
|
+
method: SignInMethod;
|
|
119
|
+
interaction: SignInInteraction;
|
|
120
|
+
/**
|
|
121
|
+
* A stable id of this installation, which Sign in with ChatGPT requires. `credentialFile` keeps one beside its
|
|
122
|
+
* file; a store of the host's own passes it here.
|
|
123
|
+
*/
|
|
124
|
+
deviceId?: string;
|
|
125
|
+
}
|
|
126
|
+
export interface SignInResult {
|
|
127
|
+
provider: string;
|
|
128
|
+
method: SignInMethod;
|
|
129
|
+
/** An API key is checked with one request before it is stored: accepted, or not decidable (stored). */
|
|
130
|
+
verified: "ok" | "unknown" | "n/a";
|
|
131
|
+
}
|
|
132
|
+
/** What a request to a provider authenticates with. */
|
|
133
|
+
export interface ProviderAuth {
|
|
134
|
+
type: SignInMethod;
|
|
135
|
+
/** Where it comes from: `OAuth`, `stored credential`, an environment variable's name. */
|
|
136
|
+
source?: string;
|
|
137
|
+
/** The key or access token a request sends. */
|
|
138
|
+
apiKey?: string;
|
|
139
|
+
/** Headers a request sends, where the provider authenticates by them (an `Authorization` of its own). */
|
|
140
|
+
headers?: Record<string, string>;
|
|
141
|
+
/** Where the request goes, where the credential decides it (an enterprise endpoint). */
|
|
142
|
+
baseUrl?: string;
|
|
143
|
+
}
|
|
144
|
+
/** The person backed out of a sign-in, or its signal aborted: a decision, not a failure. Nothing was stored. */
|
|
145
|
+
export declare class SignInCancelled extends Error {
|
|
146
|
+
name: string;
|
|
147
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model credentials and provider sign-in as an embedding host sees them: FastAgent's own types, so a host keeps the
|
|
3
|
+
* credentials where it chooses and runs a sign-in in its own UI without knowing the harness. Imports nothing; the
|
|
4
|
+
* functions over these types are in harnesses/models/host.ts.
|
|
5
|
+
*/
|
|
6
|
+
/** The person backed out of a sign-in, or its signal aborted: a decision, not a failure. Nothing was stored. */
|
|
7
|
+
export class SignInCancelled extends Error {
|
|
8
|
+
name = "SignInCancelled";
|
|
9
|
+
}
|
package/dist/tunnel.js
CHANGED
|
@@ -8,7 +8,8 @@ import { registerFeishuWebhook } from "./channels/feishu/register-webhook.js";
|
|
|
8
8
|
import { registerSlackWebhook } from "./channels/slack/register-webhook.js";
|
|
9
9
|
import { registerTelegramWebhook } from "./channels/telegram/register-webhook.js";
|
|
10
10
|
import { pointChannelsAt, webhookKinds } from "./deploy/channel-ingress.js";
|
|
11
|
-
import {
|
|
11
|
+
import { loadDotEnv } from "./env.js";
|
|
12
|
+
import { dotEnvPath } from "./value-file.js";
|
|
12
13
|
import { installProxyFetch } from "./proxy.js";
|
|
13
14
|
import { AGENT_ENVIRONMENT_ENV, resolveStateRoot } from "./paths.js";
|
|
14
15
|
import { log } from "./log.js";
|
package/dist/value-file.d.ts
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
/** Parse .env content into key → value (the dialect above; last occurrence of a key wins). */
|
|
2
|
+
export declare function parseEnvContent(content: string): Map<string, string>;
|
|
3
|
+
/**
|
|
4
|
+
* READ a value file without entering it: the values as data, never `process.env`. This is how code reasons about an
|
|
5
|
+
* environment OTHER than its own — `deploy` resolves configuration in the environment being deployed, which this
|
|
6
|
+
* file declares, and merging it into the running process would put the two environments in one bag. A missing file
|
|
7
|
+
* is no values, which is normal.
|
|
8
|
+
*/
|
|
9
|
+
export declare function loadEnvValues(file: string): Map<string, string>;
|
|
10
|
+
/** The agent's `.env` file: `<resolved secrets dir>/.env`. */
|
|
11
|
+
export declare function dotEnvPath(agentDir: string, env?: NodeJS.ProcessEnv): string;
|
|
12
|
+
/** The committable template: `<agentDir>/.secrets/.env.example`. */
|
|
13
|
+
export declare function envExamplePath(agentDir: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* What the agent's value file ({@link dotEnvPath}) adds to this process's environment, read and not entered: each
|
|
16
|
+
* variable the process does not hold, as entering the file would set it. An agent opened in a process that runs
|
|
17
|
+
* others too reads its own values through this; one that a command entered (`dev`, `start`) adds nothing here.
|
|
18
|
+
*/
|
|
19
|
+
export declare function agentValues(agentDir: string): Record<string, string>;
|
|
1
20
|
/** One variable to write: with a `value`, an active line; without one, a placeholder under its `hint`. */
|
|
2
21
|
export interface ValueEntry {
|
|
3
22
|
name: string;
|
package/dist/value-file.js
CHANGED
|
@@ -1,13 +1,69 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* The selected environment's value file (`.secrets/.env`, or `.secrets/production/.env` for a deployment), read and
|
|
3
|
+
* written; entering it into `process.env` is env.ts's. Read as data: what it declares, and what it adds to an agent's
|
|
4
|
+
* environment. Written: what a command generates (a channel's secret, the Agent API's key) goes in as an active
|
|
5
|
+
* `NAME=value` line, a value already set stays, and the secrets directory fastagent made carries its own `.gitignore`.
|
|
5
6
|
*/
|
|
7
|
+
import { readFileSync } from "node:fs";
|
|
6
8
|
import { appendFile, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
7
9
|
import { dirname, join } from "node:path";
|
|
8
|
-
import {
|
|
9
|
-
import { SECRETS_DIRNAME, SECRET_FILE_MODE, exists } from "./paths.js";
|
|
10
|
+
import { SECRETS_DIRNAME, SECRET_FILE_MODE, exists, resolveSecretsDir } from "./paths.js";
|
|
10
11
|
import { baseTemplate } from "./scaffold/templates.js";
|
|
12
|
+
/** Parse .env content into key → value (the dialect above; last occurrence of a key wins). */
|
|
13
|
+
export function parseEnvContent(content) {
|
|
14
|
+
const parsed = new Map();
|
|
15
|
+
for (const raw of content.split("\n")) {
|
|
16
|
+
const line = raw.trim();
|
|
17
|
+
if (!line || line.startsWith("#"))
|
|
18
|
+
continue;
|
|
19
|
+
const eq = line.indexOf("=");
|
|
20
|
+
if (eq < 1)
|
|
21
|
+
continue; // no `=`, or an empty key
|
|
22
|
+
const key = line.slice(0, eq).trim();
|
|
23
|
+
let value = line.slice(eq + 1).trim();
|
|
24
|
+
const quote = value[0];
|
|
25
|
+
if ((quote === '"' || quote === "'") && value.at(-1) === quote)
|
|
26
|
+
value = value.slice(1, -1);
|
|
27
|
+
parsed.set(key, value); // in-file: last occurrence wins (Map overwrite)
|
|
28
|
+
}
|
|
29
|
+
return parsed;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* READ a value file without entering it: the values as data, never `process.env`. This is how code reasons about an
|
|
33
|
+
* environment OTHER than its own — `deploy` resolves configuration in the environment being deployed, which this
|
|
34
|
+
* file declares, and merging it into the running process would put the two environments in one bag. A missing file
|
|
35
|
+
* is no values, which is normal.
|
|
36
|
+
*/
|
|
37
|
+
export function loadEnvValues(file) {
|
|
38
|
+
try {
|
|
39
|
+
return parseEnvContent(readFileSync(file, "utf8"));
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
if (error.code === "ENOENT")
|
|
43
|
+
return new Map();
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** The agent's `.env` file: `<resolved secrets dir>/.env`. */
|
|
48
|
+
export function dotEnvPath(agentDir, env = process.env) {
|
|
49
|
+
return join(resolveSecretsDir(agentDir, env), ".env");
|
|
50
|
+
}
|
|
51
|
+
/** The committable template: `<agentDir>/.secrets/.env.example`. */
|
|
52
|
+
export function envExamplePath(agentDir) {
|
|
53
|
+
return join(agentDir, SECRETS_DIRNAME, ".env.example");
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* What the agent's value file ({@link dotEnvPath}) adds to this process's environment, read and not entered: each
|
|
57
|
+
* variable the process does not hold, as entering the file would set it. An agent opened in a process that runs
|
|
58
|
+
* others too reads its own values through this; one that a command entered (`dev`, `start`) adds nothing here.
|
|
59
|
+
*/
|
|
60
|
+
export function agentValues(agentDir) {
|
|
61
|
+
const values = {};
|
|
62
|
+
for (const [key, value] of loadEnvValues(dotEnvPath(agentDir)))
|
|
63
|
+
if (!(key in process.env))
|
|
64
|
+
values[key] = value;
|
|
65
|
+
return values;
|
|
66
|
+
}
|
|
11
67
|
/** What a command says when it wrote a secret into a secrets directory fastagent does not own and git may track. */
|
|
12
68
|
export function unprotectedSecretsNote(dir) {
|
|
13
69
|
return (`${dir} (FASTAGENT_SECRETS_DIR) now holds a secret and has no .gitignore — that directory is yours, so ` +
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fastagent-sh/fastagent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.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",
|