@eliya-oss/agent-slack 0.1.11 → 0.1.13

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,31 +0,0 @@
1
- const nonEmpty = (value, name) => {
2
- if (value.trim() === "") {
3
- throw new Error(`${name} cannot be empty`);
4
- }
5
- return value;
6
- };
7
- export const TeamId = {
8
- make: (value) => nonEmpty(value, "TeamId")
9
- };
10
- export const EnterpriseId = {
11
- make: (value) => nonEmpty(value, "EnterpriseId")
12
- };
13
- export const ChannelId = {
14
- make: (value) => nonEmpty(value, "ChannelId")
15
- };
16
- export const UserId = {
17
- make: (value) => nonEmpty(value, "UserId")
18
- };
19
- export const MessageTs = {
20
- make: (value) => nonEmpty(value, "MessageTs")
21
- };
22
- export const SlackMethod = {
23
- make: (value) => nonEmpty(value, "SlackMethod")
24
- };
25
- export const ProfileName = {
26
- default: "default",
27
- make: (value) => nonEmpty(value, "ProfileName")
28
- };
29
- export const Scope = {
30
- make: (value) => nonEmpty(value, "Scope")
31
- };
@@ -1 +0,0 @@
1
- export {};
@@ -1,65 +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
- // Machine output is compact by default to minimize token cost; `--pretty`
62
- // restores indentation for humans. See ADR-002.
63
- export const serializeJson = (value, pretty) => pretty ? `${JSON.stringify(value, null, 2)}\n` : `${JSON.stringify(value)}\n`;
64
- export const toNdjson = (items) => items.map((item) => JSON.stringify(item)).join("\n") + (items.length > 0 ? "\n" : "");
65
- 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,154 +0,0 @@
1
- // Slim, token-efficient shapes for Slack read output.
2
- //
3
- // Raw Slack objects carry far more than an agent needs to reason: users.info
4
- // returns ~7 avatar URLs plus color/status/tz/team; messages carry `blocks`
5
- // (a structured duplicate of `text`), `client_msg_id`, `team`, and verbose
6
- // reactions. These transforms keep the reasoning-relevant fields and drop the
7
- // rest. `--full` bypasses them and returns the raw objects. See ADR-002.
8
- const isRec = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
9
- const asArray = (value) => (Array.isArray(value) ? value : []);
10
- const str = (record, key) => typeof record[key] === "string" && record[key].length > 0 ? record[key] : undefined;
11
- export const slimFile = (input) => {
12
- if (!isRec(input))
13
- return input;
14
- const out = { id: input.id, name: input.name };
15
- if (typeof input.mimetype === "string")
16
- out.mimetype = input.mimetype;
17
- if (typeof input.size === "number")
18
- out.size = input.size;
19
- const permalink = str(input, "permalink");
20
- if (permalink)
21
- out.permalink = permalink;
22
- return out;
23
- };
24
- export const slimMessage = (input) => {
25
- if (!isRec(input))
26
- return input;
27
- const m = input;
28
- const out = {};
29
- if (typeof m.user === "string")
30
- out.user = m.user;
31
- else if (typeof m.bot_id === "string")
32
- out.bot_id = m.bot_id;
33
- const username = str(m, "username");
34
- if (username)
35
- out.username = username;
36
- const subtype = str(m, "subtype");
37
- if (subtype)
38
- out.subtype = subtype;
39
- if (typeof m.ts === "string")
40
- out.ts = m.ts;
41
- out.text = typeof m.text === "string" ? m.text : "";
42
- if (typeof m.thread_ts === "string" && m.thread_ts !== m.ts)
43
- out.thread_ts = m.thread_ts;
44
- if (typeof m.reply_count === "number")
45
- out.reply_count = m.reply_count;
46
- const reactions = asArray(m.reactions)
47
- .filter(isRec)
48
- .map((r) => ({ name: r.name, count: r.count }));
49
- if (reactions.length > 0)
50
- out.reactions = reactions;
51
- const files = asArray(m.files).map(slimFile);
52
- if (files.length > 0)
53
- out.files = files;
54
- if (m.edited !== undefined && m.edited !== null)
55
- out.edited = true;
56
- return out;
57
- };
58
- export const slimUser = (input) => {
59
- if (!isRec(input))
60
- return input;
61
- const u = input;
62
- const profile = isRec(u.profile) ? u.profile : {};
63
- const out = { id: u.id, name: u.name };
64
- const real = str(profile, "display_name") ?? str(u, "real_name") ?? str(profile, "real_name");
65
- if (real)
66
- out.real_name = real;
67
- if (u.is_bot === true)
68
- out.is_bot = true;
69
- if (u.deleted === true)
70
- out.deleted = true;
71
- const title = str(profile, "title");
72
- if (title)
73
- out.title = title;
74
- return out;
75
- };
76
- export const slimConversation = (input) => {
77
- if (!isRec(input))
78
- return input;
79
- const c = input;
80
- const out = { id: c.id, name: c.name };
81
- if (c.is_private === true)
82
- out.is_private = true;
83
- if (c.is_archived === true)
84
- out.is_archived = true;
85
- if (c.is_im === true)
86
- out.is_im = true;
87
- if (c.is_mpim === true)
88
- out.is_mpim = true;
89
- const topic = isRec(c.topic) ? str(c.topic, "value") : undefined;
90
- if (topic)
91
- out.topic = topic;
92
- const purpose = isRec(c.purpose) ? str(c.purpose, "value") : undefined;
93
- if (purpose)
94
- out.purpose = purpose;
95
- if (typeof c.num_members === "number")
96
- out.num_members = c.num_members;
97
- return out;
98
- };
99
- export const slimTeam = (input) => {
100
- if (!isRec(input))
101
- return input;
102
- const t = input;
103
- const out = { id: t.id, name: t.name };
104
- const domain = str(t, "domain");
105
- if (domain)
106
- out.domain = domain;
107
- const emailDomain = str(t, "email_domain");
108
- if (emailDomain)
109
- out.email_domain = emailDomain;
110
- const enterpriseId = str(t, "enterprise_id");
111
- if (enterpriseId)
112
- out.enterprise_id = enterpriseId;
113
- const enterpriseName = str(t, "enterprise_name");
114
- if (enterpriseName)
115
- out.enterprise_name = enterpriseName;
116
- return out;
117
- };
118
- // Drop the always-present envelope noise from an unmapped response.
119
- const stripResponseEnvelope = (response) => {
120
- const { ok, response_metadata, ...rest } = response;
121
- void ok;
122
- void response_metadata;
123
- return rest;
124
- };
125
- // Normalize a raw Slack Web API response for a given method into slim `data`.
126
- export const normalizeResponse = (method, response) => {
127
- if (!isRec(response))
128
- return response;
129
- const r = response;
130
- switch (method) {
131
- case "conversations.history":
132
- case "conversations.replies":
133
- return { messages: asArray(r.messages).map(slimMessage) };
134
- case "conversations.info":
135
- return { channel: slimConversation(r.channel) };
136
- case "conversations.list":
137
- return { channels: asArray(r.channels).map(slimConversation) };
138
- case "users.info":
139
- case "users.lookupByEmail":
140
- return { user: slimUser(r.user) };
141
- case "users.list":
142
- return { users: asArray(r.members).map(slimUser) };
143
- case "files.info":
144
- return { file: slimFile(r.file) };
145
- case "files.list":
146
- return { files: asArray(r.files).map(slimFile) };
147
- case "team.info":
148
- return { team: slimTeam(r.team) };
149
- case "reactions.get":
150
- return isRec(r.message) ? { ...stripResponseEnvelope(r), message: slimMessage(r.message) } : stripResponseEnvelope(r);
151
- default:
152
- return stripResponseEnvelope(r);
153
- }
154
- };
@@ -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 {};