{"version":3,"file":"command.cjs","names":["resolveSignal"],"sources":["../src/command.ts"],"sourcesContent":["import { WebixApiClient, type CommandData } from \"./api-client/index.js\";\nimport { resolveSignal, type Signal } from \"./utils/resolveSignal.js\";\n\n/**\n * Cached output from a command execution.\n */\nexport interface CommandOutput {\n  stdout: string;\n  stderr: string;\n}\n\n/**\n * Serialized representation of a Command for @workflow/serde.\n */\nexport interface SerializedCommand {\n  sandboxId: string;\n  cmd: CommandData;\n  /** Cached output, included if output was fetched before serialization */\n  output?: CommandOutput;\n}\n\n/**\n * Serialized representation of a CommandFinished for @workflow/serde.\n */\nexport interface SerializedCommandFinished extends SerializedCommand {\n  exitCode: number;\n}\n\n/**\n * A command executed in a Sandbox.\n *\n * For detached commands, you can {@link wait} to get a {@link CommandFinished} instance\n * with the populated exit code. For non-detached commands, {@link Sandbox.runCommand}\n * automatically waits and returns a {@link CommandFinished} instance.\n *\n * You can iterate over command output with {@link logs}.\n *\n * @see {@link Sandbox.runCommand} to start a command.\n *\n * @hideconstructor\n */\nexport class Command {\n  /**\n   * Cached API client instance.\n   * @internal\n   */\n  protected _client: WebixApiClient | null = null;\n\n  /**\n   * Return the in-browser sandbox client bound at construction.\n   * @internal\n   */\n  protected async ensureClient(): Promise<WebixApiClient> {\n    if (this._client) return this._client;\n    throw new Error(\n      \"Command is not attached to a live in-browser sandbox host.\",\n    );\n  }\n\n  /**\n   * ID of the session this command is running in.\n   */\n  protected sessionId: string;\n\n  /**\n   * Data for the command execution.\n   */\n  protected cmd: CommandData;\n\n  public exitCode: number | null;\n\n  protected outputCache: Promise<{\n    stdout: string;\n    stderr: string;\n    both: string;\n  }> | null = null;\n\n  /**\n   * Synchronously accessible resolved output, populated after output is fetched.\n   * Used for serialization.\n   * @internal\n   */\n  protected _resolvedOutput: CommandOutput | null = null;\n\n  /**\n   * ID of the command execution.\n   */\n  get cmdId() {\n    return this.cmd.id;\n  }\n\n  get cwd() {\n    return this.cmd.cwd;\n  }\n\n  get startedAt() {\n    return this.cmd.startedAt;\n  }\n\n  /**\n   * @param params - Object containing the client, sandbox ID, and command data.\n   * @param params.client - Optional API client. If not provided, will be lazily created using global credentials.\n   * @param params.sessionId - The ID of the session where the command is running.\n   * @param params.cmd - The command data.\n   * @param params.output - Optional cached output to restore (used during deserialization).\n   */\n  constructor({\n    client,\n    sessionId,\n    cmd,\n    output,\n  }: {\n    client?: WebixApiClient;\n    sessionId: string;\n    cmd: CommandData;\n    output?: CommandOutput;\n  }) {\n    this._client = client ?? null;\n    this.sessionId = sessionId;\n    this.cmd = cmd;\n    this.exitCode = cmd.exitCode ?? null;\n    if (output) {\n      this._resolvedOutput = output;\n      // Note: `both` is reconstructed as stdout + stderr concatenation,\n      // which loses the original interleaved order of the streams.\n      this.outputCache = Promise.resolve({\n        stdout: output.stdout,\n        stderr: output.stderr,\n        both: output.stdout + output.stderr,\n      });\n    }\n  }\n\n  /**\n   * Iterate over the output of this command.\n   *\n   * ```\n   * for await (const log of cmd.logs()) {\n   *   if (log.stream === \"stdout\") {\n   *     process.stdout.write(log.data);\n   *   } else {\n   *     process.stderr.write(log.data);\n   *   }\n   * }\n   * ```\n   *\n   * @param opts - Optional parameters.\n   * @param opts.signal - An AbortSignal to cancel log streaming.\n   * @returns An async iterable of log entries from the command output.\n   *\n   * @see {@link Command.stdout}, {@link Command.stderr}, and {@link Command.output}\n   * to access output as a string.\n   */\n  logs(opts?: { signal?: AbortSignal }) {\n    if (!this._client) {\n      throw new Error(\n        \"logs() requires an API client. Call an async method first to initialize the client.\",\n      );\n    }\n    return this._client.getLogs({\n      sessionId: this.sessionId,\n      cmdId: this.cmd.id,\n      signal: opts?.signal,\n    });\n  }\n\n  /**\n   * Wait for a command to exit and populate its exit code.\n   *\n   * This method is useful for detached commands where you need to wait\n   * for completion. For non-detached commands, {@link Sandbox.runCommand}\n   * automatically waits and returns a {@link CommandFinished} instance.\n   *\n   * ```\n   * const detachedCmd = await sandbox.runCommand({ cmd: 'sleep', args: ['5'], detached: true });\n   * const result = await detachedCmd.wait();\n   * if (result.exitCode !== 0) {\n   *   console.error(\"Something went wrong...\")\n   * }\n   * ```\n   *\n   * @param params - Optional parameters.\n   * @param params.signal - An AbortSignal to cancel waiting.\n   * @returns A {@link CommandFinished} instance with populated exit code.\n   */\n  async wait(params?: { signal?: AbortSignal }) {\n    \"use step\";\n    const client = await this.ensureClient();\n    params?.signal?.throwIfAborted();\n\n    const command = await client.getCommand({\n      sessionId: this.sessionId,\n      cmdId: this.cmd.id,\n      wait: true,\n      signal: params?.signal,\n    });\n\n    return new CommandFinished({\n      client,\n      sessionId: this.sessionId,\n      cmd: command.json.command,\n      exitCode: command.json.command.exitCode ?? 0,\n    });\n  }\n\n  /**\n   * Get cached output, fetching logs only once and reusing for concurrent calls.\n   * This prevents race conditions when stdout() and stderr() are called in parallel.\n   */\n  protected async getCachedOutput(opts?: { signal?: AbortSignal }): Promise<{\n    stdout: string;\n    stderr: string;\n    both: string;\n  }> {\n    if (!this.outputCache) {\n      this.outputCache = (async () => {\n        try {\n          opts?.signal?.throwIfAborted();\n          // Ensure the API client is initialized before calling logs(),\n          // since logs() is synchronous and requires _client to be set.\n          await this.ensureClient();\n          let stdout = \"\";\n          let stderr = \"\";\n          let both = \"\";\n          for await (const log of this.logs({ signal: opts?.signal })) {\n            both += log.data;\n            if (log.stream === \"stdout\") {\n              stdout += log.data;\n            } else {\n              stderr += log.data;\n            }\n          }\n          // Store resolved output for serialization\n          this._resolvedOutput = { stdout, stderr };\n          return { stdout, stderr, both };\n        } catch (err) {\n          // Clear the promise so future calls can retry\n          this.outputCache = null;\n          throw err;\n        }\n      })();\n    }\n\n    return this.outputCache;\n  }\n\n  /**\n   * Get the output of `stdout`, `stderr`, or both as a string.\n   *\n   * NOTE: This may throw string conversion errors if the command does\n   * not output valid Unicode.\n   *\n   * @param stream - The output stream to read: \"stdout\", \"stderr\", or \"both\".\n   * @param opts - Optional parameters.\n   * @param opts.signal - An AbortSignal to cancel output streaming.\n   * @returns The output of the specified stream(s) as a string.\n   */\n  async output(\n    stream: \"stdout\" | \"stderr\" | \"both\" = \"both\",\n    opts?: { signal?: AbortSignal },\n  ) {\n    \"use step\";\n    const cached = await this.getCachedOutput(opts);\n    return cached[stream];\n  }\n\n  /**\n   * Get the output of `stdout` as a string.\n   *\n   * NOTE: This may throw string conversion errors if the command does\n   * not output valid Unicode.\n   *\n   * @param opts - Optional parameters.\n   * @param opts.signal - An AbortSignal to cancel output streaming.\n   * @returns The standard output of the command.\n   */\n  async stdout(opts?: { signal?: AbortSignal }) {\n    \"use step\";\n    return this.output(\"stdout\", opts);\n  }\n\n  /**\n   * Get the output of `stderr` as a string.\n   *\n   * NOTE: This may throw string conversion errors if the command does\n   * not output valid Unicode.\n   *\n   * @param opts - Optional parameters.\n   * @param opts.signal - An AbortSignal to cancel output streaming.\n   * @returns The standard error output of the command.\n   */\n  async stderr(opts?: { signal?: AbortSignal }) {\n    \"use step\";\n    return this.output(\"stderr\", opts);\n  }\n\n  /**\n   * Kill a running command in a sandbox.\n   *\n   * @param signal - The signal to send the running process. Defaults to SIGTERM.\n   * @param opts - Optional parameters.\n   * @param opts.abortSignal - An AbortSignal to cancel the kill operation.\n   * @returns Promise<void>.\n   */\n  async kill(signal?: Signal, opts?: { abortSignal?: AbortSignal }) {\n    \"use step\";\n    const client = await this.ensureClient();\n    await client.killCommand({\n      sessionId: this.sessionId,\n      commandId: this.cmd.id,\n      signal: resolveSignal(signal ?? \"SIGTERM\"),\n      abortSignal: opts?.abortSignal,\n    });\n  }\n}\n\n/**\n * A command that has finished executing.\n *\n * The exit code is immediately available and populated upon creation.\n * Unlike {@link Command}, you don't need to call wait() - the command\n * has already completed execution.\n *\n * @hideconstructor\n */\nexport class CommandFinished extends Command {\n  /**\n   * The exit code of the command. This is always populated for\n   * CommandFinished instances.\n   */\n  public exitCode: number;\n\n  /**\n   * @param params - Object containing client, sandbox ID, command data, and exit code.\n   * @param params.client - Optional API client. If not provided, will be lazily created using global credentials.\n   * @param params.sessionId - The ID of the session where the command ran.\n   * @param params.cmd - The command data.\n   * @param params.exitCode - The exit code of the completed command.\n   * @param params.output - Optional cached output to restore (used during deserialization).\n   */\n  constructor(params: {\n    client?: WebixApiClient;\n    sessionId: string;\n    cmd: CommandData;\n    exitCode: number;\n    output?: CommandOutput;\n  }) {\n    super({ ...params });\n    this.exitCode = params.exitCode;\n  }\n\n  /**\n   * The wait method is not needed for CommandFinished instances since\n   * the command has already completed and exitCode is populated.\n   *\n   * @deprecated This method is redundant for CommandFinished instances.\n   * The exitCode is already available.\n   * @returns This CommandFinished instance.\n   */\n  async wait(): Promise<CommandFinished> {\n    return this;\n  }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAyCA,IAAa,UAAb,MAAqB;;;;;CAWnB,MAAgB,eAAwC;AACtD,MAAI,KAAK,QAAS,QAAO,KAAK;AAC9B,QAAM,IAAI,MACR,6DACD;;;;;CA+BH,IAAI,QAAQ;AACV,SAAO,KAAK,IAAI;;CAGlB,IAAI,MAAM;AACR,SAAO,KAAK,IAAI;;CAGlB,IAAI,YAAY;AACd,SAAO,KAAK,IAAI;;;;;;;;;CAUlB,YAAY,EACV,QACA,WACA,KACA,UAMC;OAtEO,UAAiC;OAyBjC,cAIE;OAOF,kBAAwC;AAmChD,OAAK,UAAU,UAAU;AACzB,OAAK,YAAY;AACjB,OAAK,MAAM;AACX,OAAK,WAAW,IAAI,YAAY;AAChC,MAAI,QAAQ;AACV,QAAK,kBAAkB;AAGvB,QAAK,cAAc,QAAQ,QAAQ;IACjC,QAAQ,OAAO;IACf,QAAQ,OAAO;IACf,MAAM,OAAO,SAAS,OAAO;IAC9B,CAAC;;;;;;;;;;;;;;;;;;;;;;;CAwBN,KAAK,MAAiC;AACpC,MAAI,CAAC,KAAK,QACR,OAAM,IAAI,MACR,sFACD;AAEH,SAAO,KAAK,QAAQ,QAAQ;GAC1B,WAAW,KAAK;GAChB,OAAO,KAAK,IAAI;GAChB,QAAQ,MAAM;GACf,CAAC;;;;;;;;;;;;;;;;;;;;;CAsBJ,MAAM,KAAK,QAAmC;AAC5C;EACA,MAAM,SAAS,MAAM,KAAK,cAAc;AACxC,UAAQ,QAAQ,gBAAgB;EAEhC,MAAM,UAAU,MAAM,OAAO,WAAW;GACtC,WAAW,KAAK;GAChB,OAAO,KAAK,IAAI;GAChB,MAAM;GACN,QAAQ,QAAQ;GACjB,CAAC;AAEF,SAAO,IAAI,gBAAgB;GACzB;GACA,WAAW,KAAK;GAChB,KAAK,QAAQ,KAAK;GAClB,UAAU,QAAQ,KAAK,QAAQ,YAAY;GAC5C,CAAC;;;;;;CAOJ,MAAgB,gBAAgB,MAI7B;AACD,MAAI,CAAC,KAAK,YACR,MAAK,eAAe,YAAY;AAC9B,OAAI;AACF,UAAM,QAAQ,gBAAgB;AAG9B,UAAM,KAAK,cAAc;IACzB,IAAI,SAAS;IACb,IAAI,SAAS;IACb,IAAI,OAAO;AACX,eAAW,MAAM,OAAO,KAAK,KAAK,EAAE,QAAQ,MAAM,QAAQ,CAAC,EAAE;AAC3D,aAAQ,IAAI;AACZ,SAAI,IAAI,WAAW,SACjB,WAAU,IAAI;SAEd,WAAU,IAAI;;AAIlB,SAAK,kBAAkB;KAAE;KAAQ;KAAQ;AACzC,WAAO;KAAE;KAAQ;KAAQ;KAAM;YACxB,KAAK;AAEZ,SAAK,cAAc;AACnB,UAAM;;MAEN;AAGN,SAAO,KAAK;;;;;;;;;;;;;CAcd,MAAM,OACJ,SAAuC,QACvC,MACA;AACA;AAEA,UADe,MAAM,KAAK,gBAAgB,KAAK,EACjC;;;;;;;;;;;;CAahB,MAAM,OAAO,MAAiC;AAC5C;AACA,SAAO,KAAK,OAAO,UAAU,KAAK;;;;;;;;;;;;CAapC,MAAM,OAAO,MAAiC;AAC5C;AACA,SAAO,KAAK,OAAO,UAAU,KAAK;;;;;;;;;;CAWpC,MAAM,KAAK,QAAiB,MAAsC;AAChE;AAEA,SADe,MAAM,KAAK,cAAc,EAC3B,YAAY;GACvB,WAAW,KAAK;GAChB,WAAW,KAAK,IAAI;GACpB,QAAQA,oCAAc,UAAU,UAAU;GAC1C,aAAa,MAAM;GACpB,CAAC;;;;;;;;;;;;AAaN,IAAa,kBAAb,cAAqC,QAAQ;;;;;;;;;CAe3C,YAAY,QAMT;AACD,QAAM,EAAE,GAAG,QAAQ,CAAC;AACpB,OAAK,WAAW,OAAO;;;;;;;;;;CAWzB,MAAM,OAAiC;AACrC,SAAO"}