@indigoai-us/hq-cli 5.345.156 → 5.345.160
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 +255 -0
- package/assets/scaffold/core/scripts/rebuild-company-knowledge-index.sh +110 -2
- package/assets/scaffold/core/scripts/rebuild-projects-index.sh +101 -5
- package/assets/scaffold/core/scripts/worktree.sh +4 -0
- package/dist/command-catalog.generated.d.ts +535 -10
- package/dist/command-catalog.generated.js +678 -31
- package/dist/command-registration-plan.d.ts +18 -0
- package/dist/command-registration-plan.js +4 -0
- package/dist/commands/_patterns.d.ts +17 -0
- package/dist/commands/_patterns.js +22 -0
- package/dist/commands/access.d.ts +6 -0
- package/dist/commands/access.js +18 -1
- package/dist/commands/acl-lock.d.ts +41 -0
- package/dist/commands/acl-lock.js +28 -0
- package/dist/commands/agent-enroll.d.ts +2 -0
- package/dist/commands/agent-enroll.js +9 -4
- package/dist/commands/agent-kit.js +8 -6
- package/dist/commands/agents.js +5 -11
- package/dist/commands/billing-sponsor.d.ts +4 -4
- package/dist/commands/billing-sponsor.js +11 -11
- package/dist/commands/billing-usage.d.ts +22 -8
- package/dist/commands/billing-usage.js +57 -22
- package/dist/commands/billing.d.ts +7 -7
- package/dist/commands/billing.js +13 -13
- package/dist/commands/bot.js +7 -4
- package/dist/commands/channel-suggest.d.ts +56 -0
- package/dist/commands/channel-suggest.js +170 -0
- package/dist/commands/channels.js +7 -1
- package/dist/commands/cloud-provision.js +2 -2
- package/dist/commands/cloud.d.ts +45 -0
- package/dist/commands/cloud.js +157 -17
- package/dist/commands/company.js +4 -3
- package/dist/commands/core-checkpoint-native.d.ts +18 -0
- package/dist/commands/core-checkpoint-native.js +67 -2
- package/dist/commands/core-pipeline-driver-native.d.ts +12 -1
- package/dist/commands/core-pipeline-driver-native.js +131 -5
- package/dist/commands/core.js +12 -0
- package/dist/commands/crm.js +83 -6
- package/dist/commands/db-remote-deps.js +12 -1
- package/dist/commands/deploy-access.d.ts +33 -0
- package/dist/commands/deploy-access.js +322 -0
- package/dist/commands/dm-attach.d.ts +106 -0
- package/dist/commands/dm-attach.js +184 -0
- package/dist/commands/dm.d.ts +37 -0
- package/dist/commands/dm.js +172 -10
- package/dist/commands/feedback-show.d.ts +40 -0
- package/dist/commands/feedback-show.js +261 -0
- package/dist/commands/feedback.d.ts +21 -0
- package/dist/commands/feedback.js +152 -2
- package/dist/commands/files.d.ts +24 -0
- package/dist/commands/files.js +71 -8
- package/dist/commands/integrations-connect.js +7 -3
- package/dist/commands/lanes-accounts.js +16 -1
- package/dist/commands/lanes-loop.js +14 -4
- package/dist/commands/lanes-message.js +6 -4
- package/dist/commands/lanes-resume.js +36 -13
- package/dist/commands/lanes-shared.d.ts +5 -0
- package/dist/commands/lanes-shared.js +13 -3
- package/dist/commands/lanes-watch.js +1 -0
- package/dist/commands/lanes.d.ts +20 -1
- package/dist/commands/lanes.js +201 -51
- package/dist/commands/meetings.d.ts +4 -4
- package/dist/commands/meetings.js +17 -8
- package/dist/commands/mesh.js +149 -1
- package/dist/commands/monitor.d.ts +1 -0
- package/dist/commands/monitor.js +4 -1
- package/dist/commands/repo.d.ts +41 -0
- package/dist/commands/repo.js +331 -0
- package/dist/commands/scan.d.ts +3 -1
- package/dist/commands/scan.js +7 -3
- package/dist/commands/secrets.d.ts +21 -0
- package/dist/commands/secrets.js +114 -14
- package/dist/commands/signals.js +17 -5
- package/dist/commands/skill.js +6 -6
- package/dist/commands/staff/listings.d.ts +41 -0
- package/dist/commands/staff/listings.js +160 -0
- package/dist/commands/staff/reset-test-account.d.ts +3 -2
- package/dist/commands/staff/reset-test-account.js +4 -3
- package/dist/commands/whoami.js +1 -1
- package/dist/hook-engine-path.js +13 -2
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +3392 -1243
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/lib/agent-kit/run/inbox.js +2 -1
- package/dist/lib/agent-kit/wake-secrets.d.ts +7 -0
- package/dist/lib/agent-kit/wake-secrets.js +32 -0
- package/dist/lib/agent-kit/wake.d.ts +4 -0
- package/dist/lib/agent-kit/wake.js +49 -3
- package/dist/lib/billing/plan-limit-refusals.d.ts +13 -3
- package/dist/lib/billing/plan-limit-refusals.js +26 -3
- package/dist/lib/billing/plan-limit-upgrade-url.d.ts +8 -0
- package/dist/lib/billing/plan-limit-upgrade-url.js +18 -0
- package/dist/lib/billing/plan-lock.d.ts +22 -6
- package/dist/lib/billing/plan-lock.js +58 -30
- package/dist/lib/billing/usage-cost.d.ts +24 -2
- package/dist/lib/billing/usage-cost.js +103 -3
- package/dist/lib/billing/usage-only.d.ts +37 -1
- package/dist/lib/billing/usage-only.js +89 -4
- package/dist/lib/billing/usage-pause.d.ts +27 -0
- package/dist/lib/billing/usage-pause.js +50 -0
- package/dist/lib/bot/inflight.d.ts +7 -1
- package/dist/lib/bot/inflight.js +10 -2
- package/dist/lib/bot/prompt.js +3 -0
- package/dist/lib/bot/run.js +72 -20
- package/dist/lib/bot/session.d.ts +14 -0
- package/dist/lib/bot/session.js +70 -0
- package/dist/lib/capacity/collectors/claude-oauth.d.ts +6 -0
- package/dist/lib/capacity/collectors/claude-oauth.js +12 -0
- package/dist/lib/capacity/lane-login.d.ts +6 -0
- package/dist/lib/capacity/lane-login.js +5 -0
- package/dist/lib/core-utils/worktree.js +2 -0
- package/dist/lib/daemon/exit-cause.d.ts +43 -0
- package/dist/lib/daemon/exit-cause.js +88 -0
- package/dist/lib/daemon/handoff.d.ts +2 -0
- package/dist/lib/daemon/handoff.js +6 -1
- package/dist/lib/daemon/hq-child-bootstrap.mjs +20 -3
- package/dist/lib/daemon/paths.d.ts +2 -0
- package/dist/lib/daemon/paths.js +1 -0
- package/dist/lib/daemon/run.js +13 -0
- package/dist/lib/daemon/supervisor.d.ts +54 -1
- package/dist/lib/daemon/supervisor.js +297 -21
- package/dist/lib/daemon/unit-holds.d.ts +22 -0
- package/dist/lib/daemon/unit-holds.js +86 -0
- package/dist/lib/db/control-plane.d.ts +5 -0
- package/dist/lib/db/control-plane.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/hook-engine/resolve.d.ts +14 -0
- package/dist/lib/hook-engine/resolve.js +55 -1
- package/dist/lib/index-render/company-knowledge.js +1 -3
- package/dist/lib/index-render/projects.js +1 -3
- package/dist/lib/lanes/account-company.d.ts +28 -0
- package/dist/lib/lanes/account-company.js +53 -0
- package/dist/lib/lanes/accounts.d.ts +22 -3
- package/dist/lib/lanes/accounts.js +38 -2
- package/dist/lib/lanes/claude-balance.d.ts +6 -1
- package/dist/lib/lanes/claude-balance.js +6 -1
- package/dist/lib/lanes/claude-credential.d.ts +78 -0
- package/dist/lib/lanes/claude-credential.js +163 -0
- package/dist/lib/lanes/compaction.d.ts +2 -0
- package/dist/lib/lanes/compaction.js +9 -0
- package/dist/lib/lanes/config.js +6 -0
- package/dist/lib/lanes/envelope-repair-dispatch.js +2 -1
- package/dist/lib/lanes/hooks-install.js +2 -1
- package/dist/lib/lanes/host-capacity.d.ts +2 -0
- package/dist/lib/lanes/host-capacity.js +1 -0
- package/dist/lib/lanes/keep-alive.d.ts +1 -1
- package/dist/lib/lanes/keep-alive.js +3 -1
- package/dist/lib/lanes/links.js +10 -4
- package/dist/lib/lanes/loop.js +16 -1
- package/dist/lib/lanes/node-binary.d.ts +17 -0
- package/dist/lib/lanes/node-binary.js +30 -0
- package/dist/lib/lanes/outpost-dispatch.d.ts +2 -1
- package/dist/lib/lanes/outpost-dispatch.js +1 -0
- package/dist/lib/lanes/reconcile.js +4 -0
- package/dist/lib/lanes/remote-control-lane.js +2 -0
- package/dist/lib/lanes/spawn.d.ts +3 -0
- package/dist/lib/lanes/spawn.js +119 -9
- package/dist/lib/lanes/store.d.ts +6 -0
- package/dist/lib/lanes/store.js +107 -18
- package/dist/lib/lanes/types.d.ts +2 -0
- package/dist/lib/lanes/watch.js +28 -5
- package/dist/lib/lanes/watcher-main.js +4 -3
- package/dist/lib/lanes/watcher.d.ts +4 -0
- package/dist/lib/lanes/watcher.js +110 -21
- package/dist/lib/mesh/api.d.ts +32 -0
- package/dist/lib/mesh/api.js +75 -0
- package/dist/lib/plan-limit-nag.d.ts +0 -16
- package/dist/lib/plan-limit-nag.js +17 -18
- package/dist/lib/secret-scan/detect.js +9 -0
- package/dist/lib/secret-scan/repo-config.d.ts +41 -0
- package/dist/lib/secret-scan/repo-config.js +237 -0
- package/dist/lib/secret-scan/rules.d.ts +7 -1
- package/dist/lib/secret-scan/rules.js +3 -1
- package/dist/lib/secret-scan/staged.d.ts +5 -0
- package/dist/lib/secret-scan/staged.js +19 -2
- package/dist/lib/secret-scan/types.d.ts +2 -0
- package/dist/lib/sync/server-owned-local-only.d.ts +29 -0
- package/dist/lib/sync/server-owned-local-only.js +75 -0
- package/dist/main.d.ts +0 -2
- package/dist/main.js +19 -57
- package/dist/utils/package-use-lease.js +27 -9
- package/dist/utils/plan-gate-error.d.ts +30 -14
- package/dist/utils/plan-gate-error.js +60 -46
- package/dist/utils/team-upgrade.d.ts +19 -6
- package/dist/utils/team-upgrade.js +37 -39
- package/dist/utils/version-gate.d.ts +7 -0
- package/dist/utils/version-gate.js +66 -0
- package/package.json +8 -8
|
@@ -12,27 +12,27 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Tenant boundary: the company comes from the caller's own active memberships
|
|
14
14
|
* (`/membership/me`); a slug outside them is refused before any read. The
|
|
15
|
-
*
|
|
15
|
+
* company statement is owner-gated on the server as well. With no cloud
|
|
16
16
|
* company (or `--personal`) it reads the caller's personal scope.
|
|
17
17
|
*
|
|
18
|
-
* Usage numbers come through `readMonthUsage` (src/lib/billing/usage-cost.ts)
|
|
19
|
-
* the
|
|
18
|
+
* Usage numbers come through `readMonthUsage` (src/lib/billing/usage-cost.ts)
|
|
19
|
+
* from the usage statement, `GET /v1/billing/usage` (hq-pro US-006). Only a
|
|
20
|
+
* server without that route (404) falls back to the usage-limits readings.
|
|
20
21
|
*
|
|
21
22
|
* Only an account on usage pricing gets costs. The plan comes from the
|
|
22
|
-
* server
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* server says nothing about get readings only, with no rate, cost or
|
|
23
|
+
* server's shared plan status (`billingState`, hq-pro #5088) on the billing
|
|
24
|
+
* summary, with the legacy `pricingPlan` / `usageOnly` fields for a server
|
|
25
|
+
* that does not send it. An account on an earlier flat plan and an account
|
|
26
|
+
* the server says nothing about get readings only, with no rate, cost or
|
|
27
27
|
* projected bill.
|
|
28
28
|
*/
|
|
29
29
|
import chalk from "chalk";
|
|
30
30
|
import { ensureCognitoToken } from "../utils/cognito-session.js";
|
|
31
31
|
import { vaultApiFetch, vaultApiFetchPublic } from "../utils/vault-api.js";
|
|
32
|
-
import { buildReadingsReport, buildReport, formatReport, readMonthUsage, } from "../lib/billing/usage-cost.js";
|
|
32
|
+
import { buildReadingsReport, buildReport, formatReport, readMonthUsage, usageLimitsSource, usageStatementSource, } from "../lib/billing/usage-cost.js";
|
|
33
33
|
import { ratesFromPricingStatement } from "../lib/billing/usage-rate-card.js";
|
|
34
34
|
import { fetchUsageCard, formatUsageCardLines } from "../lib/billing/usage-card.js";
|
|
35
|
-
import { fetchCompanyBillingSummary, pricingPlanFromBody, usageTrialActiveFromBody, } from "../lib/billing/usage-only.js";
|
|
35
|
+
import { fetchCompanyBillingSummary, pricingPlanFromBody, hasBillingStateField, usageTrialActiveFromBody, } from "../lib/billing/usage-only.js";
|
|
36
36
|
export class UsageScopeError extends Error {
|
|
37
37
|
}
|
|
38
38
|
/** Pick the scope. Never reaches outside the caller's own memberships. */
|
|
@@ -67,7 +67,7 @@ function asRecord(value) {
|
|
|
67
67
|
/** The plan on a usage-limits body: `pricingPlan`, else the `usageOnly` / `onUsagePricing` boolean. */
|
|
68
68
|
export function pricingPlanFromUsageLimits(body) {
|
|
69
69
|
const plan = pricingPlanFromBody(body);
|
|
70
|
-
if (plan)
|
|
70
|
+
if (plan || hasBillingStateField(body))
|
|
71
71
|
return plan;
|
|
72
72
|
const rec = asRecord(body);
|
|
73
73
|
const flag = typeof rec?.onUsagePricing === "boolean" ? rec.onUsagePricing : rec?.usageOnly;
|
|
@@ -85,9 +85,12 @@ export function pricingPlanFromSummary(body) {
|
|
|
85
85
|
const rec = asRecord(body);
|
|
86
86
|
if (!rec)
|
|
87
87
|
return null;
|
|
88
|
-
// `plan: "usage"` only means "no
|
|
89
|
-
// so it never turns pricing on.
|
|
90
|
-
|
|
88
|
+
// `plan: "usage"` only means "no flat-plan subscription", not usage pricing,
|
|
89
|
+
// so it never turns pricing on. A server that sends billingState answers
|
|
90
|
+
// from it alone (unknown stays unknown); an older one without pricingPlan
|
|
91
|
+
// shows no cost.
|
|
92
|
+
const plan = pricingPlanFromBody(rec);
|
|
93
|
+
return hasBillingStateField(rec) ? plan : plan ?? "current";
|
|
91
94
|
}
|
|
92
95
|
/**
|
|
93
96
|
* True: price the meters. False: flat plan. Null: unknown, show no cost.
|
|
@@ -101,20 +104,44 @@ export function resolveOnUsagePricing(limitsPlan, summaryPlan) {
|
|
|
101
104
|
return null;
|
|
102
105
|
return plan === "usage-only";
|
|
103
106
|
}
|
|
107
|
+
/** Thrown when the company statement is refused to a caller who is not its owner. */
|
|
108
|
+
export class UsageStatementForbiddenError extends Error {
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Read the statement, or say the caller should use the legacy readings: null
|
|
112
|
+
* means the server has no statement route (404) or no reader was given.
|
|
113
|
+
*/
|
|
114
|
+
async function readStatementBody(scope, deps) {
|
|
115
|
+
if (!deps.fetchUsageStatement)
|
|
116
|
+
return null;
|
|
117
|
+
const res = await deps.fetchUsageStatement(scope);
|
|
118
|
+
if (res.status === 404)
|
|
119
|
+
return null;
|
|
120
|
+
if (res.status === 403 && scope.kind === "company") {
|
|
121
|
+
throw new UsageStatementForbiddenError(`Only the owner of '${scope.companySlug}' can see its usage statement. ` +
|
|
122
|
+
"Run `hq billing usage --personal` for your own usage.");
|
|
123
|
+
}
|
|
124
|
+
return { body: res.status >= 200 && res.status < 300 ? res.body : null };
|
|
125
|
+
}
|
|
104
126
|
export async function runBillingUsage(opts, deps) {
|
|
105
127
|
const memberships = await deps.listCompanies();
|
|
106
128
|
const scope = resolveUsageScope(memberships, opts);
|
|
107
129
|
const membership = scope.kind === "company" ? memberships.find((m) => m.uid === scope.companyUid) : undefined;
|
|
108
130
|
let limitsBody = null;
|
|
131
|
+
const statement = await readStatementBody(scope, deps);
|
|
132
|
+
const usageDeps = {
|
|
133
|
+
fetchUsageLimits: async (s) => (limitsBody = await deps.fetchUsageLimits(s)),
|
|
134
|
+
now: deps.now,
|
|
135
|
+
...(deps.readTrialActive ? { readTrialActive: deps.readTrialActive } : {}),
|
|
136
|
+
...(opts.storageScan !== false && deps.readStorageSplit && membership
|
|
137
|
+
? { readStorageSplit: () => deps.readStorageSplit(membership, { fresh: opts.fresh === true }) }
|
|
138
|
+
: {}),
|
|
139
|
+
};
|
|
140
|
+
const source = statement
|
|
141
|
+
? usageStatementSource(async () => statement.body)
|
|
142
|
+
: usageLimitsSource(usageDeps);
|
|
109
143
|
const [usage, pricing] = await Promise.all([
|
|
110
|
-
readMonthUsage(scope,
|
|
111
|
-
fetchUsageLimits: async (s) => (limitsBody = await deps.fetchUsageLimits(s)),
|
|
112
|
-
now: deps.now,
|
|
113
|
-
...(deps.readTrialActive ? { readTrialActive: deps.readTrialActive } : {}),
|
|
114
|
-
...(opts.storageScan !== false && deps.readStorageSplit && membership
|
|
115
|
-
? { readStorageSplit: () => deps.readStorageSplit(membership, { fresh: opts.fresh === true }) }
|
|
116
|
-
: {}),
|
|
117
|
-
}),
|
|
144
|
+
readMonthUsage(scope, usageDeps, source),
|
|
118
145
|
deps.fetchPricing().catch(() => null),
|
|
119
146
|
]);
|
|
120
147
|
const limitsPlan = pricingPlanFromUsageLimits(limitsBody);
|
|
@@ -142,6 +169,14 @@ function productionDeps() {
|
|
|
142
169
|
const { companiesFromMemberships } = await import("./storage.js");
|
|
143
170
|
return companiesFromMemberships(await res.json());
|
|
144
171
|
},
|
|
172
|
+
async fetchUsageStatement(scope) {
|
|
173
|
+
const res = await vaultApiFetch(scope.kind === "company"
|
|
174
|
+
? { token: await token(), path: "/v1/billing/usage", query: { companyUid: scope.companyUid } }
|
|
175
|
+
: { token: await token(), path: "/v1/billing/usage", query: { scope: "person" } });
|
|
176
|
+
if (!res.ok)
|
|
177
|
+
return { status: res.status, body: null };
|
|
178
|
+
return { status: res.status, body: await res.json().catch(() => null) };
|
|
179
|
+
},
|
|
145
180
|
async fetchUsageLimits(scope) {
|
|
146
181
|
const res = await vaultApiFetch(scope.kind === "company"
|
|
147
182
|
? { token: await token(), path: "/v1/billing/usage-limits", query: { companyUid: scope.companyUid } }
|
|
@@ -13,16 +13,16 @@
|
|
|
13
13
|
* hq billing checkout [--company <slug>] [--personal] — mint a card-capture link
|
|
14
14
|
* hq billing usage [--company <slug>] [--personal] — usage, projected bill,
|
|
15
15
|
* drivers (read-only)
|
|
16
|
-
* hq billing upgrade [--company <slug>] —
|
|
17
|
-
* (
|
|
16
|
+
* hq billing upgrade [--company <slug>] — the switch to usage
|
|
17
|
+
* pricing (earlier plans)
|
|
18
18
|
* hq billing sponsor … — ask a company to cover
|
|
19
|
-
* your
|
|
19
|
+
* your personal plan, or
|
|
20
20
|
* answer such a request
|
|
21
21
|
*
|
|
22
|
-
* Usage pricing (US-012): a company
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* checkout
|
|
22
|
+
* Usage pricing (US-012, US-024): a company on usage pricing has no plan to
|
|
23
|
+
* upgrade to; `hq billing upgrade` tells it so and points at
|
|
24
|
+
* `hq billing usage`. Every other company is pointed at the switch to usage
|
|
25
|
+
* pricing: the flat-plan checkout is closed, so the CLI never opens it.
|
|
26
26
|
*/
|
|
27
27
|
import { Command } from "commander";
|
|
28
28
|
interface DatabaseBilling {
|
package/dist/commands/billing.js
CHANGED
|
@@ -13,22 +13,22 @@
|
|
|
13
13
|
* hq billing checkout [--company <slug>] [--personal] — mint a card-capture link
|
|
14
14
|
* hq billing usage [--company <slug>] [--personal] — usage, projected bill,
|
|
15
15
|
* drivers (read-only)
|
|
16
|
-
* hq billing upgrade [--company <slug>] —
|
|
17
|
-
* (
|
|
16
|
+
* hq billing upgrade [--company <slug>] — the switch to usage
|
|
17
|
+
* pricing (earlier plans)
|
|
18
18
|
* hq billing sponsor … — ask a company to cover
|
|
19
|
-
* your
|
|
19
|
+
* your personal plan, or
|
|
20
20
|
* answer such a request
|
|
21
21
|
*
|
|
22
|
-
* Usage pricing (US-012): a company
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* checkout
|
|
22
|
+
* Usage pricing (US-012, US-024): a company on usage pricing has no plan to
|
|
23
|
+
* upgrade to; `hq billing upgrade` tells it so and points at
|
|
24
|
+
* `hq billing usage`. Every other company is pointed at the switch to usage
|
|
25
|
+
* pricing: the flat-plan checkout is closed, so the CLI never opens it.
|
|
26
26
|
*/
|
|
27
27
|
import chalk from "chalk";
|
|
28
28
|
import { ensureCognitoToken } from "../utils/cognito-session.js";
|
|
29
29
|
import { vaultApiFetch, getCompanyUid } from "../utils/vault-api.js";
|
|
30
30
|
import { mintPaymentLink } from "../utils/billing-gate.js";
|
|
31
|
-
import {
|
|
31
|
+
import { openUsageSwitch } from "../utils/team-upgrade.js";
|
|
32
32
|
import { registerBillingSponsorCommands } from "./billing-sponsor.js";
|
|
33
33
|
import { fetchCompanyPricingPlan, isUsageOnly, usageOnlyUpgradeLines, } from "../lib/billing/usage-only.js";
|
|
34
34
|
import { fetchUsageCard } from "../lib/billing/usage-card.js";
|
|
@@ -157,10 +157,10 @@ export function registerBillingCommand(program) {
|
|
|
157
157
|
});
|
|
158
158
|
billing
|
|
159
159
|
.command("upgrade")
|
|
160
|
-
.description("Open
|
|
160
|
+
.description("Open the switch to usage pricing for a company on an earlier plan (usage-pricing companies have nothing to change)")
|
|
161
161
|
.option("--company <slug>", "Company slug (resolves to companyUid)")
|
|
162
|
-
.option("--no-browser", "Print the
|
|
163
|
-
.option("--json", "Emit the
|
|
162
|
+
.option("--no-browser", "Print the URL instead of opening it")
|
|
163
|
+
.option("--json", "Emit the URL as JSON")
|
|
164
164
|
.action(async function (opts) {
|
|
165
165
|
try {
|
|
166
166
|
const token = await ensureCognitoToken();
|
|
@@ -177,8 +177,8 @@ export function registerBillingCommand(program) {
|
|
|
177
177
|
console.log(line);
|
|
178
178
|
return;
|
|
179
179
|
}
|
|
180
|
-
await
|
|
181
|
-
|
|
180
|
+
await openUsageSwitch({
|
|
181
|
+
companySlug: companyOf(this),
|
|
182
182
|
noBrowser: opts.browser === false,
|
|
183
183
|
json: opts.json,
|
|
184
184
|
});
|
package/dist/commands/bot.js
CHANGED
|
@@ -44,7 +44,7 @@ import { DEFAULT_COGNITO, ensureCognitoToken, resolveDefaultHqRoot, } from "../u
|
|
|
44
44
|
import { peekHqApiKey } from "../utils/resolve-vault-credential.js";
|
|
45
45
|
import { getCompanyUid, resolveCallerPersonUid } from "../utils/vault-api.js";
|
|
46
46
|
import { resolveHqBinary } from "../lib/mesh/live/daemon/install.js";
|
|
47
|
-
import { askDaemonToStart, askDaemonToStop, daemonOwnsProcess, daemonSupervises, daemonWillSupervise, handOffInstallHere, } from "../lib/daemon/handoff.js";
|
|
47
|
+
import { askDaemonToStart, askDaemonToStop, daemonOwnsProcess, daemonHoldReason, daemonSupervises, daemonWillSupervise, handOffInstallHere, } from "../lib/daemon/handoff.js";
|
|
48
48
|
import { daemonPaths } from "../lib/daemon/paths.js";
|
|
49
49
|
import { launchdLabel } from "../lib/bot/paths.js";
|
|
50
50
|
import { isAncestorProcess } from "../lib/bot/self-process.js";
|
|
@@ -286,9 +286,11 @@ async function startBot(name, dir, config, output, extra = {}) {
|
|
|
286
286
|
const d = botDaemonStatus(paths, daemonDeps());
|
|
287
287
|
const hqDaemon = daemonPaths();
|
|
288
288
|
let how;
|
|
289
|
+
let supervised = false;
|
|
289
290
|
// A bot created since the daemon last read its plan is the daemon's too,
|
|
290
291
|
// unless its own LaunchAgent runs it.
|
|
291
292
|
if (daemonSupervises(hqDaemon, `bots:${name}`) || (!d.installed && daemonWillSupervise(hqDaemon, `bots:${name}`))) {
|
|
293
|
+
supervised = true;
|
|
292
294
|
// `hq daemon` runs this bot; starting a copy here would run it twice.
|
|
293
295
|
const before = readBotStatus(dir);
|
|
294
296
|
if (before && isPidAlive(before.pid) && before.state === "running") {
|
|
@@ -322,18 +324,19 @@ async function startBot(name, dir, config, output, extra = {}) {
|
|
|
322
324
|
return !!s && s.state === "running" && isPidAlive(s.pid);
|
|
323
325
|
}, 15_000);
|
|
324
326
|
const status = readBotStatus(dir);
|
|
327
|
+
const lastError = (!running && supervised ? daemonHoldReason(hqDaemon, `bots:${name}`) : null) ?? status?.lastError ?? null;
|
|
325
328
|
if (output === "silent")
|
|
326
329
|
return { running, how };
|
|
327
330
|
if (output === "json") {
|
|
328
|
-
console.log(JSON.stringify({ ok: running, name, ...extra, running, how, state: status?.state ?? "unknown", pid: status?.pid ?? null, lastError
|
|
331
|
+
console.log(JSON.stringify({ ok: running, name, ...extra, running, how, state: status?.state ?? "unknown", pid: status?.pid ?? null, lastError }, null, 2));
|
|
329
332
|
return { running, how };
|
|
330
333
|
}
|
|
331
334
|
if (running)
|
|
332
335
|
console.log(chalk.green(`${name} is running`) + chalk.dim(` (${how})`));
|
|
333
336
|
else {
|
|
334
337
|
console.log(chalk.yellow(`${name} did not report running within 15s`) + chalk.dim(` (${how})`));
|
|
335
|
-
if (
|
|
336
|
-
console.log(chalk.dim(` last error: ${
|
|
338
|
+
if (lastError)
|
|
339
|
+
console.log(chalk.dim(` last error: ${lastError}`));
|
|
337
340
|
console.log(chalk.dim(` logs: hq bot logs ${name}`));
|
|
338
341
|
}
|
|
339
342
|
return { running, how };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Near-match suggestions for a channel name that resolved to nothing.
|
|
3
|
+
*
|
|
4
|
+
* `hq dm channel <name>`, `hq dm <channel> "msg"`, `hq channels members` and
|
|
5
|
+
* `hq files share --channel` all resolve a channel by exact slug or display
|
|
6
|
+
* name. When that misses, the user usually typed something close to a real
|
|
7
|
+
* channel (for example "desktop-agent-coordination" for the project channel
|
|
8
|
+
* "project-desktop-agent-creation-1331ed7f"). This module ranks the caller's
|
|
9
|
+
* channels against the typed name so the error can list the closest few.
|
|
10
|
+
*
|
|
11
|
+
* Pure: no I/O, so it is unit-testable.
|
|
12
|
+
*/
|
|
13
|
+
/** The fields the ranker reads. Structurally compatible with ChannelSummary. */
|
|
14
|
+
export interface SuggestableChannel {
|
|
15
|
+
channelId: string;
|
|
16
|
+
name?: string;
|
|
17
|
+
slug?: string;
|
|
18
|
+
scope?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface ChannelSuggestion {
|
|
21
|
+
id: string;
|
|
22
|
+
name: string;
|
|
23
|
+
}
|
|
24
|
+
/** Most suggestions an error ever lists. */
|
|
25
|
+
export declare const MAX_CHANNEL_SUGGESTIONS = 3;
|
|
26
|
+
/**
|
|
27
|
+
* Tokens used for comparison. Drops a leading "project" token and a trailing
|
|
28
|
+
* 8-hex id, which every project channel carries and which would otherwise
|
|
29
|
+
* dominate the comparison. Falls back to the raw tokens when stripping would
|
|
30
|
+
* leave nothing (a channel literally named "project").
|
|
31
|
+
*/
|
|
32
|
+
export declare function comparableTokens(value: string): string[];
|
|
33
|
+
/** Levenshtein distance over code points (so astral characters count once). */
|
|
34
|
+
export declare function editDistance(a: string, b: string): number;
|
|
35
|
+
/**
|
|
36
|
+
* Rank the caller's channels against a name that matched nothing and return
|
|
37
|
+
* up to `max` close ones, best first. Group DMs are unnamed and cannot be
|
|
38
|
+
* addressed by name, so they are never suggested. Each channel is compared by
|
|
39
|
+
* both its slug and its display name; the better score wins.
|
|
40
|
+
*/
|
|
41
|
+
export declare function suggestChannels(channels: readonly SuggestableChannel[], query: string, max?: number): ChannelSuggestion[];
|
|
42
|
+
/**
|
|
43
|
+
* Append a "Did you mean" block to a not-found message. Returns the message
|
|
44
|
+
* unchanged when there are no suggestions.
|
|
45
|
+
*/
|
|
46
|
+
export declare function withChannelSuggestions(message: string, suggestions: readonly ChannelSuggestion[]): string;
|
|
47
|
+
/**
|
|
48
|
+
* Thrown by channel resolvers when a name or id matches nothing. Carries the
|
|
49
|
+
* near-match suggestions so a --json caller can emit them structurally.
|
|
50
|
+
*/
|
|
51
|
+
export declare class ChannelNotFoundError extends Error {
|
|
52
|
+
readonly baseMessage: string;
|
|
53
|
+
readonly suggestions: ChannelSuggestion[];
|
|
54
|
+
constructor(baseMessage: string, suggestions: ChannelSuggestion[]);
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=channel-suggest.d.ts.map
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Near-match suggestions for a channel name that resolved to nothing.
|
|
3
|
+
*
|
|
4
|
+
* `hq dm channel <name>`, `hq dm <channel> "msg"`, `hq channels members` and
|
|
5
|
+
* `hq files share --channel` all resolve a channel by exact slug or display
|
|
6
|
+
* name. When that misses, the user usually typed something close to a real
|
|
7
|
+
* channel (for example "desktop-agent-coordination" for the project channel
|
|
8
|
+
* "project-desktop-agent-creation-1331ed7f"). This module ranks the caller's
|
|
9
|
+
* channels against the typed name so the error can list the closest few.
|
|
10
|
+
*
|
|
11
|
+
* Pure: no I/O, so it is unit-testable.
|
|
12
|
+
*/
|
|
13
|
+
/** Most suggestions an error ever lists. */
|
|
14
|
+
export const MAX_CHANNEL_SUGGESTIONS = 3;
|
|
15
|
+
/**
|
|
16
|
+
* Comparison strings are capped so a very long name cannot make the edit
|
|
17
|
+
* distance (quadratic) expensive across a large channel list.
|
|
18
|
+
*/
|
|
19
|
+
const MAX_COMPARE_LENGTH = 64;
|
|
20
|
+
/** Lowercase, Unicode-normalised tokens split on '-', '_' and whitespace. */
|
|
21
|
+
function rawTokens(value) {
|
|
22
|
+
return value
|
|
23
|
+
.normalize("NFKC")
|
|
24
|
+
.toLowerCase()
|
|
25
|
+
.trim()
|
|
26
|
+
.replace(/^#/, "")
|
|
27
|
+
.split(/[\s_-]+/u)
|
|
28
|
+
.filter((token) => token.length > 0);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Tokens used for comparison. Drops a leading "project" token and a trailing
|
|
32
|
+
* 8-hex id, which every project channel carries and which would otherwise
|
|
33
|
+
* dominate the comparison. Falls back to the raw tokens when stripping would
|
|
34
|
+
* leave nothing (a channel literally named "project").
|
|
35
|
+
*/
|
|
36
|
+
export function comparableTokens(value) {
|
|
37
|
+
const tokens = rawTokens(value);
|
|
38
|
+
let stripped = tokens;
|
|
39
|
+
if (stripped[0] === "project")
|
|
40
|
+
stripped = stripped.slice(1);
|
|
41
|
+
const last = stripped[stripped.length - 1];
|
|
42
|
+
if (last !== undefined && /^[0-9a-f]{8}$/.test(last)) {
|
|
43
|
+
stripped = stripped.slice(0, -1);
|
|
44
|
+
}
|
|
45
|
+
return stripped.length > 0 ? stripped : tokens;
|
|
46
|
+
}
|
|
47
|
+
function sharedPrefixLength(a, b) {
|
|
48
|
+
const max = Math.min(a.length, b.length);
|
|
49
|
+
let i = 0;
|
|
50
|
+
while (i < max && a[i] === b[i])
|
|
51
|
+
i += 1;
|
|
52
|
+
return i;
|
|
53
|
+
}
|
|
54
|
+
/** Levenshtein distance over code points (so astral characters count once). */
|
|
55
|
+
export function editDistance(a, b) {
|
|
56
|
+
const left = Array.from(a);
|
|
57
|
+
const right = Array.from(b);
|
|
58
|
+
if (left.length === 0)
|
|
59
|
+
return right.length;
|
|
60
|
+
if (right.length === 0)
|
|
61
|
+
return left.length;
|
|
62
|
+
let previous = Array.from({ length: right.length + 1 }, (_, j) => j);
|
|
63
|
+
for (let i = 1; i <= left.length; i += 1) {
|
|
64
|
+
const current = [i];
|
|
65
|
+
for (let j = 1; j <= right.length; j += 1) {
|
|
66
|
+
const cost = left[i - 1] === right[j - 1] ? 0 : 1;
|
|
67
|
+
current[j] = Math.min(previous[j] + 1, current[j - 1] + 1, previous[j - 1] + cost);
|
|
68
|
+
}
|
|
69
|
+
previous = current;
|
|
70
|
+
}
|
|
71
|
+
return previous[right.length];
|
|
72
|
+
}
|
|
73
|
+
function toComparable(value) {
|
|
74
|
+
const tokens = comparableTokens(value);
|
|
75
|
+
return {
|
|
76
|
+
tokens: new Set(tokens),
|
|
77
|
+
joined: Array.from(tokens.join("-")).slice(0, MAX_COMPARE_LENGTH).join(""),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Similarity in [0, 1], or 0 when the two are not close enough to suggest.
|
|
82
|
+
* A candidate qualifies when it shares a token, shares a prefix of at least
|
|
83
|
+
* three characters, or is within 40% edit distance of the query.
|
|
84
|
+
*/
|
|
85
|
+
function similarity(query, candidate) {
|
|
86
|
+
if (!query.joined || !candidate.joined)
|
|
87
|
+
return 0;
|
|
88
|
+
let shared = 0;
|
|
89
|
+
for (const token of query.tokens)
|
|
90
|
+
if (candidate.tokens.has(token))
|
|
91
|
+
shared += 1;
|
|
92
|
+
const union = query.tokens.size + candidate.tokens.size - shared;
|
|
93
|
+
const tokenScore = union > 0 ? shared / union : 0;
|
|
94
|
+
const prefix = sharedPrefixLength(query.joined, candidate.joined);
|
|
95
|
+
const longest = Math.max(Array.from(query.joined).length, Array.from(candidate.joined).length);
|
|
96
|
+
const prefixScore = longest > 0 ? prefix / longest : 0;
|
|
97
|
+
const editScore = longest > 0
|
|
98
|
+
? 1 - editDistance(query.joined, candidate.joined) / longest
|
|
99
|
+
: 0;
|
|
100
|
+
const close = shared > 0 || prefix >= 3 || editScore >= 0.6;
|
|
101
|
+
if (!close)
|
|
102
|
+
return 0;
|
|
103
|
+
return 0.5 * tokenScore + 0.2 * prefixScore + 0.3 * editScore;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Rank the caller's channels against a name that matched nothing and return
|
|
107
|
+
* up to `max` close ones, best first. Group DMs are unnamed and cannot be
|
|
108
|
+
* addressed by name, so they are never suggested. Each channel is compared by
|
|
109
|
+
* both its slug and its display name; the better score wins.
|
|
110
|
+
*/
|
|
111
|
+
export function suggestChannels(channels, query, max = MAX_CHANNEL_SUGGESTIONS) {
|
|
112
|
+
if (max <= 0)
|
|
113
|
+
return [];
|
|
114
|
+
const target = toComparable(query);
|
|
115
|
+
if (!target.joined)
|
|
116
|
+
return [];
|
|
117
|
+
const scored = [];
|
|
118
|
+
for (const channel of channels) {
|
|
119
|
+
if (channel.scope === "group")
|
|
120
|
+
continue;
|
|
121
|
+
const slug = channel.slug?.trim() ?? "";
|
|
122
|
+
const name = channel.name?.trim() ?? "";
|
|
123
|
+
if (!slug && !name)
|
|
124
|
+
continue;
|
|
125
|
+
const score = Math.max(slug ? similarity(target, toComparable(slug)) : 0, name ? similarity(target, toComparable(name)) : 0);
|
|
126
|
+
if (score <= 0)
|
|
127
|
+
continue;
|
|
128
|
+
scored.push({
|
|
129
|
+
suggestion: { id: channel.channelId, name: slug || name },
|
|
130
|
+
score,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
scored.sort((a, b) => b.score - a.score ||
|
|
134
|
+
a.suggestion.name.localeCompare(b.suggestion.name) ||
|
|
135
|
+
a.suggestion.id.localeCompare(b.suggestion.id));
|
|
136
|
+
return scored.slice(0, max).map((entry) => entry.suggestion);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Strip control and bidi-override characters so a server-supplied name cannot
|
|
140
|
+
* drive the terminal or visually reorder the line.
|
|
141
|
+
*/
|
|
142
|
+
function printable(value) {
|
|
143
|
+
// eslint-disable-next-line no-control-regex
|
|
144
|
+
return value.replace(/[\u0000-\u001f\u007f-\u009f\u200e\u200f\u202a-\u202e\u2066-\u2069]/g, "");
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Append a "Did you mean" block to a not-found message. Returns the message
|
|
148
|
+
* unchanged when there are no suggestions.
|
|
149
|
+
*/
|
|
150
|
+
export function withChannelSuggestions(message, suggestions) {
|
|
151
|
+
if (suggestions.length === 0)
|
|
152
|
+
return message;
|
|
153
|
+
const lines = suggestions.map((s) => ` ${printable(s.name)} (${printable(s.id)})`);
|
|
154
|
+
return `${message}\nDid you mean:\n${lines.join("\n")}`;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Thrown by channel resolvers when a name or id matches nothing. Carries the
|
|
158
|
+
* near-match suggestions so a --json caller can emit them structurally.
|
|
159
|
+
*/
|
|
160
|
+
export class ChannelNotFoundError extends Error {
|
|
161
|
+
baseMessage;
|
|
162
|
+
suggestions;
|
|
163
|
+
constructor(baseMessage, suggestions) {
|
|
164
|
+
super(withChannelSuggestions(baseMessage, suggestions));
|
|
165
|
+
this.name = "ChannelNotFoundError";
|
|
166
|
+
this.baseMessage = baseMessage;
|
|
167
|
+
this.suggestions = suggestions;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=channel-suggest.js.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { ensureCognitoToken } from "../utils/cognito-session.js";
|
|
3
3
|
import { vaultApiFetch } from "../utils/vault-api.js";
|
|
4
|
+
import { suggestChannels, withChannelSuggestions } from "./channel-suggest.js";
|
|
4
5
|
import { callerFromToken, channelSlug, fetchChannelMembers, fetchChannels, matchChannelsByName, } from "./dm.js";
|
|
5
6
|
/**
|
|
6
7
|
* Human label for a channel row. Named channels (personal/company) show their
|
|
@@ -189,7 +190,12 @@ async function runChannelsMembers(channelName, opts) {
|
|
|
189
190
|
const channels = await fetchChannels(token);
|
|
190
191
|
const matches = matchChannelsByName(channels, channelName);
|
|
191
192
|
if (matches.length === 0) {
|
|
192
|
-
|
|
193
|
+
const message = `No channel named '${channelName}' — run \`hq channels\` to see your channels.`;
|
|
194
|
+
const suggestions = suggestChannels(channels, channelName);
|
|
195
|
+
if (opts.json) {
|
|
196
|
+
console.log(JSON.stringify({ ok: false, error: message, suggestions }, null, 2));
|
|
197
|
+
}
|
|
198
|
+
console.error(chalk.red(withChannelSuggestions(message, suggestions)));
|
|
193
199
|
process.exit(1);
|
|
194
200
|
}
|
|
195
201
|
if (matches.length > 1) {
|
|
@@ -484,8 +484,8 @@ async function defaultRunInitialSync(args) {
|
|
|
484
484
|
if (event.type === "plan-limit") {
|
|
485
485
|
failureCount += Math.max(1, event.count ?? 0, event.paths?.length ?? 0);
|
|
486
486
|
firstFailure ??= {
|
|
487
|
-
path: event.paths?.[0] ?? "
|
|
488
|
-
message: event.message ?? "
|
|
487
|
+
path: event.paths?.[0] ?? "new files",
|
|
488
|
+
message: event.message ?? "new files are paused for this account",
|
|
489
489
|
};
|
|
490
490
|
return;
|
|
491
491
|
}
|
package/dist/commands/cloud.d.ts
CHANGED
|
@@ -69,6 +69,32 @@ export declare function scopeExcludedExitCode(count: number): 0 | 1;
|
|
|
69
69
|
export declare function scopeExcludedWarning(count: number): string | null;
|
|
70
70
|
/** Actionable recovery for files refused because the vault baseline is stale. */
|
|
71
71
|
export declare function staleUploadRefusalLines(count: number, paths?: readonly string[]): string[];
|
|
72
|
+
/**
|
|
73
|
+
* Upload-failure fields newer hq-cloud ShareResult carries. Read through
|
|
74
|
+
* readUploadFailures so this CLI also builds against a release without them.
|
|
75
|
+
*/
|
|
76
|
+
export interface UploadFailureCounts {
|
|
77
|
+
filesUploadFailed?: number;
|
|
78
|
+
uploadFailedPaths?: string[];
|
|
79
|
+
}
|
|
80
|
+
export declare function readUploadFailures(result: object): {
|
|
81
|
+
count: number;
|
|
82
|
+
paths: string[];
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Files the vault accepted in the plan but refused or failed at upload
|
|
86
|
+
* (feedback_943456f4). Without these lines a push that uploaded nothing
|
|
87
|
+
* printed "✓ Pushed 0" and exited 0.
|
|
88
|
+
*/
|
|
89
|
+
export declare function uploadFailedLines(count: number, paths?: readonly string[]): string[];
|
|
90
|
+
/**
|
|
91
|
+
* share() option that turns a user-named path an ignore rule drops into an
|
|
92
|
+
* "unreachable path" error instead of a silent 0-file success
|
|
93
|
+
* (feedback_63bfc302, feedback_8c294805). Spread as a plain record so this
|
|
94
|
+
* builds against an hq-cloud release whose ShareOptions predates the field;
|
|
95
|
+
* older releases ignore it.
|
|
96
|
+
*/
|
|
97
|
+
export declare function namedPathShareOptions(userNamedPaths: boolean): Record<string, boolean>;
|
|
72
98
|
/**
|
|
73
99
|
* Optional detail line listing the effective write prefixes when a push
|
|
74
100
|
* dropped files — helps debug ACL vs scope-excluded mismatches (feedback_7e9c535e).
|
|
@@ -241,6 +267,8 @@ export interface ShareCallResult {
|
|
|
241
267
|
filesExcludedByScope: number;
|
|
242
268
|
filesRefusedStale?: number;
|
|
243
269
|
filesRefusedStalePaths?: string[];
|
|
270
|
+
filesUploadFailed?: number;
|
|
271
|
+
uploadFailedPaths?: string[];
|
|
244
272
|
conflictPaths: string[];
|
|
245
273
|
aborted: boolean;
|
|
246
274
|
/** Files this leg's plan limit refused; absent or null when none were. */
|
|
@@ -286,6 +314,8 @@ export interface PushAllResult {
|
|
|
286
314
|
filesExcludedByScope: number;
|
|
287
315
|
filesRefusedStale?: number;
|
|
288
316
|
filesRefusedStalePaths?: string[];
|
|
317
|
+
filesUploadFailed?: number;
|
|
318
|
+
uploadFailedPaths?: string[];
|
|
289
319
|
errors: Array<{
|
|
290
320
|
company: string;
|
|
291
321
|
message: string;
|
|
@@ -418,4 +448,19 @@ export declare function printReadOnlyLocalOnly(hqRoot: string, journals: Readonl
|
|
|
418
448
|
files?: Record<string, unknown>;
|
|
419
449
|
};
|
|
420
450
|
}>, log?: (line: string) => void): number;
|
|
451
|
+
/**
|
|
452
|
+
* `hq sync status`: list local files under server-owned folders (sources/,
|
|
453
|
+
* signals/, ontology/ outside entities/) that are not in the sync journal.
|
|
454
|
+
* The server pipeline writes these folders; a client push refuses them and
|
|
455
|
+
* the recurring runner skips them quietly, so a file written locally there
|
|
456
|
+
* never reaches the vault and `pendingFiles: 0` still reports a clean tree
|
|
457
|
+
* (feedback_0414d71c). Uses only the journals already loaded, with no
|
|
458
|
+
* network calls. Prints nothing when nothing applies.
|
|
459
|
+
*/
|
|
460
|
+
export declare function printServerOwnedLocalOnly(hqRoot: string, journals: ReadonlyArray<{
|
|
461
|
+
slug: string;
|
|
462
|
+
journal: {
|
|
463
|
+
files?: Record<string, unknown>;
|
|
464
|
+
};
|
|
465
|
+
}>, log?: (line: string) => void): number;
|
|
421
466
|
//# sourceMappingURL=cloud.d.ts.map
|