@integrity-labs/agt-cli 0.28.150 → 0.28.152
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/dist/bin/agt.js +3 -3
- package/dist/{chunk-LCKA5XPX.js → chunk-2K6DIZXU.js} +54 -10
- package/dist/chunk-2K6DIZXU.js.map +1 -0
- package/dist/lib/manager-worker.js +9 -4
- package/dist/lib/manager-worker.js.map +1 -1
- package/dist/mcp/remote-oauth-proxy.js +168 -0
- package/dist/mcp/slack-channel.js +52 -3
- package/package.json +2 -2
- package/dist/chunk-LCKA5XPX.js.map +0 -1
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire as __augmentedCreateRequire } from 'module';
|
|
3
|
+
globalThis.require ??= __augmentedCreateRequire(import.meta.url);
|
|
4
|
+
|
|
5
|
+
// src/remote-oauth-proxy.ts
|
|
6
|
+
import { createInterface } from "readline";
|
|
7
|
+
import { readFileSync } from "fs";
|
|
8
|
+
import { fileURLToPath } from "url";
|
|
9
|
+
import { resolve as resolvePath } from "path";
|
|
10
|
+
var URL_ENV = "AGT_REMOTE_MCP_URL";
|
|
11
|
+
var TOKEN_FILE_ENV = "AGT_REMOTE_MCP_TOKEN_FILE";
|
|
12
|
+
var TOKEN_VAR_ENV = "AGT_REMOTE_MCP_TOKEN_VAR";
|
|
13
|
+
var REMOTE_FETCH_TIMEOUT_MS = 3e4;
|
|
14
|
+
var remoteUrl = process.env[URL_ENV] ?? "";
|
|
15
|
+
var tokenFile = process.env[TOKEN_FILE_ENV] ?? "";
|
|
16
|
+
var tokenVar = process.env[TOKEN_VAR_ENV] ?? "";
|
|
17
|
+
var label = process.env["AGT_REMOTE_MCP_LABEL"] || tokenVar || "remote-oauth";
|
|
18
|
+
function logErr(msg) {
|
|
19
|
+
process.stderr.write(`[remote-oauth-proxy:${label}] ${msg}
|
|
20
|
+
`);
|
|
21
|
+
}
|
|
22
|
+
function readCurrentToken(file, varName) {
|
|
23
|
+
let raw;
|
|
24
|
+
try {
|
|
25
|
+
raw = readFileSync(file, "utf8");
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
let value = null;
|
|
30
|
+
for (const line of raw.split("\n")) {
|
|
31
|
+
const trimmed = line.trim();
|
|
32
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
33
|
+
const eq = trimmed.indexOf("=");
|
|
34
|
+
if (eq < 0) continue;
|
|
35
|
+
const key = trimmed.slice(0, eq).replace(/^export\s+/, "").trim();
|
|
36
|
+
if (key !== varName) continue;
|
|
37
|
+
let v = trimmed.slice(eq + 1).trim();
|
|
38
|
+
if (v.length >= 2 && (v[0] === '"' && v[v.length - 1] === '"' || v[0] === "'" && v[v.length - 1] === "'")) {
|
|
39
|
+
v = v.slice(1, -1);
|
|
40
|
+
}
|
|
41
|
+
value = v;
|
|
42
|
+
}
|
|
43
|
+
return value && value.length > 0 ? value : null;
|
|
44
|
+
}
|
|
45
|
+
function jsonRpcError(id, code, message) {
|
|
46
|
+
return JSON.stringify({ jsonrpc: "2.0", id: id ?? null, error: { code, message } });
|
|
47
|
+
}
|
|
48
|
+
function extractJsonRpcMessages(contentType, body) {
|
|
49
|
+
const ct = (contentType || "").toLowerCase();
|
|
50
|
+
const out = [];
|
|
51
|
+
if (ct.includes("text/event-stream")) {
|
|
52
|
+
for (const line of body.split("\n")) {
|
|
53
|
+
const t = line.trimEnd();
|
|
54
|
+
if (!t.startsWith("data:")) continue;
|
|
55
|
+
const payload = t.slice(5).trim();
|
|
56
|
+
if (!payload || payload === "[DONE]") continue;
|
|
57
|
+
try {
|
|
58
|
+
JSON.parse(payload);
|
|
59
|
+
out.push(payload);
|
|
60
|
+
} catch {
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
const trimmed = body.trim();
|
|
66
|
+
if (!trimmed) return out;
|
|
67
|
+
try {
|
|
68
|
+
JSON.parse(trimmed);
|
|
69
|
+
out.push(trimmed);
|
|
70
|
+
} catch {
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
async function forward(line, id, isNotification) {
|
|
75
|
+
const token = readCurrentToken(tokenFile, tokenVar);
|
|
76
|
+
if (!token) {
|
|
77
|
+
logErr(`no token in ${tokenFile} (var ${tokenVar})`);
|
|
78
|
+
return isNotification ? [] : [jsonRpcError(id, -32e3, `${label}: no current access token available (reconnect the integration)`)];
|
|
79
|
+
}
|
|
80
|
+
let res;
|
|
81
|
+
try {
|
|
82
|
+
res = await fetch(remoteUrl, {
|
|
83
|
+
method: "POST",
|
|
84
|
+
headers: {
|
|
85
|
+
"Content-Type": "application/json",
|
|
86
|
+
Accept: "application/json, text/event-stream",
|
|
87
|
+
Authorization: `Bearer ${token}`
|
|
88
|
+
},
|
|
89
|
+
body: line,
|
|
90
|
+
signal: AbortSignal.timeout(REMOTE_FETCH_TIMEOUT_MS)
|
|
91
|
+
});
|
|
92
|
+
} catch (err) {
|
|
93
|
+
logErr(`fetch failed: ${err.message}`);
|
|
94
|
+
return isNotification ? [] : [jsonRpcError(id, -32001, `${label}: transport error contacting MCP server`)];
|
|
95
|
+
}
|
|
96
|
+
const ct = res.headers.get("content-type") ?? "";
|
|
97
|
+
let body = "";
|
|
98
|
+
try {
|
|
99
|
+
body = await res.text();
|
|
100
|
+
} catch {
|
|
101
|
+
body = "";
|
|
102
|
+
}
|
|
103
|
+
if (isNotification) return [];
|
|
104
|
+
const messages = extractJsonRpcMessages(ct, body);
|
|
105
|
+
if (messages.length > 0) return messages;
|
|
106
|
+
const detail = body.slice(0, 300).replace(/\s+/g, " ").trim();
|
|
107
|
+
return [jsonRpcError(id, -32002, `${label}: MCP server returned HTTP ${res.status}${detail ? ` (${detail})` : ""}`)];
|
|
108
|
+
}
|
|
109
|
+
function isMissingConfig() {
|
|
110
|
+
if (!remoteUrl) return URL_ENV;
|
|
111
|
+
if (!tokenFile) return TOKEN_FILE_ENV;
|
|
112
|
+
if (!tokenVar) return TOKEN_VAR_ENV;
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
async function main() {
|
|
116
|
+
const missing = isMissingConfig();
|
|
117
|
+
if (missing) {
|
|
118
|
+
logErr(`missing required env ${missing}; exiting`);
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
const rl = createInterface({ input: process.stdin, crlfDelay: Infinity });
|
|
122
|
+
let writeLock = Promise.resolve();
|
|
123
|
+
const writeReplies = (replies) => {
|
|
124
|
+
writeLock = writeLock.then(() => {
|
|
125
|
+
for (const reply of replies) process.stdout.write(reply + "\n");
|
|
126
|
+
});
|
|
127
|
+
return writeLock;
|
|
128
|
+
};
|
|
129
|
+
const inflight = /* @__PURE__ */ new Set();
|
|
130
|
+
rl.on("line", (line) => {
|
|
131
|
+
const text = line.trim();
|
|
132
|
+
if (!text) return;
|
|
133
|
+
let parsed;
|
|
134
|
+
try {
|
|
135
|
+
parsed = JSON.parse(text);
|
|
136
|
+
} catch {
|
|
137
|
+
logErr("dropping non-JSON stdin line");
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const isNotification = !("id" in parsed);
|
|
141
|
+
const id = parsed.id;
|
|
142
|
+
const task = forward(text, id, isNotification).then(writeReplies).catch((err) => {
|
|
143
|
+
logErr(`handler error: ${err.message}`);
|
|
144
|
+
});
|
|
145
|
+
inflight.add(task);
|
|
146
|
+
void task.finally(() => inflight.delete(task));
|
|
147
|
+
});
|
|
148
|
+
await new Promise((resolve) => rl.on("close", () => resolve()));
|
|
149
|
+
await Promise.all([...inflight]);
|
|
150
|
+
await writeLock;
|
|
151
|
+
}
|
|
152
|
+
var invokedDirectly = (() => {
|
|
153
|
+
try {
|
|
154
|
+
const entry = process.argv[1];
|
|
155
|
+
if (!entry) return false;
|
|
156
|
+
return fileURLToPath(import.meta.url) === resolvePath(entry);
|
|
157
|
+
} catch {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
})();
|
|
161
|
+
if (invokedDirectly) {
|
|
162
|
+
void main();
|
|
163
|
+
}
|
|
164
|
+
export {
|
|
165
|
+
extractJsonRpcMessages,
|
|
166
|
+
main,
|
|
167
|
+
readCurrentToken
|
|
168
|
+
};
|
|
@@ -16399,7 +16399,26 @@ function buildChannelInfoResult(args) {
|
|
|
16399
16399
|
return { ok: false, reason: "unknown", bot_user_handle: botUserHandle, raw_error: err };
|
|
16400
16400
|
}
|
|
16401
16401
|
const ch = infoRes.channel;
|
|
16402
|
-
if (!ch || !ch.id
|
|
16402
|
+
if (!ch || !ch.id) {
|
|
16403
|
+
return { ok: false, reason: "unknown", bot_user_handle: botUserHandle, raw_error: "malformed_response" };
|
|
16404
|
+
}
|
|
16405
|
+
const isDm = ch.is_im === true || typeof ch.user === "string" && ch.user.length > 0;
|
|
16406
|
+
if (isDm) {
|
|
16407
|
+
const dmChannel = {
|
|
16408
|
+
id: ch.id,
|
|
16409
|
+
name: ch.user ?? ch.id,
|
|
16410
|
+
is_private: true,
|
|
16411
|
+
is_archived: ch.is_archived === true,
|
|
16412
|
+
is_member: true,
|
|
16413
|
+
kind: "dm",
|
|
16414
|
+
user: ch.user
|
|
16415
|
+
};
|
|
16416
|
+
if (dmChannel.is_archived) {
|
|
16417
|
+
return { ok: false, channel: dmChannel, bot_user_handle: botUserHandle, reason: "archived" };
|
|
16418
|
+
}
|
|
16419
|
+
return { ok: true, channel: dmChannel, bot_user_handle: botUserHandle };
|
|
16420
|
+
}
|
|
16421
|
+
if (!ch.name) {
|
|
16403
16422
|
return { ok: false, reason: "unknown", bot_user_handle: botUserHandle, raw_error: "malformed_response" };
|
|
16404
16423
|
}
|
|
16405
16424
|
const channel = {
|
|
@@ -16417,6 +16436,17 @@ function buildChannelInfoResult(args) {
|
|
|
16417
16436
|
}
|
|
16418
16437
|
return { ok: true, channel, bot_user_handle: botUserHandle };
|
|
16419
16438
|
}
|
|
16439
|
+
function buildDmUserInfo(usersInfoRes) {
|
|
16440
|
+
if (!usersInfoRes || usersInfoRes.ok === false || !usersInfoRes.user) return {};
|
|
16441
|
+
const u = usersInfoRes.user;
|
|
16442
|
+
const nonEmpty = (s) => typeof s === "string" && s.trim().length > 0 ? s : void 0;
|
|
16443
|
+
const user_name = nonEmpty(u.profile?.display_name) ?? nonEmpty(u.profile?.real_name) ?? nonEmpty(u.real_name);
|
|
16444
|
+
const user_handle = nonEmpty(u.name);
|
|
16445
|
+
const out = {};
|
|
16446
|
+
if (user_handle) out.user_handle = user_handle;
|
|
16447
|
+
if (user_name) out.user_name = user_name;
|
|
16448
|
+
return out;
|
|
16449
|
+
}
|
|
16420
16450
|
|
|
16421
16451
|
// src/slack-peer-classifier.ts
|
|
16422
16452
|
var CODE_NAME_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
@@ -19208,13 +19238,13 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
19208
19238
|
},
|
|
19209
19239
|
{
|
|
19210
19240
|
name: "slack.channel_info",
|
|
19211
|
-
description: 'Verify the bot can post to a specific Slack channel. Use this before relying on a channel ID for delivery (e.g. before submitting an AWS access request that will notify an approval channel) to avoid silent posting failures. Works for
|
|
19241
|
+
description: 'Verify the bot can post to a specific Slack channel, OR resolve who is on the other side of a DM. Use this before relying on a channel ID for delivery (e.g. before submitting an AWS access request that will notify an approval channel) to avoid silent posting failures, and to confirm the human behind a DM channel id before claiming a message reached a named person. Works for public AND private channels (unlike slack.list_channels, which is public-only) and for DM (`D0...`) channels. Returns `{ ok, channel: { id, name, is_private, is_archived, is_member, kind }, bot_user_handle, reason? }`. For a DM, `kind` is "dm" and the result also carries `channel.user` (the partner\'s Slack user id) plus best-effort `channel.user_handle` and `channel.user_name` (the human\'s handle and display name); use these to VERIFY a DM\'s recipient rather than assuming whose DM it is. When `ok=false`, branch on `reason`: "channel_not_found" or "not_in_channel" \u2192 tell the user to run `/invite @<bot_user_handle>` in that channel (the configured ID exists but your bot isn\'t a member, or the ID is from a workspace the bot isn\'t installed in); "archived" \u2192 the channel exists but is archived, ask an admin to point the integration at a non-archived channel; "auth_failed" \u2192 bot token is invalid, escalate to an operator (do not surface to the end user). Pass the raw C0.../G0.../D0... channel ID, not a human name (use slack.list_channels for name \u2192 ID).',
|
|
19212
19242
|
inputSchema: {
|
|
19213
19243
|
type: "object",
|
|
19214
19244
|
properties: {
|
|
19215
19245
|
channel_id: {
|
|
19216
19246
|
type: "string",
|
|
19217
|
-
description: "Slack channel ID
|
|
19247
|
+
description: "Slack channel ID: C0... (public), G0... (legacy private), or D0... (DM). Required."
|
|
19218
19248
|
}
|
|
19219
19249
|
},
|
|
19220
19250
|
required: ["channel_id"]
|
|
@@ -19736,6 +19766,25 @@ async function handleChannelInfo(args) {
|
|
|
19736
19766
|
infoRes: infoData,
|
|
19737
19767
|
botUserHandle
|
|
19738
19768
|
});
|
|
19769
|
+
if (result.channel?.kind === "dm" && result.channel.user) {
|
|
19770
|
+
const controller = new AbortController();
|
|
19771
|
+
const timeout = setTimeout(() => controller.abort(), 3e3);
|
|
19772
|
+
try {
|
|
19773
|
+
const usersRes = await fetch(
|
|
19774
|
+
`https://slack.com/api/users.info?user=${encodeURIComponent(result.channel.user)}`,
|
|
19775
|
+
{ headers: { Authorization: `Bearer ${BOT_TOKEN}` }, signal: controller.signal }
|
|
19776
|
+
);
|
|
19777
|
+
const usersData = await usersRes.json();
|
|
19778
|
+
const { user_handle, user_name } = buildDmUserInfo(usersData);
|
|
19779
|
+
if (user_handle) result.channel.user_handle = user_handle;
|
|
19780
|
+
if (user_name) result.channel.user_name = user_name;
|
|
19781
|
+
if (user_name) result.channel.name = user_name;
|
|
19782
|
+
else if (user_handle) result.channel.name = user_handle;
|
|
19783
|
+
} catch {
|
|
19784
|
+
} finally {
|
|
19785
|
+
clearTimeout(timeout);
|
|
19786
|
+
}
|
|
19787
|
+
}
|
|
19739
19788
|
return {
|
|
19740
19789
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
19741
19790
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@integrity-labs/agt-cli",
|
|
3
|
-
"version": "0.28.
|
|
3
|
+
"version": "0.28.152",
|
|
4
4
|
"description": "Augmented Team CLI — agent provisioning and management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
},
|
|
24
24
|
"scripts": {
|
|
25
25
|
"build": "tsup && npm run build:mcp-assets && npm run build:cli-assets",
|
|
26
|
-
"build:mcp-assets": "mkdir -p dist/mcp && cp ../../packages/mcp/dist/index.js dist/mcp/index.js && cp ../../packages/mcp/dist/slack-channel.js dist/mcp/slack-channel.js && cp ../../packages/mcp/dist/direct-chat-channel.js dist/mcp/direct-chat-channel.js && cp ../../packages/mcp/dist/telegram-channel.js dist/mcp/telegram-channel.js && cp ../../packages/mcp/dist/teams-channel.js dist/mcp/teams-channel.js && cp ../../packages/mcp/dist/whatsapp-channel.js dist/mcp/whatsapp-channel.js && cp ../../packages/mcp/dist/whatsapp-link.js dist/mcp/whatsapp-link.js && cp ../../packages/augmented-admin-mcp/dist/index.js dist/mcp/augmented-admin.js",
|
|
26
|
+
"build:mcp-assets": "mkdir -p dist/mcp && cp ../../packages/mcp/dist/index.js dist/mcp/index.js && cp ../../packages/mcp/dist/slack-channel.js dist/mcp/slack-channel.js && cp ../../packages/mcp/dist/direct-chat-channel.js dist/mcp/direct-chat-channel.js && cp ../../packages/mcp/dist/telegram-channel.js dist/mcp/telegram-channel.js && cp ../../packages/mcp/dist/teams-channel.js dist/mcp/teams-channel.js && cp ../../packages/mcp/dist/whatsapp-channel.js dist/mcp/whatsapp-channel.js && cp ../../packages/mcp/dist/whatsapp-link.js dist/mcp/whatsapp-link.js && cp ../../packages/mcp/dist/remote-oauth-proxy.js dist/mcp/remote-oauth-proxy.js && cp ../../packages/augmented-admin-mcp/dist/index.js dist/mcp/augmented-admin.js",
|
|
27
27
|
"build:cli-assets": "mkdir -p dist/assets && cp assets/impersonate-statusline.sh dist/assets/impersonate-statusline.sh && chmod +x dist/assets/impersonate-statusline.sh",
|
|
28
28
|
"dev": "tsx watch src/bin/agt.ts",
|
|
29
29
|
"test": "vitest run",
|