@indigoai-us/hq-cli 5.345.147 → 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 +65 -0
- package/README.md +2 -0
- package/assets/bot-workers/setup/worker.yaml +11 -0
- package/dist/command-catalog.generated.d.ts +341 -11
- package/dist/command-catalog.generated.js +421 -11
- 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/agent-inbox.js +2 -1
- 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/cloud.js +31 -1
- package/dist/commands/core-refresh-vault-access-native.d.ts +33 -0
- package/dist/commands/core-refresh-vault-access-native.js +356 -28
- package/dist/commands/core-work-mesh-bind-trusted-native.d.ts +9 -0
- package/dist/commands/core-work-mesh-bind-trusted-native.js +26 -19
- package/dist/commands/core.js +9 -0
- package/dist/commands/daemon.d.ts +7 -0
- package/dist/commands/daemon.js +37 -3
- package/dist/commands/dm.js +18 -3
- package/dist/commands/doctor.d.ts +11 -1
- package/dist/commands/doctor.js +56 -1
- package/dist/commands/install-global.d.ts +8 -8
- package/dist/commands/install-global.js +139 -61
- package/dist/commands/lanes-shared.d.ts +3 -1
- package/dist/commands/lanes.d.ts +4 -4
- package/dist/commands/lanes.js +380 -25
- 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/pack-install.d.ts +2 -2
- package/dist/commands/pack-install.js +133 -35
- package/dist/commands/reindex.js +19 -0
- package/dist/commands/sync-manifest.d.ts +2 -9
- package/dist/commands/sync-manifest.js +4 -15
- package/dist/hook-engine-path.d.ts +4 -0
- package/dist/hook-engine-path.js +43 -0
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +2783 -2354
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.js +76 -5
- package/dist/lazy-commands.js +2 -0
- package/dist/lib/agent-kit/inbox-redact.d.ts +44 -0
- package/dist/lib/agent-kit/inbox-redact.js +361 -0
- package/dist/lib/agent-kit/inbox-state.js +11 -2
- package/dist/lib/agent-kit/mcp/tools.js +2 -1
- package/dist/lib/agent-kit/run/inbox.js +36 -7
- package/dist/lib/anywhere-runtime-flag-key.d.ts +3 -0
- package/dist/lib/anywhere-runtime-flag-key.js +9 -0
- package/dist/lib/billing/usage-cost.d.ts +31 -1
- package/dist/lib/billing/usage-cost.js +46 -0
- package/dist/lib/company-skill-wrapper.js +6 -4
- package/dist/lib/daemon/install.d.ts +47 -1
- package/dist/lib/daemon/install.js +181 -15
- package/dist/lib/daemon/lock.d.ts +32 -0
- package/dist/lib/daemon/lock.js +55 -2
- 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/__testing__/run-doctor.js +4 -0
- package/dist/lib/doctor/checks/anywhere.d.ts +15 -0
- package/dist/lib/doctor/checks/anywhere.js +77 -11
- package/dist/lib/doctor/checks/cli-path.d.ts +12 -0
- package/dist/lib/doctor/checks/cli-path.js +46 -0
- package/dist/lib/doctor/checks/hook-engine.d.ts +4 -0
- package/dist/lib/doctor/checks/hook-engine.js +17 -0
- package/dist/lib/doctor/checks/sync-health.d.ts +7 -0
- package/dist/lib/doctor/checks/sync-health.js +38 -2
- package/dist/lib/doctor/checks/temp-homes.d.ts +19 -0
- package/dist/lib/doctor/checks/temp-homes.js +54 -0
- package/dist/lib/doctor/fix/apply.d.ts +90 -1
- package/dist/lib/doctor/fix/apply.js +176 -10
- package/dist/lib/doctor/fix/json-output.d.ts +4 -1
- package/dist/lib/doctor/fix/json-output.js +6 -0
- package/dist/lib/doctor/fix/remediation.d.ts +7 -1
- package/dist/lib/doctor/fix/remediation.js +23 -1
- package/dist/lib/doctor/registry.js +10 -0
- package/dist/lib/doctor/temp-home-sweeper.d.ts +149 -0
- package/dist/lib/doctor/temp-home-sweeper.js +418 -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/flag-registry.js +9 -1
- package/dist/lib/flag-snapshot-file.js +8 -3
- 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/hook-engine/resolve.d.ts +31 -0
- package/dist/lib/hook-engine/resolve.js +134 -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/lanes/admission-host.d.ts +1 -0
- package/dist/lib/lanes/admission-host.js +8 -1
- package/dist/lib/lanes/admission.d.ts +4 -0
- package/dist/lib/lanes/admission.js +6 -2
- package/dist/lib/lanes/envelope-repair-dispatch.d.ts +5 -0
- package/dist/lib/lanes/envelope-repair-dispatch.js +12 -1
- package/dist/lib/lanes/index.d.ts +2 -2
- package/dist/lib/lanes/index.js +1 -1
- package/dist/lib/lanes/keep-alive.js +4 -1
- package/dist/lib/lanes/local-host-identity.d.ts +40 -0
- package/dist/lib/lanes/local-host-identity.js +314 -0
- package/dist/lib/lanes/reaper.d.ts +1 -1
- package/dist/lib/lanes/reaper.js +7 -0
- package/dist/lib/lanes/reconcile.d.ts +4 -0
- package/dist/lib/lanes/reconcile.js +16 -8
- package/dist/lib/lanes/spawn.d.ts +20 -0
- package/dist/lib/lanes/spawn.js +48 -2
- package/dist/lib/lanes/status-fields.js +6 -1
- package/dist/lib/lanes/store.js +8 -1
- package/dist/lib/lanes/throttle.d.ts +49 -6
- package/dist/lib/lanes/throttle.js +154 -17
- package/dist/lib/lanes/types.d.ts +22 -1
- package/dist/lib/lanes/types.js +27 -3
- package/dist/lib/lanes/watcher.js +27 -2
- package/dist/lib/lanes/worker-continuity.d.ts +2 -0
- package/dist/lib/lanes/worker-continuity.js +18 -11
- package/dist/lib/manifest-disabled.d.ts +13 -0
- package/dist/lib/manifest-disabled.js +19 -0
- package/dist/lib/service-manager/launchd.d.ts +1 -0
- package/dist/lib/service-manager/launchd.js +1 -1
- 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 +125 -0
- package/dist/lib/windows/user-path.js +275 -0
- package/dist/lib/work-context/auto-bind.d.ts +1 -1
- package/dist/lib/work-context/auto-bind.js +22 -3
- package/dist/lib/work-context/lane-bind.d.ts +31 -0
- package/dist/lib/work-context/lane-bind.js +101 -0
- package/dist/lib/work-context/organize.js +1 -0
- package/dist/lib/work-context/prd-sync.js +7 -0
- package/dist/lib/work-context/project.d.ts +36 -10
- package/dist/lib/work-context/project.js +130 -41
- package/dist/lib/work-context/reconcile.d.ts +6 -0
- package/dist/lib/work-context/reconcile.js +25 -5
- package/dist/lib/work-context/state.d.ts +6 -0
- package/dist/lib/work-context/state.js +29 -1
- 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/copy-tree.d.ts +61 -0
- package/dist/utils/copy-tree.js +249 -0
- package/dist/utils/environmental-error.js +7 -3
- package/dist/utils/hook-trust.d.ts +9 -0
- package/dist/utils/hook-trust.js +30 -2
- package/dist/utils/npm-invocation.d.ts +27 -0
- package/dist/utils/npm-invocation.js +54 -0
- 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/dist/utils/safe-link.d.ts +50 -0
- package/dist/utils/safe-link.js +300 -0
- package/package.json +17 -5
- package/scripts/hook-engine-manifest.mjs +22 -0
- package/scripts/install-hook-engine.mjs +10 -0
|
@@ -8,6 +8,7 @@ import chalk from "chalk";
|
|
|
8
8
|
import * as os from "node:os";
|
|
9
9
|
import { ensureCognitoToken } from "../utils/cognito-session.js";
|
|
10
10
|
import { vaultApiFetch } from "../utils/vault-api.js";
|
|
11
|
+
import { redactAgentInboxItem } from "../lib/agent-kit/inbox-redact.js";
|
|
11
12
|
import { markInboxDone, mergeAgentInbox, readDoneIds, summarizeInboxEntry } from "../lib/agent-kit/inbox-state.js";
|
|
12
13
|
import { agentKitPaths } from "../lib/agent-kit/paths.js";
|
|
13
14
|
import { requireExternalCreds } from "./agent-kit.js";
|
|
@@ -36,7 +37,7 @@ export function registerAgentInboxCommand(agent) {
|
|
|
36
37
|
});
|
|
37
38
|
const rows = entries
|
|
38
39
|
.filter((e) => opts.all || !done.has(e.id))
|
|
39
|
-
.map((e) => summarizeInboxEntry(e, done.has(e.id)));
|
|
40
|
+
.map((e) => summarizeInboxEntry(redactAgentInboxItem(e), done.has(e.id)));
|
|
40
41
|
if (opts.json) {
|
|
41
42
|
console.log(JSON.stringify({ count: rows.length, messages: rows, ...(note ? { note } : {}) }, null, 2));
|
|
42
43
|
return;
|
|
@@ -17,9 +17,18 @@
|
|
|
17
17
|
*
|
|
18
18
|
* Usage numbers come through `readMonthUsage` (src/lib/billing/usage-cost.ts),
|
|
19
19
|
* the single seam to switch to the US-005 daily rollup once it has a read route.
|
|
20
|
+
*
|
|
21
|
+
* Only an account on usage pricing gets costs. The plan comes from the
|
|
22
|
+
* server: `pricingPlan` / `usageOnly` on the usage-limits body (hq-pro #5042,
|
|
23
|
+
* computed with `isOnUsagePricing`). Only when that body does not say, the
|
|
24
|
+
* billing summary the command already reads for the trial. A flat-plan account (for example a Workforce
|
|
25
|
+
* company that subscribed before the 2026-10-16 cutover) and an account the
|
|
26
|
+
* server says nothing about get readings only, with no rate, cost or
|
|
27
|
+
* projected bill.
|
|
20
28
|
*/
|
|
21
29
|
import type { Command } from "commander";
|
|
22
|
-
import { type
|
|
30
|
+
import { type BillingUsageReport, type ReadMonthUsageDeps, type StorageSplit, type UsageScope } from "../lib/billing/usage-cost.js";
|
|
31
|
+
import { type PricingPlan } from "../lib/billing/usage-only.js";
|
|
23
32
|
export interface MembershipRef {
|
|
24
33
|
uid: string;
|
|
25
34
|
slug: string;
|
|
@@ -34,6 +43,12 @@ export interface BillingUsageDeps {
|
|
|
34
43
|
fetchPricing(): Promise<unknown | null>;
|
|
35
44
|
/** Whether the usage trial is running for the scope. Null = unknown. */
|
|
36
45
|
readTrialActive?: (scope: UsageScope) => Promise<boolean | null>;
|
|
46
|
+
/**
|
|
47
|
+
* The billing summary body for the scope (company: `/v1/billing/summary`,
|
|
48
|
+
* person: `/v1/billing/person/summary`). Null when it did not answer. Used
|
|
49
|
+
* for the plan when usage-limits does not carry it (older server).
|
|
50
|
+
*/
|
|
51
|
+
fetchBillingSummary?: (scope: UsageScope) => Promise<unknown | null>;
|
|
37
52
|
now(): Date;
|
|
38
53
|
}
|
|
39
54
|
export interface BillingUsageOptions {
|
|
@@ -46,7 +61,23 @@ export declare class UsageScopeError extends Error {
|
|
|
46
61
|
}
|
|
47
62
|
/** Pick the scope. Never reaches outside the caller's own memberships. */
|
|
48
63
|
export declare function resolveUsageScope(memberships: MembershipRef[], opts: BillingUsageOptions): UsageScope;
|
|
49
|
-
|
|
64
|
+
/** The plan on a usage-limits body: `pricingPlan`, else the `usageOnly` / `onUsagePricing` boolean. */
|
|
65
|
+
export declare function pricingPlanFromUsageLimits(body: unknown): PricingPlan | null;
|
|
66
|
+
/**
|
|
67
|
+
* The plan on a billing summary body. A summary that answered without saying
|
|
68
|
+
* usage-only is a flat plan: the person summary carries `personalUsage` only
|
|
69
|
+
* for usage pricing, and a company summary from a server older than usage
|
|
70
|
+
* pricing has no `pricingPlan` at all.
|
|
71
|
+
*/
|
|
72
|
+
export declare function pricingPlanFromSummary(body: unknown): PricingPlan | null;
|
|
73
|
+
/**
|
|
74
|
+
* True: price the meters. False: flat plan. Null: unknown, show no cost.
|
|
75
|
+
* When usage-limits answers, only its answer counts: the server computes it
|
|
76
|
+
* with `isOnUsagePricing`, the source of truth. The summary's plan is used
|
|
77
|
+
* only when usage-limits does not say.
|
|
78
|
+
*/
|
|
79
|
+
export declare function resolveOnUsagePricing(limitsPlan: PricingPlan | null, summaryPlan: PricingPlan | null): boolean | null;
|
|
80
|
+
export declare function runBillingUsage(opts: BillingUsageOptions, deps: BillingUsageDeps): Promise<BillingUsageReport>;
|
|
50
81
|
/**
|
|
51
82
|
* `hq billing usage --card`: the card from GET /v1/pricing, or the bundled
|
|
52
83
|
* card when the feed has none. Needs no sign-in.
|
|
@@ -17,14 +17,22 @@
|
|
|
17
17
|
*
|
|
18
18
|
* Usage numbers come through `readMonthUsage` (src/lib/billing/usage-cost.ts),
|
|
19
19
|
* the single seam to switch to the US-005 daily rollup once it has a read route.
|
|
20
|
+
*
|
|
21
|
+
* Only an account on usage pricing gets costs. The plan comes from the
|
|
22
|
+
* server: `pricingPlan` / `usageOnly` on the usage-limits body (hq-pro #5042,
|
|
23
|
+
* computed with `isOnUsagePricing`). Only when that body does not say, the
|
|
24
|
+
* billing summary the command already reads for the trial. A flat-plan account (for example a Workforce
|
|
25
|
+
* company that subscribed before the 2026-10-16 cutover) and an account the
|
|
26
|
+
* server says nothing about get readings only, with no rate, cost or
|
|
27
|
+
* projected bill.
|
|
20
28
|
*/
|
|
21
29
|
import chalk from "chalk";
|
|
22
30
|
import { ensureCognitoToken } from "../utils/cognito-session.js";
|
|
23
31
|
import { vaultApiFetch, vaultApiFetchPublic } from "../utils/vault-api.js";
|
|
24
|
-
import { buildReport, formatReport, readMonthUsage, } from "../lib/billing/usage-cost.js";
|
|
32
|
+
import { buildReadingsReport, buildReport, formatReport, readMonthUsage, } from "../lib/billing/usage-cost.js";
|
|
25
33
|
import { ratesFromPricingStatement } from "../lib/billing/usage-rate-card.js";
|
|
26
34
|
import { fetchUsageCard, formatUsageCardLines } from "../lib/billing/usage-card.js";
|
|
27
|
-
import { fetchCompanyBillingSummary, usageTrialActiveFromBody } from "../lib/billing/usage-only.js";
|
|
35
|
+
import { fetchCompanyBillingSummary, pricingPlanFromBody, usageTrialActiveFromBody, } from "../lib/billing/usage-only.js";
|
|
28
36
|
export class UsageScopeError extends Error {
|
|
29
37
|
}
|
|
30
38
|
/** Pick the scope. Never reaches outside the caller's own memberships. */
|
|
@@ -51,13 +59,56 @@ export function resolveUsageScope(memberships, opts) {
|
|
|
51
59
|
memberships.map((m) => ` --company ${m.slug}`).join("\n") +
|
|
52
60
|
"\n --personal");
|
|
53
61
|
}
|
|
62
|
+
function asRecord(value) {
|
|
63
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
64
|
+
? value
|
|
65
|
+
: null;
|
|
66
|
+
}
|
|
67
|
+
/** The plan on a usage-limits body: `pricingPlan`, else the `usageOnly` / `onUsagePricing` boolean. */
|
|
68
|
+
export function pricingPlanFromUsageLimits(body) {
|
|
69
|
+
const plan = pricingPlanFromBody(body);
|
|
70
|
+
if (plan)
|
|
71
|
+
return plan;
|
|
72
|
+
const rec = asRecord(body);
|
|
73
|
+
const flag = typeof rec?.onUsagePricing === "boolean" ? rec.onUsagePricing : rec?.usageOnly;
|
|
74
|
+
if (typeof flag === "boolean")
|
|
75
|
+
return flag ? "usage-only" : "current";
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The plan on a billing summary body. A summary that answered without saying
|
|
80
|
+
* usage-only is a flat plan: the person summary carries `personalUsage` only
|
|
81
|
+
* for usage pricing, and a company summary from a server older than usage
|
|
82
|
+
* pricing has no `pricingPlan` at all.
|
|
83
|
+
*/
|
|
84
|
+
export function pricingPlanFromSummary(body) {
|
|
85
|
+
const rec = asRecord(body);
|
|
86
|
+
if (!rec)
|
|
87
|
+
return null;
|
|
88
|
+
// `plan: "usage"` only means "no Workforce subscription", not usage pricing,
|
|
89
|
+
// so it never turns pricing on. Without pricingPlan, show no cost.
|
|
90
|
+
return pricingPlanFromBody(rec) ?? "current";
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* True: price the meters. False: flat plan. Null: unknown, show no cost.
|
|
94
|
+
* When usage-limits answers, only its answer counts: the server computes it
|
|
95
|
+
* with `isOnUsagePricing`, the source of truth. The summary's plan is used
|
|
96
|
+
* only when usage-limits does not say.
|
|
97
|
+
*/
|
|
98
|
+
export function resolveOnUsagePricing(limitsPlan, summaryPlan) {
|
|
99
|
+
const plan = limitsPlan ?? summaryPlan;
|
|
100
|
+
if (plan === null)
|
|
101
|
+
return null;
|
|
102
|
+
return plan === "usage-only";
|
|
103
|
+
}
|
|
54
104
|
export async function runBillingUsage(opts, deps) {
|
|
55
105
|
const memberships = await deps.listCompanies();
|
|
56
106
|
const scope = resolveUsageScope(memberships, opts);
|
|
57
107
|
const membership = scope.kind === "company" ? memberships.find((m) => m.uid === scope.companyUid) : undefined;
|
|
108
|
+
let limitsBody = null;
|
|
58
109
|
const [usage, pricing] = await Promise.all([
|
|
59
110
|
readMonthUsage(scope, {
|
|
60
|
-
fetchUsageLimits: deps.fetchUsageLimits,
|
|
111
|
+
fetchUsageLimits: async (s) => (limitsBody = await deps.fetchUsageLimits(s)),
|
|
61
112
|
now: deps.now,
|
|
62
113
|
...(deps.readTrialActive ? { readTrialActive: deps.readTrialActive } : {}),
|
|
63
114
|
...(opts.storageScan !== false && deps.readStorageSplit && membership
|
|
@@ -66,7 +117,18 @@ export async function runBillingUsage(opts, deps) {
|
|
|
66
117
|
}),
|
|
67
118
|
deps.fetchPricing().catch(() => null),
|
|
68
119
|
]);
|
|
69
|
-
|
|
120
|
+
const limitsPlan = pricingPlanFromUsageLimits(limitsBody);
|
|
121
|
+
const summary = limitsPlan === null && deps.fetchBillingSummary
|
|
122
|
+
? await deps.fetchBillingSummary(scope).catch((err) => {
|
|
123
|
+
// stderr keeps --json output on stdout parseable.
|
|
124
|
+
console.error(`Could not read the billing summary: ${err instanceof Error ? err.message : String(err)}`);
|
|
125
|
+
return null;
|
|
126
|
+
})
|
|
127
|
+
: null;
|
|
128
|
+
const onUsagePricing = resolveOnUsagePricing(limitsPlan, pricingPlanFromSummary(summary));
|
|
129
|
+
if (onUsagePricing !== true)
|
|
130
|
+
return buildReadingsReport(usage, onUsagePricing);
|
|
131
|
+
return { ...buildReport(usage, ratesFromPricingStatement(pricing)), onUsagePricing: true };
|
|
70
132
|
}
|
|
71
133
|
// ── Production deps ─────────────────────────────────────────────────────────
|
|
72
134
|
function productionDeps() {
|
|
@@ -117,6 +179,15 @@ function productionDeps() {
|
|
|
117
179
|
const body = await fetchCompanyBillingSummary(await token(), scope.companyUid);
|
|
118
180
|
return body === null ? null : usageTrialActiveFromBody(body);
|
|
119
181
|
},
|
|
182
|
+
async fetchBillingSummary(scope) {
|
|
183
|
+
// Company: the same cached read as readTrialActive, so no second call.
|
|
184
|
+
if (scope.kind === "company")
|
|
185
|
+
return fetchCompanyBillingSummary(await token(), scope.companyUid);
|
|
186
|
+
const res = await vaultApiFetch({ token: await token(), path: "/v1/billing/person/summary" });
|
|
187
|
+
if (!res.ok)
|
|
188
|
+
return null;
|
|
189
|
+
return res.json().catch(() => null);
|
|
190
|
+
},
|
|
120
191
|
now: () => new Date(),
|
|
121
192
|
};
|
|
122
193
|
}
|
package/dist/commands/bot.js
CHANGED
|
@@ -475,7 +475,7 @@ export function resolveCreateKind(opts, worker) {
|
|
|
475
475
|
return { kind, companies: [] };
|
|
476
476
|
}
|
|
477
477
|
if (isSetup) {
|
|
478
|
-
throw Object.assign(new Error("HQ's
|
|
478
|
+
throw Object.assign(new Error("HQ's assistant is always a personal bot; drop --kind company."), { expected: true });
|
|
479
479
|
}
|
|
480
480
|
const companies = requested.length > 0 ? requested : normalizeBotCompanies(worker?.company ? [worker.company] : []);
|
|
481
481
|
if (companies.length === 0) {
|
|
@@ -638,7 +638,7 @@ async function runBotCreateLocked(name, opts) {
|
|
|
638
638
|
const { kind, companies } = resolveCreateKind(opts, worker);
|
|
639
639
|
const isSetupBot = name === RESERVED_SETUP_BOT_NAME && kind === "personal" && worker?.id === SETUP_BOT_WORKER_ID;
|
|
640
640
|
if (name === RESERVED_SETUP_BOT_NAME && kind === "personal" && !isSetupBot) {
|
|
641
|
-
throw Object.assign(new Error(`The name ${RESERVED_SETUP_BOT_NAME} is reserved for HQ's
|
|
641
|
+
throw Object.assign(new Error(`The name ${RESERVED_SETUP_BOT_NAME} is reserved for HQ's assistant; create it with --worker ${RESERVED_SETUP_BOT_NAME}.`), { expected: true });
|
|
642
642
|
}
|
|
643
643
|
const existing = readBotConfig(dir);
|
|
644
644
|
if (existing) {
|
|
@@ -12,5 +12,32 @@ export declare function describeChannel(c: ChannelSummary): string;
|
|
|
12
12
|
* fetched list) so the formatting is unit-testable without network/auth.
|
|
13
13
|
*/
|
|
14
14
|
export declare function formatChannelsList(channels: ChannelSummary[]): string;
|
|
15
|
+
/** Activity fields the server returns on each channel row. */
|
|
16
|
+
type ChannelWithActivity = ChannelSummary & {
|
|
17
|
+
lastActivityAt?: string;
|
|
18
|
+
lastMessageAt?: string;
|
|
19
|
+
lastHumanMessageAt?: string;
|
|
20
|
+
unreadCount?: number;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Latest activity time on a channel row, as epoch ms, or 0 when the channel
|
|
24
|
+
* has never had activity. lastActivityAt moves on new messages (work-session
|
|
25
|
+
* status cards do not move it); lastMessageAt and lastHumanMessageAt are read
|
|
26
|
+
* as fallbacks for rows that carry only those.
|
|
27
|
+
* Pure → unit-testable.
|
|
28
|
+
*/
|
|
29
|
+
export declare function channelActivityMs(c: ChannelWithActivity): number;
|
|
30
|
+
/**
|
|
31
|
+
* Parse `--active-since`: an ISO timestamp, or a duration back from now such
|
|
32
|
+
* as `30m`, `2h`, `1d`, `90s`. Returns epoch ms, or null when unreadable.
|
|
33
|
+
*/
|
|
34
|
+
export declare function parseActiveSince(value: string, now?: number): number | null;
|
|
35
|
+
/**
|
|
36
|
+
* Channels with activity at or after `sinceMs`, newest first. With no bound,
|
|
37
|
+
* every channel sorted newest first (never-active channels last, in their
|
|
38
|
+
* original order). Pure → unit-testable.
|
|
39
|
+
*/
|
|
40
|
+
export declare function channelsByActivity<T extends ChannelWithActivity>(channels: T[], sinceMs?: number): T[];
|
|
15
41
|
export declare function registerChannelsCommand(program: Command): void;
|
|
42
|
+
export {};
|
|
16
43
|
//# sourceMappingURL=channels.d.ts.map
|
|
@@ -67,7 +67,64 @@ function formatChannelMembers(members, json) {
|
|
|
67
67
|
.map((member) => `${member.displayName ?? ""}\t${member.personUid}`)
|
|
68
68
|
.join("\n");
|
|
69
69
|
}
|
|
70
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Latest activity time on a channel row, as epoch ms, or 0 when the channel
|
|
72
|
+
* has never had activity. lastActivityAt moves on new messages (work-session
|
|
73
|
+
* status cards do not move it); lastMessageAt and lastHumanMessageAt are read
|
|
74
|
+
* as fallbacks for rows that carry only those.
|
|
75
|
+
* Pure → unit-testable.
|
|
76
|
+
*/
|
|
77
|
+
export function channelActivityMs(c) {
|
|
78
|
+
let best = 0;
|
|
79
|
+
for (const v of [c.lastActivityAt, c.lastMessageAt, c.lastHumanMessageAt]) {
|
|
80
|
+
const ms = typeof v === "string" ? Date.parse(v) : NaN;
|
|
81
|
+
if (Number.isFinite(ms) && ms > best)
|
|
82
|
+
best = ms;
|
|
83
|
+
}
|
|
84
|
+
return best;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Parse `--active-since`: an ISO timestamp, or a duration back from now such
|
|
88
|
+
* as `30m`, `2h`, `1d`, `90s`. Returns epoch ms, or null when unreadable.
|
|
89
|
+
*/
|
|
90
|
+
export function parseActiveSince(value, now = Date.now()) {
|
|
91
|
+
const v = value.trim();
|
|
92
|
+
const rel = /^(\d+)\s*([smhd])$/i.exec(v);
|
|
93
|
+
if (rel) {
|
|
94
|
+
const unit = { s: 1_000, m: 60_000, h: 3_600_000, d: 86_400_000 }[rel[2].toLowerCase()];
|
|
95
|
+
return now - Number(rel[1]) * unit;
|
|
96
|
+
}
|
|
97
|
+
if (!/^\d{4}-\d{2}-\d{2}/.test(v))
|
|
98
|
+
return null;
|
|
99
|
+
const ms = Date.parse(v);
|
|
100
|
+
return Number.isFinite(ms) ? ms : null;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Channels with activity at or after `sinceMs`, newest first. With no bound,
|
|
104
|
+
* every channel sorted newest first (never-active channels last, in their
|
|
105
|
+
* original order). Pure → unit-testable.
|
|
106
|
+
*/
|
|
107
|
+
export function channelsByActivity(channels, sinceMs) {
|
|
108
|
+
return channels
|
|
109
|
+
.map((c, i) => ({ c, i, at: channelActivityMs(c) }))
|
|
110
|
+
.filter(({ at }) => sinceMs === undefined || (at > 0 && at >= sinceMs))
|
|
111
|
+
.sort((a, b) => b.at - a.at || a.i - b.i)
|
|
112
|
+
.map(({ c }) => c);
|
|
113
|
+
}
|
|
114
|
+
async function runChannelsList(opts = {}) {
|
|
115
|
+
let sinceMs;
|
|
116
|
+
if (opts.activeSince !== undefined) {
|
|
117
|
+
const parsed = parseActiveSince(opts.activeSince);
|
|
118
|
+
if (parsed === null) {
|
|
119
|
+
console.error(chalk.red(`--active-since must be an ISO time (2026-10-19T17:00:00Z) or a duration (30m, 2h, 1d); got '${opts.activeSince}'.`));
|
|
120
|
+
process.exit(2);
|
|
121
|
+
}
|
|
122
|
+
sinceMs = parsed;
|
|
123
|
+
}
|
|
124
|
+
if (opts.sort !== undefined && opts.sort !== "activity" && opts.sort !== "default") {
|
|
125
|
+
console.error(chalk.red(`--sort must be 'activity' or 'default'; got '${opts.sort}'.`));
|
|
126
|
+
process.exit(2);
|
|
127
|
+
}
|
|
71
128
|
try {
|
|
72
129
|
const token = await ensureCognitoToken();
|
|
73
130
|
const res = await vaultApiFetch({ token, path: "/v1/notify/channels" });
|
|
@@ -82,7 +139,44 @@ async function runChannelsList() {
|
|
|
82
139
|
process.exit(1);
|
|
83
140
|
}
|
|
84
141
|
const data = (await res.json());
|
|
85
|
-
|
|
142
|
+
let list = data.channels ?? [];
|
|
143
|
+
if (sinceMs !== undefined || opts.sort === "activity") {
|
|
144
|
+
list = channelsByActivity(list, sinceMs);
|
|
145
|
+
}
|
|
146
|
+
console.log(opts.json ? JSON.stringify(list, null, 2) : formatChannelsList(list));
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* `hq channels changes` — one call to the channel directory feed
|
|
155
|
+
* (GET /v1/notify/channels?cursor). No cursor, or an expired one, returns a
|
|
156
|
+
* full snapshot in `rows` with `snapshot: true` (or `reset: true`); a valid
|
|
157
|
+
* cursor returns only the channels that changed since it in `changed`, plus
|
|
158
|
+
* `removedChannelIds`. Every response carries the successor `cursor`
|
|
159
|
+
* (single-use, 15-minute TTL). Prints the response as JSON.
|
|
160
|
+
*/
|
|
161
|
+
async function runChannelsChanges(opts) {
|
|
162
|
+
try {
|
|
163
|
+
const token = await ensureCognitoToken();
|
|
164
|
+
const res = await vaultApiFetch({
|
|
165
|
+
token,
|
|
166
|
+
path: "/v1/notify/channels",
|
|
167
|
+
query: { cursor: opts.cursor ?? "" },
|
|
168
|
+
});
|
|
169
|
+
if (!res.ok) {
|
|
170
|
+
if (res.status === 401) {
|
|
171
|
+
console.error(chalk.red("Not authenticated — run `hq login` and try again."));
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
const err = (await res.json().catch(() => ({})));
|
|
175
|
+
console.error(chalk.red(`Could not read channel changes: ${err.error ?? err.message ?? res.statusText}`));
|
|
176
|
+
}
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
console.log(JSON.stringify(await res.json(), null, 2));
|
|
86
180
|
}
|
|
87
181
|
catch (err) {
|
|
88
182
|
console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
|
|
@@ -119,8 +213,18 @@ export function registerChannelsCommand(program) {
|
|
|
119
213
|
channels
|
|
120
214
|
.command("list", { isDefault: true })
|
|
121
215
|
.description("List your DM channels — named channels and group DMs.")
|
|
122
|
-
.
|
|
123
|
-
|
|
216
|
+
.option("--json", "Output the channel array as JSON, including activity fields.")
|
|
217
|
+
.option("--active-since <when>", "Only channels with activity at or after an ISO time or a duration back from now (30m, 2h, 1d); newest first.")
|
|
218
|
+
.option("--sort <order>", "'activity' lists the most recently active channels first.")
|
|
219
|
+
.action(async (opts) => {
|
|
220
|
+
await runChannelsList(opts);
|
|
221
|
+
});
|
|
222
|
+
channels
|
|
223
|
+
.command("changes")
|
|
224
|
+
.description("Print the channels that changed since a feed cursor, as JSON (no cursor → full snapshot).")
|
|
225
|
+
.option("--cursor <token>", "Cursor from the previous `hq channels changes` response.")
|
|
226
|
+
.action(async (opts) => {
|
|
227
|
+
await runChannelsChanges(opts);
|
|
124
228
|
});
|
|
125
229
|
channels
|
|
126
230
|
.command("members <channel>")
|
package/dist/commands/cloud.js
CHANGED
|
@@ -1074,7 +1074,8 @@ export function registerCloudCommands(program) {
|
|
|
1074
1074
|
}
|
|
1075
1075
|
}
|
|
1076
1076
|
else {
|
|
1077
|
-
|
|
1077
|
+
const skipSummary = formatPushSkipSummary(result);
|
|
1078
|
+
log(chalk.green(`\n✓ Pushed ${result.filesUploaded} file(s) (${formatBytes(result.bytesUploaded)}, ${skipSummary})`));
|
|
1078
1079
|
}
|
|
1079
1080
|
await syncHealth.succeeded();
|
|
1080
1081
|
const exitCode = scopeExcludedExitCode(result.filesExcludedByScope + staleRefusalCount);
|
|
@@ -2058,6 +2059,35 @@ function scopeLabel(slug) {
|
|
|
2058
2059
|
return "personal (companies/personal)";
|
|
2059
2060
|
return slug;
|
|
2060
2061
|
}
|
|
2062
|
+
function formatPushSkipSummary(result) {
|
|
2063
|
+
const detail = result.filesSkippedDetail;
|
|
2064
|
+
if (!detail)
|
|
2065
|
+
return `${result.filesSkipped} skipped`;
|
|
2066
|
+
const parts = [];
|
|
2067
|
+
if (detail.upToDate > 0)
|
|
2068
|
+
parts.push(`${detail.upToDate} already up to date`);
|
|
2069
|
+
const otherTotal = detail.conflict + detail.brokenLink + detail.other;
|
|
2070
|
+
if (otherTotal > 0) {
|
|
2071
|
+
const named = [];
|
|
2072
|
+
if (detail.conflict > 0) {
|
|
2073
|
+
named.push(`${detail.conflict} conflict${detail.conflict === 1 ? "" : "s"}`);
|
|
2074
|
+
}
|
|
2075
|
+
if (detail.brokenLink > 0) {
|
|
2076
|
+
named.push(`${detail.brokenLink} broken link${detail.brokenLink === 1 ? "" : "s"}`);
|
|
2077
|
+
}
|
|
2078
|
+
if (detail.other > 0)
|
|
2079
|
+
named.push(`${detail.other} other`);
|
|
2080
|
+
if (detail.conflict > 0 || detail.brokenLink > 0) {
|
|
2081
|
+
parts.push(`${otherTotal} skipped (${named.join(", ")})`);
|
|
2082
|
+
}
|
|
2083
|
+
else {
|
|
2084
|
+
parts.push(`${otherTotal} skipped`);
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
if (parts.length === 0)
|
|
2088
|
+
return `${result.filesSkipped} skipped`;
|
|
2089
|
+
return parts.join(", ");
|
|
2090
|
+
}
|
|
2061
2091
|
function formatBytes(bytes) {
|
|
2062
2092
|
if (bytes === 0)
|
|
2063
2093
|
return "0 B";
|
|
@@ -7,6 +7,7 @@ export type HqResult = {
|
|
|
7
7
|
stdout: string;
|
|
8
8
|
stderr: string;
|
|
9
9
|
missing?: boolean;
|
|
10
|
+
timedOut?: boolean;
|
|
10
11
|
};
|
|
11
12
|
export type RefreshVaultAccessDependencies = {
|
|
12
13
|
cwd: string;
|
|
@@ -14,8 +15,40 @@ export type RefreshVaultAccessDependencies = {
|
|
|
14
15
|
now: () => Date;
|
|
15
16
|
hqAvailable: boolean;
|
|
16
17
|
runHq?: (args: string[], cwd: string) => HqResult;
|
|
18
|
+
/** How long to wait for another refresh to release the manifest lock. */
|
|
19
|
+
lockWaitMs?: number;
|
|
17
20
|
};
|
|
21
|
+
/**
|
|
22
|
+
* Per-call ceiling for each child `hq` call. SessionStart runs this helper, so
|
|
23
|
+
* a stalled vault request must not outlive the session that started it.
|
|
24
|
+
*/
|
|
25
|
+
export declare const DEFAULT_HQ_CALL_TIMEOUT_MS = 30000;
|
|
26
|
+
/**
|
|
27
|
+
* Run one child `hq` call with a timeout and with browser sign-in disabled.
|
|
28
|
+
* Without `HQ_NO_BROWSER_LOGIN` (read by cognito-session's
|
|
29
|
+
* resolveInteractiveLogin) a signed-out child opens a browser login and
|
|
30
|
+
* waits on it indefinitely. On Windows the call runs under a supervisor so a
|
|
31
|
+
* timeout ends the cmd.exe shim and every process under it.
|
|
32
|
+
*/
|
|
33
|
+
/** Windows npm shims run hq under cmd.exe, so only there does a plain kill leave the real hq running. */
|
|
34
|
+
export declare function usesTreeKillByDefault(platform?: NodeJS.Platform): boolean;
|
|
35
|
+
export declare function runHqChild(args: string[], cwd: string, env: NodeJS.ProcessEnv, killTree?: boolean): HqResult;
|
|
18
36
|
export declare function createRefreshVaultAccessDependencies(cwd: string, env: NodeJS.ProcessEnv, now: () => Date): RefreshVaultAccessDependencies;
|
|
19
37
|
export declare function runRefreshVaultAccess(args: string[], deps: RefreshVaultAccessDependencies): UtilityOutput;
|
|
38
|
+
/** Test seam: runs after a waiter has judged the lock stale and before it takes the lock over. */
|
|
39
|
+
export interface ManifestLockHooks {
|
|
40
|
+
afterStaleCheck?: () => void;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Take the manifest lock and return its owner token, or undefined when the
|
|
44
|
+
* lock stays busy past `waitMs`. The lock file is created by hard-linking a
|
|
45
|
+
* file that already holds the token, so it never exists without an owner. A
|
|
46
|
+
* stale lock (older than LOCK_STALE_MS, or whose holder on this host has
|
|
47
|
+
* exited) is taken over through removeLockIf, so two waiters that both judge
|
|
48
|
+
* it stale cannot remove each other's new lock.
|
|
49
|
+
*/
|
|
50
|
+
export declare function acquireManifestLock(lock: string, waitMs: number, hooks?: ManifestLockHooks): string | undefined;
|
|
51
|
+
/** Release the lock if `token` still owns it. Returns false when another process has taken it over. */
|
|
52
|
+
export declare function releaseManifestLock(lock: string, token: string): boolean;
|
|
20
53
|
export declare function registerCoreRefreshVaultAccessNative(core: Command): void;
|
|
21
54
|
//# sourceMappingURL=core-refresh-vault-access-native.d.ts.map
|