mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-08-14 06:47:28 +00:00
Add waiting arcade MVP
This commit is contained in:
parent
b1d8fe475a
commit
4682b261c0
16 changed files with 1192 additions and 2 deletions
87
packages/cli/src/config.ts
Normal file
87
packages/cli/src/config.ts
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { dirname, join } from "node:path";
|
||||
import { homedir } from "node:os";
|
||||
|
||||
export type JsonObject = Record<string, unknown>;
|
||||
|
||||
export const defaultConfig: JsonObject = {
|
||||
waiting: {
|
||||
arcade: {
|
||||
enabled: true,
|
||||
defaultGame: "hangman",
|
||||
random: false,
|
||||
autoStartAfterSeconds: 0,
|
||||
interruptOnApproval: true,
|
||||
interruptOnDone: true,
|
||||
interruptOnError: true,
|
||||
showTaskStatusOverlay: true
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export function configPath() {
|
||||
return join(homedir(), ".logicsrc", "config.json");
|
||||
}
|
||||
|
||||
export function readConfig() {
|
||||
const file = configPath();
|
||||
if (!existsSync(file)) {
|
||||
return structuredClone(defaultConfig);
|
||||
}
|
||||
const parsed = JSON.parse(readFileSync(file, "utf8")) as JsonObject;
|
||||
return mergeConfig(structuredClone(defaultConfig), parsed);
|
||||
}
|
||||
|
||||
export function writeConfig(config: JsonObject) {
|
||||
const file = configPath();
|
||||
mkdirSync(dirname(file), { recursive: true });
|
||||
writeFileSync(file, JSON.stringify(config, null, 2) + "\n");
|
||||
return file;
|
||||
}
|
||||
|
||||
export function getConfigValue(path: string, config = readConfig()) {
|
||||
return path.split(".").reduce<unknown>((current, key) => (isObject(current) ? current[key] : undefined), config);
|
||||
}
|
||||
|
||||
export function setConfigValue(path: string, rawValue: string, config = readConfig()) {
|
||||
const parts = path.split(".").filter(Boolean);
|
||||
if (parts.length === 0) {
|
||||
throw new Error("Config path cannot be empty.");
|
||||
}
|
||||
let current: JsonObject = config;
|
||||
for (const part of parts.slice(0, -1)) {
|
||||
if (!isObject(current[part])) {
|
||||
current[part] = {};
|
||||
}
|
||||
current = current[part] as JsonObject;
|
||||
}
|
||||
current[parts[parts.length - 1] as string] = parseConfigValue(rawValue);
|
||||
return config;
|
||||
}
|
||||
|
||||
export function parseConfigValue(value: string): unknown {
|
||||
if (value === "true") return true;
|
||||
if (value === "false") return false;
|
||||
if (value === "null") return null;
|
||||
if (/^-?\d+(\.\d+)?$/.test(value)) return Number(value);
|
||||
try {
|
||||
return JSON.parse(value) as unknown;
|
||||
} catch {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
function mergeConfig(base: JsonObject, override: JsonObject): JsonObject {
|
||||
for (const [key, value] of Object.entries(override)) {
|
||||
if (isObject(value) && isObject(base[key])) {
|
||||
base[key] = mergeConfig(base[key] as JsonObject, value);
|
||||
} else {
|
||||
base[key] = value;
|
||||
}
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
function isObject(value: unknown): value is JsonObject {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
|
@ -1,22 +1,61 @@
|
|||
#!/usr/bin/env node
|
||||
import { readFileSync } from "node:fs";
|
||||
import { Command } from "commander";
|
||||
import { renderPluginStatus, renderTui } from "@logicsrc/tui";
|
||||
import { renderArcadeList, renderPluginStatus, renderTui, runArcadeSession, type TaskSnapshot } from "@logicsrc/tui";
|
||||
import { assertSchemaKind, parseDocument, validate } from "@logicsrc/validators";
|
||||
import { getConfigValue, readConfig, setConfigValue, writeConfig } from "./config.js";
|
||||
import { boards, tasks } from "./fixtures.js";
|
||||
import { print, type OutputFormat } from "./format.js";
|
||||
import { exportOpenSpecSummary, importOpenSpec, writeOpenSpecChange } from "./openspec.js";
|
||||
import { defaultPluginRegistry } from "./registry.js";
|
||||
|
||||
process.stdout.on("error", (error: NodeJS.ErrnoException) => {
|
||||
if (error.code === "EPIPE") {
|
||||
process.exit(0);
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
|
||||
const program = new Command();
|
||||
program.enablePositionalOptions();
|
||||
|
||||
program
|
||||
.name("logicsrc")
|
||||
.description("LogicSRC OpenSpec CLI for schemas, boards, tasks, agents, payments, plugins, and TUI.")
|
||||
.option("--openspec", "Enable OpenSpec.dev-compatible repo-local specs, proposals, tasks, and deltas where supported.")
|
||||
.option("--openspec-only", "Restrict workflows to LogicSRC OpenSpec schemas, SDKs, MCP, CLI, TUI, and PWA contracts.")
|
||||
.option("--yolo", "Start the default AgentSwarm YOLO flow.")
|
||||
.option("--arcade [game]", "Launch Waiting Arcade while a long-running task executes.")
|
||||
.option("--waiting-arcade", "Alias for --arcade.")
|
||||
.option("--waiting-game <game>", "Alias for --arcade=<game>.")
|
||||
.option("--no-arcade", "Disable Waiting Arcade.")
|
||||
.version("0.1.0");
|
||||
|
||||
program.action(async (options) => {
|
||||
if (!options.yolo) {
|
||||
program.outputHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
const arcadeGame = resolveArcadeGame(options);
|
||||
if (arcadeGame) {
|
||||
await runYoloArcade(arcadeGame);
|
||||
return;
|
||||
}
|
||||
|
||||
print(
|
||||
{
|
||||
type: "logicsrc.agentswarm.session",
|
||||
status: "opening",
|
||||
mode: "yolo",
|
||||
master_agent: "agentswarm-master",
|
||||
slave_agents: ["reproduce", "patch", "review"],
|
||||
arcade: false
|
||||
},
|
||||
"json"
|
||||
);
|
||||
});
|
||||
|
||||
program
|
||||
.command("login")
|
||||
.option("--did <did>", "CoinPay DID")
|
||||
|
|
@ -124,16 +163,59 @@ program.command("events").description("Listen to LogicSRC event stream.").argume
|
|||
console.log(JSON.stringify({ type: "logicsrc.event", event: "task.created", resource_id: "task_789" }));
|
||||
});
|
||||
|
||||
const arcade = program.command("arcade").description("Play Waiting Arcade games.");
|
||||
|
||||
arcade.action(async () => {
|
||||
await runArcadeSession({ game: process.env.LOGICSRC_ARCADE_GAME || "hangman", standalone: true });
|
||||
});
|
||||
|
||||
arcade.command("list").description("List built-in Waiting Arcade games.").action(() => {
|
||||
console.log(renderArcadeList());
|
||||
});
|
||||
|
||||
arcade
|
||||
.command("play")
|
||||
.argument("[game]", "Game id or random", "hangman")
|
||||
.description("Play a standalone Waiting Arcade game.")
|
||||
.action(async (game) => {
|
||||
await runArcadeSession({ game, standalone: true });
|
||||
});
|
||||
|
||||
const config = program.command("config").description("Read and write LogicSRC config.");
|
||||
|
||||
config
|
||||
.command("get")
|
||||
.argument("<path>", "Dot-path config key")
|
||||
.description("Print a config value.")
|
||||
.action((path) => {
|
||||
console.log(JSON.stringify(getConfigValue(path), null, 2));
|
||||
});
|
||||
|
||||
config
|
||||
.command("set")
|
||||
.argument("<path>", "Dot-path config key")
|
||||
.argument("<value>", "JSON, boolean, number, or string value")
|
||||
.description("Set a config value.")
|
||||
.action((path, value) => {
|
||||
const next = setConfigValue(path, value);
|
||||
const file = writeConfig(next);
|
||||
console.log(`Set ${path} in ${file}`);
|
||||
});
|
||||
|
||||
program
|
||||
.command("agentswarm")
|
||||
.alias("agent-swarm")
|
||||
.description("Open an AgentSwarm master agent session.")
|
||||
.option("--yolo", "Start the master agent with autonomous execution enabled.")
|
||||
.option("--arcade [game]", "Launch Waiting Arcade while AgentSwarm runs.")
|
||||
.option("--waiting-arcade", "Alias for --arcade.")
|
||||
.option("--waiting-game <game>", "Alias for --arcade=<game>.")
|
||||
.option("--no-arcade", "Disable Waiting Arcade.")
|
||||
.option("--repo <repo>", "Target repository, for example profullstack/logicsrc")
|
||||
.option("--agents <agents>", "Comma-separated slave agent roles", "reproduce,patch,review")
|
||||
.option("--change <id>", "OpenSpec-compatible change id", "agentswarm-yolo")
|
||||
.option("--out <dir>", "OpenSpec-compatible output directory")
|
||||
.action((options) => {
|
||||
.action(async (options) => {
|
||||
if (!options.yolo) {
|
||||
console.log("AgentSwarm is coming soon. Run `logicsrc agentswarm --yolo` to open the master agent flow.");
|
||||
return;
|
||||
|
|
@ -157,6 +239,12 @@ program
|
|||
})
|
||||
: null;
|
||||
|
||||
const arcadeGame = resolveArcadeGame(options);
|
||||
if (arcadeGame) {
|
||||
await runYoloArcade(arcadeGame, options.repo);
|
||||
return;
|
||||
}
|
||||
|
||||
print({
|
||||
type: "logicsrc.agentswarm.session",
|
||||
status: "opening",
|
||||
|
|
@ -284,6 +372,49 @@ function toTaskSchema(item: (typeof tasks)[number]) {
|
|||
};
|
||||
}
|
||||
|
||||
function resolveArcadeGame(options: { arcade?: string | boolean; waitingArcade?: boolean; waitingGame?: string }) {
|
||||
if (options.arcade === false || process.env.LOGICSRC_NO_ARCADE === "1") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (options.waitingGame) {
|
||||
return options.waitingGame;
|
||||
}
|
||||
|
||||
if (typeof options.arcade === "string") {
|
||||
return options.arcade;
|
||||
}
|
||||
|
||||
const config = readConfig();
|
||||
const configuredDefault = String(getConfigValue("waiting.arcade.defaultGame", config) ?? "hangman");
|
||||
|
||||
if (options.arcade === true || options.waitingArcade || process.env.LOGICSRC_ARCADE === "1") {
|
||||
return process.env.LOGICSRC_ARCADE_GAME || configuredDefault;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async function runYoloArcade(game: string, repo?: string) {
|
||||
const task: TaskSnapshot = {
|
||||
id: "agentswarm_yolo",
|
||||
title: repo ? `AgentSwarm YOLO on ${repo}` : "AgentSwarm YOLO session",
|
||||
status: "running",
|
||||
phase: "starting",
|
||||
progress: 0.05,
|
||||
costUsd: 0,
|
||||
lastMessage: "launching Waiting Arcade"
|
||||
};
|
||||
|
||||
await runArcadeSession({
|
||||
game,
|
||||
standalone: false,
|
||||
task,
|
||||
simulateTask: true,
|
||||
logs: ["AgentSwarm master session started.", "Task continues while Waiting Arcade is active."]
|
||||
});
|
||||
}
|
||||
|
||||
program.parseAsync(process.argv).catch((error: unknown) => {
|
||||
console.error(error instanceof Error ? error.message : String(error));
|
||||
process.exitCode = 1;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue