Add interactive mode with menu-driven resource editing
Running with no flags launches an interactive menu powered by @inquirer/prompts. Supports chaining multiple actions (max resources, set crafted, geology, time) before saving once to clipboard. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
207
index.js
207
index.js
@@ -162,9 +162,11 @@ function main() {
|
||||
|
||||
const hasAction = flags.has("--max-resources") || flags.has("--max-time") || flags.has("--max-geology") || flags.has("--list") || craftedValue !== null;
|
||||
|
||||
if (flags.has("--help") || !hasAction) {
|
||||
if (flags.has("--help")) {
|
||||
console.log(`Usage: node index.js [options] [save-string]
|
||||
|
||||
Running with no flags launches interactive mode.
|
||||
|
||||
Options:
|
||||
--list List all resources in the save with current values
|
||||
--max-resources Set all capped resources to their max
|
||||
@@ -176,32 +178,19 @@ Options:
|
||||
--help Show this help
|
||||
|
||||
Save string can be passed as argument, piped via stdin, or read from clipboard.`);
|
||||
process.exit(flags.has("--help") ? 0 : 1);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
let input;
|
||||
if (positional.length > 0) {
|
||||
input = positional[0].trim();
|
||||
} else if (!process.stdin.isTTY) {
|
||||
try {
|
||||
input = require("fs").readFileSync("/dev/stdin", "utf-8").trim();
|
||||
} catch {
|
||||
// stdin not readable (e.g. piped but empty), fall through to clipboard
|
||||
}
|
||||
if (!hasAction && process.stdin.isTTY) {
|
||||
return interactiveMode();
|
||||
}
|
||||
|
||||
if (!input) {
|
||||
const clip = readFromClipboard();
|
||||
if (clip) {
|
||||
console.log("Reading save data from clipboard...");
|
||||
input = clip;
|
||||
} else {
|
||||
console.error("Error: No save data provided. Pass as argument, pipe via stdin, or copy to clipboard.");
|
||||
process.exit(1);
|
||||
}
|
||||
if (!hasAction) {
|
||||
console.error("Error: No action specified. Use --help for usage or run without flags for interactive mode.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const data = decodeSave(input);
|
||||
const { data, noCopy } = loadSave(positional, flags.has("--no-copy"));
|
||||
|
||||
if (flags.has("--list")) {
|
||||
listResources(data);
|
||||
@@ -245,9 +234,39 @@ Save string can be passed as argument, piped via stdin, or read from clipboard.`
|
||||
}
|
||||
}
|
||||
|
||||
outputResult(data, noCopy);
|
||||
}
|
||||
|
||||
function loadSave(positional, noCopy) {
|
||||
let input;
|
||||
if (positional.length > 0) {
|
||||
input = positional[0].trim();
|
||||
} else if (!process.stdin.isTTY) {
|
||||
try {
|
||||
input = require("fs").readFileSync("/dev/stdin", "utf-8").trim();
|
||||
} catch {
|
||||
// stdin not readable (e.g. piped but empty), fall through to clipboard
|
||||
}
|
||||
}
|
||||
|
||||
if (!input) {
|
||||
const clip = readFromClipboard();
|
||||
if (clip) {
|
||||
console.log("Reading save data from clipboard...");
|
||||
input = clip;
|
||||
} else {
|
||||
console.error("Error: No save data provided. Pass as argument, pipe via stdin, or copy to clipboard.");
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
return { data: decodeSave(input), noCopy };
|
||||
}
|
||||
|
||||
function outputResult(data, noCopy) {
|
||||
const result = encodeSave(data);
|
||||
|
||||
if (flags.has("--no-copy")) {
|
||||
if (noCopy) {
|
||||
console.log(result);
|
||||
} else if (copyToClipboard(result)) {
|
||||
console.log("Edited save copied to clipboard.");
|
||||
@@ -257,4 +276,148 @@ Save string can be passed as argument, piped via stdin, or read from clipboard.`
|
||||
}
|
||||
}
|
||||
|
||||
function getResourceChoices(data, type) {
|
||||
const choices = [];
|
||||
for (const [key, res] of Object.entries(data.resource || {})) {
|
||||
const name = res.name || key;
|
||||
const amount = res.amount ?? 0;
|
||||
const max = res.max ?? 0;
|
||||
|
||||
if (type === "capped" && max > 0) {
|
||||
const full = amount >= max;
|
||||
choices.push({
|
||||
value: key,
|
||||
name: `${name} (${Math.floor(amount)} / ${max})${full ? " [full]" : ""}`,
|
||||
checked: !full,
|
||||
});
|
||||
} else if (type === "crafted" && max === -1) {
|
||||
choices.push({
|
||||
value: key,
|
||||
name: `${name} (${Math.floor(amount)})`,
|
||||
checked: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
return choices;
|
||||
}
|
||||
|
||||
async function interactiveMode() {
|
||||
const { select, checkbox, confirm, input } = require("@inquirer/prompts");
|
||||
|
||||
const { data } = loadSave([], false);
|
||||
|
||||
let modified = false;
|
||||
|
||||
while (true) {
|
||||
const action = await select({
|
||||
message: "What would you like to do?",
|
||||
choices: [
|
||||
{ value: "max-resources", name: "Max capped resources" },
|
||||
{ value: "set-crafted", name: "Set crafted resources" },
|
||||
{ value: "max-geology", name: "Max geology bonuses" },
|
||||
{ value: "max-time", name: "Max accelerated time" },
|
||||
{ value: "list", name: "List all resources" },
|
||||
...(modified ? [{ value: "save", name: "Save & copy to clipboard" }] : []),
|
||||
{ value: "exit", name: "Exit" },
|
||||
],
|
||||
});
|
||||
|
||||
if (action === "exit") {
|
||||
if (modified) {
|
||||
const discard = await confirm({ message: "Discard unsaved changes?", default: false });
|
||||
if (!discard) continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (action === "save") {
|
||||
outputResult(data, false);
|
||||
break;
|
||||
}
|
||||
|
||||
if (action === "list") {
|
||||
listResources(data);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (action === "max-time") {
|
||||
const MAX_AT = 11520;
|
||||
const old = data.settings?.at ?? 0;
|
||||
if (old < MAX_AT) {
|
||||
data.settings.at = MAX_AT;
|
||||
console.log(`Accelerated time: ${old} -> ${MAX_AT} (adjusts to 8 hours on load)`);
|
||||
modified = true;
|
||||
} else {
|
||||
console.log(`Accelerated time already at ${old} (>= max).`);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (action === "max-geology") {
|
||||
console.log(setGeology(data));
|
||||
modified = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (action === "max-resources") {
|
||||
const choices = getResourceChoices(data, "capped");
|
||||
if (choices.length === 0) {
|
||||
console.log("No capped resources found.");
|
||||
continue;
|
||||
}
|
||||
const selected = await checkbox({
|
||||
message: "Select resources to max (space to toggle, enter to confirm)",
|
||||
choices,
|
||||
});
|
||||
if (selected.length === 0) {
|
||||
console.log("No resources selected.");
|
||||
continue;
|
||||
}
|
||||
const filter = new Set(selected.map((s) => s.toLowerCase()));
|
||||
const changed = maxResources(data, filter);
|
||||
if (changed.length > 0) {
|
||||
console.log(`Maxed ${changed.length} resources:`);
|
||||
changed.forEach((line) => console.log(line));
|
||||
modified = true;
|
||||
} else {
|
||||
console.log("Selected resources already at max.");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (action === "set-crafted") {
|
||||
const choices = getResourceChoices(data, "crafted");
|
||||
if (choices.length === 0) {
|
||||
console.log("No crafted resources found.");
|
||||
continue;
|
||||
}
|
||||
const value = await input({
|
||||
message: "Set crafted resources to what value?",
|
||||
validate: (v) => {
|
||||
const n = Number(v);
|
||||
return !isNaN(n) && n >= 0 ? true : "Enter a non-negative number";
|
||||
},
|
||||
});
|
||||
const selected = await checkbox({
|
||||
message: "Select resources to set (space to toggle, enter to confirm)",
|
||||
choices,
|
||||
});
|
||||
if (selected.length === 0) {
|
||||
console.log("No resources selected.");
|
||||
continue;
|
||||
}
|
||||
const filter = new Set(selected.map((s) => s.toLowerCase()));
|
||||
const changed = setCraftedResources(data, Number(value), filter);
|
||||
if (changed.length > 0) {
|
||||
console.log(`Set ${changed.length} crafted resources to ${value}:`);
|
||||
changed.forEach((line) => console.log(line));
|
||||
modified = true;
|
||||
} else {
|
||||
console.log("Selected resources already at or above target.");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
Reference in New Issue
Block a user