spur-board 0.3.0 → 0.4.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/dist/cli.mjs +3 -3
- package/dist/{client-C81xon5D.mjs → client-sGJ4fzQ4.mjs} +1 -1
- package/dist/client.mjs +1 -1
- package/dist/{config-D8AEf8vU.mjs → config-DPXFxZQy.mjs} +1 -1
- package/dist/daemon.mjs +303 -24
- package/dist/mcp.mjs +3 -3
- package/dist/{src-BDNEze3S.mjs → src-Cf78bzK5.mjs} +12 -0
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { F as configSchema, I as SLACK_BOT_SCOPES, s as OPERATIONS } from "./src-
|
|
2
|
-
import { a as renderCliError, i as errorExitCode, n as requestTokenEnrollment, o as escapeTerminalControls, r as CliError, t as requestOperation } from "./client-
|
|
3
|
-
import { n as loadEnrollmentConfig, t as loadClientConfig } from "./config-
|
|
1
|
+
import { F as configSchema, I as SLACK_BOT_SCOPES, s as OPERATIONS } from "./src-Cf78bzK5.mjs";
|
|
2
|
+
import { a as renderCliError, i as errorExitCode, n as requestTokenEnrollment, o as escapeTerminalControls, r as CliError, t as requestOperation } from "./client-sGJ4fzQ4.mjs";
|
|
3
|
+
import { n as loadEnrollmentConfig, t as loadClientConfig } from "./config-DPXFxZQy.mjs";
|
|
4
4
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
5
5
|
import { chmodSync, closeSync, constants, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, readlinkSync, realpathSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { homedir, hostname, userInfo } from "node:os";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { M as errorContextSchema, N as errorEnvelopeSchema, P as successEnvelopeSchema, j as ERRORS, l as renderJsonError } from "./src-
|
|
1
|
+
import { M as errorContextSchema, N as errorEnvelopeSchema, P as successEnvelopeSchema, j as ERRORS, l as renderJsonError } from "./src-Cf78bzK5.mjs";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { request } from "node:http";
|
package/dist/client.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as requestTokenEnrollment, t as requestOperation } from "./client-
|
|
1
|
+
import { n as requestTokenEnrollment, t as requestOperation } from "./client-sGJ4fzQ4.mjs";
|
|
2
2
|
export { requestOperation, requestTokenEnrollment };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as CliError } from "./client-
|
|
1
|
+
import { r as CliError } from "./client-sGJ4fzQ4.mjs";
|
|
2
2
|
import { closeSync, constants, fstatSync, lstatSync, openSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { dirname, isAbsolute, join, relative, sep } from "node:path";
|
package/dist/daemon.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as taskKeySchema, C as sessionFactsSchema, D as errorCodeOf, E as encodeActorId, F as configSchema, I as SLACK_BOT_SCOPES, O as parseActorId, P as successEnvelopeSchema, S as prRefSchema, T as actorIdSchema, _ as EVENT_KINDS, a as buildCatalogSnapshot, b as SESSION_STATUSES, c as renderHttpProblem, d as SPACE_IDS, f as conversationRefSchema, g as userIdSchema, h as messageRefSchema, i as sourceCursorSchema, k as parseRef$1, m as interactionRefSchema, n as batchSchema, o as roleCanUseOperation, p as inboundEventSchema, r as entityRefSchema, s as OPERATIONS, t as AdapterError, v as eventPayloadSchemas, w as ticketFactsSchema, x as prFactsSchema, y as SESSION_HEALTH_STATES } from "./src-
|
|
1
|
+
import { A as taskKeySchema, C as sessionFactsSchema, D as errorCodeOf, E as encodeActorId, F as configSchema, I as SLACK_BOT_SCOPES, O as parseActorId, P as successEnvelopeSchema, S as prRefSchema, T as actorIdSchema, _ as EVENT_KINDS, a as buildCatalogSnapshot, b as SESSION_STATUSES, c as renderHttpProblem, d as SPACE_IDS, f as conversationRefSchema, g as userIdSchema, h as messageRefSchema, i as sourceCursorSchema, j as ERRORS, k as parseRef$1, m as interactionRefSchema, n as batchSchema, o as roleCanUseOperation, p as inboundEventSchema, r as entityRefSchema, s as OPERATIONS, t as AdapterError, v as eventPayloadSchemas, w as ticketFactsSchema, x as prFactsSchema, y as SESSION_HEALTH_STATES } from "./src-Cf78bzK5.mjs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
4
|
import { closeSync, existsSync, fstatSync, ftruncateSync, mkdirSync, openSync, readFileSync, readdirSync, writeSync } from "node:fs";
|
|
@@ -7543,9 +7543,8 @@ var ScanNeeded = class extends Error {
|
|
|
7543
7543
|
this.strings = strings;
|
|
7544
7544
|
}
|
|
7545
7545
|
};
|
|
7546
|
-
|
|
7547
|
-
|
|
7548
|
-
return (await lintSource({
|
|
7546
|
+
function lintGatewayText(text) {
|
|
7547
|
+
return lintSource({
|
|
7549
7548
|
source: {
|
|
7550
7549
|
content: text,
|
|
7551
7550
|
filePath: "board.txt",
|
|
@@ -7559,7 +7558,59 @@ async function scanSecret(text) {
|
|
|
7559
7558
|
rule: scannerPreset
|
|
7560
7559
|
}] }
|
|
7561
7560
|
}
|
|
7562
|
-
})
|
|
7561
|
+
});
|
|
7562
|
+
}
|
|
7563
|
+
/** Return a rule ID, never a finding message or a copy of the matched text. */
|
|
7564
|
+
async function scanSecret(text) {
|
|
7565
|
+
return (await lintGatewayText(text)).messages[0]?.ruleId ?? null;
|
|
7566
|
+
}
|
|
7567
|
+
/** Replace each detected span while preserving the rest of a private reply. */
|
|
7568
|
+
async function redactSecretMatches(text) {
|
|
7569
|
+
const matches = (await lintGatewayText(text)).messages.map(({ range, ruleId }) => ({
|
|
7570
|
+
start: range[0],
|
|
7571
|
+
end: range[1],
|
|
7572
|
+
ruleId
|
|
7573
|
+
})).sort((a, b) => a.start - b.start || b.end - a.end);
|
|
7574
|
+
const regions = [];
|
|
7575
|
+
for (const match of matches) {
|
|
7576
|
+
if (!Number.isInteger(match.start) || !Number.isInteger(match.end) || match.start < 0 || match.end <= match.start || match.end > text.length) throw new Error("Invalid gateway secret scan range");
|
|
7577
|
+
const previous = regions.at(-1);
|
|
7578
|
+
if (previous !== void 0 && match.start < previous.end) previous.end = Math.max(previous.end, match.end);
|
|
7579
|
+
else regions.push({ ...match });
|
|
7580
|
+
}
|
|
7581
|
+
let safe = "";
|
|
7582
|
+
let offset = 0;
|
|
7583
|
+
for (const { start, end, ruleId } of regions) {
|
|
7584
|
+
safe += `${text.slice(offset, start)}[redacted: ${ruleId}]`;
|
|
7585
|
+
offset = end;
|
|
7586
|
+
}
|
|
7587
|
+
return safe + text.slice(offset);
|
|
7588
|
+
}
|
|
7589
|
+
function visibleInline(value) {
|
|
7590
|
+
switch (value.kind) {
|
|
7591
|
+
case "text":
|
|
7592
|
+
case "emphasis":
|
|
7593
|
+
case "code": return value.text;
|
|
7594
|
+
case "link": return `${value.text} (${value.url})`;
|
|
7595
|
+
case "mention": return `<@${value.user}>`;
|
|
7596
|
+
}
|
|
7597
|
+
}
|
|
7598
|
+
/** Scan the exact text the Slack private-reply renderer will display. */
|
|
7599
|
+
async function redactPrivateReply(body) {
|
|
7600
|
+
return Promise.all(body.map(async (block) => {
|
|
7601
|
+
if (block.kind === "code_block") return {
|
|
7602
|
+
kind: "code_block",
|
|
7603
|
+
text: await redactSecretMatches(block.text)
|
|
7604
|
+
};
|
|
7605
|
+
const text = block.children.map(visibleInline).join("");
|
|
7606
|
+
return {
|
|
7607
|
+
kind: block.kind,
|
|
7608
|
+
children: [{
|
|
7609
|
+
kind: "text",
|
|
7610
|
+
text: await redactSecretMatches(text)
|
|
7611
|
+
}]
|
|
7612
|
+
};
|
|
7613
|
+
}));
|
|
7563
7614
|
}
|
|
7564
7615
|
/** Used by the common outbox insertion point before it issues SQL. */
|
|
7565
7616
|
function checkGatewayStrings(strings) {
|
|
@@ -11192,6 +11243,69 @@ async function conversationsForRecovery(trx, gateway, now) {
|
|
|
11192
11243
|
};
|
|
11193
11244
|
}
|
|
11194
11245
|
//#endregion
|
|
11246
|
+
//#region ../core/src/app/command-replies.ts
|
|
11247
|
+
function line(text) {
|
|
11248
|
+
return {
|
|
11249
|
+
kind: "paragraph",
|
|
11250
|
+
children: [{
|
|
11251
|
+
kind: "text",
|
|
11252
|
+
text
|
|
11253
|
+
}]
|
|
11254
|
+
};
|
|
11255
|
+
}
|
|
11256
|
+
function quote(text) {
|
|
11257
|
+
return {
|
|
11258
|
+
kind: "quote",
|
|
11259
|
+
children: [{
|
|
11260
|
+
kind: "text",
|
|
11261
|
+
text
|
|
11262
|
+
}]
|
|
11263
|
+
};
|
|
11264
|
+
}
|
|
11265
|
+
/** A compact Slack view of the same validated catalog output used by CLI and MCP. */
|
|
11266
|
+
function commandReply(id, value, input) {
|
|
11267
|
+
if (id === "status.get") {
|
|
11268
|
+
const operation = OPERATIONS.find((entry) => entry.id === "status.get");
|
|
11269
|
+
if (operation === void 0) throw new Error("status.get is missing from the catalog");
|
|
11270
|
+
const status = operation.output.parse(value);
|
|
11271
|
+
return {
|
|
11272
|
+
cliHint: `board ${operation.cli.command}`,
|
|
11273
|
+
body: [
|
|
11274
|
+
line(`Board status: ${String(status.tasks.length)} tasks, ${String(status.items.length)} items needing attention.`),
|
|
11275
|
+
...status.tasks.map((task) => line(`${task.key}: ${task.column} (${task.membership})`)),
|
|
11276
|
+
...status.items.map((item) => line(`${item.key}: ${item.summary}`)),
|
|
11277
|
+
line(`Unlinked: ${String(status.unlinkedPrs.length)} pull requests, ${String(status.unlinkedSessions.length)} sessions.`),
|
|
11278
|
+
...status.sources.map((source) => line(`${source.source}: ${source.blindReason !== null ? `blind (${source.blindReason})` : source.warmingUp ? "warming up" : source.stale ? "stale" : "current"}`)),
|
|
11279
|
+
...status.warmingUp ? [line("Sources are warming up.")] : [],
|
|
11280
|
+
...status.spurUnavailable === null ? [] : [line(status.spurUnavailable)],
|
|
11281
|
+
...status.hostLoad === null ? [] : [line(status.hostLoad)]
|
|
11282
|
+
]
|
|
11283
|
+
};
|
|
11284
|
+
}
|
|
11285
|
+
if (id === "task.get") {
|
|
11286
|
+
const operation = OPERATIONS.find((entry) => entry.id === "task.get");
|
|
11287
|
+
if (operation === void 0) throw new Error("task.get is missing from the catalog");
|
|
11288
|
+
const result = operation.output.parse(value);
|
|
11289
|
+
const request = operation.input.parse(input);
|
|
11290
|
+
return {
|
|
11291
|
+
cliHint: `board ${operation.cli.command} ${request.task}`,
|
|
11292
|
+
body: [
|
|
11293
|
+
line(`Task ${result.task.key}: ${result.task.column} (${result.task.membership})`),
|
|
11294
|
+
...result.blocks.flatMap((block) => [line(`Block ${block.id} (${block.state}, ${block.type}):`), quote(block.reason)]),
|
|
11295
|
+
...result.comments.flatMap((comment) => [line(`Comment ${comment.id} by ${comment.author}:`), quote(comment.text)])
|
|
11296
|
+
]
|
|
11297
|
+
};
|
|
11298
|
+
}
|
|
11299
|
+
const operation = OPERATIONS.find((entry) => entry.id === "task.history");
|
|
11300
|
+
if (operation === void 0) throw new Error("task.history is missing from the catalog");
|
|
11301
|
+
const history = operation.output.parse(value);
|
|
11302
|
+
const request = operation.input.parse(input);
|
|
11303
|
+
return {
|
|
11304
|
+
cliHint: `board ${operation.cli.command} ${request.task}`,
|
|
11305
|
+
body: [line(`Task ${history.task.key} history:`), ...history.events.map((event) => line(`${event.at}: ${event.summary} (${event.kind})`))]
|
|
11306
|
+
};
|
|
11307
|
+
}
|
|
11308
|
+
//#endregion
|
|
11195
11309
|
//#region ../core/src/app/inbound.ts
|
|
11196
11310
|
const resultSchema = z.strictObject({
|
|
11197
11311
|
status: z.enum([
|
|
@@ -11415,9 +11529,6 @@ function createInbound(input) {
|
|
|
11415
11529
|
});
|
|
11416
11530
|
return { status: "applied" };
|
|
11417
11531
|
}
|
|
11418
|
-
function queryId(parts) {
|
|
11419
|
-
return parts.length === 1 && parts[0] === "status" ? "status.get" : parts.length === 2 && parts[0] === "task" ? "task.get" : parts.length === 3 && parts[0] === "task" && parts[2] === "history" ? "task.history" : void 0;
|
|
11420
|
-
}
|
|
11421
11532
|
async function retryStoredInbound(trx, itemId, at) {
|
|
11422
11533
|
const item = await trx.selectFrom("operator_item").selectAll().where("id", "=", itemId).executeTakeFirst();
|
|
11423
11534
|
if (item === void 0 || item.cleared_at !== null || item.inbound_event_json === null) throw new CommandError("retry_not_applicable", "This item has no stored event to retry.");
|
|
@@ -11468,16 +11579,46 @@ function createInbound(input) {
|
|
|
11468
11579
|
const id = event.actionId.split(":", 1)[0];
|
|
11469
11580
|
return OPERATIONS.some((entry) => entry.id === id && entry.slack?.kind === "button") ? id ?? "inbound.action" : "inbound.action";
|
|
11470
11581
|
}
|
|
11471
|
-
if (event.kind === "command") return
|
|
11582
|
+
if (event.kind === "command") return interactionOperation(event).id ?? "inbound.command";
|
|
11472
11583
|
return `inbound.${event.kind}`;
|
|
11473
11584
|
}
|
|
11585
|
+
/** Decode command text only for events stored before parsed selections were added. */
|
|
11586
|
+
function legacyCommand(text) {
|
|
11587
|
+
const parts = text.trim().split(/\s+/).filter(Boolean);
|
|
11588
|
+
if (parts.length === 0 || parts.length === 1 && parts[0] === "status") return {
|
|
11589
|
+
kind: "query",
|
|
11590
|
+
id: "status.get",
|
|
11591
|
+
input: {}
|
|
11592
|
+
};
|
|
11593
|
+
if (parts[0] === "task" && parts[1] !== void 0) {
|
|
11594
|
+
if (parts.length === 2) return {
|
|
11595
|
+
kind: "query",
|
|
11596
|
+
id: "task.get",
|
|
11597
|
+
input: { task: parts[1] }
|
|
11598
|
+
};
|
|
11599
|
+
if (parts.length === 3 && parts[2] === "history") return {
|
|
11600
|
+
kind: "query",
|
|
11601
|
+
id: "task.history",
|
|
11602
|
+
input: { task: parts[1] }
|
|
11603
|
+
};
|
|
11604
|
+
}
|
|
11605
|
+
return null;
|
|
11606
|
+
}
|
|
11474
11607
|
function interactionOperation(event) {
|
|
11475
11608
|
let id;
|
|
11476
11609
|
let operationInput;
|
|
11477
11610
|
if (event.kind === "command") {
|
|
11478
|
-
const
|
|
11479
|
-
|
|
11480
|
-
|
|
11611
|
+
const command = event.command;
|
|
11612
|
+
if (command?.kind === "query") {
|
|
11613
|
+
const operation = OPERATIONS.find((entry) => entry.id === command.id);
|
|
11614
|
+
if (operation?.kind === "query" && operation.slack?.kind === "slash-command") {
|
|
11615
|
+
const parsed = operation.input.safeParse(command.input);
|
|
11616
|
+
if (parsed.success) {
|
|
11617
|
+
id = operation.id;
|
|
11618
|
+
operationInput = parsed.data;
|
|
11619
|
+
}
|
|
11620
|
+
}
|
|
11621
|
+
}
|
|
11481
11622
|
} else {
|
|
11482
11623
|
const parts = event.actionId.split(":");
|
|
11483
11624
|
if (parts.length === 2 && /^I[1-9]\d*$/.test(parts[1] ?? "")) {
|
|
@@ -11561,7 +11702,9 @@ function createInbound(input) {
|
|
|
11561
11702
|
}
|
|
11562
11703
|
const { id, operationInput } = interactionOperation(event);
|
|
11563
11704
|
let response;
|
|
11564
|
-
|
|
11705
|
+
let cliHint;
|
|
11706
|
+
if (event.kind === "command" && event.command?.kind === "usage") response = plain(event.command.text);
|
|
11707
|
+
else if (id === void 0) response = plain("not allowed");
|
|
11565
11708
|
else try {
|
|
11566
11709
|
const value = await dispatchOperation({
|
|
11567
11710
|
store,
|
|
@@ -11569,13 +11712,22 @@ function createInbound(input) {
|
|
|
11569
11712
|
config,
|
|
11570
11713
|
registry
|
|
11571
11714
|
}, id, operationInput, "operator", event.dedupeKey);
|
|
11572
|
-
|
|
11715
|
+
if (event.kind === "command") {
|
|
11716
|
+
const reply = commandReply(id, value, operationInput);
|
|
11717
|
+
response = reply.body;
|
|
11718
|
+
cliHint = reply.cliHint;
|
|
11719
|
+
} else response = plain(JSON.stringify(value));
|
|
11573
11720
|
} catch (error) {
|
|
11574
11721
|
if (!(error instanceof CommandError)) await recordFailure(event, error, new Date(clock.now()).toISOString());
|
|
11575
|
-
response = error instanceof CommandError && error.code !== "not_implemented" ? error.hint : "not available yet";
|
|
11722
|
+
response = plain(error instanceof CommandError && error.code !== "not_implemented" ? event.kind === "command" ? `${ERRORS[error.code].message} ${ERRORS[error.code].hint}` : error.hint : "not available yet");
|
|
11576
11723
|
}
|
|
11577
11724
|
try {
|
|
11578
|
-
|
|
11725
|
+
const safeResponse = event.kind === "command" ? await redactPrivateReply(response) : response;
|
|
11726
|
+
const safeHint = event.kind === "command" && cliHint !== void 0 ? await redactSecretMatches(cliHint) : cliHint;
|
|
11727
|
+
await gateway.respond(event.interaction, safeResponse, {
|
|
11728
|
+
private: true,
|
|
11729
|
+
...safeHint === void 0 ? {} : { cliHint: safeHint }
|
|
11730
|
+
});
|
|
11579
11731
|
} catch {}
|
|
11580
11732
|
}
|
|
11581
11733
|
async function applyUser(event) {
|
|
@@ -11658,7 +11810,15 @@ function createInbound(input) {
|
|
|
11658
11810
|
return serial(async () => {
|
|
11659
11811
|
const item = await store.db.selectFrom("operator_item").select(["cleared_at", "inbound_event_json"]).where("id", "=", itemId).executeTakeFirst();
|
|
11660
11812
|
if (item === void 0 || item.cleared_at !== null || item.inbound_event_json === null) throw new CommandError("retry_not_applicable", "This item has no stored event to retry.");
|
|
11661
|
-
|
|
11813
|
+
let event = inboundEventSchema.parse(JSON.parse(item.inbound_event_json));
|
|
11814
|
+
if (event.kind === "command" && event.command === void 0) {
|
|
11815
|
+
const command = legacyCommand(event.text);
|
|
11816
|
+
if (command === null) throw new CommandError("retry_not_applicable", "This interaction cannot be retried.");
|
|
11817
|
+
event = {
|
|
11818
|
+
...event,
|
|
11819
|
+
command
|
|
11820
|
+
};
|
|
11821
|
+
}
|
|
11662
11822
|
if (event.kind === "action" || event.kind === "command") {
|
|
11663
11823
|
const { id, operationInput } = interactionOperation(event);
|
|
11664
11824
|
if (id === void 0) throw new CommandError("retry_not_applicable", "This interaction cannot be retried.");
|
|
@@ -14433,6 +14593,71 @@ function renderText(body) {
|
|
|
14433
14593
|
return block.kind === "quote" ? value.split("\n").map((line) => `> ${line}`).join("\n") : value;
|
|
14434
14594
|
}).filter((value) => value.length > 0).join("\n\n");
|
|
14435
14595
|
}
|
|
14596
|
+
function literal(value) {
|
|
14597
|
+
switch (value.kind) {
|
|
14598
|
+
case "text":
|
|
14599
|
+
case "emphasis":
|
|
14600
|
+
case "code": return value.text;
|
|
14601
|
+
case "link": return `${value.text} (${value.url})`;
|
|
14602
|
+
case "mention": return `<@${value.user}>`;
|
|
14603
|
+
}
|
|
14604
|
+
}
|
|
14605
|
+
/** Private replies use structured text so quoted data is never parsed as mrkdwn. */
|
|
14606
|
+
function renderResponse(body, cliHint) {
|
|
14607
|
+
const pointer = cliHint === void 0 ? "" : `More: ${cliHint} (CLI)`;
|
|
14608
|
+
const suffix = pointer === "" ? "" : `\n\n${pointer}`;
|
|
14609
|
+
let remaining = MAX_PART_TEXT - suffix.length;
|
|
14610
|
+
const maxElements = 40;
|
|
14611
|
+
const shown = [];
|
|
14612
|
+
let truncated = false;
|
|
14613
|
+
for (const block of body) {
|
|
14614
|
+
const value = block.kind === "code_block" ? block.text : block.children.map(literal).join("");
|
|
14615
|
+
if (value.length === 0) continue;
|
|
14616
|
+
if (shown.length >= maxElements || remaining <= 0) {
|
|
14617
|
+
truncated = true;
|
|
14618
|
+
break;
|
|
14619
|
+
}
|
|
14620
|
+
const part = value.slice(0, remaining);
|
|
14621
|
+
shown.push({
|
|
14622
|
+
kind: block.kind === "quote" ? "quote" : "paragraph",
|
|
14623
|
+
children: [{
|
|
14624
|
+
kind: "text",
|
|
14625
|
+
text: part
|
|
14626
|
+
}]
|
|
14627
|
+
});
|
|
14628
|
+
remaining -= part.length + 2;
|
|
14629
|
+
if (part.length < value.length) {
|
|
14630
|
+
truncated = true;
|
|
14631
|
+
break;
|
|
14632
|
+
}
|
|
14633
|
+
}
|
|
14634
|
+
const elements = shown.map((block) => ({
|
|
14635
|
+
type: block.kind === "quote" ? "rich_text_quote" : "rich_text_section",
|
|
14636
|
+
elements: [{
|
|
14637
|
+
type: "text",
|
|
14638
|
+
text: block.children.map(literal).join("")
|
|
14639
|
+
}]
|
|
14640
|
+
}));
|
|
14641
|
+
if (truncated && pointer !== "") elements.push({
|
|
14642
|
+
type: "rich_text_section",
|
|
14643
|
+
elements: [{
|
|
14644
|
+
type: "text",
|
|
14645
|
+
text: pointer
|
|
14646
|
+
}]
|
|
14647
|
+
});
|
|
14648
|
+
const fallback = shown.map((block) => {
|
|
14649
|
+
const value = block.children.map(literal).join("");
|
|
14650
|
+
return block.kind === "quote" ? `Quote: ${value}` : value;
|
|
14651
|
+
}).join("\n\n");
|
|
14652
|
+
const limit = MAX_PART_TEXT - (truncated ? suffix.length : 0);
|
|
14653
|
+
return {
|
|
14654
|
+
text: `${fallback.length <= limit ? fallback : splitText(fallback, limit)?.[0] ?? ""}${truncated ? suffix : ""}`,
|
|
14655
|
+
blocks: [{
|
|
14656
|
+
type: "rich_text",
|
|
14657
|
+
elements
|
|
14658
|
+
}]
|
|
14659
|
+
};
|
|
14660
|
+
}
|
|
14436
14661
|
/** Prefer a paragraph or word boundary; never cut a Slack entity or angle-bracket token. */
|
|
14437
14662
|
function splitText(text, max = MAX_PART_TEXT) {
|
|
14438
14663
|
if (text.length === 0) return [];
|
|
@@ -14666,6 +14891,52 @@ function createDeliveryGateway(client, channels) {
|
|
|
14666
14891
|
};
|
|
14667
14892
|
}
|
|
14668
14893
|
//#endregion
|
|
14894
|
+
//#region ../gateway-slack/src/commands.ts
|
|
14895
|
+
/** The catalog's slash bindings are the parser and help text's single source. */
|
|
14896
|
+
function descriptors(commandName) {
|
|
14897
|
+
return OPERATIONS.filter((operation) => operation.kind === "query" && operation.slack?.kind === "slash-command").map((operation) => ({
|
|
14898
|
+
operation,
|
|
14899
|
+
usage: operation.slack?.kind === "slash-command" ? operation.slack.usage.replace(/^\/board\b/, commandName) : ""
|
|
14900
|
+
}));
|
|
14901
|
+
}
|
|
14902
|
+
function commandUsage(commandName) {
|
|
14903
|
+
return `Usage:\n${descriptors(commandName).map(({ usage }) => usage).join("\n")}`;
|
|
14904
|
+
}
|
|
14905
|
+
function parseCommand(text, commandName) {
|
|
14906
|
+
const usage = () => ({
|
|
14907
|
+
kind: "usage",
|
|
14908
|
+
text: commandUsage(commandName)
|
|
14909
|
+
});
|
|
14910
|
+
const parts = text.trim().split(/\s+/).filter(Boolean);
|
|
14911
|
+
if (parts.length === 0) return {
|
|
14912
|
+
kind: "query",
|
|
14913
|
+
id: "status.get",
|
|
14914
|
+
input: {}
|
|
14915
|
+
};
|
|
14916
|
+
for (const { operation, usage: form } of descriptors(commandName)) {
|
|
14917
|
+
const pattern = form.slice(commandName.length).trim().split(/\s+/);
|
|
14918
|
+
if (pattern.length !== parts.length) continue;
|
|
14919
|
+
const input = {};
|
|
14920
|
+
if (!pattern.every((piece, index) => {
|
|
14921
|
+
const value = parts[index];
|
|
14922
|
+
if (value === void 0) return false;
|
|
14923
|
+
if (piece === "<KEY>") {
|
|
14924
|
+
const field = operation.cli.args[0];
|
|
14925
|
+
if (field === void 0) return false;
|
|
14926
|
+
input[field] = value;
|
|
14927
|
+
return true;
|
|
14928
|
+
}
|
|
14929
|
+
return piece === value;
|
|
14930
|
+
}) || !operation.input.safeParse(input).success) continue;
|
|
14931
|
+
return {
|
|
14932
|
+
kind: "query",
|
|
14933
|
+
id: operation.id,
|
|
14934
|
+
input
|
|
14935
|
+
};
|
|
14936
|
+
}
|
|
14937
|
+
return usage();
|
|
14938
|
+
}
|
|
14939
|
+
//#endregion
|
|
14669
14940
|
//#region ../gateway-slack/src/normalize.ts
|
|
14670
14941
|
function record$1(value) {
|
|
14671
14942
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
@@ -14800,7 +15071,7 @@ function normalizeMessage(channel, payload, context) {
|
|
|
14800
15071
|
return events;
|
|
14801
15072
|
}
|
|
14802
15073
|
/** Actions and commands share the same stable trigger identity as their Slack retry. */
|
|
14803
|
-
function normalizeInteraction(type, payload, channels, root) {
|
|
15074
|
+
function normalizeInteraction(type, payload, channels, root, commandName = "/board") {
|
|
14804
15075
|
const body = record$1(payload);
|
|
14805
15076
|
if (body === null) return [];
|
|
14806
15077
|
const triggerId = string$1(body.trigger_id);
|
|
@@ -14809,6 +15080,7 @@ function normalizeInteraction(type, payload, channels, root) {
|
|
|
14809
15080
|
if (user === void 0) return [];
|
|
14810
15081
|
const author = userIdSchema.parse(user);
|
|
14811
15082
|
if (type === "slash_commands") {
|
|
15083
|
+
if (body.command !== commandName) return [];
|
|
14812
15084
|
const channel = string$1(body.channel_id);
|
|
14813
15085
|
if (channel === void 0) return [];
|
|
14814
15086
|
return [{
|
|
@@ -14817,6 +15089,7 @@ function normalizeInteraction(type, payload, channels, root) {
|
|
|
14817
15089
|
interaction: interactionRef(channel, user, triggerId),
|
|
14818
15090
|
author,
|
|
14819
15091
|
text: text(body.text) ?? "",
|
|
15092
|
+
command: parseCommand(text(body.text) ?? "", commandName),
|
|
14820
15093
|
sourceUrl: sourceUrl(channel),
|
|
14821
15094
|
sourceSpace: sourceSpace(channel, channels)
|
|
14822
15095
|
}];
|
|
@@ -14992,7 +15265,7 @@ function createSocket(api, config, botIdentity) {
|
|
|
14992
15265
|
const threadTs = value(actionMessage?.thread_ts) ?? ts;
|
|
14993
15266
|
const root = channel !== void 0 && threadTs !== void 0 && threadTs !== ts ? await rootFor(api, channel, threadTs) : actionMessage;
|
|
14994
15267
|
events = normalizeInteraction(type, body, config.channels, root);
|
|
14995
|
-
} else if (type === "slash_commands") events = normalizeInteraction(type, body, config.channels);
|
|
15268
|
+
} else if (type === "slash_commands") events = normalizeInteraction(type, body, config.channels, void 0, config.command);
|
|
14996
15269
|
for (const event of events) await emit(event);
|
|
14997
15270
|
await envelope.ack?.();
|
|
14998
15271
|
}
|
|
@@ -15080,14 +15353,19 @@ function createSlackGateway(config) {
|
|
|
15080
15353
|
recover: (request) => recoverThreads(api, request, botIdentity),
|
|
15081
15354
|
async respond(interaction, text, options) {
|
|
15082
15355
|
const addressed = parseInteractionRef(interaction);
|
|
15083
|
-
const
|
|
15084
|
-
if (addressed === null ||
|
|
15356
|
+
const rendered = renderResponse(text, options.cliHint);
|
|
15357
|
+
if (addressed === null || rendered.text.length === 0) return "dropped";
|
|
15085
15358
|
try {
|
|
15086
|
-
|
|
15359
|
+
const payload = {
|
|
15087
15360
|
channel: addressed.channel,
|
|
15088
15361
|
user: addressed.user,
|
|
15089
|
-
text:
|
|
15090
|
-
|
|
15362
|
+
text: rendered.text,
|
|
15363
|
+
blocks: rendered.blocks,
|
|
15364
|
+
mrkdwn: false,
|
|
15365
|
+
parse: "none",
|
|
15366
|
+
link_names: false
|
|
15367
|
+
};
|
|
15368
|
+
return (await api.chat.postEphemeral(payload)).ok ? "sent" : "dropped";
|
|
15091
15369
|
} catch (error) {
|
|
15092
15370
|
if (options.private) {
|
|
15093
15371
|
const result = apiFailure(error);
|
|
@@ -15499,6 +15777,7 @@ function slackSettings(config, secrets) {
|
|
|
15499
15777
|
}
|
|
15500
15778
|
return {
|
|
15501
15779
|
token,
|
|
15780
|
+
command: config.slack.command,
|
|
15502
15781
|
...appToken === void 0 ? {} : { appToken },
|
|
15503
15782
|
channels: {
|
|
15504
15783
|
orchestrators,
|
package/dist/mcp.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { j as ERRORS, s as OPERATIONS, u as renderMcpError } from "./src-
|
|
2
|
-
import { r as CliError, t as requestOperation } from "./client-
|
|
3
|
-
import { t as loadClientConfig } from "./config-
|
|
1
|
+
import { j as ERRORS, s as OPERATIONS, u as renderMcpError } from "./src-Cf78bzK5.mjs";
|
|
2
|
+
import { r as CliError, t as requestOperation } from "./client-sGJ4fzQ4.mjs";
|
|
3
|
+
import { t as loadClientConfig } from "./config-DPXFxZQy.mjs";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
5
|
import { readFileSync } from "node:fs";
|
|
6
6
|
import { z } from "zod";
|
|
@@ -1117,6 +1117,18 @@ const commandEventSchema = z.strictObject({
|
|
|
1117
1117
|
interaction: interactionRefSchema,
|
|
1118
1118
|
author: userIdSchema,
|
|
1119
1119
|
text: text$1,
|
|
1120
|
+
command: z.discriminatedUnion("kind", [z.strictObject({
|
|
1121
|
+
kind: z.literal("query"),
|
|
1122
|
+
id: z.enum([
|
|
1123
|
+
"status.get",
|
|
1124
|
+
"task.get",
|
|
1125
|
+
"task.history"
|
|
1126
|
+
]),
|
|
1127
|
+
input: z.unknown()
|
|
1128
|
+
}), z.strictObject({
|
|
1129
|
+
kind: z.literal("usage"),
|
|
1130
|
+
text: z.string().min(1)
|
|
1131
|
+
})]).optional(),
|
|
1120
1132
|
sourceUrl,
|
|
1121
1133
|
sourceSpace: sourceSpace.optional()
|
|
1122
1134
|
});
|