From 113627b38cb3a413e826a028968f1835dbd3f4db Mon Sep 17 00:00:00 2001 From: Jankees van Woezik Date: Sat, 29 Aug 2026 20:02:58 +0200 Subject: [PATCH] Answer --help so agents can open files in Omawrite Ask a coding agent to open a Markdown file in Omawrite and the first thing it runs is `omawrite --help`, to work out how. Today that opens a window that says "Could not open --help.", prints nothing to the terminal, and keeps running until the window is closed. So the agent hangs on a process that never exits, and never learns that Omawrite takes a file. Parse the arguments before QApplication starts, so the usage prints even without a desktop session to open a window in, and reject unknown options instead of treating them as file names. The usage also says the process stays up until the window is closed, which is the other half of what a script needs to know. --- README.md | 10 ++++++++ omawrite.pro | 2 ++ src/cli.cpp | 56 ++++++++++++++++++++++++++++++++++++++++++ src/cli.h | 18 ++++++++++++++ src/main.cpp | 6 +++++ tests/tests.pro | 2 ++ tests/tst_omawrite.cpp | 17 +++++++++++++ 7 files changed, 111 insertions(+) create mode 100644 src/cli.cpp create mode 100644 src/cli.h diff --git a/README.md b/README.md index c44ade3..ba3bf41 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,16 @@ A dead-simple Markdown writing app built with Qt Quick and C++ that automaticall Install via the Omarchy Package Repository via the `omawrite` package. It's installed by default in new installations of Omarchy (from Quattro forward). +## Command line + +`omawrite` opens an empty document, and `omawrite FILE` opens a Markdown file. +`omawrite --help` prints that usage without opening a window. + +That last one is mostly for coding agents. Ask one to open a file in Omawrite +and it runs `omawrite --help` first to work out how, so the help has to answer +in the terminal, and it has to say that the process stays up until the window +is closed. + ## Shortcuts - `Ctrl+S` saves. Unsaved documents use the XDG desktop portal file picker. diff --git a/omawrite.pro b/omawrite.pro index 422f1d8..f14efc8 100644 --- a/omawrite.pro +++ b/omawrite.pro @@ -6,12 +6,14 @@ TEMPLATE = app HEADERS += \ src/backend.h \ + src/cli.h \ src/markdownhighlighter.h \ src/systemtheme.h SOURCES += \ src/main.cpp \ src/backend.cpp \ + src/cli.cpp \ src/markdownhighlighter.cpp \ src/systemtheme.cpp diff --git a/src/cli.cpp b/src/cli.cpp new file mode 100644 index 0000000..ce7c34a --- /dev/null +++ b/src/cli.cpp @@ -0,0 +1,56 @@ +#include "cli.h" + +#include + +QString Cli::usage() { + return QStringLiteral( + "Omawrite is a dead-simple Markdown writing app.\n" + "\n" + "Usage:\n" + " omawrite [FILE]\n" + "\n" + "Opens the Omawrite window. FILE is the Markdown file to open; without\n" + "one, Omawrite starts on an empty document, or on the draft recovered\n" + "from the last session.\n" + "\n" + "Options:\n" + " -h, --help Show this message and exit\n" + "\n" + "Omawrite is a graphical app: it needs a desktop session, and it keeps\n" + "running until the window is closed. Scripts and agents that only want\n" + "a file on screen should background it:\n" + "\n" + " omawrite draft.md &\n" + "\n" + "Everything else is a keyboard shortcut. Ctrl+? lists them in the app,\n" + "and https://github.com/omacom-io/omawrite#shortcuts has the same list.\n"); +} + +std::optional Cli::handleArguments(const QStringList &arguments) { + for (const QString &argument : arguments.mid(1)) { + if (argument == QLatin1String("-h") || argument == QLatin1String("--help")) { + QTextStream(stdout) << usage(); + return 0; + } + + // A leading dash means an option was meant, not a file. Answering with + // the usage beats opening a window for a file that cannot exist. + if (argument.startsWith(QLatin1Char('-')) && argument != QLatin1String("-")) { + QTextStream(stderr) << QStringLiteral("omawrite: unrecognized option '%1'\n\n") + .arg(argument) + << usage(); + return 1; + } + } + + return std::nullopt; +} + +std::optional Cli::handleArguments(int argc, char *argv[]) { + QStringList arguments; + arguments.reserve(argc); + for (int index = 0; index < argc; ++index) + arguments.append(QString::fromLocal8Bit(argv[index])); + + return handleArguments(arguments); +} diff --git a/src/cli.h b/src/cli.h new file mode 100644 index 0000000..86e3729 --- /dev/null +++ b/src/cli.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include +#include + +// Command line handling that runs before the GUI starts, so `omawrite --help` +// answers in the terminal instead of opening a window. +namespace Cli { + +QString usage(); + +// The exit code main should return when the arguments are answered without +// starting the app, or nothing when Omawrite should open as usual. +std::optional handleArguments(const QStringList &arguments); +std::optional handleArguments(int argc, char *argv[]); + +} diff --git a/src/main.cpp b/src/main.cpp index 8b22213..d679735 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,9 +11,15 @@ #include #include "backend.h" +#include "cli.h" #include "systemtheme.h" int main(int argc, char *argv[]) { + // Answer --help before Qt claims the terminal, so the usage prints even + // without a desktop session to open a window in. + if (const std::optional exitCode = Cli::handleArguments(argc, argv)) + return *exitCode; + QApplication app(argc, argv); app.setApplicationName(QStringLiteral("omawrite")); app.setDesktopFileName(QStringLiteral("omawrite")); diff --git a/tests/tests.pro b/tests/tests.pro index 5e564a2..5e8cc7d 100644 --- a/tests/tests.pro +++ b/tests/tests.pro @@ -7,9 +7,11 @@ INCLUDEPATH += ../src SOURCES += \ tst_omawrite.cpp \ ../src/backend.cpp \ + ../src/cli.cpp \ ../src/markdownhighlighter.cpp HEADERS += \ ../src/backend.h \ + ../src/cli.h \ ../src/markdownhighlighter.h QT += widgets printsupport quickcontrols2 quickdialogs2 dbus diff --git a/tests/tst_omawrite.cpp b/tests/tst_omawrite.cpp index 5c3306a..43038b1 100644 --- a/tests/tst_omawrite.cpp +++ b/tests/tst_omawrite.cpp @@ -6,6 +6,7 @@ #include #include "backend.h" +#include "cli.h" #include "markdownhighlighter.h" class OmawriteTest : public QObject { @@ -44,6 +45,22 @@ private slots: QStringLiteral("Already.md")); } + void answersHelpBeforeOpeningAWindow() { + const QString usage = Cli::usage(); + QVERIFY(usage.contains(QStringLiteral("omawrite [FILE]"))); + QVERIFY(usage.contains(QStringLiteral("-h, --help"))); + + QCOMPARE(Cli::handleArguments({QStringLiteral("omawrite"), QStringLiteral("--help")}), + std::optional(0)); + QCOMPARE(Cli::handleArguments({QStringLiteral("omawrite"), QStringLiteral("-h")}), + std::optional(0)); + QCOMPARE(Cli::handleArguments({QStringLiteral("omawrite"), QStringLiteral("--nope")}), + std::optional(1)); + QCOMPARE(Cli::handleArguments({QStringLiteral("omawrite")}), std::nullopt); + QCOMPARE(Cli::handleArguments({QStringLiteral("omawrite"), QStringLiteral("draft.md")}), + std::nullopt); + } + void findsInlineMarkdownRanges() { const auto markup = MarkdownHighlighter::inlineMarkup( QStringLiteral("**bold** and *italic* and [site](https://example.com)"));