@fnndsc/brasa 0.23.2 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/buildinfo.json +1 -1
- package/dist/builtins/fs/pull.args.d.ts +1 -1
- package/dist/builtins/fs/pull.args.js +5 -3
- package/dist/builtins/fs/pull.args.js.map +1 -1
- package/dist/builtins/fs/pull.js +2 -2
- package/dist/builtins/fs/touch.js +1 -1
- package/dist/builtins/fs/touch.js.map +1 -1
- package/dist/builtins/help.js +134 -10
- package/dist/builtins/help.js.map +1 -1
- package/dist/builtins/index.d.ts +6 -0
- package/dist/builtins/index.js +6 -0
- package/dist/builtins/index.js.map +1 -1
- package/dist/builtins/net/query.csv.js +14 -0
- package/dist/builtins/net/query.csv.js.map +1 -1
- package/dist/builtins/net/query.js +3 -0
- package/dist/builtins/net/query.js.map +1 -1
- package/dist/builtins/pluginExecute.js +28 -3
- package/dist/builtins/pluginExecute.js.map +1 -1
- package/dist/builtins/proc.js +32 -2
- package/dist/builtins/proc.js.map +1 -1
- package/dist/builtins/res/dicom.d.ts +35 -1
- package/dist/builtins/res/dicom.js +15 -2
- package/dist/builtins/res/dicom.js.map +1 -1
- package/dist/builtins/res/expect.d.ts +40 -0
- package/dist/builtins/res/expect.js +447 -0
- package/dist/builtins/res/expect.js.map +1 -0
- package/dist/builtins/res/gather.d.ts +10 -0
- package/dist/builtins/res/gather.js +224 -0
- package/dist/builtins/res/gather.js.map +1 -0
- package/dist/builtins/res/gather.store.d.ts +131 -0
- package/dist/builtins/res/gather.store.js +225 -0
- package/dist/builtins/res/gather.store.js.map +1 -0
- package/dist/builtins/res/pipeline.args.d.ts +5 -2
- package/dist/builtins/res/pipeline.args.js +8 -3
- package/dist/builtins/res/pipeline.args.js.map +1 -1
- package/dist/builtins/res/pipeline.js +23 -0
- package/dist/builtins/res/pipeline.js.map +1 -1
- package/dist/builtins/res/play.d.ts +70 -0
- package/dist/builtins/res/play.js +334 -0
- package/dist/builtins/res/play.js.map +1 -0
- package/dist/builtins/res/plugin.js +21 -6
- package/dist/builtins/res/plugin.js.map +1 -1
- package/dist/builtins/res/record.d.ts +49 -0
- package/dist/builtins/res/record.js +150 -0
- package/dist/builtins/res/record.js.map +1 -0
- package/dist/builtins/res/runWait.d.ts +49 -0
- package/dist/builtins/res/runWait.js +193 -0
- package/dist/builtins/res/runWait.js.map +1 -0
- package/dist/builtins/sys/motd.d.ts +41 -0
- package/dist/builtins/sys/motd.js +139 -0
- package/dist/builtins/sys/motd.js.map +1 -0
- package/dist/builtins/sys/weather.d.ts +132 -0
- package/dist/builtins/sys/weather.js +297 -0
- package/dist/builtins/sys/weather.js.map +1 -0
- package/dist/command-keys.js +4 -0
- package/dist/command-keys.js.map +1 -1
- package/dist/core/connect.d.ts +31 -1
- package/dist/core/connect.js +32 -1
- package/dist/core/connect.js.map +1 -1
- package/dist/core/dispatch.d.ts +0 -14
- package/dist/core/dispatch.js +85 -27
- package/dist/core/dispatch.js.map +1 -1
- package/dist/core/engine.d.ts +10 -0
- package/dist/core/engine.js +9 -0
- package/dist/core/engine.js.map +1 -1
- package/dist/core/expansion.d.ts +70 -0
- package/dist/core/expansion.js +289 -0
- package/dist/core/expansion.js.map +1 -0
- package/dist/core/promptContext.js +5 -1
- package/dist/core/promptContext.js.map +1 -1
- package/dist/lib/duration.d.ts +17 -0
- package/dist/lib/duration.js +25 -0
- package/dist/lib/duration.js.map +1 -0
- package/dist/lib/parser.d.ts +50 -0
- package/dist/lib/parser.js +119 -0
- package/dist/lib/parser.js.map +1 -1
- package/dist/logo/brain.d.ts +6 -27
- package/dist/logo/brain.js +5 -250
- package/dist/logo/brain.js.map +1 -1
- package/dist/session/answer.d.ts +141 -0
- package/dist/session/answer.js +163 -0
- package/dist/session/answer.js.map +1 -0
- package/dist/session/answerAdapters.d.ts +4 -0
- package/dist/session/answerAdapters.js +106 -0
- package/dist/session/answerAdapters.js.map +1 -0
- package/dist/session/recent.d.ts +68 -0
- package/dist/session/recent.js +99 -0
- package/dist/session/recent.js.map +1 -0
- package/dist/session/recorder.d.ts +61 -0
- package/dist/session/recorder.js +95 -0
- package/dist/session/recorder.js.map +1 -0
- package/package.json +5 -5
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Builtin weather.
|
|
3
|
+
*
|
|
4
|
+
* Reports the weather at a place — the current conditions and the next few
|
|
5
|
+
* days — from Open-Meteo, which needs no key and no account. A place is
|
|
6
|
+
* geocoded by name through the same service, so `weather Boston` and
|
|
7
|
+
* `weather Cape Town` both work, and a bare `weather` asks about Boston,
|
|
8
|
+
* where the lab is — in metric, as the lab reads, unless `--units imperial`.
|
|
9
|
+
*
|
|
10
|
+
* The report is a kernel fact like `date` or `fortune`: it renders once and
|
|
11
|
+
* every surface shows the same text, and the typed model beneath it carries
|
|
12
|
+
* the numbers for a surface that wants to draw them differently. The
|
|
13
|
+
* network reached is the daemon's, so a host that cannot see the internet
|
|
14
|
+
* says so in one line rather than hanging.
|
|
15
|
+
*
|
|
16
|
+
* @module
|
|
17
|
+
*/
|
|
18
|
+
import { CommandEnvelope } from '@fnndsc/cumin';
|
|
19
|
+
/** Where `weather` looks when given no place. */
|
|
20
|
+
export declare const WEATHER_DEFAULT_PLACE: string;
|
|
21
|
+
/** The two unit systems `--units` takes. */
|
|
22
|
+
export type WeatherUnits = 'metric' | 'imperial';
|
|
23
|
+
/** The system used when none is asked for: the lab reports in metric. */
|
|
24
|
+
export declare const WEATHER_UNITS_DEFAULT: WeatherUnits;
|
|
25
|
+
/**
|
|
26
|
+
* A fetch the report goes through — the global one in a daemon, a stub in
|
|
27
|
+
* a test.
|
|
28
|
+
*/
|
|
29
|
+
export type WeatherFetch = (url: string, init?: RequestInit) => Promise<Response>;
|
|
30
|
+
/** A place the geocoder answered with. */
|
|
31
|
+
export interface WeatherPlace {
|
|
32
|
+
name: string;
|
|
33
|
+
region: string;
|
|
34
|
+
country: string;
|
|
35
|
+
latitude: number;
|
|
36
|
+
longitude: number;
|
|
37
|
+
timezone: string;
|
|
38
|
+
}
|
|
39
|
+
/** The conditions now. */
|
|
40
|
+
export interface WeatherNow {
|
|
41
|
+
time: string;
|
|
42
|
+
temperature: number;
|
|
43
|
+
apparent: number;
|
|
44
|
+
humidity: number;
|
|
45
|
+
code: number;
|
|
46
|
+
condition: string;
|
|
47
|
+
windSpeed: number;
|
|
48
|
+
windDirection: string;
|
|
49
|
+
}
|
|
50
|
+
/** One day of the forecast. */
|
|
51
|
+
export interface WeatherDay {
|
|
52
|
+
date: string;
|
|
53
|
+
low: number;
|
|
54
|
+
high: number;
|
|
55
|
+
code: number;
|
|
56
|
+
condition: string;
|
|
57
|
+
}
|
|
58
|
+
/** The report, as the model beneath the rendered text. */
|
|
59
|
+
export interface WeatherReport {
|
|
60
|
+
place: WeatherPlace;
|
|
61
|
+
units: 'F' | 'C';
|
|
62
|
+
now: WeatherNow;
|
|
63
|
+
days: WeatherDay[];
|
|
64
|
+
}
|
|
65
|
+
/** The words a call line was parsed into. */
|
|
66
|
+
interface WeatherArgs {
|
|
67
|
+
place: string;
|
|
68
|
+
units: WeatherUnits;
|
|
69
|
+
days: number;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Words a WMO weather code.
|
|
73
|
+
*
|
|
74
|
+
* @param code - The code Open-Meteo reported.
|
|
75
|
+
* @returns The condition in words; an unknown code says so with its number.
|
|
76
|
+
*/
|
|
77
|
+
export declare function condition_ofCode(code: number): string;
|
|
78
|
+
/**
|
|
79
|
+
* Names a wind direction on the eight-point compass.
|
|
80
|
+
*
|
|
81
|
+
* @param degrees - Where the wind comes from, degrees clockwise from north.
|
|
82
|
+
* @returns The compass point.
|
|
83
|
+
*/
|
|
84
|
+
export declare function compass_ofDegrees(degrees: number): string;
|
|
85
|
+
/**
|
|
86
|
+
* Parses the call line: a place in as many words as it takes, `--units`
|
|
87
|
+
* for the system, `-d N` for the days shown. Any other flag is refused by
|
|
88
|
+
* name.
|
|
89
|
+
*
|
|
90
|
+
* @param args - The words after `weather`.
|
|
91
|
+
* @returns The parsed call, or the refusal to print.
|
|
92
|
+
*/
|
|
93
|
+
export declare function weatherArgs_parse(args: string[]): WeatherArgs | string;
|
|
94
|
+
/**
|
|
95
|
+
* Finds a place by name.
|
|
96
|
+
*
|
|
97
|
+
* @param fetchFn - The fetch to go through.
|
|
98
|
+
* @param name - The place as the operator typed it.
|
|
99
|
+
* @returns The first place the geocoder offers, or null when it offers none.
|
|
100
|
+
*/
|
|
101
|
+
export declare function place_find(fetchFn: WeatherFetch, name: string): Promise<WeatherPlace | null>;
|
|
102
|
+
/**
|
|
103
|
+
* Fetches the conditions at a place.
|
|
104
|
+
*
|
|
105
|
+
* @param fetchFn - The fetch to go through.
|
|
106
|
+
* @param place - Where.
|
|
107
|
+
* @param units - Metric (°C, km/h) or imperial (°F, mph).
|
|
108
|
+
* @param days - How many days of forecast.
|
|
109
|
+
* @returns The report.
|
|
110
|
+
* @throws {Error} When the service cannot be reached or answers without the
|
|
111
|
+
* fields a report is made of.
|
|
112
|
+
*/
|
|
113
|
+
export declare function report_fetch(fetchFn: WeatherFetch, place: WeatherPlace, units: WeatherUnits, days: number): Promise<WeatherReport>;
|
|
114
|
+
/**
|
|
115
|
+
* Renders a report as the lines a terminal shows.
|
|
116
|
+
*
|
|
117
|
+
* @param report - The report.
|
|
118
|
+
* @returns The text, with a trailing newline.
|
|
119
|
+
*/
|
|
120
|
+
export declare function report_render(report: WeatherReport): string;
|
|
121
|
+
/**
|
|
122
|
+
* Reports the weather at a place.
|
|
123
|
+
*
|
|
124
|
+
* @param args - `[place…] [-u|--units metric|imperial] [-d|--days N]`.
|
|
125
|
+
* @param fetchFn - The fetch to go through; the global one unless a test
|
|
126
|
+
* hands in its own.
|
|
127
|
+
* @returns An envelope carrying the rendered report and the typed report
|
|
128
|
+
* beneath it; an error envelope when a flag is unknown, the place is not
|
|
129
|
+
* found, or the service cannot be reached.
|
|
130
|
+
*/
|
|
131
|
+
export declare function builtin_weather(args: string[], fetchFn?: WeatherFetch): Promise<CommandEnvelope>;
|
|
132
|
+
export {};
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Builtin weather.
|
|
3
|
+
*
|
|
4
|
+
* Reports the weather at a place — the current conditions and the next few
|
|
5
|
+
* days — from Open-Meteo, which needs no key and no account. A place is
|
|
6
|
+
* geocoded by name through the same service, so `weather Boston` and
|
|
7
|
+
* `weather Cape Town` both work, and a bare `weather` asks about Boston,
|
|
8
|
+
* where the lab is — in metric, as the lab reads, unless `--units imperial`.
|
|
9
|
+
*
|
|
10
|
+
* The report is a kernel fact like `date` or `fortune`: it renders once and
|
|
11
|
+
* every surface shows the same text, and the typed model beneath it carries
|
|
12
|
+
* the numbers for a surface that wants to draw them differently. The
|
|
13
|
+
* network reached is the daemon's, so a host that cannot see the internet
|
|
14
|
+
* says so in one line rather than hanging.
|
|
15
|
+
*
|
|
16
|
+
* @module
|
|
17
|
+
*/
|
|
18
|
+
import { envelope_error, envelope_ok } from '@fnndsc/cumin';
|
|
19
|
+
/** Where `weather` looks when given no place. */
|
|
20
|
+
export const WEATHER_DEFAULT_PLACE = 'Boston';
|
|
21
|
+
/** Days of forecast shown when none are asked for. */
|
|
22
|
+
const DAYS_DEFAULT = 3;
|
|
23
|
+
/** The system used when none is asked for: the lab reports in metric. */
|
|
24
|
+
export const WEATHER_UNITS_DEFAULT = 'metric';
|
|
25
|
+
/** The most forecast days Open-Meteo answers. */
|
|
26
|
+
const DAYS_MAX = 16;
|
|
27
|
+
/** How long one call may take before the report gives up. */
|
|
28
|
+
const FETCH_TIMEOUT_MS = 8_000;
|
|
29
|
+
/** The geocoder: a name to coordinates. */
|
|
30
|
+
const GEOCODE_URL = 'https://geocoding-api.open-meteo.com/v1/search';
|
|
31
|
+
/** The forecast itself. */
|
|
32
|
+
const FORECAST_URL = 'https://api.open-meteo.com/v1/forecast';
|
|
33
|
+
/** The compass, eight points, clockwise from north. */
|
|
34
|
+
const COMPASS = ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW'];
|
|
35
|
+
/** WMO weather interpretation codes, as Open-Meteo reports them, in words. */
|
|
36
|
+
const CONDITIONS = {
|
|
37
|
+
0: 'clear sky',
|
|
38
|
+
1: 'mainly clear',
|
|
39
|
+
2: 'partly cloudy',
|
|
40
|
+
3: 'overcast',
|
|
41
|
+
45: 'fog',
|
|
42
|
+
48: 'rime fog',
|
|
43
|
+
51: 'light drizzle',
|
|
44
|
+
53: 'drizzle',
|
|
45
|
+
55: 'dense drizzle',
|
|
46
|
+
56: 'light freezing drizzle',
|
|
47
|
+
57: 'freezing drizzle',
|
|
48
|
+
61: 'light rain',
|
|
49
|
+
63: 'rain',
|
|
50
|
+
65: 'heavy rain',
|
|
51
|
+
66: 'light freezing rain',
|
|
52
|
+
67: 'freezing rain',
|
|
53
|
+
71: 'light snow',
|
|
54
|
+
73: 'snow',
|
|
55
|
+
75: 'heavy snow',
|
|
56
|
+
77: 'snow grains',
|
|
57
|
+
80: 'light rain showers',
|
|
58
|
+
81: 'rain showers',
|
|
59
|
+
82: 'violent rain showers',
|
|
60
|
+
85: 'light snow showers',
|
|
61
|
+
86: 'heavy snow showers',
|
|
62
|
+
95: 'thunderstorm',
|
|
63
|
+
96: 'thunderstorm with light hail',
|
|
64
|
+
99: 'thunderstorm with heavy hail',
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Words a WMO weather code.
|
|
68
|
+
*
|
|
69
|
+
* @param code - The code Open-Meteo reported.
|
|
70
|
+
* @returns The condition in words; an unknown code says so with its number.
|
|
71
|
+
*/
|
|
72
|
+
export function condition_ofCode(code) {
|
|
73
|
+
return CONDITIONS[code] ?? `conditions code ${code}`;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Names a wind direction on the eight-point compass.
|
|
77
|
+
*
|
|
78
|
+
* @param degrees - Where the wind comes from, degrees clockwise from north.
|
|
79
|
+
* @returns The compass point.
|
|
80
|
+
*/
|
|
81
|
+
export function compass_ofDegrees(degrees) {
|
|
82
|
+
const index = Math.round((((degrees % 360) + 360) % 360) / 45) % 8;
|
|
83
|
+
return COMPASS[index];
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Reads a number out of an untyped JSON field.
|
|
87
|
+
*
|
|
88
|
+
* @param value - The field.
|
|
89
|
+
* @returns The number, or null when the field is not one.
|
|
90
|
+
*/
|
|
91
|
+
function number_read(value) {
|
|
92
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Reads a string out of an untyped JSON field.
|
|
96
|
+
*
|
|
97
|
+
* @param value - The field.
|
|
98
|
+
* @returns The string, or the empty string when the field is not one.
|
|
99
|
+
*/
|
|
100
|
+
function string_read(value) {
|
|
101
|
+
return typeof value === 'string' ? value : '';
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Parses the call line: a place in as many words as it takes, `--units`
|
|
105
|
+
* for the system, `-d N` for the days shown. Any other flag is refused by
|
|
106
|
+
* name.
|
|
107
|
+
*
|
|
108
|
+
* @param args - The words after `weather`.
|
|
109
|
+
* @returns The parsed call, or the refusal to print.
|
|
110
|
+
*/
|
|
111
|
+
export function weatherArgs_parse(args) {
|
|
112
|
+
const words = [];
|
|
113
|
+
let units = WEATHER_UNITS_DEFAULT;
|
|
114
|
+
let days = DAYS_DEFAULT;
|
|
115
|
+
for (let i = 0; i < args.length; i++) {
|
|
116
|
+
const arg = args[i];
|
|
117
|
+
if (arg === '-u' || arg === '--units' || arg.startsWith('--units=')) {
|
|
118
|
+
const value = arg.startsWith('--units=') ? arg.slice('--units='.length) : (args[++i] ?? '');
|
|
119
|
+
if (value !== 'metric' && value !== 'imperial') {
|
|
120
|
+
return `weather: --units takes metric or imperial, not '${value}'`;
|
|
121
|
+
}
|
|
122
|
+
units = value;
|
|
123
|
+
}
|
|
124
|
+
else if (arg === '-d' || arg === '--days') {
|
|
125
|
+
const value = parseInt(args[i + 1] ?? '', 10);
|
|
126
|
+
if (!Number.isFinite(value) || value < 1 || value > DAYS_MAX) {
|
|
127
|
+
return `weather: ${arg} takes a number of days, 1 to ${DAYS_MAX}`;
|
|
128
|
+
}
|
|
129
|
+
days = value;
|
|
130
|
+
i++;
|
|
131
|
+
}
|
|
132
|
+
else if (arg.startsWith('-')) {
|
|
133
|
+
return `weather: unknown option '${arg}' (usage: weather [place] [-u|--units metric|imperial] [-d|--days N])`;
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
words.push(arg);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return { place: words.length > 0 ? words.join(' ') : WEATHER_DEFAULT_PLACE, units, days };
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Fetches one JSON answer, bounded in time.
|
|
143
|
+
*
|
|
144
|
+
* @param fetchFn - The fetch to go through.
|
|
145
|
+
* @param url - What to ask.
|
|
146
|
+
* @returns The parsed body.
|
|
147
|
+
* @throws {Error} When the service is unreachable, answers an error, or
|
|
148
|
+
* answers something that is not JSON.
|
|
149
|
+
*/
|
|
150
|
+
async function json_fetch(fetchFn, url) {
|
|
151
|
+
const response = await fetchFn(url, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
|
|
152
|
+
if (!response.ok)
|
|
153
|
+
throw new Error(`open-meteo answered ${response.status}`);
|
|
154
|
+
return response.json();
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Finds a place by name.
|
|
158
|
+
*
|
|
159
|
+
* @param fetchFn - The fetch to go through.
|
|
160
|
+
* @param name - The place as the operator typed it.
|
|
161
|
+
* @returns The first place the geocoder offers, or null when it offers none.
|
|
162
|
+
*/
|
|
163
|
+
export async function place_find(fetchFn, name) {
|
|
164
|
+
const url = `${GEOCODE_URL}?name=${encodeURIComponent(name)}&count=1&language=en&format=json`;
|
|
165
|
+
const answer = (await json_fetch(fetchFn, url));
|
|
166
|
+
const first = answer.results?.[0];
|
|
167
|
+
const latitude = number_read(first?.latitude);
|
|
168
|
+
const longitude = number_read(first?.longitude);
|
|
169
|
+
if (first === undefined || latitude === null || longitude === null)
|
|
170
|
+
return null;
|
|
171
|
+
return {
|
|
172
|
+
name: string_read(first.name),
|
|
173
|
+
region: string_read(first.admin1),
|
|
174
|
+
country: string_read(first.country),
|
|
175
|
+
latitude,
|
|
176
|
+
longitude,
|
|
177
|
+
timezone: string_read(first.timezone) || 'auto',
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Fetches the conditions at a place.
|
|
182
|
+
*
|
|
183
|
+
* @param fetchFn - The fetch to go through.
|
|
184
|
+
* @param place - Where.
|
|
185
|
+
* @param units - Metric (°C, km/h) or imperial (°F, mph).
|
|
186
|
+
* @param days - How many days of forecast.
|
|
187
|
+
* @returns The report.
|
|
188
|
+
* @throws {Error} When the service cannot be reached or answers without the
|
|
189
|
+
* fields a report is made of.
|
|
190
|
+
*/
|
|
191
|
+
export async function report_fetch(fetchFn, place, units, days) {
|
|
192
|
+
const celsius = units === 'metric';
|
|
193
|
+
const query = [
|
|
194
|
+
`latitude=${place.latitude}`,
|
|
195
|
+
`longitude=${place.longitude}`,
|
|
196
|
+
'current=temperature_2m,apparent_temperature,relative_humidity_2m,weather_code,wind_speed_10m,wind_direction_10m',
|
|
197
|
+
'daily=weather_code,temperature_2m_max,temperature_2m_min',
|
|
198
|
+
`temperature_unit=${celsius ? 'celsius' : 'fahrenheit'}`,
|
|
199
|
+
`wind_speed_unit=${celsius ? 'kmh' : 'mph'}`,
|
|
200
|
+
`timezone=${encodeURIComponent(place.timezone)}`,
|
|
201
|
+
`forecast_days=${days}`,
|
|
202
|
+
].join('&');
|
|
203
|
+
const answer = (await json_fetch(fetchFn, `${FORECAST_URL}?${query}`));
|
|
204
|
+
const current = answer.current;
|
|
205
|
+
const temperature = number_read(current?.temperature_2m);
|
|
206
|
+
const code = number_read(current?.weather_code);
|
|
207
|
+
if (current === undefined || temperature === null || code === null) {
|
|
208
|
+
throw new Error('open-meteo answered without current conditions');
|
|
209
|
+
}
|
|
210
|
+
const now = {
|
|
211
|
+
time: string_read(current.time),
|
|
212
|
+
temperature,
|
|
213
|
+
apparent: number_read(current.apparent_temperature) ?? temperature,
|
|
214
|
+
humidity: number_read(current.relative_humidity_2m) ?? 0,
|
|
215
|
+
code,
|
|
216
|
+
condition: condition_ofCode(code),
|
|
217
|
+
windSpeed: number_read(current.wind_speed_10m) ?? 0,
|
|
218
|
+
windDirection: compass_ofDegrees(number_read(current.wind_direction_10m) ?? 0),
|
|
219
|
+
};
|
|
220
|
+
const dates = Array.isArray(answer.daily?.time) ? answer.daily.time : [];
|
|
221
|
+
const highs = Array.isArray(answer.daily?.temperature_2m_max) ? answer.daily.temperature_2m_max : [];
|
|
222
|
+
const lows = Array.isArray(answer.daily?.temperature_2m_min) ? answer.daily.temperature_2m_min : [];
|
|
223
|
+
const codes = Array.isArray(answer.daily?.weather_code) ? answer.daily.weather_code : [];
|
|
224
|
+
const forecast = [];
|
|
225
|
+
for (let i = 0; i < dates.length; i++) {
|
|
226
|
+
const high = number_read(highs[i]);
|
|
227
|
+
const low = number_read(lows[i]);
|
|
228
|
+
const dayCode = number_read(codes[i]);
|
|
229
|
+
if (high === null || low === null || dayCode === null)
|
|
230
|
+
continue;
|
|
231
|
+
forecast.push({ date: string_read(dates[i]), low, high, code: dayCode, condition: condition_ofCode(dayCode) });
|
|
232
|
+
}
|
|
233
|
+
return { place, units: celsius ? 'C' : 'F', now, days: forecast };
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Names a forecast day: `today`, then the weekday and date.
|
|
237
|
+
*
|
|
238
|
+
* @param date - The day as `YYYY-MM-DD`.
|
|
239
|
+
* @param index - Its position in the forecast, 0 being today.
|
|
240
|
+
* @returns The label.
|
|
241
|
+
*/
|
|
242
|
+
function day_label(date, index) {
|
|
243
|
+
if (index === 0)
|
|
244
|
+
return 'today';
|
|
245
|
+
const parsed = new Date(`${date}T12:00:00Z`);
|
|
246
|
+
const weekday = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][parsed.getUTCDay()] ?? '';
|
|
247
|
+
return `${weekday} ${date.slice(5)}`;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Renders a report as the lines a terminal shows.
|
|
251
|
+
*
|
|
252
|
+
* @param report - The report.
|
|
253
|
+
* @returns The text, with a trailing newline.
|
|
254
|
+
*/
|
|
255
|
+
export function report_render(report) {
|
|
256
|
+
const unit = `°${report.units}`;
|
|
257
|
+
const speed = report.units === 'C' ? 'km/h' : 'mph';
|
|
258
|
+
const where = [report.place.name, report.place.region, report.place.country]
|
|
259
|
+
.filter((part) => part.length > 0)
|
|
260
|
+
.join(', ');
|
|
261
|
+
const when = report.now.time.replace('T', ' ');
|
|
262
|
+
const lines = [
|
|
263
|
+
`${where} · ${when}`,
|
|
264
|
+
` now ${Math.round(report.now.temperature)}${unit} (feels ${Math.round(report.now.apparent)}${unit}) · ${report.now.condition} · wind ${Math.round(report.now.windSpeed)} ${speed} ${report.now.windDirection} · humidity ${Math.round(report.now.humidity)}%`,
|
|
265
|
+
];
|
|
266
|
+
report.days.forEach((day, index) => {
|
|
267
|
+
lines.push(` ${day_label(day.date, index).padEnd(9)} ${Math.round(day.low)}–${Math.round(day.high)}${unit} · ${day.condition}`);
|
|
268
|
+
});
|
|
269
|
+
return `${lines.join('\n')}\n`;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Reports the weather at a place.
|
|
273
|
+
*
|
|
274
|
+
* @param args - `[place…] [-u|--units metric|imperial] [-d|--days N]`.
|
|
275
|
+
* @param fetchFn - The fetch to go through; the global one unless a test
|
|
276
|
+
* hands in its own.
|
|
277
|
+
* @returns An envelope carrying the rendered report and the typed report
|
|
278
|
+
* beneath it; an error envelope when a flag is unknown, the place is not
|
|
279
|
+
* found, or the service cannot be reached.
|
|
280
|
+
*/
|
|
281
|
+
export async function builtin_weather(args, fetchFn = (url, init) => fetch(url, init)) {
|
|
282
|
+
const parsed = weatherArgs_parse(args);
|
|
283
|
+
if (typeof parsed === 'string')
|
|
284
|
+
return envelope_error(`${parsed}\n`);
|
|
285
|
+
try {
|
|
286
|
+
const place = await place_find(fetchFn, parsed.place);
|
|
287
|
+
if (place === null)
|
|
288
|
+
return envelope_error(`weather: no such place '${parsed.place}'\n`);
|
|
289
|
+
const report = await report_fetch(fetchFn, place, parsed.units, parsed.days);
|
|
290
|
+
return envelope_ok(report_render(report), { kind: 'sys.weather', data: report });
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
293
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
294
|
+
return envelope_error(`weather: ${reason}\n`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
//# sourceMappingURL=weather.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"weather.js","sourceRoot":"","sources":["../../../src/builtins/sys/weather.ts"],"names":[],"mappings":"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"}
|
package/dist/command-keys.js
CHANGED
package/dist/command-keys.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command-keys.js","sourceRoot":"","sources":["../src/command-keys.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,CAAC,MAAM,qBAAqB,GAAa;IAC7C,aAAa;IACb,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI;IAC1E,QAAQ,EAAE,UAAU,EAAE,MAAM;IAC5B,MAAM;IACN,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU;IAChD,MAAM;IACN,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM;IAClG,kCAAkC;IAClC,QAAQ,EAAE,SAAS;IACnB,MAAM,EAAE,OAAO;IACf,UAAU,EAAE,WAAW;IACvB,SAAS,EAAE,UAAU;IACrB,OAAO,EAAE,OAAO,EAAE,MAAM;IACxB,OAAO;IACP,KAAK,EAAE,MAAM;IACb,OAAO,EAAE,QAAQ;IACjB,MAAM,EAAE,OAAO;IACf,YAAY,EAAE,aAAa,EAAE,MAAM,EAAE,OAAO;IAC5C,gBAAgB,EAAE,iBAAiB,EAAE,UAAU,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM;IAC3E,UAAU,EAAE,WAAW;IACvB,oBAAoB;IACpB,kBAAkB;IAClB,aAAa,EAAE,aAAa,EAAE,cAAc;CAC7C,CAAC"}
|
|
1
|
+
{"version":3,"file":"command-keys.js","sourceRoot":"","sources":["../src/command-keys.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,CAAC,MAAM,qBAAqB,GAAa;IAC7C,aAAa;IACb,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI;IAC1E,QAAQ,EAAE,UAAU,EAAE,MAAM;IAC5B,MAAM;IACN,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU;IAChD,MAAM;IACN,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM;IAClG,kCAAkC;IAClC,QAAQ,EAAE,SAAS;IACnB,MAAM,EAAE,OAAO;IACf,UAAU,EAAE,WAAW;IACvB,SAAS,EAAE,UAAU;IACrB,OAAO,EAAE,OAAO,EAAE,MAAM;IACxB,OAAO;IACP,KAAK,EAAE,MAAM;IACb,QAAQ;IACR,MAAM;IACN,QAAQ;IACR,QAAQ;IACR,OAAO,EAAE,QAAQ;IACjB,MAAM,EAAE,OAAO;IACf,YAAY,EAAE,aAAa,EAAE,MAAM,EAAE,OAAO;IAC5C,gBAAgB,EAAE,iBAAiB,EAAE,UAAU,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM;IAC3E,UAAU,EAAE,WAAW;IACvB,oBAAoB;IACpB,kBAAkB;IAClB,aAAa,EAAE,aAAa,EAAE,cAAc;CAC7C,CAAC"}
|
package/dist/core/connect.d.ts
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* context and validate its token against the server. It carries no terminal
|
|
6
6
|
* output of its own: it returns a status a frontend narrates (the CLI prints
|
|
7
7
|
* progress; a daemon logs or exits). Interactive login (prompting for a URL and
|
|
8
|
-
* password) is a frontend concern and lives with the CLI, not here.
|
|
8
|
+
* password) is a frontend concern and lives with the CLI, not here. A token a
|
|
9
|
+
* door already minted is the other headless way in, and it is here too.
|
|
9
10
|
*
|
|
10
11
|
* @module
|
|
11
12
|
*/
|
|
@@ -36,3 +37,32 @@ export interface SavedSessionResult {
|
|
|
36
37
|
* @returns The restore outcome and the resolved context.
|
|
37
38
|
*/
|
|
38
39
|
export declare function sessionConnect_fromSaved(): Promise<SavedSessionResult>;
|
|
40
|
+
/**
|
|
41
|
+
* The outcome of a token hand-over.
|
|
42
|
+
*
|
|
43
|
+
* - `connected` — the server accepted the token; the session is this identity's.
|
|
44
|
+
* - `refused` — the server did not; the session is offline and `error` says why.
|
|
45
|
+
*/
|
|
46
|
+
export interface TokenSessionResult {
|
|
47
|
+
status: 'connected' | 'refused';
|
|
48
|
+
context: SingleContext;
|
|
49
|
+
/** What the server said, when the status is `refused`. */
|
|
50
|
+
error?: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Connects a session from a CUBE token a door already minted, without prompting.
|
|
54
|
+
*
|
|
55
|
+
* A login front on a shared host exchanges the operator's password for a
|
|
56
|
+
* token itself and hands the token to the session it spawns; the session
|
|
57
|
+
* never sees a password. The token is proved against the server before the
|
|
58
|
+
* context is written, so a refusal changes nothing on disk. On acceptance
|
|
59
|
+
* the context is set the way a credentialed boot sets it: the identity, and
|
|
60
|
+
* no feed or plugin — the working directory is the identity's own and is
|
|
61
|
+
* not touched, so a session that was evicted comes back where it was.
|
|
62
|
+
*
|
|
63
|
+
* @param user - The CUBE username the token was minted for.
|
|
64
|
+
* @param url - The CUBE API base the token was minted against.
|
|
65
|
+
* @param token - The token.
|
|
66
|
+
* @returns Connected, or refused with the server's reason.
|
|
67
|
+
*/
|
|
68
|
+
export declare function sessionConnect_withToken(user: string, url: string, token: string): Promise<TokenSessionResult>;
|
package/dist/core/connect.js
CHANGED
|
@@ -5,12 +5,14 @@
|
|
|
5
5
|
* context and validate its token against the server. It carries no terminal
|
|
6
6
|
* output of its own: it returns a status a frontend narrates (the CLI prints
|
|
7
7
|
* progress; a daemon logs or exits). Interactive login (prompting for a URL and
|
|
8
|
-
* password) is a frontend concern and lives with the CLI, not here.
|
|
8
|
+
* password) is a frontend concern and lives with the CLI, not here. A token a
|
|
9
|
+
* door already minted is the other headless way in, and it is here too.
|
|
9
10
|
*
|
|
10
11
|
* @module
|
|
11
12
|
*/
|
|
12
13
|
import { session } from '../session/index.js';
|
|
13
14
|
import { context_getSingle } from '@fnndsc/salsa';
|
|
15
|
+
import { chrisContext, Context } from '@fnndsc/cumin';
|
|
14
16
|
/**
|
|
15
17
|
* Restores a session from saved credentials, without any prompting.
|
|
16
18
|
*
|
|
@@ -43,4 +45,33 @@ export async function sessionConnect_fromSaved() {
|
|
|
43
45
|
return { status: 'invalid-token', context, error: error instanceof Error ? error.message : String(error) };
|
|
44
46
|
}
|
|
45
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Connects a session from a CUBE token a door already minted, without prompting.
|
|
50
|
+
*
|
|
51
|
+
* A login front on a shared host exchanges the operator's password for a
|
|
52
|
+
* token itself and hands the token to the session it spawns; the session
|
|
53
|
+
* never sees a password. The token is proved against the server before the
|
|
54
|
+
* context is written, so a refusal changes nothing on disk. On acceptance
|
|
55
|
+
* the context is set the way a credentialed boot sets it: the identity, and
|
|
56
|
+
* no feed or plugin — the working directory is the identity's own and is
|
|
57
|
+
* not touched, so a session that was evicted comes back where it was.
|
|
58
|
+
*
|
|
59
|
+
* @param user - The CUBE username the token was minted for.
|
|
60
|
+
* @param url - The CUBE API base the token was minted against.
|
|
61
|
+
* @param token - The token.
|
|
62
|
+
* @returns Connected, or refused with the server's reason.
|
|
63
|
+
*/
|
|
64
|
+
export async function sessionConnect_withToken(user, url, token) {
|
|
65
|
+
const outcome = await session.connection.connection_connectWithToken({ user, url, token });
|
|
66
|
+
if (!outcome.connected) {
|
|
67
|
+
session.offline = true;
|
|
68
|
+
return { status: 'refused', context: await context_getSingle(), error: outcome.reason };
|
|
69
|
+
}
|
|
70
|
+
session.offline = false;
|
|
71
|
+
await chrisContext.current_set(Context.ChRISuser, user);
|
|
72
|
+
await chrisContext.current_set(Context.ChRISURL, url);
|
|
73
|
+
await chrisContext.current_set(Context.ChRISfeed, '');
|
|
74
|
+
await chrisContext.current_set(Context.ChRISplugin, '');
|
|
75
|
+
return { status: 'connected', context: await context_getSingle() };
|
|
76
|
+
}
|
|
46
77
|
//# sourceMappingURL=connect.js.map
|
package/dist/core/connect.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connect.js","sourceRoot":"","sources":["../../src/core/connect.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"connect.js","sourceRoot":"","sources":["../../src/core/connect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAyB,YAAY,EAAE,OAAO,EAA4B,MAAM,eAAe,CAAC;AAqBvG;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB;IAC5C,MAAM,OAAO,GAAkB,MAAM,iBAAiB,EAAE,CAAC;IAEzD,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;QAClC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC;IAC3C,CAAC;IAED,MAAM,KAAK,GAAkB,MAAM,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;IAC1E,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;QACvB,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC;IACzC,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAkB,MAAM,OAAO,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC;QACpE,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;YACvB,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC;QAC1C,CAAC;QACD,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;QACvB,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC;IACzC,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;QACvB,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;IAC7G,CAAC;AACH,CAAC;AAeD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAAC,IAAY,EAAE,GAAW,EAAE,KAAa;IACrF,MAAM,OAAO,GAAwB,MAAM,OAAO,CAAC,UAAU,CAAC,2BAA2B,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC;IAChH,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC;QACvB,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;QACvB,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,iBAAiB,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;IAC1F,CAAC;IACD,OAAO,CAAC,OAAO,GAAG,KAAK,CAAC;IACxB,MAAM,YAAY,CAAC,WAAW,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IACxD,MAAM,YAAY,CAAC,WAAW,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IACtD,MAAM,YAAY,CAAC,WAAW,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IACtD,MAAM,YAAY,CAAC,WAAW,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IACxD,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,iBAAiB,EAAE,EAAE,CAAC;AACrE,CAAC"}
|
package/dist/core/dispatch.d.ts
CHANGED
|
@@ -25,20 +25,6 @@ export { COMMAND_HANDLERS_KEYS } from '../command-keys.js';
|
|
|
25
25
|
* @param enabled - Whether timing display is active.
|
|
26
26
|
*/
|
|
27
27
|
export declare function command_timingMaybePrint(startTime: number, enabled: boolean): void;
|
|
28
|
-
/**
|
|
29
|
-
* Expands `$NAME` and `${NAME}` environment references in one token.
|
|
30
|
-
*
|
|
31
|
-
* Applied per token after parsing, so an expanded value can never inject
|
|
32
|
-
* command separators, pipes or redirects (the structural characters were
|
|
33
|
-
* already consumed). References to unset variables are left verbatim —
|
|
34
|
-
* kinder than the shell's silent empty string when a script forgets an
|
|
35
|
-
* export. Shell-escape lines (`!...`) never reach this point; bash does
|
|
36
|
-
* its own expansion there.
|
|
37
|
-
*
|
|
38
|
-
* @param token - A single parsed command token.
|
|
39
|
-
* @returns The token with set environment references substituted.
|
|
40
|
-
*/
|
|
41
|
-
export declare function envRefs_expand(token: string): string;
|
|
42
28
|
/**
|
|
43
29
|
* Dispatches a parsed command to its handler and returns its envelope, with
|
|
44
30
|
* any errors the command left on the stack drained into the envelope.
|