Last active
August 1, 2025 17:42
-
-
Save wjlafrance/078d14d885072d246a4d66dd24ff82c7 to your computer and use it in GitHub Desktop.
Vibe-coded net controller script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// authored entirely by ChatGPT with my prompting | |
// radioCheckInCLI.ts | |
import * as readline from "readline"; | |
import * as fs from "fs"; | |
import * as path from "path"; | |
interface CheckIn { | |
name?: string; | |
callsign: string; | |
location?: string; | |
traffic: "NTS" | "Local" | "Informal" | "None"; | |
summary?: string; | |
timestamp: string; | |
} | |
interface Regular { | |
name: string; | |
callsign: string; | |
location: string; | |
} | |
const rl = readline.createInterface({ | |
input: process.stdin, | |
output: process.stdout | |
}); | |
let checkIns: CheckIn[] = []; | |
let regulars: Regular[] = []; | |
function loadRegulars() { | |
try { | |
const data = fs.readFileSync(path.join(process.cwd(), "regulars.json"), "utf-8"); | |
regulars = JSON.parse(data); | |
} catch { | |
console.warn("⚠️ Could not load regulars.json — continuing without it."); | |
} | |
} | |
function saveCheckInsToFile() { | |
const date = new Date().toISOString().split("T")[0]; | |
fs.writeFileSync(`${date}_checkins.json`, JSON.stringify(checkIns, null, 2)); | |
} | |
function ask(question: string): Promise<string> { | |
return new Promise((resolve) => rl.question(question, resolve)); | |
} | |
async function startCheckInLoop() { | |
let keepGoing = true; | |
while (keepGoing) { | |
const keepGoingAfter = await startCheckIn(); | |
keepGoing = keepGoingAfter; | |
} | |
} | |
async function startCheckIn(): Promise<boolean> { | |
const entry: Partial<CheckIn> = { traffic: "None" }; | |
while (!entry.callsign || entry.callsign.trim() === "") { | |
const callsignInput = (await ask("Enter callsign (required): ")).trim(); | |
if (callsignInput === "") { | |
return false; // return to main menu if empty | |
} | |
entry.callsign = callsignInput.toUpperCase(); | |
} | |
const regular = regulars.find( | |
(r) => r.callsign.toLowerCase() === entry.callsign!.toLowerCase() | |
); | |
if (regular) { | |
console.log(`Matched regular: ${regular.name} from ${regular.location}`); | |
entry.name = regular.name; | |
entry.location = regular.location; | |
} | |
let done = false; | |
while (!done) { | |
console.log(`\n📝 Check-in for ${entry.callsign} | |
[c] Callsign: ${entry.callsign} | |
[n] Name: ${entry.name || "(not set)"} | |
[l] Location: ${entry.location || "(not set)"} | |
[t] Traffic: ${entry.traffic} | |
[s] Save | |
[a] Save and another | |
[x] Cancel | |
`); | |
const choice = (await ask("Select field: ")).toLowerCase(); | |
switch (choice) { | |
case "c": | |
entry.callsign = (await ask("Enter callsign: ")).trim().toUpperCase(); | |
break; | |
case "n": | |
entry.name = (await ask("Enter name: ")).trim(); | |
break; | |
case "l": | |
entry.location = (await ask("Enter location: ")).trim(); | |
break; | |
case "t": | |
console.log("[1] NTS\n[2] Local\n[3] Informal\n[4] None"); | |
const trafficChoice = await ask("Select traffic type: "); | |
entry.traffic = | |
trafficChoice === "1" | |
? "NTS" | |
: trafficChoice === "2" | |
? "Local" | |
: trafficChoice === "3" | |
? "Informal" | |
: "None"; | |
break; | |
case "s": | |
case "a": | |
if ( | |
entry.callsign || | |
(entry.name && entry.name.trim()) || | |
(entry.location && entry.location.trim()) | |
) { | |
entry.timestamp = new Date().toISOString(); | |
checkIns.push(entry as CheckIn); | |
console.log("✅ Check-in recorded."); | |
saveCheckInsToFile(); | |
await new Promise((res) => setTimeout(res, 500)); | |
return choice === "a"; // true if user chose 'a' (Save and another) | |
} else { | |
console.log("⚠️ Please complete at least one of Name, Callsign, or Location before saving."); | |
} | |
break; | |
case "x": | |
console.log("❌ Check-in canceled."); | |
done = true; | |
break; | |
default: | |
console.log("Unknown option."); | |
} | |
} | |
return false; | |
} | |
async function takeTraffic() { | |
const sorted = [...checkIns].sort((a, b) => { | |
const priority = { NTS: 1, Local: 2, Informal: 3, None: 4 }; | |
return priority[a.traffic] - priority[b.traffic]; | |
}); | |
let done = false; | |
while (!done) { | |
console.log("\n📻 Traffic Check — Select a station:"); | |
sorted.forEach((c, i) => { | |
console.log(`[${i + 1}] ${c.callsign} (${c.name || "Unknown"}) — ${c.traffic}`); | |
}); | |
console.log("[x] Back to main menu\n"); | |
const input = (await ask("Select station: ")).toLowerCase(); | |
if (input === "x") { | |
done = true; | |
} else { | |
const index = parseInt(input) - 1; | |
if (index >= 0 && index < sorted.length) { | |
const station = sorted[index]; | |
station.summary = await ask("Enter summary of traffic: "); | |
console.log("✅ Summary recorded."); | |
saveCheckInsToFile(); | |
} else { | |
console.log("Invalid selection."); | |
} | |
} | |
} | |
} | |
async function main() { | |
loadRegulars(); | |
let done = false; | |
while (!done) { | |
console.log("\n📋 Net Control Menu\n[n] New check-in\n[l] List check-ins\n[t] Take traffic\n[q] Quit\n"); | |
const input = (await ask("Select an option: ")).toLowerCase(); | |
switch (input) { | |
case "n": | |
await startCheckInLoop(); | |
break; | |
case "l": | |
console.log("\nCurrent check-ins:"); | |
checkIns.forEach((c, i) => { | |
console.log( | |
`${i + 1}. ${c.callsign} - ${c.name || "Unknown"} - ${c.location || "Unknown"} - ${c.traffic}` | |
); | |
}); | |
break; | |
case "t": | |
await takeTraffic(); | |
break; | |
case "q": | |
done = true; | |
break; | |
default: | |
console.log("Unknown option."); | |
} | |
} | |
rl.close(); | |
} | |
main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment