@velaro/cli 1.4.101 → 1.4.102

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.
@@ -1,5 +1,6 @@
1
1
  import { messagingGet, messagingPost } from '../api.js';
2
2
  import { runCommand } from '../run.js';
3
+ import { yearInReviewBody } from '../year-in-review-range.js';
3
4
 
4
5
  // -- velaro enterprise-reports ----------------------------------------------------
5
6
  // Backend: velaro-messaging /api/enterprise-reports/* (Enterprise Reports package: enterprise-internal, default OFF;
@@ -20,6 +21,7 @@ const REPORTS = {
20
21
  'promise-ledger': 'Agent commitments made in chat and whether a follow-up appeared; overdue list',
21
22
  'promise-dismiss': 'Dismiss one promise by --id (managers only)',
22
23
  'weekly-brief-send-test': 'Email this week\'s brief to the signed-in user',
24
+ 'year-in-review': 'Build the 12-month Year in review document: --from/--to (default last 12 whole Pacific months) starts a build and prints its id; --id <id> checks status and the download link',
23
25
  };
24
26
 
25
27
  export const enterpriseReportsCommand = {
@@ -43,12 +45,21 @@ export const enterpriseReportsCommand = {
43
45
  .option('time-zone', { type: 'string', describe: 'demand-coverage: IANA time zone (default America/Los_Angeles)' })
44
46
  .option('low-score', { type: 'number', describe: 'bot-containment: QA score below which a handed-off chat counts as regret (1-100, default 60)' })
45
47
  .option('include-demo', { type: 'boolean', describe: 'Show only the seeded sample (demo) data' })
46
- .option('id', { type: 'string', describe: 'promise-dismiss: the promise id' }),
48
+ .option('id', { type: 'string', describe: 'promise-dismiss: the promise id; year-in-review: the build id to check' }),
47
49
  handler: runCommand(async (argv) => {
48
50
  if (argv.report === 'weekly-brief-send-test') {
49
51
  console.log(JSON.stringify(await messagingPost('/api/enterprise-reports/weekly-brief/send-test', {}), null, 2));
50
52
  return;
51
53
  }
54
+ if (argv.report === 'year-in-review') {
55
+ // POST starts a build (202 {id}); with --id, GET its status {status: queued|building|completed|failed, downloadUrl, error}. 403 when the key is off.
56
+ if (argv.id) {
57
+ console.log(JSON.stringify(await messagingGet(`/api/enterprise-reports/year-in-review/${encodeURIComponent(String(argv.id))}`), null, 2));
58
+ return;
59
+ }
60
+ console.log(JSON.stringify(await messagingPost('/api/enterprise-reports/year-in-review', yearInReviewBody(argv.from, argv.to)), null, 2));
61
+ return;
62
+ }
52
63
  if (argv.report === 'promise-dismiss') {
53
64
  if (!argv.id) throw new Error('--id is required for promise-dismiss');
54
65
  console.log(JSON.stringify(await messagingPost(`/api/enterprise-reports/promise-ledger/${encodeURIComponent(String(argv.id))}/dismiss`, {}), null, 2));
@@ -2,9 +2,28 @@
2
2
  import test from 'node:test';
3
3
  import assert from 'node:assert/strict';
4
4
  import { enterpriseReportsCommand } from './commands/enterprise-reports.js';
5
+ import { yearInReviewBody, lastTwelveWholeMonthsKeys } from './year-in-review-range.js';
5
6
 
6
7
  test('describe lists the five novel reports and the promise dismiss action', () => {
7
8
  for (const r of ['repeat-contact', 'demand-coverage', 'bot-containment', 'effort-index', 'promise-ledger', 'promise-dismiss']) {
8
9
  assert.ok(enterpriseReportsCommand.describe.includes(r), `${r} missing from describe`);
9
10
  }
10
11
  });
12
+
13
+ // Year in review (item 16): the CLI sends Pacific-midnight instants, defaulting to the last 12 whole Pacific months.
14
+ test('describe lists year-in-review', () => {
15
+ assert.ok(enterpriseReportsCommand.describe.includes('year-in-review'));
16
+ });
17
+
18
+ test('year-in-review default range is the last 12 whole Pacific months', () => {
19
+ const now = new Date('2026-10-14T20:00:00Z');
20
+ assert.deepEqual(lastTwelveWholeMonthsKeys(now), { from: '2025-10-01', to: '2026-09-30' });
21
+ assert.deepEqual(yearInReviewBody(undefined, undefined, now), { from: '2025-10-01T07:00:00.000Z', to: '2026-10-01T07:00:00.000Z' });
22
+ });
23
+
24
+ test('year-in-review uses Pacific, not UTC, at a month boundary and honours explicit dates (inclusive to)', () => {
25
+ assert.deepEqual(lastTwelveWholeMonthsKeys(new Date('2026-11-01T03:00:00Z')), { from: '2025-10-01', to: '2026-09-30' });
26
+ assert.deepEqual(yearInReviewBody('2026-01-01', '2026-01-31'), { from: '2026-01-01T08:00:00.000Z', to: '2026-02-01T08:00:00.000Z' });
27
+ assert.throws(() => yearInReviewBody('2026-02-01', '2026-01-01'), /on or before/);
28
+ assert.throws(() => yearInReviewBody('01/01/2026', '2026-01-31'), /yyyy-MM-dd/);
29
+ });
@@ -0,0 +1,44 @@
1
+ // Year in review (Enterprise Reports item 16) range helpers. The default is the last 12 whole calendar months in Pacific time,
2
+ // sent to the server as a half-open [from, to) pair of ISO instants (Pacific midnight), same rule as the Enterprise Reports tab.
3
+ // mcp/server.js carries the same logic inline (it is a separate package); enterprise-reports.test.js pins this one.
4
+
5
+ const ZONE = 'America/Los_Angeles';
6
+
7
+ function offsetMs(instantMs) {
8
+ const parts = new Intl.DateTimeFormat('en-US', { timeZone: ZONE, hourCycle: 'h23', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' }).formatToParts(new Date(instantMs));
9
+ const g = (t) => Number(parts.find((p) => p.type === t).value);
10
+ return Date.UTC(g('year'), g('month') - 1, g('day'), g('hour'), g('minute'), g('second')) - Math.floor(instantMs / 1000) * 1000;
11
+ }
12
+
13
+ /** ISO instant of Pacific midnight at the start of the yyyy-MM-dd calendar day. */
14
+ export function pacificMidnightIso(key) {
15
+ const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(key || '');
16
+ if (!m) throw new Error(`Invalid date "${key}", expected yyyy-MM-dd`);
17
+ const wall = Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
18
+ let t = wall - offsetMs(wall);
19
+ t = wall - offsetMs(t);
20
+ return new Date(t).toISOString();
21
+ }
22
+
23
+ export function addDays(key, n) {
24
+ const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(key);
25
+ return new Date(Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3]) + n)).toISOString().slice(0, 10);
26
+ }
27
+
28
+ /** Last 12 whole Pacific calendar months as inclusive day keys: { from: first day, to: last day }. */
29
+ export function lastTwelveWholeMonthsKeys(now = new Date()) {
30
+ const today = new Intl.DateTimeFormat('en-CA', { timeZone: ZONE, year: 'numeric', month: '2-digit', day: '2-digit' }).format(now); // yyyy-MM-dd
31
+ const y = Number(today.slice(0, 4));
32
+ const mo = today.slice(5, 7);
33
+ const firstOfThisMonth = `${today.slice(0, 4)}-${mo}-01`;
34
+ return { from: `${String(y - 1).padStart(4, '0')}-${mo}-01`, to: addDays(firstOfThisMonth, -1) };
35
+ }
36
+
37
+ /** The request body: { from, to } instants. `from`/`to` are inclusive yyyy-MM-dd day keys; both default to the last 12 whole months. */
38
+ export function yearInReviewBody(from, to, now = new Date()) {
39
+ const def = lastTwelveWholeMonthsKeys(now);
40
+ const f = from || def.from;
41
+ const t = to || def.to;
42
+ if (f > t) throw new Error('--from must be on or before --to');
43
+ return { from: pacificMidnightIso(f), to: pacificMidnightIso(addDays(t, 1)) };
44
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velaro/cli",
3
- "version": "1.4.101",
3
+ "version": "1.4.102",
4
4
  "description": "Velaro Workspace v20 — command-line interface for managing bots, knowledge base ingestion, MCP API keys, search indexes, and ops health.",
5
5
  "type": "module",
6
6
  "bin": {