@indigoai-us/hq-cli 5.345.148 → 5.345.149
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 +35 -0
- package/README.md +2 -0
- package/dist/command-catalog.generated.d.ts +44 -8
- package/dist/command-catalog.generated.js +53 -8
- package/dist/command-help.js +2 -1
- package/dist/commands/access.d.ts +45 -2
- package/dist/commands/access.js +537 -223
- package/dist/commands/billing-usage.d.ts +33 -2
- package/dist/commands/billing-usage.js +75 -4
- package/dist/commands/bot.js +2 -2
- package/dist/commands/channels.d.ts +27 -0
- package/dist/commands/channels.js +108 -4
- package/dist/commands/core-refresh-vault-access-native.d.ts +16 -0
- package/dist/commands/core-refresh-vault-access-native.js +142 -9
- package/dist/commands/core.js +4 -1
- package/dist/commands/dm.js +9 -1
- package/dist/commands/install-global.d.ts +1 -8
- package/dist/commands/install-global.js +11 -27
- package/dist/commands/library-add.d.ts +31 -1
- package/dist/commands/library-add.js +69 -2
- package/dist/commands/library.js +37 -3
- package/dist/commands/reindex.js +19 -0
- package/dist/hook-engine-path.js +10 -3
- package/dist/index.d.ts +10 -0
- package/dist/index.js +70 -5
- package/dist/lazy-commands.js +2 -0
- package/dist/lib/billing/usage-cost.d.ts +31 -1
- package/dist/lib/billing/usage-cost.js +46 -0
- package/dist/lib/db/local.d.ts +2 -2
- package/dist/lib/db/local.js +2 -2
- package/dist/lib/db/sql.js +2 -2
- package/dist/lib/db/sqlite-binding.d.ts +22 -0
- package/dist/lib/db/sqlite-binding.js +168 -0
- package/dist/lib/doctor/checks/anywhere.d.ts +12 -0
- package/dist/lib/doctor/checks/anywhere.js +63 -12
- package/dist/lib/doctor/checks/cli-path.d.ts +2 -1
- package/dist/lib/doctor/checks/cli-path.js +2 -1
- package/dist/lib/doctor/checks/hook-engine.js +2 -1
- package/dist/lib/hook-engine/reconcile.d.ts +9 -0
- package/dist/lib/hook-engine/reconcile.js +134 -0
- package/dist/lib/hook-engine/registration.d.ts +17 -0
- package/dist/lib/hook-engine/registration.js +50 -0
- package/dist/lib/install/claude-targets.d.ts +2 -1
- package/dist/lib/install/claude-targets.js +8 -3
- package/dist/lib/install/codex-targets.d.ts +3 -0
- package/dist/lib/install/codex-targets.js +15 -4
- package/dist/lib/install/hook-target.d.ts +9 -0
- package/dist/lib/install/hook-target.js +39 -0
- package/dist/lib/windows/console-encoding.d.ts +24 -0
- package/dist/lib/windows/console-encoding.js +83 -0
- package/dist/lib/windows/install-followup.d.ts +35 -0
- package/dist/lib/windows/install-followup.js +142 -0
- package/dist/lib/windows/user-path.d.ts +80 -3
- package/dist/lib/windows/user-path.js +187 -26
- package/dist/main.js +2 -1
- package/dist/unknown-command.d.ts +18 -0
- package/dist/unknown-command.js +88 -0
- package/dist/utils/cognito-session.d.ts +8 -0
- package/dist/utils/cognito-session.js +14 -2
- package/dist/utils/hook-trust.d.ts +9 -0
- package/dist/utils/hook-trust.js +30 -2
- package/dist/utils/qmd-native-binding-error.js +3 -1
- package/dist/utils/resolve-executable.d.ts +2 -0
- package/dist/utils/resolve-executable.js +6 -2
- package/package.json +8 -8
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { hookTargetFor } from '../lib/install/hook-target.js';
|
|
1
2
|
/**
|
|
2
3
|
* `hq install --global --runtime claude` / `hq uninstall --global --runtime claude`
|
|
3
4
|
* (hq-anywhere-runtime US-005).
|
|
@@ -25,34 +26,11 @@ import { readRegistry, registryDir, registryFile, writeRegistry } from '../lib/r
|
|
|
25
26
|
import { readCompaniesManifestMap } from '../lib/work-context/repo-remote.js';
|
|
26
27
|
import { resolveDefaultHqRoot } from '../utils/hq-root-resolver.js';
|
|
27
28
|
import { trustCodexUserHooks } from '../utils/hook-trust.js';
|
|
28
|
-
import { DAEMON_LAUNCHD_LABEL, DAEMON_SYSTEMD_UNIT, DAEMON_WINDOWS_TASK, SYSTEM_UNIT_DIR } from '../lib/daemon/install.js';
|
|
29
|
-
import { readDaemonConfig } from '../lib/daemon/config.js';
|
|
30
|
-
import { daemonPaths } from '../lib/daemon/paths.js';
|
|
31
29
|
import { assertProviderWrappersInstallable, installProviderWrappers, providerWrapperInstallPaths, removeProviderWrappers } from '../lib/install/provider-wrappers.js';
|
|
32
|
-
import { checkShimOnPath, shimPathWarning } from '../lib/windows/user-path.js';
|
|
30
|
+
import { checkShimOnPath, ensureShimOnUserPath, shimPathWarning } from '../lib/windows/user-path.js';
|
|
33
31
|
import { addAgentsMdBlock, addCodexHooks, classifyLegacySkills, codexAdapterPath, codexCharterSource, codexHookTrustKeys, codexTargets, hasAllCodexHooks, HQ_OWNED_MARKER, LEGACY_BACKUP_NAME, LEGACY_SKILLS_NAME, removeAgentsMdBlock, removeCodexHooks, } from '../lib/install/codex-targets.js';
|
|
34
32
|
import { addClaudeMdBlock, addHqHooks, charterPath, claudeTargets, hasAllHqHooks, HQ_ANYWHERE_PACK, HQ_MCP_SERVER_NAME, hqSkillsSource, lineDiff, masterHookPath, shimPath, MCP_SUBPROCESS_PATH, removeClaudeMdBlock, removeHqHooks, SKILL_LINK_PREFIX, } from '../lib/install/claude-targets.js';
|
|
35
|
-
|
|
36
|
-
export function isDaemonInstalled(home, platform = process.platform, queryTask = (task) => spawnSync('schtasks.exe', ['/Query', '/TN', task], { encoding: 'utf8', windowsHide: true }).status === 0) {
|
|
37
|
-
if (platform === 'win32')
|
|
38
|
-
return queryTask(DAEMON_WINDOWS_TASK);
|
|
39
|
-
const candidates = platform === 'darwin'
|
|
40
|
-
? [path.join(home, 'Library', 'LaunchAgents', `${DAEMON_LAUNCHD_LABEL}.plist`)]
|
|
41
|
-
: platform === 'linux'
|
|
42
|
-
? [path.join(home, '.config', 'systemd', 'user', DAEMON_SYSTEMD_UNIT), path.join(SYSTEM_UNIT_DIR, DAEMON_SYSTEMD_UNIT)]
|
|
43
|
-
: [];
|
|
44
|
-
return candidates.some((p) => fs.existsSync(p));
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* US-010: with hqd installed (and the checkout shipping the shim), hooks go to
|
|
48
|
-
* the hqd shim; otherwise to master-hook.sh / the Codex adapter.
|
|
49
|
-
*/
|
|
50
|
-
export function hookTargetFor(hqRoot, home, daemonInstalled, env = process.env) {
|
|
51
|
-
const installed = daemonInstalled ?? isDaemonInstalled(home);
|
|
52
|
-
const configuredRoot = readDaemonConfig(daemonPaths(home, env).configPath).hqRoot;
|
|
53
|
-
const daemonMatchesRoot = configuredRoot !== undefined && realpathOrSelf(configuredRoot) === realpathOrSelf(hqRoot);
|
|
54
|
-
return installed && daemonMatchesRoot && fs.existsSync(shimPath(hqRoot)) ? 'shim' : 'master';
|
|
55
|
-
}
|
|
33
|
+
export { hookTargetFor, isDaemonInstalled } from '../lib/install/hook-target.js';
|
|
56
34
|
const HQ_PACKAGES_MARKETPLACE_REPOSITORY = 'indigoai-us/hq-packages';
|
|
57
35
|
/** Resolve local overrides first, then the published HQ marketplace. */
|
|
58
36
|
export function resolveClaudePluginSource(explicitDir, env = process.env, repository = HQ_PACKAGES_MARKETPLACE_REPOSITORY) {
|
|
@@ -615,7 +593,7 @@ export function installGlobalClaude(opts) {
|
|
|
615
593
|
if (pluginSource)
|
|
616
594
|
log(` plugin: ${pluginSource.pluginId} installed from ${pluginSource.sourceDir}; direct hooks and MCP entries removed`);
|
|
617
595
|
else {
|
|
618
|
-
log(` hooks: ${hookTarget === 'shim' ? 'hqd-hook-shim.sh' : 'master-hook.sh'} registered in ${t.settingsJson}`);
|
|
596
|
+
log(` hooks: ${typeof hookTarget === 'object' ? hookTarget.engine : hookTarget === 'shim' ? 'hqd-hook-shim.sh' : 'master-hook.sh'} registered in ${t.settingsJson}`);
|
|
619
597
|
log(` MCP: "${HQ_MCP_SERVER_NAME}" registered in ${t.claudeJson}`);
|
|
620
598
|
}
|
|
621
599
|
log(` registry: ${seeded.length} manifest entr${seeded.length === 1 ? 'y' : 'ies'} seeded`);
|
|
@@ -994,7 +972,7 @@ export function installGlobalCodex(opts) {
|
|
|
994
972
|
log(` skills linked: ${skills.link.length} new, ${skills.already.length} already present, ${skills.skipped.length} skipped`);
|
|
995
973
|
reportSkillCopies(linkKinds, log);
|
|
996
974
|
reportSkippedSkills(skills, log, false);
|
|
997
|
-
log(` hooks: ${hookTarget === 'shim' ? shimPath(hqRoot) : codexAdapterPath(hqRoot)} registered in ${t.hooksJson}`);
|
|
975
|
+
log(` hooks: ${typeof hookTarget === 'object' ? hookTarget.engine : hookTarget === 'shim' ? shimPath(hqRoot) : codexAdapterPath(hqRoot)} registered in ${t.hooksJson}`);
|
|
998
976
|
log(` MCP: "${HQ_MCP_SERVER_NAME}" registered in ${t.configToml}`);
|
|
999
977
|
return touched;
|
|
1000
978
|
}
|
|
@@ -1213,6 +1191,12 @@ export async function runInstallGlobal(opts) {
|
|
|
1213
1191
|
if (runtime === 'codex' && !opts.dryRun)
|
|
1214
1192
|
await trustInstalledCodexHooks(hqRoot, opts);
|
|
1215
1193
|
if (!opts.dryRun) {
|
|
1194
|
+
try {
|
|
1195
|
+
ensureShimOnUserPath({ env, entry: process.argv[1] });
|
|
1196
|
+
}
|
|
1197
|
+
catch {
|
|
1198
|
+
// The warning below is the fallback when the user Path cannot be updated.
|
|
1199
|
+
}
|
|
1216
1200
|
const shimPath = (opts.shimPathCheck ?? (() => checkShimOnPath({ env })))();
|
|
1217
1201
|
if (shimPath)
|
|
1218
1202
|
for (const line of shimPathWarning(shimPath))
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Readable } from "node:stream";
|
|
2
|
-
import { type CloudAssetMediaKind } from "@indigoai-us/hq-cloud";
|
|
2
|
+
import { type CloudAssetMediaKind, type PullScope, type PullScopeClient } from "@indigoai-us/hq-cloud";
|
|
3
3
|
import type { LibraryCaptionNote, LibraryCaptionRequest } from "./library-caption.js";
|
|
4
4
|
export interface LibraryCompany {
|
|
5
5
|
uid: string;
|
|
@@ -37,6 +37,13 @@ export interface LibraryAddDeps {
|
|
|
37
37
|
* a caption failure never fails the upload. Absent = no captioning.
|
|
38
38
|
*/
|
|
39
39
|
caption?: (request: LibraryCaptionRequest) => Promise<LibraryCaptionNote>;
|
|
40
|
+
/**
|
|
41
|
+
* The membership's sync scope for the company. Throws when it cannot be
|
|
42
|
+
* read. Absent = the scope is not checked and nothing is pinned.
|
|
43
|
+
*/
|
|
44
|
+
syncScope?: (company: LibraryCompany) => Promise<Pick<PullScope, "syncMode" | "prefixSet">>;
|
|
45
|
+
/** `hq files get`'s pin writer (`.hq/pins.json`). */
|
|
46
|
+
addPin?: (hqRoot: string, companySlug: string, prefix: string) => void;
|
|
40
47
|
}
|
|
41
48
|
export interface LibraryAddOptions {
|
|
42
49
|
company?: string;
|
|
@@ -60,9 +67,19 @@ export interface LibraryAddItem {
|
|
|
60
67
|
/** AI caption outcome, set when captioning ran for this item. */
|
|
61
68
|
caption?: LibraryCaptionNote;
|
|
62
69
|
}
|
|
70
|
+
export interface LibrarySyncScopeNote {
|
|
71
|
+
status: "in-scope" | "pinned" | "not-pinned";
|
|
72
|
+
/** Company-relative pins written (`pinned`) or still needed (`not-pinned`). */
|
|
73
|
+
pins: string[];
|
|
74
|
+
/** Set when not pinned and `hq files get` can pin by hand (the library folder). */
|
|
75
|
+
command?: string;
|
|
76
|
+
reason?: string;
|
|
77
|
+
}
|
|
63
78
|
export interface LibraryAddResult {
|
|
64
79
|
company: string;
|
|
65
80
|
items: LibraryAddItem[];
|
|
81
|
+
/** Set when at least one placeholder was written and the scope was checked. */
|
|
82
|
+
syncScope?: LibrarySyncScopeNote;
|
|
66
83
|
}
|
|
67
84
|
export declare function contentTypeFor(name: string, served?: string): string;
|
|
68
85
|
export declare function mediaKindFor(contentType: string): CloudAssetMediaKind;
|
|
@@ -89,6 +106,19 @@ export declare function offloadTimestamp(now: Date): string;
|
|
|
89
106
|
* only that source.
|
|
90
107
|
*/
|
|
91
108
|
export declare function runLibraryAdd(inputs: readonly string[], options: LibraryAddOptions, deps: LibraryAddDeps): Promise<LibraryAddResult>;
|
|
109
|
+
/**
|
|
110
|
+
* A `shared` or `custom` sync pushes only paths inside the membership's
|
|
111
|
+
* prefix set (explicit grants plus pins), so a placeholder written outside it
|
|
112
|
+
* never reaches the vault. Pin it with `hq files get`'s pin writer: the
|
|
113
|
+
* default library folder as a whole, and for any other `--to` folder only the
|
|
114
|
+
* written placeholders, so the rest of that folder stays out of sync.
|
|
115
|
+
*/
|
|
116
|
+
export declare function ensureLibraryInSyncScope(company: LibraryCompany, dirRel: string, placeholderPaths: readonly string[], deps: Pick<LibraryAddDeps, "hqRoot" | "syncScope" | "addPin">): Promise<LibrarySyncScopeNote | undefined>;
|
|
117
|
+
/**
|
|
118
|
+
* {@link resolvePullScope} answers `all` when any call fails. Report the
|
|
119
|
+
* failure instead, so a failed lookup is not mistaken for full sync.
|
|
120
|
+
*/
|
|
121
|
+
export declare function resolveLibrarySyncScope(client: Required<PullScopeClient>, company: LibraryCompany, hqRoot: string): Promise<PullScope>;
|
|
92
122
|
export interface LibraryTagResult {
|
|
93
123
|
placeholderPath: string;
|
|
94
124
|
tags: string[];
|
|
@@ -28,7 +28,7 @@ import http from "node:http";
|
|
|
28
28
|
import https from "node:https";
|
|
29
29
|
import path from "node:path";
|
|
30
30
|
import { Readable } from "node:stream";
|
|
31
|
-
import { formatCloudAssetBlock, formatCloudPlaceholder, parseCloudPlaceholder, } from "@indigoai-us/hq-cloud";
|
|
31
|
+
import { formatCloudAssetBlock, formatCloudPlaceholder, isCoveredByAny, parseCloudPlaceholder, resolvePullScope, } from "@indigoai-us/hq-cloud";
|
|
32
32
|
import { PLACEHOLDER_SUFFIX, companyOffloadKey, containedPath, durablePlaceholderFs, renderPointer, resolvePlaceholder, } from "./storage-offload.js";
|
|
33
33
|
import { formatPlanGateError, formatUpgradeRefusal, planGateErrorFromResponse, } from "../utils/plan-gate-error.js";
|
|
34
34
|
/** Metadata key hq-pro binds to `checksumSha256` (sync's content hash). */
|
|
@@ -321,7 +321,74 @@ export async function runLibraryAdd(inputs, options, deps) {
|
|
|
321
321
|
.catch((err) => ({ status: "failed", note: err.message }));
|
|
322
322
|
}
|
|
323
323
|
}
|
|
324
|
-
|
|
324
|
+
const written = items.flatMap((item) => (item.ok && item.placeholderPath ? [item.placeholderPath] : []));
|
|
325
|
+
if (written.length === 0)
|
|
326
|
+
return { company: slug, items };
|
|
327
|
+
const syncScope = await ensureLibraryInSyncScope(company, dirRel, written, deps);
|
|
328
|
+
return { company: slug, items, ...(syncScope ? { syncScope } : {}) };
|
|
329
|
+
}
|
|
330
|
+
const LIBRARY_FOLDER = "library/";
|
|
331
|
+
/**
|
|
332
|
+
* A `shared` or `custom` sync pushes only paths inside the membership's
|
|
333
|
+
* prefix set (explicit grants plus pins), so a placeholder written outside it
|
|
334
|
+
* never reaches the vault. Pin it with `hq files get`'s pin writer: the
|
|
335
|
+
* default library folder as a whole, and for any other `--to` folder only the
|
|
336
|
+
* written placeholders, so the rest of that folder stays out of sync.
|
|
337
|
+
*/
|
|
338
|
+
export async function ensureLibraryInSyncScope(company, dirRel, placeholderPaths, deps) {
|
|
339
|
+
if (!deps.syncScope || !deps.addPin)
|
|
340
|
+
return undefined;
|
|
341
|
+
const companyRel = (rel) => rel.split("/").slice(2).join("/");
|
|
342
|
+
const isLibrary = `${companyRel(dirRel)}/` === LIBRARY_FOLDER;
|
|
343
|
+
const targets = isLibrary ? [LIBRARY_FOLDER] : placeholderPaths.map(companyRel);
|
|
344
|
+
const command = isLibrary ? `hq files get ${LIBRARY_FOLDER} --company ${company.slug}` : undefined;
|
|
345
|
+
let scope;
|
|
346
|
+
try {
|
|
347
|
+
scope = await deps.syncScope(company);
|
|
348
|
+
}
|
|
349
|
+
catch (err) {
|
|
350
|
+
return {
|
|
351
|
+
status: "not-pinned",
|
|
352
|
+
pins: targets,
|
|
353
|
+
...(command ? { command } : {}),
|
|
354
|
+
reason: `could not read your sync scope: ${err.message}`,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
const missing = scope.syncMode === "all" ? [] : targets.filter((t) => !isCoveredByAny(t, scope.prefixSet ?? []));
|
|
358
|
+
if (missing.length === 0)
|
|
359
|
+
return { status: "in-scope", pins: [] };
|
|
360
|
+
try {
|
|
361
|
+
for (const target of missing)
|
|
362
|
+
deps.addPin(deps.hqRoot, company.slug, target);
|
|
363
|
+
}
|
|
364
|
+
catch (err) {
|
|
365
|
+
return {
|
|
366
|
+
status: "not-pinned",
|
|
367
|
+
pins: missing,
|
|
368
|
+
...(command ? { command } : {}),
|
|
369
|
+
reason: `could not write the pin: ${err.message}`,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
return { status: "pinned", pins: missing };
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* {@link resolvePullScope} answers `all` when any call fails. Report the
|
|
376
|
+
* failure instead, so a failed lookup is not mistaken for full sync.
|
|
377
|
+
*/
|
|
378
|
+
export async function resolveLibrarySyncScope(client, company, hqRoot) {
|
|
379
|
+
let failure;
|
|
380
|
+
const watch = (call) => call.catch((err) => {
|
|
381
|
+
failure ??= err;
|
|
382
|
+
throw err;
|
|
383
|
+
});
|
|
384
|
+
const scope = await resolvePullScope({
|
|
385
|
+
listMyMemberships: () => watch(client.listMyMemberships()),
|
|
386
|
+
getMembershipSyncConfig: (id) => watch(client.getMembershipSyncConfig(id)),
|
|
387
|
+
listMyExplicitGrants: (uid) => watch(client.listMyExplicitGrants(uid)),
|
|
388
|
+
}, company.uid, company.slug, hqRoot);
|
|
389
|
+
if (failure !== undefined)
|
|
390
|
+
throw failure instanceof Error ? failure : new Error(String(failure));
|
|
391
|
+
return scope;
|
|
325
392
|
}
|
|
326
393
|
async function writeAssetPlaceholder(p, options, deps, dirAbs) {
|
|
327
394
|
const ts = offloadTimestamp(deps.now());
|
package/dist/commands/library.js
CHANGED
|
@@ -35,7 +35,7 @@ import { findHqRoot } from "../utils/manifest.js";
|
|
|
35
35
|
import { networkTransportErrorCode } from "../utils/network-transport-error.js";
|
|
36
36
|
import { resolveVaultCredentialForCapability } from "../utils/resolve-vault-credential.js";
|
|
37
37
|
import { getCompanyUid, vaultApiFetch } from "../utils/vault-api.js";
|
|
38
|
-
import { defaultLibraryIo, runLibraryAdd, runLibraryTag, } from "./library-add.js";
|
|
38
|
+
import { defaultLibraryIo, resolveLibrarySyncScope, runLibraryAdd, runLibraryTag, } from "./library-add.js";
|
|
39
39
|
import { captionAddedAsset, extractCaptionFrames, runLibraryDescribe, runLocalClaude } from "./library-caption.js";
|
|
40
40
|
import { LIBRARY_LINK_DEFAULT_MINUTES, LIBRARY_LINK_MAX_MINUTES, runLibraryLink } from "./library-link.js";
|
|
41
41
|
/** Mirrors hq-pro's MAX_ASSET_SEARCH_TAGS / tag length bound. */
|
|
@@ -295,8 +295,39 @@ export function formatLibraryAddResult(result) {
|
|
|
295
295
|
return `${line}\n ${chalk.dim(`AI caption added (${item.caption.captionedBy}).`)}`;
|
|
296
296
|
return `${line}\n ${chalk.yellow(item.caption.note ?? "Captioning skipped.")}`;
|
|
297
297
|
})
|
|
298
|
+
.concat(formatLibrarySyncScope(result))
|
|
298
299
|
.join("\n");
|
|
299
300
|
}
|
|
301
|
+
function formatLibrarySyncScope({ company, syncScope }) {
|
|
302
|
+
if (!syncScope || syncScope.status === "in-scope")
|
|
303
|
+
return [];
|
|
304
|
+
const pins = syncScope.pins.map((pin) => `${company}:${pin}`).join(", ");
|
|
305
|
+
if (syncScope.status === "pinned") {
|
|
306
|
+
return [chalk.dim(`Pinned ${pins} so sync uploads these placeholders to the company vault.`)];
|
|
307
|
+
}
|
|
308
|
+
const fix = syncScope.command
|
|
309
|
+
? `Pin the folder with: ${syncScope.command}`
|
|
310
|
+
: `Add ${syncScope.pins.join(", ")} under "${company}" in .hq/pins.json at the HQ root.`;
|
|
311
|
+
return [
|
|
312
|
+
chalk.yellow(`Warning: sync may not upload these placeholders (${syncScope.reason}). ` +
|
|
313
|
+
`Until it does, teammates and cloud search will not see them. ${fix}`),
|
|
314
|
+
];
|
|
315
|
+
}
|
|
316
|
+
/** Sync scope wiring for `hq library add`; an API key has no sync tree to check. */
|
|
317
|
+
async function librarySyncScopeDeps(credential, hqRoot) {
|
|
318
|
+
if (credential.kind !== "cognito")
|
|
319
|
+
return {};
|
|
320
|
+
const [{ VaultClient }, { buildVaultConfig }, { addPin }] = await Promise.all([
|
|
321
|
+
import("@indigoai-us/hq-cloud"),
|
|
322
|
+
import("../utils/cognito-session.js"),
|
|
323
|
+
import("./files-browse.js"),
|
|
324
|
+
]);
|
|
325
|
+
const client = new VaultClient(buildVaultConfig(credential.token));
|
|
326
|
+
return {
|
|
327
|
+
syncScope: (company) => resolveLibrarySyncScope(client, company, hqRoot),
|
|
328
|
+
addPin,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
300
331
|
/** GET a presigned URL. Errors never echo the URL, which carries a signature. */
|
|
301
332
|
async function downloadPresigned(url) {
|
|
302
333
|
const res = await fetch(url);
|
|
@@ -366,9 +397,11 @@ export function registerLibraryCommand(program) {
|
|
|
366
397
|
.option("--json", "Print machine-readable JSON")
|
|
367
398
|
.action(async (sources, options) => {
|
|
368
399
|
try {
|
|
369
|
-
const
|
|
400
|
+
const credential = await resolveVaultCredentialForCapability("files:write", "library add", {
|
|
370
401
|
interactive: true,
|
|
371
402
|
});
|
|
403
|
+
const { token } = credential;
|
|
404
|
+
const hqRoot = findHqRoot();
|
|
372
405
|
const result = await runLibraryAdd(sources, {
|
|
373
406
|
company: options.company,
|
|
374
407
|
to: options.to,
|
|
@@ -376,11 +409,12 @@ export function registerLibraryCommand(program) {
|
|
|
376
409
|
title: options.title,
|
|
377
410
|
note: options.note,
|
|
378
411
|
}, {
|
|
379
|
-
hqRoot
|
|
412
|
+
hqRoot,
|
|
380
413
|
now: () => new Date(),
|
|
381
414
|
resolveCompany: (slug) => libraryCompany(token, slug),
|
|
382
415
|
presign: (body) => vaultApiFetch({ token, path: "/v1/files/presign", method: "POST", body }),
|
|
383
416
|
...defaultLibraryIo,
|
|
417
|
+
...(await librarySyncScopeDeps(credential, hqRoot)),
|
|
384
418
|
...(options.caption === false
|
|
385
419
|
? {}
|
|
386
420
|
: {
|
package/dist/commands/reindex.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { reconcileHookRegistrations } from '../lib/hook-engine/reconcile.js';
|
|
2
|
+
import { trustCodexUserHooks } from '../utils/hook-trust.js';
|
|
1
3
|
/**
|
|
2
4
|
* hq reindex — surface namespaced skills and commands, clean up retired
|
|
3
5
|
* personal-overlay mirror links, capture coding-harness session logs, regenerate
|
|
@@ -775,6 +777,23 @@ export function registerReindexCommand(program) {
|
|
|
775
777
|
// means a sync/rescue holds the shared operation lock and may be
|
|
776
778
|
// rewriting .claude/settings.json right now, and this is an unlocked
|
|
777
779
|
// whole-file read-modify-write.
|
|
780
|
+
// Registration reconcile is best-effort: a failure leaves the current
|
|
781
|
+
// registrations in place and must not stop the rest of reindex.
|
|
782
|
+
try {
|
|
783
|
+
// A file that failed is left as it was; the keys of files that were
|
|
784
|
+
// rewritten are still trusted so Codex keeps running their hooks.
|
|
785
|
+
const { codexUserKeys, failures } = reconcileHookRegistrations(hqRoot);
|
|
786
|
+
for (const failure of failures)
|
|
787
|
+
console.warn(`reindex: hook registration reconcile skipped ${failure}`);
|
|
788
|
+
if (codexUserKeys.length) {
|
|
789
|
+
const trust = await trustCodexUserHooks(hqRoot, codexUserKeys);
|
|
790
|
+
if (trust.status === 'failed')
|
|
791
|
+
console.warn(`reindex: Codex hook trust needs repair: ${trust.reason}`);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
catch (error) {
|
|
795
|
+
console.warn(`reindex: hook registration reconcile skipped: ${error.message}`);
|
|
796
|
+
}
|
|
778
797
|
dropProjectDefaultModeForBypassUser(hqRoot);
|
|
779
798
|
await trustHqRuntimeHooks(hqRoot, undefined, {
|
|
780
799
|
rootAliases: [literalHqRoot],
|
package/dist/hook-engine-path.js
CHANGED
|
@@ -11,14 +11,21 @@ export function hookEnginePathArgs(argv) {
|
|
|
11
11
|
}
|
|
12
12
|
export async function runHookEnginePath(args) {
|
|
13
13
|
try {
|
|
14
|
-
if (args.some(arg => !['--repair', '--help', '-h'].includes(arg))) {
|
|
15
|
-
throw new Error('Usage: hq core hook-engine-path [--repair]');
|
|
14
|
+
if (args.some(arg => !['--repair', '--help', '-h', '--for-registration'].includes(arg))) {
|
|
15
|
+
throw new Error('Usage: hq core hook-engine-path [--repair] [--for-registration]');
|
|
16
16
|
}
|
|
17
17
|
if (args.includes('--help') || args.includes('-h')) {
|
|
18
|
-
console.log('Usage: hq core hook-engine-path [--repair]\nResolve and pin the installed native hook engine offline.');
|
|
18
|
+
console.log('Usage: hq core hook-engine-path [--repair] [--for-registration]\nResolve and pin the installed native hook engine offline.\n--for-registration prints a path only when the launcher flag is enabled.');
|
|
19
19
|
return;
|
|
20
20
|
}
|
|
21
21
|
const { installHookEngine, hookEngineRequired, HOOK_ENGINE_REPAIR } = await import('./lib/hook-engine/resolve.js');
|
|
22
|
+
if (args.includes('--for-registration')) {
|
|
23
|
+
const { launcherEnabled } = await import('./lib/hook-engine/registration.js');
|
|
24
|
+
if (!launcherEnabled()) {
|
|
25
|
+
process.exitCode = 1;
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
22
29
|
const binary = installHookEngine({ repair: args.includes('--repair') });
|
|
23
30
|
if (binary)
|
|
24
31
|
console.log(binary);
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,15 @@ import "./node-preflight.js";
|
|
|
3
3
|
import "./node-network-compat-fast.js";
|
|
4
4
|
import { isVersionRequest } from "./version-request.js";
|
|
5
5
|
import { isFastCoreRequest } from "./fast-scaffold.js";
|
|
6
|
+
/**
|
|
7
|
+
* Whether the install follow-up would read the registry or spawn the mesh
|
|
8
|
+
* daemon. False for fast routes' callers, and for commands that are not run
|
|
9
|
+
* from an npm install: the PATH repair needs the npm shim folder above
|
|
10
|
+
* node_modules, and the daemon step needs the installed package. Inlined so
|
|
11
|
+
* those routes do not load user-path.ts. Keep the skip rules aligned with
|
|
12
|
+
* install-followup.ts.
|
|
13
|
+
*/
|
|
14
|
+
export declare function installFollowupWouldDoWork(argv: readonly string[], env: NodeJS.ProcessEnv, platform: NodeJS.Platform): boolean;
|
|
6
15
|
type PackageUseLeaseLoader = () => Promise<void>;
|
|
7
16
|
type PackageUseUpdateProbe = () => Promise<boolean>;
|
|
8
17
|
/** Resolves true once the command was re-run (exit code already set). */
|
|
@@ -18,6 +27,7 @@ declare function runCliEntry(entryUrl: string, dispatch: () => void, loadLease?:
|
|
|
18
27
|
export declare const __test__: {
|
|
19
28
|
isVersionRequest: typeof isVersionRequest;
|
|
20
29
|
isFastCoreRequest: typeof isFastCoreRequest;
|
|
30
|
+
installFollowupWouldDoWork: typeof installFollowupWouldDoWork;
|
|
21
31
|
dispatchWithPackageUseLease: typeof dispatchWithPackageUseLease;
|
|
22
32
|
waitForUpdateThenReexec: typeof waitForUpdateThenReexec;
|
|
23
33
|
runCliEntry: typeof runCliEntry;
|
package/dist/index.js
CHANGED
|
@@ -156,6 +156,26 @@ async function runMonitorCheckFastWithRecovery(request) {
|
|
|
156
156
|
return;
|
|
157
157
|
await runMainWithInstallTreeRecovery();
|
|
158
158
|
}
|
|
159
|
+
/**
|
|
160
|
+
* Whether the install follow-up would read the registry or spawn the mesh
|
|
161
|
+
* daemon. False for fast routes' callers, and for commands that are not run
|
|
162
|
+
* from an npm install: the PATH repair needs the npm shim folder above
|
|
163
|
+
* node_modules, and the daemon step needs the installed package. Inlined so
|
|
164
|
+
* those routes do not load user-path.ts. Keep the skip rules aligned with
|
|
165
|
+
* install-followup.ts.
|
|
166
|
+
*/
|
|
167
|
+
export function installFollowupWouldDoWork(argv, env, platform) {
|
|
168
|
+
if (env.HQ_INSTALL_FOLLOWUP === "1" || env.HQ_STAGED_INSTALL === "1")
|
|
169
|
+
return false;
|
|
170
|
+
const args = argv.slice(2).filter((arg) => arg !== "--verbose");
|
|
171
|
+
if (args[0] === "mesh" && args[1] === "daemon")
|
|
172
|
+
return false;
|
|
173
|
+
if (args[0] === "daemon" && (args[1] === "run" || args[1] === "install"))
|
|
174
|
+
return false;
|
|
175
|
+
if (platform === "win32")
|
|
176
|
+
return /[/\\]node_modules[/\\]/i.test(argv[1] ?? "");
|
|
177
|
+
return /node_modules[/\\]@indigoai-us[/\\]hq-cli[/\\]/i.test(argv[1] ?? "");
|
|
178
|
+
}
|
|
159
179
|
function dispatchCliEntry() {
|
|
160
180
|
// Before anything spawns: a background hq on Windows (no terminal) hides the
|
|
161
181
|
// console window of every child it starts (feedback_351a40d5).
|
|
@@ -165,6 +185,19 @@ function dispatchCliEntry() {
|
|
|
165
185
|
void runHookEnginePath(engineArgs);
|
|
166
186
|
return;
|
|
167
187
|
}
|
|
188
|
+
// cmd.exe code page 437 prints em dashes as mojibake. Pipes stay UTF-8.
|
|
189
|
+
// Loaded only on Windows so Linux fast routes do not pay for the module.
|
|
190
|
+
if (process.platform === "win32") {
|
|
191
|
+
void import("./lib/windows/console-encoding.js").then(({ installWindowsConsoleEncoding }) => {
|
|
192
|
+
installWindowsConsoleEncoding();
|
|
193
|
+
}).catch(() => undefined).then(() => {
|
|
194
|
+
dispatchCliRoutes();
|
|
195
|
+
});
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
dispatchCliRoutes();
|
|
199
|
+
}
|
|
200
|
+
function dispatchCliRoutes() {
|
|
168
201
|
if (process.platform === "win32" && coreCommandHasPathOperands(process.argv)) {
|
|
169
202
|
// Keep cygpath off non-Windows and non-path command routes.
|
|
170
203
|
process.argv = normalizeCorePathOperands(process.argv, "win32", cygpathMixedPath);
|
|
@@ -172,6 +205,37 @@ function dispatchCliEntry() {
|
|
|
172
205
|
const versionRequest = isVersionRequest(process.argv);
|
|
173
206
|
const helpRequest = !versionRequest && isCommandHelpRequest(process.argv);
|
|
174
207
|
const parsedFastCore = versionRequest || helpRequest ? null : parseFastCore(process.argv);
|
|
208
|
+
const monitorRequest = versionRequest || helpRequest ? null : parseMonitorCheckFastRequest(process.argv);
|
|
209
|
+
const secretsRequest = versionRequest || helpRequest || monitorRequest || parsedFastCore
|
|
210
|
+
? null
|
|
211
|
+
: parseSecretsEnvFastRequest(process.argv);
|
|
212
|
+
const mcpRequest = !versionRequest &&
|
|
213
|
+
!helpRequest &&
|
|
214
|
+
!monitorRequest &&
|
|
215
|
+
!parsedFastCore &&
|
|
216
|
+
!secretsRequest &&
|
|
217
|
+
isMcpServeRequest(process.argv);
|
|
218
|
+
// `--version`, hook, MCP, and secrets fast routes must not pay for PATH or
|
|
219
|
+
// daemon work, so they never start PowerShell or reg.exe. The module stays
|
|
220
|
+
// unloaded when there is nothing to do, so a Linux fast route does not
|
|
221
|
+
// import it.
|
|
222
|
+
if (!versionRequest && !monitorRequest && !parsedFastCore && !secretsRequest && !mcpRequest
|
|
223
|
+
&& installFollowupWouldDoWork(process.argv, process.env, process.platform)) {
|
|
224
|
+
void import("./lib/windows/install-followup.js").then(({ runWindowsInstallFollowup }) => {
|
|
225
|
+
try {
|
|
226
|
+
runWindowsInstallFollowup({ argv: process.argv });
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
// Install repair must not turn into a failed command.
|
|
230
|
+
}
|
|
231
|
+
}).catch(() => undefined).then(() => {
|
|
232
|
+
dispatchCliCommand(versionRequest, helpRequest, parsedFastCore, monitorRequest, secretsRequest, mcpRequest);
|
|
233
|
+
});
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
dispatchCliCommand(versionRequest, helpRequest, parsedFastCore, monitorRequest, secretsRequest, mcpRequest);
|
|
237
|
+
}
|
|
238
|
+
function dispatchCliCommand(versionRequest, helpRequest, parsedFastCore, monitorRequest, secretsRequest, mcpRequest) {
|
|
175
239
|
if (versionRequest) {
|
|
176
240
|
// Bare `--version` is the CLI semver only (the contract field updaters
|
|
177
241
|
// compare). `hq version` and `--json` print the labelled HQ/CLI report.
|
|
@@ -202,11 +266,11 @@ function dispatchCliEntry() {
|
|
|
202
266
|
await runMainWithInstallTreeRecovery();
|
|
203
267
|
});
|
|
204
268
|
}
|
|
205
|
-
else if (
|
|
269
|
+
else if (monitorRequest) {
|
|
206
270
|
// The Stop hook runs this every turn in every session. Answer the common
|
|
207
271
|
// no-active-lane case from the lane-record directory alone; anything the
|
|
208
272
|
// fast route cannot prove falls back to the full command graph.
|
|
209
|
-
void runMonitorCheckFastWithRecovery(
|
|
273
|
+
void runMonitorCheckFastWithRecovery(monitorRequest);
|
|
210
274
|
}
|
|
211
275
|
else if (parsedFastCore?.entry.kind === "native" &&
|
|
212
276
|
(process.env.HQ_FLAG_HQ_CORE_NATIVE_FAST_ROUTE === "true" ||
|
|
@@ -222,8 +286,8 @@ function dispatchCliEntry() {
|
|
|
222
286
|
// existing full-graph route.
|
|
223
287
|
void runNativeFastRouteWithRecovery(parsedFastCore);
|
|
224
288
|
}
|
|
225
|
-
else if (
|
|
226
|
-
const request =
|
|
289
|
+
else if (secretsRequest) {
|
|
290
|
+
const request = secretsRequest;
|
|
227
291
|
void import("./secrets-env-fast-route.js").then(async (route) => {
|
|
228
292
|
await route.dispatchSecretsEnvFastRequest(request, {
|
|
229
293
|
isEnabled: () => route.secretsEnvFastRouteEnabled(request),
|
|
@@ -237,7 +301,7 @@ function dispatchCliEntry() {
|
|
|
237
301
|
});
|
|
238
302
|
});
|
|
239
303
|
}
|
|
240
|
-
else if (
|
|
304
|
+
else if (mcpRequest) {
|
|
241
305
|
// Keep the minimal startup route; runMcpServe applies the hq-flags gate.
|
|
242
306
|
void import("./commands/mcp-serve.js").then(({ runMcpServe }) => runMcpServe());
|
|
243
307
|
}
|
|
@@ -373,6 +437,7 @@ void runCliEntry(import.meta.url, dispatchCliEntry);
|
|
|
373
437
|
export const __test__ = {
|
|
374
438
|
isVersionRequest,
|
|
375
439
|
isFastCoreRequest,
|
|
440
|
+
installFollowupWouldDoWork,
|
|
376
441
|
dispatchWithPackageUseLease,
|
|
377
442
|
waitForUpdateThenReexec,
|
|
378
443
|
runCliEntry,
|
package/dist/lazy-commands.js
CHANGED
|
@@ -3,6 +3,7 @@ import { existsSync } from "node:fs";
|
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { dirname, join, resolve } from "node:path";
|
|
5
5
|
import { COMMAND_CATALOG } from "./command-catalog.generated.js";
|
|
6
|
+
import { installUnknownCommandSuggestions } from "./unknown-command.js";
|
|
6
7
|
const commandCatalog = COMMAND_CATALOG;
|
|
7
8
|
/**
|
|
8
9
|
* One entry for every root, including groups that are assembled by several
|
|
@@ -105,6 +106,7 @@ export function findRuntimeHqRoot(start, parentOf = dirname, isRoot = (directory
|
|
|
105
106
|
export function registerCommandCatalog(program) {
|
|
106
107
|
for (const command of commandCatalog)
|
|
107
108
|
program.addCommand(commandDefinition(command));
|
|
109
|
+
installUnknownCommandSuggestions(program);
|
|
108
110
|
}
|
|
109
111
|
function commandForToken(token) {
|
|
110
112
|
return commandCatalog.find((candidate) => candidate.name === token || candidate.aliases.includes(token));
|
|
@@ -201,14 +201,44 @@ export interface UsageReport {
|
|
|
201
201
|
freeItems: string;
|
|
202
202
|
drivers: Driver[];
|
|
203
203
|
noSyncGuideUrl: string;
|
|
204
|
+
/** Set by `hq billing usage`: the account is on usage pricing, so meters are priced. */
|
|
205
|
+
onUsagePricing?: true;
|
|
204
206
|
readOnly: true;
|
|
205
207
|
}
|
|
208
|
+
/** One meter's reading with no price attached. */
|
|
209
|
+
export interface MeterReadingRow {
|
|
210
|
+
key: UsageMeterKey;
|
|
211
|
+
displayName: string;
|
|
212
|
+
/** In billed units (GB for storage). Null unless measured. */
|
|
213
|
+
value: number | null;
|
|
214
|
+
state: MeterState;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* The report for an account that is not on usage pricing (a flat plan, or the
|
|
218
|
+
* server did not say). Readings only: no rates, no cost, no projected bill.
|
|
219
|
+
*/
|
|
220
|
+
export interface UsageReadingsReport {
|
|
221
|
+
scope: UsageScope;
|
|
222
|
+
source: MonthUsage["source"];
|
|
223
|
+
/** False: flat plan, meters are not billed. Null: HQ did not say. */
|
|
224
|
+
onUsagePricing: false | null;
|
|
225
|
+
period: MonthUsage["period"];
|
|
226
|
+
meters: MeterReadingRow[];
|
|
227
|
+
storage?: StorageSplit;
|
|
228
|
+
/** The one line that says why no cost is shown. */
|
|
229
|
+
notice: string;
|
|
230
|
+
readOnly: true;
|
|
231
|
+
}
|
|
232
|
+
export type BillingUsageReport = UsageReport | UsageReadingsReport;
|
|
233
|
+
export declare function isReadingsReport(r: BillingUsageReport): r is UsageReadingsReport;
|
|
234
|
+
/** Readings for an account not on usage pricing. Never prices a meter. */
|
|
235
|
+
export declare function buildReadingsReport(usage: MonthUsage, onUsagePricing: false | null): UsageReadingsReport;
|
|
206
236
|
export declare function buildReport(usage: MonthUsage, rates: {
|
|
207
237
|
rates: readonly UsageMeterRate[];
|
|
208
238
|
allowanceUsd: number;
|
|
209
239
|
source: "published" | "bundled";
|
|
210
240
|
card?: UsageCard;
|
|
211
241
|
}): UsageReport;
|
|
212
|
-
export declare function formatReport(r:
|
|
242
|
+
export declare function formatReport(r: BillingUsageReport): string;
|
|
213
243
|
export {};
|
|
214
244
|
//# sourceMappingURL=usage-cost.d.ts.map
|
|
@@ -362,6 +362,38 @@ export function findDrivers(usage, bill) {
|
|
|
362
362
|
// ── Report ──────────────────────────────────────────────────────────────────
|
|
363
363
|
/** The self-hosted / no-sync guide the usage card links (feed `signup.selfHosted.url`). */
|
|
364
364
|
export const NO_SYNC_GUIDE_URL = SELF_HOSTED_GUIDE_URL;
|
|
365
|
+
export function isReadingsReport(r) {
|
|
366
|
+
return !("bill" in r);
|
|
367
|
+
}
|
|
368
|
+
function readingsNotice(scope, onUsagePricing) {
|
|
369
|
+
if (onUsagePricing === false) {
|
|
370
|
+
return scope.kind === "company"
|
|
371
|
+
? `${scope.companySlug} is on a flat plan, so usage meters are not billed.`
|
|
372
|
+
: "Your personal account is on a flat plan, so usage meters are not billed.";
|
|
373
|
+
}
|
|
374
|
+
const who = scope.kind === "company" ? scope.companySlug : "your personal account";
|
|
375
|
+
return `HQ did not say whether ${who} is on usage pricing, so no costs are shown.`;
|
|
376
|
+
}
|
|
377
|
+
/** Readings for an account not on usage pricing. Never prices a meter. */
|
|
378
|
+
export function buildReadingsReport(usage, onUsagePricing) {
|
|
379
|
+
const byKey = new Map(usage.meters.map((m) => [m.key, m]));
|
|
380
|
+
const meters = BUNDLED_USAGE_METER_RATES.map((rate) => {
|
|
381
|
+
const reading = byKey.get(rate.key);
|
|
382
|
+
return reading && reading.state === "measured" && reading.value !== null
|
|
383
|
+
? { key: rate.key, displayName: rate.displayName, value: reading.value, state: "measured" }
|
|
384
|
+
: { key: rate.key, displayName: rate.displayName, value: null, state: reading?.state ?? "not-available" };
|
|
385
|
+
});
|
|
386
|
+
return {
|
|
387
|
+
scope: usage.scope,
|
|
388
|
+
source: usage.source,
|
|
389
|
+
onUsagePricing,
|
|
390
|
+
period: usage.period,
|
|
391
|
+
meters,
|
|
392
|
+
...(usage.storage ? { storage: usage.storage } : {}),
|
|
393
|
+
notice: readingsNotice(usage.scope, onUsagePricing),
|
|
394
|
+
readOnly: true,
|
|
395
|
+
};
|
|
396
|
+
}
|
|
365
397
|
export function buildReport(usage, rates) {
|
|
366
398
|
const bill = computeBill(usage, rates.rates, rates.allowanceUsd);
|
|
367
399
|
return {
|
|
@@ -394,7 +426,21 @@ function valueText(m) {
|
|
|
394
426
|
return `${Math.round(m.value)} vault${Math.round(m.value) === 1 ? "" : "s"}`;
|
|
395
427
|
return `${Math.round(m.value)}`;
|
|
396
428
|
}
|
|
429
|
+
function formatReadingsReport(r) {
|
|
430
|
+
const who = r.scope.kind === "company" ? r.scope.companySlug : "your personal HQ";
|
|
431
|
+
const lines = [];
|
|
432
|
+
lines.push(`Usage for ${who}, ${r.period.start.slice(0, 7)} (as of ${r.period.asOf.slice(0, 10)})`);
|
|
433
|
+
lines.push("");
|
|
434
|
+
const nameW = Math.max(...r.meters.map((m) => m.displayName.length));
|
|
435
|
+
for (const m of r.meters)
|
|
436
|
+
lines.push(` ${m.displayName.padEnd(nameW)} ${valueText(m)}`);
|
|
437
|
+
lines.push("");
|
|
438
|
+
lines.push(r.notice);
|
|
439
|
+
return lines.join("\n");
|
|
440
|
+
}
|
|
397
441
|
export function formatReport(r) {
|
|
442
|
+
if (isReadingsReport(r))
|
|
443
|
+
return formatReadingsReport(r);
|
|
398
444
|
const who = r.scope.kind === "company" ? r.scope.companySlug : "your personal HQ";
|
|
399
445
|
const month = r.period.start.slice(0, 7);
|
|
400
446
|
const lines = [];
|
package/dist/lib/db/local.d.ts
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* File lives at ~/.hq/db/{company}/vault.db with WAL journal mode.
|
|
5
5
|
* Never returns or logs remote connection strings.
|
|
6
6
|
*/
|
|
7
|
-
import
|
|
7
|
+
import type BetterSqlite3 from "better-sqlite3";
|
|
8
8
|
import { type LocalDbPathEnv } from "./paths.js";
|
|
9
|
-
export type LocalDb =
|
|
9
|
+
export type LocalDb = BetterSqlite3.Database;
|
|
10
10
|
export interface OpenLocalDbOptions extends LocalDbPathEnv {
|
|
11
11
|
/** When true, do not create the file if missing (throws). Default false. */
|
|
12
12
|
readonly?: boolean;
|
package/dist/lib/db/local.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Never returns or logs remote connection strings.
|
|
6
6
|
*/
|
|
7
7
|
import fs from "node:fs";
|
|
8
|
-
import
|
|
8
|
+
import { loadBetterSqlite3 } from "./sqlite-binding.js";
|
|
9
9
|
import { ensureLocalDbDir, resolveLocalDbPath, } from "./paths.js";
|
|
10
10
|
const MIGRATIONS_TABLE = "hq_schema_migrations";
|
|
11
11
|
/**
|
|
@@ -33,7 +33,7 @@ export function openLocalDb(companySlug, opts) {
|
|
|
33
33
|
}
|
|
34
34
|
ensureLocalDbDir(companySlug, opts);
|
|
35
35
|
}
|
|
36
|
-
const db = new
|
|
36
|
+
const db = new (loadBetterSqlite3())(dbPath);
|
|
37
37
|
// WAL for concurrent readers; safe for agent/CLI workloads.
|
|
38
38
|
db.pragma("journal_mode = WAL");
|
|
39
39
|
db.pragma("foreign_keys = ON");
|