@indigoai-us/hq-cli 5.345.142 → 5.345.144

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 (61) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/dist/command-catalog.generated.d.ts +278 -6
  3. package/dist/command-catalog.generated.js +341 -6
  4. package/dist/command-registration-plan.d.ts +6 -0
  5. package/dist/command-registration-plan.js +1 -0
  6. package/dist/commands/agent-enroll.d.ts +1 -0
  7. package/dist/commands/agent-enroll.js +7 -0
  8. package/dist/commands/agents.d.ts +25 -3
  9. package/dist/commands/agents.js +57 -4
  10. package/dist/commands/api-keys.js +66 -8
  11. package/dist/commands/billing-sponsor.d.ts +4 -0
  12. package/dist/commands/billing-sponsor.js +48 -5
  13. package/dist/commands/billing-usage.d.ts +59 -0
  14. package/dist/commands/billing-usage.js +162 -0
  15. package/dist/commands/billing.d.ts +9 -0
  16. package/dist/commands/billing.js +29 -2
  17. package/dist/commands/core-policy.js +76 -13
  18. package/dist/commands/library-add.d.ts +120 -0
  19. package/dist/commands/library-add.js +434 -0
  20. package/dist/commands/library-caption.d.ts +112 -0
  21. package/dist/commands/library-caption.js +393 -0
  22. package/dist/commands/library-link.d.ts +32 -0
  23. package/dist/commands/library-link.js +95 -0
  24. package/dist/commands/library.d.ts +72 -0
  25. package/dist/commands/library.js +527 -0
  26. package/dist/commands/meetings.d.ts +8 -0
  27. package/dist/commands/meetings.js +25 -2
  28. package/dist/commands/storage-offload.d.ts +16 -2
  29. package/dist/commands/storage-offload.js +55 -4
  30. package/dist/commands/storage.d.ts +8 -1
  31. package/dist/commands/storage.js +6 -2
  32. package/dist/hq-cloud-sync-runner/bin/sync-runner.js +2410 -2063
  33. package/dist/hq-cloud-sync-runner/package.json +1 -1
  34. package/dist/lib/billing/plan-lock.d.ts +15 -1
  35. package/dist/lib/billing/plan-lock.js +45 -5
  36. package/dist/lib/billing/usage-card.d.ts +82 -0
  37. package/dist/lib/billing/usage-card.js +198 -0
  38. package/dist/lib/billing/usage-cost.d.ts +214 -0
  39. package/dist/lib/billing/usage-cost.js +440 -0
  40. package/dist/lib/billing/usage-only.d.ts +87 -0
  41. package/dist/lib/billing/usage-only.js +262 -0
  42. package/dist/lib/billing/usage-rate-card.d.ts +51 -0
  43. package/dist/lib/billing/usage-rate-card.js +62 -0
  44. package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
  45. package/dist/lib/flag-registry-worker-source.generated.js +1 -1
  46. package/dist/lib/plan-limit-nag.d.ts +4 -6
  47. package/dist/lib/plan-limit-nag.js +19 -1
  48. package/dist/lib/provider-launcher.d.ts +22 -0
  49. package/dist/lib/provider-launcher.js +65 -0
  50. package/dist/lib/search-index/index.d.ts +2 -0
  51. package/dist/lib/search-index/index.js +34 -0
  52. package/dist/lib/search-index/max-doc-bytes.d.ts +2 -1
  53. package/dist/lib/search-index/max-doc-bytes.js +2 -1
  54. package/dist/main.d.ts +9 -0
  55. package/dist/main.js +20 -3
  56. package/dist/utils/billing-gate.d.ts +19 -4
  57. package/dist/utils/billing-gate.js +25 -4
  58. package/dist/utils/plan-gate-error.d.ts +10 -0
  59. package/dist/utils/plan-gate-error.js +54 -0
  60. package/dist/utils/vault-api.js +4 -1
  61. package/package.json +2 -2
@@ -4,6 +4,7 @@
4
4
  import * as fs from 'node:fs';
5
5
  import * as path from 'node:path';
6
6
  import { policyValidatorDiagnostic } from '../lib/policy-validator.js';
7
+ import { evaluateTrigger } from '../utils/core-eval-trigger.js';
7
8
  import { packageRoot, resolveLiveRoot } from '../utils/hq-roots.js';
8
9
  import { describeCoreCommand } from './core-command-catalog.js';
9
10
  import { runPolicyFrontmatter as runPolicyFrontmatterImpl } from './core-policy-frontmatter.js';
@@ -165,12 +166,46 @@ function frontmatterScalar(file, key) {
165
166
  value = value.replace(/^["']|["']$/g, '');
166
167
  if (key === 'source' || key === 'created')
167
168
  value = value.replaceAll('"', '');
168
- if (key === 'created')
169
+ if (key === 'updated')
170
+ value = value.replace(/^["']|["']$/g, '');
171
+ if (key === 'created' || key === 'updated')
169
172
  return value.slice(0, 10);
170
173
  return value;
171
174
  }
172
175
  return '';
173
176
  }
177
+ /**
178
+ * The `when:` expression as inject-policy-on-trigger.sh reads it: the last
179
+ * `when:` line in the frontmatter, trailing ` # comment` removed, surrounding
180
+ * quotes stripped. Returns the expression when the canonical trigger evaluator
181
+ * (the one behind eval-trigger.sh) rejects it as malformed, so the policy can
182
+ * never match an event; undefined when it parses or there is no `when:`.
183
+ */
184
+ function malformedTrigger(text) {
185
+ const lines = text.split('\n');
186
+ if (!(lines[0] ?? '').startsWith('---'))
187
+ return undefined;
188
+ let depth = 0;
189
+ let when;
190
+ for (const rawLine of lines) {
191
+ const line = rawLine.replace(/\r$/, '');
192
+ if (/^---[ \t]*$/.test(line)) {
193
+ depth += 1;
194
+ if (depth === 2)
195
+ break;
196
+ continue;
197
+ }
198
+ if (depth !== 1 || !line.startsWith('when:'))
199
+ continue;
200
+ when = line
201
+ .replace(/^when:[ \t]*/, '')
202
+ .replace(/[ \t]+#.*$/, '')
203
+ .replace(/^["']|["']$/g, '');
204
+ }
205
+ if (when === undefined || when.trim() === '')
206
+ return undefined;
207
+ return evaluateTrigger(when, '') === 2 ? when : undefined;
208
+ }
174
209
  function ruleBody(file) {
175
210
  const text = fileText(file) ?? '';
176
211
  const lines = text.split('\n');
@@ -438,8 +473,18 @@ export function runPolicyAgeReport(args, root, env = process.env, nowEpoch = Mat
438
473
  const enforcement = frontmatterScalar(file, 'enforcement') || 'unset';
439
474
  const source = frontmatterScalar(file, 'source');
440
475
  const version = frontmatterScalar(file, 'version');
441
- const createdEpoch = epochFromDate(frontmatterScalar(file, 'created')) || statMtimeSeconds(file) || 0;
476
+ const mtimeEpoch = statMtimeSeconds(file);
477
+ const createdEpoch = epochFromDate(frontmatterScalar(file, 'created')) || mtimeEpoch || 0;
442
478
  const ageDays = Math.trunc((now - createdEpoch) / 86400);
479
+ // Usage history counts only from the last change to the policy (and so
480
+ // to its trigger): `updated:` frontmatter or the file mtime, whichever is
481
+ // newer. A trigger that was malformed and was just repaired is not judged
482
+ // on the period when it could not fire. An mtime later than the report
483
+ // clock (pinned clock or clock skew) is not a usable change date.
484
+ const changedEpoch = mtimeEpoch !== undefined && mtimeEpoch <= now ? mtimeEpoch : 0;
485
+ const clockEpoch = Math.max(createdEpoch, epochFromDate(frontmatterScalar(file, 'updated')), changedEpoch);
486
+ const clockDays = Math.trunc((now - clockEpoch) / 86400);
487
+ const brokenTrigger = malformedTrigger(text);
443
488
  const fired = usage.counts.get(id) ?? 0;
444
489
  const lastEpoch = usage.newest.get(id);
445
490
  const lastFired = lastEpoch === undefined ? null : new Date(lastEpoch * 1000).toISOString().slice(0, 10);
@@ -448,24 +493,30 @@ export function runPolicyAgeReport(args, root, env = process.env, nowEpoch = Mat
448
493
  const refs = refSummary(file, root);
449
494
  const singleIncident = ['user-correction', 'session-learning', 'back-pressure-failure'].includes(source) && (version || '1') === '1';
450
495
  const classes = [];
451
- if (fired === 0) {
452
- const comparison = bashTestGreaterOrEqual(ageDays, parsed.minAge, formerScriptPath(POLICY_AGE_REPORT_SCRIPT), 173, platform);
496
+ if (brokenTrigger !== undefined) {
497
+ // A policy whose `when:` cannot parse never had a chance to fire, so
498
+ // its usage history says nothing about whether the rule is needed. It
499
+ // is reported as broken and never offered for retirement.
500
+ classes.push('malformed-trigger');
501
+ }
502
+ else if (fired === 0) {
503
+ const comparison = bashTestGreaterOrEqual(clockDays, parsed.minAge, formerScriptPath(POLICY_AGE_REPORT_SCRIPT), 173, platform);
453
504
  stderr += comparison.stderr;
454
505
  if (comparison.value && usage.anyFiles)
455
506
  classes.push('never-fired');
456
507
  }
457
- if (fired > 0) {
458
- const comparison = bashTestGreaterOrEqual(lastAge, parsed.dormantDays, formerScriptPath(POLICY_AGE_REPORT_SCRIPT), 174, platform);
508
+ if (brokenTrigger === undefined && fired > 0) {
509
+ const comparison = bashTestGreaterOrEqual(Math.min(lastAge, clockDays), parsed.dormantDays, formerScriptPath(POLICY_AGE_REPORT_SCRIPT), 174, platform);
459
510
  stderr += comparison.stderr;
460
511
  if (comparison.value)
461
512
  classes.push('dormant');
462
513
  }
463
- if (refs.stale.length > 0)
514
+ if (brokenTrigger === undefined && refs.stale.length > 0)
464
515
  classes.push('stale-refs');
465
- if (refs.renamed.length > 0)
516
+ if (brokenTrigger === undefined && refs.renamed.length > 0)
466
517
  classes.push('renamed-refs');
467
- if (singleIncident) {
468
- const comparison = bashTestGreaterOrEqual(ageDays, parsed.incidentDays, formerScriptPath(POLICY_AGE_REPORT_SCRIPT), 178, platform);
518
+ if (brokenTrigger === undefined && singleIncident) {
519
+ const comparison = bashTestGreaterOrEqual(clockDays, parsed.incidentDays, formerScriptPath(POLICY_AGE_REPORT_SCRIPT), 178, platform);
469
520
  stderr += comparison.stderr;
470
521
  if (comparison.value && fired <= 1)
471
522
  classes.push('single-incident-old');
@@ -484,6 +535,7 @@ export function runPolicyAgeReport(args, root, env = process.env, nowEpoch = Mat
484
535
  retrieved,
485
536
  refs_judged: refs.judged,
486
537
  renamed_refs: refs.renamed,
538
+ ...(brokenTrigger === undefined ? {} : { malformed_trigger: brokenTrigger }),
487
539
  });
488
540
  }
489
541
  }
@@ -491,10 +543,14 @@ export function runPolicyAgeReport(args, root, env = process.env, nowEpoch = Mat
491
543
  const selected = parsed.candidates ? rows.filter((row) => row.candidate_classes.length > 0) : rows;
492
544
  return policyResult(0, `${JSON.stringify(selected)}\n`, stderr);
493
545
  }
494
- const never = rows.filter((row) => row.fired === 0).length;
495
- const staleTotal = rows.filter((row) => row.stale_refs.length > 0).length;
496
- const candidateCount = rows.filter((row) => row.candidate_classes.length > 0).length;
546
+ const malformed = rows.filter((row) => row.malformed_trigger !== undefined);
547
+ const never = rows.filter((row) => row.fired === 0 && row.malformed_trigger === undefined).length;
548
+ const staleTotal = rows.filter((row) => row.stale_refs.length > 0 && row.malformed_trigger === undefined).length;
549
+ const candidateCount = rows.filter((row) => row.candidate_classes.length > 0 && row.malformed_trigger === undefined).length;
497
550
  let stdout = `policy-age-report: ${rows.length} policies, ${never} never fired, ${staleTotal} with stale references, ${candidateCount} retirement candidates (min-age ${parsed.minAge}d, dormant ${parsed.dormantDays}d, incident ${parsed.incidentDays}d)\n`;
551
+ if (malformed.length > 0) {
552
+ stdout += `policy-age-report: ${malformed.length} with a malformed when: trigger that cannot fire (fix the trigger; not retirement candidates): ${malformed.map((row) => row.id).sort().join(', ')}\n`;
553
+ }
498
554
  if (!parsed.candidates)
499
555
  stdout += ' (add --candidates to list them, --json for machine-readable rows)\n';
500
556
  else {
@@ -805,6 +861,13 @@ function autoRetire(options, root, cwd, env, now) {
805
861
  continue;
806
862
  }
807
863
  let why = '';
864
+ if (classes.split(',').includes('malformed-trigger')) {
865
+ const expression = candidate.malformed_trigger ?? '';
866
+ reportText += `- needs-fix (not retired) **${candidate.id}** (${candidate.path}): malformed when: trigger, so it can never fire: ${expression}\n`;
867
+ stderr += `policy-retire: ${candidate.id} (${candidate.path}) has a malformed when: trigger and was not retired; fix it: ${expression}\n`;
868
+ needfix += 1;
869
+ continue;
870
+ }
808
871
  if (classes.split(',').includes('renamed-refs')) {
809
872
  reportText += `- needs-fix (not retired) **${candidate.id}** (${candidate.path}): referenced file was renamed: ${candidate.renamed_refs.join(', ')}\n`;
810
873
  needfix += 1;
@@ -0,0 +1,120 @@
1
+ import { Readable } from "node:stream";
2
+ import { type CloudAssetMediaKind } from "@indigoai-us/hq-cloud";
3
+ import type { LibraryCaptionNote, LibraryCaptionRequest } from "./library-caption.js";
4
+ export interface LibraryCompany {
5
+ uid: string;
6
+ slug: string;
7
+ }
8
+ export interface LibrarySource {
9
+ kind: "file" | "url";
10
+ /** The argument as given. */
11
+ input: string;
12
+ /** File name the placeholder is written under. */
13
+ name: string;
14
+ }
15
+ export interface OpenedSource {
16
+ stream: Readable;
17
+ contentType?: string;
18
+ }
19
+ export interface PutResponse {
20
+ status: number;
21
+ headers: Record<string, string | undefined>;
22
+ }
23
+ export interface LibraryAddDeps {
24
+ hqRoot: string;
25
+ now: () => Date;
26
+ /** Resolve the slug to an active company membership. */
27
+ resolveCompany: (slug: string) => Promise<LibraryCompany>;
28
+ /** POST /v1/files/presign. */
29
+ presign: (body: Record<string, unknown>) => Promise<Response>;
30
+ /** Open a fresh byte stream of a source. Called once to hash and once to upload. */
31
+ openSource: (source: LibrarySource) => Promise<OpenedSource>;
32
+ /** PUT `body` (exactly `size` bytes) to a presigned URL with `headers`. */
33
+ put: (url: string, headers: Record<string, string>, body: Readable, size: number) => Promise<PutResponse>;
34
+ writePlaceholder: (file: string, text: string) => Promise<void>;
35
+ /**
36
+ * Caption one written image or video placeholder (US-006). Must not throw:
37
+ * a caption failure never fails the upload. Absent = no captioning.
38
+ */
39
+ caption?: (request: LibraryCaptionRequest) => Promise<LibraryCaptionNote>;
40
+ }
41
+ export interface LibraryAddOptions {
42
+ company?: string;
43
+ to?: string;
44
+ tags: string[];
45
+ title?: string;
46
+ note?: string;
47
+ }
48
+ export interface LibraryAddItem {
49
+ source: string;
50
+ ok: boolean;
51
+ /** HQ-relative placeholder path (set when written). */
52
+ placeholderPath?: string;
53
+ originalPath?: string;
54
+ key?: string;
55
+ size?: number;
56
+ sha256?: string;
57
+ /** False when the vault already held the bytes. */
58
+ uploaded?: boolean;
59
+ error?: string;
60
+ /** AI caption outcome, set when captioning ran for this item. */
61
+ caption?: LibraryCaptionNote;
62
+ }
63
+ export interface LibraryAddResult {
64
+ company: string;
65
+ items: LibraryAddItem[];
66
+ }
67
+ export declare function contentTypeFor(name: string, served?: string): string;
68
+ export declare function mediaKindFor(contentType: string): CloudAssetMediaKind;
69
+ /** A file name safe to place in one directory: no separators, no control chars. */
70
+ export declare function safeAssetName(raw: string): string;
71
+ /** Classify an argument: an http(s) URL, otherwise a local file. */
72
+ export declare function parseSource(input: string): LibrarySource;
73
+ /**
74
+ * The library directory, HQ-relative. `--to` resolves against the HQ root and
75
+ * must be inside `companies/<co>/`, so `hq storage fetch` can find the
76
+ * company vault from the placeholder path. The default is
77
+ * `companies/<co>/library`.
78
+ */
79
+ export declare function resolveLibraryDir(hqRoot: string, options: Pick<LibraryAddOptions, "company" | "to">): {
80
+ slug: string;
81
+ dirRel: string;
82
+ dirAbs: string;
83
+ };
84
+ export declare function offloadTimestamp(now: Date): string;
85
+ /**
86
+ * Testable core. Every source is hashed first, then one presign request
87
+ * covers all of them (hq-pro rate-limits presign per user). A 402 for the
88
+ * request refuses every source and writes nothing; a per-key refusal fails
89
+ * only that source.
90
+ */
91
+ export declare function runLibraryAdd(inputs: readonly string[], options: LibraryAddOptions, deps: LibraryAddDeps): Promise<LibraryAddResult>;
92
+ export interface LibraryTagResult {
93
+ placeholderPath: string;
94
+ tags: string[];
95
+ changed: boolean;
96
+ }
97
+ /**
98
+ * Add and remove tags on a library placeholder. The placeholder is parsed and
99
+ * re-rendered by hq-cloud's placeholder writer, then replaced atomically
100
+ * (temp file + rename), so a sync watcher never reads a half-written file.
101
+ */
102
+ export declare function runLibraryTag(hqRoot: string, target: string, edits: {
103
+ add: string[];
104
+ remove: string[];
105
+ }, now?: () => Date): Promise<LibraryTagResult>;
106
+ /** Replace a placeholder atomically (temp file + rename). */
107
+ export declare function replacePlaceholderText(file: string, text: string): Promise<void>;
108
+ /** Open a local file or GET a URL as a Node stream. */
109
+ export declare function openLibrarySource(source: LibrarySource): Promise<OpenedSource>;
110
+ /**
111
+ * Stream a PUT with an explicit Content-Length: S3 presigned PUTs refuse
112
+ * chunked transfer encoding, which fetch would use for a stream body.
113
+ */
114
+ export declare function streamPut(url: string, headers: Record<string, string>, body: Readable, size: number): Promise<PutResponse>;
115
+ export declare const defaultLibraryIo: {
116
+ openSource: typeof openLibrarySource;
117
+ put: typeof streamPut;
118
+ writePlaceholder: (file: string, text: string) => Promise<void>;
119
+ };
120
+ //# sourceMappingURL=library-add.d.ts.map