frely-cli 0.23.9 → 0.23.10
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 +49 -20
- package/README.zh-CN.md +47 -19
- package/dist/agent/local/approvals.d.ts +24 -0
- package/dist/agent/local/approvals.js +79 -0
- package/dist/agent/local/approvals.js.map +1 -0
- package/dist/agent/local/command.d.ts +10 -0
- package/dist/agent/local/command.js +292 -0
- package/dist/agent/local/command.js.map +1 -0
- package/dist/agent/local/confine.d.ts +20 -0
- package/dist/agent/local/confine.js +52 -0
- package/dist/agent/local/confine.js.map +1 -0
- package/dist/agent/local/manifest.d.ts +17 -0
- package/dist/agent/local/manifest.js +55 -0
- package/dist/agent/local/manifest.js.map +1 -0
- package/dist/agent/local/model-proxy.d.ts +18 -0
- package/dist/agent/local/model-proxy.js +111 -0
- package/dist/agent/local/model-proxy.js.map +1 -0
- package/dist/agent/local/source.d.ts +28 -0
- package/dist/agent/local/source.js +64 -0
- package/dist/agent/local/source.js.map +1 -0
- package/dist/agent/local/store.d.ts +45 -0
- package/dist/agent/local/store.js +78 -0
- package/dist/agent/local/store.js.map +1 -0
- package/dist/agent/local/supervisor.d.ts +9 -0
- package/dist/agent/local/supervisor.js +62 -0
- package/dist/agent/local/supervisor.js.map +1 -0
- package/dist/agent-help.d.ts +118 -10
- package/dist/agent-help.js +25 -6
- package/dist/agent-help.js.map +1 -1
- package/dist/index.js +35 -5
- package/dist/index.js.map +1 -1
- package/dist/provider/control.d.ts +10 -0
- package/dist/provider/control.js +19 -0
- package/dist/provider/control.js.map +1 -1
- package/dist/provider/share.js +4 -1
- package/dist/provider/share.js.map +1 -1
- package/dist/runtime/sandbox.d.ts +20 -0
- package/dist/runtime/sandbox.js +39 -1
- package/dist/runtime/sandbox.js.map +1 -1
- package/dist/share/allowance.d.ts +8 -0
- package/dist/share/allowance.js +22 -0
- package/dist/share/allowance.js.map +1 -0
- package/dist/share/control.d.ts +2 -0
- package/dist/share/control.js +14 -10
- package/dist/share/control.js.map +1 -1
- package/dist/share/service-config.d.ts +39 -0
- package/dist/share/service-config.js +85 -0
- package/dist/share/service-config.js.map +1 -0
- package/dist/share/service-control.d.ts +15 -0
- package/dist/share/service-control.js +31 -0
- package/dist/share/service-control.js.map +1 -0
- package/dist/share/service-run.d.ts +55 -0
- package/dist/share/service-run.js +285 -0
- package/dist/share/service-run.js.map +1 -0
- package/dist/share/service-state.d.ts +16 -0
- package/dist/share/service-state.js +66 -0
- package/dist/share/service-state.js.map +1 -0
- package/dist/skill/access.d.ts +2 -2
- package/dist/skill/access.js +4 -2
- package/dist/skill/access.js.map +1 -1
- package/dist/skill/cloud-item.d.ts +2 -2
- package/dist/skill/cloud-item.js.map +1 -1
- package/dist/skill/entry.d.ts +1 -1
- package/dist/skill/entry.js +1 -1
- package/dist/skill/remote-mcp.d.ts +3 -0
- package/dist/skill/remote-mcp.js +7 -1
- package/dist/skill/remote-mcp.js.map +1 -1
- package/dist/skill/router.d.ts +6 -2
- package/dist/skill/router.js +4 -4
- package/dist/skill/router.js.map +1 -1
- package/dist/uninstall.d.ts +4 -0
- package/dist/uninstall.js +14 -0
- package/dist/uninstall.js.map +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Where an Agent's code comes from: a Frely Agent page (`username/name`) or a public GitHub repository. */
|
|
2
|
+
export type AgentSource = {
|
|
3
|
+
kind: "frely";
|
|
4
|
+
user: string;
|
|
5
|
+
name: string;
|
|
6
|
+
} | {
|
|
7
|
+
kind: "github";
|
|
8
|
+
owner: string;
|
|
9
|
+
repo: string;
|
|
10
|
+
};
|
|
11
|
+
export declare class SourceError extends Error {
|
|
12
|
+
constructor(message: string);
|
|
13
|
+
}
|
|
14
|
+
export declare function parseSource(target: string): AgentSource;
|
|
15
|
+
export declare const githubUrl: (source: {
|
|
16
|
+
owner: string;
|
|
17
|
+
repo: string;
|
|
18
|
+
}) => string;
|
|
19
|
+
/** The public record a Frely Agent page keeps for its repository. */
|
|
20
|
+
export interface RegisteredRepo {
|
|
21
|
+
repoUrl: string;
|
|
22
|
+
commit: string | undefined;
|
|
23
|
+
name: string | undefined;
|
|
24
|
+
}
|
|
25
|
+
/** Reads the repository the Agent's author registered on Frely. The commit is the one the author listed. */
|
|
26
|
+
export declare function resolveRegisteredRepo(user: string, name: string, relayUrl: string, fetchFn?: typeof fetch): Promise<RegisteredRepo>;
|
|
27
|
+
/** Downloads `repoUrl` into the empty-or-missing `dir`, at `commit` when one is given, else at the default branch. Returns the commit that is checked out. */
|
|
28
|
+
export declare function download(repoUrl: string, dir: string, commit: string | undefined): Promise<string>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { mkdir } from "node:fs/promises";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
const run = promisify(execFile);
|
|
6
|
+
export class SourceError extends Error {
|
|
7
|
+
constructor(message) { super(message); this.name = "SourceError"; }
|
|
8
|
+
}
|
|
9
|
+
const FRELY_PART = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u;
|
|
10
|
+
const GITHUB_URL = /^https:\/\/github\.com\/([A-Za-z0-9][A-Za-z0-9-]{0,38})\/([A-Za-z0-9._-]{1,100}?)(?:\.git)?\/?$/u;
|
|
11
|
+
const COMMIT = /^[0-9a-f]{40}$/u;
|
|
12
|
+
export function parseSource(target) {
|
|
13
|
+
const github = GITHUB_URL.exec(target);
|
|
14
|
+
if (github)
|
|
15
|
+
return { kind: "github", owner: github[1], repo: github[2] };
|
|
16
|
+
const [user, name, extra] = target.replace(/^@/u, "").split("/");
|
|
17
|
+
if (user !== undefined && name !== undefined && extra === undefined && FRELY_PART.test(user) && FRELY_PART.test(name))
|
|
18
|
+
return { kind: "frely", user, name };
|
|
19
|
+
throw new SourceError("Use a Frely Agent name such as username/name, or a public GitHub address such as https://github.com/owner/repo.");
|
|
20
|
+
}
|
|
21
|
+
export const githubUrl = (source) => `https://github.com/${source.owner}/${source.repo}`;
|
|
22
|
+
/** Reads the repository the Agent's author registered on Frely. The commit is the one the author listed. */
|
|
23
|
+
export async function resolveRegisteredRepo(user, name, relayUrl, fetchFn = fetch) {
|
|
24
|
+
const response = await fetchFn(`${relayUrl}/api/public/agent-repos/${encodeURIComponent(user)}/${encodeURIComponent(name)}`, { headers: { accept: "application/json" }, redirect: "error", signal: AbortSignal.timeout(15_000) });
|
|
25
|
+
if (response.status === 404)
|
|
26
|
+
throw new SourceError(`${user}/${name} is not a Frely Agent with a repository.`);
|
|
27
|
+
if (!response.ok)
|
|
28
|
+
throw new SourceError(`Frely could not look up ${user}/${name} (HTTP ${response.status}).`);
|
|
29
|
+
const body = await response.json().catch(() => null);
|
|
30
|
+
if (!body || typeof body.repoUrl !== "string" || !GITHUB_URL.test(body.repoUrl))
|
|
31
|
+
throw new SourceError(`Frely returned no usable repository for ${user}/${name}.`);
|
|
32
|
+
const commit = typeof body.commit === "string" && COMMIT.test(body.commit) ? body.commit : undefined;
|
|
33
|
+
return { repoUrl: body.repoUrl.replace(/\.git$/u, "").replace(/\/$/u, ""), commit, name: typeof body.name === "string" ? body.name : undefined };
|
|
34
|
+
}
|
|
35
|
+
/** git with no prompts, no hooks, no submodules and https only: the repository is untrusted. */
|
|
36
|
+
async function git(args, cwd) {
|
|
37
|
+
const safe = ["-c", "protocol.allow=never", "-c", "protocol.https.allow=always", "-c", "core.hooksPath=/dev/null", "-c", "core.fsmonitor=false", "-c", "submodule.recurse=false"];
|
|
38
|
+
try {
|
|
39
|
+
const { stdout } = await run("git", [...safe, ...args], { cwd, env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_CONFIG_NOSYSTEM: "1" }, timeout: 5 * 60_000, maxBuffer: 4 * 1024 * 1024 });
|
|
40
|
+
return stdout.trim();
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
const stderr = error.stderr?.trim().split("\n").slice(-3).join(" ");
|
|
44
|
+
throw new SourceError(`git ${args[0]} failed${stderr ? `: ${stderr}` : "."}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** Downloads `repoUrl` into the empty-or-missing `dir`, at `commit` when one is given, else at the default branch. Returns the commit that is checked out. */
|
|
48
|
+
export async function download(repoUrl, dir, commit) {
|
|
49
|
+
await mkdir(dirname(dir), { recursive: true });
|
|
50
|
+
if (commit) {
|
|
51
|
+
await mkdir(dir, { recursive: true });
|
|
52
|
+
await git(["init", "--quiet"], dir);
|
|
53
|
+
await git(["fetch", "--quiet", "--depth", "1", "--no-tags", "--no-recurse-submodules", repoUrl, commit], dir);
|
|
54
|
+
await git(["checkout", "--quiet", "FETCH_HEAD"], dir);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
await git(["clone", "--quiet", "--depth", "1", "--no-tags", "--single-branch", "--no-recurse-submodules", repoUrl, dir], undefined);
|
|
58
|
+
}
|
|
59
|
+
const head = await git(["rev-parse", "HEAD"], dir);
|
|
60
|
+
if (!COMMIT.test(head))
|
|
61
|
+
throw new SourceError("git did not report a commit.");
|
|
62
|
+
return head;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source.js","sourceRoot":"","sources":["../../../src/agent/local/source.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What is known about one Agent running on this computer. It lives with Frely's own files, not in the Agent's folder: the sandbox
|
|
3
|
+
* cannot read or write there, so the program it runs cannot change which sites it was approved for.
|
|
4
|
+
*/
|
|
5
|
+
export interface LocalAgentRecord {
|
|
6
|
+
version: 1;
|
|
7
|
+
slug: string;
|
|
8
|
+
/** What the person typed: `username/name` or a GitHub address. */
|
|
9
|
+
source: string;
|
|
10
|
+
repoUrl: string;
|
|
11
|
+
commit: string;
|
|
12
|
+
name: string;
|
|
13
|
+
workspace: string;
|
|
14
|
+
setup: string | undefined;
|
|
15
|
+
run: string;
|
|
16
|
+
/** Sites from frely.yaml, asked for when the Agent is added and started. */
|
|
17
|
+
network: string[];
|
|
18
|
+
addedAt: string;
|
|
19
|
+
setupDone: boolean;
|
|
20
|
+
/** Approved site -> when the approval ends (ISO time). */
|
|
21
|
+
approvals: Record<string, string>;
|
|
22
|
+
pid: number | undefined;
|
|
23
|
+
startedAt: string | undefined;
|
|
24
|
+
}
|
|
25
|
+
export declare const SLUG: RegExp;
|
|
26
|
+
/** Records, logs and sockets: `~/.config/frely/agent-local`. */
|
|
27
|
+
export declare const stateDir: () => string;
|
|
28
|
+
/** One folder per Agent, the only place its program can write besides the system temp folder. */
|
|
29
|
+
export declare const agentsRoot: () => string;
|
|
30
|
+
export declare function assertSlug(slug: string): void;
|
|
31
|
+
export declare const workspaceOf: (slug: string) => string;
|
|
32
|
+
export declare const recordPath: (slug: string) => string;
|
|
33
|
+
export declare const logPath: (slug: string) => string;
|
|
34
|
+
/** Short enough for the ~100 character limit of a Unix socket path. */
|
|
35
|
+
export declare const socketPath: (slug: string) => string;
|
|
36
|
+
/** Whether `path` is inside the Agents folder (and not the folder itself), so a removal can never reach anything else. */
|
|
37
|
+
export declare function insideAgentsRoot(path: string): boolean;
|
|
38
|
+
export declare function readRecord(slug: string): Promise<LocalAgentRecord | undefined>;
|
|
39
|
+
export declare function writeRecord(record: LocalAgentRecord): Promise<void>;
|
|
40
|
+
export declare function listRecords(): Promise<LocalAgentRecord[]>;
|
|
41
|
+
export declare function removeRecordFiles(slug: string): Promise<void>;
|
|
42
|
+
/** Whether the process the record names is still running. */
|
|
43
|
+
export declare function isRunning(record: Pick<LocalAgentRecord, "pid">): boolean;
|
|
44
|
+
/** Approvals that have not ended yet, as a site list. */
|
|
45
|
+
export declare function approvedHosts(record: Pick<LocalAgentRecord, "approvals" | "network">, now?: number): string[];
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { chmod, mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join, resolve, sep } from "node:path";
|
|
5
|
+
export const SLUG = /^[a-z0-9][a-z0-9-]{0,62}$/u;
|
|
6
|
+
const configRoot = () => process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
7
|
+
/** Records, logs and sockets: `~/.config/frely/agent-local`. */
|
|
8
|
+
export const stateDir = () => join(configRoot(), "frely", "agent-local");
|
|
9
|
+
/** One folder per Agent, the only place its program can write besides the system temp folder. */
|
|
10
|
+
export const agentsRoot = () => resolve(process.env.FRELY_AGENTS_DIR || join(homedir(), "frely-agents"));
|
|
11
|
+
export function assertSlug(slug) {
|
|
12
|
+
if (!SLUG.test(slug))
|
|
13
|
+
throw new Error("The Agent name must be lower-case letters, digits and dashes.");
|
|
14
|
+
}
|
|
15
|
+
export const workspaceOf = (slug) => join(agentsRoot(), slug);
|
|
16
|
+
export const recordPath = (slug) => join(stateDir(), `${slug}.json`);
|
|
17
|
+
export const logPath = (slug) => join(stateDir(), `${slug}.log`);
|
|
18
|
+
/** Short enough for the ~100 character limit of a Unix socket path. */
|
|
19
|
+
export const socketPath = (slug) => join(process.env.XDG_RUNTIME_DIR || stateDir(), `m-${createHash("sha256").update(slug).digest("hex").slice(0, 10)}.sock`);
|
|
20
|
+
/** Whether `path` is inside the Agents folder (and not the folder itself), so a removal can never reach anything else. */
|
|
21
|
+
export function insideAgentsRoot(path) {
|
|
22
|
+
const root = agentsRoot();
|
|
23
|
+
const target = resolve(path);
|
|
24
|
+
return target !== root && target.startsWith(root.endsWith(sep) ? root : root + sep);
|
|
25
|
+
}
|
|
26
|
+
export async function readRecord(slug) {
|
|
27
|
+
assertSlug(slug);
|
|
28
|
+
const raw = await readFile(recordPath(slug), "utf8").catch(() => undefined);
|
|
29
|
+
if (raw === undefined)
|
|
30
|
+
return undefined;
|
|
31
|
+
try {
|
|
32
|
+
const value = JSON.parse(raw);
|
|
33
|
+
return value.version === 1 && value.slug === slug && typeof value.run === "string" ? value : undefined;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export async function writeRecord(record) {
|
|
40
|
+
await mkdir(stateDir(), { recursive: true, mode: 0o700 });
|
|
41
|
+
const target = recordPath(record.slug);
|
|
42
|
+
const temp = `${target}.${process.pid}.tmp`;
|
|
43
|
+
await writeFile(temp, JSON.stringify(record, null, 2) + "\n", { mode: 0o600 });
|
|
44
|
+
await rename(temp, target);
|
|
45
|
+
await chmod(target, 0o600).catch(() => undefined);
|
|
46
|
+
}
|
|
47
|
+
export async function listRecords() {
|
|
48
|
+
const names = await readdir(stateDir()).catch(() => []);
|
|
49
|
+
const records = [];
|
|
50
|
+
for (const name of names.filter((file) => file.endsWith(".json")).sort()) {
|
|
51
|
+
const record = await readRecord(name.slice(0, -5)).catch(() => undefined);
|
|
52
|
+
if (record)
|
|
53
|
+
records.push(record);
|
|
54
|
+
}
|
|
55
|
+
return records;
|
|
56
|
+
}
|
|
57
|
+
export async function removeRecordFiles(slug) {
|
|
58
|
+
await rm(recordPath(slug), { force: true });
|
|
59
|
+
await rm(logPath(slug), { force: true });
|
|
60
|
+
await rm(socketPath(slug), { force: true });
|
|
61
|
+
}
|
|
62
|
+
/** Whether the process the record names is still running. */
|
|
63
|
+
export function isRunning(record) {
|
|
64
|
+
if (record.pid === undefined)
|
|
65
|
+
return false;
|
|
66
|
+
try {
|
|
67
|
+
process.kill(record.pid, 0);
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
return error.code === "EPERM";
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** Approvals that have not ended yet, as a site list. */
|
|
75
|
+
export function approvedHosts(record, now = Date.now()) {
|
|
76
|
+
return record.network.filter((host) => { const end = record.approvals[host]; return end !== undefined && Date.parse(end) > now; });
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/agent/local/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare class SupervisorError extends Error {
|
|
2
|
+
constructor(message: string);
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Runs one Agent until it exits or this process is told to stop: the model proxy on a Unix socket, then the Agent's `local.run`
|
|
6
|
+
* inside the sandbox with only Frely's gateway, the proxy name and the approved sites reachable. Everything the program prints goes to
|
|
7
|
+
* this process's output, which `frely agent local start` points at the Agent's log file.
|
|
8
|
+
*/
|
|
9
|
+
export declare function superviseAgent(slug: string): Promise<number>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { requireLogin } from "../../auth.js";
|
|
2
|
+
import { terminateProcessTree } from "../../runtime/process-tree.js";
|
|
3
|
+
import { restrictSandboxNetwork, shutdownSandbox } from "../../runtime/sandbox.js";
|
|
4
|
+
import { agentEnv, gatewayOrigin, readApiKey, spawnConfined } from "./confine.js";
|
|
5
|
+
import { MODEL_HOST, startModelProxy } from "./model-proxy.js";
|
|
6
|
+
import { approvedHosts, readRecord, socketPath, writeRecord } from "./store.js";
|
|
7
|
+
export class SupervisorError extends Error {
|
|
8
|
+
constructor(message) { super(message); this.name = "SupervisorError"; }
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Runs one Agent until it exits or this process is told to stop: the model proxy on a Unix socket, then the Agent's `local.run`
|
|
12
|
+
* inside the sandbox with only Frely's gateway, the proxy name and the approved sites reachable. Everything the program prints goes to
|
|
13
|
+
* this process's output, which `frely agent local start` points at the Agent's log file.
|
|
14
|
+
*/
|
|
15
|
+
export async function superviseAgent(slug) {
|
|
16
|
+
const record = await readRecord(slug);
|
|
17
|
+
if (!record)
|
|
18
|
+
throw new SupervisorError(`No local Agent named ${slug}. Add it with frely agent local add.`);
|
|
19
|
+
if (!record.setupDone)
|
|
20
|
+
throw new SupervisorError(`${slug} was not set up yet. Run frely agent local add again to finish.`);
|
|
21
|
+
const login = await requireLogin().catch(() => { throw new SupervisorError("Frely login is required. Run `frely login`."); });
|
|
22
|
+
const origin = gatewayOrigin(login.config.relayUrl);
|
|
23
|
+
const apiKey = await readApiKey(origin);
|
|
24
|
+
if (!apiKey)
|
|
25
|
+
throw new SupervisorError(`${slug} needs a Frely API key for its model calls. Create one at ${login.config.relayUrl}/user/keys, then run: frely agent local key --api-key-stdin`);
|
|
26
|
+
const sites = approvedHosts(record);
|
|
27
|
+
const skipped = record.network.filter((host) => !sites.includes(host));
|
|
28
|
+
if (skipped.length > 0)
|
|
29
|
+
process.stderr.write(`Not approved, so blocked: ${skipped.join(", ")}. Run frely agent local start ${slug} again to ask.\n`);
|
|
30
|
+
const socket = socketPath(slug);
|
|
31
|
+
restrictSandboxNetwork({ allowedDomains: [new URL(origin).hostname, MODEL_HOST, ...sites], mitmProxy: { socketPath: socket, domains: [MODEL_HOST] } });
|
|
32
|
+
const proxy = await startModelProxy({ socketPath: socket, upstream: origin, authorization: async () => `Bearer ${apiKey}` });
|
|
33
|
+
let confined;
|
|
34
|
+
try {
|
|
35
|
+
confined = await spawnConfined(record.run, record.workspace, agentEnv(record.workspace, { model: true }));
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
await proxy.close();
|
|
39
|
+
await shutdownSandbox();
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
const { child } = confined;
|
|
43
|
+
child.stdout?.pipe(process.stdout, { end: false });
|
|
44
|
+
child.stderr?.pipe(process.stderr, { end: false });
|
|
45
|
+
await writeRecord({ ...record, pid: process.pid, startedAt: new Date().toISOString() });
|
|
46
|
+
process.stderr.write(`[frely] ${slug} started${sites.length ? `; sites: ${sites.join(", ")}` : ""}\n`);
|
|
47
|
+
const stop = () => { void terminateProcessTree(child); };
|
|
48
|
+
process.once("SIGTERM", stop);
|
|
49
|
+
process.once("SIGINT", stop);
|
|
50
|
+
const code = await new Promise((done) => { child.once("error", () => done(1)); child.once("close", (exit) => done(exit ?? 1)); });
|
|
51
|
+
const blocked = confined.violations();
|
|
52
|
+
if (blocked.length > 0)
|
|
53
|
+
process.stderr.write(`[frely] The sandbox blocked:\n${[...new Set(blocked)].slice(0, 10).join("\n")}\n`);
|
|
54
|
+
await proxy.close();
|
|
55
|
+
await shutdownSandbox();
|
|
56
|
+
const latest = await readRecord(slug);
|
|
57
|
+
if (latest)
|
|
58
|
+
await writeRecord({ ...latest, pid: undefined, startedAt: undefined });
|
|
59
|
+
process.stderr.write(`[frely] ${slug} stopped (exit ${code}).\n`);
|
|
60
|
+
return code;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=supervisor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"supervisor.js","sourceRoot":"","sources":["../../../src/agent/local/supervisor.ts"],"names":[],"mappings":"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"}
|
package/dist/agent-help.d.ts
CHANGED
|
@@ -144,19 +144,19 @@ export declare const COMMANDS: readonly [{
|
|
|
144
144
|
readonly purpose: "Install a download-install Prompt or Skill from the marketplace through Frely Cloud. The paid part is installed only with a valid pass; otherwise the command prints how to buy one. Skills go to the host Skill folder, Prompts to --dir (default: current directory). Scripts are saved without execute permission.";
|
|
145
145
|
}, {
|
|
146
146
|
readonly id: "remote.tools";
|
|
147
|
-
readonly usage: "frely remote tools <username/name> [--json]";
|
|
147
|
+
readonly usage: "frely remote tools <username/name> [--json] [--replace]";
|
|
148
148
|
readonly auth: "cloud-oauth";
|
|
149
149
|
readonly effect: "remote-read";
|
|
150
|
-
readonly purpose: "List the tools of a remote MCP product from the marketplace, with their input schemas, without configuring it in an MCP client. The first use asks Frely for a key that works only for this MCP.";
|
|
150
|
+
readonly purpose: "List the tools of a remote MCP product from the marketplace, with their input schemas, without configuring it in an MCP client. The first use asks Frely for a key that works only for this MCP. If the name now belongs to a different MCP than the one connected before, it stops; --replace switches to the new one.";
|
|
151
151
|
}, {
|
|
152
152
|
readonly id: "remote.call";
|
|
153
|
-
readonly usage: "frely remote call <username/name> <tool> [--json '<object>' | --input <file>]";
|
|
153
|
+
readonly usage: "frely remote call <username/name> <tool> [--json '<object>' | --input <file>] [--replace]";
|
|
154
154
|
readonly auth: "cloud-oauth";
|
|
155
155
|
readonly effect: "remote-call";
|
|
156
156
|
readonly purpose: "Call one tool of a remote MCP product and print the result as JSON (exit code 1 when the tool reports an error). Each call is billed or needs a pass exactly as in an MCP client; ask the user before a call that writes data they did not ask for.";
|
|
157
157
|
}, {
|
|
158
158
|
readonly id: "remote.config";
|
|
159
|
-
readonly usage: "frely remote config <username/name> [--write <file>]";
|
|
159
|
+
readonly usage: "frely remote config <username/name> [--write <file>] [--replace]";
|
|
160
160
|
readonly auth: "cloud-oauth";
|
|
161
161
|
readonly effect: "remote-write";
|
|
162
162
|
readonly purpose: "Get a new key for one remote MCP product (it replaces the previous one) and print the mcpServers JSON that MCP clients take for it; with --write the entry is merged into that JSON file (created 0600) instead of printed. The key works only for this MCP.";
|
|
@@ -184,12 +184,54 @@ export declare const COMMANDS: readonly [{
|
|
|
184
184
|
readonly auth: "none";
|
|
185
185
|
readonly effect: "local-write";
|
|
186
186
|
readonly purpose: "Remove an installed Agent Skill and its saved API key.";
|
|
187
|
+
}, {
|
|
188
|
+
readonly id: "agent.local.add";
|
|
189
|
+
readonly usage: "frely agent local add <username/name | https://github.com/owner/repo> [--name <folder-name>]";
|
|
190
|
+
readonly auth: "optional-account";
|
|
191
|
+
readonly effect: "local-write";
|
|
192
|
+
readonly purpose: "Download an Agent's repository into its own folder under ~/frely-agents and run its setup inside the sandbox. The sandbox can write only that folder, cannot read credential folders and can reach only Frely's API plus the sites the repository lists in frely.yaml, each of which the owner approves once through a link (exit code 3 with the link when nobody can wait; approve, then run the same command again). Needs a frely.yaml with a local.run command.";
|
|
193
|
+
}, {
|
|
194
|
+
readonly id: "agent.local.start";
|
|
195
|
+
readonly usage: "frely agent local start <name> [--foreground]";
|
|
196
|
+
readonly auth: "account-and-api-key";
|
|
197
|
+
readonly effect: "local-process";
|
|
198
|
+
readonly purpose: "Run a local Agent in the background inside the sandbox. Its model calls go through a local proxy that adds the Frely API key (saved with `frely agent local key`), so the repository's code never sees it. Billed to the key like any other API call.";
|
|
199
|
+
}, {
|
|
200
|
+
readonly id: "agent.local.stop";
|
|
201
|
+
readonly usage: "frely agent local stop <name>";
|
|
202
|
+
readonly auth: "none";
|
|
203
|
+
readonly effect: "local-process";
|
|
204
|
+
readonly purpose: "Stop a running local Agent. Run start again afterwards to apply newly approved sites.";
|
|
205
|
+
}, {
|
|
206
|
+
readonly id: "agent.local.logs";
|
|
207
|
+
readonly usage: "frely agent local logs <name> [--lines <n>] [--follow]";
|
|
208
|
+
readonly auth: "none";
|
|
209
|
+
readonly effect: "read";
|
|
210
|
+
readonly purpose: "Show what a local Agent printed, including what the sandbox blocked.";
|
|
211
|
+
}, {
|
|
212
|
+
readonly id: "agent.local.list";
|
|
213
|
+
readonly usage: "frely agent local list [--json]";
|
|
214
|
+
readonly auth: "none";
|
|
215
|
+
readonly effect: "read";
|
|
216
|
+
readonly purpose: "List the Agents on this computer, whether each is running, and the sites each has been approved for.";
|
|
217
|
+
}, {
|
|
218
|
+
readonly id: "agent.local.remove";
|
|
219
|
+
readonly usage: "frely agent local remove <name>";
|
|
220
|
+
readonly auth: "none";
|
|
221
|
+
readonly effect: "local-write";
|
|
222
|
+
readonly purpose: "Stop a local Agent and delete its folder and records. Only the Agent's own folder is removed.";
|
|
223
|
+
}, {
|
|
224
|
+
readonly id: "agent.local.key";
|
|
225
|
+
readonly usage: "frely agent local key --api-key-stdin | --clear";
|
|
226
|
+
readonly auth: "account";
|
|
227
|
+
readonly effect: "local-write";
|
|
228
|
+
readonly purpose: "Save the Frely API key that local Agents use for model calls; it goes only through stdin to secure storage. FRELY_API_KEY overrides it.";
|
|
187
229
|
}, {
|
|
188
230
|
readonly id: "provider.share";
|
|
189
231
|
readonly usage: "frely provider share [ollama|openai-compatible] [--url <loopback-v1-url>] [--models <a,b>] [--slot <slot-id>] [--name <name>]";
|
|
190
232
|
readonly auth: "account";
|
|
191
233
|
readonly effect: "remote-write";
|
|
192
|
-
readonly purpose: "Publish a local model Provider. If a prepared Provider on this device is not finished, resume it instead.";
|
|
234
|
+
readonly purpose: "Publish a local model Provider. If a prepared Provider on this device is not finished, resume it instead. Prints this week's remaining relay allowance; relayed traffic counts against the weekly relay allowance.";
|
|
193
235
|
}, {
|
|
194
236
|
readonly id: "provider.list";
|
|
195
237
|
readonly usage: "frely provider list [--json]";
|
|
@@ -202,6 +244,18 @@ export declare const COMMANDS: readonly [{
|
|
|
202
244
|
readonly auth: "account";
|
|
203
245
|
readonly effect: "remote-write";
|
|
204
246
|
readonly purpose: "Make a web service on this computer (http://127.0.0.1:<port>) reachable from the Internet by a private link through Frely's relay, until Ctrl+C or expiry (at most 24 hours). Asks first. The relay allowance is a plan limit per UTC week; --rewrite-host sends Host: localhost to dev servers that refuse other names. Not the same as `frely provider share` (local models) or `frely tunnel` (command sandbox).";
|
|
247
|
+
}, {
|
|
248
|
+
readonly id: "service.share";
|
|
249
|
+
readonly usage: "frely service share --type mcp|a2a --port <n> [--label <text>] [--background] [--json]";
|
|
250
|
+
readonly auth: "account";
|
|
251
|
+
readonly effect: "remote-write";
|
|
252
|
+
readonly purpose: "Connect an MCP server or A2A Agent that runs on this computer (127.0.0.1:<port>) to Frely so you can sell it, without a public address: only Frely's own cloud can reach it, the port never leaves this computer, and Frely never supplies a target. Runs until Ctrl+C, or with --background the device background service keeps it connected. Prints this week's remaining relay allowance; relayed traffic counts against the weekly relay allowance.";
|
|
253
|
+
}, {
|
|
254
|
+
readonly id: "service.manage";
|
|
255
|
+
readonly usage: "frely service list [--json] | stop <id|label> | remove <id|label>";
|
|
256
|
+
readonly auth: "account";
|
|
257
|
+
readonly effect: "remote-write";
|
|
258
|
+
readonly purpose: "List registered services, stop one on this computer (it stays registered), or remove it from Frely (products that pointed at it become unreachable).";
|
|
205
259
|
}, {
|
|
206
260
|
readonly id: "tunnel";
|
|
207
261
|
readonly usage: "frely tunnel <host>:<port> [--local-port N]";
|
|
@@ -517,19 +571,19 @@ export declare function agentHelp(): {
|
|
|
517
571
|
readonly purpose: "Install a download-install Prompt or Skill from the marketplace through Frely Cloud. The paid part is installed only with a valid pass; otherwise the command prints how to buy one. Skills go to the host Skill folder, Prompts to --dir (default: current directory). Scripts are saved without execute permission.";
|
|
518
572
|
}, {
|
|
519
573
|
readonly id: "remote.tools";
|
|
520
|
-
readonly usage: "frely remote tools <username/name> [--json]";
|
|
574
|
+
readonly usage: "frely remote tools <username/name> [--json] [--replace]";
|
|
521
575
|
readonly auth: "cloud-oauth";
|
|
522
576
|
readonly effect: "remote-read";
|
|
523
|
-
readonly purpose: "List the tools of a remote MCP product from the marketplace, with their input schemas, without configuring it in an MCP client. The first use asks Frely for a key that works only for this MCP.";
|
|
577
|
+
readonly purpose: "List the tools of a remote MCP product from the marketplace, with their input schemas, without configuring it in an MCP client. The first use asks Frely for a key that works only for this MCP. If the name now belongs to a different MCP than the one connected before, it stops; --replace switches to the new one.";
|
|
524
578
|
}, {
|
|
525
579
|
readonly id: "remote.call";
|
|
526
|
-
readonly usage: "frely remote call <username/name> <tool> [--json '<object>' | --input <file>]";
|
|
580
|
+
readonly usage: "frely remote call <username/name> <tool> [--json '<object>' | --input <file>] [--replace]";
|
|
527
581
|
readonly auth: "cloud-oauth";
|
|
528
582
|
readonly effect: "remote-call";
|
|
529
583
|
readonly purpose: "Call one tool of a remote MCP product and print the result as JSON (exit code 1 when the tool reports an error). Each call is billed or needs a pass exactly as in an MCP client; ask the user before a call that writes data they did not ask for.";
|
|
530
584
|
}, {
|
|
531
585
|
readonly id: "remote.config";
|
|
532
|
-
readonly usage: "frely remote config <username/name> [--write <file>]";
|
|
586
|
+
readonly usage: "frely remote config <username/name> [--write <file>] [--replace]";
|
|
533
587
|
readonly auth: "cloud-oauth";
|
|
534
588
|
readonly effect: "remote-write";
|
|
535
589
|
readonly purpose: "Get a new key for one remote MCP product (it replaces the previous one) and print the mcpServers JSON that MCP clients take for it; with --write the entry is merged into that JSON file (created 0600) instead of printed. The key works only for this MCP.";
|
|
@@ -557,12 +611,54 @@ export declare function agentHelp(): {
|
|
|
557
611
|
readonly auth: "none";
|
|
558
612
|
readonly effect: "local-write";
|
|
559
613
|
readonly purpose: "Remove an installed Agent Skill and its saved API key.";
|
|
614
|
+
}, {
|
|
615
|
+
readonly id: "agent.local.add";
|
|
616
|
+
readonly usage: "frely agent local add <username/name | https://github.com/owner/repo> [--name <folder-name>]";
|
|
617
|
+
readonly auth: "optional-account";
|
|
618
|
+
readonly effect: "local-write";
|
|
619
|
+
readonly purpose: "Download an Agent's repository into its own folder under ~/frely-agents and run its setup inside the sandbox. The sandbox can write only that folder, cannot read credential folders and can reach only Frely's API plus the sites the repository lists in frely.yaml, each of which the owner approves once through a link (exit code 3 with the link when nobody can wait; approve, then run the same command again). Needs a frely.yaml with a local.run command.";
|
|
620
|
+
}, {
|
|
621
|
+
readonly id: "agent.local.start";
|
|
622
|
+
readonly usage: "frely agent local start <name> [--foreground]";
|
|
623
|
+
readonly auth: "account-and-api-key";
|
|
624
|
+
readonly effect: "local-process";
|
|
625
|
+
readonly purpose: "Run a local Agent in the background inside the sandbox. Its model calls go through a local proxy that adds the Frely API key (saved with `frely agent local key`), so the repository's code never sees it. Billed to the key like any other API call.";
|
|
626
|
+
}, {
|
|
627
|
+
readonly id: "agent.local.stop";
|
|
628
|
+
readonly usage: "frely agent local stop <name>";
|
|
629
|
+
readonly auth: "none";
|
|
630
|
+
readonly effect: "local-process";
|
|
631
|
+
readonly purpose: "Stop a running local Agent. Run start again afterwards to apply newly approved sites.";
|
|
632
|
+
}, {
|
|
633
|
+
readonly id: "agent.local.logs";
|
|
634
|
+
readonly usage: "frely agent local logs <name> [--lines <n>] [--follow]";
|
|
635
|
+
readonly auth: "none";
|
|
636
|
+
readonly effect: "read";
|
|
637
|
+
readonly purpose: "Show what a local Agent printed, including what the sandbox blocked.";
|
|
638
|
+
}, {
|
|
639
|
+
readonly id: "agent.local.list";
|
|
640
|
+
readonly usage: "frely agent local list [--json]";
|
|
641
|
+
readonly auth: "none";
|
|
642
|
+
readonly effect: "read";
|
|
643
|
+
readonly purpose: "List the Agents on this computer, whether each is running, and the sites each has been approved for.";
|
|
644
|
+
}, {
|
|
645
|
+
readonly id: "agent.local.remove";
|
|
646
|
+
readonly usage: "frely agent local remove <name>";
|
|
647
|
+
readonly auth: "none";
|
|
648
|
+
readonly effect: "local-write";
|
|
649
|
+
readonly purpose: "Stop a local Agent and delete its folder and records. Only the Agent's own folder is removed.";
|
|
650
|
+
}, {
|
|
651
|
+
readonly id: "agent.local.key";
|
|
652
|
+
readonly usage: "frely agent local key --api-key-stdin | --clear";
|
|
653
|
+
readonly auth: "account";
|
|
654
|
+
readonly effect: "local-write";
|
|
655
|
+
readonly purpose: "Save the Frely API key that local Agents use for model calls; it goes only through stdin to secure storage. FRELY_API_KEY overrides it.";
|
|
560
656
|
}, {
|
|
561
657
|
readonly id: "provider.share";
|
|
562
658
|
readonly usage: "frely provider share [ollama|openai-compatible] [--url <loopback-v1-url>] [--models <a,b>] [--slot <slot-id>] [--name <name>]";
|
|
563
659
|
readonly auth: "account";
|
|
564
660
|
readonly effect: "remote-write";
|
|
565
|
-
readonly purpose: "Publish a local model Provider. If a prepared Provider on this device is not finished, resume it instead.";
|
|
661
|
+
readonly purpose: "Publish a local model Provider. If a prepared Provider on this device is not finished, resume it instead. Prints this week's remaining relay allowance; relayed traffic counts against the weekly relay allowance.";
|
|
566
662
|
}, {
|
|
567
663
|
readonly id: "provider.list";
|
|
568
664
|
readonly usage: "frely provider list [--json]";
|
|
@@ -575,6 +671,18 @@ export declare function agentHelp(): {
|
|
|
575
671
|
readonly auth: "account";
|
|
576
672
|
readonly effect: "remote-write";
|
|
577
673
|
readonly purpose: "Make a web service on this computer (http://127.0.0.1:<port>) reachable from the Internet by a private link through Frely's relay, until Ctrl+C or expiry (at most 24 hours). Asks first. The relay allowance is a plan limit per UTC week; --rewrite-host sends Host: localhost to dev servers that refuse other names. Not the same as `frely provider share` (local models) or `frely tunnel` (command sandbox).";
|
|
674
|
+
}, {
|
|
675
|
+
readonly id: "service.share";
|
|
676
|
+
readonly usage: "frely service share --type mcp|a2a --port <n> [--label <text>] [--background] [--json]";
|
|
677
|
+
readonly auth: "account";
|
|
678
|
+
readonly effect: "remote-write";
|
|
679
|
+
readonly purpose: "Connect an MCP server or A2A Agent that runs on this computer (127.0.0.1:<port>) to Frely so you can sell it, without a public address: only Frely's own cloud can reach it, the port never leaves this computer, and Frely never supplies a target. Runs until Ctrl+C, or with --background the device background service keeps it connected. Prints this week's remaining relay allowance; relayed traffic counts against the weekly relay allowance.";
|
|
680
|
+
}, {
|
|
681
|
+
readonly id: "service.manage";
|
|
682
|
+
readonly usage: "frely service list [--json] | stop <id|label> | remove <id|label>";
|
|
683
|
+
readonly auth: "account";
|
|
684
|
+
readonly effect: "remote-write";
|
|
685
|
+
readonly purpose: "List registered services, stop one on this computer (it stays registered), or remove it from Frely (products that pointed at it become unreachable).";
|
|
578
686
|
}, {
|
|
579
687
|
readonly id: "tunnel";
|
|
580
688
|
readonly usage: "frely tunnel <host>:<port> [--local-port N]";
|
package/dist/agent-help.js
CHANGED
|
@@ -24,16 +24,25 @@ export const COMMANDS = [
|
|
|
24
24
|
{ id: "item.run", usage: "frely item run <script> [args...]", auth: "cloud-oauth", effect: "local-execution", purpose: "Run an encrypted paid script of an installed item inside the Frely command sandbox. The placeholder file at the script's path calls this for you. The access lease is renewed when it is near its end; without a valid pass the script does not run." },
|
|
25
25
|
{ id: "item.update", usage: "frely item update [<folder>] [--json]", auth: "cloud-oauth", effect: "local-write", purpose: "Without a folder, list the installed marketplace items and say which have a newer version; nothing is changed. With a folder, install the newer version into that folder, after you decide to. Frely never updates an item by itself. A folder you edited is refused, and a new version needs a new review (frely item trust) unless Frely's scan passed for it." },
|
|
26
26
|
{ id: "item.install", usage: "frely item install <username/name[@version]> [--host chatgpt|codex|claude-code|pi|generic] [--scope global|project] [--dir <path>] [--json]", auth: "cloud-oauth", effect: "local-write", purpose: "Install a download-install Prompt or Skill from the marketplace through Frely Cloud. The paid part is installed only with a valid pass; otherwise the command prints how to buy one. Skills go to the host Skill folder, Prompts to --dir (default: current directory). Scripts are saved without execute permission." },
|
|
27
|
-
{ id: "remote.tools", usage: "frely remote tools <username/name> [--json]", auth: "cloud-oauth", effect: "remote-read", purpose: "List the tools of a remote MCP product from the marketplace, with their input schemas, without configuring it in an MCP client. The first use asks Frely for a key that works only for this MCP." },
|
|
28
|
-
{ id: "remote.call", usage: "frely remote call <username/name> <tool> [--json '<object>' | --input <file>]", auth: "cloud-oauth", effect: "remote-call", purpose: "Call one tool of a remote MCP product and print the result as JSON (exit code 1 when the tool reports an error). Each call is billed or needs a pass exactly as in an MCP client; ask the user before a call that writes data they did not ask for." },
|
|
29
|
-
{ id: "remote.config", usage: "frely remote config <username/name> [--write <file>]", auth: "cloud-oauth", effect: "remote-write", purpose: "Get a new key for one remote MCP product (it replaces the previous one) and print the mcpServers JSON that MCP clients take for it; with --write the entry is merged into that JSON file (created 0600) instead of printed. The key works only for this MCP." },
|
|
27
|
+
{ id: "remote.tools", usage: "frely remote tools <username/name> [--json] [--replace]", auth: "cloud-oauth", effect: "remote-read", purpose: "List the tools of a remote MCP product from the marketplace, with their input schemas, without configuring it in an MCP client. The first use asks Frely for a key that works only for this MCP. If the name now belongs to a different MCP than the one connected before, it stops; --replace switches to the new one." },
|
|
28
|
+
{ id: "remote.call", usage: "frely remote call <username/name> <tool> [--json '<object>' | --input <file>] [--replace]", auth: "cloud-oauth", effect: "remote-call", purpose: "Call one tool of a remote MCP product and print the result as JSON (exit code 1 when the tool reports an error). Each call is billed or needs a pass exactly as in an MCP client; ask the user before a call that writes data they did not ask for." },
|
|
29
|
+
{ id: "remote.config", usage: "frely remote config <username/name> [--write <file>] [--replace]", auth: "cloud-oauth", effect: "remote-write", purpose: "Get a new key for one remote MCP product (it replaces the previous one) and print the mcpServers JSON that MCP clients take for it; with --write the entry is merged into that JSON file (created 0600) instead of printed. The key works only for this MCP." },
|
|
30
30
|
{ id: "agent.install", usage: "frely agent install <username/name[@version]> [--host chatgpt|codex|claude-code|pi|generic] [--scope global|project] [--dir <project>] [--api-key-stdin] [--json]", auth: "optional-api-key", effect: "local-write", purpose: "Install the public Agent's trigger Skill; an API key goes only through stdin to secure storage." },
|
|
31
31
|
{ id: "agent.run", usage: "frely agent run <username/name> (--input <text>|--input-stdin) [--json]", auth: "installed-api-key-or-account", effect: "remote-call", purpose: "Send the complete relevant user request to the installed Agent through model-scoped MCP. Usage may be billed." },
|
|
32
32
|
{ id: "agent.status", usage: "frely agent status (<username/name>|--api-key-stdin [--relay <url>]) [--json]", auth: "none-or-api-key", effect: "read", purpose: "Inspect an installed Agent Skill and, for API-key installs, its Key budget. With --api-key-stdin, read that Key's budget without installing or logging in." },
|
|
33
33
|
{ id: "agent.remove", usage: "frely agent remove <username/name> [--json]", auth: "none", effect: "local-write", purpose: "Remove an installed Agent Skill and its saved API key." },
|
|
34
|
-
{ id: "
|
|
34
|
+
{ id: "agent.local.add", usage: "frely agent local add <username/name | https://github.com/owner/repo> [--name <folder-name>]", auth: "optional-account", effect: "local-write", purpose: "Download an Agent's repository into its own folder under ~/frely-agents and run its setup inside the sandbox. The sandbox can write only that folder, cannot read credential folders and can reach only Frely's API plus the sites the repository lists in frely.yaml, each of which the owner approves once through a link (exit code 3 with the link when nobody can wait; approve, then run the same command again). Needs a frely.yaml with a local.run command." },
|
|
35
|
+
{ id: "agent.local.start", usage: "frely agent local start <name> [--foreground]", auth: "account-and-api-key", effect: "local-process", purpose: "Run a local Agent in the background inside the sandbox. Its model calls go through a local proxy that adds the Frely API key (saved with `frely agent local key`), so the repository's code never sees it. Billed to the key like any other API call." },
|
|
36
|
+
{ id: "agent.local.stop", usage: "frely agent local stop <name>", auth: "none", effect: "local-process", purpose: "Stop a running local Agent. Run start again afterwards to apply newly approved sites." },
|
|
37
|
+
{ id: "agent.local.logs", usage: "frely agent local logs <name> [--lines <n>] [--follow]", auth: "none", effect: "read", purpose: "Show what a local Agent printed, including what the sandbox blocked." },
|
|
38
|
+
{ id: "agent.local.list", usage: "frely agent local list [--json]", auth: "none", effect: "read", purpose: "List the Agents on this computer, whether each is running, and the sites each has been approved for." },
|
|
39
|
+
{ id: "agent.local.remove", usage: "frely agent local remove <name>", auth: "none", effect: "local-write", purpose: "Stop a local Agent and delete its folder and records. Only the Agent's own folder is removed." },
|
|
40
|
+
{ id: "agent.local.key", usage: "frely agent local key --api-key-stdin | --clear", auth: "account", effect: "local-write", purpose: "Save the Frely API key that local Agents use for model calls; it goes only through stdin to secure storage. FRELY_API_KEY overrides it." },
|
|
41
|
+
{ id: "provider.share", usage: "frely provider share [ollama|openai-compatible] [--url <loopback-v1-url>] [--models <a,b>] [--slot <slot-id>] [--name <name>]", auth: "account", effect: "remote-write", purpose: "Publish a local model Provider. If a prepared Provider on this device is not finished, resume it instead. Prints this week's remaining relay allowance; relayed traffic counts against the weekly relay allowance." },
|
|
35
42
|
{ id: "provider.list", usage: "frely provider list [--json]", auth: "account", effect: "read", purpose: "List configured local Providers." },
|
|
36
43
|
{ id: "share", usage: "frely share <port> [--ttl <hours>] [--rewrite-host] [--yes] [--json]", auth: "account", effect: "remote-write", purpose: "Make a web service on this computer (http://127.0.0.1:<port>) reachable from the Internet by a private link through Frely's relay, until Ctrl+C or expiry (at most 24 hours). Asks first. The relay allowance is a plan limit per UTC week; --rewrite-host sends Host: localhost to dev servers that refuse other names. Not the same as `frely provider share` (local models) or `frely tunnel` (command sandbox)." },
|
|
44
|
+
{ id: "service.share", usage: "frely service share --type mcp|a2a --port <n> [--label <text>] [--background] [--json]", auth: "account", effect: "remote-write", purpose: "Connect an MCP server or A2A Agent that runs on this computer (127.0.0.1:<port>) to Frely so you can sell it, without a public address: only Frely's own cloud can reach it, the port never leaves this computer, and Frely never supplies a target. Runs until Ctrl+C, or with --background the device background service keeps it connected. Prints this week's remaining relay allowance; relayed traffic counts against the weekly relay allowance." },
|
|
45
|
+
{ id: "service.manage", usage: "frely service list [--json] | stop <id|label> | remove <id|label>", auth: "account", effect: "remote-write", purpose: "List registered services, stop one on this computer (it stays registered), or remove it from Frely (products that pointed at it become unreachable)." },
|
|
37
46
|
{ id: "tunnel", usage: "frely tunnel <host>:<port> [--local-port N]", auth: "none", effect: "local-execution", purpose: "Run inside a sandboxed command (run_command): listen on 127.0.0.1:<local-port> (default: the same port) and forward each TCP connection to host:port through the command sandbox's proxy, so database and redis clients connect to 127.0.0.1 without turning the sandbox off. Start it in the same command as the client, for example `frely tunnel db.example.com:5432 & sleep 1; psql -h 127.0.0.1 ...`. Prints the local address and runs until killed." },
|
|
38
47
|
{ id: "sandbox.proxy", usage: "frely sandbox proxy [<http://host:port> [--no-proxy <list>] | --clear]", auth: "none", effect: "local-write", purpose: "Show, save or remove the HTTP(S) proxy that the command sandbox forwards its network traffic to (your own proxy, for example Clash). Without one saved it uses the HTTPS_PROXY / HTTP_PROXY / ALL_PROXY of the CLI process and, on macOS, the system proxy. Your proxy settings are never changed. Takes effect when the MCP service restarts." },
|
|
39
48
|
{ id: "devices", usage: "frely devices [--add] [--json]", auth: "controller", effect: "read", purpose: "List your Frely devices with online state and workspaces (list_devices). --add prints a one-time install command for a new computer." },
|
|
@@ -89,11 +98,11 @@ const GROUPS = [
|
|
|
89
98
|
{ title: "Control your devices", ids: ["devices", "run", "ps", "logs", "kill", "read", "ls", "grep", "edit", "permission", "tools", "call", "controller"] },
|
|
90
99
|
{ title: "Account", ids: ["login", "logout", "uninstall", "doctor", "update"] },
|
|
91
100
|
{ title: "Device MCP", ids: ["mcp.start", "mcp.workspace", "mcp.local", "mcp.lifecycle"] },
|
|
92
|
-
{ title: "Agents", ids: ["agent.install", "agent.run", "agent.status", "agent.remove"] },
|
|
101
|
+
{ title: "Agents", ids: ["agent.install", "agent.run", "agent.status", "agent.remove", "agent.local"] },
|
|
93
102
|
{ title: "Skills", ids: ["skill.install"] },
|
|
94
103
|
{ title: "Marketplace items", ids: ["item.install", "item.update", "item.run", "item.trust"] },
|
|
95
104
|
{ title: "Providers", ids: ["provider.share", "provider.list"] },
|
|
96
|
-
{ title: "Share", ids: ["share"] },
|
|
105
|
+
{ title: "Share", ids: ["share", "service.share", "service.manage"] },
|
|
97
106
|
{ title: "Command sandbox", ids: ["tunnel", "sandbox.proxy"] },
|
|
98
107
|
{ title: "API", ids: ["api", "remote.tools", "remote.call", "remote.config"] },
|
|
99
108
|
];
|
|
@@ -133,9 +142,19 @@ const SHORT_USAGE = {
|
|
|
133
142
|
"agent.run": "frely agent run <username/name> (--input <text>|--input-stdin)",
|
|
134
143
|
"agent.status": "frely agent status <username/name>",
|
|
135
144
|
"agent.remove": "frely agent remove <username/name>",
|
|
145
|
+
"agent.local": "frely agent local add|start|stop|logs|list|remove|key",
|
|
146
|
+
"agent.local.add": "frely agent local add <username/name | github-url>",
|
|
147
|
+
"agent.local.start": "frely agent local start <name>",
|
|
148
|
+
"agent.local.stop": "frely agent local stop <name>",
|
|
149
|
+
"agent.local.logs": "frely agent local logs <name> [--follow]",
|
|
150
|
+
"agent.local.list": "frely agent local list",
|
|
151
|
+
"agent.local.remove": "frely agent local remove <name>",
|
|
152
|
+
"agent.local.key": "frely agent local key --api-key-stdin",
|
|
136
153
|
"provider.share": "frely provider share [ollama|openai-compatible] [--models <a,b>]",
|
|
137
154
|
"provider.list": "frely provider list",
|
|
138
155
|
share: "frely share <port> [--ttl <hours>]",
|
|
156
|
+
"service.share": "frely service share --type mcp|a2a --port <n>",
|
|
157
|
+
"service.manage": "frely service list|stop|remove",
|
|
139
158
|
tunnel: "frely tunnel <host>:<port> [--local-port N]",
|
|
140
159
|
"sandbox.proxy": "frely sandbox proxy [<url> [--no-proxy <list>] | --clear]",
|
|
141
160
|
api: "frely api list|describe|call",
|