feat(tooling): wire consumers in through Debug::Use and a launcher
A consumer loads Crafter.Debug.Build through
#pragma crafter dependency "<Crafter.Debug>" project "tooling/project.cpp"
and calls Debug::Use in its own CrafterBuildProject, the way
Crafter.LintRules gets in. With the consumer's flag on, Use adds the
library from the checkout the pragma loaded, and writes
build/crafter-debug: a POSIX sh launcher for the client that library
builds. The client is one of the library's dependencies, so a debug
build compiles it in the same pass as the app, from the same sources.
Why a launcher and not a script: every `crafter-build run` loads the
whole project first, which costs 13-28 s per call on 3DForts (a git
pull per unpinned dependency plus rescanning sources), while the
launcher is one sandbox start, 0.08 s. A client built in the Flatpak
links the Flatpak's libc++ against the runtime's glibc, so the launcher
starts it through `flatpak run` and never on the host itself; inside a
CrafterBuild sandbox it runs it directly.
The library, the client and examples/Basic are placeholders that prove
the wiring: the debug build writes a launcher whose client exists, a
plain build has no Crafter::Debug symbols and keeps the notes from
tooling/AGENTS.consumer.md, and a fresh plain build writes a stub that
names the flag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:18:07 +01:00
|
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
|
|
|
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
|
|
|
|
|
feat(cli): reach instances over QUIC
crafter-debug had a placeholder transport that reported every instance
unreachable. Cli::ConnectQuic dials 127.0.0.1 at the port in the
discovery record on the first call, keeps the connection for the next,
and dials again after one is lost. Each call opens a stream, sends the
request with a FIN, and reads lines: one with a result or an error is
the reply, anything before it a stream line for onLine, which runs on
the calling thread so a slow terminal never stalls msquic's worker.
Past the deadline the stream is aborted and the call times out.
The handshake gives up after 3 s, and keep-alives every 2 s hold a
quiet follow or a long wait open under the 10 s idle timeout. main
starts a pool with one worker, which Network reports some failures on,
and ends with _Exit once Run has closed its connections, since msquic's
teardown at exit waits on connections.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 03:07:09 +01:00
|
|
|
// crafter-debug: Cli::Run with the QUIC transport.
|
feat(tooling): wire consumers in through Debug::Use and a launcher
A consumer loads Crafter.Debug.Build through
#pragma crafter dependency "<Crafter.Debug>" project "tooling/project.cpp"
and calls Debug::Use in its own CrafterBuildProject, the way
Crafter.LintRules gets in. With the consumer's flag on, Use adds the
library from the checkout the pragma loaded, and writes
build/crafter-debug: a POSIX sh launcher for the client that library
builds. The client is one of the library's dependencies, so a debug
build compiles it in the same pass as the app, from the same sources.
Why a launcher and not a script: every `crafter-build run` loads the
whole project first, which costs 13-28 s per call on 3DForts (a git
pull per unpinned dependency plus rescanning sources), while the
launcher is one sandbox start, 0.08 s. A client built in the Flatpak
links the Flatpak's libc++ against the runtime's glibc, so the launcher
starts it through `flatpak run` and never on the host itself; inside a
CrafterBuild sandbox it runs it directly.
The library, the client and examples/Basic are placeholders that prove
the wiring: the debug build writes a launcher whose client exists, a
plain build has no Crafter::Debug symbols and keeps the notes from
tooling/AGENTS.consumer.md, and a fresh plain build writes a stub that
names the flag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:18:07 +01:00
|
|
|
import std;
|
feat(cli): reach instances over QUIC
crafter-debug had a placeholder transport that reported every instance
unreachable. Cli::ConnectQuic dials 127.0.0.1 at the port in the
discovery record on the first call, keeps the connection for the next,
and dials again after one is lost. Each call opens a stream, sends the
request with a FIN, and reads lines: one with a result or an error is
the reply, anything before it a stream line for onLine, which runs on
the calling thread so a slow terminal never stalls msquic's worker.
Past the deadline the stream is aborted and the call times out.
The handshake gives up after 3 s, and keep-alives every 2 s hold a
quiet follow or a long wait open under the 10 s idle timeout. main
starts a pool with one worker, which Network reports some failures on,
and ends with _Exit once Run has closed its connections, since msquic's
teardown at exit waits on connections.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 03:07:09 +01:00
|
|
|
import Crafter.Thread;
|
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
|
|
|
import Crafter.Debug.Core;
|
|
|
|
|
import Crafter.Debug.Cli;
|
|
|
|
|
using namespace Crafter::Debug;
|
|
|
|
|
|
feat(tooling): wire consumers in through Debug::Use and a launcher
A consumer loads Crafter.Debug.Build through
#pragma crafter dependency "<Crafter.Debug>" project "tooling/project.cpp"
and calls Debug::Use in its own CrafterBuildProject, the way
Crafter.LintRules gets in. With the consumer's flag on, Use adds the
library from the checkout the pragma loaded, and writes
build/crafter-debug: a POSIX sh launcher for the client that library
builds. The client is one of the library's dependencies, so a debug
build compiles it in the same pass as the app, from the same sources.
Why a launcher and not a script: every `crafter-build run` loads the
whole project first, which costs 13-28 s per call on 3DForts (a git
pull per unpinned dependency plus rescanning sources), while the
launcher is one sandbox start, 0.08 s. A client built in the Flatpak
links the Flatpak's libc++ against the runtime's glibc, so the launcher
starts it through `flatpak run` and never on the host itself; inside a
CrafterBuild sandbox it runs it directly.
The library, the client and examples/Basic are placeholders that prove
the wiring: the debug build writes a launcher whose client exists, a
plain build has no Crafter::Debug symbols and keeps the notes from
tooling/AGENTS.consumer.md, and a fresh plain build writes a stub that
names the flag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:18:07 +01:00
|
|
|
int main(int argc, char** argv) {
|
feat(cli): reach instances over QUIC
crafter-debug had a placeholder transport that reported every instance
unreachable. Cli::ConnectQuic dials 127.0.0.1 at the port in the
discovery record on the first call, keeps the connection for the next,
and dials again after one is lost. Each call opens a stream, sends the
request with a FIN, and reads lines: one with a result or an error is
the reply, anything before it a stream line for onLine, which runs on
the calling thread so a slow terminal never stalls msquic's worker.
Past the deadline the stream is aborted and the call times out.
The handshake gives up after 3 s, and keep-alives every 2 s hold a
quiet follow or a long wait open under the 10 s idle timeout. main
starts a pool with one worker, which Network reports some failures on,
and ends with _Exit once Run has closed its connections, since msquic's
teardown at exit waits on connections.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 03:07:09 +01:00
|
|
|
// Crafter.Network reports some failures through the pool. One request
|
|
|
|
|
// at a time needs one worker.
|
|
|
|
|
Crafter::ThreadPool::Start(2);
|
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
|
|
|
const std::vector<std::string_view> args(argv + 1, argv + argc);
|
feat(cli): reach instances over QUIC
crafter-debug had a placeholder transport that reported every instance
unreachable. Cli::ConnectQuic dials 127.0.0.1 at the port in the
discovery record on the first call, keeps the connection for the next,
and dials again after one is lost. Each call opens a stream, sends the
request with a FIN, and reads lines: one with a result or an error is
the reply, anything before it a stream line for onLine, which runs on
the calling thread so a slow terminal never stalls msquic's worker.
Past the deadline the stream is aborted and the call times out.
The handshake gives up after 3 s, and keep-alives every 2 s hold a
quiet follow or a long wait open under the 10 s idle timeout. main
starts a pool with one worker, which Network reports some failures on,
and ends with _Exit once Run has closed its connections, since msquic's
teardown at exit waits on connections.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 03:07:09 +01:00
|
|
|
std::int32_t code = 0;
|
|
|
|
|
{
|
|
|
|
|
Cli::Environment environment;
|
|
|
|
|
environment.connect = Cli::ConnectQuic;
|
|
|
|
|
environment.haveLldb = [] { return std::filesystem::exists("/app/bin/lldb") || std::filesystem::exists("/usr/bin/lldb"); };
|
|
|
|
|
// Run closes its connections before it returns.
|
|
|
|
|
code = Cli::Run(args, environment);
|
|
|
|
|
}
|
|
|
|
|
std::cout.flush();
|
|
|
|
|
std::cerr.flush();
|
|
|
|
|
std::fflush(nullptr);
|
|
|
|
|
// Not through exit: msquic's teardown at exit waits for connections,
|
|
|
|
|
// and the pool's workers would need stopping first.
|
|
|
|
|
std::_Exit(code);
|
feat(tooling): wire consumers in through Debug::Use and a launcher
A consumer loads Crafter.Debug.Build through
#pragma crafter dependency "<Crafter.Debug>" project "tooling/project.cpp"
and calls Debug::Use in its own CrafterBuildProject, the way
Crafter.LintRules gets in. With the consumer's flag on, Use adds the
library from the checkout the pragma loaded, and writes
build/crafter-debug: a POSIX sh launcher for the client that library
builds. The client is one of the library's dependencies, so a debug
build compiles it in the same pass as the app, from the same sources.
Why a launcher and not a script: every `crafter-build run` loads the
whole project first, which costs 13-28 s per call on 3DForts (a git
pull per unpinned dependency plus rescanning sources), while the
launcher is one sandbox start, 0.08 s. A client built in the Flatpak
links the Flatpak's libc++ against the runtime's glibc, so the launcher
starts it through `flatpak run` and never on the host itself; inside a
CrafterBuild sandbox it runs it directly.
The library, the client and examples/Basic are placeholders that prove
the wiring: the debug build writes a launcher whose client exists, a
plain build has no Crafter::Debug symbols and keeps the notes from
tooling/AGENTS.consumer.md, and a fresh plain build writes a stub that
names the flag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:18:07 +01:00
|
|
|
}
|