spur-board 0.4.1 → 0.5.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 +22 -3
- package/dist/{client-CHohYNxa.mjs → client-BnkZmz1i.mjs} +1 -1
- package/dist/client.mjs +1 -1
- package/dist/{config-BuxxS_er.mjs → config-UyXnaekw.mjs} +2 -2
- package/dist/daemon.mjs +16 -6
- package/dist/mcp.mjs +3 -3
- package/dist/{src-BEo3mvRC.mjs → src-VWyX7Nwm.mjs} +6 -2
- package/package.json +1 -1
- package/skills/setup/references/slack.md +2 -2
package/dist/cli.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as renderCliError, i as errorExitCode, n as requestTokenEnrollment, o as escapeTerminalControls, r as CliError, t as requestOperation } from "./client-
|
|
3
|
-
import { i as resolveDaemonAddress, n as loadClientConfig, r as loadEnrollmentConfig, t as configFile } from "./config-
|
|
1
|
+
import { R as configSchema, s as OPERATIONS, z as SLACK_BOT_SCOPES } from "./src-VWyX7Nwm.mjs";
|
|
2
|
+
import { a as renderCliError, i as errorExitCode, n as requestTokenEnrollment, o as escapeTerminalControls, r as CliError, t as requestOperation } from "./client-BnkZmz1i.mjs";
|
|
3
|
+
import { i as resolveDaemonAddress, n as loadClientConfig, r as loadEnrollmentConfig, t as configFile } from "./config-UyXnaekw.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";
|
|
@@ -13,6 +13,23 @@ import { parseEnv } from "node:util";
|
|
|
13
13
|
import { access, cp, lstat, mkdir, readFile, readdir, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
15
15
|
//#region src/manifest.ts
|
|
16
|
+
function loadSlackManifestDefaults(env, home) {
|
|
17
|
+
let contents;
|
|
18
|
+
try {
|
|
19
|
+
contents = readFileSync(configFile(env, home), "utf8");
|
|
20
|
+
} catch (error) {
|
|
21
|
+
if (error !== null && typeof error === "object" && "code" in error && error.code === "ENOENT") return {};
|
|
22
|
+
throw new CliError("config", void 0, void 0, "Read config.yaml before generating a manifest.");
|
|
23
|
+
}
|
|
24
|
+
const document = YAML.parseDocument(contents);
|
|
25
|
+
if (document.errors.length > 0) throw new CliError("config", void 0, void 0, "Fix the YAML in config.yaml.");
|
|
26
|
+
const parsed = configSchema.safeParse(document.toJS());
|
|
27
|
+
if (!parsed.success) throw new CliError("config", void 0, void 0, `Fix ${parsed.error.issues[0]?.path.map(String).join(".") || "config.yaml"} in config.yaml.`);
|
|
28
|
+
return {
|
|
29
|
+
appName: parsed.data.slack.appName,
|
|
30
|
+
command: parsed.data.slack.command
|
|
31
|
+
};
|
|
32
|
+
}
|
|
16
33
|
function renderSlackManifest(options = {}) {
|
|
17
34
|
const appName = (options.appName ?? "spur-board").trim();
|
|
18
35
|
const command = options.command ?? "/board";
|
|
@@ -988,7 +1005,9 @@ function registerSetupCommands(program, options, io, env) {
|
|
|
988
1005
|
const appName = manifest.opts()["appName"];
|
|
989
1006
|
const command = manifest.opts()["command"];
|
|
990
1007
|
if (appName !== void 0 && typeof appName !== "string" || command !== void 0 && typeof command !== "string") throw new CliError("usage");
|
|
1008
|
+
const defaults = appName === void 0 || command === void 0 ? loadSlackManifestDefaults(env, options.home ?? homedir()) : {};
|
|
991
1009
|
io.stdout(renderSlackManifest({
|
|
1010
|
+
...defaults,
|
|
992
1011
|
...appName === void 0 ? {} : { appName },
|
|
993
1012
|
...command === void 0 ? {} : { command }
|
|
994
1013
|
}));
|
|
@@ -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-VWyX7Nwm.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-BnkZmz1i.mjs";
|
|
2
2
|
export { requestOperation, requestTokenEnrollment };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { r as CliError } from "./client-
|
|
1
|
+
import { R as configSchema } from "./src-VWyX7Nwm.mjs";
|
|
2
|
+
import { r as CliError } from "./client-BnkZmz1i.mjs";
|
|
3
3
|
import { closeSync, constants, fstatSync, lstatSync, openSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { dirname, isAbsolute, join, relative, resolve, 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,
|
|
1
|
+
import { A as taskKeySchema, C as sessionFactsSchema, D as errorCodeOf, E as encodeActorId, L as DEFAULT_SLACK_COMMAND, O as parseActorId, P as successEnvelopeSchema, R as configSchema, 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, z as SLACK_BOT_SCOPES } from "./src-VWyX7Nwm.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";
|
|
@@ -11546,6 +11546,13 @@ function createInbound(input) {
|
|
|
11546
11546
|
await clearItem(trx, item.id, new Date(at));
|
|
11547
11547
|
}
|
|
11548
11548
|
async function runUser(trx, event, at) {
|
|
11549
|
+
if (event.kind === "command" && event.name !== void 0 && event.name !== config.slack.command) return deny(trx, {
|
|
11550
|
+
event,
|
|
11551
|
+
reason: "Command name does not match the configured command.",
|
|
11552
|
+
actor: "system",
|
|
11553
|
+
gateway: gateway.kind,
|
|
11554
|
+
at
|
|
11555
|
+
});
|
|
11549
11556
|
if (event.kind !== "message_deleted" && event.author !== config.operator.slack) return deny(trx, {
|
|
11550
11557
|
event,
|
|
11551
11558
|
reason: "Only the configured operator may act.",
|
|
@@ -11731,7 +11738,7 @@ function createInbound(input) {
|
|
|
11731
11738
|
} catch {}
|
|
11732
11739
|
}
|
|
11733
11740
|
async function applyUser(event) {
|
|
11734
|
-
if ((event.kind === "command" || event.kind === "action") && event.author === config.operator.slack && (event.kind === "command" || event.sourceSpace !== null)) {
|
|
11741
|
+
if ((event.kind === "command" || event.kind === "action") && event.author === config.operator.slack && (event.kind !== "command" || event.name === void 0 || event.name === config.slack.command) && (event.kind === "command" || event.sourceSpace !== null)) {
|
|
11735
11742
|
await dispatchInteraction(event);
|
|
11736
11743
|
return;
|
|
11737
11744
|
}
|
|
@@ -11811,6 +11818,7 @@ function createInbound(input) {
|
|
|
11811
11818
|
const item = await store.db.selectFrom("operator_item").select(["cleared_at", "inbound_event_json"]).where("id", "=", itemId).executeTakeFirst();
|
|
11812
11819
|
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.");
|
|
11813
11820
|
let event = inboundEventSchema.parse(JSON.parse(item.inbound_event_json));
|
|
11821
|
+
if (event.kind === "command" && event.name !== void 0 && event.name !== config.slack.command) throw new CommandError("retry_not_applicable", "This interaction cannot be retried.");
|
|
11814
11822
|
if (event.kind === "command" && event.command === void 0) {
|
|
11815
11823
|
const command = legacyCommand(event.text);
|
|
11816
11824
|
if (command === null) throw new CommandError("retry_not_applicable", "This interaction cannot be retried.");
|
|
@@ -14896,7 +14904,7 @@ function createDeliveryGateway(client, channels) {
|
|
|
14896
14904
|
function descriptors(commandName) {
|
|
14897
14905
|
return OPERATIONS.filter((operation) => operation.kind === "query" && operation.slack?.kind === "slash-command").map((operation) => ({
|
|
14898
14906
|
operation,
|
|
14899
|
-
usage: operation.slack?.kind === "slash-command" ? operation.slack.usage.replace(
|
|
14907
|
+
usage: operation.slack?.kind === "slash-command" ? operation.slack.usage.replace(/^\/\S+/, commandName) : ""
|
|
14900
14908
|
}));
|
|
14901
14909
|
}
|
|
14902
14910
|
function commandUsage(commandName) {
|
|
@@ -15071,7 +15079,7 @@ function normalizeMessage(channel, payload, context) {
|
|
|
15071
15079
|
return events;
|
|
15072
15080
|
}
|
|
15073
15081
|
/** Actions and commands share the same stable trigger identity as their Slack retry. */
|
|
15074
|
-
function normalizeInteraction(type, payload, channels, root, commandName =
|
|
15082
|
+
function normalizeInteraction(type, payload, channels, root, commandName = DEFAULT_SLACK_COMMAND) {
|
|
15075
15083
|
const body = record$1(payload);
|
|
15076
15084
|
if (body === null) return [];
|
|
15077
15085
|
const triggerId = string$1(body.trigger_id);
|
|
@@ -15080,16 +15088,18 @@ function normalizeInteraction(type, payload, channels, root, commandName = "/boa
|
|
|
15080
15088
|
if (user === void 0) return [];
|
|
15081
15089
|
const author = userIdSchema.parse(user);
|
|
15082
15090
|
if (type === "slash_commands") {
|
|
15083
|
-
|
|
15091
|
+
const name = string$1(body.command);
|
|
15092
|
+
if (name === void 0) return [];
|
|
15084
15093
|
const channel = string$1(body.channel_id);
|
|
15085
15094
|
if (channel === void 0) return [];
|
|
15086
15095
|
return [{
|
|
15087
15096
|
kind: "command",
|
|
15097
|
+
name,
|
|
15088
15098
|
dedupeKey: `slack:${triggerId}`,
|
|
15089
15099
|
interaction: interactionRef(channel, user, triggerId),
|
|
15090
15100
|
author,
|
|
15091
15101
|
text: text(body.text) ?? "",
|
|
15092
|
-
command: parseCommand(text(body.text) ?? "", commandName),
|
|
15102
|
+
...name === commandName ? { command: parseCommand(text(body.text) ?? "", commandName) } : {},
|
|
15093
15103
|
sourceUrl: sourceUrl(channel),
|
|
15094
15104
|
sourceSpace: sourceSpace(channel, channels)
|
|
15095
15105
|
}];
|
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 { n as loadClientConfig } from "./config-
|
|
1
|
+
import { j as ERRORS, s as OPERATIONS, u as renderMcpError } from "./src-VWyX7Nwm.mjs";
|
|
2
|
+
import { r as CliError, t as requestOperation } from "./client-BnkZmz1i.mjs";
|
|
3
|
+
import { n as loadClientConfig } from "./config-UyXnaekw.mjs";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
5
|
import { readFileSync } from "node:fs";
|
|
6
6
|
import { z } from "zod";
|
|
@@ -111,6 +111,8 @@ const SLACK_BOT_SCOPES = [
|
|
|
111
111
|
//#endregion
|
|
112
112
|
//#region ../contracts/src/config.ts
|
|
113
113
|
const DEFAULT_HTTP_PORT = 4311;
|
|
114
|
+
const DEFAULT_SLACK_APP_NAME = "spur-board";
|
|
115
|
+
const DEFAULT_SLACK_COMMAND = "/board";
|
|
114
116
|
const WEEKDAYS = [
|
|
115
117
|
"monday",
|
|
116
118
|
"tuesday",
|
|
@@ -247,7 +249,8 @@ const configSchema = z.strictObject({
|
|
|
247
249
|
}),
|
|
248
250
|
operator: z.strictObject({ slack: nonEmpty }),
|
|
249
251
|
slack: z.strictObject({
|
|
250
|
-
|
|
252
|
+
appName: z.string().trim().min(1).default(DEFAULT_SLACK_APP_NAME),
|
|
253
|
+
command: z.string().regex(/^\/[a-z0-9_-]+$/, "Expected a slash command such as /board").default(DEFAULT_SLACK_COMMAND),
|
|
251
254
|
appId: z.string().regex(/^A[A-Z0-9]+$/, "Expected a Slack app ID").optional(),
|
|
252
255
|
channels: z.strictObject({
|
|
253
256
|
orchestrators: nonEmpty.optional(),
|
|
@@ -1114,6 +1117,7 @@ const actionEventSchema = z.strictObject({
|
|
|
1114
1117
|
/** `text` is the command's argument text. */
|
|
1115
1118
|
const commandEventSchema = z.strictObject({
|
|
1116
1119
|
kind: z.literal("command"),
|
|
1120
|
+
name: z.string().min(1).optional(),
|
|
1117
1121
|
dedupeKey,
|
|
1118
1122
|
interaction: interactionRefSchema,
|
|
1119
1123
|
author: userIdSchema,
|
|
@@ -2631,4 +2635,4 @@ var AdapterError = class extends Error {
|
|
|
2631
2635
|
}
|
|
2632
2636
|
};
|
|
2633
2637
|
//#endregion
|
|
2634
|
-
export { taskKeySchema as A, sessionFactsSchema as C, errorCodeOf as D, encodeActorId as E, DEFAULT_HTTP_PORT as F,
|
|
2638
|
+
export { taskKeySchema as A, sessionFactsSchema as C, errorCodeOf as D, encodeActorId as E, DEFAULT_HTTP_PORT as F, DEFAULT_SLACK_APP_NAME as I, DEFAULT_SLACK_COMMAND as L, errorContextSchema as M, errorEnvelopeSchema as N, parseActorId as O, successEnvelopeSchema as P, configSchema as R, prRefSchema as S, actorIdSchema as T, EVENT_KINDS as _, buildCatalogSnapshot as a, SESSION_STATUSES as b, renderHttpProblem as c, SPACE_IDS as d, conversationRefSchema as f, userIdSchema as g, messageRefSchema as h, sourceCursorSchema as i, ERRORS as j, parseRef as k, renderJsonError as l, interactionRefSchema as m, batchSchema as n, roleCanUseOperation as o, inboundEventSchema as p, entityRefSchema as r, OPERATIONS as s, AdapterError as t, renderMcpError as u, eventPayloadSchemas as v, ticketFactsSchema as w, prFactsSchema as x, SESSION_HEALTH_STATES as y, SLACK_BOT_SCOPES as z };
|
package/package.json
CHANGED
|
@@ -4,6 +4,6 @@ Use this when the orchestrators or feed channel is not configured, or the app-le
|
|
|
4
4
|
|
|
5
5
|
1. Check the bot's current channel membership. List the private channels it can see and ask the operator, one choice at a time, which should serve orchestrators and feed. An empty list does not prove no channels exist: the bot sees only private channels to which it was invited. Store channel IDs in config, never guessed names.
|
|
6
6
|
2. If no channel fits, ask for an operator-chosen prefix and suggest available names using it. The operator creates or invites the app to channels; ask before any app or channel change.
|
|
7
|
-
3. Check whether an app and slash command already exist. If either is missing, use `board slack manifest --app-name <name> --command <slash-command>` to prepare a manifest with operator-chosen names. The operator creates the Slack app; do not claim the manifest creates it.
|
|
8
|
-
4. The manifest cannot create the app-level token with `connections:write`.
|
|
7
|
+
3. Check whether an app and slash command already exist. If either is missing, use `board slack manifest --app-name <name> --command <slash-command>` to prepare a manifest with operator-chosen names. The operator creates the Slack app; do not claim the manifest creates it. Set `slack.appName` and `slack.command` in config to the selected names.
|
|
8
|
+
4. The manifest cannot create the app-level token with `connections:write`. In the Slack app settings, open **Basic Information → App-Level Tokens** and create a token with that scope. Then use the private entry procedure in [secret files](secrets.md). Never ask for a token value in chat.
|
|
9
9
|
5. Check channel membership and command configuration with online doctor. If a check fails, apply its reported fix after inspecting state.
|