@tameflare/cli 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/commands/connector.js +1 -1
- package/dist/commands/init.js +265 -16
- package/dist/commands/kill-switch.js +2 -2
- package/dist/commands/login.d.ts +13 -0
- package/dist/commands/login.js +180 -0
- package/dist/commands/run.js +5 -4
- package/dist/commands/status.js +26 -10
- package/dist/index.js +5 -2
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @tameflare/cli
|
|
2
2
|
|
|
3
|
-
The official CLI for [TameFlare](https://tameflare.com)
|
|
3
|
+
The official CLI for [TameFlare](https://tameflare.com) - secure and govern AI agent traffic through a transparent proxy gateway.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -31,12 +31,12 @@ tf run --name my-agent -- python agent.py
|
|
|
31
31
|
|
|
32
32
|
| Command | Description |
|
|
33
33
|
|---------|-------------|
|
|
34
|
-
| `tf init` | Initialize TameFlare
|
|
34
|
+
| `tf init` | Initialize TameFlare - downloads gateway, creates config |
|
|
35
35
|
| `tf run --name <n> <cmd>` | Run a process through the proxy gateway |
|
|
36
36
|
| `tf status` | Show gateway status and active processes |
|
|
37
37
|
| `tf stop` | Stop the gateway |
|
|
38
38
|
| `tf logs` | View recent traffic logs |
|
|
39
|
-
| `tf kill-switch` | Emergency stop
|
|
39
|
+
| `tf kill-switch` | Emergency stop - block all traffic |
|
|
40
40
|
| `tf connector add <type>` | Add a connector (github, openai, slack, stripe, generic) |
|
|
41
41
|
| `tf connector list` | List active connectors |
|
|
42
42
|
| `tf connector remove <id>` | Remove a connector |
|
|
@@ -75,9 +75,9 @@ Then open [http://localhost:3000](http://localhost:3000).
|
|
|
75
75
|
## Links
|
|
76
76
|
|
|
77
77
|
- [Documentation](https://tameflare.com/docs)
|
|
78
|
-
- [GitHub](https://github.com/
|
|
78
|
+
- [GitHub](https://github.com/tameflare/tameflare)
|
|
79
79
|
- [Website](https://tameflare.com)
|
|
80
80
|
|
|
81
81
|
## License
|
|
82
82
|
|
|
83
|
-
[ELv2](https://github.com/
|
|
83
|
+
[ELv2](https://github.com/tameflare/tameflare/blob/main/LICENSE)
|
|
@@ -45,7 +45,7 @@ function connectorCommand() {
|
|
|
45
45
|
cmd
|
|
46
46
|
.command("add <type>")
|
|
47
47
|
.description("Add a connector (github, generic)")
|
|
48
|
-
.option("--token <token>", "API token or credential (visible in shell history
|
|
48
|
+
.option("--token <token>", "API token or credential (visible in shell history - prefer --token-stdin or --token-env)")
|
|
49
49
|
.option("--token-stdin", "Read token from stdin (avoids shell history exposure)")
|
|
50
50
|
.option("--token-env <var>", "Read token from environment variable")
|
|
51
51
|
.option("--name <name>", "Display name")
|
package/dist/commands/init.js
CHANGED
|
@@ -5,15 +5,42 @@ const commander_1 = require("commander");
|
|
|
5
5
|
const child_process_1 = require("child_process");
|
|
6
6
|
const fs_1 = require("fs");
|
|
7
7
|
const path_1 = require("path");
|
|
8
|
+
const readline_1 = require("readline");
|
|
8
9
|
const utils_1 = require("../utils");
|
|
10
|
+
const https_1 = require("https");
|
|
11
|
+
const http_1 = require("http");
|
|
12
|
+
const login_1 = require("./login");
|
|
13
|
+
const GATEWAY_VERSION = "0.8.0";
|
|
14
|
+
const GITHUB_REPO = "tameflare/tameflare";
|
|
9
15
|
function initCommand() {
|
|
10
16
|
const cmd = new commander_1.Command("init")
|
|
11
17
|
.description("Initialize TameFlare gateway in the current directory")
|
|
12
18
|
.option("--port <port>", "Gateway port", "9443")
|
|
13
19
|
.option("--enforcement <level>", "Enforcement level: monitor, soft_enforce, full_enforce", "monitor")
|
|
14
20
|
.option("--platform <name>", "Platform template: openclaw, langchain, n8n, claude-code")
|
|
21
|
+
.option("--dashboard-url <url>", "Dashboard URL for config phone-home (e.g. https://tameflare.com)")
|
|
22
|
+
.option("--gateway-id <id>", "Gateway ID from dashboard")
|
|
23
|
+
.option("--gateway-token <token>", "Gateway auth token from dashboard")
|
|
24
|
+
.option("--list", "List your gateways and pick one (requires tf login first)")
|
|
15
25
|
.action(async (opts) => {
|
|
16
26
|
const tfDir = (0, utils_1.getTfDir)();
|
|
27
|
+
// If --list or no gateway credentials provided, try to auto-detect from tf login
|
|
28
|
+
const creds = (0, login_1.loadCredentials)();
|
|
29
|
+
if ((opts.list || (!opts.gatewayId && !opts.gatewayToken)) && creds?.token) {
|
|
30
|
+
const dashUrl = opts.dashboardUrl || creds.dashboard_url;
|
|
31
|
+
const picked = await pickGatewayInteractive(dashUrl, creds.token, opts.list);
|
|
32
|
+
if (picked) {
|
|
33
|
+
opts.dashboardUrl = dashUrl;
|
|
34
|
+
opts.gatewayId = picked.id;
|
|
35
|
+
opts.gatewayToken = picked.gateway_token;
|
|
36
|
+
console.log(`[TF] Selected gateway: ${picked.name} (${picked.id})`);
|
|
37
|
+
}
|
|
38
|
+
else if (opts.list) {
|
|
39
|
+
// --list was explicit but no gateway picked
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
// If not --list and auto-detect failed, fall through to manual mode
|
|
43
|
+
}
|
|
17
44
|
if ((0, utils_1.isGatewayInitialized)()) {
|
|
18
45
|
console.log("[TF] Already initialized at", tfDir);
|
|
19
46
|
console.log("[TF] Starting gateway...");
|
|
@@ -26,8 +53,8 @@ function initCommand() {
|
|
|
26
53
|
const enforcement = opts.platform
|
|
27
54
|
? "full_enforce"
|
|
28
55
|
: opts.enforcement;
|
|
29
|
-
// Write config
|
|
30
|
-
const
|
|
56
|
+
// Write config (persist dashboard credentials so they survive restarts)
|
|
57
|
+
const configLines = [
|
|
31
58
|
"gateway:",
|
|
32
59
|
` port: ${opts.port}`,
|
|
33
60
|
" dashboard_port: 3000",
|
|
@@ -40,7 +67,18 @@ function initCommand() {
|
|
|
40
67
|
"tls:",
|
|
41
68
|
` ca_cert: "${(0, path_1.join)(tfDir, "ca.crt").replace(/\\/g, "/")}"`,
|
|
42
69
|
` ca_key: "${(0, path_1.join)(tfDir, "ca.key").replace(/\\/g, "/")}"`,
|
|
43
|
-
]
|
|
70
|
+
];
|
|
71
|
+
if (opts.dashboardUrl || opts.gatewayId || opts.gatewayToken) {
|
|
72
|
+
configLines.push("");
|
|
73
|
+
configLines.push("dashboard:");
|
|
74
|
+
if (opts.dashboardUrl)
|
|
75
|
+
configLines.push(` url: "${opts.dashboardUrl}"`);
|
|
76
|
+
if (opts.gatewayId)
|
|
77
|
+
configLines.push(` gateway_id: "${opts.gatewayId}"`);
|
|
78
|
+
if (opts.gatewayToken)
|
|
79
|
+
configLines.push(` gateway_token: "${opts.gatewayToken}"`);
|
|
80
|
+
}
|
|
81
|
+
const config = configLines.join("\n");
|
|
44
82
|
(0, fs_1.writeFileSync)((0, utils_1.getConfigPath)(), config, "utf-8");
|
|
45
83
|
console.log("[TF] Config created at", (0, utils_1.getConfigPath)());
|
|
46
84
|
// Write platform template
|
|
@@ -65,17 +103,69 @@ function initCommand() {
|
|
|
65
103
|
}
|
|
66
104
|
}
|
|
67
105
|
}
|
|
68
|
-
// Find
|
|
69
|
-
|
|
106
|
+
// Find or download the gateway binary
|
|
107
|
+
let gatewayBin = findGatewayBinary();
|
|
70
108
|
if (!gatewayBin) {
|
|
71
|
-
console.
|
|
72
|
-
|
|
73
|
-
|
|
109
|
+
console.log("[TF] Gateway binary not found. Downloading...");
|
|
110
|
+
try {
|
|
111
|
+
gatewayBin = await downloadGatewayBinary(tfDir);
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
console.error(`[TF] Failed to download gateway: ${err.message}`);
|
|
115
|
+
console.error("[TF] You can manually download from: https://github.com/tameflare/tameflare/releases");
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Build environment variables for the gateway binary.
|
|
120
|
+
// Priority: CLI flags > config file > tf login credentials
|
|
121
|
+
const gatewayEnv = { ...process.env };
|
|
122
|
+
let dashUrl = opts.dashboardUrl;
|
|
123
|
+
let gwId = opts.gatewayId;
|
|
124
|
+
let gwToken = opts.gatewayToken;
|
|
125
|
+
// Try reading from config file
|
|
126
|
+
if (!dashUrl || !gwId || !gwToken) {
|
|
127
|
+
try {
|
|
128
|
+
const cfgText = (0, fs_1.readFileSync)((0, utils_1.getConfigPath)(), "utf-8");
|
|
129
|
+
const dashSection = cfgText.split(/^dashboard:/m)[1] ?? "";
|
|
130
|
+
if (!dashUrl) {
|
|
131
|
+
const m = dashSection.match(/^\s+url:\s*"(.+?)"/m);
|
|
132
|
+
if (m)
|
|
133
|
+
dashUrl = m[1];
|
|
134
|
+
}
|
|
135
|
+
if (!gwId) {
|
|
136
|
+
const m = dashSection.match(/gateway_id:\s*"(.+?)"/);
|
|
137
|
+
if (m)
|
|
138
|
+
gwId = m[1];
|
|
139
|
+
}
|
|
140
|
+
if (!gwToken) {
|
|
141
|
+
const m = dashSection.match(/gateway_token:\s*"(.+?)"/);
|
|
142
|
+
if (m)
|
|
143
|
+
gwToken = m[1];
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch { }
|
|
147
|
+
}
|
|
148
|
+
// Fall back to tf login credentials for dashboard URL
|
|
149
|
+
if (!dashUrl && creds?.dashboard_url) {
|
|
150
|
+
dashUrl = creds.dashboard_url;
|
|
151
|
+
}
|
|
152
|
+
if (dashUrl)
|
|
153
|
+
gatewayEnv.TF_DASHBOARD_URL = dashUrl;
|
|
154
|
+
if (gwId)
|
|
155
|
+
gatewayEnv.TF_GATEWAY_ID = gwId;
|
|
156
|
+
if (gwToken)
|
|
157
|
+
gatewayEnv.TF_GATEWAY_TOKEN = gwToken;
|
|
158
|
+
if (dashUrl && gwId && gwToken) {
|
|
159
|
+
console.log(`[TF] Dashboard: ${dashUrl} (gateway: ${gwId})`);
|
|
160
|
+
}
|
|
161
|
+
else if (!gwId || !gwToken) {
|
|
162
|
+
console.log("[TF] No gateway credentials configured. Run 'tf login' then 'tf init --list' to select a gateway.");
|
|
74
163
|
}
|
|
75
164
|
console.log("[TF] Starting gateway...");
|
|
76
165
|
const gateway = (0, child_process_1.spawn)(gatewayBin, ["--config", (0, utils_1.getConfigPath)()], {
|
|
77
166
|
stdio: "inherit",
|
|
78
167
|
detached: false,
|
|
168
|
+
env: gatewayEnv,
|
|
79
169
|
});
|
|
80
170
|
gateway.on("error", (err) => {
|
|
81
171
|
console.error("[TF] Failed to start gateway:", err.message);
|
|
@@ -98,11 +188,64 @@ function initCommand() {
|
|
|
98
188
|
});
|
|
99
189
|
return cmd;
|
|
100
190
|
}
|
|
191
|
+
async function pickGatewayInteractive(dashboardUrl, token, explicit) {
|
|
192
|
+
try {
|
|
193
|
+
const res = await fetch(`${dashboardUrl}/api/cli/gateways`, {
|
|
194
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
195
|
+
});
|
|
196
|
+
if (!res.ok) {
|
|
197
|
+
if (res.status === 401) {
|
|
198
|
+
console.error("[TF] Token expired or invalid. Run 'tf login' again.");
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
console.error(`[TF] Failed to fetch gateways: ${res.status}`);
|
|
202
|
+
}
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
const data = await res.json();
|
|
206
|
+
const gws = data.gateways ?? [];
|
|
207
|
+
if (gws.length === 0) {
|
|
208
|
+
console.log("[TF] No gateways found. Create one at the dashboard first.");
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
// Auto-select if only one gateway
|
|
212
|
+
if (gws.length === 1) {
|
|
213
|
+
console.log(`[TF] Found 1 gateway: ${gws[0].name}`);
|
|
214
|
+
return gws[0];
|
|
215
|
+
}
|
|
216
|
+
// Interactive selection
|
|
217
|
+
console.log("");
|
|
218
|
+
console.log("[TF] Your gateways:");
|
|
219
|
+
gws.forEach((gw, i) => {
|
|
220
|
+
console.log(` ${i + 1}. ${gw.name} (${gw.id}) [${gw.enforcement_level}]`);
|
|
221
|
+
});
|
|
222
|
+
console.log("");
|
|
223
|
+
const rl = (0, readline_1.createInterface)({ input: process.stdin, output: process.stdout });
|
|
224
|
+
const answer = await new Promise((resolve) => {
|
|
225
|
+
rl.question("[TF] Select gateway (number): ", (ans) => {
|
|
226
|
+
rl.close();
|
|
227
|
+
resolve(ans.trim());
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
const idx = parseInt(answer, 10) - 1;
|
|
231
|
+
if (isNaN(idx) || idx < 0 || idx >= gws.length) {
|
|
232
|
+
console.error("[TF] Invalid selection.");
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
return gws[idx];
|
|
236
|
+
}
|
|
237
|
+
catch (err) {
|
|
238
|
+
if (!explicit)
|
|
239
|
+
return null; // Silent fail for auto-detect
|
|
240
|
+
console.error(`[TF] Failed to list gateways: ${err.message}`);
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
101
244
|
function getPlatformTemplate(platform) {
|
|
102
245
|
const templates = {
|
|
103
246
|
openclaw: {
|
|
104
247
|
name: "OpenClaw",
|
|
105
|
-
description: "AI agent framework
|
|
248
|
+
description: "AI agent framework - blocks all unknown domains, pre-allows LLM APIs",
|
|
106
249
|
connectors: ["openai", "anthropic"],
|
|
107
250
|
setup_commands: [
|
|
108
251
|
'npx tf connector add openai --token $OPENAI_API_KEY',
|
|
@@ -114,7 +257,7 @@ function getPlatformTemplate(platform) {
|
|
|
114
257
|
},
|
|
115
258
|
langchain: {
|
|
116
259
|
name: "LangChain",
|
|
117
|
-
description: "LLM framework
|
|
260
|
+
description: "LLM framework - pre-allows LLM APIs and search, blocks file:// and localhost",
|
|
118
261
|
connectors: ["openai", "anthropic"],
|
|
119
262
|
setup_commands: [
|
|
120
263
|
'npx tf connector add openai --token $OPENAI_API_KEY',
|
|
@@ -127,7 +270,7 @@ function getPlatformTemplate(platform) {
|
|
|
127
270
|
},
|
|
128
271
|
n8n: {
|
|
129
272
|
name: "n8n",
|
|
130
|
-
description: "Workflow automation
|
|
273
|
+
description: "Workflow automation - pre-allows common integration endpoints",
|
|
131
274
|
connectors: ["openai", "slack", "github"],
|
|
132
275
|
setup_commands: [
|
|
133
276
|
'npx tf connector add openai --token $OPENAI_API_KEY',
|
|
@@ -141,7 +284,7 @@ function getPlatformTemplate(platform) {
|
|
|
141
284
|
},
|
|
142
285
|
"claude-code": {
|
|
143
286
|
name: "Claude Code",
|
|
144
|
-
description: "AI coding assistant
|
|
287
|
+
description: "AI coding assistant - pre-allows package registries and git, blocks all other outbound",
|
|
145
288
|
connectors: ["github"],
|
|
146
289
|
setup_commands: [
|
|
147
290
|
'npx tf connector add github --token $GITHUB_TOKEN',
|
|
@@ -158,11 +301,18 @@ function getPlatformTemplate(platform) {
|
|
|
158
301
|
return templates[platform] ?? null;
|
|
159
302
|
}
|
|
160
303
|
function findGatewayBinary() {
|
|
304
|
+
const tfDir = (0, utils_1.getTfDir)();
|
|
305
|
+
const isWin = process.platform === "win32";
|
|
306
|
+
const binName = isWin ? "tameflare-gateway.exe" : "tameflare-gateway";
|
|
161
307
|
const candidates = [
|
|
162
|
-
|
|
163
|
-
(0, path_1.join)(
|
|
164
|
-
|
|
165
|
-
(0, path_1.join)(process.cwd(), "gateway"),
|
|
308
|
+
// Preferred: downloaded binary in .tf/bin/
|
|
309
|
+
(0, path_1.join)(tfDir, "bin", binName),
|
|
310
|
+
// Dev: local build in monorepo
|
|
311
|
+
(0, path_1.join)(process.cwd(), "apps", "gateway-v2", binName),
|
|
312
|
+
(0, path_1.join)(process.cwd(), "apps", "gateway-v2", isWin ? "gateway.exe" : "gateway"),
|
|
313
|
+
// Fallback: cwd
|
|
314
|
+
(0, path_1.join)(process.cwd(), binName),
|
|
315
|
+
(0, path_1.join)(process.cwd(), isWin ? "gateway.exe" : "gateway"),
|
|
166
316
|
];
|
|
167
317
|
for (const candidate of candidates) {
|
|
168
318
|
if ((0, fs_1.existsSync)(candidate)) {
|
|
@@ -171,3 +321,102 @@ function findGatewayBinary() {
|
|
|
171
321
|
}
|
|
172
322
|
return null;
|
|
173
323
|
}
|
|
324
|
+
function getPlatformArch() {
|
|
325
|
+
const platform = process.platform;
|
|
326
|
+
const arch = process.arch;
|
|
327
|
+
let os;
|
|
328
|
+
switch (platform) {
|
|
329
|
+
case "linux":
|
|
330
|
+
os = "linux";
|
|
331
|
+
break;
|
|
332
|
+
case "darwin":
|
|
333
|
+
os = "darwin";
|
|
334
|
+
break;
|
|
335
|
+
case "win32":
|
|
336
|
+
os = "windows";
|
|
337
|
+
break;
|
|
338
|
+
default: throw new Error(`Unsupported platform: ${platform}`);
|
|
339
|
+
}
|
|
340
|
+
let goArch;
|
|
341
|
+
switch (arch) {
|
|
342
|
+
case "x64":
|
|
343
|
+
goArch = "amd64";
|
|
344
|
+
break;
|
|
345
|
+
case "arm64":
|
|
346
|
+
goArch = "arm64";
|
|
347
|
+
break;
|
|
348
|
+
default: throw new Error(`Unsupported architecture: ${arch}`);
|
|
349
|
+
}
|
|
350
|
+
const ext = os === "windows" ? "zip" : "tar.gz";
|
|
351
|
+
return { os, arch: goArch, ext };
|
|
352
|
+
}
|
|
353
|
+
function followRedirects(url) {
|
|
354
|
+
return new Promise((resolve, reject) => {
|
|
355
|
+
const getter = url.startsWith("https") ? https_1.get : http_1.get;
|
|
356
|
+
getter(url, (res) => {
|
|
357
|
+
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
358
|
+
followRedirects(res.headers.location).then(resolve).catch(reject);
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
362
|
+
reject(new Error(`HTTP ${res.statusCode} downloading ${url}`));
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
const chunks = [];
|
|
366
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
367
|
+
res.on("end", () => resolve(Buffer.concat(chunks)));
|
|
368
|
+
res.on("error", reject);
|
|
369
|
+
}).on("error", reject);
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
async function downloadGatewayBinary(tfDir) {
|
|
373
|
+
const { os, arch, ext } = getPlatformArch();
|
|
374
|
+
const archiveName = `tameflare-gateway_${GATEWAY_VERSION}_${os}_${arch}.${ext}`;
|
|
375
|
+
const downloadUrl = `https://github.com/${GITHUB_REPO}/releases/download/gateway-v${GATEWAY_VERSION}/${archiveName}`;
|
|
376
|
+
console.log(`[TF] Downloading gateway v${GATEWAY_VERSION} for ${os}/${arch}...`);
|
|
377
|
+
console.log(`[TF] From: ${downloadUrl}`);
|
|
378
|
+
const data = await followRedirects(downloadUrl);
|
|
379
|
+
const binDir = (0, path_1.join)(tfDir, "bin");
|
|
380
|
+
(0, fs_1.mkdirSync)(binDir, { recursive: true });
|
|
381
|
+
const archivePath = (0, path_1.join)(binDir, archiveName);
|
|
382
|
+
(0, fs_1.writeFileSync)(archivePath, data);
|
|
383
|
+
const isWin = os === "windows";
|
|
384
|
+
const binName = isWin ? "tameflare-gateway.exe" : "tameflare-gateway";
|
|
385
|
+
const binPath = (0, path_1.join)(binDir, binName);
|
|
386
|
+
// Extract the binary from the archive
|
|
387
|
+
if (ext === "tar.gz") {
|
|
388
|
+
try {
|
|
389
|
+
(0, child_process_1.execSync)(`tar -xzf "${archivePath}" -C "${binDir}" ${binName}`, { stdio: "pipe" });
|
|
390
|
+
}
|
|
391
|
+
catch {
|
|
392
|
+
// Some tar versions need different syntax
|
|
393
|
+
(0, child_process_1.execSync)(`tar -xzf "${archivePath}" -C "${binDir}"`, { stdio: "pipe" });
|
|
394
|
+
}
|
|
395
|
+
(0, fs_1.chmodSync)(binPath, 0o755);
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
// Windows zip - use PowerShell
|
|
399
|
+
(0, child_process_1.execSync)(`powershell -Command "Expand-Archive -Force -Path '${archivePath}' -DestinationPath '${binDir}'"`, { stdio: "pipe" });
|
|
400
|
+
}
|
|
401
|
+
// Clean up archive
|
|
402
|
+
try {
|
|
403
|
+
(0, fs_1.unlinkSync)(archivePath);
|
|
404
|
+
}
|
|
405
|
+
catch { }
|
|
406
|
+
if (!(0, fs_1.existsSync)(binPath)) {
|
|
407
|
+
// Binary might be in a subdirectory - search for it
|
|
408
|
+
const files = (0, fs_1.readdirSync)(binDir, { recursive: true });
|
|
409
|
+
const found = files.find((f) => typeof f === "string" && f.endsWith(binName));
|
|
410
|
+
if (found) {
|
|
411
|
+
const foundPath = (0, path_1.join)(binDir, found);
|
|
412
|
+
if (foundPath !== binPath) {
|
|
413
|
+
(0, fs_1.renameSync)(foundPath, binPath);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
throw new Error(`Binary ${binName} not found in downloaded archive`);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
console.log(`[TF] Gateway binary installed at ${binPath}`);
|
|
421
|
+
return binPath;
|
|
422
|
+
}
|
|
@@ -8,7 +8,7 @@ function killSwitchCommand() {
|
|
|
8
8
|
.description("Emergency: block all gateway traffic");
|
|
9
9
|
cmd
|
|
10
10
|
.command("activate")
|
|
11
|
-
.description("Activate kill switch
|
|
11
|
+
.description("Activate kill switch - block ALL traffic immediately")
|
|
12
12
|
.option("--reason <reason>", "Reason for activation", "Manual activation")
|
|
13
13
|
.action(async (opts) => {
|
|
14
14
|
(0, utils_1.requireGateway)();
|
|
@@ -28,7 +28,7 @@ function killSwitchCommand() {
|
|
|
28
28
|
});
|
|
29
29
|
cmd
|
|
30
30
|
.command("deactivate")
|
|
31
|
-
.description("Deactivate kill switch
|
|
31
|
+
.description("Deactivate kill switch - resume normal enforcement")
|
|
32
32
|
.action(async () => {
|
|
33
33
|
(0, utils_1.requireGateway)();
|
|
34
34
|
try {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
export interface TameFlareCredentials {
|
|
3
|
+
token: string;
|
|
4
|
+
dashboard_url: string;
|
|
5
|
+
org_id?: string;
|
|
6
|
+
user_id?: string;
|
|
7
|
+
created_at: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function getCredentialsPath(): string;
|
|
10
|
+
export declare function loadCredentials(): TameFlareCredentials | null;
|
|
11
|
+
export declare function saveCredentials(creds: TameFlareCredentials): void;
|
|
12
|
+
export declare function loginCommand(): Command;
|
|
13
|
+
export declare function logoutCommand(): Command;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.getCredentialsPath = getCredentialsPath;
|
|
37
|
+
exports.loadCredentials = loadCredentials;
|
|
38
|
+
exports.saveCredentials = saveCredentials;
|
|
39
|
+
exports.loginCommand = loginCommand;
|
|
40
|
+
exports.logoutCommand = logoutCommand;
|
|
41
|
+
const commander_1 = require("commander");
|
|
42
|
+
const fs_1 = require("fs");
|
|
43
|
+
const path_1 = require("path");
|
|
44
|
+
const os_1 = require("os");
|
|
45
|
+
const CREDENTIALS_DIR = (0, path_1.join)((0, os_1.homedir)(), ".tameflare");
|
|
46
|
+
const CREDENTIALS_FILE = (0, path_1.join)(CREDENTIALS_DIR, "credentials.json");
|
|
47
|
+
function getCredentialsPath() {
|
|
48
|
+
return CREDENTIALS_FILE;
|
|
49
|
+
}
|
|
50
|
+
function loadCredentials() {
|
|
51
|
+
try {
|
|
52
|
+
if (!(0, fs_1.existsSync)(CREDENTIALS_FILE))
|
|
53
|
+
return null;
|
|
54
|
+
const raw = (0, fs_1.readFileSync)(CREDENTIALS_FILE, "utf-8");
|
|
55
|
+
return JSON.parse(raw);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function saveCredentials(creds) {
|
|
62
|
+
(0, fs_1.mkdirSync)(CREDENTIALS_DIR, { recursive: true });
|
|
63
|
+
(0, fs_1.writeFileSync)(CREDENTIALS_FILE, JSON.stringify(creds, null, 2), "utf-8");
|
|
64
|
+
}
|
|
65
|
+
function loginCommand() {
|
|
66
|
+
const cmd = new commander_1.Command("login")
|
|
67
|
+
.description("Authenticate with tameflare.com (opens browser)")
|
|
68
|
+
.option("--dashboard-url <url>", "Dashboard URL", "https://tameflare.com")
|
|
69
|
+
.option("--token <token>", "Use a personal API token directly (for CI/CD)")
|
|
70
|
+
.action(async (opts) => {
|
|
71
|
+
const dashboardUrl = opts.dashboardUrl.replace(/\/+$/, "");
|
|
72
|
+
// Option 1: Direct token (for CI/CD, no browser)
|
|
73
|
+
if (opts.token) {
|
|
74
|
+
saveCredentials({
|
|
75
|
+
token: opts.token,
|
|
76
|
+
dashboard_url: dashboardUrl,
|
|
77
|
+
created_at: new Date().toISOString(),
|
|
78
|
+
});
|
|
79
|
+
console.log(`[TF] Token saved to ${CREDENTIALS_FILE}`);
|
|
80
|
+
console.log("[TF] Logged in. Run 'tf init' to set up a gateway.");
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
// Option 2: Browser OAuth flow (device authorization grant)
|
|
84
|
+
console.log("[TF] Logging in to TameFlare...");
|
|
85
|
+
console.log("");
|
|
86
|
+
// 1. Start auth session
|
|
87
|
+
let sessionData;
|
|
88
|
+
try {
|
|
89
|
+
const res = await fetch(`${dashboardUrl}/api/cli/auth`, { method: "POST" });
|
|
90
|
+
if (!res.ok) {
|
|
91
|
+
const err = await res.json().catch(() => ({ error: res.statusText }));
|
|
92
|
+
console.error(`[TF] Failed to start auth session: ${err.error}`);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
sessionData = await res.json();
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
console.error(`[TF] Cannot reach ${dashboardUrl}: ${err.message}`);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
// 2. Open browser
|
|
102
|
+
const url = sessionData.verification_url;
|
|
103
|
+
console.log("[TF] Opening browser to authorize...");
|
|
104
|
+
console.log(`[TF] If the browser doesn't open, visit: ${url}`);
|
|
105
|
+
console.log("");
|
|
106
|
+
try {
|
|
107
|
+
const { exec } = await Promise.resolve().then(() => __importStar(require("child_process")));
|
|
108
|
+
const openCmd = process.platform === "win32" ? `start "" "${url}"`
|
|
109
|
+
: process.platform === "darwin" ? `open "${url}"`
|
|
110
|
+
: `xdg-open "${url}"`;
|
|
111
|
+
exec(openCmd);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// Browser open failed — user can manually visit the URL
|
|
115
|
+
}
|
|
116
|
+
// 3. Poll for approval
|
|
117
|
+
console.log("[TF] Waiting for authorization...");
|
|
118
|
+
const pollInterval = (sessionData.poll_interval || 2) * 1000;
|
|
119
|
+
const maxWait = (sessionData.expires_in || 600) * 1000;
|
|
120
|
+
const startTime = Date.now();
|
|
121
|
+
while (Date.now() - startTime < maxWait) {
|
|
122
|
+
await new Promise((r) => setTimeout(r, pollInterval));
|
|
123
|
+
try {
|
|
124
|
+
const res = await fetch(`${dashboardUrl}/api/cli/auth?code=${sessionData.code}`);
|
|
125
|
+
const data = await res.json();
|
|
126
|
+
if (data.status === "approved" && data.token) {
|
|
127
|
+
// Save credentials
|
|
128
|
+
saveCredentials({
|
|
129
|
+
token: data.token,
|
|
130
|
+
dashboard_url: dashboardUrl,
|
|
131
|
+
org_id: data.org_id,
|
|
132
|
+
user_id: data.user_id,
|
|
133
|
+
created_at: new Date().toISOString(),
|
|
134
|
+
});
|
|
135
|
+
console.log("");
|
|
136
|
+
console.log("[TF] Logged in successfully!");
|
|
137
|
+
console.log(`[TF] Credentials saved to ${CREDENTIALS_FILE}`);
|
|
138
|
+
console.log("");
|
|
139
|
+
console.log("[TF] Next steps:");
|
|
140
|
+
console.log(" tf init # Set up a gateway in this directory");
|
|
141
|
+
console.log(" tf init --list # List your gateways and pick one");
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (data.status === "expired") {
|
|
145
|
+
console.error("\n[TF] Session expired. Run 'tf login' again.");
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
// Still pending — show a dot to indicate progress
|
|
149
|
+
process.stdout.write(".");
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
// Network error — keep trying
|
|
153
|
+
process.stdout.write("x");
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
console.error("\n[TF] Timed out waiting for authorization. Run 'tf login' again.");
|
|
157
|
+
process.exit(1);
|
|
158
|
+
});
|
|
159
|
+
return cmd;
|
|
160
|
+
}
|
|
161
|
+
function logoutCommand() {
|
|
162
|
+
const cmd = new commander_1.Command("logout")
|
|
163
|
+
.description("Remove saved TameFlare credentials")
|
|
164
|
+
.action(() => {
|
|
165
|
+
try {
|
|
166
|
+
const { unlinkSync } = require("fs");
|
|
167
|
+
if ((0, fs_1.existsSync)(CREDENTIALS_FILE)) {
|
|
168
|
+
unlinkSync(CREDENTIALS_FILE);
|
|
169
|
+
console.log("[TF] Logged out. Credentials removed.");
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
console.log("[TF] Not logged in.");
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
console.error(`[TF] Failed to remove credentials: ${err.message}`);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
return cmd;
|
|
180
|
+
}
|
package/dist/commands/run.js
CHANGED
|
@@ -6,12 +6,13 @@ const child_process_1 = require("child_process");
|
|
|
6
6
|
const utils_1 = require("../utils");
|
|
7
7
|
function runCommand() {
|
|
8
8
|
const cmd = new commander_1.Command("run")
|
|
9
|
-
.description("Run a process through the TameFlare proxy")
|
|
10
|
-
.requiredOption("--
|
|
9
|
+
.description("Run a process through the TameFlare gateway proxy")
|
|
10
|
+
.requiredOption("--gateway <name>", "Gateway name (configured at tameflare.com/dashboard/gateways)")
|
|
11
|
+
.option("--name <name>", "Alias for --gateway (deprecated)")
|
|
11
12
|
.argument("<command...>", "Command to run")
|
|
12
13
|
.action(async (commandArgs, opts) => {
|
|
13
14
|
(0, utils_1.requireGateway)();
|
|
14
|
-
const processName = opts.name;
|
|
15
|
+
const processName = opts.gateway || opts.name;
|
|
15
16
|
const [command, ...args] = commandArgs;
|
|
16
17
|
console.log(`[TF] Registering process "${processName}"...`);
|
|
17
18
|
// Register process with gateway
|
|
@@ -80,6 +81,6 @@ async function deregister(name) {
|
|
|
80
81
|
console.log(`[TF] Process "${name}" deregistered`);
|
|
81
82
|
}
|
|
82
83
|
catch {
|
|
83
|
-
// Gateway may already be down
|
|
84
|
+
// Gateway may already be down - ignore
|
|
84
85
|
}
|
|
85
86
|
}
|
package/dist/commands/status.js
CHANGED
|
@@ -10,21 +10,36 @@ function statusCommand() {
|
|
|
10
10
|
(0, utils_1.requireGateway)();
|
|
11
11
|
try {
|
|
12
12
|
const status = await (0, utils_1.gatewayRequest)("GET", "/internal/status");
|
|
13
|
-
console.log("[TF] Gateway Status");
|
|
14
|
-
console.log(" Status: ", status.status);
|
|
15
|
-
console.log(" Enforcement: ", status.enforcement_level);
|
|
16
|
-
console.log(" Proxy mode: ", status.proxy_mode);
|
|
17
|
-
console.log(" Processes: ", status.processes_active);
|
|
18
13
|
console.log("");
|
|
19
|
-
console.log("
|
|
20
|
-
console.log("
|
|
21
|
-
|
|
22
|
-
|
|
14
|
+
console.log(" TameFlare Gateway");
|
|
15
|
+
console.log(" ─────────────────────────────────");
|
|
16
|
+
if (status.gateway_name) {
|
|
17
|
+
console.log(` Gateway: ${status.gateway_name}`);
|
|
18
|
+
}
|
|
19
|
+
if (status.version) {
|
|
20
|
+
console.log(` Version: ${status.version}`);
|
|
21
|
+
}
|
|
22
|
+
console.log(` Status: \x1b[32m${status.status}\x1b[0m`);
|
|
23
|
+
console.log(` Enforcement: ${status.enforcement_level}`);
|
|
24
|
+
if (status.uptime) {
|
|
25
|
+
console.log(` Uptime: ${status.uptime}`);
|
|
26
|
+
}
|
|
27
|
+
console.log(` Processes: ${status.processes_active}`);
|
|
28
|
+
if (status.connectors_active !== undefined) {
|
|
29
|
+
console.log(` Connectors: ${status.connectors_active}`);
|
|
30
|
+
}
|
|
31
|
+
console.log("");
|
|
32
|
+
console.log(" Traffic");
|
|
33
|
+
console.log(" ─────────────────────────────────");
|
|
34
|
+
console.log(` Total: ${status.traffic.total}`);
|
|
35
|
+
console.log(` Allowed: \x1b[32m${status.traffic.allowed}\x1b[0m`);
|
|
36
|
+
console.log(` Denied: \x1b[31m${status.traffic.denied}\x1b[0m`);
|
|
23
37
|
// List processes
|
|
24
38
|
const processes = await (0, utils_1.gatewayRequest)("GET", "/internal/processes");
|
|
25
39
|
if (processes && processes.length > 0) {
|
|
26
40
|
console.log("");
|
|
27
|
-
console.log("
|
|
41
|
+
console.log(" Active Processes");
|
|
42
|
+
console.log(" ─────────────────────────────────");
|
|
28
43
|
const rows = processes.map((p) => [
|
|
29
44
|
p.name,
|
|
30
45
|
String(p.port),
|
|
@@ -33,6 +48,7 @@ function statusCommand() {
|
|
|
33
48
|
]);
|
|
34
49
|
console.log((0, utils_1.formatTable)(["Name", "Port", "Requests", "Last Activity"], rows));
|
|
35
50
|
}
|
|
51
|
+
console.log("");
|
|
36
52
|
}
|
|
37
53
|
catch (err) {
|
|
38
54
|
console.error("[TF] Gateway is not running.");
|
package/dist/index.js
CHANGED
|
@@ -10,11 +10,14 @@ const logs_1 = require("./commands/logs");
|
|
|
10
10
|
const kill_switch_1 = require("./commands/kill-switch");
|
|
11
11
|
const connector_1 = require("./commands/connector");
|
|
12
12
|
const approvals_1 = require("./commands/approvals");
|
|
13
|
+
const login_1 = require("./commands/login");
|
|
13
14
|
const program = new commander_1.Command();
|
|
14
15
|
program
|
|
15
16
|
.name("tf")
|
|
16
|
-
.description("TameFlare
|
|
17
|
-
.version("0.
|
|
17
|
+
.description("TameFlare - Govern and secure AI gateway traffic")
|
|
18
|
+
.version("0.9.0");
|
|
19
|
+
program.addCommand((0, login_1.loginCommand)());
|
|
20
|
+
program.addCommand((0, login_1.logoutCommand)());
|
|
18
21
|
program.addCommand((0, init_1.initCommand)());
|
|
19
22
|
program.addCommand((0, run_1.runCommand)());
|
|
20
23
|
program.addCommand((0, status_1.statusCommand)());
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tameflare/cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "TameFlare CLI
|
|
3
|
+
"version": "0.9.0",
|
|
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
|
},
|
|
@@ -26,12 +26,12 @@
|
|
|
26
26
|
},
|
|
27
27
|
"repository": {
|
|
28
28
|
"type": "git",
|
|
29
|
-
"url": "https://github.com/
|
|
29
|
+
"url": "https://github.com/tameflare/tameflare",
|
|
30
30
|
"directory": "packages/cli"
|
|
31
31
|
},
|
|
32
32
|
"homepage": "https://tameflare.com",
|
|
33
33
|
"bugs": {
|
|
34
|
-
"url": "https://github.com/
|
|
34
|
+
"url": "https://github.com/tameflare/tameflare/issues"
|
|
35
35
|
},
|
|
36
36
|
"keywords": [
|
|
37
37
|
"tameflare",
|