Compare commits

..
15 Commits
Author SHA1 Message Date
me acb4f38f71 Merge pull request 'Add Root Details' (#2) from dev into main
Reviewed-on: #2
2026-09-12 13:15:30 -07:00
me 41c80130d5 Reference that there is wiki info! 2026-09-12 13:12:45 -07:00
me 5bb9e51bfe Add planned directories with blank README's. 2026-09-12 13:09:23 -07:00
me 0ccd900219 Merge pull request 'prototype.js' (#1) from prod into main
Reviewed-on: #1
2026-09-12 13:07:29 -07:00
me f433cd87a8 Update the README for the main project with a planned directory structure 2026-09-12 13:06:06 -07:00
me 68aba793b9 Add a README for the CLI. 2026-09-12 12:58:04 -07:00
me b3d3573fae Move Node.js prototype to a separate cli folder. Works great! 2026-09-12 12:52:16 -07:00
me bd02d4611a Add usage as well as fix the global install to work properly with node_modules. 2026-09-12 12:48:22 -07:00
me d70c5d3069 install, add a remove option. 2026-09-12 12:25:28 -07:00
me cde66dcd67 Change script to actually install the Node,js file, including in the global bin. 2026-09-12 12:21:02 -07:00
me 64e9abb85c Add script which allows the CLI tool to be run by local user. 2026-09-12 12:06:20 -07:00
me 7b9839b50e Add paramater so that provider calculates the elevation. This is the default in the API, but not with this program. 2026-09-12 11:01:38 -07:00
me 1893979909 Finalize the locations, for now. :) 2026-09-12 10:40:56 -07:00
me 1928fc0116 Change default day values and add a few more specific locations to the array. 2026-09-12 10:23:36 -07:00
me 806dc5967e Add a gitignore and the package-lock.json. 2026-09-12 10:00:40 -07:00
10 changed files with 429 additions and 24 deletions

No files matched your search

+18 -2
View File
@@ -1,3 +1,19 @@
# template-mit # Nomadic Weather
Template with MIT license, protected `main` branch, and available `dev` branch. See the [wiki](https://git.hyperling.com/me/nomadic-weather/wiki) for more information.
## cli
Programs made while testing with the API endpoint.
## doc
Documentation.
## gui
Flutter project directed at Android and Linux.
## srv
Backend code.
+34
View File
@@ -0,0 +1,34 @@
# Dependencies
node_modules/
# Build output
dist/
build/
out/
# Environment variables
.env
.env.local
.env.*.local
!.env.example
# Logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Caches
.npm
.eslintcache
.cache/
coverage/
# OS
.DS_Store
Thumbs.db
# IDE
.idea/
.vscode/
*.swp
+18
View File
@@ -0,0 +1,18 @@
# Nomadic Weather CLI
Prototype for playing with [Open-Meteo](https://open-meteo.com).
Locations need modified manually since it's still just a prototype.
## prototype.js
Got a little carried away and made it pretty formal.
Still a prototype though since the locations are hardcoded.
Would be made more complete by accepting a list of values, an env file,
or basically just any way of handling the locations as user input.
## install.sh
Allows the script to be installed either in ~/bin/ or /usr/local/bin/.
Also handles removal if it is no longer wanted.
Currently does not remove from /usr/local/src/.
View File
Whitespace-only changes.
View File
Whitespace-only changes.
Executable
+110
View File
@@ -0,0 +1,110 @@
#!/usr/bin/env bash
# 20260912 - Hyperling
# "Install" script to get this program runnable from anywhere on the system.
## Variables ##
DIR="$(cd "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"
PROG="$(basename -- "${BASH_SOURCE[0]}")"
LOCAL=~/bin
GLOBAL=/usr/local/bin
GLOBAL_SRC=/usr/local/src
name_local="prototype.js"
name_short="nomad"
name_long="nomadic-weather"
name_abs="weather-list"
## Functions ##
function usage {
cat <<- EOF
$PROG [-h] [-g] [a|l|s] [-r]
Install $name_local to the system. Can be local, global, or removed.
Parameters)
h) Display this message.
g) Install globally ($GLOBAL), otherwise local ($LOCAL).
a) Abstract name, unrelated to being nomadic. ($name_abs)
l) Long name, the full name of the project. ($name_long)
s) Short name, the full name of the project. ($name_short)
r) Remove / uninstall the project according to the above flags.
Examples)
- Install to the local user with the abstract name.
$PROG
- Install to all users with all names.
$PROG -alsg
- Remove all instances.
$PROG -alsr && $PROG -alsrg
Happy weather watching! :)
~ Hyperling
EOF
exit $1
}
## Parameters ##
while getopts ":gslarh" arg; do
case $arg in
g) global="Y" ;;
s) short="Y" ;;
l) long="Y" ;;
a) abstract="Y" ;;
r) remove="Y" ;;
h) usage ;;
*) echo "ERROR: Parameter $OPTARG was not recognized." && usage 1 ;;
esac
done
## Processing ##
name=""
if [[ "$short" == "Y" ]]; then
name="$name $name_short"
fi
if [[ "$long" == "Y" ]]; then
name="$name $name_long"
fi
if [[ "$abstract" == "Y" || ( -z "$short" && -z "$long" ) ]]; then
name="$name $name_abs"
fi
cmd=""
dir="$LOCAL"
src="$DIR"
if [[ "$global" == "Y" ]]; then
dir="$GLOBAL"
cmd="sudo"
echo "Requesting sudo privileges... "
sudo echo "Success!"
mkdir -pv /usr/local/src
sudo cp -Rv "$DIR" $GLOBAL_SRC/
src="$GLOBAL_SRC/`basename "$DIR"`"
fi
if [[ -z "$remove" ]]; then
cmd="$cmd ln -svf $src/$name_local"
else
cmd="$cmd rm -v"
fi
## Main ##
# Untested
#if [[ "$global" == "Y" && "$remove" == "Y" ]]; then
# sudo rm -rfv "$src"
#fi
for prog in $name; do
mkdir -pv "$dir"
$cmd "$dir/$prog"
done
## Finish ##
echo "Done!"
exit 0
+203
View File
@@ -0,0 +1,203 @@
{
"name": "nomadic-weather",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"yargs": "^18.1.0"
}
},
"node_modules/ansi-regex": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz",
"integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/ansi-styles": {
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/cliui": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz",
"integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==",
"license": "ISC",
"dependencies": {
"string-width": "^7.2.0",
"strip-ansi": "^7.1.0",
"wrap-ansi": "^9.0.0"
},
"engines": {
"node": ">=20"
}
},
"node_modules/cliui/node_modules/string-width": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
"integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^10.3.0",
"get-east-asian-width": "^1.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/emoji-regex": {
"version": "10.6.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
"integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
"license": "MIT"
},
"node_modules/escalade": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"license": "ISC",
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/get-east-asian-width": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz",
"integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/string-width": {
"version": "8.2.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz",
"integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==",
"license": "MIT",
"dependencies": {
"get-east-asian-width": "^1.5.0",
"strip-ansi": "^7.1.2"
},
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/strip-ansi": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
"integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.2.2"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
"integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.2.1",
"string-width": "^7.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi/node_modules/string-width": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
"integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^10.3.0",
"get-east-asian-width": "^1.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"license": "ISC",
"engines": {
"node": ">=10"
}
},
"node_modules/yargs": {
"version": "18.1.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-18.1.0.tgz",
"integrity": "sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==",
"license": "MIT",
"dependencies": {
"cliui": "^9.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"string-width": "^8.2.1",
"y18n": "^5.0.5",
"yargs-parser": "^22.0.0"
},
"engines": {
"node": "^20.19.0 || ^22.12.0 || >=23"
}
},
"node_modules/yargs-parser": {
"version": "22.0.0",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz",
"integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==",
"license": "ISC",
"engines": {
"node": "^20.19.0 || ^22.12.0 || >=23"
}
}
}
}
View File
File renamed without changes.
+46 -22
View File
@@ -28,23 +28,30 @@ const { hideBin } = require('yargs/helpers');
const version = '0.0.1'; const version = '0.0.1';
const days_fore_max = 16 const days_fore_max = 16;
const days_fore_default = 10; const days_fore_default = 12;
const days_past_default = 4; const days_past_default = 2;
const ft_to_m = 0.3048;
const argv = yargs(hideBin(process.argv)) const argv = yargs(hideBin(process.argv))
.option('days_fore', { .option('days_fore', {
alias: 'f', alias: 'f',
describe: 'Set forecast days', describe: 'Set forecast days, such as "-f 10". Maximum of 16.',
type: 'number', type: 'number',
requiresArg: true, requiresArg: true,
}) })
.option('days_past', { .option('days_past', {
alias: 'p', alias: 'p',
describe: 'Set past days', describe: 'Set past days, such as "-p 3"',
type: 'number', type: 'number',
requiresArg: true, requiresArg: true,
}) })
.option('calc_elevation', {
alias: 'e',
describe: 'Set to calculate elevation at lat/long.',
type: 'boolean',
})
.check((argv) => { .check((argv) => {
if (argv.f !== undefined && (isNaN(argv.f) || argv.f < 0)) { if (argv.f !== undefined && (isNaN(argv.f) || argv.f < 0)) {
throw new Error('Forecast days must be a non-negative number'); throw new Error('Forecast days must be a non-negative number');
@@ -66,23 +73,38 @@ function max (value, upper) {
} }
const days_fore = max(argv.hasOwnProperty("days_fore") ? argv.days_fore : days_fore_default, days_fore_max); const days_fore = max(argv.hasOwnProperty("days_fore") ? argv.days_fore : days_fore_default, days_fore_max);
const days_past = argv.hasOwnProperty("days_past") ? argv.days_past : days_past_default; const days_past = argv.hasOwnProperty("days_past") ? argv.days_past : days_past_default;
const calc_elevation = argv.calc_elevation ? true : false;
const days = days_fore + days_past; const days = days_fore + days_past;
console.log(`Running for ${days} days: ${days_fore} forecasted, ${days_past} past.`); console.log(`Averaging for ${days} days: ${days_fore} forecasted, ${days_past} past.`);
if (calc_elevation) console.log("Calculating elevation rather than using provided values.");
else console.log("Using user-provided elevations when they exist.");
const locations = [ const locations = [
{ name: "Flagstaff, AZ", lat: 35.1983, lon: -111.6513, elevation: 6903 }, //{ name: "Flagstaff, AZ", lat: 35.1983, lon: -111.6513, elevation: 6903 },
{ name: "Flagstaff, ++", lat: 35.1983, lon: -111.6513, elevation: 8200 }, //{ name: "Flagstaff: NG", lat: 35.1983, lon: -111.6513, elevation: 6931 },
{ name: "Tucson, AZ", lat: 32.2226, lon: -110.9747, elevation: 2490 }, { name: "Flagstaff: ++", lat: 35.1983, lon: -111.6513, elevation: 8200 },
{ name: "Prescott, AZ", lat: 34.5400, lon: -112.4685, elevation: 5374 }, //{ name: "Tucson, AZ", lat: 32.2226, lon: -110.9747, elevation: 2490 },
{ name: "Prescott, ++", lat: 34.5400, lon: -112.4685, elevation: 6500 }, //{ name: "Tucson: NG", lat: 32.2226, lon: -110.9747, elevation: 2483 },
{ name: "Frisco, CO", lat: 39.5744, lon: -106.0975, elevation: 9075 }, //{ name: "Tucson: GV", lat: 32.2226, lon: -110.9747, elevation: 2371 },
{ name: "Frisco, ++", lat: 39.5744, lon: -106.0975, elevation: 9800 }, { name: "Tucson: ++", lat: 32.2226, lon: -110.9747, elevation: 2150 },
{ name: "Greer, AZ", lat: 34.0100, lon: -109.4587, elevation: 8356 }, //{ name: "Prescott, AZ", lat: 34.5400, lon: -112.4685, elevation: 5374 },
{ name: "Greer, ++", lat: 34.0100, lon: -109.4587, elevation: 9000 }, //{ name: "Prescott: NG", lat: 34.5400, lon: -112.4685, elevation: 5496 },
{ name: "Sedona, AZ", lat: 34.8697, lon: -111.7610, elevation: 4350 }, { name: "Prescott: ++", lat: 34.5400, lon: -112.4685, elevation: 6400 },
{ name: "Ehrenberg, AZ", lat: 33.9897, lon: -114.3550, elevation: 333 }, //{ name: "Frisco, CO", lat: 39.5744, lon: -106.0975, elevation: 9075 },
//{ name: "Frisco: NG", lat: 39.5744, lon: -106.0975, elevation: 9067 },
{ name: "Frisco: ++", lat: 39.5744, lon: -106.0975, elevation: 10200 },
//{ name: "Greer, AZ", lat: 34.0100, lon: -109.4587, elevation: 8356 },
{ name: "Greer: ++", lat: 34.0100, lon: -109.4587, elevation: 9000 },
//{ name: "Sedona, AZ", lat: 34.8697, lon: -111.7610, elevation: 4350 },
{ name: "Sedona: ++", lat: 34.8697, lon: -111.7610, elevation: 4500 },
{ name: "Cottonwood: ++", lat: 34.7392, lon: -112.0099, elevation: 3600 },
{ name: "Cherry: ++", lat: 34.5781, lon: -112.0677, elevation: 5300 },
//{ name: "Sedona: NG", lat: 34.8697, lon: -111.7610, elevation: 4420 },
//{ name: "Ehrenberg, AZ", lat: 33.9897, lon: -114.3550, elevation: 333 },
{ name: "Ehrenberg: ++", lat: 33.9897, lon: -114.3550, elevation: 300 },
{ name: "Quartzsite: ++", lat: 33.6763, lon: -114.2104, elevation: 900 },
]; ];
locations.sort((a, b) => a.elevation - b.elevation); locations.sort((a, b) => a.elevation - b.elevation);
@@ -143,7 +165,7 @@ async function getForecast(loc) {
const url = new URL("https://api.open-meteo.com/v1/forecast"); const url = new URL("https://api.open-meteo.com/v1/forecast");
url.searchParams.set("latitude", loc.lat); url.searchParams.set("latitude", loc.lat);
url.searchParams.set("longitude", loc.lon); url.searchParams.set("longitude", loc.lon);
url.searchParams.set("elevation", loc.elevation * 0.3048); if (!calc_elevation && loc.elevation) url.searchParams.set("elevation", loc.elevation * ft_to_m);
url.searchParams.set("daily", "temperature_2m_max,temperature_2m_min,apparent_temperature_max,apparent_temperature_min"); url.searchParams.set("daily", "temperature_2m_max,temperature_2m_min,apparent_temperature_max,apparent_temperature_min");
url.searchParams.set("temperature_unit", "fahrenheit"); url.searchParams.set("temperature_unit", "fahrenheit");
url.searchParams.set("precipitation_unit", "inch"); url.searchParams.set("precipitation_unit", "inch");
@@ -167,12 +189,14 @@ async function getForecast(loc) {
const avgHigh2 = (highs2.reduce((a, b) => a + b, 0) / highs2.length).toFixed(1); const avgHigh2 = (highs2.reduce((a, b) => a + b, 0) / highs2.length).toFixed(1);
const avgLow2 = (lows2.reduce((a, b) => a + b, 0) / lows2.length).toFixed(1); const avgLow2 = (lows2.reduce((a, b) => a + b, 0) / lows2.length).toFixed(1);
return { name: loc.name, elevation: loc.elevation, avgHigh, avgLow, avgHigh2, avgLow2 }; const elev = (!calc_elevation && loc.elevation) ? loc.elevation : (data.elevation / ft_to_m).toFixed(0);
return { name: loc.name, elevation: elev, avgHigh, avgLow, avgHigh2, avgLow2 };
} }
(async () => { (async () => {
console.log("Location | Elev (ft) | Avg High (°F) | Avg Low (°F) | App High (°F) | App Low (°F)"); console.log("Location | Elev (ft) | High (°F) | Low (°F) | FeelH (°F) | FeelL (°F)");
console.log("----------------------+-----------+---------------+--------------+---------------+-------------"); console.log("----------------------+-----------+-----------+----------+------------+------------");
for (const loc of locations) { for (const loc of locations) {
try { try {
@@ -182,7 +206,7 @@ async function getForecast(loc) {
const hi2 = check(r.avgHigh2, pref_high_lower, pref_high_upper); const hi2 = check(r.avgHigh2, pref_high_lower, pref_high_upper);
const lo2 = check(r.avgLow2, pref_low_lower, pref_low_upper); const lo2 = check(r.avgLow2, pref_low_lower, pref_low_upper);
console.log( console.log(
`${pad(r.name, 21)} | ${pad(String(r.elevation), 9)} | ${pad(hi, 13)} | ${pad(lo, 12)} | ${pad(hi2, 13)} | ${pad(lo2, 12)}` `${pad(r.name, 21)} | ${pad(String(r.elevation), 9)} | ${pad(hi, 9)} | ${pad(lo, 8)} | ${pad(hi2, 10)} | ${pad(lo2, 10)}`
); );
} catch (e) { } catch (e) {
console.log(`${loc.name}: error – ${e.message}`); console.log(`${loc.name}: error – ${e.message}`);
View File
Whitespace-only changes.