feat(cli): crafter-debug, the client agents drive an app with
Cli::Run is the whole client but its transport, so tests run it in
process against a real Session; main will hand it the QUIC connection.
- Commands: ls, status, cmds, help, call, get, set, pause, resume,
step, wait, trace, log (with --follow), screenshot, quit, debugger,
guide and version, with -i, --json and --timeout.
- call types key=value from the command's argument schema (slot=1 is
a number, name=1 a string, at.x=3 nests), so agents need no JSON
for simple calls; a value that cannot fit its field is a usage
error before anything is sent.
- The instance comes from discovery; one running instance needs no -i.
The record's protocol is checked before any request: a mismatch
exits 4, says which side is newer and prints the app's own client.
Other sources only warn.
- A request with no reply is diagnosed: the app has exited, stopped
in a debugger (with the attach command), or no longer calls Pump
(with what to change). Exit codes: 2 usage, 3 no instance,
4 version, 5 timeout, 6 the app's error.
- Output is short by default, arrays cut at 20 with a note; --json
prints everything. `guide` is the full reference for agents.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 02:03:38 +01:00
|
|
|
// 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();
|
fix(cli): print lldb by its path, and report a held app as held
Tried on the example with the app's sandbox, two things were wrong:
- `debugger` printed `flatpak enter <id> lldb -p <pid>`, and flatpak
enter runs a command without the sandbox's PATH, which /app/bin is
on: "Failed to execute child process lldb". It now prints the path
it found, /app/bin/lldb, which attached, listed threads and detached.
- With the app held by that debugger, the handshake times out, and
the client said "cannot reach basic on port ..." and exited 3, no
instance, though its lock said it lived. It now says the app is
alive but not answering, stopped in a debugger or hung, prints how
to attach, check and end it, and exits 5.
Environment::haveLldb becomes lldb, the path or empty. ShouldServeOverQuic
covers the second with an instance whose lock is held and whose port
is silent.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 03:21:41 +01:00
|
|
|
// 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;
|
feat(cli): crafter-debug, the client agents drive an app with
Cli::Run is the whole client but its transport, so tests run it in
process against a real Session; main will hand it the QUIC connection.
- Commands: ls, status, cmds, help, call, get, set, pause, resume,
step, wait, trace, log (with --follow), screenshot, quit, debugger,
guide and version, with -i, --json and --timeout.
- call types key=value from the command's argument schema (slot=1 is
a number, name=1 a string, at.x=3 nests), so agents need no JSON
for simple calls; a value that cannot fit its field is a usage
error before anything is sent.
- The instance comes from discovery; one running instance needs no -i.
The record's protocol is checked before any request: a mismatch
exits 4, says which side is newer and prints the app's own client.
Other sources only warn.
- A request with no reply is diagnosed: the app has exited, stopped
in a debugger (with the attach command), or no longer calls Pump
(with what to change). Exit codes: 2 usage, 3 no instance,
4 version, 5 timeout, 6 the app's error.
- Output is short by default, arrays cut at 20 with a note; --json
prints everything. `guide` is the full reference for agents.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 02:03:38 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|