@fnndsc/brasa 0.23.2 → 0.24.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.
Files changed (89) hide show
  1. package/dist/buildinfo.json +1 -1
  2. package/dist/builtins/fs/pull.args.d.ts +1 -1
  3. package/dist/builtins/fs/pull.args.js +5 -3
  4. package/dist/builtins/fs/pull.args.js.map +1 -1
  5. package/dist/builtins/fs/pull.js +2 -2
  6. package/dist/builtins/fs/touch.js +1 -1
  7. package/dist/builtins/fs/touch.js.map +1 -1
  8. package/dist/builtins/help.js +128 -10
  9. package/dist/builtins/help.js.map +1 -1
  10. package/dist/builtins/index.d.ts +5 -0
  11. package/dist/builtins/index.js +5 -0
  12. package/dist/builtins/index.js.map +1 -1
  13. package/dist/builtins/net/query.csv.js +14 -0
  14. package/dist/builtins/net/query.csv.js.map +1 -1
  15. package/dist/builtins/net/query.js +3 -0
  16. package/dist/builtins/net/query.js.map +1 -1
  17. package/dist/builtins/pluginExecute.js +28 -3
  18. package/dist/builtins/pluginExecute.js.map +1 -1
  19. package/dist/builtins/proc.js +32 -2
  20. package/dist/builtins/proc.js.map +1 -1
  21. package/dist/builtins/res/dicom.d.ts +35 -1
  22. package/dist/builtins/res/dicom.js +15 -2
  23. package/dist/builtins/res/dicom.js.map +1 -1
  24. package/dist/builtins/res/expect.d.ts +40 -0
  25. package/dist/builtins/res/expect.js +447 -0
  26. package/dist/builtins/res/expect.js.map +1 -0
  27. package/dist/builtins/res/gather.d.ts +10 -0
  28. package/dist/builtins/res/gather.js +224 -0
  29. package/dist/builtins/res/gather.js.map +1 -0
  30. package/dist/builtins/res/gather.store.d.ts +131 -0
  31. package/dist/builtins/res/gather.store.js +225 -0
  32. package/dist/builtins/res/gather.store.js.map +1 -0
  33. package/dist/builtins/res/pipeline.args.d.ts +5 -2
  34. package/dist/builtins/res/pipeline.args.js +8 -3
  35. package/dist/builtins/res/pipeline.args.js.map +1 -1
  36. package/dist/builtins/res/pipeline.js +23 -0
  37. package/dist/builtins/res/pipeline.js.map +1 -1
  38. package/dist/builtins/res/play.d.ts +70 -0
  39. package/dist/builtins/res/play.js +334 -0
  40. package/dist/builtins/res/play.js.map +1 -0
  41. package/dist/builtins/res/plugin.js +21 -6
  42. package/dist/builtins/res/plugin.js.map +1 -1
  43. package/dist/builtins/res/record.d.ts +49 -0
  44. package/dist/builtins/res/record.js +150 -0
  45. package/dist/builtins/res/record.js.map +1 -0
  46. package/dist/builtins/res/runWait.d.ts +49 -0
  47. package/dist/builtins/res/runWait.js +193 -0
  48. package/dist/builtins/res/runWait.js.map +1 -0
  49. package/dist/builtins/sys/weather.d.ts +132 -0
  50. package/dist/builtins/sys/weather.js +297 -0
  51. package/dist/builtins/sys/weather.js.map +1 -0
  52. package/dist/command-keys.js +4 -0
  53. package/dist/command-keys.js.map +1 -1
  54. package/dist/core/connect.d.ts +31 -1
  55. package/dist/core/connect.js +32 -1
  56. package/dist/core/connect.js.map +1 -1
  57. package/dist/core/dispatch.d.ts +0 -14
  58. package/dist/core/dispatch.js +83 -27
  59. package/dist/core/dispatch.js.map +1 -1
  60. package/dist/core/engine.d.ts +10 -0
  61. package/dist/core/engine.js +9 -0
  62. package/dist/core/engine.js.map +1 -1
  63. package/dist/core/expansion.d.ts +70 -0
  64. package/dist/core/expansion.js +289 -0
  65. package/dist/core/expansion.js.map +1 -0
  66. package/dist/core/promptContext.js +5 -1
  67. package/dist/core/promptContext.js.map +1 -1
  68. package/dist/lib/duration.d.ts +17 -0
  69. package/dist/lib/duration.js +25 -0
  70. package/dist/lib/duration.js.map +1 -0
  71. package/dist/lib/parser.d.ts +50 -0
  72. package/dist/lib/parser.js +119 -0
  73. package/dist/lib/parser.js.map +1 -1
  74. package/dist/logo/brain.d.ts +6 -27
  75. package/dist/logo/brain.js +5 -250
  76. package/dist/logo/brain.js.map +1 -1
  77. package/dist/session/answer.d.ts +141 -0
  78. package/dist/session/answer.js +163 -0
  79. package/dist/session/answer.js.map +1 -0
  80. package/dist/session/answerAdapters.d.ts +4 -0
  81. package/dist/session/answerAdapters.js +106 -0
  82. package/dist/session/answerAdapters.js.map +1 -0
  83. package/dist/session/recent.d.ts +68 -0
  84. package/dist/session/recent.js +99 -0
  85. package/dist/session/recent.js.map +1 -0
  86. package/dist/session/recorder.d.ts +61 -0
  87. package/dist/session/recorder.js +95 -0
  88. package/dist/session/recorder.js.map +1 -0
  89. package/package.json +5 -5
@@ -0,0 +1,49 @@
1
+ import { CommandEnvelope } from '@fnndsc/cumin';
2
+ /** The model kind under which a finished recording travels. */
3
+ export declare const RECORD_MODEL_KIND: string;
4
+ /** What a recording turned into. */
5
+ export interface RecordedManifest {
6
+ /** Where it was written. */
7
+ path: string;
8
+ /** How many lines it holds. */
9
+ lines: number;
10
+ /** The parameters it was given, and the literal each replaced. */
11
+ params: Array<{
12
+ name: string;
13
+ was: string;
14
+ }>;
15
+ /** How long the recording ran. */
16
+ elapsedMs: number;
17
+ }
18
+ /**
19
+ * Offers parameters for the identifiers a recording carries.
20
+ *
21
+ * @param lines - The captured lines.
22
+ * @returns The lines with identifiers replaced, and what was replaced.
23
+ */
24
+ export declare function identifiers_offer(lines: string[]): {
25
+ lines: string[];
26
+ params: Array<{
27
+ name: string;
28
+ was: string;
29
+ }>;
30
+ };
31
+ /**
32
+ * Renders the manifest a recording became.
33
+ *
34
+ * @param name - What to call it.
35
+ * @param lines - The captured lines, already parameterized.
36
+ * @param params - The parameters offered.
37
+ * @returns The file's text.
38
+ */
39
+ export declare function manifest_render(name: string, lines: string[], params: Array<{
40
+ name: string;
41
+ was: string;
42
+ }>): string;
43
+ /**
44
+ * Builtin handler for the `record` command.
45
+ *
46
+ * @param args - Parsed command arguments.
47
+ * @returns An envelope reporting what the recorder is doing, or what it wrote.
48
+ */
49
+ export declare function builtin_record(args: string[]): Promise<CommandEnvelope>;
@@ -0,0 +1,150 @@
1
+ /**
2
+ * @file Builtin record command: writing a session down as a manifest.
3
+ *
4
+ * Subcommands: start, stop, status.
5
+ *
6
+ * Every verb lowers to a line the operator could have typed, so a session's
7
+ * line history is very nearly a manifest already — a press on a surface and
8
+ * a line at a console arrive at the same place, and the recorder sits there.
9
+ * That is why this costs almost nothing: the lowering was already the law.
10
+ *
11
+ * @module
12
+ */
13
+ import chalk from 'chalk';
14
+ import { envelope_ok, envelope_error } from '@fnndsc/cumin';
15
+ import { files_touch as chefs_touch_cmd } from '@fnndsc/chili/commands/fs/touch.js';
16
+ import { files_mkdir as chefs_mkdir_cmd } from '@fnndsc/chili/commands/fs/mkdir.js';
17
+ import path from 'path';
18
+ import { commandArgs_process, path_resolve } from '../utils.js';
19
+ import { recorder_start, recorder_state, recorder_stop, } from '../../session/recorder.js';
20
+ /** The model kind under which a finished recording travels. */
21
+ export const RECORD_MODEL_KIND = 'manifest.recorded';
22
+ /**
23
+ * Literals worth offering as parameters.
24
+ *
25
+ * A recording is personal until its identifiers come out of it: the MRN and
26
+ * the accession are exactly what the next person has to change, and they are
27
+ * the two a PACS line wears on its face. Nothing else is guessed at — a
28
+ * parameter the author did not want is worse than one they have to add.
29
+ */
30
+ const IDENTIFIER_PATTERNS = [
31
+ { criterion: 'PatientID', param: 'MRN' },
32
+ { criterion: 'AccessionNumber', param: 'ACC' },
33
+ { criterion: 'PatientName', param: 'PATIENT' },
34
+ ];
35
+ /**
36
+ * Offers parameters for the identifiers a recording carries.
37
+ *
38
+ * @param lines - The captured lines.
39
+ * @returns The lines with identifiers replaced, and what was replaced.
40
+ */
41
+ export function identifiers_offer(lines) {
42
+ const params = [];
43
+ const taken = new Map();
44
+ const rewritten = lines.map((line) => {
45
+ let text = line;
46
+ for (const { criterion, param } of IDENTIFIER_PATTERNS) {
47
+ // The value stops at an underscore or a slash: a query's projection
48
+ // folder is `PatientID:1279049_qid:3125_owner`, and a value that ran on
49
+ // to the end of the word would parameterize the query id too, so the
50
+ // same MRN in a path and in a query would read as two patients.
51
+ const pattern = new RegExp(`${criterion}:([^\\s'"/_]+)`, 'g');
52
+ text = text.replace(pattern, (match, value) => {
53
+ const held = taken.get(match);
54
+ if (held !== undefined)
55
+ return `${criterion}:\${${held}}`;
56
+ // A second distinct value for the same criterion gets its own name,
57
+ // or a workflow over two patients would collapse into one.
58
+ const name = taken.size === 0 || ![...taken.values()].includes(param)
59
+ ? param
60
+ : `${param}_${taken.size + 1}`;
61
+ taken.set(match, name);
62
+ params.push({ name, was: value });
63
+ return `${criterion}:\${${name}}`;
64
+ });
65
+ }
66
+ return text;
67
+ });
68
+ return { lines: rewritten, params };
69
+ }
70
+ /**
71
+ * Renders the manifest a recording became.
72
+ *
73
+ * @param name - What to call it.
74
+ * @param lines - The captured lines, already parameterized.
75
+ * @param params - The parameters offered.
76
+ * @returns The file's text.
77
+ */
78
+ export function manifest_render(name, lines, params) {
79
+ const header = [
80
+ `@name ${name}`,
81
+ `@description Recorded from a session on ${new Date().toISOString().slice(0, 10)}.`,
82
+ ];
83
+ for (const param of params)
84
+ header.push(`@param ${param.name} = ${param.was}`);
85
+ return `${[...header, '', ...lines].join('\n')}\n`;
86
+ }
87
+ /**
88
+ * Builtin handler for the `record` command.
89
+ *
90
+ * @param args - Parsed command arguments.
91
+ * @returns An envelope reporting what the recorder is doing, or what it wrote.
92
+ */
93
+ export async function builtin_record(args) {
94
+ const parsed = commandArgs_process(args);
95
+ const subcommand = parsed._[0];
96
+ if (subcommand === 'start') {
97
+ const target = parsed._[1];
98
+ if (target === undefined) {
99
+ process.exitCode = 1;
100
+ return envelope_error('', undefined, `${chalk.red('Usage: record start <manifest>')}\n`);
101
+ }
102
+ const state = recorder_state();
103
+ if (state.target !== null) {
104
+ process.exitCode = 1;
105
+ return envelope_error('', undefined, `${chalk.red(`record: already recording to ${state.target} — stop it first.`)}\n`);
106
+ }
107
+ const resolved = await path_resolve(target);
108
+ recorder_start(resolved);
109
+ return envelope_ok(`${chalk.green(`record: recording to ${resolved}. Do the work; \`record stop\` writes it.`)}\n`);
110
+ }
111
+ if (subcommand === 'stop') {
112
+ const held = recorder_stop();
113
+ if (held === null) {
114
+ process.exitCode = 1;
115
+ return envelope_error('', undefined, `${chalk.red('record: nothing is recording.')}\n`);
116
+ }
117
+ if (held.lines.length === 0) {
118
+ return envelope_ok(`${chalk.yellow(`record: nothing to write — the session ran no lines while recording.`)}\n`);
119
+ }
120
+ const { lines, params } = identifiers_offer(held.lines);
121
+ const name = path.posix.basename(held.target).replace(/\.[^.]+$/, '');
122
+ await chefs_mkdir_cmd(path.posix.dirname(held.target));
123
+ const written = await chefs_touch_cmd(held.target, {
124
+ withContents: manifest_render(name, lines, params),
125
+ });
126
+ if (!written) {
127
+ process.exitCode = 1;
128
+ return envelope_error('', undefined, `${chalk.red(`record: could not write ${held.target}.`)}\n`);
129
+ }
130
+ const recorded = {
131
+ path: held.target,
132
+ lines: lines.length,
133
+ params,
134
+ elapsedMs: held.elapsedMs,
135
+ };
136
+ const offered = params.length === 0
137
+ ? ''
138
+ : `${chalk.gray(` offered ${params.length} param${params.length === 1 ? '' : 's'}: `)}${params.map((param) => `${param.was} → \${${param.name}}`).join(', ')}\n`;
139
+ return envelope_ok(`${chalk.green(`record: wrote ${held.target} · ${lines.length} line${lines.length === 1 ? '' : 's'}`)}\n${offered}`, { kind: RECORD_MODEL_KIND, data: recorded });
140
+ }
141
+ if (subcommand === undefined || subcommand === 'status') {
142
+ const state = recorder_state();
143
+ if (state.target === null)
144
+ return envelope_ok(`${chalk.gray('Nothing is recording.')}\n`);
145
+ return envelope_ok(`${chalk.green(`Recording to ${state.target}`)} ${chalk.gray(`· ${state.lines} line${state.lines === 1 ? '' : 's'} · ${Math.round(state.elapsedMs / 1000)}s`)}\n`);
146
+ }
147
+ process.exitCode = 1;
148
+ return envelope_error('', undefined, `${chalk.red(`Unknown subcommand: ${subcommand}. Usage: record <start|stop|status>`)}\n`);
149
+ }
150
+ //# sourceMappingURL=record.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record.js","sourceRoot":"","sources":["../../../src/builtins/res/record.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ /** Statuses a run does not leave. */
2
+ export declare const RUN_TERMINAL_STATUSES: ReadonlySet<string>;
3
+ /** How a wait ended. */
4
+ export type RunSettlementOutcome = 'settled' | 'detached' | 'unreachable';
5
+ /** What a wait found out. */
6
+ export interface RunSettlement {
7
+ /** The runs waited on. */
8
+ instanceIDs: number[];
9
+ /** Each run's status: terminal when settled, last seen otherwise. */
10
+ statuses: Map<number, string>;
11
+ /** How the wait ended. */
12
+ outcome: RunSettlementOutcome;
13
+ /** How many runs ended in a status that is not success. */
14
+ failed: number;
15
+ /** How long the wait lasted. */
16
+ elapsedMs: number;
17
+ }
18
+ /** How often a wait asks, for a caller that knows better than the default. */
19
+ export interface RunWaitCadence {
20
+ /** Milliseconds between asks. Both windows collapse to this when given. */
21
+ pollMs?: number;
22
+ }
23
+ /**
24
+ * Waits for every given run to reach a status it does not leave.
25
+ *
26
+ * @param instanceIDs - The plugin instances to wait on.
27
+ * @param label - What to call the work in progress and readouts.
28
+ * @param cadence - How often to ask, when the default rhythm is wrong.
29
+ * @returns How the wait ended, and the statuses it ended on.
30
+ */
31
+ export declare function runs_awaitSettled(instanceIDs: number[], label: string, cadence?: RunWaitCadence): Promise<RunSettlement>;
32
+ /**
33
+ * Renders how long a wait lasted, the way an operator says it.
34
+ *
35
+ * @param ms - Elapsed milliseconds.
36
+ * @returns A short duration, such as `2m14s`.
37
+ */
38
+ export declare function elapsed_render(ms: number): string;
39
+ /**
40
+ * Renders what a wait found, in the operator's terms.
41
+ *
42
+ * A detached wait says how to get back to the work, because the run is still
43
+ * going and the handle is the only way to reach it.
44
+ *
45
+ * @param settlement - What the wait found.
46
+ * @param label - What the work is called.
47
+ * @returns The line to print.
48
+ */
49
+ export declare function settlement_render(settlement: RunSettlement, label: string): string;
@@ -0,0 +1,193 @@
1
+ /**
2
+ * @file Waiting for scheduled work to settle.
3
+ *
4
+ * A scheduled run used to return the moment CUBE accepted it, which reads as
5
+ * completion and is not: the next line in a script runs against output that
6
+ * does not exist yet, and the only way to sequence work was a sleep, which is
7
+ * a lie about time. A verb that starts work therefore waits for it, and
8
+ * `--detach` is how an operator asks for the handle instead.
9
+ *
10
+ * Waiting holds the LINE, never the session. Progress goes out on the
11
+ * progress channel while the wait runs, so a surface shows the work moving
12
+ * and other panes keep refreshing; a cancellation (Esc at a surface, Ctrl-C
13
+ * at a console) DETACHES rather than kills — the run continues and its
14
+ * handle is printed, which is what `--detach` would have given, asked for
15
+ * later.
16
+ *
17
+ * A pipeline schedules several nodes at once, so a wait is over a SET: it
18
+ * ends when every node it was given has reached a status it does not leave.
19
+ *
20
+ * @module
21
+ */
22
+ import chalk from 'chalk';
23
+ import { jobs_statusBatch } from '@fnndsc/salsa';
24
+ import { sink_get } from '../../core/sink.js';
25
+ import { commandCancellation_enable, commandCancellation_signalGet } from '../../core/cancellation.js';
26
+ /** Statuses a run does not leave. */
27
+ export const RUN_TERMINAL_STATUSES = new Set([
28
+ 'finishedSuccessfully',
29
+ 'finishedWithError',
30
+ 'cancelled',
31
+ ]);
32
+ /** First seconds are polled briskly; a long run is not worth the traffic. */
33
+ const POLL_FAST_MS = 2000;
34
+ /** What the interval settles to once the work is clearly not instant. */
35
+ const POLL_SLOW_MS = 5000;
36
+ /** When the interval stretches. */
37
+ const POLL_BRISK_WINDOW_MS = 30000;
38
+ /** Consecutive unanswered status asks before the wait gives up on knowing. */
39
+ const STATUS_MISSES_MAX = 5;
40
+ /**
41
+ * Sleeps, unless the command is cancelled first.
42
+ *
43
+ * @param ms - How long to wait.
44
+ * @param signal - The foreground command's abort signal, when there is one.
45
+ * @returns True when the sleep completed, false when cancellation cut it short.
46
+ */
47
+ async function pause_take(ms, signal) {
48
+ if (signal?.aborted === true)
49
+ return false;
50
+ return await new Promise((resolve) => {
51
+ const onAbort = () => {
52
+ clearTimeout(timer);
53
+ resolve(false);
54
+ };
55
+ const timer = setTimeout(() => {
56
+ signal?.removeEventListener('abort', onAbort);
57
+ resolve(true);
58
+ }, ms);
59
+ signal?.addEventListener('abort', onAbort, { once: true });
60
+ });
61
+ }
62
+ /**
63
+ * Says how the wait is going, on the channel progress travels on.
64
+ *
65
+ * @param label - What to call the work on a surface.
66
+ * @param settled - How many of the runs have finished.
67
+ * @param total - How many runs the wait covers.
68
+ * @param phase - Whether the work is still going, done, or failed.
69
+ */
70
+ function runProgress_emit(label, settled, total, phase) {
71
+ sink_get().progress_write({
72
+ operation: 'workflow',
73
+ kind: 'workflow',
74
+ phase,
75
+ label,
76
+ current: settled,
77
+ total,
78
+ percent: total > 0 ? Math.min(100, (settled / total) * 100) : undefined,
79
+ unit: 'jobs',
80
+ status: phase === 'working' ? 'running' : (phase === 'complete' ? 'done' : 'error'),
81
+ });
82
+ }
83
+ /**
84
+ * Counts the runs that have reached a terminal status, and those that failed.
85
+ *
86
+ * @param statuses - Status by instance id.
87
+ * @returns How many are settled, and how many of those did not succeed.
88
+ */
89
+ function settlement_count(statuses) {
90
+ let settled = 0;
91
+ let failed = 0;
92
+ for (const status of statuses.values()) {
93
+ if (!RUN_TERMINAL_STATUSES.has(status))
94
+ continue;
95
+ settled += 1;
96
+ if (status !== 'finishedSuccessfully')
97
+ failed += 1;
98
+ }
99
+ return { settled, failed };
100
+ }
101
+ /**
102
+ * Waits for every given run to reach a status it does not leave.
103
+ *
104
+ * @param instanceIDs - The plugin instances to wait on.
105
+ * @param label - What to call the work in progress and readouts.
106
+ * @param cadence - How often to ask, when the default rhythm is wrong.
107
+ * @returns How the wait ended, and the statuses it ended on.
108
+ */
109
+ export async function runs_awaitSettled(instanceIDs, label, cadence = {}) {
110
+ const started = Date.now();
111
+ const statuses = new Map(instanceIDs.map((id) => [id, 'scheduled']));
112
+ if (instanceIDs.length === 0) {
113
+ return { instanceIDs, statuses, outcome: 'settled', failed: 0, elapsedMs: 0 };
114
+ }
115
+ // A wait is exactly the kind of work a cancellation should reach.
116
+ commandCancellation_enable();
117
+ const signal = commandCancellation_signalGet();
118
+ let misses = 0;
119
+ for (;;) {
120
+ const read = await jobs_statusBatch(instanceIDs);
121
+ if (read.size === 0) {
122
+ // One unanswered ask is a network hiccup, not a verdict on the work.
123
+ misses += 1;
124
+ if (misses >= STATUS_MISSES_MAX) {
125
+ return { instanceIDs, statuses, outcome: 'unreachable', failed: 0, elapsedMs: Date.now() - started };
126
+ }
127
+ }
128
+ else {
129
+ misses = 0;
130
+ for (const [id, status] of read)
131
+ statuses.set(id, status);
132
+ const { settled, failed } = settlement_count(statuses);
133
+ if (settled === instanceIDs.length) {
134
+ runProgress_emit(label, settled, instanceIDs.length, failed > 0 ? 'failed' : 'complete');
135
+ return { instanceIDs, statuses, outcome: 'settled', failed, elapsedMs: Date.now() - started };
136
+ }
137
+ runProgress_emit(label, settled, instanceIDs.length, 'working');
138
+ }
139
+ const elapsed = Date.now() - started;
140
+ const interval = cadence.pollMs
141
+ ?? (elapsed < POLL_BRISK_WINDOW_MS ? POLL_FAST_MS : POLL_SLOW_MS);
142
+ const slept = await pause_take(interval, signal);
143
+ if (!slept) {
144
+ const { failed } = settlement_count(statuses);
145
+ return { instanceIDs, statuses, outcome: 'detached', failed, elapsedMs: Date.now() - started };
146
+ }
147
+ }
148
+ }
149
+ /**
150
+ * Renders how long a wait lasted, the way an operator says it.
151
+ *
152
+ * @param ms - Elapsed milliseconds.
153
+ * @returns A short duration, such as `2m14s`.
154
+ */
155
+ export function elapsed_render(ms) {
156
+ const seconds = Math.round(ms / 1000);
157
+ if (seconds < 60)
158
+ return `${seconds}s`;
159
+ const minutes = Math.floor(seconds / 60);
160
+ return `${minutes}m${`${seconds % 60}`.padStart(2, '0')}s`;
161
+ }
162
+ /**
163
+ * Renders what a wait found, in the operator's terms.
164
+ *
165
+ * A detached wait says how to get back to the work, because the run is still
166
+ * going and the handle is the only way to reach it.
167
+ *
168
+ * @param settlement - What the wait found.
169
+ * @param label - What the work is called.
170
+ * @returns The line to print.
171
+ */
172
+ export function settlement_render(settlement, label) {
173
+ const took = elapsed_render(settlement.elapsedMs);
174
+ const handles = settlement.instanceIDs.join(', ');
175
+ if (settlement.outcome === 'detached') {
176
+ return chalk.yellow(`${label}: detached after ${took} — the run continues. Instance ${handles}; follow it with \`plugininstance list\` or the feed's graph.`);
177
+ }
178
+ if (settlement.outcome === 'unreachable') {
179
+ return chalk.yellow(`${label}: lost touch with CUBE after ${took} — the run may still be going. Instance ${handles}.`);
180
+ }
181
+ if (settlement.failed > 0) {
182
+ const failedIds = [...settlement.statuses.entries()]
183
+ .filter(([, status]) => status !== 'finishedSuccessfully')
184
+ .map(([id, status]) => `${id} ${status}`)
185
+ .join(', ');
186
+ return chalk.red(`${label}: finished with errors after ${took} — ${failedIds}`);
187
+ }
188
+ const nodes = settlement.instanceIDs.length === 1
189
+ ? `instance ${handles}`
190
+ : `${settlement.instanceIDs.length} nodes`;
191
+ return chalk.green(`${label}: finished successfully in ${took} (${nodes})`);
192
+ }
193
+ //# sourceMappingURL=runWait.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runWait.js","sourceRoot":"","sources":["../../../src/builtins/res/runWait.ts"],"names":[],"mappings":"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"}
@@ -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 {};