@zapier/zapier-sdk-cli 0.13.2 → 0.13.4
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/CHANGELOG.md +18 -0
- package/dist/cli.cjs +124 -19
- package/dist/cli.mjs +125 -20
- package/dist/index.cjs +110 -10
- package/dist/index.d.mts +91 -2
- package/dist/index.d.ts +91 -2
- package/dist/index.mjs +111 -12
- package/dist/package.json +1 -1
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +1 -0
- package/dist/src/plugins/login/index.d.ts +8 -1
- package/dist/src/plugins/login/index.js +55 -9
- package/dist/src/sdk.d.ts +8 -0
- package/dist/src/sdk.js +1 -0
- package/dist/src/telemetry/builders.d.ts +42 -0
- package/dist/src/telemetry/builders.js +56 -0
- package/dist/src/telemetry/events.d.ts +37 -0
- package/dist/src/telemetry/events.js +4 -0
- package/dist/src/utils/schema-formatter.js +13 -7
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/index.ts +8 -0
- package/src/plugins/login/index.ts +69 -9
- package/src/sdk.ts +9 -0
- package/src/telemetry/builders.ts +114 -0
- package/src/telemetry/events.ts +39 -0
- package/src/utils/cli-generator.test.ts +102 -0
- package/src/utils/schema-formatter.ts +15 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zapier/zapier-sdk-cli",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.4",
|
|
4
4
|
"description": "Command line interface for Zapier SDK",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"ora": "^8.2.0",
|
|
43
43
|
"pkce-challenge": "^5.0.0",
|
|
44
44
|
"zod": "^3.25.67",
|
|
45
|
-
"@zapier/zapier-sdk": "0.13.
|
|
46
|
-
"@zapier/zapier-sdk-mcp": "0.3.
|
|
45
|
+
"@zapier/zapier-sdk": "0.13.4",
|
|
46
|
+
"@zapier/zapier-sdk-mcp": "0.3.17",
|
|
47
47
|
"@zapier/zapier-sdk-cli-login": "0.3.2"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
// Main exports for the CLI package
|
|
2
2
|
export { createZapierCliSdk, type ZapierCliSdkOptions } from "./sdk";
|
|
3
3
|
|
|
4
|
+
// Export CLI telemetry builders and types
|
|
5
|
+
export type {
|
|
6
|
+
CliEventContext,
|
|
7
|
+
CliCommandExecutedEventData,
|
|
8
|
+
} from "./telemetry/builders";
|
|
9
|
+
export { buildCliCommandExecutedEvent } from "./telemetry/builders";
|
|
10
|
+
export type { CliCommandExecutedEvent } from "./telemetry/events";
|
|
11
|
+
|
|
4
12
|
// All CLI functionality is now schema-driven via generateCLICommands
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import { createFunction } from "@zapier/zapier-sdk";
|
|
2
2
|
import type { Plugin } from "@zapier/zapier-sdk";
|
|
3
|
+
import type { EventEmissionContext } from "@zapier/zapier-sdk";
|
|
3
4
|
import login from "../../utils/auth/login";
|
|
4
5
|
import { getLoggedInUser } from "@zapier/zapier-sdk-cli-login";
|
|
5
6
|
import { LoginSchema, type LoginOptions } from "./schemas";
|
|
7
|
+
import { buildCliCommandExecutedEvent } from "../../telemetry/builders";
|
|
8
|
+
import cliPackageJson from "../../../package.json";
|
|
9
|
+
|
|
10
|
+
interface CliContext {
|
|
11
|
+
session_id?: string | null;
|
|
12
|
+
selected_api?: string | null;
|
|
13
|
+
app_id?: number | null;
|
|
14
|
+
app_version_id?: number | null;
|
|
15
|
+
}
|
|
6
16
|
|
|
7
17
|
interface LoginPluginProvides {
|
|
8
18
|
login: (options: LoginOptions) => Promise<void>;
|
|
@@ -16,6 +26,9 @@ interface LoginPluginProvides {
|
|
|
16
26
|
};
|
|
17
27
|
}
|
|
18
28
|
|
|
29
|
+
const CLI_COMMAND_EXECUTED_EVENT_SUBJECT =
|
|
30
|
+
"platform.sdk.CliCommandExecutedEvent";
|
|
31
|
+
|
|
19
32
|
const loginWithSdk = createFunction(async function loginWithSdk(
|
|
20
33
|
options: LoginOptions,
|
|
21
34
|
): Promise<void> {
|
|
@@ -33,16 +46,63 @@ const loginWithSdk = createFunction(async function loginWithSdk(
|
|
|
33
46
|
setTimeout(() => process.exit(0), 100);
|
|
34
47
|
}, LoginSchema);
|
|
35
48
|
|
|
36
|
-
export const loginPlugin: Plugin<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
49
|
+
export const loginPlugin: Plugin<
|
|
50
|
+
{},
|
|
51
|
+
EventEmissionContext & CliContext,
|
|
52
|
+
LoginPluginProvides
|
|
53
|
+
> = ({ context }) => {
|
|
54
|
+
// Wrap the login function to emit telemetry events
|
|
55
|
+
const loginWithTelemetry = async (options: LoginOptions): Promise<void> => {
|
|
56
|
+
const startTime = Date.now();
|
|
57
|
+
let success = false;
|
|
58
|
+
let errorMessage: string | null = null;
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
await loginWithSdk(options);
|
|
62
|
+
success = true;
|
|
63
|
+
} catch (error) {
|
|
64
|
+
success = false;
|
|
65
|
+
errorMessage = error instanceof Error ? error.message : "Login failed";
|
|
66
|
+
throw error;
|
|
67
|
+
} finally {
|
|
68
|
+
// Emit CLI command executed event if event emission is available
|
|
69
|
+
const event = buildCliCommandExecutedEvent({
|
|
70
|
+
data: {
|
|
71
|
+
cli_primary_command: "login",
|
|
72
|
+
success_flag: success,
|
|
73
|
+
execution_duration_ms: Date.now() - startTime,
|
|
74
|
+
exit_code: success ? 0 : 1,
|
|
75
|
+
error_message: errorMessage,
|
|
76
|
+
command_category: "authentication",
|
|
77
|
+
requires_auth: false,
|
|
78
|
+
cli_arguments: [
|
|
79
|
+
"login",
|
|
80
|
+
options.timeout ? `--timeout=${options.timeout}` : null,
|
|
81
|
+
].filter(Boolean) as string[],
|
|
82
|
+
},
|
|
83
|
+
context: {
|
|
84
|
+
session_id: context.session_id,
|
|
85
|
+
selected_api: context.selected_api,
|
|
86
|
+
app_id: context.app_id,
|
|
87
|
+
app_version_id: context.app_version_id,
|
|
88
|
+
},
|
|
89
|
+
cliVersion: cliPackageJson.version,
|
|
90
|
+
});
|
|
91
|
+
context.eventEmission.emit(CLI_COMMAND_EXECUTED_EVENT_SUBJECT, event);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
login: loginWithTelemetry,
|
|
97
|
+
context: {
|
|
98
|
+
meta: {
|
|
99
|
+
login: {
|
|
100
|
+
categories: ["account"],
|
|
101
|
+
inputSchema: LoginSchema,
|
|
102
|
+
},
|
|
43
103
|
},
|
|
44
104
|
},
|
|
45
|
-
}
|
|
46
|
-
}
|
|
105
|
+
};
|
|
106
|
+
};
|
|
47
107
|
|
|
48
108
|
export type { LoginPluginProvides };
|
package/src/sdk.ts
CHANGED
|
@@ -14,6 +14,14 @@ import {
|
|
|
14
14
|
|
|
15
15
|
export interface ZapierCliSdkOptions {
|
|
16
16
|
debug?: boolean;
|
|
17
|
+
eventEmission?: {
|
|
18
|
+
enabled?: boolean;
|
|
19
|
+
transport?: {
|
|
20
|
+
type: "http" | "console" | "noop";
|
|
21
|
+
endpoint?: string;
|
|
22
|
+
headers?: Record<string, string>;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
17
25
|
}
|
|
18
26
|
|
|
19
27
|
/**
|
|
@@ -26,6 +34,7 @@ export function createZapierCliSdk(
|
|
|
26
34
|
// Create SDK instance without registry
|
|
27
35
|
let sdk = createZapierSdkWithoutRegistry({
|
|
28
36
|
debug: options.debug,
|
|
37
|
+
eventEmission: options.eventEmission,
|
|
29
38
|
});
|
|
30
39
|
|
|
31
40
|
// Add CLI-specific plugins before registry
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI-specific event builders
|
|
3
|
+
*
|
|
4
|
+
* Provides builder functions for CLI command telemetry that auto-populate
|
|
5
|
+
* common CLI fields and system information.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { BaseEvent } from "@zapier/zapier-sdk";
|
|
9
|
+
import {
|
|
10
|
+
generateEventId,
|
|
11
|
+
getCurrentTimestamp,
|
|
12
|
+
getReleaseId,
|
|
13
|
+
getOsInfo,
|
|
14
|
+
getPlatformVersions,
|
|
15
|
+
isCi,
|
|
16
|
+
getCiPlatform,
|
|
17
|
+
} from "@zapier/zapier-sdk";
|
|
18
|
+
import type { CliCommandExecutedEvent } from "./events";
|
|
19
|
+
import cliPackageJson from "../../package.json";
|
|
20
|
+
|
|
21
|
+
// CLI context that can be injected into events
|
|
22
|
+
export interface CliEventContext {
|
|
23
|
+
customuser_id?: number | null;
|
|
24
|
+
account_id?: number | null;
|
|
25
|
+
identity_id?: number | null;
|
|
26
|
+
visitor_id?: string | null;
|
|
27
|
+
correlation_id?: string | null;
|
|
28
|
+
session_id?: string | null;
|
|
29
|
+
selected_api?: string | null;
|
|
30
|
+
app_id?: number | null;
|
|
31
|
+
app_version_id?: number | null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Create base event for CLI events
|
|
35
|
+
function createCliBaseEvent(context: CliEventContext = {}): BaseEvent {
|
|
36
|
+
return {
|
|
37
|
+
event_id: generateEventId(),
|
|
38
|
+
timestamp_ms: getCurrentTimestamp(),
|
|
39
|
+
release_id: getReleaseId(),
|
|
40
|
+
customuser_id: context.customuser_id ?? null,
|
|
41
|
+
account_id: context.account_id ?? null,
|
|
42
|
+
identity_id: context.identity_id ?? null,
|
|
43
|
+
visitor_id: context.visitor_id ?? null,
|
|
44
|
+
correlation_id: context.correlation_id ?? null,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// CLI Command Executed Event Builder
|
|
49
|
+
// This interface corresponds to the cli_command_executed_event.avsc Avro schema
|
|
50
|
+
export interface CliCommandExecutedEventData {
|
|
51
|
+
cli_primary_command: string;
|
|
52
|
+
success_flag: boolean;
|
|
53
|
+
execution_duration_ms?: number | null;
|
|
54
|
+
exit_code?: number | null;
|
|
55
|
+
error_message?: string | null;
|
|
56
|
+
command_category?: string | null;
|
|
57
|
+
requires_auth?: boolean | null;
|
|
58
|
+
cli_arguments?: (string | null)[] | null;
|
|
59
|
+
// Allow overriding auto-detected values if needed
|
|
60
|
+
cli_version?: string | null;
|
|
61
|
+
made_network_requests?: boolean | null;
|
|
62
|
+
files_modified_count?: number | null;
|
|
63
|
+
files_created_count?: number | null;
|
|
64
|
+
files_processed_size_bytes?: number | null;
|
|
65
|
+
peak_memory_usage_bytes?: number | null;
|
|
66
|
+
cpu_time_ms?: number | null;
|
|
67
|
+
subprocess_count?: number | null;
|
|
68
|
+
package_manager?: string | null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function buildCliCommandExecutedEvent({
|
|
72
|
+
data,
|
|
73
|
+
context = {},
|
|
74
|
+
cliVersion = cliPackageJson.version,
|
|
75
|
+
}: {
|
|
76
|
+
data: CliCommandExecutedEventData;
|
|
77
|
+
context?: CliEventContext;
|
|
78
|
+
cliVersion?: string;
|
|
79
|
+
}): CliCommandExecutedEvent {
|
|
80
|
+
const osInfo = getOsInfo();
|
|
81
|
+
const platformVersions = getPlatformVersions();
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
...createCliBaseEvent(context),
|
|
85
|
+
system_name: "zapier-sdk-cli",
|
|
86
|
+
session_id: context.session_id ?? null,
|
|
87
|
+
cli_version: data.cli_version ?? cliVersion,
|
|
88
|
+
cli_arguments: data.cli_arguments ?? null,
|
|
89
|
+
cli_primary_command: data.cli_primary_command,
|
|
90
|
+
os_platform: osInfo.platform,
|
|
91
|
+
os_release: osInfo.release,
|
|
92
|
+
os_architecture: osInfo.architecture,
|
|
93
|
+
platform_versions: platformVersions,
|
|
94
|
+
selected_api: context.selected_api ?? null,
|
|
95
|
+
app_id: context.app_id ?? null,
|
|
96
|
+
app_version_id: context.app_version_id ?? null,
|
|
97
|
+
execution_duration_ms: data.execution_duration_ms ?? null,
|
|
98
|
+
success_flag: data.success_flag,
|
|
99
|
+
exit_code: data.exit_code ?? (data.success_flag ? 0 : 1),
|
|
100
|
+
error_message: data.error_message ?? null,
|
|
101
|
+
command_category: data.command_category ?? null,
|
|
102
|
+
requires_auth: data.requires_auth ?? null,
|
|
103
|
+
is_ci_environment: isCi(),
|
|
104
|
+
ci_platform: getCiPlatform(),
|
|
105
|
+
package_manager: data.package_manager ?? "pnpm", // Default based on project setup
|
|
106
|
+
made_network_requests: data.made_network_requests ?? null,
|
|
107
|
+
files_modified_count: data.files_modified_count ?? null,
|
|
108
|
+
files_created_count: data.files_created_count ?? null,
|
|
109
|
+
files_processed_size_bytes: data.files_processed_size_bytes ?? null,
|
|
110
|
+
peak_memory_usage_bytes: data.peak_memory_usage_bytes ?? null,
|
|
111
|
+
cpu_time_ms: data.cpu_time_ms ?? null,
|
|
112
|
+
subprocess_count: data.subprocess_count ?? null,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI-specific telemetry event definitions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { BaseEvent } from "@zapier/zapier-sdk";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Event emitted when a CLI command is executed
|
|
9
|
+
*/
|
|
10
|
+
export interface CliCommandExecutedEvent extends BaseEvent {
|
|
11
|
+
system_name: string;
|
|
12
|
+
session_id?: string | null;
|
|
13
|
+
cli_version?: string | null;
|
|
14
|
+
cli_arguments?: (string | null)[] | null;
|
|
15
|
+
cli_primary_command?: string | null;
|
|
16
|
+
os_platform?: string | null;
|
|
17
|
+
os_release?: string | null;
|
|
18
|
+
os_architecture?: string | null;
|
|
19
|
+
platform_versions?: Record<string, string | null> | null;
|
|
20
|
+
selected_api?: string | null;
|
|
21
|
+
app_id?: number | null;
|
|
22
|
+
app_version_id?: number | null;
|
|
23
|
+
execution_duration_ms?: number | null;
|
|
24
|
+
success_flag: boolean;
|
|
25
|
+
exit_code?: number | null;
|
|
26
|
+
error_message?: string | null;
|
|
27
|
+
command_category?: string | null;
|
|
28
|
+
requires_auth?: boolean | null;
|
|
29
|
+
is_ci_environment?: boolean | null;
|
|
30
|
+
ci_platform?: string | null;
|
|
31
|
+
package_manager?: string | null;
|
|
32
|
+
made_network_requests?: boolean | null;
|
|
33
|
+
files_modified_count?: number | null;
|
|
34
|
+
files_created_count?: number | null;
|
|
35
|
+
files_processed_size_bytes?: number | null;
|
|
36
|
+
peak_memory_usage_bytes?: number | null;
|
|
37
|
+
cpu_time_ms?: number | null;
|
|
38
|
+
subprocess_count?: number | null;
|
|
39
|
+
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
2
|
import chalk from "chalk";
|
|
3
|
+
import { generateCliCommands } from "./cli-generator";
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import type { ZapierSdk } from "@zapier/zapier-sdk";
|
|
6
|
+
import { z } from "zod";
|
|
3
7
|
|
|
4
8
|
// We need to create a test for the formatItemsGeneric function that's defined in cli-generator.ts
|
|
5
9
|
// Since it's not exported, we'll create a simple test that verifies the numbering logic
|
|
@@ -91,3 +95,101 @@ describe("CLI Generator Pagination Numbering", () => {
|
|
|
91
95
|
expect(output.some((line) => line.includes("2."))).toBe(true);
|
|
92
96
|
});
|
|
93
97
|
});
|
|
98
|
+
|
|
99
|
+
// Create proper Zod schema for testing
|
|
100
|
+
const testSchema = z
|
|
101
|
+
.object({
|
|
102
|
+
maxItems: z.number().optional(),
|
|
103
|
+
pageSize: z.number().optional(),
|
|
104
|
+
})
|
|
105
|
+
.describe("List all apps");
|
|
106
|
+
|
|
107
|
+
// Mock SDK for CLI tests
|
|
108
|
+
const mockSdk = {
|
|
109
|
+
getRegistry: vi.fn(() => ({
|
|
110
|
+
functions: [
|
|
111
|
+
{
|
|
112
|
+
name: "listApps",
|
|
113
|
+
type: "list",
|
|
114
|
+
inputSchema: testSchema,
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
categories: [],
|
|
118
|
+
})),
|
|
119
|
+
listApps: vi.fn().mockResolvedValue({ data: [] }),
|
|
120
|
+
} as unknown as ZapierSdk;
|
|
121
|
+
|
|
122
|
+
describe("CLI Command Generation", () => {
|
|
123
|
+
beforeEach(() => {
|
|
124
|
+
vi.clearAllMocks();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("should execute CLI commands successfully", async () => {
|
|
128
|
+
const program = new Command();
|
|
129
|
+
program.exitOverride(); // Prevent process.exit() in tests
|
|
130
|
+
|
|
131
|
+
// Generate CLI commands
|
|
132
|
+
generateCliCommands(program, mockSdk);
|
|
133
|
+
|
|
134
|
+
// Execute the command through Commander.js parsing
|
|
135
|
+
await program.parseAsync(["node", "test", "list-apps", "--json"]);
|
|
136
|
+
|
|
137
|
+
// Verify the SDK method was called
|
|
138
|
+
expect(mockSdk.listApps).toHaveBeenCalled();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("should handle CLI command failures gracefully", async () => {
|
|
142
|
+
const program = new Command();
|
|
143
|
+
program.exitOverride(); // Prevent process.exit() in tests
|
|
144
|
+
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
145
|
+
|
|
146
|
+
// Generate CLI commands
|
|
147
|
+
generateCliCommands(program, mockSdk);
|
|
148
|
+
|
|
149
|
+
// Mock the SDK method to throw an error
|
|
150
|
+
(mockSdk as unknown).listApps = vi
|
|
151
|
+
.fn()
|
|
152
|
+
.mockRejectedValue(new Error("API error"));
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
// Execute the command through Commander.js parsing
|
|
156
|
+
await program.parseAsync(["node", "test", "list-apps", "--json"]);
|
|
157
|
+
} catch {
|
|
158
|
+
// Expected due to exitOverride throwing CommanderError
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Verify the SDK method was attempted
|
|
162
|
+
expect(mockSdk.listApps).toHaveBeenCalled();
|
|
163
|
+
|
|
164
|
+
// Cleanup
|
|
165
|
+
consoleSpy.mockRestore();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("should generate commands for SDK without event emission", async () => {
|
|
169
|
+
const mockSdkWithoutEvents = {
|
|
170
|
+
getRegistry: vi.fn(() => ({
|
|
171
|
+
functions: [
|
|
172
|
+
{
|
|
173
|
+
name: "listApps",
|
|
174
|
+
type: "list",
|
|
175
|
+
inputSchema: testSchema,
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
categories: [],
|
|
179
|
+
})),
|
|
180
|
+
listApps: vi.fn().mockResolvedValue({ data: [] }),
|
|
181
|
+
} as unknown as ZapierSdk;
|
|
182
|
+
|
|
183
|
+
const program = new Command();
|
|
184
|
+
program.exitOverride(); // Prevent process.exit() in tests
|
|
185
|
+
|
|
186
|
+
// Generate CLI commands
|
|
187
|
+
generateCliCommands(program, mockSdkWithoutEvents);
|
|
188
|
+
|
|
189
|
+
// Should not throw even without event emission capability
|
|
190
|
+
await program.parseAsync(["node", "test", "list-apps", "--json"]);
|
|
191
|
+
|
|
192
|
+
// Verify the command executed successfully
|
|
193
|
+
expect(mockSdkWithoutEvents.listApps).toHaveBeenCalled();
|
|
194
|
+
});
|
|
195
|
+
});
|
|
@@ -66,13 +66,22 @@ function formatSingleItem(formatted: FormattedItem, itemNumber: number): void {
|
|
|
66
66
|
// Build the main title line with optional subtitle
|
|
67
67
|
let titleLine = `${chalk.gray(`${itemNumber + 1}.`)} ${chalk.cyan(formatted.title)}`;
|
|
68
68
|
|
|
69
|
-
// Generate subtitle
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
titleLine += ` ${chalk.gray(`(${formatted.keys.join(", ")})`)}`;
|
|
69
|
+
// Generate subtitle - always show key if available, then ID if available
|
|
70
|
+
const subtitleParts = [];
|
|
71
|
+
if (formatted.keys) {
|
|
72
|
+
subtitleParts.push(...formatted.keys);
|
|
74
73
|
} else if (formatted.key) {
|
|
75
|
-
|
|
74
|
+
subtitleParts.push(formatted.key);
|
|
75
|
+
}
|
|
76
|
+
if (formatted.id) {
|
|
77
|
+
subtitleParts.push(formatted.id);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Remove duplicates while preserving order
|
|
81
|
+
const uniqueParts = [...new Set(subtitleParts)];
|
|
82
|
+
|
|
83
|
+
if (uniqueParts.length > 0) {
|
|
84
|
+
titleLine += ` ${chalk.gray(`(${uniqueParts.join(", ")})`)}`;
|
|
76
85
|
}
|
|
77
86
|
console.log(titleLine);
|
|
78
87
|
|