@indigoai-us/hq-cli 5.345.80 → 5.345.82
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 +17 -0
- package/dist/command-catalog.generated.d.ts +146 -0
- package/dist/command-catalog.generated.js +178 -0
- package/dist/command-help.js +2 -0
- package/dist/command-registration-plan.d.ts +6 -0
- package/dist/command-registration-plan.js +1 -0
- package/dist/commands/doctor.d.ts +5 -0
- package/dist/commands/doctor.js +2 -0
- package/dist/commands/storage-core.d.ts +98 -0
- package/dist/commands/storage-core.js +223 -0
- package/dist/commands/storage-gate.d.ts +35 -0
- package/dist/commands/storage-gate.js +85 -0
- package/dist/commands/storage-offload.d.ts +342 -0
- package/dist/commands/storage-offload.js +943 -0
- package/dist/commands/storage.d.ts +249 -0
- package/dist/commands/storage.js +1171 -0
- package/dist/fast-scaffold.js +5 -0
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +5140 -3412
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/lib/cli-flag-keys.d.ts +6 -0
- package/dist/lib/cli-flag-keys.js +6 -0
- package/dist/lib/doctor/fix/remediation.js +10 -0
- package/dist/lib/doctor/json-output.d.ts +16 -0
- package/dist/lib/doctor/json-output.js +10 -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/storage-feature-flag.d.ts +56 -0
- package/dist/lib/storage-feature-flag.js +104 -0
- package/dist/main.js +12 -1
- package/dist/utils/resident-package-preload.js +3 -1
- package/package.json +2 -2
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `hq storage` — how much space HQ backups use, and pruning of old ones.
|
|
3
|
+
*
|
|
4
|
+
* Two stores:
|
|
5
|
+
* - Local autosave history (the HQ root git repo). Measured and pruned by the
|
|
6
|
+
* hq-core script `core/scripts/hq-storage.sh`; older HQ installs lack it and
|
|
7
|
+
* report `{ available: false, reason: "update HQ" }`.
|
|
8
|
+
* - Cloud file history: S3 object versioning on company vault buckets. Read
|
|
9
|
+
* with ListObjectVersions and pruned with batched DeleteObjects, using
|
|
10
|
+
* membership-scoped credentials from `/sts/vend`.
|
|
11
|
+
*
|
|
12
|
+
* Tenant isolation: cloud work only ever targets companies in the caller's own
|
|
13
|
+
* active memberships (`/membership/me`). `--company` narrows that set; a slug
|
|
14
|
+
* outside it is rejected before any credentials are vended. Deleting cloud
|
|
15
|
+
* history is for owners and admins only: prune checks the membership role
|
|
16
|
+
* before vending, and `status` marks each company with `can_delete`.
|
|
17
|
+
*
|
|
18
|
+
* `offload` and `fetch` (big files to the personal vault) live in
|
|
19
|
+
* `storage-offload.ts`; this file registers them and adds the offload section
|
|
20
|
+
* to `status`.
|
|
21
|
+
*/
|
|
22
|
+
import { Command } from "commander";
|
|
23
|
+
import { type ReclaimReport } from "./storage-core.js";
|
|
24
|
+
import { ListObjectVersionsCommand, DeleteObjectsCommand, type ListObjectVersionsCommandOutput, type DeleteObjectsCommandOutput } from "@aws-sdk/client-s3";
|
|
25
|
+
import { type OffloadResult, type OffloadStatus } from "./storage-offload.js";
|
|
26
|
+
export type TrancheId = "7d" | "30d" | "90d" | "365d" | "older";
|
|
27
|
+
export declare const TRANCHES: readonly {
|
|
28
|
+
id: TrancheId;
|
|
29
|
+
label: string;
|
|
30
|
+
maxAgeDays: number;
|
|
31
|
+
}[];
|
|
32
|
+
export declare function trancheFor(lastModified: Date, now: Date): TrancheId;
|
|
33
|
+
export type LocalReport = Record<string, unknown> & {
|
|
34
|
+
available?: boolean;
|
|
35
|
+
reason?: string;
|
|
36
|
+
};
|
|
37
|
+
export interface CloudTranche {
|
|
38
|
+
id: TrancheId;
|
|
39
|
+
label: string;
|
|
40
|
+
count: number;
|
|
41
|
+
bytes: number;
|
|
42
|
+
}
|
|
43
|
+
export interface CloudCompanyStatus {
|
|
44
|
+
company: string;
|
|
45
|
+
available: boolean;
|
|
46
|
+
/** Whether the caller may delete this company's old versions (owner/admin). */
|
|
47
|
+
can_delete: boolean;
|
|
48
|
+
current_bytes: number;
|
|
49
|
+
noncurrent_bytes: number;
|
|
50
|
+
noncurrent_count: number;
|
|
51
|
+
delete_markers: number;
|
|
52
|
+
tranches: CloudTranche[];
|
|
53
|
+
error?: string;
|
|
54
|
+
/** When these figures were listed (ISO). Older than now when served from the cache. */
|
|
55
|
+
listed_at?: string;
|
|
56
|
+
}
|
|
57
|
+
export interface StorageStatus {
|
|
58
|
+
local: LocalReport;
|
|
59
|
+
cloud: CloudCompanyStatus[];
|
|
60
|
+
/** Set when cloud history could not be read at all, e.g. not signed in. */
|
|
61
|
+
cloud_error?: string;
|
|
62
|
+
offload?: OffloadStatus;
|
|
63
|
+
generated_at: string;
|
|
64
|
+
}
|
|
65
|
+
export interface CloudPruneResult {
|
|
66
|
+
company: string;
|
|
67
|
+
deleted_count: number;
|
|
68
|
+
deleted_bytes: number;
|
|
69
|
+
errors: string[];
|
|
70
|
+
/** Set when the company was not touched, e.g. the caller is not an owner/admin. */
|
|
71
|
+
skipped_reason?: string;
|
|
72
|
+
/** True when S3 refused the delete for this company (role not granted). */
|
|
73
|
+
access_denied?: boolean;
|
|
74
|
+
}
|
|
75
|
+
export interface StoragePruneResult {
|
|
76
|
+
local: LocalReport | null;
|
|
77
|
+
cloud: CloudPruneResult[];
|
|
78
|
+
dry_run: boolean;
|
|
79
|
+
/** Expired backups dropped before a real local prune; absent otherwise. */
|
|
80
|
+
reclaim?: ReclaimReport;
|
|
81
|
+
}
|
|
82
|
+
/** Subset of S3Client this command uses. Injectable for tests. */
|
|
83
|
+
export interface StorageS3Client {
|
|
84
|
+
send(cmd: ListObjectVersionsCommand): Promise<ListObjectVersionsCommandOutput>;
|
|
85
|
+
send(cmd: DeleteObjectsCommand): Promise<DeleteObjectsCommandOutput>;
|
|
86
|
+
}
|
|
87
|
+
export interface CompanyRef {
|
|
88
|
+
uid: string;
|
|
89
|
+
slug: string;
|
|
90
|
+
/** Membership role from `/membership/me`: owner, admin, member or guest. */
|
|
91
|
+
role?: string;
|
|
92
|
+
}
|
|
93
|
+
/** Cloud history delete is owners and admins only. A missing role is not enough. */
|
|
94
|
+
export declare function canDeleteCloudHistory(company: CompanyRef): boolean;
|
|
95
|
+
export declare const DELETE_ROLE_REASON = "Only company owners and admins can delete cloud file history.";
|
|
96
|
+
export interface CompanyStore {
|
|
97
|
+
bucket: string;
|
|
98
|
+
s3: StorageS3Client;
|
|
99
|
+
}
|
|
100
|
+
export type ScriptRunner = (scriptPath: string, args: string[]) => Promise<{
|
|
101
|
+
code: number;
|
|
102
|
+
stdout: string;
|
|
103
|
+
stderr: string;
|
|
104
|
+
}>;
|
|
105
|
+
export interface StorageDeps {
|
|
106
|
+
hqRoot: string;
|
|
107
|
+
now: () => Date;
|
|
108
|
+
/** The caller's active company memberships. */
|
|
109
|
+
listCompanies: () => Promise<CompanyRef[]>;
|
|
110
|
+
/** Vend credentials and resolve the bucket for one member company. */
|
|
111
|
+
openCompany: (company: CompanyRef) => Promise<CompanyStore>;
|
|
112
|
+
runScript: ScriptRunner;
|
|
113
|
+
confirm: (message: string) => Promise<boolean>;
|
|
114
|
+
log: (line: string) => void;
|
|
115
|
+
/** Offload section for `status`. Absent in deps that only prune. */
|
|
116
|
+
getOffload?: () => Promise<OffloadStatus>;
|
|
117
|
+
/** Cache of cloud status per company (see cloudStatusCache). Absent = always list. */
|
|
118
|
+
cloudCache?: CloudStatusCache;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Listing every version of a large vault takes minutes (about 2 for 2.4M
|
|
122
|
+
* versions, most of it parsing S3's XML), so `status` keeps each company's
|
|
123
|
+
* cloud figures for CLOUD_CACHE_TTL_MS. Only complete, successful listings are
|
|
124
|
+
* kept; a real cloud prune drops the company's entry; dry runs and real prunes
|
|
125
|
+
* always list live.
|
|
126
|
+
*/
|
|
127
|
+
export interface CloudStatusCache {
|
|
128
|
+
read(company: CompanyRef): CloudCompanyStatus | undefined;
|
|
129
|
+
write(company: CompanyRef, status: CloudCompanyStatus): void;
|
|
130
|
+
drop(company: CompanyRef): void;
|
|
131
|
+
}
|
|
132
|
+
export declare const CLOUD_CACHE_TTL_MS: number;
|
|
133
|
+
/** File cache under <hq>/workspace/.storage-cache/cloud-<company uid>.json. */
|
|
134
|
+
export declare function cloudStatusCache(hqRoot: string, now: () => Date, ttlMs?: number): CloudStatusCache;
|
|
135
|
+
export declare function localScriptPath(hqRoot: string): string;
|
|
136
|
+
/**
|
|
137
|
+
* Core `drop-backups --expired` right before a real local prune; undefined
|
|
138
|
+
* when HQ lacks it. Status, dry runs and fetch never call it.
|
|
139
|
+
*/
|
|
140
|
+
export declare function dropExpiredLocal(deps: StorageDeps): Promise<ReclaimReport | undefined>;
|
|
141
|
+
export declare function getLocalReport(deps: StorageDeps): Promise<LocalReport>;
|
|
142
|
+
export declare function selectCompanies(memberships: CompanyRef[], requested: string | undefined): CompanyRef[];
|
|
143
|
+
/**
|
|
144
|
+
* Active memberships from a `/membership/me` body, one per company. A company
|
|
145
|
+
* listed more than once keeps its highest-privilege role.
|
|
146
|
+
*/
|
|
147
|
+
export declare function companiesFromMemberships(body: unknown): CompanyRef[];
|
|
148
|
+
export interface VersionEntry {
|
|
149
|
+
key: string;
|
|
150
|
+
versionId: string;
|
|
151
|
+
isLatest: boolean;
|
|
152
|
+
lastModified: Date;
|
|
153
|
+
size: number;
|
|
154
|
+
isDeleteMarker: boolean;
|
|
155
|
+
/**
|
|
156
|
+
* When this version stopped being current: the LastModified of the next
|
|
157
|
+
* newer version or delete marker of the same key. Unset for current entries.
|
|
158
|
+
*/
|
|
159
|
+
noncurrentSince?: Date;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Fill `noncurrentSince` on every noncurrent entry. A noncurrent version's
|
|
163
|
+
* age is how long it has been a backup, not when it was written: a file
|
|
164
|
+
* written two years ago and overwritten yesterday became an undo copy
|
|
165
|
+
* yesterday. Within a key the current entry comes first, then newest to
|
|
166
|
+
* oldest. A noncurrent entry with no newer entry in the listing falls back
|
|
167
|
+
* to its own LastModified.
|
|
168
|
+
*/
|
|
169
|
+
export declare function annotateNoncurrentSince(entries: VersionEntry[]): VersionEntry[];
|
|
170
|
+
/** Folder levels split into parallel listings (`a/`, then `a/b/`). */
|
|
171
|
+
export declare const LIST_SPLIT_DEPTH = 2;
|
|
172
|
+
/** Listings in flight at once per bucket. */
|
|
173
|
+
export declare const LIST_CONCURRENCY = 32;
|
|
174
|
+
/**
|
|
175
|
+
* A printable-ASCII key strictly between `lo` and `hi` in S3's (UTF-8 byte)
|
|
176
|
+
* order, or undefined when none is found. Used to split one long listing into
|
|
177
|
+
* two that run at the same time.
|
|
178
|
+
*/
|
|
179
|
+
export declare function keyBetween(lo: string, hi: string): string | undefined;
|
|
180
|
+
/** Plain message when S3 refuses a version delete for one company. */
|
|
181
|
+
export declare function deleteDeniedMessage(slug: string): string;
|
|
182
|
+
export declare function describeCloudError(err: unknown, action: "list" | "delete", slug?: string): string;
|
|
183
|
+
export declare function getCloudStatus(company: CompanyRef, deps: StorageDeps): Promise<CloudCompanyStatus>;
|
|
184
|
+
/**
|
|
185
|
+
* Versions eligible for prune: noncurrent content versions and noncurrent
|
|
186
|
+
* delete markers that stopped being current before the cutoff (see
|
|
187
|
+
* annotateNoncurrentSince). The current version of a key (content or delete
|
|
188
|
+
* marker) is never selected.
|
|
189
|
+
*/
|
|
190
|
+
export declare function selectPrunable(versions: VersionEntry[], cutoff: Date): VersionEntry[];
|
|
191
|
+
export declare function pruneCloud(company: CompanyRef, cutoff: Date, dryRun: boolean, deps: StorageDeps): Promise<CloudPruneResult>;
|
|
192
|
+
export declare function runStorageStatus(opts: {
|
|
193
|
+
company?: string;
|
|
194
|
+
noCache?: boolean;
|
|
195
|
+
}, deps: StorageDeps): Promise<StorageStatus>;
|
|
196
|
+
export declare function parseCutoff(value: string, flag: string): Date;
|
|
197
|
+
export declare function runStoragePrune(opts: {
|
|
198
|
+
localBefore?: string;
|
|
199
|
+
cloudBefore?: string;
|
|
200
|
+
company?: string;
|
|
201
|
+
dryRun: boolean;
|
|
202
|
+
yes: boolean;
|
|
203
|
+
}, deps: StorageDeps): Promise<StoragePruneResult | undefined>;
|
|
204
|
+
export declare function formatBytes(bytes: number): string;
|
|
205
|
+
export declare function formatStorageStatus(status: StorageStatus): string;
|
|
206
|
+
export declare function formatOffloadStatus(o: OffloadStatus): string[];
|
|
207
|
+
export declare function formatOffload(r: OffloadResult): string;
|
|
208
|
+
/** One line on when backed-up space comes back, or nothing. */
|
|
209
|
+
export declare function reclaimLine(r: Record<string, unknown>): string | undefined;
|
|
210
|
+
/** Readable name for a ref that keeps old history: "branch wip", "tag v1". */
|
|
211
|
+
export declare function holderName(ref: string): string;
|
|
212
|
+
/** Plain refusal text when branches or tags keep the history prune would remove. */
|
|
213
|
+
export declare function formatHoldingRefusal(local: LocalReport): string[];
|
|
214
|
+
/** Exit code for a prune result: 4 for nothing to free, 5 for a precondition, else 0/1. */
|
|
215
|
+
export declare function pruneExitCode(result: StoragePruneResult): number;
|
|
216
|
+
/** Plain lines for a local prune that was not refused (dry or real). */
|
|
217
|
+
export declare function formatLocalPrune(local: LocalReport, dryRun: boolean): string[];
|
|
218
|
+
export declare function formatStoragePrune(result: StoragePruneResult): string;
|
|
219
|
+
export declare const defaultRunScript: ScriptRunner;
|
|
220
|
+
export declare const NEEDS_YES = "No terminal to confirm in. Re-run with --yes to go ahead, or --dry-run to preview.";
|
|
221
|
+
/**
|
|
222
|
+
* Ask y/N on a terminal. Without a TTY the question could never be answered,
|
|
223
|
+
* so it throws and the command exits non-zero instead of hanging or exiting 0.
|
|
224
|
+
*/
|
|
225
|
+
export declare function confirmOnTty(message: string, input?: NodeJS.ReadableStream & {
|
|
226
|
+
isTTY?: boolean;
|
|
227
|
+
}, output?: NodeJS.WritableStream): Promise<boolean>;
|
|
228
|
+
/** STS credentials from one vend, with the time they stop working. */
|
|
229
|
+
export interface VendedCredentials {
|
|
230
|
+
accessKeyId: string;
|
|
231
|
+
secretAccessKey: string;
|
|
232
|
+
sessionToken: string;
|
|
233
|
+
expiresAt?: string;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* AWS credential provider that vends fresh credentials on first use and again
|
|
237
|
+
* whenever the previous ones are within `marginMs` of expiring. Each vend calls
|
|
238
|
+
* `vend` anew, which signs in again (the CLI refreshes an expiring Cognito
|
|
239
|
+
* session there), so a cloud listing or delete that starts after a long local
|
|
240
|
+
* scan, or that itself outlasts one STS session, never sends an expired token.
|
|
241
|
+
*/
|
|
242
|
+
export declare function refreshingVendedCredentials(vend: () => Promise<VendedCredentials>, now?: () => number, marginMs?: number): () => Promise<{
|
|
243
|
+
accessKeyId: string;
|
|
244
|
+
secretAccessKey: string;
|
|
245
|
+
sessionToken: string;
|
|
246
|
+
expiration?: Date;
|
|
247
|
+
}>;
|
|
248
|
+
export declare function registerStorageCommand(program: Command): Command;
|
|
249
|
+
//# sourceMappingURL=storage.d.ts.map
|