feat: Effect services + HttpApi implementation + kit entry/CLI/dev server
- RomConfig/RomCache on the kit's ConfigService/CacheStore; RomSync wires the pure domain into the kit SyncEngine (poll/watch/coalesce/fingerprint) and ProviderClient; EngineStatus assembly shared by REST + SSE - makeApi: HttpApiBuilder groups over live service values (handler runtime shares the plugin runtime's singletons) + sseRoute status feed - index.ts: definePluginKit entry (async-main boundary); headless-first (UI serve failure logged, engine continues) - cli.ts on the kit dispatcher: scan/detect/preview offline, sync/uninstall online; set-password is gone with the standalone server - dev.ts: auth-free loopback API server (:5885) — the dev:live proxy target; never bundled - verified live host-less: raw config round-trip on disk, status/platforms/ preview endpoints, soft-fail reconcile without a host - CI: workspace install, per-package typecheck, publish from plugin/ Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env bun
|
||||
// Ops CLI on the kit's dispatcher — the same layer graph as the plugin entry, so every
|
||||
// command sees the exact services the runner runs. scan/detect/preview work offline;
|
||||
// sync/uninstall talk to the host.
|
||||
import {
|
||||
type CliCommand,
|
||||
ProviderClient,
|
||||
runPluginCli,
|
||||
} from "@punktfunk/plugin-kit";
|
||||
import { Effect } from "effect";
|
||||
import pkg from "../package.json" with { type: "json" };
|
||||
import { PLUGIN_NAME } from "./index.js";
|
||||
import { type RomServices, romLayer } from "./layer.js";
|
||||
import { PROVIDER_ID, RomSync } from "./services/engine.js";
|
||||
|
||||
const print = (line: string) => Effect.sync(() => console.log(line));
|
||||
|
||||
const commands: Record<string, CliCommand<RomServices>> = {
|
||||
scan: {
|
||||
summary: "Scan configured ROM roots and list candidates",
|
||||
offline: true,
|
||||
run: () =>
|
||||
Effect.gen(function* () {
|
||||
const sync = yield* RomSync;
|
||||
const { report } = yield* sync.preview;
|
||||
yield* print(
|
||||
`${report.considered} candidate(s), ${report.included} would be included`,
|
||||
);
|
||||
for (const [platform, n] of Object.entries(report.perPlatform)) {
|
||||
yield* print(` ${platform}: ${n}`);
|
||||
}
|
||||
}),
|
||||
},
|
||||
detect: {
|
||||
summary: "Probe for installed emulators",
|
||||
offline: true,
|
||||
run: () =>
|
||||
Effect.gen(function* () {
|
||||
const sync = yield* RomSync;
|
||||
const detected = yield* sync.detect(true);
|
||||
for (const d of detected) {
|
||||
yield* print(
|
||||
`${d.id.padEnd(14)} ${d.via.padEnd(8)} ${d.exePath ?? d.exeToken}${d.cores ? ` (${d.cores.length} cores)` : ""}`,
|
||||
);
|
||||
}
|
||||
if (detected.length === 0) yield* print("no emulators found");
|
||||
}),
|
||||
},
|
||||
preview: {
|
||||
summary: "Dry-run: show the entries a sync would reconcile",
|
||||
offline: true,
|
||||
run: () =>
|
||||
Effect.gen(function* () {
|
||||
const sync = yield* RomSync;
|
||||
const { entries, report } = yield* sync.preview;
|
||||
for (const e of entries) yield* print(`${e.external_id} ${e.title}`);
|
||||
for (const s of report.skipped) {
|
||||
yield* print(`SKIP ${s.external_id}: ${s.reason}`);
|
||||
}
|
||||
yield* print(
|
||||
`${report.included} included, ${report.skipped.length} skipped`,
|
||||
);
|
||||
}),
|
||||
},
|
||||
sync: {
|
||||
summary: "Reconcile the library provider now",
|
||||
run: () =>
|
||||
Effect.gen(function* () {
|
||||
const sync = yield* RomSync;
|
||||
const outcome = yield* sync.engine.sync("manual");
|
||||
yield* print(
|
||||
outcome._tag === "AlreadyRunning"
|
||||
? "a sync is already running"
|
||||
: `${outcome._tag}: ${outcome.report.included} entries`,
|
||||
);
|
||||
}),
|
||||
},
|
||||
uninstall: {
|
||||
summary: "Remove every rom-manager entry from the host library",
|
||||
run: () =>
|
||||
Effect.gen(function* () {
|
||||
const provider = yield* ProviderClient;
|
||||
yield* provider.remove(PROVIDER_ID);
|
||||
yield* print("provider entries removed");
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
await runPluginCli({
|
||||
def: {
|
||||
name: PLUGIN_NAME,
|
||||
version: pkg.version,
|
||||
layer: romLayer,
|
||||
main: Effect.void,
|
||||
},
|
||||
commands,
|
||||
});
|
||||
Reference in New Issue
Block a user