@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.
- package/CHANGELOG.md +74 -0
- package/dist/command-catalog.generated.d.ts +278 -6
- package/dist/command-catalog.generated.js +341 -6
- package/dist/command-registration-plan.d.ts +6 -0
- package/dist/command-registration-plan.js +1 -0
- package/dist/commands/agent-enroll.d.ts +1 -0
- package/dist/commands/agent-enroll.js +7 -0
- package/dist/commands/agents.d.ts +25 -3
- package/dist/commands/agents.js +57 -4
- package/dist/commands/api-keys.js +66 -8
- package/dist/commands/billing-sponsor.d.ts +4 -0
- package/dist/commands/billing-sponsor.js +48 -5
- package/dist/commands/billing-usage.d.ts +59 -0
- package/dist/commands/billing-usage.js +162 -0
- package/dist/commands/billing.d.ts +9 -0
- package/dist/commands/billing.js +29 -2
- package/dist/commands/core-policy.js +76 -13
- package/dist/commands/library-add.d.ts +120 -0
- package/dist/commands/library-add.js +434 -0
- package/dist/commands/library-caption.d.ts +112 -0
- package/dist/commands/library-caption.js +393 -0
- package/dist/commands/library-link.d.ts +32 -0
- package/dist/commands/library-link.js +95 -0
- package/dist/commands/library.d.ts +72 -0
- package/dist/commands/library.js +527 -0
- package/dist/commands/meetings.d.ts +8 -0
- package/dist/commands/meetings.js +25 -2
- package/dist/commands/storage-offload.d.ts +16 -2
- package/dist/commands/storage-offload.js +55 -4
- package/dist/commands/storage.d.ts +8 -1
- package/dist/commands/storage.js +6 -2
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +2410 -2063
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/lib/billing/plan-lock.d.ts +15 -1
- package/dist/lib/billing/plan-lock.js +45 -5
- package/dist/lib/billing/usage-card.d.ts +82 -0
- package/dist/lib/billing/usage-card.js +198 -0
- package/dist/lib/billing/usage-cost.d.ts +214 -0
- package/dist/lib/billing/usage-cost.js +440 -0
- package/dist/lib/billing/usage-only.d.ts +87 -0
- package/dist/lib/billing/usage-only.js +262 -0
- package/dist/lib/billing/usage-rate-card.d.ts +51 -0
- package/dist/lib/billing/usage-rate-card.js +62 -0
- package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
- package/dist/lib/flag-registry-worker-source.generated.js +1 -1
- package/dist/lib/plan-limit-nag.d.ts +4 -6
- package/dist/lib/plan-limit-nag.js +19 -1
- package/dist/lib/provider-launcher.d.ts +22 -0
- package/dist/lib/provider-launcher.js +65 -0
- package/dist/lib/search-index/index.d.ts +2 -0
- package/dist/lib/search-index/index.js +34 -0
- package/dist/lib/search-index/max-doc-bytes.d.ts +2 -1
- package/dist/lib/search-index/max-doc-bytes.js +2 -1
- package/dist/main.d.ts +9 -0
- package/dist/main.js +20 -3
- package/dist/utils/billing-gate.d.ts +19 -4
- package/dist/utils/billing-gate.js +25 -4
- package/dist/utils/plan-gate-error.d.ts +10 -0
- package/dist/utils/plan-gate-error.js +54 -0
- package/dist/utils/vault-api.js +4 -1
- 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 === '
|
|
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
|
|
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 (
|
|
452
|
-
|
|
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(
|
|
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
|
|
495
|
-
const
|
|
496
|
-
const
|
|
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
|