UNPKG

ccremote

Version:

Claude Code Remote: approve prompts from Discord, auto-continue sessions after quota resets, and schedule quota windows around your workday.

1,458 lines (1,447 loc) 175 kB
#!/usr/bin/env node import { n as __toESM, t as __commonJS } from "./chunk-CVmoYqME.js"; import { i as DiscordBot, n as SessionManager, r as generateQuotaMessage, t as TmuxManager } from "./tmux-D_WriM83.js"; import { i as getSessionLogPath } from "./paths-CZvcxIsd.js"; import { n as validateConfig, t as loadConfig } from "./config-dUyvuHWL.js"; import { accessSync, constants, existsSync, promises, readFileSync, writeFileSync } from "node:fs"; import { basename, dirname, join } from "node:path"; import process$1, { stdin, stdout } from "node:process"; import consola, { consola as consola$1 } from "consola"; import { homedir } from "node:os"; import { exec, spawn } from "node:child_process"; import { promisify, stripVTControlCharacters } from "node:util"; import { fileURLToPath } from "node:url"; import updateNotifier from "update-notifier"; import * as g from "node:readline"; import O from "node:readline"; import { Writable } from "node:stream"; //#region node_modules/gunshi/lib/utils-D41C8Abf.js /** * The default locale string, which format is BCP 47 language tag. */ const DEFAULT_LOCALE = "en-US"; const BUILT_IN_PREFIX = "_"; const ARG_PREFIX = "arg"; const BUILT_IN_KEY_SEPARATOR = ":"; const ANONYMOUS_COMMAND_NAME = "(anonymous)"; const NOOP = () => {}; const COMMON_ARGS = { help: { type: "boolean", short: "h", description: "Display this help message" }, version: { type: "boolean", short: "v", description: "Display this version" } }; const COMMAND_OPTIONS_DEFAULT = { name: void 0, description: void 0, version: void 0, cwd: void 0, usageSilent: false, subCommands: void 0, leftMargin: 2, middleMargin: 10, usageOptionType: false, usageOptionValue: true, renderHeader: void 0, renderUsage: void 0, renderValidationErrors: void 0, translationAdapterFactory: void 0 }; function isLazyCommand(cmd) { return typeof cmd === "function" && "commandName" in cmd && !!cmd.commandName; } async function resolveLazyCommand(cmd, name$1, needRunResolving = false) { let command; if (isLazyCommand(cmd)) { command = Object.assign(create(), { name: cmd.commandName, description: cmd.description, args: cmd.args, examples: cmd.examples, resource: cmd.resource }); if (needRunResolving) { const loaded = await cmd(); if (typeof loaded === "function") command.run = loaded; else if (typeof loaded === "object") { if (loaded.run == null) throw new TypeError(`'run' is required in command: ${cmd.name || name$1}`); command.run = loaded.run; command.name = loaded.name; command.description = loaded.description; command.args = loaded.args; command.examples = loaded.examples; command.resource = loaded.resource; } else throw new TypeError(`Cannot resolve command: ${cmd.name || name$1}`); } } else command = Object.assign(create(), cmd); if (command.name == null && name$1) command.name = name$1; return deepFreeze(command); } function resolveBuiltInKey(key) { return `${BUILT_IN_PREFIX}${BUILT_IN_KEY_SEPARATOR}${key}`; } function resolveArgKey(key) { return `${ARG_PREFIX}${BUILT_IN_KEY_SEPARATOR}${key}`; } async function resolveExamples(ctx, examples) { return typeof examples === "string" ? examples : typeof examples === "function" ? await examples(ctx) : ""; } function mapResourceWithBuiltinKey(resource) { return Object.entries(resource).reduce((acc, [key, value]) => { acc[resolveBuiltInKey(key)] = value; return acc; }, create()); } function create(obj = null) { return Object.create(obj); } function log(...args) { console.log(...args); } function deepFreeze(obj) { if (obj === null || typeof obj !== "object") return obj; for (const key of Object.keys(obj)) { const value = obj[key]; if (typeof value === "object" && value !== null) deepFreeze(value); } return Object.freeze(obj); } //#endregion //#region node_modules/gunshi/lib/context-D_EmfRNA.js var en_US_default = { COMMAND: "COMMAND", COMMANDS: "COMMANDS", SUBCOMMAND: "SUBCOMMAND", USAGE: "USAGE", ARGUMENTS: "ARGUMENTS", OPTIONS: "OPTIONS", EXAMPLES: "EXAMPLES", FORMORE: "For more info, run any command with the `--help` flag:", NEGATABLE: "Negatable of", DEFAULT: "default", CHOICES: "choices", help: "Display this help message", version: "Display this version" }; function createTranslationAdapter(options) { return new DefaultTranslation(options); } var DefaultTranslation = class { #resources = /* @__PURE__ */ new Map(); #options; constructor(options) { this.#options = options; this.#resources.set(options.locale, create()); if (options.locale !== options.fallbackLocale) this.#resources.set(options.fallbackLocale, create()); } getResource(locale) { return this.#resources.get(locale); } setResource(locale, resource) { this.#resources.set(locale, resource); } getMessage(locale, key) { const resource = this.getResource(locale); if (resource) return resource[key]; } translate(locale, key, values = create()) { let message = this.getMessage(locale, key); if (message === void 0 && locale !== this.#options.fallbackLocale) message = this.getMessage(this.#options.fallbackLocale, key); if (message === void 0) return; return message.replaceAll(/\{\{(\w+)\}\}/g, (_$2, name$1) => { return values[name$1] == null ? "" : values[name$1].toString(); }); } }; const BUILT_IN_PREFIX_CODE = BUILT_IN_PREFIX.codePointAt(0); /** * Create a {@link CommandContext | command context} * @param param A {@link CommandContextParams | parameters} to create a {@link CommandContext | command context} * @returns A {@link CommandContext | command context}, which is readonly */ async function createCommandContext({ args, values, positionals, rest, argv: argv$1, tokens, command, cliOptions, callMode = "entry", omitted = false }) { /** * normailize the options schema and values, to avoid prototype pollution */ const _args = Object.entries(args).reduce((acc, [key, value]) => { acc[key] = Object.assign(create(), value); return acc; }, create()); /** * setup the environment */ const env$1 = Object.assign(create(), COMMAND_OPTIONS_DEFAULT, cliOptions); const locale = resolveLocale(cliOptions.locale); const localeStr = locale.toString(); const adapter = (cliOptions.translationAdapterFactory || createTranslationAdapter)({ locale: localeStr, fallbackLocale: DEFAULT_LOCALE }); const localeResources = /* @__PURE__ */ new Map(); let builtInLoadedResources; /** * load the built-in locale resources */ localeResources.set(DEFAULT_LOCALE, mapResourceWithBuiltinKey(en_US_default)); if (DEFAULT_LOCALE !== localeStr) try { builtInLoadedResources = (await import(`./locales/${localeStr}.json`, { with: { type: "json" } })).default; localeResources.set(localeStr, mapResourceWithBuiltinKey(builtInLoadedResources)); } catch {} /** * define the translation function, which is used to {@link CommandContext.translate}. * */ function translate(key, values$1 = create()) { const strKey = key; if (strKey.codePointAt(0) === BUILT_IN_PREFIX_CODE) return (localeResources.get(localeStr) || localeResources.get(DEFAULT_LOCALE))[strKey] || strKey; else return adapter.translate(locale.toString(), strKey, values$1) || ""; } /** * load the sub commands */ let cachedCommands; async function loadCommands() { if (cachedCommands) return cachedCommands; const subCommands$1 = [...cliOptions.subCommands || []]; return cachedCommands = await Promise.all(subCommands$1.map(async ([name$1, cmd]) => await resolveLazyCommand(cmd, name$1))); } /** * create the context */ const ctx = deepFreeze(Object.assign(create(), { name: getCommandName(command), description: command.description, omitted, callMode, locale, env: env$1, args: _args, values, positionals, rest, _: argv$1, tokens, toKebab: command.toKebab, log: cliOptions.usageSilent ? NOOP : log, loadCommands, translate })); const defaultCommandResource = Object.entries(args).map(([key, arg]) => { return [key, arg.description || ""]; }).reduce((res, [key, value]) => { res[resolveArgKey(key)] = value; return res; }, create()); defaultCommandResource.description = command.description || ""; defaultCommandResource.examples = await resolveExamples(ctx, command.examples); adapter.setResource(DEFAULT_LOCALE, defaultCommandResource); const originalResource = await loadCommandResource(ctx, command); if (originalResource) { const resource = Object.assign(create(), originalResource, { examples: await resolveExamples(ctx, originalResource.examples) }); if (builtInLoadedResources) { resource.help = builtInLoadedResources.help; resource.version = builtInLoadedResources.version; } adapter.setResource(localeStr, resource); } return ctx; } function getCommandName(cmd) { if (isLazyCommand(cmd)) return cmd.commandName || cmd.name || ANONYMOUS_COMMAND_NAME; else if (typeof cmd === "object") return cmd.name || ANONYMOUS_COMMAND_NAME; else return ANONYMOUS_COMMAND_NAME; } function resolveLocale(locale) { return locale instanceof Intl.Locale ? locale : typeof locale === "string" ? new Intl.Locale(locale) : new Intl.Locale(DEFAULT_LOCALE); } async function loadCommandResource(ctx, command) { let resource; try { resource = await command.resource?.(ctx); } catch {} return resource; } //#endregion //#region node_modules/gunshi/lib/definition-wq1Kmbvq.js /** * Define a {@link Command | command} with type inference * @param definition A {@link Command | command} definition * @returns A {@link Command | command} definition with type inference */ function define(definition) { return definition; } //#endregion //#region node_modules/args-tokens/lib/utils-N7UlhLbz.js /** * Entry point of utils. * * Note that this entry point is used by gunshi to import utility functions. * * @module */ /** * @author kazuya kawaguchi (a.k.a. kazupon) * @license MIT */ function kebabnize(str) { return str.replace(/[A-Z]/g, (match, offset) => (offset > 0 ? "-" : "") + match.toLowerCase()); } //#endregion //#region node_modules/gunshi/lib/renderer-BzRfaLdJ.js /** * Render the header. * @param ctx A {@link CommandContext | command context} * @returns A rendered header. */ function renderHeader(ctx) { const title = ctx.env.description || ctx.env.name || ""; return Promise.resolve(title ? `${title} (${ctx.env.name || ""}${ctx.env.version ? ` v${ctx.env.version}` : ""})` : title); } const COMMON_ARGS_KEYS = Object.keys(COMMON_ARGS); /** * Render the usage. * @param ctx A {@link CommandContext | command context} * @returns A rendered usage. */ async function renderUsage(ctx) { const messages = []; if (!ctx.omitted) { const description$1 = resolveDescription(ctx); if (description$1) messages.push(description$1, ""); } messages.push(...await renderUsageSection(ctx), ""); if (ctx.omitted && await hasCommands(ctx)) messages.push(...await renderCommandsSection(ctx), ""); if (hasPositionalArgs(ctx)) messages.push(...await renderPositionalArgsSection(ctx), ""); if (hasOptionalArgs(ctx)) messages.push(...await renderOptionalArgsSection(ctx), ""); const examples = await renderExamplesSection(ctx); if (examples.length > 0) messages.push(...examples, ""); return messages.join("\n"); } /** * Render the positional arguments section * @param ctx A {@link CommandContext | command context} * @returns A rendered arguments section */ async function renderPositionalArgsSection(ctx) { const messages = []; messages.push(`${ctx.translate(resolveBuiltInKey("ARGUMENTS"))}:`); messages.push(await generatePositionalArgsUsage(ctx)); return messages; } /** * Render the optional arguments section * @param ctx A {@link CommandContext | command context} * @returns A rendered options section */ async function renderOptionalArgsSection(ctx) { const messages = []; messages.push(`${ctx.translate(resolveBuiltInKey("OPTIONS"))}:`); messages.push(await generateOptionalArgsUsage(ctx, getOptionalArgsPairs(ctx))); return messages; } /** * Render the examples section * @param ctx A {@link CommandContext | command context} * @returns A rendered examples section */ async function renderExamplesSection(ctx) { const messages = []; const resolvedExamples = await resolveExamples$1(ctx); if (resolvedExamples) { const examples = resolvedExamples.split("\n").map((example) => example.padStart(ctx.env.leftMargin + example.length)); messages.push(`${ctx.translate(resolveBuiltInKey("EXAMPLES"))}:`, ...examples); } return messages; } /** * Render the usage section * @param ctx A {@link CommandContext | command context} * @returns A rendered usage section */ async function renderUsageSection(ctx) { const messages = [`${ctx.translate(resolveBuiltInKey("USAGE"))}:`]; if (ctx.omitted) { const defaultCommand = `${resolveEntry(ctx)}${await hasCommands(ctx) ? ` [${resolveSubCommand(ctx)}]` : ""} ${[generateOptionsSymbols(ctx), generatePositionalSymbols(ctx)].filter(Boolean).join(" ")}`; messages.push(defaultCommand.padStart(ctx.env.leftMargin + defaultCommand.length)); if (await hasCommands(ctx)) { const commandsUsage = `${resolveEntry(ctx)} <${ctx.translate(resolveBuiltInKey("COMMANDS"))}>`; messages.push(commandsUsage.padStart(ctx.env.leftMargin + commandsUsage.length)); } } else { const usageStr = `${resolveEntry(ctx)} ${resolveSubCommand(ctx)} ${[generateOptionsSymbols(ctx), generatePositionalSymbols(ctx)].filter(Boolean).join(" ")}`; messages.push(usageStr.padStart(ctx.env.leftMargin + usageStr.length)); } return messages; } /** * Render the commands section * @param ctx A {@link CommandContext | command context} * @returns A rendered commands section */ async function renderCommandsSection(ctx) { const messages = [`${ctx.translate(resolveBuiltInKey("COMMANDS"))}:`]; const loadedCommands = await ctx.loadCommands(); const commandMaxLength = Math.max(...loadedCommands.map((cmd) => (cmd.name || "").length)); const commandsStr = await Promise.all(loadedCommands.map((cmd) => { const key = cmd.name || ""; const desc = cmd.description || ""; const command = `${key.padEnd(commandMaxLength + ctx.env.middleMargin)}${desc} `; return `${command.padStart(ctx.env.leftMargin + command.length)} `; })); messages.push(...commandsStr, "", ctx.translate(resolveBuiltInKey("FORMORE"))); messages.push(...loadedCommands.map((cmd) => { const commandHelp = `${ctx.env.name} ${cmd.name} --help`; return `${commandHelp.padStart(ctx.env.leftMargin + commandHelp.length)}`; })); return messages; } /** * Resolve the entry command name * @param ctx A {@link CommandContext | command context} * @returns The entry command name */ function resolveEntry(ctx) { return ctx.env.name || ctx.translate(resolveBuiltInKey("COMMAND")); } /** * Resolve the sub command name * @param ctx A {@link CommandContext | command context} * @returns The sub command name */ function resolveSubCommand(ctx) { return ctx.name || ctx.translate(resolveBuiltInKey("SUBCOMMAND")); } /** * Resolve the command description * @param ctx A {@link CommandContext | command context} * @returns resolved command description */ function resolveDescription(ctx) { return ctx.translate("description") || ctx.description || ""; } /** * Resolve the command examples * @param ctx A {@link CommandContext | command context} * @returns resolved command examples, if not resolved, return empty string */ async function resolveExamples$1(ctx) { const ret = ctx.translate("examples"); if (ret) return ret; const command = ctx.env.subCommands?.get(ctx.name || ""); return await resolveExamples(ctx, command?.examples); } /** * Check if the command has sub commands * @param ctx A {@link CommandContext | command context} * @returns True if the command has sub commands */ async function hasCommands(ctx) { return (await ctx.loadCommands()).length > 1; } /** * Check if the command has optional arguments * @param ctx A {@link CommandContext | command context} * @returns True if the command has options */ function hasOptionalArgs(ctx) { return !!(ctx.args && Object.values(ctx.args).some((arg) => arg.type !== "positional")); } /** * Check if the command has positional arguments * @param ctx A {@link CommandContext | command context} * @returns True if the command has options */ function hasPositionalArgs(ctx) { return !!(ctx.args && Object.values(ctx.args).some((arg) => arg.type === "positional")); } /** * Check if all options have default values * @param ctx A {@link CommandContext | command context} * @returns True if all options have default values */ function hasAllDefaultOptions(ctx) { return !!(ctx.args && Object.values(ctx.args).every((arg) => arg.default)); } /** * Generate options symbols for usage * @param ctx A {@link CommandContext | command context} * @returns Options symbols for usage */ function generateOptionsSymbols(ctx) { return hasOptionalArgs(ctx) ? hasAllDefaultOptions(ctx) ? `[${ctx.translate(resolveBuiltInKey("OPTIONS"))}]` : `<${ctx.translate(resolveBuiltInKey("OPTIONS"))}>` : ""; } function makeShortLongOptionPair(schema, name$1, toKebab) { let key = `--${toKebab || schema.toKebab ? kebabnize(name$1) : name$1}`; if (schema.short) key = `-${schema.short}, ${key}`; return key; } /** * Get optional arguments pairs for usage * @param ctx A {@link CommandContext | command context} * @returns Options pairs for usage */ function getOptionalArgsPairs(ctx) { return Object.entries(ctx.args).reduce((acc, [name$1, schema]) => { if (schema.type === "positional") return acc; let key = makeShortLongOptionPair(schema, name$1, ctx.toKebab); if (schema.type !== "boolean") { const displayName = ctx.toKebab || schema.toKebab ? kebabnize(name$1) : name$1; key = schema.default ? `${key} [${displayName}]` : `${key} <${displayName}>`; } acc[name$1] = key; if (schema.type === "boolean" && schema.negatable && !COMMON_ARGS_KEYS.includes(name$1)) { const displayName = ctx.toKebab || schema.toKebab ? kebabnize(name$1) : name$1; acc[`no-${name$1}`] = `--no-${displayName}`; } return acc; }, create()); } const resolveNegatableKey = (key) => key.split("no-")[1]; function resolveNegatableType(key, ctx) { return ctx.args[key.startsWith("no-") ? resolveNegatableKey(key) : key].type; } function generateDefaultDisplayValue(ctx, schema) { return `${ctx.translate(resolveBuiltInKey("DEFAULT"))}: ${schema.default}`; } function resolveDisplayValue(ctx, key) { if (COMMON_ARGS_KEYS.includes(key)) return ""; const schema = ctx.args[key]; if ((schema.type === "boolean" || schema.type === "number" || schema.type === "string" || schema.type === "custom") && schema.default !== void 0) return `(${generateDefaultDisplayValue(ctx, schema)})`; if (schema.type === "enum") { const _default = schema.default !== void 0 ? generateDefaultDisplayValue(ctx, schema) : ""; const choices = `${ctx.translate(resolveBuiltInKey("CHOICES"))}: ${schema.choices.join(" | ")}`; return `(${_default ? `${_default}, ${choices}` : choices})`; } return ""; } /** * Generate optional arguments usage * @param ctx A {@link CommandContext | command context} * @param optionsPairs Options pairs for usage * @returns Generated options usage */ async function generateOptionalArgsUsage(ctx, optionsPairs) { const optionsMaxLength = Math.max(...Object.entries(optionsPairs).map(([_$2, value]) => value.length)); const optionSchemaMaxLength = ctx.env.usageOptionType ? Math.max(...Object.entries(optionsPairs).map(([key]) => resolveNegatableType(key, ctx).length)) : 0; return (await Promise.all(Object.entries(optionsPairs).map(([key, value]) => { let rawDesc = ctx.translate(resolveArgKey(key)); if (!rawDesc && key.startsWith("no-")) { const name$1 = resolveNegatableKey(key); const schema = ctx.args[name$1]; const optionKey = makeShortLongOptionPair(schema, name$1, ctx.toKebab); rawDesc = `${ctx.translate(resolveBuiltInKey("NEGATABLE"))} ${optionKey}`; } const optionsSchema = ctx.env.usageOptionType ? `[${resolveNegatableType(key, ctx)}] ` : ""; const valueDesc = key.startsWith("no-") ? "" : resolveDisplayValue(ctx, key); const desc = `${optionsSchema ? optionsSchema.padEnd(optionSchemaMaxLength + 3) : ""}${rawDesc}`; const option = `${value.padEnd(optionsMaxLength + ctx.env.middleMargin)}${desc}${valueDesc ? ` ${valueDesc}` : ""}`; return `${option.padStart(ctx.env.leftMargin + option.length)}`; }))).join("\n"); } function getPositionalArgs(ctx) { return Object.entries(ctx.args).filter(([_$2, schema]) => schema.type === "positional"); } async function generatePositionalArgsUsage(ctx) { const positionals = getPositionalArgs(ctx); const argsMaxLength = Math.max(...positionals.map(([name$1]) => name$1.length)); return (await Promise.all(positionals.map(([name$1]) => { const desc = ctx.translate(resolveArgKey(name$1)) || ctx.args[name$1].description || ""; const arg = `${name$1.padEnd(argsMaxLength + ctx.env.middleMargin)} ${desc}`; return `${arg.padStart(ctx.env.leftMargin + arg.length)}`; }))).join("\n"); } function generatePositionalSymbols(ctx) { return hasPositionalArgs(ctx) ? getPositionalArgs(ctx).map(([name$1]) => `<${name$1}>`).join(" ") : ""; } /** * Render the validation errors. * @param ctx A {@link CommandContext | command context} * @param error An {@link AggregateError} of option in `args-token` validation * @returns A rendered validation error. */ function renderValidationErrors(_ctx, error) { const messages = []; for (const err of error.errors) messages.push(err.message); return Promise.resolve(messages.join("\n")); } //#endregion //#region node_modules/args-tokens/lib/parser-Dr4iAGaX.js const HYPHEN_CHAR = "-"; const HYPHEN_CODE = HYPHEN_CHAR.codePointAt(0); const EQUAL_CHAR = "="; const EQUAL_CODE = EQUAL_CHAR.codePointAt(0); const TERMINATOR = "--"; const SHORT_OPTION_PREFIX = HYPHEN_CHAR; const LONG_OPTION_PREFIX = "--"; /** * Parse command line arguments. * @example * ```js * import { parseArgs } from 'args-tokens' // for Node.js and Bun * // import { parseArgs } from 'jsr:@kazupon/args-tokens' // for Deno * * const tokens = parseArgs(['--foo', 'bar', '-x', '--bar=baz']) * // do something with using tokens * // ... * console.log('tokens:', tokens) * ``` * @param args command line arguments * @param options parse options * @returns Argument tokens. */ function parseArgs(args, options = {}) { const { allowCompatible = false } = options; const tokens = []; const remainings = [...args]; let index = -1; let groupCount = 0; let hasShortValueSeparator = false; while (remainings.length > 0) { const arg = remainings.shift(); if (arg == void 0) break; const nextArg = remainings[0]; if (groupCount > 0) groupCount--; else index++; if (arg === TERMINATOR) { tokens.push({ kind: "option-terminator", index }); const mapped = remainings.map((arg$1) => { return { kind: "positional", index: ++index, value: arg$1 }; }); tokens.push(...mapped); break; } if (isShortOption(arg)) { const shortOption = arg.charAt(1); let value; let inlineValue; if (groupCount) { tokens.push({ kind: "option", name: shortOption, rawName: arg, index, value, inlineValue }); if (groupCount === 1 && hasOptionValue(nextArg)) { value = remainings.shift(); if (hasShortValueSeparator) { inlineValue = true; hasShortValueSeparator = false; } tokens.push({ kind: "option", index, value, inlineValue }); } } else tokens.push({ kind: "option", name: shortOption, rawName: arg, index, value, inlineValue }); if (value != null) ++index; continue; } if (isShortOptionGroup(arg)) { const expanded = []; let shortValue = ""; for (let i = 1; i < arg.length; i++) { const shortableOption = arg.charAt(i); if (hasShortValueSeparator) shortValue += shortableOption; else if (!allowCompatible && shortableOption.codePointAt(0) === EQUAL_CODE) hasShortValueSeparator = true; else expanded.push(`${SHORT_OPTION_PREFIX}${shortableOption}`); } if (shortValue) expanded.push(shortValue); remainings.unshift(...expanded); groupCount = expanded.length; continue; } if (isLongOption(arg)) { const longOption = arg.slice(2); tokens.push({ kind: "option", name: longOption, rawName: arg, index, value: void 0, inlineValue: void 0 }); continue; } if (isLongOptionAndValue(arg)) { const equalIndex = arg.indexOf(EQUAL_CHAR); const longOption = arg.slice(2, equalIndex); const value = arg.slice(equalIndex + 1); tokens.push({ kind: "option", name: longOption, rawName: `${LONG_OPTION_PREFIX}${longOption}`, index, value, inlineValue: true }); continue; } tokens.push({ kind: "positional", index, value: arg }); } return tokens; } /** * Check if `arg` is a short option (e.g. `-f`). * @param arg the argument to check * @returns whether `arg` is a short option. */ function isShortOption(arg) { return arg.length === 2 && arg.codePointAt(0) === HYPHEN_CODE && arg.codePointAt(1) !== HYPHEN_CODE; } /** * Check if `arg` is a short option group (e.g. `-abc`). * @param arg the argument to check * @returns whether `arg` is a short option group. */ function isShortOptionGroup(arg) { if (arg.length <= 2) return false; if (arg.codePointAt(0) !== HYPHEN_CODE) return false; if (arg.codePointAt(1) === HYPHEN_CODE) return false; return true; } /** * Check if `arg` is a long option (e.g. `--foo`). * @param arg the argument to check * @returns whether `arg` is a long option. */ function isLongOption(arg) { return hasLongOptionPrefix(arg) && !arg.includes(EQUAL_CHAR, 3); } /** * Check if `arg` is a long option with value (e.g. `--foo=bar`). * @param arg the argument to check * @returns whether `arg` is a long option. */ function isLongOptionAndValue(arg) { return hasLongOptionPrefix(arg) && arg.includes(EQUAL_CHAR, 3); } /** * Check if `arg` is a long option prefix (e.g. `--`). * @param arg the argument to check * @returns whether `arg` is a long option prefix. */ function hasLongOptionPrefix(arg) { return arg.length > 2 && ~arg.indexOf(LONG_OPTION_PREFIX); } /** * Check if a `value` is an option value. * @param value a value to check * @returns whether a `value` is an option value. */ function hasOptionValue(value) { return !(value == null) && value.codePointAt(0) !== HYPHEN_CODE; } //#endregion //#region node_modules/args-tokens/lib/resolver-Q4k2fgTW.js const SKIP_POSITIONAL_DEFAULT = -1; /** * Resolve command line arguments. * @param args - An arguments that contains {@link ArgSchema | arguments schema}. * @param tokens - An array of {@link ArgToken | tokens}. * @param resolveArgs - An arguments that contains {@link ResolveArgs | resolve arguments}. * @returns An object that contains the values of the arguments, positional arguments, rest arguments, and {@link AggregateError | validation errors}. */ function resolveArgs(args, tokens, { shortGrouping = false, skipPositional = SKIP_POSITIONAL_DEFAULT, toKebab = false } = {}) { const skipPositionalIndex = typeof skipPositional === "number" ? Math.max(skipPositional, SKIP_POSITIONAL_DEFAULT) : SKIP_POSITIONAL_DEFAULT; const rest = []; const optionTokens = []; const positionalTokens = []; let currentLongOption; let currentShortOption; const expandableShortOptions = []; function toShortValue() { if (expandableShortOptions.length === 0) return void 0; else { const value = expandableShortOptions.map((token) => token.name).join(""); expandableShortOptions.length = 0; return value; } } function applyLongOptionValue(value = void 0) { if (currentLongOption) { currentLongOption.value = value; optionTokens.push({ ...currentLongOption }); currentLongOption = void 0; } } function applyShortOptionValue(value = void 0) { if (currentShortOption) { currentShortOption.value = value || toShortValue(); optionTokens.push({ ...currentShortOption }); currentShortOption = void 0; } } /** * analyze phase to resolve value * separate tokens into positionals, long and short options, after that resolve values */ const schemas = Object.values(args); let terminated = false; for (let i = 0; i < tokens.length; i++) { const token = tokens[i]; if (token.kind === "positional") { if (terminated && token.value) { rest.push(token.value); continue; } if (currentShortOption) { if (schemas.find((schema) => schema.short === currentShortOption.name && schema.type === "boolean")) positionalTokens.push({ ...token }); } else if (currentLongOption) { if (args[currentLongOption.name]?.type === "boolean") positionalTokens.push({ ...token }); } else positionalTokens.push({ ...token }); applyLongOptionValue(token.value); applyShortOptionValue(token.value); } else if (token.kind === "option") if (token.rawName) { if (hasLongOptionPrefix(token.rawName)) { applyLongOptionValue(); if (token.inlineValue) optionTokens.push({ ...token }); else currentLongOption = { ...token }; applyShortOptionValue(); } else if (isShortOption(token.rawName)) if (currentShortOption) { if (currentShortOption.index === token.index) if (shortGrouping) { currentShortOption.value = token.value; optionTokens.push({ ...currentShortOption }); currentShortOption = { ...token }; } else expandableShortOptions.push({ ...token }); else { currentShortOption.value = toShortValue(); optionTokens.push({ ...currentShortOption }); currentShortOption = { ...token }; } applyLongOptionValue(); } else { currentShortOption = { ...token }; applyLongOptionValue(); } } else { if (currentShortOption && currentShortOption.index == token.index && token.inlineValue) { currentShortOption.value = token.value; optionTokens.push({ ...currentShortOption }); currentShortOption = void 0; } applyLongOptionValue(); } else { if (token.kind === "option-terminator") terminated = true; applyLongOptionValue(); applyShortOptionValue(); } } /** * check if the last long or short option is not resolved */ applyLongOptionValue(); applyShortOptionValue(); /** * resolve values */ const values = Object.create(null); const errors = []; function checkTokenName(option, schema, token) { return token.name === (schema.type === "boolean" ? schema.negatable && token.name?.startsWith("no-") ? `no-${option}` : option : option); } const positionalItemCount = tokens.filter((token) => token.kind === "positional").length; function getPositionalSkipIndex() { return Math.min(skipPositionalIndex, positionalItemCount); } let positionalsCount = 0; for (const [rawArg, schema] of Object.entries(args)) { const arg = toKebab || schema.toKebab ? kebabnize(rawArg) : rawArg; if (schema.required) { if (!optionTokens.find((token) => { return schema.short && token.name === schema.short || token.rawName && hasLongOptionPrefix(token.rawName) && token.name === arg; })) { errors.push(createRequireError(arg, schema)); continue; } } if (schema.type === "positional") { if (skipPositionalIndex > SKIP_POSITIONAL_DEFAULT) while (positionalsCount <= getPositionalSkipIndex()) positionalsCount++; const positional = positionalTokens[positionalsCount]; if (positional != null) values[rawArg] = positional.value; else errors.push(createRequireError(arg, schema)); positionalsCount++; continue; } for (let i = 0; i < optionTokens.length; i++) { const token = optionTokens[i]; if (checkTokenName(arg, schema, token) && token.rawName != void 0 && hasLongOptionPrefix(token.rawName) || schema.short === token.name && token.rawName != void 0 && isShortOption(token.rawName)) { const invalid = validateRequire(token, arg, schema); if (invalid) { errors.push(invalid); continue; } if (schema.type === "boolean") token.value = void 0; const [parsedValue, error] = parse(token, arg, schema); if (error) errors.push(error); else if (schema.multiple) { values[rawArg] ||= []; values[rawArg].push(parsedValue); } else values[rawArg] = parsedValue; } } if (values[rawArg] == null && schema.default != null) values[rawArg] = schema.default; } return { values, positionals: positionalTokens.map((token) => token.value), rest, error: errors.length > 0 ? new AggregateError(errors) : void 0 }; } function parse(token, option, schema) { switch (schema.type) { case "string": return typeof token.value === "string" ? [token.value || schema.default, void 0] : [void 0, createTypeError(option, schema)]; case "boolean": return token.value ? [token.value || schema.default, void 0] : [!(schema.negatable && token.name.startsWith("no-")), void 0]; case "number": if (!isNumeric(token.value)) return [void 0, createTypeError(option, schema)]; return token.value ? [+token.value, void 0] : [+(schema.default || ""), void 0]; case "enum": if (schema.choices && !schema.choices.includes(token.value)) return [void 0, new ArgResolveError(`Optional argument '--${option}' ${schema.short ? `or '-${schema.short}' ` : ""}should be chosen from '${schema.type}' [${schema.choices.map((c) => JSON.stringify(c)).join(", ")}] values`, option, "type", schema)]; return [token.value || schema.default, void 0]; case "custom": if (typeof schema.parse !== "function") throw new TypeError(`argument '${option}' should have a 'parse' function`); try { return [schema.parse(token.value || String(schema.default || "")), void 0]; } catch (error) { return [void 0, error]; } default: throw new Error(`Unsupported argument type '${schema.type}' for option '${option}'`); } } function createRequireError(option, schema) { return new ArgResolveError(schema.type === "positional" ? `Positional argument '${option}' is required` : `Optional argument '--${option}' ${schema.short ? `or '-${schema.short}' ` : ""}is required`, option, "required", schema); } /** * An error that occurs when resolving arguments. * This error is thrown when the argument is not valid. */ var ArgResolveError = class extends Error { name; schema; type; constructor(message, name$1, type, schema) { super(message); this.name = name$1; this.type = type; this.schema = schema; } }; function validateRequire(token, option, schema) { if (schema.required && schema.type !== "boolean" && !token.value) return createRequireError(option, schema); } function isNumeric(str) { return str.trim() !== "" && !isNaN(str); } function createTypeError(option, schema) { return new ArgResolveError(`Optional argument '--${option}' ${schema.short ? `or '-${schema.short}' ` : ""}should be '${schema.type}'`, option, "type", schema); } //#endregion //#region node_modules/gunshi/lib/cli-DVGNVw3h.js /** * Run the command. * @param args Command line arguments * @param entry A {@link Command | entry command}, an {@link CommandRunner | inline command runner}, or a {@link LazyCommand | lazily-loaded command} * @param options A {@link CliOptions | CLI options} * @returns A rendered usage or undefined. if you will use {@link CliOptions.usageSilent} option, it will return rendered usage string. */ async function cli(argv$1, entry, options = {}) { const cliOptions = resolveCliOptions(options, entry); const tokens = parseArgs(argv$1); const subCommand = getSubCommand(tokens); const { commandName: name$1, command, callMode } = await resolveCommand(subCommand, entry, cliOptions); if (!command) throw new Error(`Command not found: ${name$1 || ""}`); const args = resolveArguments(getCommandArgs(command)); const { values, positionals, rest, error } = resolveArgs(args, tokens, { shortGrouping: true, toKebab: command.toKebab, skipPositional: cliOptions.subCommands.size > 0 ? 0 : -1 }); const ctx = await createCommandContext({ args, values, positionals, rest, argv: argv$1, tokens, omitted: !subCommand, callMode, command, cliOptions }); if (values.version) { showVersion(ctx); return; } const usageBuffer = []; const header = await showHeader(ctx); if (header) usageBuffer.push(header); if (values.help) { const usage = await showUsage(ctx); if (usage) usageBuffer.push(usage); return usageBuffer.join("\n"); } if (error) { await showValidationErrors(ctx, error); return; } await executeCommand(command, ctx, name$1 || ""); } function getCommandArgs(cmd) { if (isLazyCommand(cmd)) return cmd.args || create(); else if (typeof cmd === "object") return cmd.args || create(); else return create(); } function resolveArguments(args) { return Object.assign(create(), args, COMMON_ARGS); } function resolveCliOptions(options, entry) { const subCommands$1 = new Map(options.subCommands); if (options.subCommands) { if (isLazyCommand(entry)) subCommands$1.set(entry.commandName, entry); else if (typeof entry === "object" && entry.name) subCommands$1.set(entry.name, entry); } return Object.assign(create(), COMMAND_OPTIONS_DEFAULT, options, { subCommands: subCommands$1 }); } function getSubCommand(tokens) { const firstToken = tokens[0]; return firstToken && firstToken.kind === "positional" && firstToken.index === 0 && firstToken.value ? firstToken.value : ""; } async function showUsage(ctx) { if (ctx.env.renderUsage === null) return; const usage = await (ctx.env.renderUsage || renderUsage)(ctx); if (usage) { ctx.log(usage); return usage; } } function showVersion(ctx) { ctx.log(ctx.env.version); } async function showHeader(ctx) { if (ctx.env.renderHeader === null) return; const header = await (ctx.env.renderHeader || renderHeader)(ctx); if (header) { ctx.log(header); ctx.log(); return header; } } async function showValidationErrors(ctx, error) { if (ctx.env.renderValidationErrors === null) return; const render = ctx.env.renderValidationErrors || renderValidationErrors; ctx.log(await render(ctx, error)); } const CANNOT_RESOLVE_COMMAND = { callMode: "unexpected" }; async function resolveCommand(sub, entry, options) { const omitted = !sub; async function doResolveCommand() { if (typeof entry === "function") if ("commandName" in entry && entry.commandName) return { commandName: entry.commandName, command: entry, callMode: "entry" }; else return { command: { run: entry }, callMode: "entry" }; else if (typeof entry === "object") return { commandName: resolveEntryName(entry), command: entry, callMode: "entry" }; else return CANNOT_RESOLVE_COMMAND; } if (omitted || options.subCommands?.size === 0) return doResolveCommand(); const cmd = options.subCommands?.get(sub); if (cmd == null) return { commandName: sub, callMode: "unexpected" }; if (isLazyCommand(cmd) && cmd.commandName == null) cmd.commandName = sub; else if (typeof cmd === "object" && cmd.name == null) cmd.name = sub; return { commandName: sub, command: cmd, callMode: "subCommand" }; } function resolveEntryName(entry) { return entry.name || ANONYMOUS_COMMAND_NAME; } async function executeCommand(cmd, ctx, name$1) { const resolved = isLazyCommand(cmd) ? await resolveLazyCommand(cmd, name$1, true) : cmd; if (resolved.run == null) throw new Error(`'run' not found on Command \`${name$1}\``); await resolved.run(ctx); } //#endregion //#region package.json var name = "ccremote"; var version = "0.2.0"; var description = "Claude Code Remote: approve prompts from Discord, auto-continue sessions after quota resets, and schedule quota windows around your workday."; //#endregion //#region src/core/daemon-manager.ts var DaemonManager = class { daemons = /* @__PURE__ */ new Map(); globalConfigDir; daemonPidsFile; initPromise = null; constructor() { this.globalConfigDir = join(homedir(), ".ccremote"); this.daemonPidsFile = join(this.globalConfigDir, "daemon-pids.json"); this.initPromise = this.loadDaemonPids().catch((error) => { console.warn("Failed to load existing daemon PIDs on startup:", error instanceof Error ? error.message : error); }); } /** * Ensure the daemon manager is fully initialized */ async ensureInitialized() { if (this.initPromise) { try { await Promise.race([this.initPromise, new Promise((_$2, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error("Daemon initialization timeout")), 1e4))]); } catch (error) { console.warn("Daemon initialization failed or timed out:", error instanceof Error ? error.message : error); } this.initPromise = null; } } /** * Get the PM2 binary path from the bundled package */ getPm2BinaryPath() { const currentFileUrl = import.meta.url; const currentDir = dirname(fileURLToPath(currentFileUrl)); const possiblePaths = [ join(currentDir, "../../node_modules/pm2/bin/pm2"), join(currentDir, "../node_modules/pm2/bin/pm2"), join(dirname(process.argv[1]), "../node_modules/pm2/bin/pm2"), join(dirname(currentDir), "node_modules/pm2/bin/pm2") ]; for (const path of possiblePaths) try { accessSync(path, constants.F_OK); return path; } catch {} throw new Error("PM2 binary not found. Please ensure PM2 is properly installed."); } /** * Prepare PM2 command arguments and binary */ preparePm2Command(args) { return { binary: this.getPm2BinaryPath(), args }; } /** * Get the daemon script path */ async getDaemonScriptPath() { const currentFileUrl = import.meta.url; const currentDir = dirname(fileURLToPath(currentFileUrl)); const possiblePaths = [ join(currentDir, "../../dist/daemon.js"), join(currentDir, "../daemon.js"), join(currentDir, "daemon.js") ]; for (const path of possiblePaths) try { await promises.access(path); return path; } catch {} throw new Error("Could not find daemon.js script"); } /** * Spawn a new daemon process for a session using PM2 */ async spawnDaemon(config) { const pm2Name = `${config.sessionId}-daemon`; if (await this.checkPm2ProcessExists(pm2Name)) await this.forceStopPm2Process(pm2Name); const daemonScript = await this.getDaemonScriptPath(); const pm2Command = this.preparePm2Command([ "start", daemonScript, "--name", pm2Name, "--no-autorestart", "--output", config.logFile, "--error", config.logFile, "--merge-logs" ]); return new Promise((resolve$1, reject) => { const pm2Process = spawn(pm2Command.binary, pm2Command.args, { stdio: [ "ignore", "pipe", "pipe" ], cwd: process.cwd(), env: { ...process.env, NODE_ENV: "production", CCREMOTE_SESSION_ID: config.sessionId, CCREMOTE_LOG_FILE: config.logFile } }); let stdout$1 = ""; let stderr = ""; pm2Process.stdout?.on("data", (data) => { stdout$1 += data.toString(); }); pm2Process.stderr?.on("data", (data) => { stderr += data.toString(); }); pm2Process.on("close", (code) => { if (code !== 0) { reject(/* @__PURE__ */ new Error(`PM2 start failed: ${stderr || stdout$1}`)); return; } const listCommand$1 = this.preparePm2Command([ "list", pm2Name, "--format" ]); const listProcess = spawn(listCommand$1.binary, listCommand$1.args, { stdio: [ "ignore", "pipe", "pipe" ] }); listProcess.stdout?.on("data", (_data) => {}); listProcess.on("close", (_listCode) => { const daemon = { sessionId: config.sessionId, pm2Id: pm2Name, logFile: config.logFile, startTime: /* @__PURE__ */ new Date() }; this.daemons.set(config.sessionId, daemon); this.saveDaemonPids().then(() => resolve$1(daemon)).catch(reject); }); }); }); } /** * Stop a daemon process using PM2 */ async stopDaemon(sessionId) { const daemon = this.daemons.get(sessionId); if (!daemon) return false; return new Promise((resolve$1, reject) => { const stopCommand$1 = this.preparePm2Command(["stop", daemon.pm2Id]); spawn(stopCommand$1.binary, stopCommand$1.args, { stdio: "ignore" }).on("close", (_code) => { const deleteCommand = this.preparePm2Command(["delete", daemon.pm2Id]); spawn(deleteCommand.binary, deleteCommand.args, { stdio: "ignore" }).on("close", () => { this.daemons.delete(sessionId); this.saveDaemonPids().then(() => resolve$1(true)).catch(reject); }); }); }); } /** * Check if a daemon is running for a session */ isDaemonRunning(sessionId) { if (!this.daemons.get(sessionId)) return false; return true; } /** * Get daemon info for a session */ getDaemon(sessionId) { return this.daemons.get(sessionId); } /** * Get all running daemons */ getAllDaemons() { return Array.from(this.daemons.values()); } /** * Stop all daemons */ async stopAllDaemons() { const sessionIds = Array.from(this.daemons.keys()); await Promise.all(sessionIds.map(async (id) => this.stopDaemon(id))); } /** * Load daemon PIDs from file (for recovery after restart) */ async loadDaemonPids() { try { const data = await promises.readFile(this.daemonPidsFile, "utf-8"); const loadPromises = JSON.parse(data).map(async (pidInfo) => { if (!pidInfo.pm2Id) return; try { if (await this.checkPm2ProcessExists(pidInfo.pm2Id)) this.daemons.set(pidInfo.sessionId, { sessionId: pidInfo.sessionId, pm2Id: pidInfo.pm2Id, logFile: pidInfo.logFile, startTime: new Date(pidInfo.startTime) }); } catch {} }); await Promise.all(loadPromises); } catch {} } /** * Check if a PM2 process exists */ async checkPm2ProcessExists(pm2Id) { return new Promise((resolve$1) => { try { const describeCommand = this.preparePm2Command(["describe", pm2Id]); const checkProcess = spawn(describeCommand.binary, describeCommand.args, { stdio: [ "ignore", "pipe", "pipe" ] }); const timeout = setTimeout(() => { checkProcess.kill("SIGTERM"); resolve$1(false); }, 5e3); checkProcess.on("close", (code) => { clearTimeout(timeout); resolve$1(code === 0); }); checkProcess.on("error", () => { clearTimeout(timeout); resolve$1(false); }); } catch { resolve$1(false); } }); } /** * Force stop and delete a PM2 process by name */ async forceStopPm2Process(pm2Name) { return new Promise((resolve$1) => { const stopCommand$1 = this.preparePm2Command(["stop", pm2Name]); const stopProcess = spawn(stopCommand$1.binary, stopCommand$1.args, { stdio: [ "ignore", "pipe", "pipe" ] }); stopProcess.stderr?.on("data", () => {}); stopProcess.on("close", (_stopCode) => { const deleteCommand = this.preparePm2Command(["delete", pm2Name]); const deleteProcess = spawn(deleteCommand.binary, deleteCommand.args, { stdio: [ "ignore", "pipe", "pipe" ] }); let deleteError = ""; deleteProcess.stderr?.on("data", (data) => { deleteError += data.toString(); }); deleteProcess.on("close", (deleteCode) => { if (deleteCode === 0) resolve$1(); else { console.warn(`Failed to force-delete PM2 process ${pm2Name}: ${deleteError}`); resolve$1(); } }); deleteProcess.on("error", (error) => { console.warn(`Error deleting PM2 process ${pm2Name}:`, error.message); resolve$1(); }); }); stopProcess.on("error", (error) => { console.warn(`Error stopping PM2 process ${pm2Name}:`, error.message); const deleteCommand = this.preparePm2Command(["delete", pm2Name]); const deleteProcess = spawn(deleteCommand.binary, deleteCommand.args, { stdio: "ignore" }); deleteProcess.on("close", () => resolve$1()); deleteProcess.on("error", () => resolve$1()); }); }); } /** * Save daemon PIDs to file */ async saveDaemonPids() { try { await promises.mkdir(this.globalConfigDir, { recursive: true }); const pids = Array.from(this.daemons.values()).map((daemon) => ({ sessionId: daemon.sessionId, pm2Id: daemon.pm2Id, logFile: daemon.logFile, startTime: daemon.startTime.toISOString() })); await promises.writeFile(this.daemonPidsFile, JSON.stringify(pids, null, 2)); } catch (error) { console.info("Failed to save daemon PIDs:", error); } } }; const daemonManager = new DaemonManager(); //#endregion //#region src/commands/clean.ts const cleanCommand = define({ name: "clean", description: "Remove ended and dead sessions, archive log files", args: { "dry-run": { type: "boolean", description: "Show what would be cleaned without making changes" }, "all": { type: "boolean", description: "Clean sessions from all projects (default: current project only)" } }, async run(ctx) { const { "dry-run": dryRun, "all": cleanAll } = ctx.values; if (dryRun) consola$1.info("🔍 Running in dry-run mode - no changes will be made"); if (cleanAll) consola$1.info("🌍 Cleaning sessions from all projects"); else consola$1.info("📁 Cleaning sessions from current project only (use --all to clean all projects)"); consola$1.start("Cleaning up sessions..."); try { const sessionManager = new SessionManager(); const tmuxManager = new TmuxManager(); await sessionManager.initialize(); let discordBot = null; const botToken = process.env.CCREMOTE_DISCORD_BOT_TOKEN; const ownerId = process.env.CCREMOTE_DISCORD_OWNER_ID; if (botToken && ownerId) try { discordBot = new DiscordBot(sessionManager, tmuxManager); const authorizedUsersConfig = process.env.CCREMOTE_DISCORD_AUTHORIZED_USERS; const authorizedUsers = authorizedUsersConfig ? authorizedUsersConfig.split(",").map((u$1) => u$1.trim()) : []; await discordBot.start(botToken, ownerId, authorizedUsers); consola$1.info("🤖 Discord bot initialized for channel cleanup"); } catch (error) { consola$1.warn(`Failed to initialize Discord bot: ${error instanceof Error ? error.message : String(error)}`); discordBot = null; } await daemonManager.ensureInitialized(); const sessions = cleanAll ? await sessionManager.listSession