@tameflare/cli 0.9.0 → 0.10.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.
@@ -2,195 +2,92 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.initCommand = initCommand;
4
4
  const commander_1 = require("commander");
5
- const child_process_1 = require("child_process");
6
5
  const fs_1 = require("fs");
7
- const path_1 = require("path");
8
6
  const readline_1 = require("readline");
9
7
  const utils_1 = require("../utils");
10
- const https_1 = require("https");
11
- const http_1 = require("http");
12
8
  const login_1 = require("./login");
13
- const GATEWAY_VERSION = "0.8.0";
14
- const GITHUB_REPO = "tameflare/tameflare";
9
+ const PROXY_HOST = "proxy.tameflare.com";
15
10
  function initCommand() {
16
11
  const cmd = new commander_1.Command("init")
17
- .description("Initialize TameFlare gateway in the current directory")
18
- .option("--port <port>", "Gateway port", "9443")
19
- .option("--enforcement <level>", "Enforcement level: monitor, soft_enforce, full_enforce", "monitor")
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")
12
+ .description("Connect this directory to a TameFlare cloud gateway")
13
+ .option("--dashboard-url <url>", "Dashboard URL (default: https://tameflare.com)")
14
+ .option("--gateway-id <id>", "Gateway ID from dashboard (for CI/CD)")
15
+ .option("--gateway-token <token>", "Gateway auth token from dashboard (for CI/CD)")
24
16
  .option("--list", "List your gateways and pick one (requires tf login first)")
25
17
  .action(async (opts) => {
26
18
  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);
19
+ // If already initialized, show current config
20
+ if ((0, utils_1.isInitialized)() && !opts.list && !opts.gatewayId) {
21
+ const cfg = (0, utils_1.loadConfig)();
22
+ if (cfg) {
23
+ console.log("[TF] Already initialized at", tfDir);
24
+ console.log(`[TF] Gateway: ${cfg.gateway_id}`);
25
+ console.log(`[TF] Proxy: https://${PROXY_HOST}`);
26
+ console.log("");
27
+ console.log(" Run your agent: tf run -- node agent.js");
28
+ console.log(" Reconfigure: tf init --list");
29
+ return;
41
30
  }
42
- // If not --list and auto-detect failed, fall through to manual mode
43
31
  }
44
- if ((0, utils_1.isGatewayInitialized)()) {
45
- console.log("[TF] Already initialized at", tfDir);
46
- console.log("[TF] Starting gateway...");
47
- }
48
- else {
49
- console.log("[TF] Initializing TameFlare...");
50
- // Create .tf directory
51
- (0, fs_1.mkdirSync)(tfDir, { recursive: true });
52
- // Apply platform template enforcement level
53
- const enforcement = opts.platform
54
- ? "full_enforce"
55
- : opts.enforcement;
56
- // Write config (persist dashboard credentials so they survive restarts)
57
- const configLines = [
58
- "gateway:",
59
- ` port: ${opts.port}`,
60
- " dashboard_port: 3000",
61
- ` enforcement_level: ${enforcement}`,
62
- " proxy_mode: standard",
63
- "",
64
- "database:",
65
- ` url: "file:${(0, path_1.join)(tfDir, "local.db").replace(/\\/g, "/")}"`,
66
- "",
67
- "tls:",
68
- ` ca_cert: "${(0, path_1.join)(tfDir, "ca.crt").replace(/\\/g, "/")}"`,
69
- ` ca_key: "${(0, path_1.join)(tfDir, "ca.key").replace(/\\/g, "/")}"`,
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");
82
- (0, fs_1.writeFileSync)((0, utils_1.getConfigPath)(), config, "utf-8");
83
- console.log("[TF] Config created at", (0, utils_1.getConfigPath)());
84
- // Write platform template
85
- if (opts.platform) {
86
- const template = getPlatformTemplate(opts.platform);
87
- if (template) {
88
- const templatePath = (0, path_1.join)(tfDir, "platform.json");
89
- (0, fs_1.writeFileSync)(templatePath, JSON.stringify(template, null, 2), "utf-8");
90
- console.log(`[TF] Platform template: ${opts.platform}`);
91
- console.log(`[TF] Enforcement: full_enforce (platform default)`);
92
- console.log(`[TF] After gateway starts, run the setup commands below:`);
93
- console.log();
94
- for (const cmd of template.setup_commands) {
95
- console.log(` ${cmd}`);
96
- }
97
- console.log();
98
- }
99
- else {
100
- console.error(`[TF] Unknown platform: ${opts.platform}`);
101
- console.error(`[TF] Available: openclaw, langchain, n8n, claude-code`);
32
+ // Resolve gateway credentials: --list / auto-detect / manual flags
33
+ const creds = (0, login_1.loadCredentials)();
34
+ let dashUrl = opts.dashboardUrl || creds?.dashboard_url || "https://tameflare.com";
35
+ let gwId = opts.gatewayId;
36
+ let gwToken = opts.gatewayToken;
37
+ if (!gwId || !gwToken) {
38
+ if (!creds?.token) {
39
+ if (opts.list) {
40
+ console.error("[TF] Not logged in. Run 'tf login' first.");
102
41
  process.exit(1);
103
42
  }
104
- }
105
- }
106
- // Find or download the gateway binary
107
- let gatewayBin = findGatewayBinary();
108
- if (!gatewayBin) {
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");
43
+ console.error("[TF] No gateway configured.");
44
+ console.error(" Run 'tf login' then 'tf init' to select a gateway.");
45
+ console.error(" Or pass --gateway-id and --gateway-token for CI/CD.");
116
46
  process.exit(1);
117
47
  }
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
- }
48
+ const picked = await pickGatewayInteractive(dashUrl, creds.token, opts.list ?? false);
49
+ if (!picked) {
50
+ process.exit(1);
145
51
  }
146
- catch { }
147
- }
148
- // Fall back to tf login credentials for dashboard URL
149
- if (!dashUrl && creds?.dashboard_url) {
150
- dashUrl = creds.dashboard_url;
52
+ gwId = picked.id;
53
+ gwToken = picked.gateway_token;
54
+ console.log(`[TF] Selected gateway: ${picked.name} (${picked.id})`);
151
55
  }
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.");
163
- }
164
- console.log("[TF] Starting gateway...");
165
- const gateway = (0, child_process_1.spawn)(gatewayBin, ["--config", (0, utils_1.getConfigPath)()], {
166
- stdio: "inherit",
167
- detached: false,
168
- env: gatewayEnv,
169
- });
170
- gateway.on("error", (err) => {
171
- console.error("[TF] Failed to start gateway:", err.message);
56
+ if (!gwToken) {
57
+ console.error("[TF] Gateway has no token. Recreate it in the dashboard.");
172
58
  process.exit(1);
173
- });
174
- // Write PID file for stop command
175
- const pidFile = (0, path_1.join)(tfDir, "gateway.pid");
176
- if (gateway.pid) {
177
- (0, fs_1.writeFileSync)(pidFile, String(gateway.pid), "utf-8");
178
59
  }
179
- // Handle clean shutdown
180
- process.on("SIGINT", () => {
181
- gateway.kill("SIGTERM");
182
- process.exit(0);
183
- });
184
- process.on("SIGTERM", () => {
185
- gateway.kill("SIGTERM");
186
- process.exit(0);
187
- });
60
+ // Write config
61
+ (0, fs_1.mkdirSync)(tfDir, { recursive: true });
62
+ const config = [
63
+ "# TameFlare gateway configuration",
64
+ `# Generated by tf init`,
65
+ "",
66
+ "dashboard:",
67
+ ` url: "${dashUrl}"`,
68
+ ` gateway_id: "${gwId}"`,
69
+ ` gateway_token: "${gwToken}"`,
70
+ "",
71
+ "proxy:",
72
+ ` host: "${PROXY_HOST}"`,
73
+ ].join("\n");
74
+ (0, fs_1.writeFileSync)((0, utils_1.getConfigPath)(), config, "utf-8");
75
+ console.log("");
76
+ console.log("[TF] Initialized successfully!");
77
+ console.log(`[TF] Config: ${(0, utils_1.getConfigPath)()}`);
78
+ console.log(`[TF] Gateway: ${gwId}`);
79
+ console.log(`[TF] Proxy: https://${PROXY_HOST}`);
80
+ console.log("");
81
+ console.log(" Next steps:");
82
+ console.log(" tf run -- node agent.js # Run your agent through the gateway");
83
+ console.log(" tf status # Check gateway status");
84
+ console.log(" tf logs # View traffic logs");
188
85
  });
189
86
  return cmd;
190
87
  }
191
88
  async function pickGatewayInteractive(dashboardUrl, token, explicit) {
192
89
  try {
193
- const res = await fetch(`${dashboardUrl}/api/cli/gateways`, {
90
+ const res = await fetch(`${dashboardUrl}/api/cli/gateways?token=${encodeURIComponent(token)}`, {
194
91
  headers: { Authorization: `Bearer ${token}` },
195
92
  });
196
93
  if (!res.ok) {
@@ -205,7 +102,8 @@ async function pickGatewayInteractive(dashboardUrl, token, explicit) {
205
102
  const data = await res.json();
206
103
  const gws = data.gateways ?? [];
207
104
  if (gws.length === 0) {
208
- console.log("[TF] No gateways found. Create one at the dashboard first.");
105
+ console.log("[TF] No gateways found. Create one at the dashboard:");
106
+ console.log(` ${dashboardUrl}/dashboard/gateways/new`);
209
107
  return null;
210
108
  }
211
109
  // Auto-select if only one gateway
@@ -236,187 +134,8 @@ async function pickGatewayInteractive(dashboardUrl, token, explicit) {
236
134
  }
237
135
  catch (err) {
238
136
  if (!explicit)
239
- return null; // Silent fail for auto-detect
137
+ return null;
240
138
  console.error(`[TF] Failed to list gateways: ${err.message}`);
241
139
  return null;
242
140
  }
243
141
  }
244
- function getPlatformTemplate(platform) {
245
- const templates = {
246
- openclaw: {
247
- name: "OpenClaw",
248
- description: "AI agent framework - blocks all unknown domains, pre-allows LLM APIs",
249
- connectors: ["openai", "anthropic"],
250
- setup_commands: [
251
- 'npx tf connector add openai --token $OPENAI_API_KEY',
252
- 'npx tf connector add anthropic --token $ANTHROPIC_API_KEY',
253
- 'npx tf permissions set --gateway "default" --connector openai --action "openai.chat.*" --decision allow',
254
- 'npx tf permissions set --gateway "default" --connector anthropic --action "anthropic.message.*" --decision allow',
255
- '# All other outbound traffic is blocked by default',
256
- ],
257
- },
258
- langchain: {
259
- name: "LangChain",
260
- description: "LLM framework - pre-allows LLM APIs and search, blocks file:// and localhost",
261
- connectors: ["openai", "anthropic"],
262
- setup_commands: [
263
- 'npx tf connector add openai --token $OPENAI_API_KEY',
264
- 'npx tf connector add anthropic --token $ANTHROPIC_API_KEY',
265
- 'npx tf permissions set --gateway "default" --connector openai --action "*" --decision allow',
266
- 'npx tf permissions set --gateway "default" --connector anthropic --action "*" --decision allow',
267
- '# Add tool connectors as needed:',
268
- '# npx tf connector add generic --domains serpapi.com --token $SERP_KEY',
269
- ],
270
- },
271
- n8n: {
272
- name: "n8n",
273
- description: "Workflow automation - pre-allows common integration endpoints",
274
- connectors: ["openai", "slack", "github"],
275
- setup_commands: [
276
- 'npx tf connector add openai --token $OPENAI_API_KEY',
277
- 'npx tf connector add slack --token $SLACK_BOT_TOKEN',
278
- 'npx tf connector add github --token $GITHUB_TOKEN',
279
- 'npx tf permissions set --gateway "workflow" --connector openai --action "*" --decision allow',
280
- 'npx tf permissions set --gateway "workflow" --connector slack --action "slack.message.*" --decision allow',
281
- 'npx tf permissions set --gateway "workflow" --connector github --action "github.issue.*" --decision allow',
282
- 'npx tf permissions set --gateway "workflow" --connector github --action "github.pr.merge" --decision require_approval',
283
- ],
284
- },
285
- "claude-code": {
286
- name: "Claude Code",
287
- description: "AI coding assistant - pre-allows package registries and git, blocks all other outbound",
288
- connectors: ["github"],
289
- setup_commands: [
290
- 'npx tf connector add github --token $GITHUB_TOKEN',
291
- 'npx tf connector add generic --domains registry.npmjs.org,pypi.org --auth-type none',
292
- 'npx tf permissions set --gateway "claude" --connector github --action "github.api.read" --decision allow',
293
- 'npx tf permissions set --gateway "claude" --connector github --action "github.pr.create" --decision allow',
294
- 'npx tf permissions set --gateway "claude" --connector github --action "github.pr.merge" --decision require_approval',
295
- 'npx tf permissions set --gateway "claude" --connector github --action "github.file.commit" --decision allow',
296
- 'npx tf permissions set --gateway "claude" --connector generic --action "*" --decision allow',
297
- '# All other outbound traffic is blocked by default',
298
- ],
299
- },
300
- };
301
- return templates[platform] ?? null;
302
- }
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";
307
- const candidates = [
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"),
316
- ];
317
- for (const candidate of candidates) {
318
- if ((0, fs_1.existsSync)(candidate)) {
319
- return candidate;
320
- }
321
- }
322
- return null;
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
- }
@@ -1,69 +1,61 @@
1
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
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.logsCommand = logsCommand;
4
37
  const commander_1 = require("commander");
5
38
  const utils_1 = require("../utils");
6
39
  function logsCommand() {
7
40
  const cmd = new commander_1.Command("logs")
8
- .description("View recent traffic logs")
9
- .option("--gateway <name>", "Filter by gateway name")
10
- .option("-n, --limit <count>", "Number of entries", "25")
11
- .action(async (opts) => {
12
- (0, utils_1.requireGateway)();
41
+ .description("View traffic logs in the dashboard")
42
+ .action(async () => {
43
+ const cfg = (0, utils_1.requireConfig)();
44
+ const url = `${cfg.dashboard_url}/dashboard/actions`;
45
+ console.log(`[TF] Traffic logs are available in the dashboard:`);
46
+ console.log(` ${url}`);
47
+ console.log("");
48
+ console.log(` Gateway: ${cfg.gateway_id}`);
49
+ // Try to open the browser
13
50
  try {
14
- const entries = await (0, utils_1.gatewayRequest)("GET", "/internal/traffic");
15
- if (!entries || entries.length === 0) {
16
- console.log("[TF] No traffic logged yet.");
17
- console.log(" Run a process with: tf run --name <name> <command>");
18
- return;
19
- }
20
- let filtered = entries;
21
- if (opts.gateway) {
22
- filtered = entries.filter((e) => e.gateway_name === opts.gateway);
23
- }
24
- const limit = parseInt(opts.limit, 10);
25
- filtered = filtered.slice(0, limit);
26
- console.log("[TF] Recent Traffic");
27
- console.log("");
28
- for (const entry of filtered) {
29
- const time = new Date(entry.timestamp).toLocaleTimeString();
30
- const symbol = getSymbol(entry.decision);
31
- const gwName = entry.gateway_name.padEnd(15);
32
- const method = entry.method.padEnd(7);
33
- const url = truncate(entry.url, 55);
34
- const decision = entry.decision;
35
- console.log(` ${time} ${symbol} ${gwName} ${method} ${url} ${decision}`);
36
- if (entry.reason && entry.decision !== "allowed") {
37
- console.log(` ${" ".repeat(15)} ${"".padEnd(7)} -> ${entry.reason}`);
38
- }
39
- }
40
- console.log("");
41
- console.log(` Showing ${filtered.length} of ${entries.length} entries`);
51
+ const { exec } = await Promise.resolve().then(() => __importStar(require("child_process")));
52
+ const openCmd = process.platform === "win32" ? "start" :
53
+ process.platform === "darwin" ? "open" : "xdg-open";
54
+ exec(`${openCmd} ${url}`);
42
55
  }
43
- catch (err) {
44
- console.error("[TF] Gateway is not running.");
45
- console.error(" Start it with: tf init");
56
+ catch {
57
+ // Browser open is best-effort
46
58
  }
47
59
  });
48
60
  return cmd;
49
61
  }
50
- function getSymbol(decision) {
51
- switch (decision) {
52
- case "allowed":
53
- case "would_allow":
54
- return "\x1b[32m✓\x1b[0m";
55
- case "denied":
56
- return "\x1b[31m✗\x1b[0m";
57
- case "would_deny":
58
- return "\x1b[33m~\x1b[0m";
59
- case "error":
60
- return "\x1b[31m!\x1b[0m";
61
- default:
62
- return "?";
63
- }
64
- }
65
- function truncate(s, maxLen) {
66
- if (s.length <= maxLen)
67
- return s;
68
- return s.substring(0, maxLen - 3) + "...";
69
- }
@@ -6,48 +6,23 @@ 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 gateway proxy")
10
- .requiredOption("--gateway <name>", "Gateway name (configured at tameflare.com/dashboard/gateways)")
11
- .option("--name <name>", "Alias for --gateway (deprecated)")
12
- .argument("<command...>", "Command to run")
13
- .action(async (commandArgs, opts) => {
14
- (0, utils_1.requireGateway)();
15
- const processName = opts.gateway || opts.name;
9
+ .description("Run a process through the TameFlare cloud gateway")
10
+ .argument("<command...>", "Command to run (use -- before the command)")
11
+ .action(async (commandArgs) => {
12
+ const cfg = (0, utils_1.requireConfig)();
13
+ const proxyUrl = (0, utils_1.getProxyUrl)(cfg);
16
14
  const [command, ...args] = commandArgs;
17
- console.log(`[TF] Registering process "${processName}"...`);
18
- // Register process with gateway
19
- let registration;
20
- try {
21
- registration = await (0, utils_1.gatewayRequest)("POST", "/internal/processes/register", {
22
- name: processName,
23
- pid: process.pid,
24
- });
25
- }
26
- catch (err) {
27
- console.error(`[TF] Failed to register process: ${err.message}`);
28
- process.exit(1);
29
- }
30
- const proxyUrl = registration.proxy_url;
31
- const caCert = registration.ca_cert;
32
- console.log(`[TF] Process "${processName}" registered on port ${registration.port}`);
33
- console.log(`[TF] Proxy: ${proxyUrl}`);
34
- console.log(`[TF] Starting: ${command} ${args.join(" ")}`);
15
+ console.log(`[TF] Gateway: ${cfg.gateway_id}`);
16
+ console.log(`[TF] Proxy: https://${cfg.proxy_host}`);
17
+ console.log(`[TF] Running: ${command} ${args.join(" ")}`);
35
18
  console.log("");
36
- // Spawn child process with proxy environment variables
37
19
  const env = {
38
20
  ...process.env,
39
21
  HTTP_PROXY: proxyUrl,
40
22
  HTTPS_PROXY: proxyUrl,
41
23
  http_proxy: proxyUrl,
42
24
  https_proxy: proxyUrl,
43
- // Trust the TameFlare CA certificate for various runtimes
44
- NODE_EXTRA_CA_CERTS: caCert,
45
- SSL_CERT_FILE: caCert,
46
- REQUESTS_CA_BUNDLE: caCert,
47
- CURL_CA_BUNDLE: caCert,
48
- // Mark as TameFlare-wrapped (processes can detect this if needed)
49
- TF_PROCESS_NAME: processName,
50
- TF_PROXY_PORT: String(registration.port),
25
+ TF_GATEWAY_ID: cfg.gateway_id,
51
26
  };
52
27
  const child = (0, child_process_1.spawn)(command, args, {
53
28
  stdio: "inherit",
@@ -56,31 +31,18 @@ function runCommand() {
56
31
  });
57
32
  child.on("error", (err) => {
58
33
  console.error(`[TF] Failed to start command: ${err.message}`);
59
- deregister(processName);
60
34
  process.exit(1);
61
35
  });
62
36
  child.on("exit", (code, signal) => {
63
37
  console.log("");
64
- console.log(`[TF] Process "${processName}" exited (code ${code ?? signal})`);
65
- deregister(processName);
38
+ console.log(`[TF] Process exited (code ${code ?? signal})`);
66
39
  process.exit(code ?? 1);
67
40
  });
68
- // Clean shutdown on signals
69
41
  const cleanup = () => {
70
42
  child.kill("SIGTERM");
71
- deregister(processName);
72
43
  };
73
44
  process.on("SIGINT", cleanup);
74
45
  process.on("SIGTERM", cleanup);
75
46
  });
76
47
  return cmd;
77
48
  }
78
- async function deregister(name) {
79
- try {
80
- await (0, utils_1.gatewayRequest)("POST", "/internal/processes/deregister", { name });
81
- console.log(`[TF] Process "${name}" deregistered`);
82
- }
83
- catch {
84
- // Gateway may already be down - ignore
85
- }
86
- }
@@ -5,55 +5,33 @@ const commander_1 = require("commander");
5
5
  const utils_1 = require("../utils");
6
6
  function statusCommand() {
7
7
  const cmd = new commander_1.Command("status")
8
- .description("Show gateway and process status")
8
+ .description("Show gateway configuration and connection status")
9
9
  .action(async () => {
10
- (0, utils_1.requireGateway)();
10
+ const cfg = (0, utils_1.requireConfig)();
11
+ console.log("");
12
+ console.log(" TameFlare Gateway");
13
+ console.log(" ─────────────────────────────────");
14
+ console.log(` Gateway ID: ${cfg.gateway_id}`);
15
+ console.log(` Dashboard: ${cfg.dashboard_url}`);
16
+ console.log(` Proxy: https://${cfg.proxy_host}`);
17
+ console.log("");
18
+ // Check cloud proxy connectivity
11
19
  try {
12
- const status = await (0, utils_1.gatewayRequest)("GET", "/internal/status");
13
- console.log("");
14
- console.log(" TameFlare Gateway");
15
- console.log(" ─────────────────────────────────");
16
- if (status.gateway_name) {
17
- console.log(` Gateway: ${status.gateway_name}`);
20
+ const res = await fetch(`${cfg.dashboard_url}/api/health`);
21
+ if (res.ok) {
22
+ console.log(` Dashboard: \x1b[32mreachable\x1b[0m`);
18
23
  }
19
- if (status.version) {
20
- console.log(` Version: ${status.version}`);
24
+ else {
25
+ console.log(` Dashboard: \x1b[31munreachable (${res.status})\x1b[0m`);
21
26
  }
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`);
37
- // List processes
38
- const processes = await (0, utils_1.gatewayRequest)("GET", "/internal/processes");
39
- if (processes && processes.length > 0) {
40
- console.log("");
41
- console.log(" Active Processes");
42
- console.log(" ─────────────────────────────────");
43
- const rows = processes.map((p) => [
44
- p.name,
45
- String(p.port),
46
- String(p.request_count),
47
- p.last_activity ? new Date(p.last_activity).toLocaleTimeString() : "-",
48
- ]);
49
- console.log((0, utils_1.formatTable)(["Name", "Port", "Requests", "Last Activity"], rows));
50
- }
51
- console.log("");
52
27
  }
53
- catch (err) {
54
- console.error("[TF] Gateway is not running.");
55
- console.error(" Start it with: tf init");
28
+ catch {
29
+ console.log(` Dashboard: \x1b[31munreachable\x1b[0m`);
56
30
  }
31
+ console.log("");
32
+ console.log(" Run your agent:");
33
+ console.log(" tf run -- node agent.js");
34
+ console.log("");
57
35
  });
58
36
  return cmd;
59
37
  }
@@ -3,37 +3,22 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.stopCommand = stopCommand;
4
4
  const commander_1 = require("commander");
5
5
  const fs_1 = require("fs");
6
- const path_1 = require("path");
7
6
  const utils_1 = require("../utils");
8
7
  function stopCommand() {
9
- const cmd = new commander_1.Command("stop")
10
- .description("Stop the TameFlare gateway")
8
+ const cmd = new commander_1.Command("reset")
9
+ .description("Remove TameFlare configuration from this directory")
11
10
  .action(async () => {
12
- (0, utils_1.requireGateway)();
13
- const pidFile = (0, path_1.join)((0, utils_1.getTfDir)(), "gateway.pid");
14
- if (!(0, fs_1.existsSync)(pidFile)) {
15
- console.log("[TF] No running gateway found (no PID file).");
11
+ if (!(0, utils_1.isInitialized)()) {
12
+ console.log("[TF] Not initialized in this directory.");
16
13
  return;
17
14
  }
18
- const pid = parseInt((0, fs_1.readFileSync)(pidFile, "utf-8").trim(), 10);
19
15
  try {
20
- process.kill(pid, "SIGTERM");
21
- console.log(`[TF] Gateway (PID ${pid}) stopped.`);
16
+ (0, fs_1.rmSync)((0, utils_1.getTfDir)(), { recursive: true, force: true });
17
+ console.log("[TF] Configuration removed.");
18
+ console.log(" Run 'tf init' to set up again.");
22
19
  }
23
20
  catch (err) {
24
- if (err.code === "ESRCH") {
25
- console.log("[TF] Gateway process not found (already stopped).");
26
- }
27
- else {
28
- console.error(`[TF] Failed to stop gateway: ${err.message}`);
29
- }
30
- }
31
- // Clean up PID file
32
- try {
33
- (0, fs_1.unlinkSync)(pidFile);
34
- }
35
- catch {
36
- // Ignore
21
+ console.error(`[TF] Failed to remove config: ${err.message}`);
37
22
  }
38
23
  });
39
24
  return cmd;
package/dist/index.js CHANGED
@@ -2,20 +2,17 @@
2
2
  "use strict";
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  const commander_1 = require("commander");
5
+ const login_1 = require("./commands/login");
5
6
  const init_1 = require("./commands/init");
6
7
  const run_1 = require("./commands/run");
7
8
  const status_1 = require("./commands/status");
8
9
  const stop_1 = require("./commands/stop");
9
10
  const logs_1 = require("./commands/logs");
10
- const kill_switch_1 = require("./commands/kill-switch");
11
- const connector_1 = require("./commands/connector");
12
- const approvals_1 = require("./commands/approvals");
13
- const login_1 = require("./commands/login");
14
11
  const program = new commander_1.Command();
15
12
  program
16
13
  .name("tf")
17
- .description("TameFlare - Govern and secure AI gateway traffic")
18
- .version("0.9.0");
14
+ .description("TameFlare - Route AI agent traffic through a secure cloud gateway")
15
+ .version("0.10.0");
19
16
  program.addCommand((0, login_1.loginCommand)());
20
17
  program.addCommand((0, login_1.logoutCommand)());
21
18
  program.addCommand((0, init_1.initCommand)());
@@ -23,8 +20,4 @@ program.addCommand((0, run_1.runCommand)());
23
20
  program.addCommand((0, status_1.statusCommand)());
24
21
  program.addCommand((0, stop_1.stopCommand)());
25
22
  program.addCommand((0, logs_1.logsCommand)());
26
- program.addCommand((0, kill_switch_1.killSwitchCommand)());
27
- program.addCommand((0, connector_1.connectorCommand)());
28
- program.addCommand((0, connector_1.permissionsCommand)());
29
- program.addCommand((0, approvals_1.approvalsCommand)());
30
23
  program.parse();
package/dist/utils.d.ts CHANGED
@@ -1,7 +1,14 @@
1
+ export interface TfConfig {
2
+ dashboard_url: string;
3
+ gateway_id: string;
4
+ gateway_token: string;
5
+ proxy_host: string;
6
+ }
1
7
  export declare function getTfDir(): string;
2
8
  export declare function getConfigPath(): string;
3
- export declare function getGatewayUrl(): string;
4
- export declare function isGatewayInitialized(): boolean;
5
- export declare function gatewayRequest(method: string, path: string, body?: any): Promise<any>;
6
- export declare function requireGateway(): void;
9
+ export declare function isInitialized(): boolean;
10
+ export declare function loadConfig(): TfConfig | null;
11
+ export declare function requireConfig(): TfConfig;
12
+ export declare function getProxyUrl(cfg: TfConfig): string;
13
+ export declare function dashboardRequest(cfg: TfConfig, method: string, path: string, body?: any): Promise<any>;
7
14
  export declare function formatTable(headers: string[], rows: string[][]): string;
package/dist/utils.js CHANGED
@@ -2,57 +2,77 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getTfDir = getTfDir;
4
4
  exports.getConfigPath = getConfigPath;
5
- exports.getGatewayUrl = getGatewayUrl;
6
- exports.isGatewayInitialized = isGatewayInitialized;
7
- exports.gatewayRequest = gatewayRequest;
8
- exports.requireGateway = requireGateway;
5
+ exports.isInitialized = isInitialized;
6
+ exports.loadConfig = loadConfig;
7
+ exports.requireConfig = requireConfig;
8
+ exports.getProxyUrl = getProxyUrl;
9
+ exports.dashboardRequest = dashboardRequest;
9
10
  exports.formatTable = formatTable;
10
11
  const fs_1 = require("fs");
11
12
  const path_1 = require("path");
12
- const GATEWAY_PORT = 9443;
13
13
  function getTfDir() {
14
14
  return (0, path_1.join)(process.cwd(), ".tf");
15
15
  }
16
16
  function getConfigPath() {
17
17
  return (0, path_1.join)(getTfDir(), "config.yaml");
18
18
  }
19
- function getGatewayUrl() {
20
- return `http://127.0.0.1:${GATEWAY_PORT}`;
21
- }
22
- function isGatewayInitialized() {
19
+ function isInitialized() {
23
20
  return (0, fs_1.existsSync)(getConfigPath());
24
21
  }
25
- async function gatewayRequest(method, path, body) {
26
- const url = `${getGatewayUrl()}${path}`;
27
- const headers = {
28
- "Content-Type": "application/json",
29
- };
22
+ function loadConfig() {
30
23
  try {
31
- const response = await fetch(url, {
32
- method,
33
- headers,
34
- body: body ? JSON.stringify(body) : undefined,
35
- });
36
- if (!response.ok) {
37
- const error = await response.json().catch(() => ({
38
- error: response.statusText,
39
- }));
40
- throw new Error(`Gateway error (${response.status}): ${error.error || error.message || response.statusText}`);
41
- }
42
- return response.json();
24
+ const text = (0, fs_1.readFileSync)(getConfigPath(), "utf-8");
25
+ const get = (key) => {
26
+ const m = text.match(new RegExp(`${key}:\\s*"(.+?)"`));
27
+ return m?.[1] ?? "";
28
+ };
29
+ const cfg = {
30
+ dashboard_url: get("url"),
31
+ gateway_id: get("gateway_id"),
32
+ gateway_token: get("gateway_token"),
33
+ proxy_host: get("host") || "proxy.tameflare.com",
34
+ };
35
+ if (!cfg.gateway_id || !cfg.gateway_token)
36
+ return null;
37
+ return cfg;
43
38
  }
44
- catch (err) {
45
- if (err.cause?.code === "ECONNREFUSED") {
46
- throw new Error("Gateway is not running. Start it with 'tf init' first.");
47
- }
48
- throw err;
39
+ catch {
40
+ return null;
49
41
  }
50
42
  }
51
- function requireGateway() {
52
- if (!isGatewayInitialized()) {
53
- console.error("Error: TameFlare not initialized. Run 'tf init' first.");
43
+ function requireConfig() {
44
+ if (!isInitialized()) {
45
+ console.error("[TF] Not initialized. Run 'tf init' first.");
46
+ process.exit(1);
47
+ }
48
+ const cfg = loadConfig();
49
+ if (!cfg) {
50
+ console.error("[TF] Invalid config. Run 'tf init' to reconfigure.");
54
51
  process.exit(1);
55
52
  }
53
+ return cfg;
54
+ }
55
+ function getProxyUrl(cfg) {
56
+ return `https://${cfg.gateway_token}@${cfg.proxy_host}`;
57
+ }
58
+ async function dashboardRequest(cfg, method, path, body) {
59
+ const url = `${cfg.dashboard_url}${path}`;
60
+ const headers = {
61
+ "Content-Type": "application/json",
62
+ "X-Gateway-Token": cfg.gateway_token,
63
+ };
64
+ const response = await fetch(url, {
65
+ method,
66
+ headers,
67
+ body: body ? JSON.stringify(body) : undefined,
68
+ });
69
+ if (!response.ok) {
70
+ const error = await response.json().catch(() => ({
71
+ error: response.statusText,
72
+ }));
73
+ throw new Error(`Dashboard error (${response.status}): ${error.error || error.message || response.statusText}`);
74
+ }
75
+ return response.json();
56
76
  }
57
77
  function formatTable(headers, rows) {
58
78
  const widths = headers.map((h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length)));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tameflare/cli",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
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"