@junoflow/cli 0.14.0 → 0.15.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/README.md CHANGED
@@ -165,9 +165,18 @@ The group's working-copy commands (`integration init`, `gen-types`,
165
165
 
166
166
  **`finding`**
167
167
 
168
- | Command | Tool | Positional | Effect |
169
- | ------------------ | ------------- | ---------- | ------ |
170
- | `juno finding get` | `get_finding` | `slug` | read |
168
+ | Command | Tool | Positional | Effect |
169
+ | -------------------------- | --------------------- | ---------- | ------ |
170
+ | `juno finding list` | `list_findings` | — | read |
171
+ | `juno finding get` | `get_finding` | `slug` | read |
172
+ | `juno finding get-history` | `get_finding_history` | `slug` | read |
173
+ | `juno finding dismiss` | `dismiss_finding` | `slug` | act |
174
+
175
+ `finding list` shows open findings of every owner — flows, integrations,
176
+ devices, states and tasks — each naming its source as `owner_kind`, `owner` and
177
+ `owner_name`; `--status all` adds cleared ones, and `--owner-kind`, `--owner`
178
+ and `--severity` (a floor) narrow it. `get-history` and `dismiss` take `--key`
179
+ for a keyed finding; `get-history` also takes `--limit`.
171
180
 
172
181
  **`webhook`**
173
182
 
@@ -178,16 +187,22 @@ The group's working-copy commands (`integration init`, `gen-types`,
178
187
  | `juno webhook write` | `write_webhook` | `id` | change |
179
188
  | `juno webhook fire` | `fire_webhook` | `id` | act |
180
189
 
181
- **`job`** (external jobs)
190
+ **`task`** (tasks)
182
191
 
183
- | Command | Tool | Positional | Effect |
184
- | ------------------------------------ | -------------- | ---------- | ------ |
185
- | `juno job list` | `list_jobs` | — | read |
186
- | `juno job get-runs` | `get_job_runs` | `id` | read |
187
- | `juno job write` | `write_job` | `id` | change |
188
- | `juno job delete` | `delete_job` | `id` | act |
189
- | `juno job run [<url>] -- <command>…` | none | ping URL | act |
190
- | `juno job ping [<url>]` | none | ping URL | act |
192
+ | Command | Tool | Positional | Effect |
193
+ | ------------------------------------- | --------------- | ---------- | ------ |
194
+ | `juno task list` | `list_tasks` | — | read |
195
+ | `juno task get-runs` | `get_task_runs` | `id` | read |
196
+ | `juno task write` | `write_task` | `id` | change |
197
+ | `juno task delete` | `delete_task` | `id` | act |
198
+ | `juno task run [<url>] -- <command>…` | none | ping URL | act |
199
+ | `juno task ping [<url>]` | none | ping URL | act |
200
+
201
+ **`series`**
202
+
203
+ | Command | Tool | Positional | Effect |
204
+ | ----------------- | ------------ | ---------- | ------ |
205
+ | `juno series get` | `get_series` | `id` | read |
191
206
 
192
207
  **`credential`**
193
208
 
@@ -338,15 +353,15 @@ exits 1 without sending anything otherwise. Any other message sent to a session
338
353
  waiting for an approval denies it, and the assistant sees the message as the
339
354
  reason.
340
355
 
341
- ### `juno job run`
356
+ ### `juno task run`
342
357
 
343
- Wraps a scheduled command so its runs are reported to an external job's ping URL
344
- (`juno job write` answers it as `url`):
358
+ Wraps a scheduled command so its runs are reported to a task's ping URL
359
+ (`juno task write` answers it as `url`):
345
360
 
346
361
  ```bash
347
- juno job run https://juno.example/ping/<token> -- ./backup.sh --full
348
- juno job run --url-file .secrets/juno-jobs/backup -- ./backup.sh --full
349
- JUNO_JOB_URL=https://juno.example/ping/<token> juno job run -- python3 check.py
362
+ juno task run https://juno.example/ping/<token> -- ./backup.sh --full
363
+ juno task run --url-file .secrets/juno-tasks/backup -- ./backup.sh --full
364
+ JUNO_TASK_URL=https://juno.example/ping/<token> juno task run -- python3 check.py
350
365
  ```
351
366
 
352
367
  The command runs exactly as it would unwrapped: same stdin, the same stdout and
@@ -355,33 +370,33 @@ signal `N`; 127, or 126 for a file that is not executable, when it cannot be
355
370
  started). Everything after `--` is the command's, flags included. Juno gets a
356
371
  `start`, then the exit status with the last 64 KiB of stdout and stderr, paired
357
372
  by a run id. The URL comes from the argument, else `--url-file` (keeps the token
358
- out of `ps`), else `$JUNO_JOB_URL`; nothing else is read — no instance config,
373
+ out of `ps`), else `$JUNO_TASK_URL`; nothing else is read — no instance config,
359
374
  no API key. A report that fails is retried once and then dropped silently
360
375
  (`--verbose` prints one line): Juno turns a run it never heard about into an
361
376
  overdue finding by itself. `SIGINT`, `SIGTERM` and `SIGHUP` are passed on to the
362
377
  command.
363
378
 
364
- The wrapper never times a command out: a run that hangs stays open, and the job
379
+ The wrapper never times a command out: a run that hangs stays open, and the task
365
380
  goes down once its start plus grace has passed. A wrapper killed outright leaves
366
381
  the run open the same way.
367
382
 
368
- ### `juno job ping`
383
+ ### `juno task ping`
369
384
 
370
385
  Sends one report, for a task with no command to wrap:
371
386
 
372
387
  ```bash
373
- juno job ping --url-file .secrets/juno-jobs/digest # success
374
- juno job ping --url-file .secrets/juno-jobs/digest --start --rid r1
375
- juno job ping --url-file .secrets/juno-jobs/digest --exit 3 --rid r1
376
- juno job ping --url-file .secrets/juno-jobs/digest --fail
388
+ juno task ping --url-file .secrets/juno-tasks/digest # success
389
+ juno task ping --url-file .secrets/juno-tasks/digest --start --rid r1
390
+ juno task ping --url-file .secrets/juno-tasks/digest --exit 3 --rid r1
391
+ juno task ping --url-file .secrets/juno-tasks/digest --fail
377
392
  ```
378
393
 
379
- The URL comes from the same three places as `juno job run`'s. `--log` sends a
394
+ The URL comes from the same three places as `juno task run`'s. `--log` sends a
380
395
  log report, and `--rid` pairs a start with its finish. A report that still fails
381
396
  after one retry exits 1 with the reason.
382
397
 
383
- See [External jobs](https://boivie.github.io/juno/external-jobs/) for creating
384
- the job, the ping protocol, and systemd and crontab examples.
398
+ See [Tasks](https://boivie.github.io/juno/tasks/) for creating the task, the
399
+ ping protocol, and systemd and crontab examples.
385
400
 
386
401
  ### `juno credential set`
387
402
 
package/app-client.js CHANGED
@@ -14,27 +14,36 @@
14
14
  * rejects with an Error whose message is the handler's (or the host's).
15
15
  *
16
16
  * window.juno.values
17
- * The app's bound states and controls, by binding name:
18
- * {states: {<b>: value}, controls: {<b>: {value, active, expires_at}}}, or
19
- * null until the first snapshot has arrived.
17
+ * The app's bound states, controls and series, by binding name:
18
+ * {states: {<b>: value}, controls: {<b>: {value, active, expires_at}},
19
+ * series: {<b>: {published_at, issued_at, covered_until, intervals}}}, or
20
+ * null until the first snapshot has arrived. A series moves on every
21
+ * publish, changed or not.
20
22
  *
21
23
  * window.juno.watch(cb) → unwatch()
22
24
  * Calls cb(values, changed) on every snapshot and update, where changed
23
- * lists the keys that moved ("states.<b>", "controls.<b>"; every key on a
24
- * snapshot). Registered after a snapshot, cb is called at once with every
25
- * key.
25
+ * lists the keys that moved ("states.<b>", "controls.<b>", "series.<b>";
26
+ * every key on a snapshot). Registered after a snapshot, cb is called at
27
+ * once with every key.
28
+ *
29
+ * window.juno.series.<binding>.get(range?) → Promise<read>
30
+ * window.juno.series.<binding>.view(spec) → Promise<result>
31
+ * What a handler's series.<binding>.get / .view answer, with no handler:
32
+ * GET _juno/series/<binding>?from=&to= and POST _juno/series/<binding>/view.
33
+ * A Date bound is sent as its ISO string.
26
34
  *
27
35
  * window.juno.controls.<binding>.onPress(cb) → unwatch()
28
36
  * Calls cb(at) on every press of that bound button control, at an RFC 3339
29
37
  * timestamp.
30
38
  *
31
- * handlers and controls are proxies, not maps of what the manifest declares:
39
+ * handlers, controls and series are proxies, not maps of what the manifest declares:
32
40
  * the client is never told the names, so any name reaches the server, and an
33
41
  * unknown handler is a rejected call carrying the host's 404 — the same answer
34
42
  * a hand-typed name got before. A name some JavaScript protocol looks up on
35
43
  * every object (then, toJSON, toString, …) answers undefined instead, so
36
44
  * `await juno.handlers` or JSON.stringify(juno) never fires a call;
37
- * manifest.ValidateApp refuses those names for a handler or a control binding.
45
+ * manifest.ValidateApp refuses those names for a handler, a control binding or
46
+ * a series binding.
38
47
  *
39
48
  * The values arrive over one EventSource on _juno/stream, opened by the first
40
49
  * watch or onPress and never on page load: a page that does not watch holds no
@@ -147,6 +156,67 @@
147
156
  return body.result;
148
157
  }
149
158
 
159
+ // readJSON answers a series route: the body on success, its error thrown
160
+ // otherwise. Like a call, every answer names the version that served it.
161
+ async function readJSON(res) {
162
+ checkVersion(res);
163
+ var body;
164
+ try {
165
+ body = await res.json();
166
+ } catch (e) {
167
+ throw new Error('HTTP ' + res.status);
168
+ }
169
+ if (!res.ok) {
170
+ throw new Error(
171
+ body && typeof body.error === 'string'
172
+ ? body.error
173
+ : 'HTTP ' + res.status
174
+ );
175
+ }
176
+ return body;
177
+ }
178
+
179
+ function instant(v) {
180
+ // Duck-typed rather than instanceof, so a Date from another realm (an
181
+ // iframe) is still sent as its ISO string.
182
+ return v && typeof v.toISOString === 'function'
183
+ ? v.toISOString()
184
+ : String(v);
185
+ }
186
+
187
+ function seriesURL(binding) {
188
+ return base + '_juno/series/' + encodeURIComponent(binding);
189
+ }
190
+
191
+ async function seriesGet(binding, range) {
192
+ var q = [];
193
+ if (range && range.from !== undefined && range.from !== null) {
194
+ q.push('from=' + encodeURIComponent(instant(range.from)));
195
+ }
196
+ if (range && range.to !== undefined && range.to !== null) {
197
+ q.push('to=' + encodeURIComponent(instant(range.to)));
198
+ }
199
+ var res = await fetch(
200
+ seriesURL(binding) + (q.length ? '?' + q.join('&') : ''),
201
+ {
202
+ headers: { 'X-Juno-App-Version': String(version) },
203
+ }
204
+ );
205
+ return readJSON(res);
206
+ }
207
+
208
+ async function seriesView(binding, spec) {
209
+ var res = await fetch(seriesURL(binding) + '/view', {
210
+ method: 'POST',
211
+ headers: {
212
+ 'Content-Type': 'application/json',
213
+ 'X-Juno-App-Version': String(version),
214
+ },
215
+ body: JSON.stringify(spec === undefined ? {} : spec),
216
+ });
217
+ return readJSON(res);
218
+ }
219
+
150
220
  // Live values. The stream is opened lazily and at most one is open.
151
221
  var values = null;
152
222
  var watchers = [];
@@ -190,6 +260,9 @@
190
260
  Object.keys(vals.controls || {}).forEach(function (b) {
191
261
  out.push('controls.' + b);
192
262
  });
263
+ Object.keys(vals.series || {}).forEach(function (b) {
264
+ out.push('series.' + b);
265
+ });
193
266
  return out;
194
267
  }
195
268
 
@@ -207,6 +280,7 @@
207
280
  values = {
208
281
  states: data.values.states || {},
209
282
  controls: data.values.controls || {},
283
+ series: data.values.series || {},
210
284
  };
211
285
  var changed = keys(values);
212
286
  watchers.slice().forEach(function (cb) {
@@ -214,7 +288,7 @@
214
288
  });
215
289
  }
216
290
 
217
- // onUpdate merges per binding. An update carries both maps, the one that did
291
+ // onUpdate merges per binding. An update carries every map, the ones that did
218
292
  // not move empty, so the changed keys are exactly the bindings present.
219
293
  function onUpdate(ev) {
220
294
  var data = parse(ev);
@@ -222,10 +296,12 @@
222
296
  var moved = {
223
297
  states: data.values.states || {},
224
298
  controls: data.values.controls || {},
299
+ series: data.values.series || {},
225
300
  };
226
301
  values = {
227
302
  states: Object.assign({}, values.states, moved.states),
228
303
  controls: Object.assign({}, values.controls, moved.controls),
304
+ series: Object.assign({}, values.series, moved.series),
229
305
  };
230
306
  var changed = keys(moved);
231
307
  if (!changed.length) return;
@@ -298,6 +374,16 @@
298
374
  },
299
375
  });
300
376
  }),
377
+ series: namespace(function (binding) {
378
+ return Object.freeze({
379
+ get: function (range) {
380
+ return seriesGet(binding, range);
381
+ },
382
+ view: function (spec) {
383
+ return seriesView(binding, spec);
384
+ },
385
+ });
386
+ }),
301
387
  get values() {
302
388
  return values;
303
389
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@junoflow/cli",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "Develop Juno flows, integrations and apps, and operate a Juno instance from the command line",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -40,7 +40,7 @@
40
40
  "node": ">=20"
41
41
  },
42
42
  "dependencies": {
43
- "@junoflow/eslint-plugin": "0.14.0",
43
+ "@junoflow/eslint-plugin": "0.15.0",
44
44
  "@typescript-eslint/parser": "^8.0.0"
45
45
  },
46
46
  "devDependencies": {
@@ -8,8 +8,8 @@ import { parseArgs } from 'node:util';
8
8
  import { UsageError } from '../errors.js';
9
9
  import { EXIT_FAILURE, EXIT_OK } from '../exit.js';
10
10
 
11
- export const JOB_RUN_USAGE =
12
- 'juno job run [<url>] [--url-file <path>] [--verbose] -- <command> [args…]';
11
+ export const TASK_RUN_USAGE =
12
+ 'juno task run [<url>] [--url-file <path>] [--verbose] -- <command> [args…]';
13
13
 
14
14
  /** The tail of each stream a run reports; Juno keeps the same. */
15
15
  export const TAIL_BYTES = 64 << 10;
@@ -24,7 +24,7 @@ const DEFAULT_TIMEOUTS = {
24
24
  };
25
25
 
26
26
  /**
27
- * `juno job run` runs a command and reports it to an external job's ping URL
27
+ * `juno task run` runs a command and reports it to a task's ping URL
28
28
  * (plan-external-jobs-2-wrapper-and-tools.md, Decisions 3–5). Its first rule
29
29
  * is transparency: the command gets the same stdin, and the wrapper's stdout,
30
30
  * stderr and exit status are the command's, byte for byte — a scheduler such
@@ -34,11 +34,11 @@ const DEFAULT_TIMEOUTS = {
34
34
  * on its own, so the wrapper never queues and never fails the command.
35
35
  *
36
36
  * It reads no Juno config and no API key; the URL is all it needs. argv is
37
- * everything after `job run`, `--` included. The exit code is returned, not
37
+ * everything after `task run`, `--` included. The exit code is returned, not
38
38
  * passed to process.exit, so stdout is flushed before the process ends.
39
39
  * deps is injectable for the tests; the default binds the real ones.
40
40
  */
41
- export async function jobRun(argv, io, deps = {}) {
41
+ export async function taskRun(argv, io, deps = {}) {
42
42
  const {
43
43
  spawn = nodeSpawn,
44
44
  fetch = globalThis.fetch,
@@ -65,20 +65,20 @@ export async function jobRun(argv, io, deps = {}) {
65
65
  });
66
66
  } catch (err) {
67
67
  throw new UsageError(
68
- `juno job run: ${err.message}\nusage: ${JOB_RUN_USAGE}`
68
+ `juno task run: ${err.message}\nusage: ${TASK_RUN_USAGE}`
69
69
  );
70
70
  }
71
71
  if (command.length === 0) {
72
72
  throw new UsageError(
73
- `juno job run: missing -- <command>\nusage: ${JOB_RUN_USAGE}`
73
+ `juno task run: missing -- <command>\nusage: ${TASK_RUN_USAGE}`
74
74
  );
75
75
  }
76
76
  if (parsed.positionals.length > 1) {
77
77
  throw new UsageError(
78
- `juno job run: one URL before --, got ${parsed.positionals.length} arguments\nusage: ${JOB_RUN_USAGE}`
78
+ `juno task run: one URL before --, got ${parsed.positionals.length} arguments\nusage: ${TASK_RUN_USAGE}`
79
79
  );
80
80
  }
81
- const url = await pingURL('job run', JOB_RUN_USAGE, {
81
+ const url = await pingURL('task run', TASK_RUN_USAGE, {
82
82
  positional: parsed.positionals[0],
83
83
  file: parsed.values['url-file'],
84
84
  env,
@@ -107,7 +107,11 @@ export async function jobRun(argv, io, deps = {}) {
107
107
  let suffix;
108
108
  let exitCode;
109
109
  if (outcome.startError) {
110
- const line = `juno job run: ${command[0]}: ${outcome.startError.message}`;
110
+ // With no shell, `FOO=bar cmd` names an executable called "FOO=bar".
111
+ const line =
112
+ outcome.startError.code === 'ENOENT' && command[0].includes('=')
113
+ ? `juno task run: "${command[0]}" looks like an env assignment; write -- env ${command.join(' ')}`
114
+ : `juno task run: ${command[0]}: ${outcome.startError.message}`;
111
115
  io.error(line);
112
116
  err.push(Buffer.from(`${line}\n`));
113
117
  suffix = 'fail';
@@ -136,14 +140,14 @@ export async function jobRun(argv, io, deps = {}) {
136
140
  failure = await finish();
137
141
  }
138
142
  if (failure && verbose) {
139
- io.error(`juno job run: could not report to Juno: ${failure}`);
143
+ io.error(`juno task run: could not report to Juno: ${failure}`);
140
144
  }
141
145
  return exitCode;
142
146
  }
143
147
 
144
148
  /**
145
149
  * pingURL is the URL from the first source that yields one: the positional,
146
- * then --url-file (its contents, trimmed), then $JUNO_JOB_URL. The two
150
+ * then --url-file (its contents, trimmed), then $JUNO_TASK_URL. The two
147
151
  * indirect sources keep the token off the command line, where `ps` and the
148
152
  * scheduler's job definition would show it. command and usage name the verb
149
153
  * in its refusals.
@@ -165,26 +169,26 @@ async function pingURL(
165
169
  }
166
170
  if (text.trim()) return text.trim();
167
171
  }
168
- if (env.JUNO_JOB_URL?.trim()) return env.JUNO_JOB_URL.trim();
172
+ if (env.JUNO_TASK_URL?.trim()) return env.JUNO_TASK_URL.trim();
169
173
  throw new UsageError(
170
- `juno ${command}: no ping URL — pass it, --url-file or JUNO_JOB_URL\nusage: ${usage}`
174
+ `juno ${command}: no ping URL — pass it, --url-file or JUNO_TASK_URL\nusage: ${usage}`
171
175
  );
172
176
  }
173
177
 
174
178
  /**
175
179
  * endpoint is `<url>/<suffix>?rid=<rid>`, used as given otherwise: no /ping/
176
- * check, so a Healthchecks URL works too. A query the URL already carries is
177
- * kept; without a rid there is no `rid` parameter.
180
+ * check, so a Healthchecks URL works too. An empty suffix is a success, which
181
+ * is the bare URL. A query the URL already carries is kept; without a rid
182
+ * there is no `rid` parameter.
178
183
  */
179
184
  export function endpoint(url, suffix, rid) {
180
185
  const q = url.indexOf('?');
181
- const path = (q === -1 ? url : url.slice(0, q)).replace(/\/+$/, '');
186
+ const base = (q === -1 ? url : url.slice(0, q)).replace(/\/+$/, '');
187
+ const path = suffix ? `${base}/${suffix}` : base;
182
188
  const params = [];
183
189
  if (q !== -1 && q < url.length - 1) params.push(url.slice(q + 1));
184
190
  if (rid) params.push(`rid=${encodeURIComponent(rid)}`);
185
- return params.length
186
- ? `${path}/${suffix}?${params.join('&')}`
187
- : `${path}/${suffix}`;
191
+ return params.length ? `${path}?${params.join('&')}` : path;
188
192
  }
189
193
 
190
194
  /**
@@ -291,24 +295,24 @@ export class Tail {
291
295
  }
292
296
  }
293
297
 
294
- export const JOB_PING_USAGE =
295
- 'juno job ping [<url>] [--url-file <path>] [--start | --fail | --exit <0-255> | --log] [--rid <id>]';
298
+ export const TASK_PING_USAGE =
299
+ 'juno task ping [<url>] [--url-file <path>] [--start | --fail | --exit <0-255> | --log] [--rid <id>]';
296
300
 
297
301
  const PING_KINDS = ['start', 'fail', 'exit', 'log'];
298
302
 
299
303
  /**
300
- * `juno job ping` sends one report to an external job's ping URL, for a task
301
- * `job run` cannot wrap — one whose scheduler runs no command, or whose start
304
+ * `juno task ping` sends one report to a task's ping URL, for a task
305
+ * `task run` cannot wrap — one whose scheduler runs no command, or whose start
302
306
  * and finish happen in different places. Without a flag it reports success;
303
307
  * `--start`, `--fail`, `--exit <n>` and `--log` send the other reports, and
304
308
  * `--rid` pairs a start with its finish. It sends no output.
305
309
  *
306
- * It finds the URL as `job run` does and, like it, reads no Juno config and no
307
- * API key. Unlike `job run` it has nothing else to do, so a report that still
310
+ * It finds the URL as `task run` does and, like it, reads no Juno config and no
311
+ * API key. Unlike `task run` it has nothing else to do, so a report that still
308
312
  * fails after one retry is exit 1 with the reason, not silence: the caller
309
313
  * asked for exactly this and can `|| true` it.
310
314
  */
311
- export async function jobPing(args, io, deps = {}) {
315
+ export async function taskPing(args, io, deps = {}) {
312
316
  const {
313
317
  fetch = globalThis.fetch,
314
318
  env = process.env,
@@ -316,7 +320,7 @@ export async function jobPing(args, io, deps = {}) {
316
320
  timeouts = DEFAULT_TIMEOUTS,
317
321
  } = deps;
318
322
  const usage = (msg) =>
319
- new UsageError(`juno job ping: ${msg}\nusage: ${JOB_PING_USAGE}`);
323
+ new UsageError(`juno task ping: ${msg}\nusage: ${TASK_PING_USAGE}`);
320
324
 
321
325
  if (args._.length > 1) {
322
326
  throw usage(`one URL, got ${args._.length} arguments`);
@@ -327,7 +331,8 @@ export async function jobPing(args, io, deps = {}) {
327
331
  if (kinds.length > 1) {
328
332
  throw usage(`--${kinds.join(' and --')} are different reports; pick one`);
329
333
  }
330
- let suffix = 'success';
334
+ // A success is the bare URL: the protocol has no /success suffix.
335
+ let suffix = '';
331
336
  if (kinds[0] === 'exit') {
332
337
  const n = args.exit.trim();
333
338
  if (!/^\d{1,3}$/.test(n) || Number(n) > 255) {
@@ -337,7 +342,7 @@ export async function jobPing(args, io, deps = {}) {
337
342
  } else if (kinds[0]) {
338
343
  suffix = kinds[0];
339
344
  }
340
- const url = await pingURL('job ping', JOB_PING_USAGE, {
345
+ const url = await pingURL('task ping', TASK_PING_USAGE, {
341
346
  positional: args._[0],
342
347
  file: args['url-file'],
343
348
  env,
@@ -356,7 +361,7 @@ export async function jobPing(args, io, deps = {}) {
356
361
  failure = await send();
357
362
  }
358
363
  if (failure) {
359
- io.error(`juno job ping: could not report to Juno: ${failure}`);
364
+ io.error(`juno task ping: could not report to Juno: ${failure}`);
360
365
  return EXIT_FAILURE;
361
366
  }
362
367
  return EXIT_OK;
package/src/exit.js CHANGED
@@ -13,7 +13,7 @@
13
13
  * 5 `juno ask` / `session approve`: the turn is still running (pending)
14
14
  * 6 `juno ask` / `session approve`: the assistant is awaiting input
15
15
  *
16
- * `juno job run` is the exception once its own arguments are accepted: it
16
+ * `juno task run` is the exception once its own arguments are accepted: it
17
17
  * exits with the wrapped command's status, as a shell would report it.
18
18
  */
19
19
  export const EXIT_OK = 0;
package/src/index.js CHANGED
@@ -30,11 +30,11 @@ import { FLOW_PULL_USAGE, flowPull } from './commands/flow-pull.js';
30
30
  import { GEN_TYPES_USAGE, genTypes } from './commands/gen-types.js';
31
31
  import { INIT_USAGE, init } from './commands/init.js';
32
32
  import {
33
- JOB_PING_USAGE,
34
- JOB_RUN_USAGE,
35
- jobPing,
36
- jobRun,
37
- } from './commands/job-run.js';
33
+ TASK_PING_USAGE,
34
+ TASK_RUN_USAGE,
35
+ taskPing,
36
+ taskRun,
37
+ } from './commands/task-run.js';
38
38
  import { PULL_USAGE, pull } from './commands/pull.js';
39
39
  import { SKILL_USAGE, skill } from './commands/skill.js';
40
40
  import {
@@ -111,19 +111,19 @@ const NON_TOOL = {
111
111
  // raw: everything after `--` is the wrapped command's, verbatim, so the
112
112
  // argv goes to the handler unparsed (see runRow). No --url or --token: the
113
113
  // wrapper reads no Juno config, only the ping URL.
114
- 'job run': {
115
- run: (argv, io) => jobRun(argv, io, { env: io.env }),
114
+ 'task run': {
115
+ run: (argv, io) => taskRun(argv, io, { env: io.env }),
116
116
  options: {
117
117
  'url-file': { type: 'string' },
118
118
  verbose: { type: 'boolean', default: false },
119
119
  },
120
- usage: JOB_RUN_USAGE,
120
+ usage: TASK_RUN_USAGE,
121
121
  raw: true,
122
122
  },
123
- // No --url or --token either: one report to the ping URL, found as job run
123
+ // No --url or --token either: one report to the ping URL, found as task run
124
124
  // finds it.
125
- 'job ping': {
126
- run: (args, io) => jobPing(args, io, { env: io.env }),
125
+ 'task ping': {
126
+ run: (args, io) => taskPing(args, io, { env: io.env }),
127
127
  options: {
128
128
  'url-file': { type: 'string' },
129
129
  start: { type: 'boolean' },
@@ -132,7 +132,7 @@ const NON_TOOL = {
132
132
  log: { type: 'boolean' },
133
133
  rid: { type: 'string' },
134
134
  },
135
- usage: JOB_PING_USAGE,
135
+ usage: TASK_PING_USAGE,
136
136
  },
137
137
  'config set': { run: configSet, options: {}, usage: CONFIG_SET_USAGE },
138
138
  'agent-skill install': {
package/verbs.json CHANGED
@@ -212,7 +212,32 @@
212
212
  "working_copy": true,
213
213
  "summary": "Serve this app locally on mocked data"
214
214
  },
215
+ {
216
+ "command": "finding list",
217
+ "tool": "list_findings",
218
+ "flags": {
219
+ "status": { "field": "status", "type": "string" },
220
+ "owner-kind": { "field": "owner_kind", "type": "string" },
221
+ "owner": { "field": "owner", "type": "string" },
222
+ "severity": { "field": "severity", "type": "string" }
223
+ }
224
+ },
215
225
  { "command": "finding get", "tool": "get_finding", "id_field": "slug" },
226
+ {
227
+ "command": "finding get-history",
228
+ "tool": "get_finding_history",
229
+ "id_field": "slug",
230
+ "flags": {
231
+ "key": { "field": "key", "type": "string" },
232
+ "limit": { "field": "limit", "type": "integer" }
233
+ }
234
+ },
235
+ {
236
+ "command": "finding dismiss",
237
+ "tool": "dismiss_finding",
238
+ "id_field": "slug",
239
+ "flags": { "key": { "field": "key", "type": "string" } }
240
+ },
216
241
  { "command": "webhook list", "tool": "list_webhooks" },
217
242
  {
218
243
  "command": "webhook get-deliveries",
@@ -221,10 +246,12 @@
221
246
  },
222
247
  { "command": "webhook write", "tool": "write_webhook", "id_field": "id" },
223
248
  { "command": "webhook fire", "tool": "fire_webhook", "id_field": "id" },
224
- { "command": "job list", "tool": "list_jobs" },
225
- { "command": "job get-runs", "tool": "get_job_runs", "id_field": "id" },
226
- { "command": "job write", "tool": "write_job", "id_field": "id" },
227
- { "command": "job delete", "tool": "delete_job", "id_field": "id" },
249
+ { "command": "task list", "tool": "list_tasks" },
250
+ { "command": "task get-runs", "tool": "get_task_runs", "id_field": "id" },
251
+ { "command": "task write", "tool": "write_task", "id_field": "id" },
252
+ { "command": "task delete", "tool": "delete_task", "id_field": "id" },
253
+ { "command": "task command", "tool": "command_task", "id_field": "id" },
254
+ { "command": "series get", "tool": "get_series", "id_field": "id" },
228
255
  { "command": "credential list", "tool": "list_credentials" },
229
256
  { "command": "person list", "tool": "list_people" },
230
257
  { "command": "person get", "tool": "get_person", "id_field": "id" },
@@ -240,6 +267,16 @@
240
267
  "id_field": "run_id",
241
268
  "flags": { "handle": { "field": "handle_id", "type": "string" } }
242
269
  },
270
+ {
271
+ "command": "question answer",
272
+ "tool": "answer_question",
273
+ "id_field": "run_id",
274
+ "flags": {
275
+ "handle": { "field": "handle_id", "type": "string" },
276
+ "answer": { "field": "answer", "type": "string" },
277
+ "for": { "field": "for", "type": "string" }
278
+ }
279
+ },
243
280
  {
244
281
  "command": "ha get-entity",
245
282
  "tool": "get_entity",
@@ -276,14 +313,14 @@
276
313
  "summary": "Approve the tool call an assistant session is waiting on"
277
314
  },
278
315
  {
279
- "command": "job run",
316
+ "command": "task run",
280
317
  "effect": "act",
281
- "summary": "Run a command and report it to an external job's ping URL"
318
+ "summary": "Run a command and report it to a task's ping URL"
282
319
  },
283
320
  {
284
- "command": "job ping",
321
+ "command": "task ping",
285
322
  "effect": "act",
286
- "summary": "Send one report (success, start, fail, an exit status or a log) to an external job's ping URL"
323
+ "summary": "Send one report (success, start, fail, an exit status or a log) to a task's ping URL"
287
324
  },
288
325
  {
289
326
  "command": "credential set",