Discovery is per user, so every project's launcher sees every debug app running, and with two of them up every command refused to guess and asked for -i, though the agent in one project meant its own app. Two copies of the example, both named basic, could only be told apart by id. The launcher now exports CRAFTER_DEBUG_PROJECT, its project's directory (flatpak-spawn gets it spelled out), and the client counts an app as the project's when its executable lies under it, as `-r` runs it from <project>/bin/<variant>/. With other projects' apps running, commands pick this project's; two copies of it still need -i. Another project's app is never picked by default: with this project's app not up yet, or crashed, the agent would otherwise drive, or quit, someone else's. It is listed instead, for -i. `ls` lists this project's apps first, marked, `--json` says which are, and `--wait` waits for one of them rather than any app; the exit code says whether one runs. A client started without the launcher has no project and picks the one app running, as before, and the command the protocol check prints for an older app's own client now names it with -i. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
79 lines
3.3 KiB
C++
79 lines
3.3 KiB
C++
// SPDX-License-Identifier: LGPL-3.0-only
|
|
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
|
|
|
// crafter-debug, the client agents drive an app with. Run is the whole
|
|
// program but for the transport: main hands it a Connector that reaches an
|
|
// instance over QUIC, and tests hand it one that reaches a Session in the
|
|
// same process.
|
|
//
|
|
// Exit codes:
|
|
// 0 done
|
|
// 2 a usage error, or no debug build yet (the launcher's stub)
|
|
// 3 no instance to talk to
|
|
// 4 the app and this client speak different protocols
|
|
// 5 timed out
|
|
// 6 the app answered with an error
|
|
// 1 is left to the launcher and `flatpak run` failing.
|
|
export module Crafter.Debug.Cli:Client;
|
|
import std;
|
|
import Crafter.Debug.Core;
|
|
|
|
export namespace Crafter::Debug::Cli {
|
|
inline constexpr std::int32_t exitOk = 0;
|
|
inline constexpr std::int32_t exitUsage = 2;
|
|
inline constexpr std::int32_t exitNoInstance = 3;
|
|
inline constexpr std::int32_t exitVersion = 4;
|
|
inline constexpr std::int32_t exitTimeout = 5;
|
|
inline constexpr std::int32_t exitError = 6;
|
|
|
|
struct CallError {
|
|
enum class Kind : std::uint8_t {
|
|
// No connection: the app is gone, or its port does not answer.
|
|
Unreachable,
|
|
// Connected, but no reply within the timeout.
|
|
TimedOut,
|
|
// The reply was not what the protocol says.
|
|
Broken,
|
|
};
|
|
Kind kind = Kind::Unreachable;
|
|
std::string message;
|
|
};
|
|
|
|
// One request to an instance. The reply is the last object it sends;
|
|
// objects before it are stream lines (`log --follow`), handed to
|
|
// `onLine`, which returns false to stop.
|
|
class Connection {
|
|
public:
|
|
virtual ~Connection() = default;
|
|
virtual std::expected<Value, CallError> Call(const Value& request, std::chrono::milliseconds timeout,
|
|
const std::function<bool(const Value& line)>& onLine) = 0;
|
|
};
|
|
|
|
using Connector = std::function<std::unique_ptr<Connection>(const InstanceRecord& instance)>;
|
|
|
|
struct Environment {
|
|
Connector connect;
|
|
std::function<std::vector<Instance>()> list = ListInstances;
|
|
std::ostream* out = &std::cout;
|
|
std::ostream* err = &std::cerr;
|
|
// Where a screenshot without -o goes, relative paths included.
|
|
std::filesystem::path cwd = std::filesystem::current_path();
|
|
// The project the launcher that started this client was written for
|
|
// (CRAFTER_DEBUG_PROJECT). Its apps, the ones whose executable lies
|
|
// under it, are picked without -i when other apps run too, and no
|
|
// other app is. Empty: whichever app runs, as long as only one does.
|
|
std::filesystem::path project;
|
|
// Where lldb is here, for the attach command `debugger` prints: its
|
|
// absolute path, since `flatpak enter` runs a command without the
|
|
// sandbox's PATH, which /app/bin is on. Empty for none: gdb then.
|
|
std::function<std::string()> lldb;
|
|
};
|
|
|
|
std::int32_t Run(std::span<const std::string_view> args, Environment& environment);
|
|
|
|
// The full reference an agent reads with `crafter-debug guide`.
|
|
std::string_view Guide();
|
|
|
|
// "30s", "500ms", "2m", "1h"; a bare number is seconds.
|
|
std::optional<std::chrono::milliseconds> ParseDuration(std::string_view text);
|
|
}
|