Skip to content
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,28 @@ bun run index.ts --client-id 123456789
bun run index.ts --help
```

> [!NOTE]
> **Current Implementation Status**
>
> - `discord-search --help`: Shows help message ✓
> - `discord-search --version`: Shows version number ✓
> - Interactive mode: Not yet implemented (see `src/index.ts`)
> - Search command: Not yet implemented (see `src/index.ts`)
> - Preset command: Not yet implemented (see `src/index.ts`)
> - Settings command: Not yet implemented (see `src/index.ts`)
>
> Refer to `src/index.ts` for the current implementation status of each command.

Interactive mode
The CLI guides you through setting up searches:
1. Choose "New search" to start
2. Enter your Guild ID (server ID)
3. Optionally filter by content, author, mentions, or content types
4. Browse results or export them

> [!WARNING]
> Interactive mode and all subcommands (search, preset, settings) are currently unimplemented. Running these will display an error message and exit with code 1. Only `--help` and `--version` are functional in this release.

**Search filters**
- Content text search
- Author IDs (comma-separated)
Expand Down
47 changes: 35 additions & 12 deletions __tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,36 @@
/**
* Placeholder test file
*
* This file exists to ensure CI passes during infrastructure setup.
* Actual tests will be added in subsequent PRs.
*/

import { test } from "bun:test";

test("placeholder", () => {
// This is a placeholder test to make CI pass
// Real tests will be added as functionality is developed
import { expect, test } from "bun:test";
import { parseArgs } from "@/cli/args-parse.ts";
import type { HelpArgs } from "@/cli/args-types.ts";

test("global --help returns help command", () => {
const args = parseArgs(["--help"]);
expect(args.command).toBe("help");
expect((args as HelpArgs).help).toBe(true);
});

test("search --help returns help command with targetCommand search", () => {
const args = parseArgs(["search", "--help"]);
expect(args.command).toBe("help");
const helpArgs = args as HelpArgs;
expect(helpArgs.targetCommand).toBe("search");
expect(helpArgs.help).toBe(true);
});

test("search --help with --guild still returns help command", () => {
const args = parseArgs(["search", "--help", "--guild", "123"]);
expect(args.command).toBe("help");
const helpArgs = args as HelpArgs;
expect(helpArgs.targetCommand).toBe("search");
expect(helpArgs.help).toBe(true);
});

test("no subcommand without --help returns interactive command", () => {
const args = parseArgs([]);
expect(args.command).toBe("interactive");
});

test("no subcommand with --help returns help command (not interactive)", () => {
const args = parseArgs(["--help"]);
expect(args.command).toBe("help");
expect(args.command).not.toBe("interactive");
});
2 changes: 2 additions & 0 deletions src/cli/args-help.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ const GLOBAL_OPTIONS = section("GLOBAL OPTIONS", [
opt("--help, -h", "Show this help message"),
opt("--version, -v", "Show version number"),
opt("--token, -t <token>", "Bot token (overrides DISCORD_BOT_TOKEN)"),
opt("--guild, -g <id>", "Default guild/server ID"),
opt("--client-id, -c <id>", "Bot client ID (for invite link)"),
]);

const SEARCH_FILTERING = section("FILTERING", [
Expand Down
79 changes: 69 additions & 10 deletions src/cli/args-parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import {
type PresetSaveArgs,
type SearchArgs,
} from "@/cli/args-types.ts";
import { parseCommaSeparated } from "@/cli/utils.ts";
import type { SearchParams } from "@/discord/schemas.ts";
import { INTEGER_REGEX, parseCommaSeparated } from "@/cli/utils.ts";
import { type SearchParams, SNOWFLAKE_REGEX } from "@/discord/schemas.ts";

const parseWithError = <T>(data: unknown, subcommand: string): T => {
const result = ParsedArgsSchema.safeParse(data);
Expand Down Expand Up @@ -107,8 +107,6 @@ const BOOLEAN_FLAGS: Record<string, keyof SearchParams> = {
"--mention-everyone": "mentionEveryone",
};

const INTEGER_REGEX = /^\d+$/;

type SearchFlagsResult = {
params: Omit<SearchParams, "guildId"> & { guildId?: string };
export?: string;
Expand Down Expand Up @@ -313,26 +311,39 @@ const parseSearchCommand = (
global: GlobalFlags & { guild?: string }
): SearchArgs | HelpArgs => {
const guildId = global.guild;
if (!(global.help || guildId)) {
return exitWithError(
"Missing required --guild/-g (guildId) for search command",
if (global.help) {
return parseWithError<HelpArgs>(
{
command: "help",
targetCommand: "search",
help: true,
version: global.version,
token: global.token,
},
"search"
);
}

if (global.help && !guildId) {
if (global.version) {
return parseWithError<HelpArgs>(
{
command: "help",
targetCommand: "search",
help: true,
version: global.version,
help: global.help,
version: true,
token: global.token,
},
"search"
);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

if (!guildId) {
return exitWithError(
"Missing required --guild/-g (guildId) for search command",
"search"
);
}

const parsed = parseSearchFlags(remaining.slice(1), guildId, "search");
return parseWithError<SearchArgs>(
{
Expand Down Expand Up @@ -395,6 +406,13 @@ const parsePresetRunAllAction = (
}
}

if (all && names.length > 0) {
exitWithError(
"Cannot specify both --all and named presets",
"preset run-all"
);
}

if (!all && names.length === 0) {
exitWithError("You must pass --all or at least one preset name", "preset");
}
Expand Down Expand Up @@ -478,6 +496,19 @@ const parsePresetCommand = (
);
}

if (global.version) {
return parseWithError<ParsedArgs>(
{
command: "preset",
action: "list",
help: global.help,
version: true,
token: global.token,
},
"preset"
);
}
Comment thread
mynameistito marked this conversation as resolved.

const action = remaining[1];

if (action === "list") {
Expand Down Expand Up @@ -551,6 +582,19 @@ const parseSettingsCommand = (
);
}

if (global.version) {
return parseWithError<ParsedArgs>(
{
command: "settings",
action: "show",
help: global.help,
version: true,
token: global.token,
},
"settings"
);
}

const action = remaining[1];

if (action === "show") {
Expand Down Expand Up @@ -583,6 +627,15 @@ const parseSettingsCommand = (
"settings"
);
}
if (
(key === "guild" || key === "client-id") &&
!SNOWFLAKE_REGEX.test(value)
) {
return exitWithError(
`Invalid value for ${key}: must be a 17-20 digit snowflake ID`,
"settings set"
);
}
checkNoLeftovers(remaining.slice(4), "settings set");
return parseWithError<ParsedArgs>(
{
Expand Down Expand Up @@ -623,6 +676,12 @@ export const parseArgs = (args: string[]): ParsedArgs => {
const subcommand = remaining[0];

if (!subcommand) {
if (global.help) {
return parseWithError<ParsedArgs>(
{ command: "help", ...global },
"interactive"
);
}
return parseWithError<ParsedArgs>(
{ command: "interactive", ...global },
"interactive"
Expand Down
Loading
Loading