@tameflare/cli 0.10.1 → 0.10.3
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 +23 -45
- package/dist/commands/init.js +1 -1
- package/dist/commands/run.js +22 -1
- package/dist/commands/status.js +12 -3
- package/dist/index.js +1 -1
- package/dist/proxy-bootstrap.js +68 -0
- package/dist/utils.js +1 -1
- package/package.json +7 -6
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @tameflare/cli
|
|
2
2
|
|
|
3
|
-
The official CLI for [TameFlare](https://tameflare.com) - secure and govern AI agent traffic through a
|
|
3
|
+
The official CLI for [TameFlare](https://tameflare.com) - secure and govern AI agent traffic through a cloud proxy gateway.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -8,75 +8,53 @@ The official CLI for [TameFlare](https://tameflare.com) - secure and govern AI a
|
|
|
8
8
|
npm i -g @tameflare/cli
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
Or run directly with npx:
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
npx @tameflare/cli init
|
|
15
|
-
```
|
|
16
|
-
|
|
17
11
|
## Quick Start
|
|
18
12
|
|
|
19
13
|
```bash
|
|
20
|
-
# 1.
|
|
21
|
-
tf
|
|
14
|
+
# 1. Log in (opens browser)
|
|
15
|
+
tf login
|
|
22
16
|
|
|
23
|
-
# 2.
|
|
24
|
-
tf
|
|
17
|
+
# 2. Pick a gateway
|
|
18
|
+
tf init --list
|
|
25
19
|
|
|
26
|
-
# 3. Run your agent through the
|
|
27
|
-
tf run --
|
|
20
|
+
# 3. Run your agent through the cloud proxy
|
|
21
|
+
tf run -- python agent.py
|
|
28
22
|
```
|
|
29
23
|
|
|
30
24
|
## Commands
|
|
31
25
|
|
|
32
26
|
| Command | Description |
|
|
33
27
|
|---------|-------------|
|
|
34
|
-
| `tf
|
|
35
|
-
| `tf
|
|
36
|
-
| `tf
|
|
37
|
-
| `tf
|
|
38
|
-
| `tf
|
|
39
|
-
| `tf
|
|
40
|
-
| `tf
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
| `tf permissions set` | Set access rules for a gateway |
|
|
44
|
-
| `tf permissions list` | List access rules |
|
|
45
|
-
| `tf approvals list` | List pending approval requests |
|
|
46
|
-
| `tf approvals approve <id>` | Approve a pending request |
|
|
47
|
-
| `tf approvals deny <id>` | Deny a pending request |
|
|
28
|
+
| `tf login` | Authenticate via browser (saves credentials to `~/.tameflare/`) |
|
|
29
|
+
| `tf logout` | Remove saved credentials |
|
|
30
|
+
| `tf init` | Select a gateway and save config to `.tf/config.yaml` |
|
|
31
|
+
| `tf run -- <cmd>` | Run a process through the cloud proxy |
|
|
32
|
+
| `tf status` | Show gateway config and connectivity |
|
|
33
|
+
| `tf logs` | Open dashboard traffic view in browser |
|
|
34
|
+
| `tf reset` | Remove `.tf/` directory |
|
|
35
|
+
|
|
36
|
+
Connectors, permissions, approvals, and kill switch are managed in the [dashboard](https://tameflare.com/dashboard).
|
|
48
37
|
|
|
49
38
|
## How It Works
|
|
50
39
|
|
|
51
|
-
|
|
40
|
+
When you run `tf run -- python agent.py`, the CLI:
|
|
52
41
|
|
|
53
|
-
1.
|
|
54
|
-
2.
|
|
55
|
-
3.
|
|
56
|
-
4.
|
|
42
|
+
1. Sets `HTTPS_PROXY=https://{gateway_token}@proxy.tameflare.com`
|
|
43
|
+
2. Spawns your process with the proxy environment variable
|
|
44
|
+
3. All outbound HTTP traffic routes through the cloud gateway
|
|
45
|
+
4. The gateway parses requests, checks permissions, and logs everything
|
|
57
46
|
|
|
58
|
-
|
|
47
|
+
No local binary, no Docker, no server to manage. The gateway runs in the cloud at `proxy.tameflare.com`.
|
|
59
48
|
|
|
60
49
|
## Requirements
|
|
61
50
|
|
|
62
51
|
- Node.js >= 18
|
|
63
|
-
- The TameFlare gateway binary (downloaded automatically by `tf init`)
|
|
64
|
-
|
|
65
|
-
## Dashboard
|
|
66
|
-
|
|
67
|
-
TameFlare includes a web dashboard for real-time traffic monitoring, policy management, and approval workflows. Start it with:
|
|
68
|
-
|
|
69
|
-
```bash
|
|
70
|
-
docker compose up
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
Then open [http://localhost:3000](http://localhost:3000).
|
|
74
52
|
|
|
75
53
|
## Links
|
|
76
54
|
|
|
77
55
|
- [Documentation](https://tameflare.com/docs)
|
|
78
56
|
- [GitHub](https://github.com/tameflare/tameflare)
|
|
79
|
-
- [
|
|
57
|
+
- [Dashboard](https://tameflare.com/dashboard)
|
|
80
58
|
|
|
81
59
|
## License
|
|
82
60
|
|
package/dist/commands/init.js
CHANGED
|
@@ -6,7 +6,7 @@ const fs_1 = require("fs");
|
|
|
6
6
|
const readline_1 = require("readline");
|
|
7
7
|
const utils_1 = require("../utils");
|
|
8
8
|
const login_1 = require("./login");
|
|
9
|
-
const PROXY_HOST = "
|
|
9
|
+
const PROXY_HOST = "proxy.tameflare.com";
|
|
10
10
|
function initCommand() {
|
|
11
11
|
const cmd = new commander_1.Command("init")
|
|
12
12
|
.description("Connect this directory to a TameFlare cloud gateway")
|
package/dist/commands/run.js
CHANGED
|
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.runCommand = runCommand;
|
|
4
4
|
const commander_1 = require("commander");
|
|
5
5
|
const child_process_1 = require("child_process");
|
|
6
|
+
const path_1 = require("path");
|
|
7
|
+
const fs_1 = require("fs");
|
|
6
8
|
const utils_1 = require("../utils");
|
|
7
9
|
function runCommand() {
|
|
8
10
|
const cmd = new commander_1.Command("run")
|
|
@@ -16,13 +18,32 @@ function runCommand() {
|
|
|
16
18
|
console.log(`[TF] Proxy: https://${cfg.proxy_host}`);
|
|
17
19
|
console.log(`[TF] Running: ${command} ${args.join(" ")}`);
|
|
18
20
|
console.log("");
|
|
21
|
+
// Register heartbeat so dashboard shows gateway as online
|
|
22
|
+
try {
|
|
23
|
+
await fetch(`${cfg.dashboard_url}/api/gateway/v2/verify`, {
|
|
24
|
+
method: "POST",
|
|
25
|
+
headers: { "Content-Type": "application/json" },
|
|
26
|
+
body: JSON.stringify({ gateway_token: cfg.gateway_token }),
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Non-fatal — gateway still works even if dashboard is temporarily unreachable
|
|
31
|
+
}
|
|
32
|
+
// Locate the proxy bootstrap script (shipped alongside the CLI)
|
|
33
|
+
// Use forward slashes to avoid Windows shell escaping issues with --require
|
|
34
|
+
const bootstrapPath = (0, path_1.resolve)((0, path_1.dirname)(__filename), "..", "proxy-bootstrap.js").replace(/\\/g, "/");
|
|
35
|
+
let nodeOptions = process.env.NODE_OPTIONS || "";
|
|
36
|
+
if ((0, fs_1.existsSync)(bootstrapPath)) {
|
|
37
|
+
const requireFlag = `--require "${bootstrapPath}"`;
|
|
38
|
+
nodeOptions = nodeOptions ? `${nodeOptions} ${requireFlag}` : requireFlag;
|
|
39
|
+
}
|
|
19
40
|
const env = {
|
|
20
41
|
...process.env,
|
|
21
42
|
HTTP_PROXY: proxyUrl,
|
|
22
43
|
HTTPS_PROXY: proxyUrl,
|
|
23
44
|
http_proxy: proxyUrl,
|
|
24
45
|
https_proxy: proxyUrl,
|
|
25
|
-
|
|
46
|
+
NODE_OPTIONS: nodeOptions,
|
|
26
47
|
};
|
|
27
48
|
const child = (0, child_process_1.spawn)(command, args, {
|
|
28
49
|
stdio: "inherit",
|
package/dist/commands/status.js
CHANGED
|
@@ -15,14 +15,23 @@ function statusCommand() {
|
|
|
15
15
|
console.log(` Dashboard: ${cfg.dashboard_url}`);
|
|
16
16
|
console.log(` Proxy: https://${cfg.proxy_host}`);
|
|
17
17
|
console.log("");
|
|
18
|
-
//
|
|
18
|
+
// Ping dashboard and register gateway heartbeat
|
|
19
19
|
try {
|
|
20
|
-
const res = await fetch(`${cfg.dashboard_url}/api/
|
|
20
|
+
const res = await fetch(`${cfg.dashboard_url}/api/gateway/v2/verify`, {
|
|
21
|
+
method: "POST",
|
|
22
|
+
headers: { "Content-Type": "application/json" },
|
|
23
|
+
body: JSON.stringify({ gateway_token: cfg.gateway_token }),
|
|
24
|
+
});
|
|
21
25
|
if (res.ok) {
|
|
22
26
|
console.log(` Dashboard: \x1b[32mreachable\x1b[0m`);
|
|
27
|
+
console.log(` Gateway: \x1b[32monline\x1b[0m`);
|
|
28
|
+
}
|
|
29
|
+
else if (res.status === 401) {
|
|
30
|
+
console.log(` Dashboard: \x1b[32mreachable\x1b[0m`);
|
|
31
|
+
console.log(` Gateway: \x1b[31minvalid token\x1b[0m`);
|
|
23
32
|
}
|
|
24
33
|
else {
|
|
25
|
-
console.log(` Dashboard: \x1b[
|
|
34
|
+
console.log(` Dashboard: \x1b[31merror (${res.status})\x1b[0m`);
|
|
26
35
|
}
|
|
27
36
|
}
|
|
28
37
|
catch {
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ const program = new commander_1.Command();
|
|
|
12
12
|
program
|
|
13
13
|
.name("tf")
|
|
14
14
|
.description("TameFlare - Route AI agent traffic through a secure cloud gateway")
|
|
15
|
-
.version("0.10.
|
|
15
|
+
.version("0.10.2");
|
|
16
16
|
program.addCommand((0, login_1.loginCommand)());
|
|
17
17
|
program.addCommand((0, login_1.logoutCommand)());
|
|
18
18
|
program.addCommand((0, init_1.initCommand)());
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TameFlare Proxy Bootstrap
|
|
3
|
+
*
|
|
4
|
+
* This script is injected via NODE_OPTIONS="--require <this-file>" by `tf run`.
|
|
5
|
+
* It overrides globalThis.fetch to route all HTTP/HTTPS requests through the
|
|
6
|
+
* TameFlare cloud proxy.
|
|
7
|
+
*
|
|
8
|
+
* Architecture: The cloud proxy runs behind Railway's TLS termination, so
|
|
9
|
+
* standard HTTP CONNECT tunneling doesn't work. Instead, we send requests
|
|
10
|
+
* directly to the proxy with the target URL in the X-TF-Target-URL header.
|
|
11
|
+
* The proxy reads this header, forwards the request to the real upstream,
|
|
12
|
+
* and returns the response.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
"use strict";
|
|
16
|
+
|
|
17
|
+
const proxyUrl = process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy;
|
|
18
|
+
|
|
19
|
+
if (proxyUrl) {
|
|
20
|
+
const originalFetch = globalThis.fetch;
|
|
21
|
+
|
|
22
|
+
// Parse proxy URL: https://token@proxy.tameflare.com
|
|
23
|
+
let proxyOrigin, proxyToken;
|
|
24
|
+
try {
|
|
25
|
+
const parsed = new URL(proxyUrl);
|
|
26
|
+
proxyToken = parsed.username || "";
|
|
27
|
+
parsed.username = "";
|
|
28
|
+
parsed.password = "";
|
|
29
|
+
proxyOrigin = parsed.origin;
|
|
30
|
+
} catch (_) {}
|
|
31
|
+
|
|
32
|
+
if (proxyOrigin && originalFetch) {
|
|
33
|
+
globalThis.fetch = function tfProxyFetch(input, init) {
|
|
34
|
+
try {
|
|
35
|
+
const req = new Request(input, init);
|
|
36
|
+
const targetUrl = req.url;
|
|
37
|
+
|
|
38
|
+
// Only proxy http/https requests
|
|
39
|
+
if (!targetUrl.startsWith("http://") && !targetUrl.startsWith("https://")) {
|
|
40
|
+
return originalFetch(input, init);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Extract the path from the target URL to build the proxy request URL
|
|
44
|
+
// e.g. https://api.openai.com/v1/models → proxy sends to proxy.tameflare.com/v1/models
|
|
45
|
+
const targetParsed = new URL(targetUrl);
|
|
46
|
+
const proxyRequestUrl = proxyOrigin + targetParsed.pathname + targetParsed.search;
|
|
47
|
+
|
|
48
|
+
// Build headers
|
|
49
|
+
const headers = new Headers(req.headers);
|
|
50
|
+
headers.set("X-TF-Target-URL", targetUrl);
|
|
51
|
+
headers.set("X-TF-Target-Host", targetParsed.host);
|
|
52
|
+
if (proxyToken) {
|
|
53
|
+
headers.set("X-TF-Gateway-Token", proxyToken);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return originalFetch(proxyRequestUrl, {
|
|
57
|
+
method: req.method,
|
|
58
|
+
headers: headers,
|
|
59
|
+
body: req.body,
|
|
60
|
+
redirect: "manual",
|
|
61
|
+
duplex: "half",
|
|
62
|
+
});
|
|
63
|
+
} catch (_) {
|
|
64
|
+
return originalFetch(input, init);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
package/dist/utils.js
CHANGED
|
@@ -30,7 +30,7 @@ function loadConfig() {
|
|
|
30
30
|
dashboard_url: get("url"),
|
|
31
31
|
gateway_id: get("gateway_id"),
|
|
32
32
|
gateway_token: get("gateway_token"),
|
|
33
|
-
proxy_host: get("host") || "
|
|
33
|
+
proxy_host: get("host") || "proxy.tameflare.com",
|
|
34
34
|
};
|
|
35
35
|
if (!cfg.gateway_id || !cfg.gateway_token)
|
|
36
36
|
return null;
|
package/package.json
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tameflare/cli",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.3",
|
|
4
4
|
"description": "TameFlare CLI - secure and govern AI agent traffic through a transparent proxy gateway",
|
|
5
5
|
"bin": {
|
|
6
6
|
"tf": "dist/index.js"
|
|
7
7
|
},
|
|
8
8
|
"scripts": {
|
|
9
|
-
"build": "tsc",
|
|
9
|
+
"build": "tsc && node -e \"require('fs').copyFileSync('src/proxy-bootstrap.js','dist/proxy-bootstrap.js')\"",
|
|
10
10
|
"dev": "tsc --watch",
|
|
11
|
-
"prepublishOnly": "tsc"
|
|
11
|
+
"prepublishOnly": "tsc && node -e \"require('fs').copyFileSync('src/proxy-bootstrap.js','dist/proxy-bootstrap.js')\""
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"commander": "^12.1.0"
|
|
14
|
+
"commander": "^12.1.0",
|
|
15
|
+
"undici": "^7.21.0"
|
|
15
16
|
},
|
|
16
17
|
"devDependencies": {
|
|
17
|
-
"
|
|
18
|
-
"
|
|
18
|
+
"@types/node": "^22.0.0",
|
|
19
|
+
"typescript": "^5.7.0"
|
|
19
20
|
},
|
|
20
21
|
"files": [
|
|
21
22
|
"dist",
|