@tameflare/cli 0.8.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 +83 -0
- package/dist/commands/actions.d.ts +2 -0
- package/dist/commands/actions.js +50 -0
- package/dist/commands/agents.d.ts +2 -0
- package/dist/commands/agents.js +46 -0
- package/dist/commands/approvals.d.ts +2 -0
- package/dist/commands/approvals.js +122 -0
- package/dist/commands/connector.d.ts +3 -0
- package/dist/commands/connector.js +228 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +173 -0
- package/dist/commands/kill-switch.d.ts +2 -0
- package/dist/commands/kill-switch.js +45 -0
- package/dist/commands/license.d.ts +2 -0
- package/dist/commands/license.js +143 -0
- package/dist/commands/logs.d.ts +2 -0
- package/dist/commands/logs.js +69 -0
- package/dist/commands/policies.d.ts +2 -0
- package/dist/commands/policies.js +113 -0
- package/dist/commands/run.d.ts +2 -0
- package/dist/commands/run.js +85 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.js +43 -0
- package/dist/commands/stop.d.ts +2 -0
- package/dist/commands/stop.js +40 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +27 -0
- package/dist/utils.d.ts +7 -0
- package/dist/utils.js +63 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# @tameflare/cli
|
|
2
|
+
|
|
3
|
+
The official CLI for [TameFlare](https://tameflare.com) — secure and govern AI agent traffic through a transparent proxy gateway.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i -g @tameflare/cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or run directly with npx:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx @tameflare/cli init
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# 1. Initialize TameFlare in your project
|
|
21
|
+
tf init
|
|
22
|
+
|
|
23
|
+
# 2. Add a connector (e.g. GitHub)
|
|
24
|
+
tf connector add github --token ghp_xxx
|
|
25
|
+
|
|
26
|
+
# 3. Run your agent through the gateway
|
|
27
|
+
tf run --name my-agent -- python agent.py
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Commands
|
|
31
|
+
|
|
32
|
+
| Command | Description |
|
|
33
|
+
|---------|-------------|
|
|
34
|
+
| `tf init` | Initialize TameFlare — downloads gateway, creates config |
|
|
35
|
+
| `tf run --name <n> <cmd>` | Run a process through the proxy gateway |
|
|
36
|
+
| `tf status` | Show gateway status and active processes |
|
|
37
|
+
| `tf stop` | Stop the gateway |
|
|
38
|
+
| `tf logs` | View recent traffic logs |
|
|
39
|
+
| `tf kill-switch` | Emergency stop — block all traffic |
|
|
40
|
+
| `tf connector add <type>` | Add a connector (github, openai, slack, stripe, generic) |
|
|
41
|
+
| `tf connector list` | List active connectors |
|
|
42
|
+
| `tf connector remove <id>` | Remove a connector |
|
|
43
|
+
| `tf permissions set` | Set access rules for a gateway |
|
|
44
|
+
| `tf permissions list` | List access rules |
|
|
45
|
+
| `tf approvals list` | List pending approval requests |
|
|
46
|
+
| `tf approvals approve <id>` | Approve a pending request |
|
|
47
|
+
| `tf approvals deny <id>` | Deny a pending request |
|
|
48
|
+
|
|
49
|
+
## How It Works
|
|
50
|
+
|
|
51
|
+
TameFlare acts as a transparent HTTP/HTTPS proxy between your AI agent and external APIs. When you run `tf run`, the CLI:
|
|
52
|
+
|
|
53
|
+
1. Starts (or connects to) the TameFlare gateway
|
|
54
|
+
2. Registers your process and gets a dedicated proxy port
|
|
55
|
+
3. Sets `HTTP_PROXY` and `HTTPS_PROXY` env vars on your process
|
|
56
|
+
4. All outbound traffic flows through the gateway for inspection and enforcement
|
|
57
|
+
|
|
58
|
+
The gateway matches requests against connectors (GitHub, OpenAI, Stripe, etc.), applies permission rules, and either allows, denies, or holds requests for human approval.
|
|
59
|
+
|
|
60
|
+
## Requirements
|
|
61
|
+
|
|
62
|
+
- Node.js >= 18
|
|
63
|
+
- The TameFlare gateway binary (downloaded automatically by `tf init`)
|
|
64
|
+
|
|
65
|
+
## Dashboard
|
|
66
|
+
|
|
67
|
+
TameFlare includes a web dashboard for real-time traffic monitoring, policy management, and approval workflows. Start it with:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
docker compose up
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Then open [http://localhost:3000](http://localhost:3000).
|
|
74
|
+
|
|
75
|
+
## Links
|
|
76
|
+
|
|
77
|
+
- [Documentation](https://tameflare.com/docs)
|
|
78
|
+
- [GitHub](https://github.com/agentfirewall/agentfirewall)
|
|
79
|
+
- [Website](https://tameflare.com)
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
[ELv2](https://github.com/agentfirewall/agentfirewall/blob/main/LICENSE)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.actionsCommand = actionsCommand;
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const utils_1 = require("../utils");
|
|
6
|
+
function actionsCommand() {
|
|
7
|
+
const cmd = new commander_1.Command("actions").description("View action requests");
|
|
8
|
+
cmd
|
|
9
|
+
.command("list")
|
|
10
|
+
.description("List recent action requests")
|
|
11
|
+
.option("--limit <n>", "Number of results", "20")
|
|
12
|
+
.action(async (opts) => {
|
|
13
|
+
const data = await (0, utils_1.apiRequest)("GET", `/v1/audit/events?limit=${opts.limit}`);
|
|
14
|
+
const events = (data.events ?? []);
|
|
15
|
+
const actionEvents = events.filter((e) => e.type?.startsWith("action."));
|
|
16
|
+
if (actionEvents.length === 0) {
|
|
17
|
+
console.log("No action events found.");
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
console.log((0, utils_1.formatTable)(["Time", "Type", "Agent", "Action"], actionEvents.map((e) => [
|
|
21
|
+
e.timestamp ? new Date(e.timestamp).toLocaleString() : "—",
|
|
22
|
+
e.type,
|
|
23
|
+
e.agent_id ? e.agent_id.slice(0, 12) + "..." : "—",
|
|
24
|
+
e.action_type ?? "—",
|
|
25
|
+
])));
|
|
26
|
+
});
|
|
27
|
+
cmd
|
|
28
|
+
.command("get")
|
|
29
|
+
.description("Get action request details")
|
|
30
|
+
.requiredOption("--id <id>", "Action request ID")
|
|
31
|
+
.action(async (opts) => {
|
|
32
|
+
const data = await (0, utils_1.apiRequest)("GET", `/v1/actions/${opts.id}`);
|
|
33
|
+
console.log(`ID: ${data.action_request_id}`);
|
|
34
|
+
console.log(`Status: ${data.status}`);
|
|
35
|
+
const decision = data.decision;
|
|
36
|
+
if (decision) {
|
|
37
|
+
console.log(`Outcome: ${decision.outcome}`);
|
|
38
|
+
console.log(`Reason: ${decision.reason}`);
|
|
39
|
+
console.log(`Risk: ${decision.risk_score}`);
|
|
40
|
+
}
|
|
41
|
+
if (data.decision_token) {
|
|
42
|
+
console.log(`Token: ${String(data.decision_token).slice(0, 20)}...`);
|
|
43
|
+
}
|
|
44
|
+
const approval = data.approval;
|
|
45
|
+
if (approval) {
|
|
46
|
+
console.log(`Approval: ${approval.status}`);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
return cmd;
|
|
50
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.agentsCommand = agentsCommand;
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const utils_1 = require("../utils");
|
|
6
|
+
function agentsCommand() {
|
|
7
|
+
const cmd = new commander_1.Command("agents").description("Manage agents");
|
|
8
|
+
cmd
|
|
9
|
+
.command("list")
|
|
10
|
+
.description("List registered agents")
|
|
11
|
+
.action(async () => {
|
|
12
|
+
const data = await (0, utils_1.apiRequest)("GET", "/v1/agents");
|
|
13
|
+
const agents = (data.agents ?? []);
|
|
14
|
+
if (agents.length === 0) {
|
|
15
|
+
console.log("No agents registered.");
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
console.log((0, utils_1.formatTable)(["Name", "ID", "Environment", "Status", "Last Seen"], agents.map((a) => [
|
|
19
|
+
a.name,
|
|
20
|
+
a.id?.slice(0, 12) + "...",
|
|
21
|
+
a.environment,
|
|
22
|
+
a.status,
|
|
23
|
+
a.last_seen_at ?? "Never",
|
|
24
|
+
])));
|
|
25
|
+
});
|
|
26
|
+
cmd
|
|
27
|
+
.command("register")
|
|
28
|
+
.description("Register a new agent")
|
|
29
|
+
.requiredOption("--name <name>", "Agent name")
|
|
30
|
+
.option("--runtime <runtime>", "Runtime (e.g. node, python)")
|
|
31
|
+
.option("--environment <env>", "Environment", "development")
|
|
32
|
+
.action(async (opts) => {
|
|
33
|
+
const data = await (0, utils_1.apiRequest)("POST", "/v1/agents/register", {
|
|
34
|
+
name: opts.name,
|
|
35
|
+
runtime: opts.runtime,
|
|
36
|
+
environment: opts.environment,
|
|
37
|
+
});
|
|
38
|
+
console.log("Agent registered successfully!");
|
|
39
|
+
console.log(` ID: ${data.agent_id}`);
|
|
40
|
+
console.log(` Prefix: ${data.api_key_prefix}`);
|
|
41
|
+
console.log(` Key: ${data.api_key}`);
|
|
42
|
+
console.log("");
|
|
43
|
+
console.log("Store this API key securely. It will not be shown again.");
|
|
44
|
+
});
|
|
45
|
+
return cmd;
|
|
46
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.approvalsCommand = approvalsCommand;
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const GATEWAY_URL = "http://127.0.0.1:9443";
|
|
6
|
+
function approvalsCommand() {
|
|
7
|
+
const cmd = new commander_1.Command("approvals")
|
|
8
|
+
.description("Manage pending approval requests");
|
|
9
|
+
cmd
|
|
10
|
+
.command("list")
|
|
11
|
+
.description("List pending approval requests")
|
|
12
|
+
.option("--all", "Show all recent approvals (not just pending)")
|
|
13
|
+
.action(async (opts) => {
|
|
14
|
+
try {
|
|
15
|
+
const url = opts.all
|
|
16
|
+
? `${GATEWAY_URL}/internal/approvals/recent`
|
|
17
|
+
: `${GATEWAY_URL}/internal/approvals`;
|
|
18
|
+
const res = await fetch(url);
|
|
19
|
+
if (!res.ok) {
|
|
20
|
+
console.error(`Error: ${res.statusText}`);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
const data = await res.json();
|
|
24
|
+
const items = opts.all ? data : data.pending;
|
|
25
|
+
if (!items || items.length === 0) {
|
|
26
|
+
console.log(opts.all ? "No recent approvals." : "No pending approvals.");
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (!opts.all) {
|
|
30
|
+
console.log(`\n${data.count} pending approval(s):\n`);
|
|
31
|
+
}
|
|
32
|
+
for (const req of items) {
|
|
33
|
+
const status = req.status === "pending"
|
|
34
|
+
? "\x1b[33m⏳ pending\x1b[0m"
|
|
35
|
+
: req.status === "approved"
|
|
36
|
+
? "\x1b[32m✓ approved\x1b[0m"
|
|
37
|
+
: req.status === "denied"
|
|
38
|
+
? "\x1b[31m✗ denied\x1b[0m"
|
|
39
|
+
: `\x1b[90m${req.status}\x1b[0m`;
|
|
40
|
+
console.log(` ${status} ${req.id}`);
|
|
41
|
+
console.log(` Gateway: ${req.gateway_name}`);
|
|
42
|
+
console.log(` Action: ${req.action_type} (${req.connector_type})`);
|
|
43
|
+
console.log(` Request: ${req.method} ${req.url}`);
|
|
44
|
+
console.log(` Created: ${req.created_at}`);
|
|
45
|
+
if (req.status === "pending") {
|
|
46
|
+
console.log(` Expires: ${req.expires_at}`);
|
|
47
|
+
}
|
|
48
|
+
if (req.responded_by) {
|
|
49
|
+
console.log(` By: ${req.responded_by}`);
|
|
50
|
+
}
|
|
51
|
+
if (req.response_note) {
|
|
52
|
+
console.log(` Note: ${req.response_note}`);
|
|
53
|
+
}
|
|
54
|
+
console.log();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
console.error("Failed to connect to gateway. Is it running?");
|
|
59
|
+
console.error(` Run: tf init`);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
cmd
|
|
64
|
+
.command("approve <id>")
|
|
65
|
+
.description("Approve a pending request")
|
|
66
|
+
.option("--by <name>", "Who is approving", "cli-user")
|
|
67
|
+
.option("--note <text>", "Optional note")
|
|
68
|
+
.action(async (id, opts) => {
|
|
69
|
+
try {
|
|
70
|
+
const res = await fetch(`${GATEWAY_URL}/internal/approvals/respond`, {
|
|
71
|
+
method: "POST",
|
|
72
|
+
headers: { "Content-Type": "application/json" },
|
|
73
|
+
body: JSON.stringify({
|
|
74
|
+
id,
|
|
75
|
+
approved: true,
|
|
76
|
+
by: opts.by,
|
|
77
|
+
note: opts.note ?? "",
|
|
78
|
+
}),
|
|
79
|
+
});
|
|
80
|
+
if (!res.ok) {
|
|
81
|
+
const text = await res.text();
|
|
82
|
+
console.error(`Error: ${text}`);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
console.log(`\x1b[32m✓\x1b[0m Approved: ${id}`);
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
console.error("Failed to connect to gateway. Is it running?");
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
cmd
|
|
93
|
+
.command("deny <id>")
|
|
94
|
+
.description("Deny a pending request")
|
|
95
|
+
.option("--by <name>", "Who is denying", "cli-user")
|
|
96
|
+
.option("--note <text>", "Reason for denial")
|
|
97
|
+
.action(async (id, opts) => {
|
|
98
|
+
try {
|
|
99
|
+
const res = await fetch(`${GATEWAY_URL}/internal/approvals/respond`, {
|
|
100
|
+
method: "POST",
|
|
101
|
+
headers: { "Content-Type": "application/json" },
|
|
102
|
+
body: JSON.stringify({
|
|
103
|
+
id,
|
|
104
|
+
approved: false,
|
|
105
|
+
by: opts.by,
|
|
106
|
+
note: opts.note ?? "",
|
|
107
|
+
}),
|
|
108
|
+
});
|
|
109
|
+
if (!res.ok) {
|
|
110
|
+
const text = await res.text();
|
|
111
|
+
console.error(`Error: ${text}`);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
console.log(`\x1b[31m✗\x1b[0m Denied: ${id}`);
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
console.error("Failed to connect to gateway. Is it running?");
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
return cmd;
|
|
122
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
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.connectorCommand = connectorCommand;
|
|
37
|
+
exports.permissionsCommand = permissionsCommand;
|
|
38
|
+
const commander_1 = require("commander");
|
|
39
|
+
const readline = __importStar(require("readline"));
|
|
40
|
+
const utils_1 = require("../utils");
|
|
41
|
+
function connectorCommand() {
|
|
42
|
+
const cmd = new commander_1.Command("connector")
|
|
43
|
+
.description("Manage connectors (API integrations)");
|
|
44
|
+
// tf connector add <type>
|
|
45
|
+
cmd
|
|
46
|
+
.command("add <type>")
|
|
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)")
|
|
49
|
+
.option("--token-stdin", "Read token from stdin (avoids shell history exposure)")
|
|
50
|
+
.option("--token-env <var>", "Read token from environment variable")
|
|
51
|
+
.option("--name <name>", "Display name")
|
|
52
|
+
.option("--domains <domains>", "Comma-separated domains (for generic type)")
|
|
53
|
+
.option("--auth-type <type>", "Auth type: bearer, api_key, basic, none", "bearer")
|
|
54
|
+
.option("--auth-header <header>", "Custom auth header name (for api_key type)")
|
|
55
|
+
.action(async (type, opts) => {
|
|
56
|
+
(0, utils_1.requireGateway)();
|
|
57
|
+
let token = opts.token;
|
|
58
|
+
if (opts.tokenEnv) {
|
|
59
|
+
token = process.env[opts.tokenEnv];
|
|
60
|
+
if (!token) {
|
|
61
|
+
console.error(`[TF] Environment variable ${opts.tokenEnv} is not set.`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else if (opts.tokenStdin) {
|
|
66
|
+
token = await readStdin();
|
|
67
|
+
if (!token) {
|
|
68
|
+
console.error("[TF] No token received from stdin.");
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
else if (!token) {
|
|
73
|
+
token = await prompt(`Paste your ${type} token: `);
|
|
74
|
+
}
|
|
75
|
+
const body = {
|
|
76
|
+
type,
|
|
77
|
+
display_name: opts.name || type,
|
|
78
|
+
token,
|
|
79
|
+
};
|
|
80
|
+
if (type === "generic") {
|
|
81
|
+
if (!opts.domains) {
|
|
82
|
+
const domainsStr = await prompt("Domains (comma-separated): ");
|
|
83
|
+
body.domains = domainsStr.split(",").map((d) => d.trim());
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
body.domains = opts.domains.split(",").map((d) => d.trim());
|
|
87
|
+
}
|
|
88
|
+
body.auth_type = opts.authType;
|
|
89
|
+
if (opts.authHeader) {
|
|
90
|
+
body.auth_header = opts.authHeader;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
const result = await (0, utils_1.gatewayRequest)("POST", "/internal/connectors/add", body);
|
|
95
|
+
console.log(`[TF] Connector added: ${result.name} (${result.type})`);
|
|
96
|
+
console.log(` ID: ${result.id}`);
|
|
97
|
+
console.log(` Domains: ${result.domains.join(", ")}`);
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
console.error(`[TF] Failed: ${err.message}`);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
// tf connector list
|
|
105
|
+
cmd
|
|
106
|
+
.command("list")
|
|
107
|
+
.description("List all connectors")
|
|
108
|
+
.action(async () => {
|
|
109
|
+
(0, utils_1.requireGateway)();
|
|
110
|
+
try {
|
|
111
|
+
const connectors = await (0, utils_1.gatewayRequest)("GET", "/internal/connectors");
|
|
112
|
+
if (!connectors || connectors.length === 0) {
|
|
113
|
+
console.log("[TF] No connectors configured.");
|
|
114
|
+
console.log(" Add one with: tf connector add github --token <PAT>");
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
console.log("[TF] Connectors");
|
|
118
|
+
const rows = connectors.map((c) => [
|
|
119
|
+
c.id,
|
|
120
|
+
c.type,
|
|
121
|
+
c.display_name,
|
|
122
|
+
(c.domains || []).join(", "),
|
|
123
|
+
]);
|
|
124
|
+
console.log((0, utils_1.formatTable)(["ID", "Type", "Name", "Domains"], rows));
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
console.error(`[TF] Failed: ${err.message}`);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
// tf connector remove <id>
|
|
131
|
+
cmd
|
|
132
|
+
.command("remove <id>")
|
|
133
|
+
.description("Remove a connector")
|
|
134
|
+
.action(async (id) => {
|
|
135
|
+
(0, utils_1.requireGateway)();
|
|
136
|
+
try {
|
|
137
|
+
await (0, utils_1.gatewayRequest)("POST", "/internal/connectors/remove", { id });
|
|
138
|
+
console.log(`[TF] Connector ${id} removed.`);
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
console.error(`[TF] Failed: ${err.message}`);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
return cmd;
|
|
145
|
+
}
|
|
146
|
+
// tf permissions command (separate top-level for clarity)
|
|
147
|
+
function permissionsCommand() {
|
|
148
|
+
const cmd = new commander_1.Command("permissions")
|
|
149
|
+
.description("Manage gateway permissions on connectors");
|
|
150
|
+
// tf permissions set
|
|
151
|
+
cmd
|
|
152
|
+
.command("set")
|
|
153
|
+
.description("Set a permission rule")
|
|
154
|
+
.requiredOption("--gateway <name>", "Gateway name")
|
|
155
|
+
.requiredOption("--connector <type>", "Connector type (github, generic, or *)")
|
|
156
|
+
.requiredOption("--action <pattern>", "Action pattern (e.g. github.pr.*, *)")
|
|
157
|
+
.requiredOption("--decision <decision>", "Decision: allow, deny, require_approval")
|
|
158
|
+
.action(async (opts) => {
|
|
159
|
+
(0, utils_1.requireGateway)();
|
|
160
|
+
try {
|
|
161
|
+
await (0, utils_1.gatewayRequest)("POST", "/internal/permissions/set", {
|
|
162
|
+
gateway_name: opts.gateway,
|
|
163
|
+
connector_type: opts.connector,
|
|
164
|
+
action_pattern: opts.action,
|
|
165
|
+
decision: opts.decision,
|
|
166
|
+
});
|
|
167
|
+
console.log(`[TF] Permission set: ${opts.gateway} → ${opts.connector}.${opts.action} = ${opts.decision}`);
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
console.error(`[TF] Failed: ${err.message}`);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
// tf permissions list
|
|
174
|
+
cmd
|
|
175
|
+
.command("list")
|
|
176
|
+
.description("List permission rules")
|
|
177
|
+
.option("--gateway <name>", "Filter by gateway name")
|
|
178
|
+
.action(async (opts) => {
|
|
179
|
+
(0, utils_1.requireGateway)();
|
|
180
|
+
try {
|
|
181
|
+
const url = opts.gateway
|
|
182
|
+
? `/internal/permissions?gateway=${encodeURIComponent(opts.gateway)}`
|
|
183
|
+
: "/internal/permissions";
|
|
184
|
+
const rules = await (0, utils_1.gatewayRequest)("GET", url);
|
|
185
|
+
if (!rules || rules.length === 0) {
|
|
186
|
+
console.log("[TF] No permissions configured.");
|
|
187
|
+
console.log(" Default: all actions denied (deny-all).");
|
|
188
|
+
console.log(" Set with: tf permissions set --gateway <name> --connector <type> --action '*' --decision allow");
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
console.log("[TF] Permissions");
|
|
192
|
+
const rows = rules.map((r) => [
|
|
193
|
+
r.gateway_name,
|
|
194
|
+
r.connector_type,
|
|
195
|
+
r.action_pattern,
|
|
196
|
+
r.decision,
|
|
197
|
+
]);
|
|
198
|
+
console.log((0, utils_1.formatTable)(["Gateway", "Connector", "Action Pattern", "Decision"], rows));
|
|
199
|
+
}
|
|
200
|
+
catch (err) {
|
|
201
|
+
console.error(`[TF] Failed: ${err.message}`);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
return cmd;
|
|
205
|
+
}
|
|
206
|
+
function readStdin() {
|
|
207
|
+
return new Promise((resolve) => {
|
|
208
|
+
let data = "";
|
|
209
|
+
process.stdin.setEncoding("utf8");
|
|
210
|
+
process.stdin.on("data", (chunk) => { data += chunk; });
|
|
211
|
+
process.stdin.on("end", () => { resolve(data.trim()); });
|
|
212
|
+
if (process.stdin.isTTY) {
|
|
213
|
+
resolve("");
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
function prompt(question) {
|
|
218
|
+
const rl = readline.createInterface({
|
|
219
|
+
input: process.stdin,
|
|
220
|
+
output: process.stdout,
|
|
221
|
+
});
|
|
222
|
+
return new Promise((resolve) => {
|
|
223
|
+
rl.question(question, (answer) => {
|
|
224
|
+
rl.close();
|
|
225
|
+
resolve(answer.trim());
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
}
|