@kodiak-finance/orderly-devkit 1.0.2

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.
@@ -0,0 +1,224 @@
1
+ const {
2
+ heading,
3
+ info,
4
+ warn,
5
+ error,
6
+ success,
7
+ select,
8
+ confirm,
9
+ getApiErrorInfo,
10
+ } = require("../shared");
11
+ const {
12
+ isLoggedIn,
13
+ getToken,
14
+ authenticatedFetch,
15
+ } = require("../internal/auth");
16
+ const {
17
+ MARKETPLACE_API_MY_PLUGINS_URL,
18
+ getMarketplaceApiPluginUrl,
19
+ } = require("../internal/constants");
20
+
21
+ /**
22
+ * Normalize unknown list payloads into an array.
23
+ * This keeps delete flow stable across minor API response shape changes.
24
+ * @param {unknown} data
25
+ * @returns {Array<Record<string, unknown>>}
26
+ */
27
+ function normalizePlugins(data) {
28
+ if (Array.isArray(data)) {
29
+ return data;
30
+ }
31
+
32
+ if (Array.isArray(data?.data)) {
33
+ return data.data;
34
+ }
35
+
36
+ if (Array.isArray(data?.plugins)) {
37
+ return data.plugins;
38
+ }
39
+
40
+ if (Array.isArray(data?.items)) {
41
+ return data.items;
42
+ }
43
+
44
+ if (Array.isArray(data?.results)) {
45
+ return data.results;
46
+ }
47
+
48
+ if (Array.isArray(data?.data?.items)) {
49
+ return data.data.items;
50
+ }
51
+
52
+ if (Array.isArray(data?.data?.plugins)) {
53
+ return data.data.plugins;
54
+ }
55
+
56
+ if (Array.isArray(data?.data?.results)) {
57
+ return data.data.results;
58
+ }
59
+
60
+ return [];
61
+ }
62
+
63
+ /**
64
+ * Render a compact plugin label for the selection prompt.
65
+ * @param {Record<string, unknown>} plugin
66
+ * @returns {string}
67
+ */
68
+ function toPluginChoiceLabel(plugin) {
69
+ const name = plugin?.name || plugin?.npmName || "Unknown";
70
+ const id = plugin?.id || plugin?.pluginId || "unknown-id";
71
+ const status = plugin?.status || "unknown";
72
+ return `${name} (${id}) [${status}]`;
73
+ }
74
+
75
+ module.exports = {
76
+ command: "delete",
77
+ describe: "Delete one of your submitted plugins from Marketplace",
78
+ builder: (yargs) => {
79
+ return yargs
80
+ .option("pluginId", {
81
+ type: "string",
82
+ describe:
83
+ "string; plugin ID to delete directly. If omitted, an interactive selector is shown",
84
+ demandOption: false,
85
+ })
86
+ .example("orderly delete", "Select one of your plugins and delete it")
87
+ .example(
88
+ "orderly delete --pluginId my-plugin-id",
89
+ "Delete a specific plugin directly without selection",
90
+ )
91
+ .example(
92
+ "orderly delete --help",
93
+ "Show command help and usage for plugin deletion",
94
+ );
95
+ },
96
+ handler: async (argv) => {
97
+ heading("Delete My Plugin");
98
+ info(
99
+ "This command will permanently delete one of your submitted plugins.\n",
100
+ );
101
+
102
+ // Deletion is author-scoped and requires authenticated identity.
103
+ if (!isLoggedIn()) {
104
+ warn("You are not logged in.");
105
+ info("Please run 'orderly login' first to authenticate.");
106
+ process.exitCode = 1;
107
+ return;
108
+ }
109
+
110
+ const token = getToken();
111
+ if (!token) {
112
+ error("Authentication token not found.");
113
+ info("Please run 'orderly login' again.");
114
+ process.exitCode = 1;
115
+ return;
116
+ }
117
+
118
+ try {
119
+ // Skip interactive selection when pluginId is provided explicitly.
120
+ let selectedPluginId =
121
+ typeof argv.pluginId === "string" ? argv.pluginId.trim() : "";
122
+ if (!selectedPluginId) {
123
+ info("Fetching your submitted plugins...");
124
+ const listResponse = await authenticatedFetch(
125
+ MARKETPLACE_API_MY_PLUGINS_URL,
126
+ {
127
+ method: "GET",
128
+ headers: {
129
+ Accept: "application/json",
130
+ Authorization: `Bearer ${token}`,
131
+ },
132
+ },
133
+ );
134
+
135
+ const listData = await listResponse.json().catch(() => null);
136
+ if (!listResponse.ok) {
137
+ const { message } = getApiErrorInfo(listData, listResponse.status);
138
+ error(`Failed to fetch plugins: ${message}`);
139
+ process.exitCode = 1;
140
+ return;
141
+ }
142
+
143
+ const plugins = normalizePlugins(listData);
144
+ if (plugins.length === 0) {
145
+ info("You have not submitted any plugins yet.");
146
+ info(
147
+ "If Marketplace Web shows records, run `orderly whoami` to confirm account consistency and `orderly list --json` to inspect the raw API response.",
148
+ );
149
+ return;
150
+ }
151
+
152
+ const choices = plugins
153
+ .map((plugin) => ({
154
+ name: String(plugin?.id || plugin?.pluginId || ""),
155
+ message: toPluginChoiceLabel(plugin),
156
+ value: String(plugin?.id || plugin?.pluginId || ""),
157
+ }))
158
+ .filter((choice) => Boolean(choice.value));
159
+
160
+ if (choices.length === 0) {
161
+ error("No valid plugin IDs found in marketplace response.");
162
+ process.exitCode = 1;
163
+ return;
164
+ }
165
+
166
+ selectedPluginId = await select(
167
+ "Select a plugin to delete permanently:",
168
+ choices,
169
+ 0,
170
+ );
171
+ } else {
172
+ info(`Using pluginId from command argument: ${selectedPluginId}`);
173
+ }
174
+
175
+ if (!selectedPluginId) {
176
+ warn("No plugin selected.");
177
+ process.exitCode = 1;
178
+ return;
179
+ }
180
+
181
+ const confirmed = await confirm(
182
+ `Are you sure you want to delete plugin "${selectedPluginId}"? This action cannot be undone.`,
183
+ );
184
+ if (!confirmed) {
185
+ warn("Deletion cancelled.");
186
+ return;
187
+ }
188
+
189
+ const endpoint = getMarketplaceApiPluginUrl(selectedPluginId);
190
+ info(`Submitting delete request to ${endpoint} ...`);
191
+ const deleteResponse = await authenticatedFetch(endpoint, {
192
+ method: "DELETE",
193
+ headers: {
194
+ Accept: "application/json",
195
+ Authorization: `Bearer ${token}`,
196
+ },
197
+ });
198
+
199
+ if (deleteResponse.status === 204) {
200
+ success("\nPlugin deleted successfully.");
201
+ info(`Plugin ID: ${selectedPluginId}`);
202
+ return;
203
+ }
204
+
205
+ const deleteData = await deleteResponse.json().catch(() => null);
206
+ const { code, message } = getApiErrorInfo(
207
+ deleteData,
208
+ deleteResponse.status,
209
+ );
210
+ error(`Failed to delete plugin: ${message}`);
211
+ if (code) {
212
+ info(`Error code: ${code}`);
213
+ }
214
+ info("Please verify plugin ownership and API availability.");
215
+ process.exitCode = 1;
216
+ } catch (e) {
217
+ // Include endpoint context to help diagnose request/runtime failures.
218
+ const cause = e?.message || String(e);
219
+ error(`Request failed while calling marketplace APIs: ${cause}`);
220
+ info("Please verify network connectivity and API availability.");
221
+ process.exitCode = 1;
222
+ }
223
+ },
224
+ };
@@ -0,0 +1,219 @@
1
+ const {
2
+ heading,
3
+ info,
4
+ warn,
5
+ error,
6
+ success,
7
+ select,
8
+ getApiErrorInfo,
9
+ } = require("../shared");
10
+ const {
11
+ isLoggedIn,
12
+ getToken,
13
+ authenticatedFetch,
14
+ } = require("../internal/auth");
15
+ const {
16
+ MARKETPLACE_API_MY_PLUGINS_URL,
17
+ getMarketplaceApiPluginSelfStatusUrl,
18
+ } = require("../internal/constants");
19
+
20
+ /**
21
+ * Normalize unknown list payloads into an array.
22
+ * Keeps this command resilient to small response shape changes.
23
+ * @param {unknown} data
24
+ * @returns {Array<Record<string, unknown>>}
25
+ */
26
+ function normalizePlugins(data) {
27
+ if (Array.isArray(data)) {
28
+ return data;
29
+ }
30
+
31
+ if (Array.isArray(data?.data)) {
32
+ return data.data;
33
+ }
34
+
35
+ if (Array.isArray(data?.plugins)) {
36
+ return data.plugins;
37
+ }
38
+
39
+ if (Array.isArray(data?.items)) {
40
+ return data.items;
41
+ }
42
+
43
+ if (Array.isArray(data?.results)) {
44
+ return data.results;
45
+ }
46
+
47
+ if (Array.isArray(data?.data?.items)) {
48
+ return data.data.items;
49
+ }
50
+
51
+ if (Array.isArray(data?.data?.plugins)) {
52
+ return data.data.plugins;
53
+ }
54
+
55
+ if (Array.isArray(data?.data?.results)) {
56
+ return data.data.results;
57
+ }
58
+
59
+ return [];
60
+ }
61
+
62
+ /**
63
+ * Format a compact prompt label so users can identify the target plugin quickly.
64
+ * @param {Record<string, unknown>} plugin
65
+ * @returns {string}
66
+ */
67
+ function toPluginChoiceLabel(plugin) {
68
+ const name = plugin?.name || plugin?.npmName || "Unknown";
69
+ const id = plugin?.id || plugin?.pluginId || "unknown-id";
70
+ const status = plugin?.status || "unknown";
71
+ return `${name} (${id}) [${status}]`;
72
+ }
73
+
74
+ module.exports = {
75
+ command: "disable",
76
+ describe: "Delist one of your submitted plugins from Marketplace",
77
+ builder: (yargs) => {
78
+ return yargs
79
+ .option("pluginId", {
80
+ type: "string",
81
+ describe:
82
+ "string; plugin ID to delist directly. If omitted, an interactive selector is shown",
83
+ demandOption: false,
84
+ })
85
+ .example("orderly disable", "Select one of your plugins and delist it")
86
+ .example(
87
+ "orderly disable --pluginId my-plugin-id",
88
+ "Delist a specific plugin directly without selection",
89
+ )
90
+ .example(
91
+ "orderly disable --help",
92
+ "Show command help and usage for plugin delisting",
93
+ );
94
+ },
95
+ handler: async (argv) => {
96
+ heading("Disable My Plugin");
97
+ info("This command will delist one of your submitted plugins.\n");
98
+
99
+ // Require login first because both listing and status update are owner-scoped operations.
100
+ if (!isLoggedIn()) {
101
+ warn("You are not logged in.");
102
+ info("Please run 'orderly login' first to authenticate.");
103
+ process.exitCode = 1;
104
+ return;
105
+ }
106
+
107
+ const token = getToken();
108
+ if (!token) {
109
+ error("Authentication token not found.");
110
+ info("Please run 'orderly login' again.");
111
+ process.exitCode = 1;
112
+ return;
113
+ }
114
+
115
+ try {
116
+ // Skip interactive selection when pluginId is provided explicitly.
117
+ let selectedPluginId =
118
+ typeof argv.pluginId === "string" ? argv.pluginId.trim() : "";
119
+ if (!selectedPluginId) {
120
+ info("Fetching your submitted plugins...");
121
+ const listResponse = await authenticatedFetch(
122
+ MARKETPLACE_API_MY_PLUGINS_URL,
123
+ {
124
+ method: "GET",
125
+ headers: {
126
+ Accept: "application/json",
127
+ Authorization: `Bearer ${token}`,
128
+ },
129
+ },
130
+ );
131
+
132
+ const listData = await listResponse.json().catch(() => null);
133
+ if (!listResponse.ok) {
134
+ const { message } = getApiErrorInfo(listData, listResponse.status);
135
+ error(`Failed to fetch plugins: ${message}`);
136
+ process.exitCode = 1;
137
+ return;
138
+ }
139
+
140
+ const plugins = normalizePlugins(listData);
141
+ if (plugins.length === 0) {
142
+ info("You have not submitted any plugins yet.");
143
+ info(
144
+ "If Marketplace Web shows records, run `orderly whoami` to confirm account consistency and `orderly list --json` to inspect the raw API response.",
145
+ );
146
+ return;
147
+ }
148
+
149
+ const choices = plugins
150
+ .map((plugin) => ({
151
+ name: String(plugin?.id || plugin?.pluginId || ""),
152
+ message: toPluginChoiceLabel(plugin),
153
+ value: String(plugin?.id || plugin?.pluginId || ""),
154
+ }))
155
+ .filter((choice) => Boolean(choice.value));
156
+
157
+ if (choices.length === 0) {
158
+ error("No valid plugin IDs found in marketplace response.");
159
+ process.exitCode = 1;
160
+ return;
161
+ }
162
+
163
+ selectedPluginId = await select(
164
+ "Select a plugin to delist (set status to disabled):",
165
+ choices,
166
+ 0,
167
+ );
168
+ } else {
169
+ info(`Using pluginId from command argument: ${selectedPluginId}`);
170
+ }
171
+
172
+ if (!selectedPluginId) {
173
+ warn("No plugin selected.");
174
+ process.exitCode = 1;
175
+ return;
176
+ }
177
+
178
+ const endpoint = getMarketplaceApiPluginSelfStatusUrl(selectedPluginId);
179
+ info(`Submitting delist request to ${endpoint} ...`);
180
+ const disableResponse = await authenticatedFetch(endpoint, {
181
+ method: "PATCH",
182
+ headers: {
183
+ "Content-Type": "application/json",
184
+ Accept: "application/json",
185
+ Authorization: `Bearer ${token}`,
186
+ },
187
+ body: JSON.stringify({ status: "disabled" }),
188
+ });
189
+
190
+ const disableData = await disableResponse.json().catch(() => null);
191
+ if (!disableResponse.ok) {
192
+ const { code, message } = getApiErrorInfo(
193
+ disableData,
194
+ disableResponse.status,
195
+ );
196
+ error(`Failed to delist plugin: ${message}`);
197
+ if (code) {
198
+ info(`Error code: ${code}`);
199
+ }
200
+ info("Please verify plugin ownership and API availability.");
201
+ process.exitCode = 1;
202
+ return;
203
+ }
204
+
205
+ const updatedName = disableData?.name || disableData?.npmName || "N/A";
206
+ const updatedStatus = disableData?.status || "disabled";
207
+ success("\nPlugin delisted successfully.");
208
+ info(`Plugin ID: ${selectedPluginId}`);
209
+ info(`Plugin Name: ${updatedName}`);
210
+ info(`Status: ${updatedStatus}`);
211
+ } catch (e) {
212
+ // Include endpoint context so users can triage connectivity issues quickly.
213
+ const cause = e?.message || String(e);
214
+ error(`Request failed while calling marketplace APIs: ${cause}`);
215
+ info("Please verify network connectivity and API availability.");
216
+ process.exitCode = 1;
217
+ }
218
+ },
219
+ };
@@ -0,0 +1,196 @@
1
+ const {
2
+ heading,
3
+ info,
4
+ warn,
5
+ error,
6
+ success,
7
+ getErrorMessage,
8
+ } = require("../shared");
9
+ const {
10
+ isLoggedIn,
11
+ getToken,
12
+ authenticatedFetch,
13
+ } = require("../internal/auth");
14
+ const { MARKETPLACE_API_MY_PLUGINS_URL } = require("../internal/constants");
15
+
16
+ /**
17
+ * Convert unknown plugin payload into a list shape safely.
18
+ * This keeps CLI output stable across minor API response changes.
19
+ */
20
+ function normalizePlugins(data) {
21
+ if (Array.isArray(data)) {
22
+ return data;
23
+ }
24
+
25
+ if (Array.isArray(data?.data)) {
26
+ return data.data;
27
+ }
28
+
29
+ if (Array.isArray(data?.plugins)) {
30
+ return data.plugins;
31
+ }
32
+
33
+ if (Array.isArray(data?.items)) {
34
+ return data.items;
35
+ }
36
+
37
+ if (Array.isArray(data?.results)) {
38
+ return data.results;
39
+ }
40
+
41
+ if (Array.isArray(data?.data?.items)) {
42
+ return data.data.items;
43
+ }
44
+
45
+ if (Array.isArray(data?.data?.plugins)) {
46
+ return data.data.plugins;
47
+ }
48
+
49
+ if (Array.isArray(data?.data?.results)) {
50
+ return data.data.results;
51
+ }
52
+
53
+ return [];
54
+ }
55
+
56
+ /**
57
+ * Keep table columns compact so terminal output stays readable.
58
+ */
59
+ function truncate(value, maxLength = 64) {
60
+ const text = String(value ?? "-");
61
+ if (text.length <= maxLength) {
62
+ return text;
63
+ }
64
+ return `${text.slice(0, maxLength - 3)}...`;
65
+ }
66
+
67
+ /**
68
+ * Select common plugin fields with graceful fallbacks.
69
+ */
70
+ function mapRow(plugin) {
71
+ return {
72
+ id: plugin?.id || plugin?.pluginId || "-",
73
+ name: plugin?.name || plugin?.npmName || "-",
74
+ version: plugin?.version || plugin?.latestVersion || "-",
75
+ status: plugin?.status || "-",
76
+ description: truncate(plugin?.description || "-", 72),
77
+ };
78
+ }
79
+
80
+ /**
81
+ * Render simple aligned table without extra dependencies.
82
+ */
83
+ function renderTable(rows) {
84
+ const headers = ["ID", "Name", "Version", "Status", "Description"];
85
+ const keys = ["id", "name", "version", "status", "description"];
86
+
87
+ const widths = keys.map((key, index) => {
88
+ const headerWidth = headers[index].length;
89
+ const maxValueWidth = rows.reduce((max, row) => {
90
+ const width = String(row[key] ?? "-").length;
91
+ return width > max ? width : max;
92
+ }, 0);
93
+ return Math.max(headerWidth, maxValueWidth);
94
+ });
95
+
96
+ const buildLine = (values) =>
97
+ values
98
+ .map((value, index) => String(value).padEnd(widths[index]))
99
+ .join(" ");
100
+
101
+ const headerLine = buildLine(headers);
102
+ const dividerLine = widths.map((width) => "-".repeat(width)).join(" ");
103
+ const rowLines = rows.map((row) =>
104
+ buildLine(keys.map((key) => row[key] ?? "-")),
105
+ );
106
+
107
+ return [headerLine, dividerLine, ...rowLines].join("\n");
108
+ }
109
+
110
+ module.exports = {
111
+ command: "list",
112
+ describe: "List my submitted plugins from Marketplace",
113
+ builder: (yargs) => {
114
+ return yargs
115
+ .option("json", {
116
+ type: "boolean",
117
+ describe:
118
+ "boolean; when true, output the raw marketplace response as JSON (otherwise prints a compact table)",
119
+ default: false,
120
+ })
121
+ .example("orderly list", "List your submitted plugins as a compact table")
122
+ .example(
123
+ "orderly list --json",
124
+ "Output the raw JSON payload (useful for debugging/agents)",
125
+ );
126
+ },
127
+ handler: async (argv) => {
128
+ heading("My Plugins");
129
+
130
+ if (!isLoggedIn()) {
131
+ warn("You are not logged in.");
132
+ info("Please run 'orderly login' first to authenticate.");
133
+ process.exitCode = 1;
134
+ return;
135
+ }
136
+
137
+ const token = getToken();
138
+ if (!token) {
139
+ error("Authentication token not found.");
140
+ info("Please run 'orderly login' again.");
141
+ process.exitCode = 1;
142
+ return;
143
+ }
144
+
145
+ try {
146
+ // Use Headers to merge defaults safely and inject auth token consistently.
147
+ const headers = new Headers({ Accept: "application/json" });
148
+ headers.set("Authorization", `Bearer ${token}`);
149
+
150
+ const response = await authenticatedFetch(
151
+ MARKETPLACE_API_MY_PLUGINS_URL,
152
+ {
153
+ method: "GET",
154
+ headers,
155
+ },
156
+ );
157
+
158
+ const responseData = await response.json().catch(() => null);
159
+
160
+ if (!response.ok) {
161
+ const serverMessage = getErrorMessage(responseData, response.status);
162
+ error(`Failed to fetch plugins: ${serverMessage}`);
163
+ process.exitCode = 1;
164
+ return;
165
+ }
166
+
167
+ const plugins = normalizePlugins(responseData);
168
+
169
+ if (argv.json) {
170
+ // Print real server payload for troubleshooting response shape mismatches.
171
+ console.log(JSON.stringify(responseData, null, 2));
172
+ return;
173
+ }
174
+
175
+ if (plugins.length === 0) {
176
+ info("You have not submitted any plugins yet.");
177
+ info(
178
+ "If Marketplace Web shows records, run `orderly whoami` to confirm account consistency and `orderly list --json` to inspect the raw API response.",
179
+ );
180
+ return;
181
+ }
182
+
183
+ const rows = plugins.map(mapRow);
184
+ console.log(renderTable(rows));
185
+ success(`\nTotal: ${plugins.length} plugin(s)`);
186
+ } catch (e) {
187
+ // Surface target endpoint to make network/runtime failures actionable.
188
+ const cause = e?.message || String(e);
189
+ error(
190
+ `Request failed while calling ${MARKETPLACE_API_MY_PLUGINS_URL}: ${cause}`,
191
+ );
192
+ info("Please verify network connectivity and API availability.");
193
+ process.exitCode = 1;
194
+ }
195
+ },
196
+ };