@fastagent-sh/fastagent 0.25.0 → 0.26.1
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 +56 -56
- package/dist/harnesses/models/models.d.ts +4 -3
- package/dist/harnesses/models/models.js +51 -12
- package/dist/harnesses/pi-durable/agent.js +4 -0
- package/dist/harnesses/pi-durable/ids.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/parked.d.ts +18 -0
- package/dist/harnesses/pi-durable/parked.js +27 -0
- package/dist/harnesses/pi-durable/release.d.ts +3 -2
- package/dist/harnesses/pi-durable/serve.d.ts +11 -1
- package/dist/harnesses/pi-durable/serve.js +42 -12
- package/dist/harnesses/pi-durable/storage.js +4 -2
- package/dist/harnesses/pi-durable/wake.d.ts +8 -1
- package/dist/harnesses/pi-durable/wake.js +10 -1
- package/dist/http/contract.d.ts +3 -3
- package/dist/index.d.ts +5 -3
- package/dist/index.js +3 -0
- package/dist/scaffold/add-channel.js +1 -1
- package/dist/schedule/cron.d.ts +4 -2
- package/dist/schedule/cron.js +4 -2
- package/dist/schedule/eventbridge-cron.d.ts +7 -3
- package/dist/schedule/eventbridge-cron.js +9 -8
- 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 +16 -16
package/README.md
CHANGED
|
@@ -152,15 +152,16 @@ The package exports the supported surface only, from one entry per kind of user,
|
|
|
152
152
|
| The Agent API | `Agent`, `AgentError` ([Agent API](docs/SPEC.md)) | Stable within its version |
|
|
153
153
|
| Directory → agent → service | `openAgent`, `LocalAgent`, `serve()`'s `Service` (`fetch`, `listen`) | Usable now, may tighten before 1.0 |
|
|
154
154
|
| Over HTTP | `connectAgent` (`/client`), the `/v1` routes ([Agent API](docs/SPEC.md) §9) | Stable within its version |
|
|
155
|
+
| A host embedding agents | `createAgent`, `CredentialStore`, `credentialFile`, `signIn`, `signInOptions`, `providerAuth` ([embedding](docs/embedding.md#6-a-desktop-host)) | Usable now, may tighten before 1.0 |
|
|
155
156
|
| Authoring | `defineAgent`, `defineChannel`, `defineSchedule`, `needs`, `sessionIdOf`, `readBodyCapped`, `text`, `secretEquals` | Usable now, may tighten before 1.0 |
|
|
156
|
-
| Not exported | The assembly's parts (`router`, `createScheduler`), content,
|
|
157
|
+
| Not exported | The assembly's parts (`router`, `createScheduler`), content editing, the model environment, harness internals | No compatibility promise |
|
|
157
158
|
|
|
158
159
|
A tool or an extension is pi-durable's own (`defineTool`, `defineExtension` from `@earendil-works/pi-durable`), and a
|
|
159
160
|
custom provider is pi-ai's (`createProvider` from `@earendil-works/pi-ai`): `init` adds both packages to the agent.
|
|
160
161
|
|
|
161
162
|
Entry points (`./package.json` is also exported, for tools that read the version):
|
|
162
163
|
|
|
163
|
-
- `@fastagent-sh/fastagent` — the Agent API, `openAgent`, and what an author writes;
|
|
164
|
+
- `@fastagent-sh/fastagent` — the Agent API, `openAgent`, what a host embedding agents needs, and what an author writes;
|
|
164
165
|
- `@fastagent-sh/fastagent/client` — `connectAgent`, the Agent API over HTTP, for a program anywhere `fetch` runs;
|
|
165
166
|
- `@fastagent-sh/fastagent/telegram` — Telegram bot channel;
|
|
166
167
|
- `@fastagent-sh/fastagent/slack` — Slack Events API bot channel;
|
package/dist/agent-api.d.ts
CHANGED
|
@@ -50,11 +50,15 @@ declare const AgentInfo_base: Schema.Opaque<AgentInfo, Schema.Struct<{
|
|
|
50
50
|
readonly name: Schema.String;
|
|
51
51
|
readonly description: Schema.optionalKey<Schema.String>;
|
|
52
52
|
}>>;
|
|
53
|
-
/**
|
|
53
|
+
/**
|
|
54
|
+
* The models a session may name: those whose provider has a credential here. `contextWindow`, in tokens, is the
|
|
55
|
+
* window the agent runs the model with, absent when the model declares none.
|
|
56
|
+
*/
|
|
54
57
|
readonly models: Schema.$Array<Schema.Struct<{
|
|
55
58
|
readonly provider: Schema.String;
|
|
56
59
|
readonly modelId: Schema.String;
|
|
57
60
|
readonly name: Schema.optionalKey<Schema.String>;
|
|
61
|
+
readonly contextWindow: Schema.optionalKey<Schema.Number>;
|
|
58
62
|
}>>;
|
|
59
63
|
readonly defaultModel: Schema.optionalKey<Schema.Struct<{
|
|
60
64
|
readonly provider: Schema.String;
|
|
@@ -73,11 +77,15 @@ declare const AgentInfo_base: Schema.Opaque<AgentInfo, Schema.Struct<{
|
|
|
73
77
|
readonly name: Schema.String;
|
|
74
78
|
readonly description: Schema.optionalKey<Schema.String>;
|
|
75
79
|
}>>;
|
|
76
|
-
/**
|
|
80
|
+
/**
|
|
81
|
+
* The models a session may name: those whose provider has a credential here. `contextWindow`, in tokens, is the
|
|
82
|
+
* window the agent runs the model with, absent when the model declares none.
|
|
83
|
+
*/
|
|
77
84
|
readonly models: Schema.$Array<Schema.Struct<{
|
|
78
85
|
readonly provider: Schema.String;
|
|
79
86
|
readonly modelId: Schema.String;
|
|
80
87
|
readonly name: Schema.optionalKey<Schema.String>;
|
|
88
|
+
readonly contextWindow: Schema.optionalKey<Schema.Number>;
|
|
81
89
|
}>>;
|
|
82
90
|
readonly defaultModel: Schema.optionalKey<Schema.Struct<{
|
|
83
91
|
readonly provider: Schema.String;
|
package/dist/agent-api.js
CHANGED
|
@@ -28,8 +28,16 @@ export class AgentInfo extends Schema.Opaque()(Schema.Struct({
|
|
|
28
28
|
commands: Schema.Array(Named),
|
|
29
29
|
/** The tools a session may enable. */
|
|
30
30
|
tools: Schema.Array(Named),
|
|
31
|
-
/**
|
|
32
|
-
|
|
31
|
+
/**
|
|
32
|
+
* The models a session may name: those whose provider has a credential here. `contextWindow`, in tokens, is the
|
|
33
|
+
* window the agent runs the model with, absent when the model declares none.
|
|
34
|
+
*/
|
|
35
|
+
models: Schema.Array(Schema.Struct({
|
|
36
|
+
provider: Schema.String,
|
|
37
|
+
modelId: Schema.String,
|
|
38
|
+
name: Schema.optionalKey(Schema.String),
|
|
39
|
+
contextWindow: Schema.optionalKey(Schema.Number),
|
|
40
|
+
})),
|
|
33
41
|
defaultModel: Schema.optionalKey(ModelRef),
|
|
34
42
|
})) {
|
|
35
43
|
}
|
package/dist/authoring.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ import type { ContentAddition } from "./content/source.ts";
|
|
|
9
9
|
export declare class ContentNameError extends Error {
|
|
10
10
|
name: string;
|
|
11
11
|
}
|
|
12
|
-
export interface
|
|
12
|
+
export interface InitAgentOptions {
|
|
13
13
|
/** What the agent works on and knows: repositories into `context.json`, this machine's directories into
|
|
14
14
|
* `context.local.json`. */
|
|
15
15
|
content?: ContentAddition[];
|
|
@@ -26,8 +26,6 @@ export interface CreatedAgent {
|
|
|
26
26
|
dir: string;
|
|
27
27
|
/** The files written, relative to `dir`. */
|
|
28
28
|
created: string[];
|
|
29
|
-
/** The content, resolved for this instance. */
|
|
30
|
-
content: ResolvedContent[];
|
|
31
29
|
/**
|
|
32
30
|
* What became of the agent's git repository, as a sentence to show: created with the scaffold as its first commit,
|
|
33
31
|
* or why not (inside a repository that tracks it, git missing, no commit identity).
|
|
@@ -35,12 +33,18 @@ export interface CreatedAgent {
|
|
|
35
33
|
repository: string;
|
|
36
34
|
}
|
|
37
35
|
/**
|
|
38
|
-
* Create an agent in `dir
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
|
|
36
|
+
* Create an agent in `dir` as `fastagent init` does, which must be new or empty: the whole scaffold, and a git
|
|
37
|
+
* repository whose first commit is what was written, since the agent changes itself and version control is how its
|
|
38
|
+
* author goes back to a version that worked. It installs no dependencies; the agent opens without them.
|
|
39
|
+
*/
|
|
40
|
+
export declare function createAgent(dir: string): Promise<CreatedAgent>;
|
|
41
|
+
/**
|
|
42
|
+
* {@link createAgent} with what `fastagent init` adds: content, and the install. Every content entry is checked before
|
|
43
|
+
* anything is written, and the scaffold is removed again when writing the content fails.
|
|
42
44
|
*/
|
|
43
|
-
export declare function
|
|
45
|
+
export declare function initAgent(dir: string, options?: InitAgentOptions): Promise<CreatedAgent & {
|
|
46
|
+
content: ResolvedContent[];
|
|
47
|
+
}>;
|
|
44
48
|
/**
|
|
45
49
|
* The agent's content, resolved for this instance (what `fastagent content list` shows), and the directories this
|
|
46
50
|
* machine binds that are not here: the agent is not told of those.
|
package/dist/authoring.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Creating an agent and editing its content: `fastagent init` and `fastagent content` are thin wrappers over these (and
|
|
3
3
|
* `fastagent env` writes its declaration through {@link writeEnvironment}), so the rules live here, not in a command.
|
|
4
|
-
* Nothing here prints or exits; every refusal is thrown with the message the CLI shows.
|
|
5
|
-
*
|
|
4
|
+
* Nothing here prints or exits; every refusal is thrown with the message the CLI shows. The package exports
|
|
5
|
+
* {@link createAgent}; the content and environment edits wait for context packages, whose shape they follow.
|
|
6
6
|
*/
|
|
7
7
|
import { existsSync, lstatSync, readFileSync, statSync } from "node:fs";
|
|
8
8
|
import { rm } from "node:fs/promises";
|
|
@@ -25,12 +25,19 @@ export class ContentNameError extends Error {
|
|
|
25
25
|
name = "ContentNameError";
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
|
-
* Create an agent in `dir
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* it changes itself, and version control is how its author goes back to a version that worked.
|
|
28
|
+
* Create an agent in `dir` as `fastagent init` does, which must be new or empty: the whole scaffold, and a git
|
|
29
|
+
* repository whose first commit is what was written, since the agent changes itself and version control is how its
|
|
30
|
+
* author goes back to a version that worked. It installs no dependencies; the agent opens without them.
|
|
32
31
|
*/
|
|
33
|
-
export async function createAgent(dir
|
|
32
|
+
export async function createAgent(dir) {
|
|
33
|
+
const { content: _, ...created } = await initAgent(dir);
|
|
34
|
+
return created;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* {@link createAgent} with what `fastagent init` adds: content, and the install. Every content entry is checked before
|
|
38
|
+
* anything is written, and the scaffold is removed again when writing the content fails.
|
|
39
|
+
*/
|
|
40
|
+
export async function initAgent(dir, options = {}) {
|
|
34
41
|
const agentDir = resolve(dir);
|
|
35
42
|
const additions = options.content ?? [];
|
|
36
43
|
let maps = { content: {}, bindings: {} };
|
package/dist/cli/add-feishu.js
CHANGED
|
@@ -10,7 +10,7 @@ import { cloudFor } from "../channels/feishu/cloud.js";
|
|
|
10
10
|
import { createFeishuApi, isFeishuConfigApiMissing, isTransientFeishuRegistrationError, } from "../channels/feishu/feishu-api.js";
|
|
11
11
|
import { registerFeishuApp } from "../channels/feishu/register-app.js";
|
|
12
12
|
import { LARK_CONSOLE_URL, onboardLarkApp } from "../channels/lark/onboard.js";
|
|
13
|
-
import { dotEnvPath, loadEnvValues, parseEnvContent } from "../
|
|
13
|
+
import { dotEnvPath, loadEnvValues, parseEnvContent } from "../value-file.js";
|
|
14
14
|
import { openExternalUrl } from "../open-url.js";
|
|
15
15
|
import { terminalPerson } from "./person.js";
|
|
16
16
|
import { AGENT_ENVIRONMENT_ENV } from "../paths.js";
|
package/dist/cli/add-slack.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { basename } from "node:path";
|
|
3
3
|
import { isCancel, log as clackLog, password, select, text as clackText } from "@clack/prompts";
|
|
4
|
-
import { dotEnvPath, parseEnvContent } from "../
|
|
4
|
+
import { dotEnvPath, parseEnvContent } from "../value-file.js";
|
|
5
5
|
import { openExternalUrl } from "../open-url.js";
|
|
6
6
|
import { appendChannelDotEnv } from "../scaffold/add-channel.js";
|
|
7
7
|
import { newSlackOnboardingState, onboardSlackApp } from "../channels/slack/onboard.js";
|
package/dist/cli/box-login.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type BoxShell } from "../deploy/box-shell.ts";
|
|
2
2
|
import type { DeployHost } from "../deploy/hosts.ts";
|
|
3
|
-
import type { LoginIO
|
|
3
|
+
import type { LoginIO } from "../harnesses/models/login.ts";
|
|
4
|
+
import type { SignInMethod } from "../sign-in-api.ts";
|
|
4
5
|
import type { TicketStep } from "./ticket.ts";
|
|
5
6
|
export interface BoxLoginRequest {
|
|
6
7
|
host: DeployHost;
|
|
@@ -8,7 +9,7 @@ export interface BoxLoginRequest {
|
|
|
8
9
|
/** Skip the provider menu. */
|
|
9
10
|
provider?: string;
|
|
10
11
|
/** Skip the method menu. */
|
|
11
|
-
method?:
|
|
12
|
+
method?: SignInMethod;
|
|
12
13
|
/** Keep a credential the box already holds for `provider` (a redeploy), instead of replacing it. */
|
|
13
14
|
ifMissing?: boolean;
|
|
14
15
|
/** false: never ask; a box without the credential is a failure naming the command to run in a terminal. */
|
package/dist/cli/commands/add.js
CHANGED
|
@@ -13,7 +13,8 @@ import { feishuIngressFor } from "../../channels/feishu/setup-mode.js";
|
|
|
13
13
|
import { markDevServe, unmarkDevServe } from "../../serving-command.js";
|
|
14
14
|
import { channelIngress } from "../../channels/discover.js";
|
|
15
15
|
import { buildDefinitionShape, prepareDefinition } from "../../harnesses/pi-durable/open.js";
|
|
16
|
-
import {
|
|
16
|
+
import { enterAgentEnv } from "../../env.js";
|
|
17
|
+
import { dotEnvPath, loadEnvValues } from "../../value-file.js";
|
|
17
18
|
import { selectAgentEnvironment, resolveStateRoot, SECRETS_DIRNAME, isUnderDir, displayPath } from "../../paths.js";
|
|
18
19
|
import { detectRuntime, readPackageJson } from "../../runtime.js";
|
|
19
20
|
import { appendChannelDotEnv, appendChannelEnv, assertChannelReady, channelExists, channelSetup, scaffoldChannel, scaffoldCompanionTools, } from "../../scaffold/add-channel.js";
|
|
@@ -6,7 +6,7 @@ import { relative } from "node:path";
|
|
|
6
6
|
import { cloudFor } from "../../channels/feishu/cloud.js";
|
|
7
7
|
import { FEISHU_INGRESS_SETTINGS } from "../../channels/feishu/setup-mode.js";
|
|
8
8
|
import { assembleSecrets, divergentSettingsGate, missingValuesGate } from "../../deploy/secrets.js";
|
|
9
|
-
import { dotEnvPath, loadEnvValues } from "../../
|
|
9
|
+
import { dotEnvPath, loadEnvValues } from "../../value-file.js";
|
|
10
10
|
import { unmarkDevServe } from "../../serving-command.js";
|
|
11
11
|
import { assertIndependentFeishuApps, feishuAppSecretNames, larkCredentialsNeeded, prepareFeishuApps, unpreparedFeishuApps, } from "../add-feishu.js";
|
|
12
12
|
import { displayPath, selectAgentEnvironment } from "../../paths.js";
|
package/dist/cli/commands/env.js
CHANGED
|
@@ -7,11 +7,12 @@ import { resolve } from "node:path";
|
|
|
7
7
|
import { loadEnvironment, readContextFile } from "../../content/file.js";
|
|
8
8
|
import { aptAdvice, missingAptPackages } from "../../environment/apt.js";
|
|
9
9
|
import { declareEnvironment, declaredVariables, parsePackageArgument, runtimeAdvice, } from "../../environment/declare.js";
|
|
10
|
-
import { checkPackages, ensureMise,
|
|
10
|
+
import { checkPackages, ensureMise, installPackages, miseBinary, prepareEnvironment } from "../../environment/mise.js";
|
|
11
11
|
import { writeEnvironment } from "../../authoring.js";
|
|
12
12
|
import { log } from "../../log.js";
|
|
13
13
|
import { agentDirOrExit, failStartup } from "../fail.js";
|
|
14
|
-
import {
|
|
14
|
+
import { enterAgentEnv } from "../../env.js";
|
|
15
|
+
import { dotEnvPath } from "../../value-file.js";
|
|
15
16
|
import { missingSecrets } from "../../declared-secrets.js";
|
|
16
17
|
const NONE = { packages: {}, aptPackages: [], variables: {} };
|
|
17
18
|
/**
|
|
@@ -147,10 +148,10 @@ export async function runEnvInstall() {
|
|
|
147
148
|
}
|
|
148
149
|
export async function runEnvExec(command) {
|
|
149
150
|
const agentDir = enterAgentHere();
|
|
150
|
-
await
|
|
151
|
+
const variables = await prepareEnvironment(agentDir, process.env).catch(failStartup);
|
|
151
152
|
const [program, ...args] = command;
|
|
152
153
|
const code = await new Promise((done, fail) => {
|
|
153
|
-
const child = spawn(program, args, { stdio: "inherit" });
|
|
154
|
+
const child = spawn(program, args, { stdio: "inherit", env: { ...process.env, ...variables } });
|
|
154
155
|
child.on("error", fail);
|
|
155
156
|
child.on("close", (exit, signal) => done(exit ?? (signal ? 1 : 0)));
|
|
156
157
|
}).catch((error) => failStartup(error.code === "ENOENT" ? new Error(`${program}: command not found in the environment`) : error));
|
|
@@ -2,19 +2,19 @@
|
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
4
|
import { displayPath } from "../../paths.js";
|
|
5
|
-
import {
|
|
5
|
+
import { initAgent } from "../../authoring.js";
|
|
6
6
|
import { readContentSource } from "../../content/source.js";
|
|
7
7
|
import { failEdit } from "./content.js";
|
|
8
8
|
import { contentLines } from "../content-view.js";
|
|
9
9
|
import { failStartup } from "../fail.js";
|
|
10
10
|
export async function runInit(dirArg, opts) {
|
|
11
11
|
const dir = resolve(dirArg);
|
|
12
|
-
// Every source is read, and every entry checked (by
|
|
12
|
+
// Every source is read, and every entry checked (by initAgent), BEFORE anything is created.
|
|
13
13
|
const read = await Promise.resolve()
|
|
14
14
|
.then(() => opts.content.map((source) => readContentSource(source, process.cwd())))
|
|
15
15
|
.catch(failStartup);
|
|
16
16
|
let installFailed = false;
|
|
17
|
-
const { created, content, repository } = await
|
|
17
|
+
const { created, content, repository } = await initAgent(dir, {
|
|
18
18
|
content: read.map((source) => source.addition),
|
|
19
19
|
...(opts.install
|
|
20
20
|
? {
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { join, relative, resolve } from "node:path";
|
|
7
7
|
import { AgentError } from "../../agent-api.js";
|
|
8
|
-
import {
|
|
8
|
+
import { enterAgentEnv } from "../../env.js";
|
|
9
|
+
import { dotEnvPath, loadEnvValues } from "../../value-file.js";
|
|
9
10
|
import { installProxyFetch } from "../../proxy.js";
|
|
10
11
|
import { modelRefOf } from "../../harnesses/pi-durable/open.js";
|
|
11
12
|
import { openServing } from "../../harnesses/pi-durable/serve.js";
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { enterAgentEnv
|
|
1
|
+
import { enterAgentEnv } from "../../env.js";
|
|
2
|
+
import { dotEnvPath } from "../../value-file.js";
|
|
2
3
|
import { GLOBAL_AUTH_PATH, resolveAuthPath, } from "../../harnesses/models/credentials.js";
|
|
3
4
|
import { modelEnvironment } from "../../harnesses/models/environment.js";
|
|
4
5
|
import { DEPLOY_HOSTS } from "../../deploy/hosts.js";
|
|
5
6
|
import { findAgentDir, globalHome, selectAgentEnvironment } from "../../paths.js";
|
|
6
|
-
import {
|
|
7
|
+
import { SignInCancelled } from "../../sign-in-api.js";
|
|
8
|
+
import { LoginRefused, PI_DEFAULT_MODELS, chooseLogin, loginFlow, loginOptionsOver, loginOver, loginStatusText, } from "../../harnesses/models/login.js";
|
|
7
9
|
import { environmentAuth, keyVariables, loginProviders } from "../../harnesses/models/models.js";
|
|
8
10
|
import { definitionProviders, loadConfig, modelSpecFor } from "../../harnesses/pi-durable/open.js";
|
|
9
11
|
import { loginOnBox, ticketLoginIO } from "../box-login.js";
|
|
@@ -95,7 +97,7 @@ export async function runLogin(provider, opts) {
|
|
|
95
97
|
}).catch((error) => {
|
|
96
98
|
if (timeout?.aborted)
|
|
97
99
|
failStartup(new Error(`login timed out after ${givenTimeout}s (--timeout)`));
|
|
98
|
-
if (error instanceof
|
|
100
|
+
if (error instanceof SignInCancelled) {
|
|
99
101
|
// A decision, not a failure — neutral wording; non-zero exit because no credential was stored.
|
|
100
102
|
console.error(`[fastagent] login cancelled`);
|
|
101
103
|
process.exit(1);
|
|
@@ -470,7 +472,7 @@ async function requestDeploymentLogin(agentDir, host, given, opts, timeoutSecond
|
|
|
470
472
|
*/
|
|
471
473
|
async function runStdioLogin(provider, opts) {
|
|
472
474
|
const wire = stdioLoginIO(process.stdin, process.stdout);
|
|
473
|
-
const result = await stdioLogin(wire.io, provider, opts).catch((error) => error instanceof
|
|
475
|
+
const result = await stdioLogin(wire.io, provider, opts).catch((error) => error instanceof SignInCancelled
|
|
474
476
|
? { ok: false, reason: "cancelled", message: "cancelled" }
|
|
475
477
|
: { ok: false, reason: "failed", message: error.message });
|
|
476
478
|
wire.result(result);
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { Readable, Writable } from "node:stream";
|
|
2
|
-
import type { LoginIO
|
|
2
|
+
import type { LoginIO } from "../harnesses/models/login.ts";
|
|
3
|
+
import type { SignInMethod } from "../sign-in-api.ts";
|
|
3
4
|
/** How a login on the box ended, as the box reports it. */
|
|
4
5
|
export type RelayResult =
|
|
5
6
|
/** Logged in now: `method` is how, `path` where the box saved it. */
|
|
6
7
|
{
|
|
7
8
|
ok: true;
|
|
8
9
|
provider: string;
|
|
9
|
-
method:
|
|
10
|
+
method: SignInMethod;
|
|
10
11
|
path: string;
|
|
11
12
|
}
|
|
12
13
|
/**
|
|
@@ -24,7 +25,7 @@ export type RelayResult =
|
|
|
24
25
|
};
|
|
25
26
|
/**
|
|
26
27
|
* The box half: a {@link LoginIO} over `input`/`output`. Once `input` ends (the session dropped), every pending and
|
|
27
|
-
* later question reads as backed out, so the flow ends in `
|
|
28
|
+
* later question reads as backed out, so the flow ends in `SignInCancelled` instead of waiting forever.
|
|
28
29
|
*/
|
|
29
30
|
export declare function stdioLoginIO(input: Readable, output: Writable): {
|
|
30
31
|
io: LoginIO;
|
package/dist/cli/login-relay.js
CHANGED
|
@@ -12,7 +12,7 @@ import { BOX_CLI_PREDATES } from "../deploy/box-shell.js";
|
|
|
12
12
|
const send = (out, message) => void out.write(`${JSON.stringify(message)}\n`);
|
|
13
13
|
/**
|
|
14
14
|
* The box half: a {@link LoginIO} over `input`/`output`. Once `input` ends (the session dropped), every pending and
|
|
15
|
-
* later question reads as backed out, so the flow ends in `
|
|
15
|
+
* later question reads as backed out, so the flow ends in `SignInCancelled` instead of waiting forever.
|
|
16
16
|
*/
|
|
17
17
|
export function stdioLoginIO(input, output) {
|
|
18
18
|
const pending = new Map();
|
package/dist/cli/shared.js
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
import { resolve } from "node:path";
|
|
6
6
|
import { autocomplete, isCancel, log as clackLog, password, select, text as clackText } from "@clack/prompts";
|
|
7
7
|
import { buildModelPickerOptions } from "./models-view.js";
|
|
8
|
-
import {
|
|
8
|
+
import { loginFlow } from "../harnesses/models/login.js";
|
|
9
|
+
import { SignInCancelled } from "../sign-in-api.js";
|
|
9
10
|
import { modelEnvironment } from "../harnesses/models/environment.js";
|
|
10
11
|
import { listModels, providerAuthStatuses, providerOf } from "../harnesses/models/models.js";
|
|
11
12
|
import { definitionProviders, loadConfig, modelSpecFor } from "../harnesses/pi-durable/open.js";
|
|
@@ -13,7 +14,8 @@ import { formatAuthReport } from "./auth-view.js";
|
|
|
13
14
|
import { contentLines } from "./content-view.js";
|
|
14
15
|
import { deployedHost, isDeployedWorkspace } from "../paths.js";
|
|
15
16
|
import { log } from "../log.js";
|
|
16
|
-
import {
|
|
17
|
+
import { enterAgentEnv } from "../env.js";
|
|
18
|
+
import { dotEnvPath } from "../value-file.js";
|
|
17
19
|
import { openExternalUrl } from "../open-url.js";
|
|
18
20
|
import { bindAddress, isBindAddress } from "../bind.js";
|
|
19
21
|
import { agentDirOrExit, failStartup, failUsage } from "./fail.js";
|
|
@@ -180,7 +182,7 @@ async function pickWithCredentials(models, authPath, providers) {
|
|
|
180
182
|
console.error(`[fastagent] logged in to ${provider} — saved to ${authPath}`);
|
|
181
183
|
}
|
|
182
184
|
catch (error) {
|
|
183
|
-
if (error instanceof
|
|
185
|
+
if (error instanceof SignInCancelled)
|
|
184
186
|
return undefined; // user backed out — discard the choice, like a picker cancel
|
|
185
187
|
// A FAILED login keeps the choice: the pick persists, the startup auth report names the remedy, and a later
|
|
186
188
|
// `fastagent login` fixes auth without re-picking the model.
|
package/dist/content/git.d.ts
CHANGED
|
@@ -38,4 +38,4 @@ export type CloneOutcome = {
|
|
|
38
38
|
* - None yet: cloned beside it and renamed into place. A first clone that fails stops the start.
|
|
39
39
|
* - A clone of another repository there (the entry was renamed or redeclared): refused, naming it.
|
|
40
40
|
*/
|
|
41
|
-
export declare function refreshClone(repo: string, ref: string | undefined, dir: string): Promise<CloneOutcome>;
|
|
41
|
+
export declare function refreshClone(repo: string, ref: string | undefined, dir: string, env?: NodeJS.ProcessEnv): Promise<CloneOutcome>;
|
package/dist/content/git.js
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
import { existsSync, mkdirSync, realpathSync, renameSync, rmSync, statSync } from "node:fs";
|
|
7
7
|
import { basename, dirname, join } from "node:path";
|
|
8
8
|
import { gitFor } from "../git.js";
|
|
9
|
-
const
|
|
9
|
+
const WHY = "github content needs it on this machine";
|
|
10
|
+
const { answer: gitAnswer } = gitFor(WHY);
|
|
10
11
|
/** The `owner/repo` a GitHub remote URL names, in any of its spellings (https, ssh, scp-like), or undefined. */
|
|
11
12
|
export function githubRepoOf(url) {
|
|
12
13
|
const match = /^(?:(?:https?|git|ssh):\/\/(?:[^@/]+@)?github\.com\/|(?:[^@/:]+@)?github\.com:)([\w.-]+)\/([\w.-]+?)(?:\.git)?\/?$/i.exec(url.trim());
|
|
@@ -94,14 +95,16 @@ const HELPER_KEY = "credential.https://github.com.helper";
|
|
|
94
95
|
* - None yet: cloned beside it and renamed into place. A first clone that fails stops the start.
|
|
95
96
|
* - A clone of another repository there (the entry was renamed or redeclared): refused, naming it.
|
|
96
97
|
*/
|
|
97
|
-
export async function refreshClone(repo, ref, dir) {
|
|
98
|
+
export async function refreshClone(repo, ref, dir, env = process.env) {
|
|
99
|
+
// The agent's environment, whose GITHUB_TOKEN the clone's credential helper reads.
|
|
100
|
+
const { run: runGit } = gitFor(WHY, env);
|
|
98
101
|
// A library caller can hand this anything; git reads a leading "-" as an option, and no repository or ref has one
|
|
99
102
|
// (a declaration with one is refused at load, declare.ts).
|
|
100
103
|
if (repo.startsWith("-") || ref?.startsWith("-")) {
|
|
101
104
|
throw new Error(`github ${repo}${ref !== undefined ? ` at ${ref}` : ""} would reach git as an option`);
|
|
102
105
|
}
|
|
103
106
|
if (!existsSync(dir))
|
|
104
|
-
return cloneInto(repo, ref, dir);
|
|
107
|
+
return cloneInto(repo, ref, dir, runGit);
|
|
105
108
|
const origin = gitAnswer(["config", "--get", "remote.origin.url"], dir);
|
|
106
109
|
if (origin === undefined || githubRepoOf(origin)?.toLowerCase() !== repo.toLowerCase()) {
|
|
107
110
|
throw new Error(`${dir} is a clone of ${origin ?? "no repository"}, not of github ${repo}: move it away (push what should last ` +
|
|
@@ -162,7 +165,7 @@ function gitReason(error) {
|
|
|
162
165
|
* process reads no clone or a whole one. When another process put its own there first, that one stands and this one
|
|
163
166
|
* is discarded. Shallow: a starting point to work from; a later fetch brings the history an update needs.
|
|
164
167
|
*/
|
|
165
|
-
async function cloneInto(repo, ref, dir) {
|
|
168
|
+
async function cloneInto(repo, ref, dir, runGit) {
|
|
166
169
|
const parent = dirname(dir);
|
|
167
170
|
mkdirSync(parent, { recursive: true });
|
|
168
171
|
const next = join(parent, `.${basename(dir)}.next-${process.pid}-${Date.now()}`);
|
package/dist/content/resolve.js
CHANGED
|
@@ -103,11 +103,11 @@ export function checkBinding(agentDir, name, target, repository) {
|
|
|
103
103
|
* agent did in it (git.ts `refreshClone`): what git refuses, or a remote that cannot be reached, keeps it as it is,
|
|
104
104
|
* with the reason. Only a process that runs the agent calls this. A checkout this machine binds is never cloned over.
|
|
105
105
|
*/
|
|
106
|
-
export async function cloneContent(entry) {
|
|
106
|
+
export async function cloneContent(entry, env = process.env) {
|
|
107
107
|
if (!entry.clone)
|
|
108
108
|
throw new Error(`content "${entry.name}" is bound to the checkout ${entry.location}, not a clone`);
|
|
109
109
|
ensureContentDir(dirname(entry.location));
|
|
110
|
-
return refreshClone(entry.repo, entry.ref, entry.location).catch((error) => {
|
|
110
|
+
return refreshClone(entry.repo, entry.ref, entry.location, env).catch((error) => {
|
|
111
111
|
throw new Error(`content "${entry.name}": ${error.message}`);
|
|
112
112
|
});
|
|
113
113
|
}
|
package/dist/deploy/preflight.js
CHANGED
|
@@ -19,7 +19,7 @@ import { RUNTIME_FLOOR, belowFloor, detectRuntime, listsFastagent, readPackageJs
|
|
|
19
19
|
import { fastagentVersion } from "../version.js";
|
|
20
20
|
import { isGeneratedDockerfile } from "./container.js";
|
|
21
21
|
import { buildContextPaths, checkKeptIgnoreFiles } from "./build-context.js";
|
|
22
|
-
import { dotEnvPath, loadEnvValues } from "../
|
|
22
|
+
import { dotEnvPath, loadEnvValues } from "../value-file.js";
|
|
23
23
|
import { deploymentSecrets, isEnvKey } from "./secrets.js";
|
|
24
24
|
import { DEFAULT_HTTP_PORT } from "../service.js";
|
|
25
25
|
import { declaredVariables, declaresTool, runtimeAdvice } from "../environment/declare.js";
|
package/dist/dev-supervisor.js
CHANGED
|
@@ -6,7 +6,7 @@ import { spawn } from "node:child_process";
|
|
|
6
6
|
import { relative, sep } from "node:path";
|
|
7
7
|
import { watch as watchTree } from "chokidar";
|
|
8
8
|
import { AGENT_CONFIG_FILE, CONTEXT_FILE, LOCAL_CONTEXT_FILE, resolveStateRoot, isUnderDir } from "./paths.js";
|
|
9
|
-
import { dotEnvPath } from "./
|
|
9
|
+
import { dotEnvPath } from "./value-file.js";
|
|
10
10
|
import { log } from "./log.js";
|
|
11
11
|
import { openExternalUrl } from "./open-url.js";
|
|
12
12
|
import { declaredChannels } from "./channels/discover.js";
|
package/dist/env.d.ts
CHANGED
|
@@ -4,19 +4,6 @@
|
|
|
4
4
|
* occurrence wins ({@link parseEnvContent}).
|
|
5
5
|
*/
|
|
6
6
|
export declare function applyEnvValues(values: ReadonlyMap<string, string>): void;
|
|
7
|
-
/** Parse .env content into key → value (the dialect above; last occurrence of a key wins). */
|
|
8
|
-
export declare function parseEnvContent(content: string): Map<string, string>;
|
|
9
|
-
/**
|
|
10
|
-
* READ a value file without entering it: the values as data, never `process.env`. This is how code reasons about an
|
|
11
|
-
* environment OTHER than its own — `deploy` resolves configuration in the environment being deployed, which this
|
|
12
|
-
* file declares, and merging it into the running process would put the two environments in one bag. A missing file
|
|
13
|
-
* is no values, which is normal.
|
|
14
|
-
*/
|
|
15
|
-
export declare function loadEnvValues(file: string): Map<string, string>;
|
|
16
|
-
/** The agent's `.env` file: `<resolved secrets dir>/.env`. */
|
|
17
|
-
export declare function dotEnvPath(agentDir: string, env?: NodeJS.ProcessEnv): string;
|
|
18
|
-
/** The committable template: `<agentDir>/.secrets/.env.example`. */
|
|
19
|
-
export declare function envExamplePath(agentDir: string): string;
|
|
20
7
|
/**
|
|
21
8
|
* ENTER an agent directory's runtime environment: its `.env` becomes `process.env`, and this process's outbound fetch
|
|
22
9
|
* follows whatever proxy that (or the ambient environment) declares.
|
package/dist/env.js
CHANGED
|
@@ -2,7 +2,7 @@ import { readFileSync, existsSync } from "node:fs";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { log } from "./log.js";
|
|
4
4
|
import { installProxyFetch } from "./proxy.js";
|
|
5
|
-
import {
|
|
5
|
+
import { dotEnvPath, loadEnvValues, parseEnvContent } from "./value-file.js";
|
|
6
6
|
/**
|
|
7
7
|
* Write parsed values into `process.env`, matching Node's `--env-file` / `process.loadEnvFile` precedence on BOTH
|
|
8
8
|
* axes (verified against Node in test/env.test.ts): a real env var wins over the file, and within the file the last
|
|
@@ -13,49 +13,6 @@ export function applyEnvValues(values) {
|
|
|
13
13
|
if (!(key in process.env))
|
|
14
14
|
process.env[key] = value;
|
|
15
15
|
}
|
|
16
|
-
/** Parse .env content into key → value (the dialect above; last occurrence of a key wins). */
|
|
17
|
-
export function parseEnvContent(content) {
|
|
18
|
-
const parsed = new Map();
|
|
19
|
-
for (const raw of content.split("\n")) {
|
|
20
|
-
const line = raw.trim();
|
|
21
|
-
if (!line || line.startsWith("#"))
|
|
22
|
-
continue;
|
|
23
|
-
const eq = line.indexOf("=");
|
|
24
|
-
if (eq < 1)
|
|
25
|
-
continue; // no `=`, or an empty key
|
|
26
|
-
const key = line.slice(0, eq).trim();
|
|
27
|
-
let value = line.slice(eq + 1).trim();
|
|
28
|
-
const quote = value[0];
|
|
29
|
-
if ((quote === '"' || quote === "'") && value.at(-1) === quote)
|
|
30
|
-
value = value.slice(1, -1);
|
|
31
|
-
parsed.set(key, value); // in-file: last occurrence wins (Map overwrite)
|
|
32
|
-
}
|
|
33
|
-
return parsed;
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* READ a value file without entering it: the values as data, never `process.env`. This is how code reasons about an
|
|
37
|
-
* environment OTHER than its own — `deploy` resolves configuration in the environment being deployed, which this
|
|
38
|
-
* file declares, and merging it into the running process would put the two environments in one bag. A missing file
|
|
39
|
-
* is no values, which is normal.
|
|
40
|
-
*/
|
|
41
|
-
export function loadEnvValues(file) {
|
|
42
|
-
try {
|
|
43
|
-
return parseEnvContent(readFileSync(file, "utf8"));
|
|
44
|
-
}
|
|
45
|
-
catch (error) {
|
|
46
|
-
if (error.code === "ENOENT")
|
|
47
|
-
return new Map();
|
|
48
|
-
throw error;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
/** The agent's `.env` file: `<resolved secrets dir>/.env`. */
|
|
52
|
-
export function dotEnvPath(agentDir, env = process.env) {
|
|
53
|
-
return join(resolveSecretsDir(agentDir, env), ".env");
|
|
54
|
-
}
|
|
55
|
-
/** The committable template: `<agentDir>/.secrets/.env.example`. */
|
|
56
|
-
export function envExamplePath(agentDir) {
|
|
57
|
-
return join(agentDir, SECRETS_DIRNAME, ".env.example");
|
|
58
|
-
}
|
|
59
16
|
/**
|
|
60
17
|
* ENTER an agent directory's runtime environment: its `.env` becomes `process.env`, and this process's outbound fetch
|
|
61
18
|
* follows whatever proxy that (or the ambient environment) declares.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type MissingRuntime } from "./declare.ts";
|
|
2
2
|
/** Every platform's package: an agent lists them all, so its lockfile has the image's too. */
|
|
3
3
|
export declare const MISE_PACKAGE_NAMES: readonly string[];
|
|
4
4
|
/**
|
|
@@ -40,11 +40,12 @@ export declare function checkPackages(agentDir: string, bin: string, packages: R
|
|
|
40
40
|
* so a start whose packages are installed needs no network and keeps the versions it has. An install that matches may
|
|
41
41
|
* be another project's: mise's data directory is the machine's.
|
|
42
42
|
*/
|
|
43
|
-
export declare function installPackages(agentDir: string, bin: string, packages: Record<string, string>, newest?: readonly string[] | "all"): Promise<void>;
|
|
43
|
+
export declare function installPackages(agentDir: string, bin: string, packages: Record<string, string>, newest?: readonly string[] | "all", env?: NodeJS.ProcessEnv): Promise<void>;
|
|
44
44
|
/**
|
|
45
|
-
*
|
|
46
|
-
* yet, and put them first on
|
|
47
|
-
*
|
|
48
|
-
*
|
|
45
|
+
* The agent's declared environment, for a process that runs it: install the declared packages that have no install
|
|
46
|
+
* here yet, and answer the variables that put them first on the PATH of the commands the agent runs. mise runs in
|
|
47
|
+
* `env`, the agent's environment so far, and nothing is written into this process's: a process may run several agents.
|
|
48
|
+
* mise itself is not put on the PATH. An agent without packages borrows the machine's commands (no variables). Apt
|
|
49
|
+
* packages are the image's to install; one missing here is said, with how.
|
|
49
50
|
*/
|
|
50
|
-
export declare function
|
|
51
|
+
export declare function prepareEnvironment(agentDir: string, env: NodeJS.ProcessEnv): Promise<Record<string, string>>;
|