flingit 0.0.11 → 0.0.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.
Files changed (93) hide show
  1. package/README.md +8 -1
  2. package/dist/cli/commands/db.d.ts +11 -0
  3. package/dist/cli/commands/db.d.ts.map +1 -1
  4. package/dist/cli/commands/db.js +60 -48
  5. package/dist/cli/commands/db.js.map +1 -1
  6. package/dist/cli/commands/dev.d.ts +9 -0
  7. package/dist/cli/commands/dev.d.ts.map +1 -1
  8. package/dist/cli/commands/dev.js +96 -100
  9. package/dist/cli/commands/dev.js.map +1 -1
  10. package/dist/cli/commands/feedback.d.ts +23 -0
  11. package/dist/cli/commands/feedback.d.ts.map +1 -1
  12. package/dist/cli/commands/feedback.js +60 -73
  13. package/dist/cli/commands/feedback.js.map +1 -1
  14. package/dist/cli/commands/init.d.ts +9 -0
  15. package/dist/cli/commands/init.d.ts.map +1 -1
  16. package/dist/cli/commands/init.js +96 -107
  17. package/dist/cli/commands/init.js.map +1 -1
  18. package/dist/cli/commands/launch.d.ts +11 -1
  19. package/dist/cli/commands/launch.d.ts.map +1 -1
  20. package/dist/cli/commands/launch.js +32 -36
  21. package/dist/cli/commands/launch.js.map +1 -1
  22. package/dist/cli/commands/login.d.ts +30 -0
  23. package/dist/cli/commands/login.d.ts.map +1 -1
  24. package/dist/cli/commands/login.js +71 -58
  25. package/dist/cli/commands/login.js.map +1 -1
  26. package/dist/cli/commands/logs.d.ts +39 -0
  27. package/dist/cli/commands/logs.d.ts.map +1 -1
  28. package/dist/cli/commands/logs.js +121 -90
  29. package/dist/cli/commands/logs.js.map +1 -1
  30. package/dist/cli/commands/onboard.d.ts +1 -1
  31. package/dist/cli/commands/onboard.d.ts.map +1 -1
  32. package/dist/cli/commands/onboard.js +94 -41
  33. package/dist/cli/commands/onboard.js.map +1 -1
  34. package/dist/cli/commands/plugin.d.ts +43 -0
  35. package/dist/cli/commands/plugin.d.ts.map +1 -1
  36. package/dist/cli/commands/plugin.js +229 -211
  37. package/dist/cli/commands/plugin.js.map +1 -1
  38. package/dist/cli/commands/project.d.ts +27 -0
  39. package/dist/cli/commands/project.d.ts.map +1 -1
  40. package/dist/cli/commands/project.js +101 -85
  41. package/dist/cli/commands/project.js.map +1 -1
  42. package/dist/cli/commands/push.d.ts +39 -0
  43. package/dist/cli/commands/push.d.ts.map +1 -1
  44. package/dist/cli/commands/push.js +152 -118
  45. package/dist/cli/commands/push.js.map +1 -1
  46. package/dist/cli/commands/storage.d.ts +32 -0
  47. package/dist/cli/commands/storage.d.ts.map +1 -1
  48. package/dist/cli/commands/storage.js +180 -140
  49. package/dist/cli/commands/storage.js.map +1 -1
  50. package/dist/cli/commands/tunnel.d.ts +41 -0
  51. package/dist/cli/commands/tunnel.d.ts.map +1 -0
  52. package/dist/cli/commands/tunnel.js +210 -0
  53. package/dist/cli/commands/tunnel.js.map +1 -0
  54. package/dist/cli/commands/upgrade.d.ts +15 -0
  55. package/dist/cli/commands/upgrade.d.ts.map +1 -0
  56. package/dist/cli/commands/upgrade.js +82 -0
  57. package/dist/cli/commands/upgrade.js.map +1 -0
  58. package/dist/cli/index.js +35 -4
  59. package/dist/cli/index.js.map +1 -1
  60. package/dist/cli/utils/cli-io-impl.d.ts +64 -0
  61. package/dist/cli/utils/cli-io-impl.d.ts.map +1 -0
  62. package/dist/cli/utils/cli-io-impl.js +521 -0
  63. package/dist/cli/utils/cli-io-impl.js.map +1 -0
  64. package/dist/cli/utils/cli-io.d.ts +350 -0
  65. package/dist/cli/utils/cli-io.d.ts.map +1 -0
  66. package/dist/cli/utils/cli-io.js +13 -0
  67. package/dist/cli/utils/cli-io.js.map +1 -0
  68. package/dist/cli/utils/config.d.ts +60 -2
  69. package/dist/cli/utils/config.d.ts.map +1 -1
  70. package/dist/cli/utils/config.js +158 -37
  71. package/dist/cli/utils/config.js.map +1 -1
  72. package/dist/cli/utils/project.d.ts +60 -0
  73. package/dist/cli/utils/project.d.ts.map +1 -1
  74. package/dist/cli/utils/project.js +112 -2
  75. package/dist/cli/utils/project.js.map +1 -1
  76. package/dist/cli/utils/prompt-new-project.d.ts +7 -3
  77. package/dist/cli/utils/prompt-new-project.d.ts.map +1 -1
  78. package/dist/cli/utils/prompt-new-project.js +12 -17
  79. package/dist/cli/utils/prompt-new-project.js.map +1 -1
  80. package/dist/cli/utils/registry.d.ts +2 -2
  81. package/dist/cli/utils/registry.d.ts.map +1 -1
  82. package/dist/cli/utils/registry.js +13 -3
  83. package/dist/cli/utils/registry.js.map +1 -1
  84. package/dist/runtime/log.d.ts.map +1 -1
  85. package/dist/runtime/log.js +8 -4
  86. package/dist/runtime/log.js.map +1 -1
  87. package/dist/runtime/storage.js +1 -1
  88. package/dist/runtime/storage.js.map +1 -1
  89. package/package.json +3 -2
  90. package/templates/default/CLAUDE.md +1 -0
  91. package/templates/default/skills/fling/.hash +1 -0
  92. package/templates/default/skills/fling/SKILL.md +12 -4
  93. /package/templates/default/skills/{discord/SKILL.md → fling/DISCORD.md} +0 -0
@@ -4,75 +4,58 @@
4
4
  * Install, configure, and manage platform plugins like Discord.
5
5
  */
6
6
  import { Command } from "commander";
7
- import { createInterface } from "node:readline";
8
- import { spawn } from "node:child_process";
9
7
  import { isLoggedIn, platformFetch, getApiUrl, getToken } from "../utils/config.js";
8
+ import { consoleIO, processIO, createSimplePromptIO, browserIO, timingIO } from "../utils/cli-io-impl.js";
10
9
  const SUPPORTED_PLUGINS = ["discord"];
10
+ // ═══════════════════════════════════════════════════════
11
+ // DEFAULT IMPLEMENTATIONS
12
+ // ═══════════════════════════════════════════════════════
13
+ export function createDefaultPluginDeps() {
14
+ return {
15
+ console: consoleIO,
16
+ process: processIO,
17
+ user: createSimplePromptIO(processIO),
18
+ browser: browserIO,
19
+ timing: timingIO,
20
+ isLoggedIn,
21
+ getToken,
22
+ getApiUrl,
23
+ platformFetch,
24
+ };
25
+ }
26
+ export const defaultPluginDeps = createDefaultPluginDeps();
27
+ // ═══════════════════════════════════════════════════════
28
+ // CORE FUNCTIONS (with DI)
29
+ // ═══════════════════════════════════════════════════════
11
30
  /**
12
31
  * Guard: exit if not logged in.
13
32
  */
14
- function requireLogin() {
15
- if (!isLoggedIn()) {
16
- console.error("Error: Not logged in. Run 'fling login' first.");
17
- process.exit(1);
33
+ function requireLogin(deps) {
34
+ if (!deps.isLoggedIn()) {
35
+ deps.console.error("Error: Not logged in. Run 'fling login' first.");
36
+ deps.process.exit(1);
18
37
  }
19
38
  }
20
39
  /**
21
40
  * Guard: exit if plugin name is not recognized.
22
41
  */
23
- function requireValidPlugin(plugin) {
42
+ function requireValidPlugin(plugin, deps) {
24
43
  if (!SUPPORTED_PLUGINS.includes(plugin)) {
25
- console.error(`Error: Unknown plugin '${plugin}'.`);
26
- console.error(`Available plugins: ${SUPPORTED_PLUGINS.join(", ")}`);
27
- process.exit(1);
28
- }
29
- }
30
- /**
31
- * Prompt for input.
32
- */
33
- async function prompt(question) {
34
- const rl = createInterface({
35
- input: process.stdin,
36
- output: process.stdout,
37
- });
38
- return new Promise((resolve) => {
39
- rl.question(question, (answer) => {
40
- rl.close();
41
- resolve(answer);
42
- });
43
- });
44
- }
45
- /**
46
- * Open a URL in the default browser.
47
- */
48
- function openBrowser(url) {
49
- const platform = process.platform;
50
- let command;
51
- let args;
52
- if (platform === "darwin") {
53
- command = "open";
54
- args = [url];
55
- }
56
- else if (platform === "win32") {
57
- command = "cmd";
58
- args = ["/c", "start", url];
59
- }
60
- else {
61
- command = "xdg-open";
62
- args = [url];
44
+ deps.console.error(`Error: Unknown plugin '${plugin}'.`);
45
+ deps.console.error(`Available plugins: ${SUPPORTED_PLUGINS.join(", ")}`);
46
+ deps.process.exit(1);
63
47
  }
64
- spawn(command, args, { detached: true, stdio: "ignore" }).unref();
65
48
  }
66
49
  /**
67
50
  * Wait for Discord OAuth to complete by polling status.
68
51
  */
69
- async function waitForDiscordOAuth(maxWaitMs = 120000) {
52
+ async function waitForDiscordOAuth(deps, maxWaitMs = 120000) {
70
53
  const pollInterval = 2000;
71
54
  const maxAttempts = Math.ceil(maxWaitMs / pollInterval);
72
55
  for (let i = 0; i < maxAttempts; i++) {
73
- await new Promise((resolve) => setTimeout(resolve, pollInterval));
56
+ await deps.timing.sleep(pollInterval);
74
57
  try {
75
- const response = await platformFetch("/discord/status");
58
+ const response = await deps.platformFetch("/discord/status");
76
59
  if (response.ok) {
77
60
  const data = (await response.json());
78
61
  if (data.connected) {
@@ -86,44 +69,24 @@ async function waitForDiscordOAuth(maxWaitMs = 120000) {
86
69
  }
87
70
  return false;
88
71
  }
89
- export const pluginCommand = new Command("plugin")
90
- .description("Manage platform plugins")
91
- .addHelpText("after", `
92
- Available plugins:
93
- discord Discord chatops integration
94
-
95
- Examples:
96
- fling plugin install discord Install and configure Discord
97
- fling plugin permissions discord Show Discord permissions/status
98
- fling plugin remove discord Disconnect Discord
99
- `);
100
- // =============================================================================
101
- // Install Command
102
- // =============================================================================
103
- pluginCommand
104
- .command("install <plugin>")
105
- .description("Install and configure a plugin")
106
- .addHelpText("after", `
107
- Supported plugins:
108
- discord Connect Discord for chatops
109
-
110
- Examples:
111
- fling plugin install discord OAuth with Discord, add bot to server
112
- `)
113
- .action(async (plugin) => {
114
- requireLogin();
115
- requireValidPlugin(plugin);
72
+ /**
73
+ * Run the plugin install command.
74
+ * Exported for testing with injected dependencies.
75
+ */
76
+ export async function runPluginInstall(plugin, deps = defaultPluginDeps) {
77
+ requireLogin(deps);
78
+ requireValidPlugin(plugin, deps);
116
79
  // Check if already connected
117
80
  try {
118
- const statusRes = await platformFetch("/discord/status");
81
+ const statusRes = await deps.platformFetch("/discord/status");
119
82
  if (statusRes.ok) {
120
83
  const status = (await statusRes.json());
121
84
  if (status.connected) {
122
- console.log(`Discord already connected as ${status.discordUser?.username}`);
123
- console.log(`Servers: ${status.servers.map((s) => s.name).join(", ") || "(none claimed)"}`);
124
- console.log("");
125
- console.log("To add more servers: fling plugin discord add-server");
126
- console.log("To reconnect: fling plugin remove discord && fling plugin install discord");
85
+ deps.console.log(`Discord already connected as ${status.discordUser?.username}`);
86
+ deps.console.log(`Servers: ${status.servers.map((s) => s.name).join(", ") || "(none claimed)"}`);
87
+ deps.console.log("");
88
+ deps.console.log("To add more servers: fling plugin discord add-server");
89
+ deps.console.log("To reconnect: fling plugin remove discord && fling plugin install discord");
127
90
  return;
128
91
  }
129
92
  }
@@ -131,277 +94,332 @@ Examples:
131
94
  catch {
132
95
  // Not connected or endpoint not available
133
96
  }
134
- console.log("Opening Discord authorization...\n");
97
+ deps.console.log("Opening Discord authorization...\n");
135
98
  // Open browser to OAuth URL (pass token as query param since browser can't send auth headers).
136
99
  // TODO: Replace with a short-lived, single-use exchange token to avoid exposing the
137
100
  // long-lived API token in browser history. Flow: CLI POSTs to /discord/oauth/initiate
138
101
  // with Authorization header, gets back a single-use code, opens browser with just that code.
139
- const token = getToken();
102
+ const token = deps.getToken();
140
103
  if (!token) {
141
- console.error("Error: Not logged in. Run 'fling login' first.");
142
- process.exit(1);
104
+ deps.console.error("Error: Not logged in. Run 'fling login' first.");
105
+ deps.process.exit(1);
143
106
  }
144
- const oauthUrl = `${getApiUrl()}/discord/oauth/authorize?token=${encodeURIComponent(token)}`;
145
- openBrowser(oauthUrl);
146
- console.log("Please complete the authorization in your browser.");
147
- console.log("Waiting for Discord connection...\n");
107
+ const oauthUrl = `${deps.getApiUrl()}/discord/oauth/authorize?token=${encodeURIComponent(token)}`;
108
+ deps.browser.open(oauthUrl);
109
+ deps.console.log("Please complete the authorization in your browser.");
110
+ deps.console.log("Waiting for Discord connection...\n");
148
111
  // Poll for completion
149
- const connected = await waitForDiscordOAuth();
112
+ const connected = await waitForDiscordOAuth(deps);
150
113
  if (!connected) {
151
- console.error("\nError: OAuth timed out. Please try again.");
152
- process.exit(1);
114
+ deps.console.error("\nError: OAuth timed out. Please try again.");
115
+ deps.process.exit(1);
153
116
  }
154
117
  // Get updated status
155
- const statusRes = await platformFetch("/discord/status");
118
+ const statusRes = await deps.platformFetch("/discord/status");
156
119
  const status = (await statusRes.json());
157
- console.log(`\nConnected as ${status.discordUser?.username}!`);
120
+ deps.console.log(`\nConnected as ${status.discordUser?.username}!`);
158
121
  if (status.servers.length > 0) {
159
- console.log(`Fling Bot joined: ${status.servers.map((s) => s.name).join(", ")}`);
122
+ deps.console.log(`Fling Bot joined: ${status.servers.map((s) => s.name).join(", ")}`);
160
123
  }
161
- console.log("\nTip: You can rename the bot in Server Settings > Members > Right-click Fling Bot > Change Nickname");
162
- console.log("\nTo add more servers:");
163
- console.log(" fling plugin discord add-server");
164
- });
124
+ deps.console.log("\nTip: You can rename the bot in Server Settings > Members > Right-click Fling Bot > Change Nickname");
125
+ deps.console.log("\nTo add more servers:");
126
+ deps.console.log(" fling plugin discord add-server");
127
+ }
128
+ export const pluginCommand = new Command("plugin")
129
+ .description("Manage platform plugins")
130
+ .addHelpText("after", `
131
+ Available plugins:
132
+ discord Discord chatops integration
133
+
134
+ Examples:
135
+ fling plugin install discord Install and configure Discord
136
+ fling plugin permissions discord Show Discord permissions/status
137
+ fling plugin remove discord Disconnect Discord
138
+ `);
165
139
  // =============================================================================
166
- // Permissions Command
140
+ // Install Command
167
141
  // =============================================================================
168
142
  pluginCommand
169
- .command("permissions <plugin>")
170
- .description("Show plugin permissions and status")
143
+ .command("install <plugin>")
144
+ .description("Install and configure a plugin")
171
145
  .addHelpText("after", `
146
+ Supported plugins:
147
+ discord Connect Discord for chatops
148
+
172
149
  Examples:
173
- fling plugin permissions discord Show Discord connection status
150
+ fling plugin install discord OAuth with Discord, add bot to server
174
151
  `)
175
152
  .action(async (plugin) => {
176
- requireLogin();
177
- requireValidPlugin(plugin);
153
+ await runPluginInstall(plugin);
154
+ });
155
+ // =============================================================================
156
+ // Permissions Command
157
+ // =============================================================================
158
+ /**
159
+ * Run the plugin permissions command.
160
+ * Exported for testing with injected dependencies.
161
+ */
162
+ export async function runPluginPermissions(plugin, deps = defaultPluginDeps) {
163
+ requireLogin(deps);
164
+ requireValidPlugin(plugin, deps);
178
165
  try {
179
- const response = await platformFetch("/discord/status");
166
+ const response = await deps.platformFetch("/discord/status");
180
167
  if (!response.ok) {
181
168
  if (response.status === 503) {
182
- console.error("Discord plugin is not configured on the platform.");
183
- process.exit(1);
169
+ deps.console.error("Discord plugin is not configured on the platform.");
170
+ deps.process.exit(1);
184
171
  }
185
172
  const data = (await response.json());
186
- console.error(`Error: ${data.error ?? "Failed to get status"}`);
187
- process.exit(1);
173
+ deps.console.error(`Error: ${data.error ?? "Failed to get status"}`);
174
+ deps.process.exit(1);
188
175
  }
189
176
  const status = (await response.json());
190
- console.log("Discord Plugin");
191
- console.log("──────────────");
192
- console.log(`Status: ${status.connected ? "Connected" : "Not connected"}`);
177
+ deps.console.log("Discord Plugin");
178
+ deps.console.log("──────────────");
179
+ deps.console.log(`Status: ${status.connected ? "Connected" : "Not connected"}`);
193
180
  if (status.discordUser) {
194
- console.log(`Discord User: ${status.discordUser.username}`);
181
+ deps.console.log(`Discord User: ${status.discordUser.username}`);
195
182
  }
196
- console.log("");
183
+ deps.console.log("");
197
184
  if (status.servers.length > 0) {
198
- console.log("Claimed Servers:");
185
+ deps.console.log("Claimed Servers:");
199
186
  for (const server of status.servers) {
200
187
  const claimedDate = new Date(server.claimedAt).toLocaleDateString();
201
- console.log(` • ${server.name} (ID: ${server.id})`);
202
- console.log(` - Claimed: ${claimedDate}`);
188
+ deps.console.log(` • ${server.name} (ID: ${server.id})`);
189
+ deps.console.log(` - Claimed: ${claimedDate}`);
203
190
  }
204
191
  }
205
192
  else {
206
- console.log("Claimed Servers: (none)");
207
- console.log(" Run 'fling plugin discord add-server' to claim a server.");
193
+ deps.console.log("Claimed Servers: (none)");
194
+ deps.console.log(" Run 'fling plugin discord add-server' to claim a server.");
208
195
  }
209
- console.log("");
210
- console.log("Available APIs:");
211
- console.log(" discord.sendMessage()");
212
- console.log(" discord.editMessage()");
213
- console.log(" discord.addReaction()");
214
- console.log(" discord.replyToInteraction()");
215
- console.log(" discord.verifyInteraction()");
196
+ deps.console.log("");
197
+ deps.console.log("Available APIs:");
198
+ deps.console.log(" discord.sendMessage()");
199
+ deps.console.log(" discord.editMessage()");
200
+ deps.console.log(" discord.addReaction()");
201
+ deps.console.log(" discord.replyToInteraction()");
202
+ deps.console.log(" discord.verifyInteraction()");
216
203
  }
217
204
  catch (error) {
218
- console.error(`Error: ${error instanceof Error ? error.message : "Failed to get plugin status"}`);
219
- process.exit(1);
205
+ deps.console.error(`Error: ${error instanceof Error ? error.message : "Failed to get plugin status"}`);
206
+ deps.process.exit(1);
220
207
  }
221
- });
222
- // =============================================================================
223
- // Remove Command
224
- // =============================================================================
208
+ }
225
209
  pluginCommand
226
- .command("remove <plugin>")
227
- .description("Disconnect and remove a plugin")
210
+ .command("permissions <plugin>")
211
+ .description("Show plugin permissions and status")
228
212
  .addHelpText("after", `
229
213
  Examples:
230
- fling plugin remove discord Disconnect Discord, release all servers
214
+ fling plugin permissions discord Show Discord connection status
231
215
  `)
232
216
  .action(async (plugin) => {
233
- requireLogin();
234
- requireValidPlugin(plugin);
217
+ await runPluginPermissions(plugin);
218
+ });
219
+ // =============================================================================
220
+ // Remove Command
221
+ // =============================================================================
222
+ /**
223
+ * Run the plugin remove command.
224
+ * Exported for testing with injected dependencies.
225
+ */
226
+ export async function runPluginRemove(plugin, deps = defaultPluginDeps) {
227
+ requireLogin(deps);
228
+ requireValidPlugin(plugin, deps);
235
229
  // Get current status
236
- const statusRes = await platformFetch("/discord/status");
230
+ const statusRes = await deps.platformFetch("/discord/status");
237
231
  if (!statusRes.ok) {
238
- console.log("Discord plugin is not connected.");
232
+ deps.console.log("Discord plugin is not connected.");
239
233
  return;
240
234
  }
241
235
  const status = (await statusRes.json());
242
236
  if (!status.connected) {
243
- console.log("Discord plugin is not connected.");
237
+ deps.console.log("Discord plugin is not connected.");
244
238
  return;
245
239
  }
246
240
  // Confirm removal
247
241
  if (status.servers.length > 0) {
248
- console.log("This will release the following servers:");
242
+ deps.console.log("This will release the following servers:");
249
243
  for (const server of status.servers) {
250
- console.log(` • ${server.name}`);
244
+ deps.console.log(` • ${server.name}`);
251
245
  }
252
- console.log("");
246
+ deps.console.log("");
253
247
  }
254
- const answer = await prompt("Remove Discord plugin? (y/N) ");
248
+ const answer = await deps.user.prompt("Remove Discord plugin? (y/N) ");
255
249
  if (answer.toLowerCase() !== "y") {
256
- console.log("Cancelled.");
250
+ deps.console.log("Cancelled.");
257
251
  return;
258
252
  }
259
253
  // Release all servers
260
254
  const failedServers = [];
261
255
  for (const server of status.servers) {
262
256
  try {
263
- await platformFetch(`/discord/servers/${server.id}`, {
257
+ await deps.platformFetch(`/discord/servers/${server.id}`, {
264
258
  method: "DELETE",
265
259
  });
266
260
  }
267
261
  catch (err) {
268
- console.error(`Warning: Failed to release server "${server.name}": ${err instanceof Error ? err.message : "Unknown error"}`);
262
+ deps.console.error(`Warning: Failed to release server "${server.name}": ${err instanceof Error ? err.message : "Unknown error"}`);
269
263
  failedServers.push(server.name);
270
264
  }
271
265
  }
272
266
  if (failedServers.length > 0) {
273
- console.log(`\nDiscord plugin removed (${failedServers.length} server(s) could not be released).`);
267
+ deps.console.log(`\nDiscord plugin removed (${failedServers.length} server(s) could not be released).`);
274
268
  }
275
269
  else {
276
- console.log("\nDiscord plugin removed.");
270
+ deps.console.log("\nDiscord plugin removed.");
277
271
  }
278
- console.log("Note: Fling Bot will remain in your Discord servers.");
279
- console.log("You can kick it from Server Settings > Members if desired.");
272
+ deps.console.log("Note: Fling Bot will remain in your Discord servers.");
273
+ deps.console.log("You can kick it from Server Settings > Members if desired.");
274
+ }
275
+ pluginCommand
276
+ .command("remove <plugin>")
277
+ .description("Disconnect and remove a plugin")
278
+ .addHelpText("after", `
279
+ Examples:
280
+ fling plugin remove discord Disconnect Discord, release all servers
281
+ `)
282
+ .action(async (plugin) => {
283
+ await runPluginRemove(plugin);
280
284
  });
281
285
  // =============================================================================
282
286
  // Discord Subcommand
283
287
  // =============================================================================
284
- const discordSubcommand = new Command("discord")
285
- .description("Discord-specific plugin commands");
286
- discordSubcommand
287
- .command("add-server")
288
- .description("Add another Discord server to your project")
289
- .action(async () => {
290
- requireLogin();
288
+ /**
289
+ * Run the discord add-server command.
290
+ * Exported for testing with injected dependencies.
291
+ */
292
+ export async function runDiscordAddServer(deps = defaultPluginDeps) {
293
+ requireLogin(deps);
291
294
  // Check connection status
292
- const statusRes = await platformFetch("/discord/status");
295
+ const statusRes = await deps.platformFetch("/discord/status");
293
296
  if (!statusRes.ok) {
294
- console.error("Error: Discord not connected. Run 'fling plugin install discord' first.");
295
- process.exit(1);
297
+ deps.console.error("Error: Discord not connected. Run 'fling plugin install discord' first.");
298
+ deps.process.exit(1);
296
299
  }
297
300
  const status = (await statusRes.json());
298
301
  if (!status.connected) {
299
- console.error("Error: Discord not connected. Run 'fling plugin install discord' first.");
300
- process.exit(1);
302
+ deps.console.error("Error: Discord not connected. Run 'fling plugin install discord' first.");
303
+ deps.process.exit(1);
301
304
  }
302
305
  // Get available servers
303
- console.log("Fetching available servers...\n");
304
- const serversRes = await platformFetch("/discord/servers");
306
+ deps.console.log("Fetching available servers...\n");
307
+ const serversRes = await deps.platformFetch("/discord/servers");
305
308
  if (!serversRes.ok) {
306
309
  const data = (await serversRes.json());
307
- console.error(`Error: ${data.error ?? "Failed to fetch servers"}`);
308
- process.exit(1);
310
+ deps.console.error(`Error: ${data.error ?? "Failed to fetch servers"}`);
311
+ deps.process.exit(1);
309
312
  }
310
313
  const { servers } = (await serversRes.json());
311
314
  // Filter to unclaimed servers
312
315
  const available = servers.filter((s) => !s.claimed);
313
316
  const alreadyClaimed = servers.filter((s) => s.claimedByMe);
314
317
  if (alreadyClaimed.length > 0) {
315
- console.log("Already claimed by this project:");
318
+ deps.console.log("Already claimed by this project:");
316
319
  for (const s of alreadyClaimed) {
317
- console.log(` • ${s.name}`);
320
+ deps.console.log(` • ${s.name}`);
318
321
  }
319
- console.log("");
322
+ deps.console.log("");
320
323
  }
321
324
  if (available.length === 0) {
322
- console.log("No available servers to claim.");
323
- console.log("\nTo add Fling Bot to a new server:");
324
- console.log(" 1. Go to Discord and open Server Settings > Integrations");
325
- console.log(" 2. Add the Fling Bot application");
326
- console.log(" 3. Run this command again");
325
+ deps.console.log("No available servers to claim.");
326
+ deps.console.log("\nTo add Fling Bot to a new server:");
327
+ deps.console.log(" 1. Go to Discord and open Server Settings > Integrations");
328
+ deps.console.log(" 2. Add the Fling Bot application");
329
+ deps.console.log(" 3. Run this command again");
327
330
  return;
328
331
  }
329
- console.log("Available servers:");
332
+ deps.console.log("Available servers:");
330
333
  for (let i = 0; i < available.length; i++) {
331
334
  const s = available[i];
332
335
  if (s) {
333
- console.log(` ${i + 1}. ${s.name}`);
336
+ deps.console.log(` ${i + 1}. ${s.name}`);
334
337
  }
335
338
  }
336
- console.log("");
337
- const choice = await prompt(`Select server (1-${available.length}): `);
339
+ deps.console.log("");
340
+ const choice = await deps.user.prompt(`Select server (1-${available.length}): `);
338
341
  const idx = parseInt(choice, 10) - 1;
339
342
  const server = available[idx];
340
343
  if (!server) {
341
- console.error("Invalid selection.");
342
- process.exit(1);
344
+ deps.console.error("Invalid selection.");
345
+ deps.process.exit(1);
343
346
  }
344
347
  // Claim the server
345
- const claimRes = await platformFetch(`/discord/servers/${server.id}/claim`, {
348
+ const claimRes = await deps.platformFetch(`/discord/servers/${server.id}/claim`, {
346
349
  method: "POST",
347
350
  headers: { "Content-Type": "application/json" },
348
351
  body: JSON.stringify({ guildName: server.name }),
349
352
  });
350
353
  if (!claimRes.ok) {
351
354
  const data = (await claimRes.json());
352
- console.error(`Error: ${data.error ?? "Failed to claim server"}`);
353
- process.exit(1);
355
+ deps.console.error(`Error: ${data.error ?? "Failed to claim server"}`);
356
+ deps.process.exit(1);
354
357
  }
355
- console.log(`\nServer "${server.name}" claimed successfully!`);
356
- console.log("\nYour slash commands will be registered when you run 'fling push'.");
357
- });
358
- discordSubcommand
359
- .command("remove-server")
360
- .description("Remove a Discord server from your project")
361
- .action(async () => {
362
- requireLogin();
358
+ deps.console.log(`\nServer "${server.name}" claimed successfully!`);
359
+ deps.console.log("\nYour slash commands will be registered when you run 'fling push'.");
360
+ }
361
+ /**
362
+ * Run the discord remove-server command.
363
+ * Exported for testing with injected dependencies.
364
+ */
365
+ export async function runDiscordRemoveServer(deps = defaultPluginDeps) {
366
+ requireLogin(deps);
363
367
  // Get current status
364
- const statusRes = await platformFetch("/discord/status");
368
+ const statusRes = await deps.platformFetch("/discord/status");
365
369
  if (!statusRes.ok) {
366
- console.error("Error: Discord not connected.");
367
- process.exit(1);
370
+ deps.console.error("Error: Discord not connected.");
371
+ deps.process.exit(1);
368
372
  }
369
373
  const status = (await statusRes.json());
370
374
  if (status.servers.length === 0) {
371
- console.log("No servers claimed.");
375
+ deps.console.log("No servers claimed.");
372
376
  return;
373
377
  }
374
- console.log("Claimed servers:");
378
+ deps.console.log("Claimed servers:");
375
379
  for (let i = 0; i < status.servers.length; i++) {
376
380
  const s = status.servers[i];
377
381
  if (s) {
378
- console.log(` ${i + 1}. ${s.name}`);
382
+ deps.console.log(` ${i + 1}. ${s.name}`);
379
383
  }
380
384
  }
381
- console.log("");
382
- const choice = await prompt(`Select server to remove (1-${status.servers.length}): `);
385
+ deps.console.log("");
386
+ const choice = await deps.user.prompt(`Select server to remove (1-${status.servers.length}): `);
383
387
  const idx = parseInt(choice, 10) - 1;
384
388
  const server = status.servers[idx];
385
389
  if (!server) {
386
- console.error("Invalid selection.");
387
- process.exit(1);
390
+ deps.console.error("Invalid selection.");
391
+ deps.process.exit(1);
388
392
  }
389
- const confirm = await prompt(`Remove "${server.name}"? (y/N) `);
393
+ const confirm = await deps.user.prompt(`Remove "${server.name}"? (y/N) `);
390
394
  if (confirm.toLowerCase() !== "y") {
391
- console.log("Cancelled.");
395
+ deps.console.log("Cancelled.");
392
396
  return;
393
397
  }
394
398
  // Remove the server
395
- const removeRes = await platformFetch(`/discord/servers/${server.id}`, {
399
+ const removeRes = await deps.platformFetch(`/discord/servers/${server.id}`, {
396
400
  method: "DELETE",
397
401
  });
398
402
  if (!removeRes.ok) {
399
403
  const data = (await removeRes.json());
400
- console.error(`Error: ${data.error ?? "Failed to remove server"}`);
401
- process.exit(1);
404
+ deps.console.error(`Error: ${data.error ?? "Failed to remove server"}`);
405
+ deps.process.exit(1);
402
406
  }
403
- console.log(`\nServer "${server.name}" removed.`);
404
- console.log("Slash commands have been unregistered from this server.");
407
+ deps.console.log(`\nServer "${server.name}" removed.`);
408
+ deps.console.log("Slash commands have been unregistered from this server.");
409
+ }
410
+ const discordSubcommand = new Command("discord")
411
+ .description("Discord-specific plugin commands");
412
+ discordSubcommand
413
+ .command("add-server")
414
+ .description("Add another Discord server to your project")
415
+ .action(async () => {
416
+ await runDiscordAddServer();
417
+ });
418
+ discordSubcommand
419
+ .command("remove-server")
420
+ .description("Remove a Discord server from your project")
421
+ .action(async () => {
422
+ await runDiscordRemoveServer();
405
423
  });
406
424
  pluginCommand.addCommand(discordSubcommand);
407
425
  //# sourceMappingURL=plugin.js.map