@indigoai-us/hq-cli 5.265.0 → 5.267.0
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 +20 -0
- package/dist/command-catalog.generated.d.ts +3 -0
- package/dist/command-catalog.generated.js +4 -0
- package/dist/commands/daemon.d.ts +3 -0
- package/dist/commands/daemon.js +11 -0
- package/dist/lib/billing/plan-limit-upgrade-url.d.ts +3 -2
- package/dist/lib/billing/plan-limit-upgrade-url.js +50 -11
- package/dist/lib/bot/inbox-poll-backoff-flag.d.ts +1 -1
- package/dist/lib/bot/inbox-poll-backoff-flag.js +4 -1
- package/dist/lib/daemon/handoff.d.ts +2 -0
- package/dist/lib/daemon/handoff.js +11 -2
- package/dist/lib/daemon/run.d.ts +19 -0
- package/dist/lib/daemon/run.js +23 -4
- package/dist/lib/daemon/supervisor.d.ts +5 -0
- package/dist/lib/daemon/supervisor.js +1 -0
- package/dist/utils/plan-gate-error.js +9 -6
- package/dist/utils/team-upgrade.js +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.267.0] — 2026-09-28
|
|
6
|
+
|
|
7
|
+
- Company billing links opened by CLI plan-limit flows now carry cli_limit attribution into checkout telemetry.
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Local bots now read the idle inbox backoff flag with a 2 s budget. The
|
|
12
|
+
150 ms budget timed out from home networks, so bots kept polling every 2 s
|
|
13
|
+
even when the flag was on for them.
|
|
14
|
+
|
|
15
|
+
## [5.266.0] — 2026-09-28
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- `hq daemon run --host desktop` lets the HQ desktop app run the daemon as its
|
|
20
|
+
own child process. The daemon then runs sync even though the app is open, and
|
|
21
|
+
records the app as its host in `state.json`. Older per-service installers
|
|
22
|
+
such as `hq mesh daemon install` treat a hosted daemon as installed and hand
|
|
23
|
+
their service to it.
|
|
24
|
+
|
|
5
25
|
## [5.265.0] — 2026-09-28
|
|
6
26
|
|
|
7
27
|
### Added
|
|
@@ -8978,6 +8978,9 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
8978
8978
|
}, {
|
|
8979
8979
|
readonly flags: "--managed";
|
|
8980
8980
|
readonly description: "Started by the OS unit, which starts the daemon again whenever it exits";
|
|
8981
|
+
}, {
|
|
8982
|
+
readonly flags: "--host <app>";
|
|
8983
|
+
readonly description: "Run as a child of this app (only \"desktop\"), which has handed sync over to the daemon";
|
|
8981
8984
|
}];
|
|
8982
8985
|
readonly subcommands: readonly [];
|
|
8983
8986
|
}, {
|
|
@@ -11611,6 +11611,10 @@ export const COMMAND_CATALOG = [
|
|
|
11611
11611
|
{
|
|
11612
11612
|
"flags": "--managed",
|
|
11613
11613
|
"description": "Started by the OS unit, which starts the daemon again whenever it exits"
|
|
11614
|
+
},
|
|
11615
|
+
{
|
|
11616
|
+
"flags": "--host <app>",
|
|
11617
|
+
"description": "Run as a child of this app (only \"desktop\"), which has handed sync over to the daemon"
|
|
11614
11618
|
}
|
|
11615
11619
|
],
|
|
11616
11620
|
"subcommands": []
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import type { Command } from "commander";
|
|
17
17
|
import { type LegacyUnit } from "../lib/daemon/legacy.js";
|
|
18
|
+
import type { HostApp } from "../lib/daemon/supervisor.js";
|
|
18
19
|
import type { RunCommand } from "../lib/service-manager/launchd.js";
|
|
19
20
|
export interface DaemonCommandDeps {
|
|
20
21
|
env: NodeJS.ProcessEnv;
|
|
@@ -46,6 +47,8 @@ export interface DaemonCommandDeps {
|
|
|
46
47
|
* (HQ_ROOT, the tree around the current folder, the folder the installer or
|
|
47
48
|
* menubar recorded, the well-known locations). Undefined when none resolves.
|
|
48
49
|
*/
|
|
50
|
+
/** Validate `hq daemon run --host`; the desktop app is the only host. */
|
|
51
|
+
export declare function parseDaemonHost(value: string | undefined): HostApp | undefined;
|
|
49
52
|
export declare function resolveDaemonHqRoot(flag: string | undefined, where: {
|
|
50
53
|
env: NodeJS.ProcessEnv;
|
|
51
54
|
cwd: string;
|
package/dist/commands/daemon.js
CHANGED
|
@@ -36,6 +36,14 @@ class DaemonCommandError extends Error {
|
|
|
36
36
|
* (HQ_ROOT, the tree around the current folder, the folder the installer or
|
|
37
37
|
* menubar recorded, the well-known locations). Undefined when none resolves.
|
|
38
38
|
*/
|
|
39
|
+
/** Validate `hq daemon run --host`; the desktop app is the only host. */
|
|
40
|
+
export function parseDaemonHost(value) {
|
|
41
|
+
if (value === undefined)
|
|
42
|
+
return undefined;
|
|
43
|
+
if (value === "desktop")
|
|
44
|
+
return value;
|
|
45
|
+
throw new Error(`--host must be "desktop" (got "${value}")`);
|
|
46
|
+
}
|
|
39
47
|
export function resolveDaemonHqRoot(flag, where) {
|
|
40
48
|
if (flag)
|
|
41
49
|
return path.resolve(flag);
|
|
@@ -574,13 +582,16 @@ export function registerDaemonCommand(program, over = {}) {
|
|
|
574
582
|
.option("--foreground", "Also print the supervisor log to the terminal")
|
|
575
583
|
.option("--daemon-dir <path>", "Use this folder instead of ~/.hq/daemon (how the Windows task passes HQ_DAEMON_DIR)")
|
|
576
584
|
.option("--managed", "Started by the OS unit, which starts the daemon again whenever it exits")
|
|
585
|
+
.option("--host <app>", "Run as a child of this app (only \"desktop\"), which has handed sync over to the daemon")
|
|
577
586
|
.action(wrap(async (d, opts) => {
|
|
587
|
+
const host = parseDaemonHost(opts.host);
|
|
578
588
|
const { runDaemon } = await import("../lib/daemon/run.js");
|
|
579
589
|
// Services inherit this environment, so they see the same folder.
|
|
580
590
|
const env = opts.daemonDir ? { ...d.env, HQ_DAEMON_DIR: opts.daemonDir } : d.env;
|
|
581
591
|
process.exitCode = await runDaemon({
|
|
582
592
|
foreground: opts.foreground,
|
|
583
593
|
managed: opts.managed,
|
|
594
|
+
host,
|
|
584
595
|
home: d.home,
|
|
585
596
|
env,
|
|
586
597
|
nodeBinary: d.nodeBinary,
|
|
@@ -6,8 +6,9 @@ export interface PlanLimitUpgradeUrlChoices {
|
|
|
6
6
|
legacyUrl: string;
|
|
7
7
|
}
|
|
8
8
|
export declare function isPlanLimitUpgradeFallbackEnabled(): boolean;
|
|
9
|
-
/** Resolve
|
|
9
|
+
/** Resolve and attribute the final selected company billing destination. */
|
|
10
10
|
export declare function selectPlanLimitUpgradeUrl(companySlug: string | undefined, choices: PlanLimitUpgradeUrlChoices): string;
|
|
11
|
-
|
|
11
|
+
export declare function withCliLimitEntrySurface(value: string): string;
|
|
12
|
+
/** Keep the existing destination and flag behavior while labeling company limit links. */
|
|
12
13
|
export declare function planLimitUpgradeFallbackUrl(companySlug: string | undefined, previousFallbackUrl: string): string;
|
|
13
14
|
//# sourceMappingURL=plan-limit-upgrade-url.d.ts.map
|
|
@@ -13,23 +13,62 @@ function companyBillingUrl(companySlug) {
|
|
|
13
13
|
return null;
|
|
14
14
|
const slug = companySlug?.trim();
|
|
15
15
|
const route = slug
|
|
16
|
-
? `/companies/${encodeURIComponent(slug)}/billing?upgrade=1`
|
|
16
|
+
? `/companies/${encodeURIComponent(slug)}/billing?upgrade=1&entrySurface=cli_limit`
|
|
17
17
|
: "/billing";
|
|
18
18
|
return `${origin}${route}`;
|
|
19
19
|
}
|
|
20
|
-
/** Resolve
|
|
20
|
+
/** Resolve and attribute the final selected company billing destination. */
|
|
21
21
|
export function selectPlanLimitUpgradeUrl(companySlug, choices) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const nestedUrl = choices.nestedUrl?.trim() || undefined;
|
|
26
|
-
const generatedUrl = companyBillingUrl(companySlug);
|
|
27
|
-
if (companySlug?.trim()) {
|
|
28
|
-
return generatedUrl ?? topLevelUrl ?? nestedUrl ?? choices.legacyUrl;
|
|
22
|
+
let selected;
|
|
23
|
+
if (!isPlanLimitUpgradeFallbackEnabled()) {
|
|
24
|
+
selected = choices.legacyUrl;
|
|
29
25
|
}
|
|
30
|
-
|
|
26
|
+
else {
|
|
27
|
+
const topLevelUrl = choices.topLevelUrl?.trim() || undefined;
|
|
28
|
+
const nestedUrl = choices.nestedUrl?.trim() || undefined;
|
|
29
|
+
const generatedUrl = companyBillingUrl(companySlug);
|
|
30
|
+
if (companySlug?.trim()) {
|
|
31
|
+
selected = generatedUrl ?? topLevelUrl ?? nestedUrl ?? choices.legacyUrl;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
selected = topLevelUrl ?? nestedUrl ?? generatedUrl ?? choices.legacyUrl;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return withCliLimitEntrySurface(selected);
|
|
38
|
+
}
|
|
39
|
+
export function withCliLimitEntrySurface(value) {
|
|
40
|
+
try {
|
|
41
|
+
const url = new URL(value);
|
|
42
|
+
if (url.protocol !== "https:" || url.username || url.password)
|
|
43
|
+
return value;
|
|
44
|
+
const callbackUrl = url.searchParams.get("callbackUrl");
|
|
45
|
+
if (callbackUrl) {
|
|
46
|
+
const callback = new URL(callbackUrl, url.origin);
|
|
47
|
+
const parts = callback.pathname.split("/").filter(Boolean);
|
|
48
|
+
if (parts.length === 5 && parts[0] === "api" && parts[1] === "companies" &&
|
|
49
|
+
/^cmp_[A-Za-z0-9_-]+$/.test(parts[2]) && parts[3] === "billing" && parts[4] === "upgrade") {
|
|
50
|
+
callback.searchParams.set("entrySurface", "cli_limit");
|
|
51
|
+
const serializedCallback = /^[a-z][a-z0-9+.-]*:/i.test(callbackUrl)
|
|
52
|
+
? callback.toString()
|
|
53
|
+
: callbackUrl.startsWith("//")
|
|
54
|
+
? `//${callback.host}${callback.pathname}${callback.search}${callback.hash}`
|
|
55
|
+
: `${callback.pathname}${callback.search}${callback.hash}`;
|
|
56
|
+
url.searchParams.set("callbackUrl", serializedCallback);
|
|
57
|
+
return url.toString();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const parts = url.pathname.split("/").filter(Boolean);
|
|
61
|
+
if (parts.length === 3 && parts[0] === "companies" && parts[2] === "billing") {
|
|
62
|
+
url.searchParams.set("entrySurface", "cli_limit");
|
|
63
|
+
return url.toString();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
return value;
|
|
31
70
|
}
|
|
32
|
-
/** Keep
|
|
71
|
+
/** Keep the existing destination and flag behavior while labeling company limit links. */
|
|
33
72
|
export function planLimitUpgradeFallbackUrl(companySlug, previousFallbackUrl) {
|
|
34
73
|
return selectPlanLimitUpgradeUrl(companySlug, {
|
|
35
74
|
legacyUrl: previousFallbackUrl,
|
|
@@ -2,7 +2,7 @@ import { type FlagClient, type FlagClientOptions } from "@indigoai-us/hq-flags-c
|
|
|
2
2
|
import { type BotConfig } from "./config.js";
|
|
3
3
|
export declare const INBOX_POLL_BACKOFF_FLAG = "agents.inbox-poll-backoff-local-cli";
|
|
4
4
|
export declare const INBOX_POLL_BACKOFF_FLAG_FALLBACK = false;
|
|
5
|
-
export declare const INBOX_POLL_BACKOFF_FLAG_TIMEOUT_MS =
|
|
5
|
+
export declare const INBOX_POLL_BACKOFF_FLAG_TIMEOUT_MS = 2000;
|
|
6
6
|
export declare const INBOX_POLL_BACKOFF_FLAG_CACHE_MS: number;
|
|
7
7
|
type InboxPollBackoffFlagClient = Pick<FlagClient, "ready" | "isEnabled" | "close">;
|
|
8
8
|
export interface InboxPollBackoffFlagReaderDependencies {
|
|
@@ -5,7 +5,10 @@ import { resolveFlagRegistryEndpoint } from "../flag-registry-endpoint.js";
|
|
|
5
5
|
import { effectiveBotCompanies, effectiveBotKind } from "./config.js";
|
|
6
6
|
export const INBOX_POLL_BACKOFF_FLAG = "agents.inbox-poll-backoff-local-cli";
|
|
7
7
|
export const INBOX_POLL_BACKOFF_FLAG_FALLBACK = false;
|
|
8
|
-
|
|
8
|
+
// One read every three minutes on a fresh connection. From a home network the
|
|
9
|
+
// TCP and TLS handshakes plus the resolve call take about 250-400 ms, so a
|
|
10
|
+
// 150 ms budget timed out on every read and kept the gate off.
|
|
11
|
+
export const INBOX_POLL_BACKOFF_FLAG_TIMEOUT_MS = 2_000;
|
|
9
12
|
export const INBOX_POLL_BACKOFF_FLAG_CACHE_MS = 3 * 60_000;
|
|
10
13
|
const COMPANY_UID_RE = /^cmp_[A-Za-z0-9]{3,128}$/;
|
|
11
14
|
export function safeErrorClass(error) {
|
|
@@ -24,6 +24,8 @@ export declare function daemonRunsService(paths: DaemonPaths, service: ServiceNa
|
|
|
24
24
|
export declare function askDaemonToStart(paths: DaemonPaths, unitId: string): void;
|
|
25
25
|
/** Ask the running daemon to stop a unit and keep it down until it is started again. */
|
|
26
26
|
export declare function askDaemonToStop(paths: DaemonPaths, unitId: string): void;
|
|
27
|
+
/** A live daemon runs as the desktop app's child, which counts as installed even though no OS unit exists. */
|
|
28
|
+
export declare function hostedDaemonRunning(paths: DaemonPaths, alive?: (pid: number) => boolean): boolean;
|
|
27
29
|
export interface DaemonUnitHost {
|
|
28
30
|
platform: NodeJS.Platform;
|
|
29
31
|
home: string;
|
|
@@ -67,6 +67,15 @@ export function askDaemonToStart(paths, unitId) {
|
|
|
67
67
|
export function askDaemonToStop(paths, unitId) {
|
|
68
68
|
writeControlRequest(paths.requestsDir, { action: "stop", unit: unitId });
|
|
69
69
|
}
|
|
70
|
+
/** A live daemon runs as the desktop app's child, which counts as installed even though no OS unit exists. */
|
|
71
|
+
export function hostedDaemonRunning(paths, alive = (pid) => isDaemonProcess(pid)) {
|
|
72
|
+
try {
|
|
73
|
+
return liveState(paths, alive)?.host === "desktop";
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
70
79
|
/** The `hq daemon` OS unit is installed on this machine (for this user, or as the box's system unit). */
|
|
71
80
|
export function daemonUnitInstalled(host) {
|
|
72
81
|
switch (host.platform) {
|
|
@@ -114,14 +123,14 @@ export function handOffInstall(opts) {
|
|
|
114
123
|
/** handOffInstall for this machine and user, for the older install commands. */
|
|
115
124
|
export function handOffInstallHere(services, env = process.env, opts = {}) {
|
|
116
125
|
const home = os.homedir();
|
|
126
|
+
const paths = daemonPaths(home, env);
|
|
117
127
|
const installed = daemonUnitInstalled({
|
|
118
128
|
platform: process.platform,
|
|
119
129
|
home,
|
|
120
130
|
windowsTaskExists: (task) => spawnSync("schtasks.exe", ["/Query", "/TN", task], { encoding: "utf8", windowsHide: true }).status === 0,
|
|
121
|
-
});
|
|
131
|
+
}) || hostedDaemonRunning(paths);
|
|
122
132
|
if (!installed)
|
|
123
133
|
return null;
|
|
124
|
-
const paths = daemonPaths(home, env);
|
|
125
134
|
const config = readDaemonConfig(paths.configPath);
|
|
126
135
|
const machineType = detectMachineType(undefined, {
|
|
127
136
|
platform: process.platform,
|
package/dist/lib/daemon/run.d.ts
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { type UnitPlan } from "./catalog.js";
|
|
9
9
|
import { type LegacyUnit } from "./legacy.js";
|
|
10
|
+
import { type MachineType } from "./machine-type.js";
|
|
11
|
+
import { type HostApp } from "./supervisor.js";
|
|
10
12
|
export declare const LOG_ROTATE_BYTES: number;
|
|
11
13
|
/** Exit code for "restart me": non-zero so every service manager relaunches (Windows tasks only restart on failure). */
|
|
12
14
|
export declare const RESTART_EXIT_CODE = 75;
|
|
@@ -14,6 +16,8 @@ export interface RunDaemonOptions {
|
|
|
14
16
|
foreground?: boolean;
|
|
15
17
|
/** Started by the OS unit, which relaunches the daemon whenever it exits. */
|
|
16
18
|
managed?: boolean;
|
|
19
|
+
/** The app that runs this daemon as its child (it relaunches the daemon on RESTART_EXIT_CODE when also managed). */
|
|
20
|
+
host?: HostApp;
|
|
17
21
|
home?: string;
|
|
18
22
|
env?: NodeJS.ProcessEnv;
|
|
19
23
|
nodeBinary: string;
|
|
@@ -35,6 +39,21 @@ export declare function findUserBinary(name: string, env: NodeJS.ProcessEnv, hom
|
|
|
35
39
|
export declare function cliSelfUpdatable(): boolean;
|
|
36
40
|
/** The HQ desktop app process is running here (it runs its own sync). */
|
|
37
41
|
export declare function desktopAppRunning(platform: NodeJS.Platform): boolean;
|
|
42
|
+
/** A parent (the OS unit, or the app hosting the daemon) starts the daemon again after it exits with RESTART_EXIT_CODE. */
|
|
43
|
+
export declare function restartsForParent(opts: {
|
|
44
|
+
managed?: boolean;
|
|
45
|
+
host?: HostApp;
|
|
46
|
+
}): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* The desktop app runs sync itself, so the daemon's sync unit waits. When the
|
|
49
|
+
* app hosts the daemon it has handed sync over, and the daemon runs it.
|
|
50
|
+
*/
|
|
51
|
+
export declare function desktopAppManagesSync(opts: {
|
|
52
|
+
machineType: MachineType;
|
|
53
|
+
host?: HostApp;
|
|
54
|
+
legacy: readonly LegacyUnit[];
|
|
55
|
+
appRunning: () => boolean;
|
|
56
|
+
}): boolean;
|
|
38
57
|
/** Copy-and-truncate, so children appending with O_APPEND keep writing to the live file. */
|
|
39
58
|
export declare function rotateLogs(logsDir: string, maxBytes?: number): number;
|
|
40
59
|
/**
|
package/dist/lib/daemon/run.js
CHANGED
|
@@ -133,6 +133,19 @@ export function desktopAppRunning(platform) {
|
|
|
133
133
|
const r = spawnSync("pgrep", ["-x", "hq-sync-menubar"], { encoding: "utf8" });
|
|
134
134
|
return r.status === 0;
|
|
135
135
|
}
|
|
136
|
+
/** A parent (the OS unit, or the app hosting the daemon) starts the daemon again after it exits with RESTART_EXIT_CODE. */
|
|
137
|
+
export function restartsForParent(opts) {
|
|
138
|
+
return opts.managed === true || opts.host !== undefined;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* The desktop app runs sync itself, so the daemon's sync unit waits. When the
|
|
142
|
+
* app hosts the daemon it has handed sync over, and the daemon runs it.
|
|
143
|
+
*/
|
|
144
|
+
export function desktopAppManagesSync(opts) {
|
|
145
|
+
if (opts.machineType !== "desktop" || opts.host === "desktop")
|
|
146
|
+
return false;
|
|
147
|
+
return opts.legacy.some((u) => u.service === "sync" && !u.migratable) || opts.appRunning();
|
|
148
|
+
}
|
|
136
149
|
function jobsReconcileScript(hqRoot) {
|
|
137
150
|
if (!hqRoot)
|
|
138
151
|
return null;
|
|
@@ -231,7 +244,8 @@ export async function runDaemon(opts) {
|
|
|
231
244
|
releaseDaemonLock(paths.lockPath);
|
|
232
245
|
return 1;
|
|
233
246
|
}
|
|
234
|
-
|
|
247
|
+
const hostNote = opts.host ? `, hosted by the ${opts.host} app` : "";
|
|
248
|
+
log("info", `starting hq daemon (pid ${process.pid}) as ${machineType} (${detected.detail})${hostNote}`);
|
|
235
249
|
if (!resolveSyncRunnerPath())
|
|
236
250
|
log("warn", "the hq-cloud sync runner was not found in this CLI install; sync is unavailable");
|
|
237
251
|
const updatable = cliSelfUpdatable();
|
|
@@ -270,8 +284,12 @@ export async function runDaemon(opts) {
|
|
|
270
284
|
legacySyncEnv: readLegacySyncEnv(platform),
|
|
271
285
|
signedIn: signedIn(env, home, log),
|
|
272
286
|
kitConfigured: fs.existsSync(agentKitPaths(home, env).kitConfigPath),
|
|
273
|
-
desktopAppManagesSync:
|
|
274
|
-
|
|
287
|
+
desktopAppManagesSync: desktopAppManagesSync({
|
|
288
|
+
machineType,
|
|
289
|
+
host: opts.host,
|
|
290
|
+
legacy,
|
|
291
|
+
appRunning: () => desktopAppRunning(platform),
|
|
292
|
+
}),
|
|
275
293
|
bots: listLocalBots(),
|
|
276
294
|
legacyOwner: legacyOwnerLookup(legacy),
|
|
277
295
|
cliSelfUpdatable: updatable,
|
|
@@ -312,6 +330,7 @@ export async function runDaemon(opts) {
|
|
|
312
330
|
let restartDaemon = () => { };
|
|
313
331
|
const supervisor = createSupervisor({
|
|
314
332
|
machineType,
|
|
333
|
+
host: opts.host,
|
|
315
334
|
pid: process.pid,
|
|
316
335
|
plan,
|
|
317
336
|
spawn: spawnUnit,
|
|
@@ -352,7 +371,7 @@ export async function runDaemon(opts) {
|
|
|
352
371
|
});
|
|
353
372
|
};
|
|
354
373
|
restartDaemon = () => {
|
|
355
|
-
if (!opts
|
|
374
|
+
if (!restartsForParent(opts)) {
|
|
356
375
|
log("warn", "a restart was requested, but no service manager would start this daemon again; restart it yourself");
|
|
357
376
|
return;
|
|
358
377
|
}
|
|
@@ -50,16 +50,21 @@ export interface UnitState {
|
|
|
50
50
|
nextRunAt?: number;
|
|
51
51
|
lastResult?: "ok" | "failed";
|
|
52
52
|
}
|
|
53
|
+
/** An app that runs the daemon as its own child process instead of an OS unit. */
|
|
54
|
+
export type HostApp = "desktop";
|
|
53
55
|
export interface DaemonState {
|
|
54
56
|
version: 1;
|
|
55
57
|
pid: number;
|
|
56
58
|
startedAt: number;
|
|
57
59
|
updatedAt: number;
|
|
58
60
|
machineType: MachineType;
|
|
61
|
+
/** Set when an app runs the daemon as its child; absent under the OS unit. */
|
|
62
|
+
host?: HostApp;
|
|
59
63
|
units: UnitState[];
|
|
60
64
|
}
|
|
61
65
|
export interface SupervisorDeps {
|
|
62
66
|
machineType: MachineType;
|
|
67
|
+
host?: HostApp;
|
|
63
68
|
pid: number;
|
|
64
69
|
plan: () => UnitPlan[];
|
|
65
70
|
spawn: (unit: UnitPlan) => SupervisedChild;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* message instead of every command parsing and printing a server response.
|
|
5
5
|
*/
|
|
6
6
|
import { renderPersonalPlanFeatureNotice } from "../lib/plan-limit-nag.js";
|
|
7
|
+
import { withCliLimitEntrySurface } from "../lib/billing/plan-limit-upgrade-url.js";
|
|
7
8
|
function planGateReason(code, details) {
|
|
8
9
|
if (details.reason?.trim())
|
|
9
10
|
return details.reason.trim();
|
|
@@ -27,11 +28,12 @@ function planGateReason(code, details) {
|
|
|
27
28
|
/** One renderer for server-selected upgrade links across CLI plan refusals. */
|
|
28
29
|
export function formatUpgradeRefusal(reason, upgradeUrl, options = {}) {
|
|
29
30
|
const safeUrl = safeUpgradeUrl(upgradeUrl);
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
const cliLimitUrl = safeUrl ? withCliLimitEntrySurface(safeUrl) : undefined;
|
|
32
|
+
if (options.urlOnly && cliLimitUrl)
|
|
33
|
+
return cliLimitUrl;
|
|
34
|
+
if (!cliLimitUrl)
|
|
33
35
|
return reason;
|
|
34
|
-
return `${reason}\nUpgrade: ${
|
|
36
|
+
return `${reason}\nUpgrade: ${cliLimitUrl}`;
|
|
35
37
|
}
|
|
36
38
|
function safeUpgradeUrl(value) {
|
|
37
39
|
if (typeof value !== "string" ||
|
|
@@ -94,6 +96,7 @@ export async function offerUpgradeLink(url, deps = {}) {
|
|
|
94
96
|
const safeUrl = safeUpgradeUrl(url);
|
|
95
97
|
if (!safeUrl)
|
|
96
98
|
return false;
|
|
99
|
+
const cliLimitUrl = withCliLimitEntrySurface(safeUrl);
|
|
97
100
|
if (!(deps.interactive ?? canOfferTeamUpgrade(deps.argv)))
|
|
98
101
|
return false;
|
|
99
102
|
let answer;
|
|
@@ -113,11 +116,11 @@ export async function offerUpgradeLink(url, deps = {}) {
|
|
|
113
116
|
if (!/^y(?:es)?$/i.test(answer.trim()))
|
|
114
117
|
return false;
|
|
115
118
|
if (deps.openUrl) {
|
|
116
|
-
await deps.openUrl(
|
|
119
|
+
await deps.openUrl(cliLimitUrl);
|
|
117
120
|
}
|
|
118
121
|
else {
|
|
119
122
|
const { default: open } = await import("open");
|
|
120
|
-
await open(
|
|
123
|
+
await open(cliLimitUrl);
|
|
121
124
|
}
|
|
122
125
|
return true;
|
|
123
126
|
}
|