@eliya-oss/agent-slack 0.1.10 → 0.1.12

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.
@@ -1,62 +0,0 @@
1
- import { randomUUID } from "node:crypto";
2
- import { isSlkError, normalizeUnknownError } from "../domain/errors.js";
3
- export const pagingFrom = (response) => {
4
- const metadata = response.response_metadata;
5
- const nextCursor = typeof metadata === "object" && metadata !== null && "next_cursor" in metadata
6
- ? metadata.next_cursor
7
- : undefined;
8
- return {
9
- next_cursor: typeof nextCursor === "string" && nextCursor.length > 0 ? nextCursor : null,
10
- has_more: response.has_more === true || (typeof nextCursor === "string" && nextCursor.length > 0)
11
- };
12
- };
13
- export const successEnvelope = (input) => ({
14
- ok: true,
15
- method: input.method,
16
- team_id: input.profile?.teamId ?? null,
17
- enterprise_id: input.profile?.enterpriseId ?? null,
18
- profile: input.profile?.name ?? null,
19
- data: input.data,
20
- paging: input.paging ?? { next_cursor: null, has_more: false },
21
- warnings: input.warnings ?? []
22
- });
23
- export const errorEnvelope = (error) => {
24
- const normalized = normalizeUnknownError(error);
25
- const details = Object.fromEntries(Object.entries(normalized.details).filter(([key]) => key !== "suggestion"));
26
- const detailsSlackError = typeof details.slackError === "string" ? details.slackError : undefined;
27
- const slackError = "slackError" in normalized ? normalized.slackError ?? detailsSlackError : detailsSlackError;
28
- const retryAfterSeconds = "retryAfterSeconds" in normalized ? normalized.retryAfterSeconds : undefined;
29
- const detailSuggestion = typeof normalized.details.suggestion === "string" ? normalized.details.suggestion : undefined;
30
- const suggestion = detailSuggestion ?? suggestionFor(normalized._tag);
31
- const envelope = {
32
- ok: false,
33
- error: {
34
- type: normalized._tag,
35
- title: normalized.message,
36
- ...(slackError === undefined ? {} : { slack_error: slackError }),
37
- retriable: normalized._tag === "SlackRateLimited",
38
- ...(retryAfterSeconds === undefined ? {} : { retry_after_seconds: retryAfterSeconds }),
39
- ...(suggestion === undefined ? {} : { suggestion }),
40
- trace_id: `slk_${randomUUID()}`,
41
- ...(Object.keys(details).length === 0 ? {} : { details })
42
- }
43
- };
44
- return { envelope, exitCode: normalized.exitCode };
45
- };
46
- const suggestionFor = (tag) => {
47
- switch (tag) {
48
- case "NotAuthenticated":
49
- return "Run agent-slack auth login, or use --token with an existing Slack bot token.";
50
- case "MissingScope":
51
- return "Reinstall or reauthorize the Slack app with the missing scope.";
52
- case "SlackRateLimited":
53
- return "Retry after the provided delay or reduce page size.";
54
- case "UnsafeMethodBlocked":
55
- return "Pass --allow-write --yes only when the operator intentionally allows this call.";
56
- default:
57
- return undefined;
58
- }
59
- };
60
- export const stringifyJson = (value) => `${JSON.stringify(value, null, 2)}\n`;
61
- export const toNdjson = (items) => items.map((item) => JSON.stringify(item)).join("\n") + (items.length > 0 ? "\n" : "");
62
- export const exitCodeOf = (error) => isSlkError(error) ? error.exitCode : 1;
@@ -1,302 +0,0 @@
1
- const codes = {
2
- bold: [1, 22],
3
- cyan: [36, 39],
4
- dim: [2, 22],
5
- green: [32, 39],
6
- red: [31, 39],
7
- yellow: [33, 39]
8
- };
9
- export const renderHumanEnvelope = (envelope, options) => {
10
- const paint = (name, value) => options.color ? `\u001b[${codes[name][0]}m${value}\u001b[${codes[name][1]}m` : value;
11
- if (isCommandCatalog(envelope.data)) {
12
- return `${renderCommandCatalog(envelope.data, paint).join("\n")}\n`;
13
- }
14
- const metadata = renderMetadata(envelope, paint);
15
- const warnings = envelope.warnings.map((warning) => `${paint("yellow", "Warning")}: ${warning}`);
16
- const data = renderHumanData(envelope.data, paint);
17
- const lines = [
18
- `${paint("green", "OK")} ${paint("bold", envelope.method)}`,
19
- ...(metadata === "" ? [] : [metadata]),
20
- ...(warnings.length === 0 ? [] : warnings),
21
- ...(data.length === 0 ? [] : ["", ...data])
22
- ];
23
- return `${lines.join("\n")}\n`;
24
- };
25
- export const renderHumanErrorEnvelope = (envelope, options) => {
26
- const paint = (name, value) => options.color ? `\u001b[${codes[name][0]}m${value}\u001b[${codes[name][1]}m` : value;
27
- const error = envelope.error;
28
- const lines = [
29
- `${paint("red", "Error")} ${paint("bold", error.type)}`,
30
- error.title,
31
- ...(error.slack_error === undefined ? [] : ["", `${paint("dim", "Slack")} ${error.slack_error}`]),
32
- ...(error.retry_after_seconds === undefined ? [] : [`${paint("dim", "Retry after")} ${error.retry_after_seconds}s`]),
33
- ...(error.suggestion === undefined ? [] : ["", paint("yellow", "Next"), ` ${error.suggestion}`]),
34
- "",
35
- `${paint("dim", "Trace")} ${error.trace_id}`
36
- ];
37
- return `${lines.join("\n")}\n`;
38
- };
39
- const isCommandCatalog = (value) => {
40
- if (!isRecord(value) || typeof value.name !== "string" || !Array.isArray(value.commands)) {
41
- return false;
42
- }
43
- return value.commands.every((command) => isRecord(command) &&
44
- Array.isArray(command.path) &&
45
- command.path.every((part) => typeof part === "string") &&
46
- typeof command.summary === "string");
47
- };
48
- const renderCommandCatalog = (catalog, paint) => {
49
- const title = catalog.version === undefined ? `${titleCase(catalog.name)} Commands` : "Agent Slack";
50
- const aliases = catalog.aliases?.join(", ");
51
- const groups = groupCommands(catalog.commands);
52
- return [
53
- `${paint("bold", title)}${catalog.version === undefined ? "" : ` ${paint("dim", catalog.version)}`}`,
54
- paint("dim", "Slack context CLI for agents"),
55
- "",
56
- `${paint("dim", "Use")} ${paint("cyan", `${catalog.name} <command> [--json]`)}`,
57
- ...(aliases === undefined ? [] : [`${paint("dim", "Alias")} ${aliases}`]),
58
- `${paint("dim", "Output")} readable in terminals; JSON for pipes and --json`,
59
- "",
60
- ...groups.flatMap((group) => renderCommandGroup(group, paint)),
61
- "",
62
- paint("dim", `Run ${catalog.name} <command> --help for details.`)
63
- ];
64
- };
65
- const renderCommandGroup = (group, paint) => {
66
- return [
67
- paint("yellow", group.title),
68
- ...group.commands.map((command) => {
69
- const safety = command.safety === "destructive"
70
- ? ` ${paint("yellow", "[destructive]")}`
71
- : command.safety === "unknown"
72
- ? ` ${paint("yellow", "[review]")}`
73
- : "";
74
- return ` ${paint("cyan", commandSignature(command))} - ${command.summary}${safety}`;
75
- }),
76
- ""
77
- ];
78
- };
79
- const groupCommands = (commands) => {
80
- const groups = new Map();
81
- for (const command of commands) {
82
- const key = groupKey(command);
83
- groups.set(key, [...(groups.get(key) ?? []), command]);
84
- }
85
- return commandGroupOrder
86
- .map((group) => ({
87
- ...group,
88
- commands: groups.get(group.key) ?? []
89
- }))
90
- .filter((group) => group.commands.length > 0);
91
- };
92
- const commandGroupOrder = [
93
- { key: "system", title: "System" },
94
- { key: "auth", title: "Auth" },
95
- { key: "workspace", title: "Workspace" },
96
- { key: "people", title: "People" },
97
- { key: "conversations", title: "Conversations" },
98
- { key: "search", title: "Search" },
99
- { key: "files", title: "Files" },
100
- { key: "surfaces", title: "Slack Objects" },
101
- { key: "api", title: "Web API" }
102
- ];
103
- const groupKey = (command) => {
104
- const root = command.path[0];
105
- if (root === "describe" || root === "completion") {
106
- return "system";
107
- }
108
- if (root === "team" || root === "enterprise") {
109
- return "workspace";
110
- }
111
- if (root === "user" || root === "usergroups") {
112
- return "people";
113
- }
114
- if (root === "conversation" || root === "thread" || root === "message") {
115
- return "conversations";
116
- }
117
- if (root === "file") {
118
- return "files";
119
- }
120
- if (root === "reaction" || root === "pin" || root === "bookmark" || root === "emoji" || root === "dnd") {
121
- return "surfaces";
122
- }
123
- return root ?? "system";
124
- };
125
- const commandSignature = (command) => {
126
- const args = command.args?.filter((arg) => arg !== "PAYLOAD_STDIN_MARKER").join(" ");
127
- return [command.path.join(" "), args].filter((part) => part !== undefined && part !== "").join(" ");
128
- };
129
- const renderMetadata = (envelope, paint) => {
130
- const parts = [
131
- envelope.profile === null ? undefined : `profile ${envelope.profile}`,
132
- envelope.team_id === null ? undefined : `team ${envelope.team_id}`,
133
- envelope.enterprise_id === null ? undefined : `enterprise ${envelope.enterprise_id}`,
134
- envelope.paging.has_more ? `next ${envelope.paging.next_cursor ?? "cursor"}` : undefined
135
- ].filter((part) => part !== undefined);
136
- return parts.length === 0 ? "" : paint("dim", parts.join(" | "));
137
- };
138
- const renderHumanData = (value, paint) => {
139
- if (Array.isArray(value)) {
140
- return renderArray(value, paint);
141
- }
142
- if (isRecord(value)) {
143
- const primaryArray = findPrimaryArray(value);
144
- const excludedKeys = new Set(primaryArray === null ? [] : [primaryArray.key]);
145
- const fieldLines = renderFields(value, excludedKeys, paint);
146
- const hasComplexValues = hasVisibleComplexValues(value, excludedKeys);
147
- if (primaryArray !== null) {
148
- return [
149
- ...fieldLines,
150
- fieldLines.length === 0 ? "" : "",
151
- `${paint("cyan", labelFor(primaryArray.key))} (${primaryArray.items.length})`,
152
- ...renderArray(primaryArray.items, paint)
153
- ].filter((line) => line !== "");
154
- }
155
- if (fieldLines.length > 0 && !hasComplexValues) {
156
- return fieldLines;
157
- }
158
- }
159
- return ["data:", indent(JSON.stringify(value, null, 2))];
160
- };
161
- const findPrimaryArray = (record) => {
162
- const preferred = ["messages", "members", "channels", "files", "users", "results", "items"];
163
- for (const key of preferred) {
164
- const value = record[key];
165
- if (Array.isArray(value) && !value.every(isScalar)) {
166
- return { key, items: value };
167
- }
168
- }
169
- for (const [key, value] of Object.entries(record)) {
170
- if (Array.isArray(value) && !value.every(isScalar)) {
171
- return { key, items: value };
172
- }
173
- }
174
- return null;
175
- };
176
- const renderFields = (record, excludedKeys, paint) => {
177
- const entries = Object.entries(record)
178
- .filter(([key, value]) => !excludedKeys.has(key) && shouldDisplayField(key, value) && isHumanFieldValue(value))
179
- .map(([key, value]) => ({ key, label: labelFor(key), value }))
180
- .sort((left, right) => fieldOrder(left.key) - fieldOrder(right.key));
181
- if (entries.length === 0) {
182
- return [];
183
- }
184
- const labelWidth = Math.max(...entries.map((entry) => entry.label.length));
185
- return entries.map((entry) => `${paint("dim", pad(entry.label, labelWidth))} ${formatHumanValue(entry.value)}`);
186
- };
187
- const hasVisibleComplexValues = (record, excludedKeys) => Object.entries(record).some(([key, value]) => !excludedKeys.has(key) && shouldDisplayField(key, value) && !isHumanFieldValue(value));
188
- const renderArray = (values, paint) => {
189
- if (values.length === 0) {
190
- return ["(empty)"];
191
- }
192
- if (!values.every(isRecord)) {
193
- return [indent(JSON.stringify(values, null, 2))];
194
- }
195
- const rows = values.map((value) => value);
196
- const columns = columnsFor(rows);
197
- if (columns.length === 0) {
198
- return [indent(JSON.stringify(values, null, 2))];
199
- }
200
- const renderedRows = rows.slice(0, 20).map((row) => columns.map((column) => truncate(formatScalar(row[column]), widthFor(column))));
201
- const widths = columns.map((column, index) => Math.max(column.length, ...renderedRows.map((row) => row[index]?.length ?? 0)));
202
- const header = columns.map((column, index) => pad(truncate(column, widthFor(column)), widths[index] ?? column.length)).join(" ");
203
- const divider = widths.map((width) => "-".repeat(width)).join(" ");
204
- const body = renderedRows.map((row) => row.map((cell, index) => pad(cell, widths[index] ?? cell.length)).join(" "));
205
- const hiddenCount = values.length - renderedRows.length;
206
- return [
207
- paint("dim", header),
208
- paint("dim", divider),
209
- ...body,
210
- hiddenCount > 0 ? paint("dim", `... ${hiddenCount} more`) : ""
211
- ].filter((line) => line !== "");
212
- };
213
- const columnsFor = (rows) => {
214
- const preferred = ["id", "channel", "ts", "user", "name", "real_name", "type", "subtype", "text"];
215
- const discovered = new Set(rows.flatMap((row) => Object.keys(row).filter((key) => isScalar(row[key]))));
216
- const ordered = preferred.filter((key) => discovered.has(key));
217
- for (const key of discovered) {
218
- if (!ordered.includes(key)) {
219
- ordered.push(key);
220
- }
221
- if (ordered.length >= 5) {
222
- break;
223
- }
224
- }
225
- return ordered.slice(0, 5);
226
- };
227
- const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
228
- const isScalar = (value) => value === null || ["boolean", "number", "string"].includes(typeof value);
229
- const isHumanFieldValue = (value) => isScalar(value) || (Array.isArray(value) && value.every(isScalar));
230
- const shouldDisplayField = (key, value) => {
231
- if (key === "ok" && value === true) {
232
- return false;
233
- }
234
- if (value === null || value === undefined) {
235
- return false;
236
- }
237
- if (/^has[A-Z].*Token$/.test(key) && value === false) {
238
- return false;
239
- }
240
- return true;
241
- };
242
- const labelOverrides = {
243
- botId: "Bot",
244
- botToken: "Bot token",
245
- channelId: "Channel",
246
- enterpriseId: "Enterprise",
247
- hasAdminToken: "Admin token",
248
- hasAppToken: "App token",
249
- hasBotToken: "Bot token",
250
- hasUserToken: "User token",
251
- messageTs: "Message TS",
252
- name: "Name",
253
- profile: "Profile",
254
- scopes: "Scopes",
255
- teamId: "Team",
256
- tokenType: "Token",
257
- userId: "User"
258
- };
259
- const orderedFields = [
260
- "name",
261
- "teamId",
262
- "enterpriseId",
263
- "userId",
264
- "botId",
265
- "tokenType",
266
- "hasBotToken",
267
- "hasUserToken",
268
- "hasAdminToken",
269
- "hasAppToken",
270
- "scopes"
271
- ];
272
- const fieldOrder = (key) => {
273
- const index = orderedFields.indexOf(key);
274
- return index === -1 ? orderedFields.length : index;
275
- };
276
- const labelFor = (key) => labelOverrides[key] ?? titleCase(key.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/_/g, " "));
277
- const titleCase = (value) => value.split(" ").map((word) => word.toLowerCase() === "id" ? "ID" : word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
278
- const formatHumanValue = (value) => {
279
- if (Array.isArray(value)) {
280
- return value.length === 0 ? "(none)" : value.map(formatScalar).join(", ");
281
- }
282
- if (typeof value === "boolean") {
283
- return value ? "yes" : "no";
284
- }
285
- return formatScalar(value);
286
- };
287
- const formatScalar = (value) => {
288
- if (value === undefined) {
289
- return "";
290
- }
291
- if (value === null) {
292
- return "-";
293
- }
294
- if (typeof value === "string") {
295
- return value;
296
- }
297
- return String(value);
298
- };
299
- const indent = (value) => value.split("\n").map((line) => ` ${line}`).join("\n");
300
- const pad = (value, width) => value + " ".repeat(Math.max(0, width - value.length));
301
- const widthFor = (column) => column === "text" ? 72 : 24;
302
- const truncate = (value, width) => value.length > width ? `${value.slice(0, Math.max(0, width - 3))}...` : value;
@@ -1,55 +0,0 @@
1
- import { UsageError } from "../domain/errors.js";
2
- export const projectFields = (value, fields) => {
3
- if (fields === undefined || fields.trim() === "") {
4
- return value;
5
- }
6
- const paths = fields.split(",").map((field) => field.trim()).filter(Boolean);
7
- if (paths.length === 0) {
8
- return value;
9
- }
10
- const projected = {};
11
- for (const path of paths) {
12
- assignPath(projected, path, readPath(value, path));
13
- }
14
- return projected;
15
- };
16
- const readPath = (value, path) => {
17
- let current = value;
18
- for (const segment of path.split(".")) {
19
- if (segment === "") {
20
- throw new UsageError("--fields paths cannot contain empty segments", { path });
21
- }
22
- if (Array.isArray(current)) {
23
- const index = Number(segment);
24
- if (!Number.isInteger(index) || index < 0) {
25
- return undefined;
26
- }
27
- current = current[index];
28
- continue;
29
- }
30
- if (typeof current !== "object" || current === null || !(segment in current)) {
31
- return undefined;
32
- }
33
- current = current[segment];
34
- }
35
- return current;
36
- };
37
- const assignPath = (target, path, value) => {
38
- const segments = path.split(".");
39
- let current = target;
40
- for (const segment of segments.slice(0, -1)) {
41
- const next = current[segment];
42
- if (typeof next === "object" && next !== null && !Array.isArray(next)) {
43
- current = next;
44
- continue;
45
- }
46
- const created = {};
47
- current[segment] = created;
48
- current = created;
49
- }
50
- const last = segments.at(-1);
51
- if (last === undefined || last === "") {
52
- throw new UsageError("--fields paths cannot contain empty segments", { path });
53
- }
54
- current[last] = value;
55
- };
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};