@tameflare/cli 0.8.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md 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 transparent proxy gateway.
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 downloads gateway, creates config |
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 block all traffic |
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/agentfirewall/agentfirewall)
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/agentfirewall/agentfirewall/blob/main/LICENSE)
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 prefer --token-stdin or --token-env)")
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")
@@ -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 config = [
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
- ].join("\n");
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 and start the gateway binary
69
- const gatewayBin = findGatewayBinary();
106
+ // Find or download the gateway binary
107
+ let gatewayBin = findGatewayBinary();
70
108
  if (!gatewayBin) {
71
- console.error("[TF] Gateway binary not found.");
72
- console.error("[TF] Build it with: cd apps/gateway-v2 && go build -o gateway ./cmd/gateway");
73
- process.exit(1);
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?token=${encodeURIComponent(token)}`, {
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 blocks all unknown domains, pre-allows LLM APIs",
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 pre-allows LLM APIs and search, blocks file:// and localhost",
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 pre-allows common integration endpoints",
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 pre-allows package registries and git, blocks all other outbound",
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
- (0, path_1.join)(process.cwd(), "apps", "gateway-v2", "gateway.exe"),
163
- (0, path_1.join)(process.cwd(), "apps", "gateway-v2", "gateway"),
164
- (0, path_1.join)(process.cwd(), "gateway.exe"),
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 block ALL traffic immediately")
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 resume normal enforcement")
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
+ }
@@ -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("--name <name>", "Process name")
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 ignore
84
+ // Gateway may already be down - ignore
84
85
  }
85
86
  }
@@ -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("[TF] Traffic");
20
- console.log(" Total: ", status.traffic.total);
21
- console.log(" Allowed: ", status.traffic.allowed);
22
- console.log(" Denied: ", status.traffic.denied);
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("[TF] Active Processes");
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 Govern and secure AI gateway traffic")
17
- .version("0.8.0");
17
+ .description("TameFlare - Govern and secure AI gateway traffic")
18
+ .version("0.9.1");
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.8.0",
4
- "description": "TameFlare CLI secure and govern AI agent traffic through a transparent proxy gateway",
3
+ "version": "0.9.1",
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/agentfirewall/agentfirewall",
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/agentfirewall/agentfirewall/issues"
34
+ "url": "https://github.com/tameflare/tameflare/issues"
35
35
  },
36
36
  "keywords": [
37
37
  "tameflare",