// // MetrikaCLI.swift // Metrika // // Author: Simon-Pierre Boucher // Contact: contact@spboucher.ai // Copyright © 2026 Simon-Pierre Boucher. All rights reserved. // import ArgumentParser import Foundation import ZQEngine /// Companion command-line tool: runs ZQL commands or `.zyq` do-files /// against a headless session (default subcommand), and generates the /// documentation site (`docs`). @main struct MetrikaCLI: AsyncParsableCommand { static let configuration = CommandConfiguration( commandName: "metrika-cli", abstract: "Metrika statistical engine — command-line interface.", subcommands: [RunCommand.self, DocsCommand.self], defaultSubcommand: RunCommand.self ) } /// Default behavior: execute commands, run a do-file, or start a REPL. struct RunCommand: AsyncParsableCommand { static let configuration = CommandConfiguration( commandName: "run", abstract: "Execute ZQL commands or a do-file (default)." ) @Option( name: [.short, .customLong("execute")], help: "Execute a ZQL command (repeatable, runs in order)." ) var execute: [String] = [] @Argument(help: "A .zyq do-file to run.") var doFile: String? mutating func run() async throws { let session = try ZQSession() if let doFile { let url = URL(fileURLWithPath: (doFile as NSString).expandingTildeInPath) let text = try String(contentsOf: url, encoding: .utf8) let result = try await session.executeScript(text) print(result.text) return } if !execute.isEmpty { for command in execute { let result = try await session.execute(command) if !result.text.isEmpty { print(result.text) } } return } // REPL print("Metrika — type ZQL commands, 'exit' to quit.") while true { print(". ", terminator: "") guard let line = readLine() else { break } if line == "exit" || line == "quit" { break } do { let result = try await session.execute(line) if !result.text.isEmpty { print(result.text) } } catch { print("error: \(error)") } } } }