@indigoai-us/hq-cli 5.125.0 → 5.126.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 +118 -0
- package/assets/bot-workers/setup/skills/joining-a-team.md +125 -0
- package/assets/bot-workers/setup/skills/your-business.md +270 -0
- package/assets/bot-workers/setup/worker.yaml +189 -58
- package/dist/command-catalog.generated.d.ts +407 -4
- package/dist/command-catalog.generated.js +522 -4
- package/dist/command-registration-plan.d.ts +6 -0
- package/dist/command-registration-plan.js +1 -0
- package/dist/commands/bot.d.ts +10 -0
- package/dist/commands/bot.js +6 -0
- package/dist/commands/lanes.d.ts +75 -0
- package/dist/commands/lanes.js +2032 -0
- package/dist/commands/mesh.js +37 -9
- package/dist/commands/onboard-create-identity.d.ts +26 -0
- package/dist/commands/onboard-create-identity.js +23 -0
- package/dist/commands/onboard.js +13 -4
- package/dist/lib/bot/progress.d.ts +6 -0
- package/dist/lib/bot/progress.js +16 -0
- package/dist/lib/bot/runtime/grok.d.ts +30 -7
- package/dist/lib/bot/runtime/grok.js +86 -8
- package/dist/lib/bot/runtime/index.js +15 -8
- package/dist/lib/bot/runtime/messages-stream.d.ts +12 -3
- package/dist/lib/bot/runtime/messages-stream.js +49 -3
- package/dist/lib/lanes/accounts.d.ts +43 -0
- package/dist/lib/lanes/accounts.js +128 -0
- package/dist/lib/lanes/config.d.ts +56 -0
- package/dist/lib/lanes/config.js +351 -0
- package/dist/lib/lanes/errors.d.ts +39 -0
- package/dist/lib/lanes/errors.js +72 -0
- package/dist/lib/lanes/index.d.ts +19 -0
- package/dist/lib/lanes/index.js +12 -0
- package/dist/lib/lanes/liveness.d.ts +89 -0
- package/dist/lib/lanes/liveness.js +273 -0
- package/dist/lib/lanes/registry.d.ts +103 -0
- package/dist/lib/lanes/registry.js +247 -0
- package/dist/lib/lanes/store.d.ts +64 -0
- package/dist/lib/lanes/store.js +627 -0
- package/dist/lib/lanes/throttle.d.ts +89 -0
- package/dist/lib/lanes/throttle.js +297 -0
- package/dist/lib/lanes/types.d.ts +193 -0
- package/dist/lib/lanes/types.js +217 -0
- package/dist/lib/mesh/live/daemon/config.d.ts +27 -0
- package/dist/lib/mesh/live/daemon/config.js +75 -0
- package/dist/lib/mesh/live/daemon/index.d.ts +4 -3
- package/dist/lib/mesh/live/daemon/index.js +3 -3
- package/dist/lib/mesh/live/daemon/install.d.ts +143 -1
- package/dist/lib/mesh/live/daemon/install.js +444 -2
- package/dist/lib/mesh/live/daemon/paths.d.ts +2 -0
- package/dist/lib/mesh/live/daemon/paths.js +2 -0
- package/dist/lib/scan-packages/index.js +17 -12
- package/dist/lib/work-context/config.d.ts +20 -0
- package/dist/lib/work-context/config.js +26 -5
- package/dist/lib/work-context/reconcile.js +28 -7
- package/dist/lib/work-context/windows-acl.d.ts +123 -0
- package/dist/lib/work-context/windows-acl.js +425 -0
- package/dist/utils/pack-contributions.js +5 -4
- package/package.json +1 -1
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lane record, question routing record, envelope, and config types (spec §3,
|
|
3
|
+
* §4, §5.5, §6.2). Finite sets are string-literal unions plus `const` arrays
|
|
4
|
+
* so the command layer can validate flags explicitly.
|
|
5
|
+
*/
|
|
6
|
+
import { LanesError, LaneSchemaError } from "./errors.js";
|
|
7
|
+
export const LANE_SCHEMA_VERSION = "hq-lane.v1";
|
|
8
|
+
export const QUESTION_SCHEMA_VERSION = "hq-lane-question.v1";
|
|
9
|
+
export const ENVELOPE_SCHEMA_VERSION = "hq-lane-envelope.v1";
|
|
10
|
+
export const CONFIG_SCHEMA_VERSION = "hq-lanes-config.v1";
|
|
11
|
+
export const LANE_STATES = [
|
|
12
|
+
"queued",
|
|
13
|
+
"running",
|
|
14
|
+
"awaiting_input",
|
|
15
|
+
"blocked",
|
|
16
|
+
"done",
|
|
17
|
+
"failed",
|
|
18
|
+
"interrupted",
|
|
19
|
+
"cancelled",
|
|
20
|
+
"killed",
|
|
21
|
+
];
|
|
22
|
+
/** Exact terminal set from spec §6.7. `awaiting_input` and `blocked` are not terminal. */
|
|
23
|
+
export const TERMINAL_LANE_STATES = [
|
|
24
|
+
"done",
|
|
25
|
+
"failed",
|
|
26
|
+
"interrupted",
|
|
27
|
+
"cancelled",
|
|
28
|
+
"killed",
|
|
29
|
+
];
|
|
30
|
+
export const LANE_PROVIDERS = ["claude", "codex", "grok"];
|
|
31
|
+
export const QUEUED_REASONS = ["cap", "queue_position", "throttle"];
|
|
32
|
+
export const HOST_KINDS = ["local", "outpost"];
|
|
33
|
+
export const SENIOR_KINDS = ["session", "lane", "user"];
|
|
34
|
+
export const ENVELOPE_DECISIONS = ["done", "ask", "blocked"];
|
|
35
|
+
export const STORY_STATUSES = [
|
|
36
|
+
"queued",
|
|
37
|
+
"in_progress",
|
|
38
|
+
"review",
|
|
39
|
+
"done",
|
|
40
|
+
];
|
|
41
|
+
export const QUESTION_STATUSES = [
|
|
42
|
+
"pending",
|
|
43
|
+
"answered",
|
|
44
|
+
"cancelled",
|
|
45
|
+
"expired",
|
|
46
|
+
];
|
|
47
|
+
export const RAISED_VIA = ["cli", "envelope"];
|
|
48
|
+
export const ROUTING_REASONS = [
|
|
49
|
+
"asked",
|
|
50
|
+
"escalated",
|
|
51
|
+
"audience_ended",
|
|
52
|
+
"reparented",
|
|
53
|
+
"unanswered_timeout",
|
|
54
|
+
"cancelled",
|
|
55
|
+
];
|
|
56
|
+
export const QUESTION_BODY_STORES = ["company", "personal"];
|
|
57
|
+
export const LIVENESS_STATES = ["live", "idle", "ended", "lost"];
|
|
58
|
+
export const HISTORY_CAP = 50;
|
|
59
|
+
const TERMINAL_SET = new Set(TERMINAL_LANE_STATES);
|
|
60
|
+
export function isLaneState(value) {
|
|
61
|
+
return typeof value === "string" && LANE_STATES.includes(value);
|
|
62
|
+
}
|
|
63
|
+
export function isTerminalLaneState(value) {
|
|
64
|
+
return typeof value === "string" && TERMINAL_SET.has(value);
|
|
65
|
+
}
|
|
66
|
+
export function isLaneProvider(value) {
|
|
67
|
+
return (typeof value === "string" &&
|
|
68
|
+
LANE_PROVIDERS.includes(value));
|
|
69
|
+
}
|
|
70
|
+
export function isSeniorKind(value) {
|
|
71
|
+
return (typeof value === "string" &&
|
|
72
|
+
SENIOR_KINDS.includes(value));
|
|
73
|
+
}
|
|
74
|
+
export function isHostKind(value) {
|
|
75
|
+
return (typeof value === "string" &&
|
|
76
|
+
HOST_KINDS.includes(value));
|
|
77
|
+
}
|
|
78
|
+
export function isEnvelopeDecision(value) {
|
|
79
|
+
return (typeof value === "string" &&
|
|
80
|
+
ENVELOPE_DECISIONS.includes(value));
|
|
81
|
+
}
|
|
82
|
+
export function isQuestionStatus(value) {
|
|
83
|
+
return (typeof value === "string" &&
|
|
84
|
+
QUESTION_STATUSES.includes(value));
|
|
85
|
+
}
|
|
86
|
+
export function isLivenessState(value) {
|
|
87
|
+
return (typeof value === "string" &&
|
|
88
|
+
LIVENESS_STATES.includes(value));
|
|
89
|
+
}
|
|
90
|
+
export function parseLaneProvider(value) {
|
|
91
|
+
if (isLaneProvider(value))
|
|
92
|
+
return value;
|
|
93
|
+
throw new LanesError("unknown_provider", `Unknown provider ${JSON.stringify(value)}. Accepted: ${LANE_PROVIDERS.join(", ")}`, { accepted: [...LANE_PROVIDERS], value });
|
|
94
|
+
}
|
|
95
|
+
export function parseLaneState(value) {
|
|
96
|
+
if (isLaneState(value))
|
|
97
|
+
return value;
|
|
98
|
+
throw new LanesError("unknown_state", `Unknown lane state ${JSON.stringify(value)}. Accepted: ${LANE_STATES.join(", ")}`, { accepted: [...LANE_STATES], value });
|
|
99
|
+
}
|
|
100
|
+
export function parseQuestionStatus(value) {
|
|
101
|
+
if (isQuestionStatus(value))
|
|
102
|
+
return value;
|
|
103
|
+
throw new LanesError("unknown_status", `Unknown question status ${JSON.stringify(value)}. Accepted: ${QUESTION_STATUSES.join(", ")}`, { accepted: [...QUESTION_STATUSES], value });
|
|
104
|
+
}
|
|
105
|
+
export function parseLivenessState(value) {
|
|
106
|
+
if (isLivenessState(value))
|
|
107
|
+
return value;
|
|
108
|
+
throw new LanesError("unknown_state", `Unknown session liveness state ${JSON.stringify(value)}. Accepted: ${LIVENESS_STATES.join(", ")}`, { accepted: [...LIVENESS_STATES], value });
|
|
109
|
+
}
|
|
110
|
+
export function parseSeniorKind(value) {
|
|
111
|
+
if (isSeniorKind(value))
|
|
112
|
+
return value;
|
|
113
|
+
throw new LanesError("unknown_senior_kind", `Unknown senior kind ${JSON.stringify(value)}. Accepted: ${SENIOR_KINDS.join(", ")}`, { accepted: [...SENIOR_KINDS], value });
|
|
114
|
+
}
|
|
115
|
+
function isPlainObject(value) {
|
|
116
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
117
|
+
}
|
|
118
|
+
function requireString(value, field) {
|
|
119
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
120
|
+
throw new LaneSchemaError(`Expected non-empty string for ${field}`, {
|
|
121
|
+
field,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return value;
|
|
125
|
+
}
|
|
126
|
+
export function parseMajorSchema(schemaVersion, expectedPrefix, expectedMajor) {
|
|
127
|
+
const match = new RegExp(`^${expectedPrefix}\\.v(\\d+)$`).exec(schemaVersion);
|
|
128
|
+
if (!match) {
|
|
129
|
+
throw new LaneSchemaError(`Unknown schema_version ${JSON.stringify(schemaVersion)} (expected ${expectedPrefix}.v${expectedMajor})`, { schema_version: schemaVersion, expected: `${expectedPrefix}.v${expectedMajor}` });
|
|
130
|
+
}
|
|
131
|
+
const major = Number(match[1]);
|
|
132
|
+
if (major !== expectedMajor) {
|
|
133
|
+
throw new LaneSchemaError(`Unsupported ${expectedPrefix} major version ${major} (found ${schemaVersion})`, { schema_version: schemaVersion, major });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
export function parseLaneEnvelope(raw) {
|
|
137
|
+
if (!isPlainObject(raw)) {
|
|
138
|
+
throw new LaneSchemaError("Lane envelope must be a JSON object");
|
|
139
|
+
}
|
|
140
|
+
const extra = Object.keys(raw).filter((key) => ![
|
|
141
|
+
"schema_version",
|
|
142
|
+
"decision",
|
|
143
|
+
"summary",
|
|
144
|
+
"questions",
|
|
145
|
+
"story_status",
|
|
146
|
+
"artifacts",
|
|
147
|
+
"blocked_reason",
|
|
148
|
+
].includes(key));
|
|
149
|
+
if (extra.length > 0) {
|
|
150
|
+
throw new LaneSchemaError(`Lane envelope rejects unknown properties: ${extra.join(", ")}`, { extra });
|
|
151
|
+
}
|
|
152
|
+
const schema_version = requireString(raw.schema_version, "schema_version");
|
|
153
|
+
parseMajorSchema(schema_version, "hq-lane-envelope", 1);
|
|
154
|
+
if (!isEnvelopeDecision(raw.decision)) {
|
|
155
|
+
throw new LaneSchemaError(`Unknown envelope decision ${JSON.stringify(raw.decision)}. Accepted: ${ENVELOPE_DECISIONS.join(", ")}`, { accepted: [...ENVELOPE_DECISIONS], value: raw.decision });
|
|
156
|
+
}
|
|
157
|
+
const summary = requireString(raw.summary, "summary");
|
|
158
|
+
if (typeof raw.story_status !== "string" ||
|
|
159
|
+
!STORY_STATUSES.includes(raw.story_status)) {
|
|
160
|
+
throw new LaneSchemaError(`Unknown story_status ${JSON.stringify(raw.story_status)}. Accepted: ${STORY_STATUSES.join(", ")}`, { accepted: [...STORY_STATUSES], value: raw.story_status });
|
|
161
|
+
}
|
|
162
|
+
if (!Array.isArray(raw.artifacts)) {
|
|
163
|
+
throw new LaneSchemaError("Envelope artifacts must be an array");
|
|
164
|
+
}
|
|
165
|
+
const questions = raw.questions;
|
|
166
|
+
if (raw.decision === "ask") {
|
|
167
|
+
if (!Array.isArray(questions) || questions.length === 0) {
|
|
168
|
+
throw new LaneSchemaError('questions must be non-empty when decision is "ask"');
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
else if (questions !== undefined) {
|
|
172
|
+
if (!Array.isArray(questions) || questions.length !== 0) {
|
|
173
|
+
throw new LaneSchemaError('questions must be omitted or empty unless decision is "ask"');
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (raw.decision === "blocked") {
|
|
177
|
+
if (typeof raw.blocked_reason !== "string" || raw.blocked_reason.length === 0) {
|
|
178
|
+
throw new LaneSchemaError('blocked_reason is required when decision is "blocked"');
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
else if (raw.blocked_reason !== undefined) {
|
|
182
|
+
throw new LaneSchemaError('blocked_reason is present exactly when decision is "blocked"');
|
|
183
|
+
}
|
|
184
|
+
const envelope = {
|
|
185
|
+
schema_version: ENVELOPE_SCHEMA_VERSION,
|
|
186
|
+
decision: raw.decision,
|
|
187
|
+
summary,
|
|
188
|
+
story_status: raw.story_status,
|
|
189
|
+
artifacts: raw.artifacts,
|
|
190
|
+
};
|
|
191
|
+
if (Array.isArray(questions) && questions.length > 0) {
|
|
192
|
+
envelope.questions = questions.map((item, index) => {
|
|
193
|
+
if (!isPlainObject(item)) {
|
|
194
|
+
throw new LaneSchemaError(`questions[${index}] must be an object`);
|
|
195
|
+
}
|
|
196
|
+
const options = item.options;
|
|
197
|
+
if (!Array.isArray(options) ||
|
|
198
|
+
options.some((opt) => typeof opt !== "string")) {
|
|
199
|
+
throw new LaneSchemaError(`questions[${index}].options must be string[]`);
|
|
200
|
+
}
|
|
201
|
+
const parsed = {
|
|
202
|
+
question: requireString(item.question, `questions[${index}].question`),
|
|
203
|
+
context: requireString(item.context, `questions[${index}].context`),
|
|
204
|
+
options,
|
|
205
|
+
};
|
|
206
|
+
if (item.recommended !== undefined) {
|
|
207
|
+
parsed.recommended = requireString(item.recommended, `questions[${index}].recommended`);
|
|
208
|
+
}
|
|
209
|
+
return parsed;
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
if (typeof raw.blocked_reason === "string") {
|
|
213
|
+
envelope.blocked_reason = raw.blocked_reason;
|
|
214
|
+
}
|
|
215
|
+
return envelope;
|
|
216
|
+
}
|
|
217
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -3,6 +3,33 @@ export interface DaemonConfig {
|
|
|
3
3
|
}
|
|
4
4
|
export declare function daemonConfigPath(daemonDirectory: string): string;
|
|
5
5
|
export declare function verifyHqRoot(candidate: string | undefined): string | null;
|
|
6
|
+
/**
|
|
7
|
+
* A directory the daemon may be installed for: it must carry
|
|
8
|
+
* companies/manifest.yaml (what verifyHqRoot demands at runtime) and one of
|
|
9
|
+
* the tree markers utils/hq-roots.ts accepts (core/, .claude/, personal/).
|
|
10
|
+
* A bare .claude/ directory — every repo with Claude settings has one — is
|
|
11
|
+
* not an HQ root and must never be persisted or passed as --hq-root.
|
|
12
|
+
*/
|
|
13
|
+
export declare function isInstallableHqRoot(dir: string): boolean;
|
|
14
|
+
/** Nearest ancestor of startDir (inclusive) that is an installable HQ root, realpath'd; null if none. */
|
|
15
|
+
export declare function findAncestorHqRoot(startDir: string): string | null;
|
|
16
|
+
export interface InstallHqRootResolution {
|
|
17
|
+
root: string;
|
|
18
|
+
source: "flag" | "env" | "ancestor";
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Resolve the HQ root `hq mesh daemon install` records and passes to the
|
|
22
|
+
* service. Precedence: --hq-root, then HQ_ROOT, then the nearest ancestor of
|
|
23
|
+
* cwd that is an installable HQ root. An explicit value that is not an HQ
|
|
24
|
+
* root is an error rather than a silent fallback, and with nothing
|
|
25
|
+
* qualifying the install is refused: an unverified root would be persisted
|
|
26
|
+
* and trusted by the daemon for company attribution.
|
|
27
|
+
*/
|
|
28
|
+
export declare function resolveInstallHqRoot(opts?: {
|
|
29
|
+
explicit?: string;
|
|
30
|
+
env?: NodeJS.ProcessEnv;
|
|
31
|
+
cwd?: string;
|
|
32
|
+
}): InstallHqRootResolution;
|
|
6
33
|
export declare function readDaemonConfig(daemonDirectory: string): DaemonConfig;
|
|
7
34
|
/** Record a verified local HQ checkout without trusting a caller-supplied path at runtime. */
|
|
8
35
|
export declare function registerTrustedHqRoot(daemonDirectory: string, candidate: string | undefined): DaemonConfig;
|
|
@@ -16,6 +16,81 @@ export function verifyHqRoot(candidate) {
|
|
|
16
16
|
return null;
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* A directory the daemon may be installed for: it must carry
|
|
21
|
+
* companies/manifest.yaml (what verifyHqRoot demands at runtime) and one of
|
|
22
|
+
* the tree markers utils/hq-roots.ts accepts (core/, .claude/, personal/).
|
|
23
|
+
* A bare .claude/ directory — every repo with Claude settings has one — is
|
|
24
|
+
* not an HQ root and must never be persisted or passed as --hq-root.
|
|
25
|
+
*/
|
|
26
|
+
export function isInstallableHqRoot(dir) {
|
|
27
|
+
if (verifyHqRoot(dir) === null)
|
|
28
|
+
return false;
|
|
29
|
+
return ["core", ".claude", "personal"].some((marker) => {
|
|
30
|
+
try {
|
|
31
|
+
return fs.statSync(path.join(dir, marker)).isDirectory();
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/** Nearest ancestor of startDir (inclusive) that is an installable HQ root, realpath'd; null if none. */
|
|
39
|
+
export function findAncestorHqRoot(startDir) {
|
|
40
|
+
let dir;
|
|
41
|
+
try {
|
|
42
|
+
dir = fs.realpathSync(startDir);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
dir = path.resolve(startDir);
|
|
46
|
+
}
|
|
47
|
+
for (;;) {
|
|
48
|
+
if (isInstallableHqRoot(dir))
|
|
49
|
+
return fs.realpathSync(dir);
|
|
50
|
+
const parent = path.dirname(dir);
|
|
51
|
+
if (parent === dir)
|
|
52
|
+
return null;
|
|
53
|
+
dir = parent;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Resolve the HQ root `hq mesh daemon install` records and passes to the
|
|
58
|
+
* service. Precedence: --hq-root, then HQ_ROOT, then the nearest ancestor of
|
|
59
|
+
* cwd that is an installable HQ root. An explicit value that is not an HQ
|
|
60
|
+
* root is an error rather than a silent fallback, and with nothing
|
|
61
|
+
* qualifying the install is refused: an unverified root would be persisted
|
|
62
|
+
* and trusted by the daemon for company attribution.
|
|
63
|
+
*/
|
|
64
|
+
export function resolveInstallHqRoot(opts = {}) {
|
|
65
|
+
const env = opts.env ?? process.env;
|
|
66
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
67
|
+
const explicit = opts.explicit?.trim();
|
|
68
|
+
if (explicit) {
|
|
69
|
+
if (!isInstallableHqRoot(explicit)) {
|
|
70
|
+
throw expectedError(`--hq-root ${explicit} is not an HQ root (needs companies/manifest.yaml plus core/, .claude/, or personal/).`);
|
|
71
|
+
}
|
|
72
|
+
return { root: fs.realpathSync(explicit), source: "flag" };
|
|
73
|
+
}
|
|
74
|
+
const fromEnv = env.HQ_ROOT?.trim();
|
|
75
|
+
if (fromEnv) {
|
|
76
|
+
if (!isInstallableHqRoot(fromEnv)) {
|
|
77
|
+
throw expectedError(`HQ_ROOT=${fromEnv} is not an HQ root (needs companies/manifest.yaml plus core/, .claude/, or personal/). ` +
|
|
78
|
+
`Fix HQ_ROOT or pass --hq-root <path>.`);
|
|
79
|
+
}
|
|
80
|
+
return { root: fs.realpathSync(fromEnv), source: "env" };
|
|
81
|
+
}
|
|
82
|
+
const ancestor = findAncestorHqRoot(cwd);
|
|
83
|
+
if (!ancestor) {
|
|
84
|
+
throw expectedError(`No HQ root found above ${cwd} (a directory with companies/manifest.yaml plus core/, .claude/, or personal/). ` +
|
|
85
|
+
`Run from inside your HQ tree or pass --hq-root <path>.`);
|
|
86
|
+
}
|
|
87
|
+
return { root: ancestor, source: "ancestor" };
|
|
88
|
+
}
|
|
89
|
+
function expectedError(message) {
|
|
90
|
+
const err = new Error(message);
|
|
91
|
+
err.expected = true;
|
|
92
|
+
return err;
|
|
93
|
+
}
|
|
19
94
|
export function readDaemonConfig(daemonDirectory) {
|
|
20
95
|
try {
|
|
21
96
|
const raw = JSON.parse(fs.readFileSync(daemonConfigPath(daemonDirectory), "utf8"));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { DAEMON_DIRNAME, DAEMON_LOG_MAX_BYTES, DAEMON_LOG_NAME, DAEMON_PID_NAME, DAEMON_STATE_NAME, LAUNCHD_LABEL, SYSTEMD_UNIT_NAME, daemonDir, daemonLogPath, daemonPidPath, daemonStatePath, sessionBoardPath, } from "./paths.js";
|
|
1
|
+
export { DAEMON_DIRNAME, DAEMON_LOG_MAX_BYTES, DAEMON_LOG_NAME, DAEMON_PID_NAME, DAEMON_STATE_NAME, LAUNCHD_LABEL, SYSTEMD_UNIT_NAME, WINDOWS_TASK_NAME, daemonDir, daemonLogPath, daemonPidPath, daemonStatePath, sessionBoardPath, } from "./paths.js";
|
|
2
2
|
export { acquirePidLock, defaultPidLockDeps, parsePidLock, pidLockStatus, readPidLock, releasePidLock, resolveDaemonDir, } from "./pid-lock.js";
|
|
3
3
|
export type { PidLockDeps, PidLockRecord, PidLockResult } from "./pid-lock.js";
|
|
4
4
|
export { CREDENTIAL_RENEWAL_FRACTION, DEFAULT_REFUSED_RETRY_MS, MAX_RETRY_DELAY_MS, MAX_TIMER_DELAY_MS, MQTT_KEEPALIVE_SECONDS, REALTIME_CREDENTIALS_PATH, REFUSED_RETRY_ENV, CredentialRenewalManager, CredentialVendError, classifyCredentialVendFailure, clampRetryDelayMs, createContract3Fetcher, defaultRefusedRetryMs, extractVendErrorCode, isCredentialVendRefused, normalizeContract3Bundle, parseRetryAfterMs, realTimerHost, refusedRetryDelayMs, renewalDelayMs, scheduleBoundedTimeout, } from "./credentials.js";
|
|
@@ -12,9 +12,10 @@ export type { DaemonStateFile, PresenceRefusalState } from "./state.js";
|
|
|
12
12
|
export { appendDaemonLog, daemonLogLine, ensureDaemonLog, resolveDaemonAssetDir, rotateDaemonLogIfNeeded, } from "./log.js";
|
|
13
13
|
export { BOARD_REFRESH_INTERVAL_MS, createVaultBoardReader, formatBoardMarkdown, refreshBoundSessionBoards, writeBoardMarkdown, } from "./board-refresh.js";
|
|
14
14
|
export type { BoardReader, BoardRefreshDeps, BoardStorySnapshot } from "./board-refresh.js";
|
|
15
|
-
export { buildInstallPaths, daemonServiceStatus, detectPlatform, installDaemonService, readAssetTemplate, restartDaemonServiceIfInstalled, renderLaunchdPlist, renderSystemdSystemUnit, renderSystemdUserUnit, resolveHqBinary, resolveNodeBinary, uninstallDaemonService, } from "./install.js";
|
|
15
|
+
export { buildInstallPaths, daemonServiceStatus, detectPlatform, installDaemonService, readAssetTemplate, restartDaemonServiceIfInstalled, renderLaunchdPlist, renderSystemdSystemUnit, renderSystemdUserUnit, resolveHqBinary, resolveNodeBinary, parseSchtasksLastResult, parseWindowsTaskHqRoot, readRecordedWindowsHqRoot, schtasksLastResultFailed, uninstallDaemonService, windowsLaunchMode, windowsLogonNotice, } from "./install.js";
|
|
16
16
|
export type { DaemonInstallPlatform, InstallPaths, InstallResult, } from "./install.js";
|
|
17
|
-
export { daemonConfigPath, readDaemonConfig, registerTrustedHqRoot, verifyHqRoot } from "./config.js";
|
|
17
|
+
export { daemonConfigPath, findAncestorHqRoot, isInstallableHqRoot, readDaemonConfig, registerTrustedHqRoot, resolveInstallHqRoot, verifyHqRoot, } from "./config.js";
|
|
18
|
+
export type { InstallHqRootResolution } from "./config.js";
|
|
18
19
|
export type { DaemonConfig } from "./config.js";
|
|
19
20
|
export { UNHEALTHY_SPOOL_AGE_MS, collectDaemonDoctor, formatDaemonDoctor, } from "./doctor.js";
|
|
20
21
|
export type { DaemonDoctorDeps, DaemonDoctorReport } from "./doctor.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { DAEMON_DIRNAME, DAEMON_LOG_MAX_BYTES, DAEMON_LOG_NAME, DAEMON_PID_NAME, DAEMON_STATE_NAME, LAUNCHD_LABEL, SYSTEMD_UNIT_NAME, daemonDir, daemonLogPath, daemonPidPath, daemonStatePath, sessionBoardPath, } from "./paths.js";
|
|
1
|
+
export { DAEMON_DIRNAME, DAEMON_LOG_MAX_BYTES, DAEMON_LOG_NAME, DAEMON_PID_NAME, DAEMON_STATE_NAME, LAUNCHD_LABEL, SYSTEMD_UNIT_NAME, WINDOWS_TASK_NAME, daemonDir, daemonLogPath, daemonPidPath, daemonStatePath, sessionBoardPath, } from "./paths.js";
|
|
2
2
|
export { acquirePidLock, defaultPidLockDeps, parsePidLock, pidLockStatus, readPidLock, releasePidLock, resolveDaemonDir, } from "./pid-lock.js";
|
|
3
3
|
export { CREDENTIAL_RENEWAL_FRACTION, DEFAULT_REFUSED_RETRY_MS, MAX_RETRY_DELAY_MS, MAX_TIMER_DELAY_MS, MQTT_KEEPALIVE_SECONDS, REALTIME_CREDENTIALS_PATH, REFUSED_RETRY_ENV, CredentialRenewalManager, CredentialVendError, classifyCredentialVendFailure, clampRetryDelayMs, createContract3Fetcher, defaultRefusedRetryMs, extractVendErrorCode, isCredentialVendRefused, normalizeContract3Bundle, parseRetryAfterMs, realTimerHost, refusedRetryDelayMs, renewalDelayMs, scheduleBoundedTimeout, } from "./credentials.js";
|
|
4
4
|
export { amzDateOf, hex, presignIotWssUrl, rfc3986Encode } from "./presign.js";
|
|
@@ -6,8 +6,8 @@ export { CLOSE_LOG_ALWAYS_COUNT, CLOSE_LOG_INTERVAL_MS, CONNECT_STABLE_GRACE_MS,
|
|
|
6
6
|
export { defaultDaemonState, patchDaemonState, readDaemonState, writeDaemonState, } from "./state.js";
|
|
7
7
|
export { appendDaemonLog, daemonLogLine, ensureDaemonLog, resolveDaemonAssetDir, rotateDaemonLogIfNeeded, } from "./log.js";
|
|
8
8
|
export { BOARD_REFRESH_INTERVAL_MS, createVaultBoardReader, formatBoardMarkdown, refreshBoundSessionBoards, writeBoardMarkdown, } from "./board-refresh.js";
|
|
9
|
-
export { buildInstallPaths, daemonServiceStatus, detectPlatform, installDaemonService, readAssetTemplate, restartDaemonServiceIfInstalled, renderLaunchdPlist, renderSystemdSystemUnit, renderSystemdUserUnit, resolveHqBinary, resolveNodeBinary, uninstallDaemonService, } from "./install.js";
|
|
10
|
-
export { daemonConfigPath, readDaemonConfig, registerTrustedHqRoot, verifyHqRoot } from "./config.js";
|
|
9
|
+
export { buildInstallPaths, daemonServiceStatus, detectPlatform, installDaemonService, readAssetTemplate, restartDaemonServiceIfInstalled, renderLaunchdPlist, renderSystemdSystemUnit, renderSystemdUserUnit, resolveHqBinary, resolveNodeBinary, parseSchtasksLastResult, parseWindowsTaskHqRoot, readRecordedWindowsHqRoot, schtasksLastResultFailed, uninstallDaemonService, windowsLaunchMode, windowsLogonNotice, } from "./install.js";
|
|
10
|
+
export { daemonConfigPath, findAncestorHqRoot, isInstallableHqRoot, readDaemonConfig, registerTrustedHqRoot, resolveInstallHqRoot, verifyHqRoot, } from "./config.js";
|
|
11
11
|
export { UNHEALTHY_SPOOL_AGE_MS, collectDaemonDoctor, formatDaemonDoctor, } from "./doctor.js";
|
|
12
12
|
export { scopeBundleToAgentIdentity, } from "./credentials.js";
|
|
13
13
|
export { FLUSH_INTERVAL_MS, DAEMON_VERSION_DRIFT_EXIT_CODE, SPOOL_DEBOUNCE_MS, runMeshDaemon, } from "./run.js";
|
|
@@ -4,13 +4,36 @@
|
|
|
4
4
|
* - macOS: LaunchAgent ai.getindigo.hq-mesh-daemon
|
|
5
5
|
* - Linux: systemd user unit hq-mesh-daemon.service
|
|
6
6
|
* (documented fallback: root-owned unit running as ec2-user)
|
|
7
|
+
* - Windows: per-user Scheduled Task ai.getindigo.hq-mesh-daemon registered
|
|
8
|
+
* from a rendered task XML via schtasks.exe (no native dependencies)
|
|
7
9
|
*
|
|
8
10
|
* The unit embeds the hq binary resolved at install time. Logs go to
|
|
9
11
|
* ~/.hq/work-mesh/daemon/log (rotation at 5MB handled by the daemon).
|
|
10
12
|
* Tests must not actually install services — use a temp HOME/prefix.
|
|
11
13
|
*/
|
|
12
14
|
import * as fs from "node:fs";
|
|
13
|
-
export type DaemonInstallPlatform = "darwin" | "linux" | "other";
|
|
15
|
+
export type DaemonInstallPlatform = "darwin" | "linux" | "win32" | "other";
|
|
16
|
+
/** Result of one schtasks.exe invocation (tests inject; never the real binary). */
|
|
17
|
+
export interface SchtasksResult {
|
|
18
|
+
status: number | null;
|
|
19
|
+
stdout: string;
|
|
20
|
+
stderr: string;
|
|
21
|
+
}
|
|
22
|
+
export type SchtasksRunner = (args: string[]) => SchtasksResult;
|
|
23
|
+
/**
|
|
24
|
+
* `schtasks /End` terminates the task's root process only. The daemon is
|
|
25
|
+
* launched through `conhost.exe --headless`, so ending the task kills conhost
|
|
26
|
+
* and leaves node running as an orphan that still holds the pid lock. Stop
|
|
27
|
+
* the recorded daemon pid explicitly. Returns the pid that was signalled, or
|
|
28
|
+
* null when no live daemon pid was recorded.
|
|
29
|
+
*/
|
|
30
|
+
export declare function stopWindowsDaemonProcess(dir: string, deps?: {
|
|
31
|
+
readFileSync?: typeof fs.readFileSync;
|
|
32
|
+
existsSync?: typeof fs.existsSync;
|
|
33
|
+
kill?: (pid: number) => void;
|
|
34
|
+
selfPid?: number;
|
|
35
|
+
}): number | null;
|
|
36
|
+
export declare const defaultSchtasks: SchtasksRunner;
|
|
14
37
|
export interface InstallPaths {
|
|
15
38
|
home: string;
|
|
16
39
|
hqBinary: string;
|
|
@@ -21,10 +44,22 @@ export interface InstallPaths {
|
|
|
21
44
|
plistPath: string;
|
|
22
45
|
systemdUserDir: string;
|
|
23
46
|
systemdUnitPath: string;
|
|
47
|
+
/** Windows: rendered Task Scheduler XML kept under the daemon dir. */
|
|
48
|
+
windowsTaskXmlPath: string;
|
|
49
|
+
/** Windows: `DOMAIN\user` the task is registered for (logon trigger + principal). */
|
|
50
|
+
windowsUser: string;
|
|
51
|
+
/**
|
|
52
|
+
* Windows: `conhost.exe` used as `conhost --headless node hq ...` so the
|
|
53
|
+
* logon-triggered daemon gets no visible console window. Undefined when the
|
|
54
|
+
* host has no conhost (the task then runs node directly).
|
|
55
|
+
*/
|
|
56
|
+
windowsHeadlessLauncher?: string;
|
|
24
57
|
/** When true, render the root/ec2-user system unit instead of user unit. */
|
|
25
58
|
systemUser?: string;
|
|
26
59
|
/** Verified HQ root to persist and pass to the installed service. */
|
|
27
60
|
hqRoot?: string;
|
|
61
|
+
/** How hqRoot was chosen (shown in the Windows install confirmation). */
|
|
62
|
+
hqRootSource?: "flag" | "env" | "ancestor";
|
|
28
63
|
}
|
|
29
64
|
export interface InstallResult {
|
|
30
65
|
platform: DaemonInstallPlatform;
|
|
@@ -34,6 +69,22 @@ export interface InstallResult {
|
|
|
34
69
|
dest?: string;
|
|
35
70
|
running?: boolean;
|
|
36
71
|
message: string;
|
|
72
|
+
/**
|
|
73
|
+
* Windows status: the task's "Last Result" code from `schtasks /Query /V`
|
|
74
|
+
* (normalized to unsigned 32-bit), or null when unavailable.
|
|
75
|
+
*/
|
|
76
|
+
lastResult?: number | null;
|
|
77
|
+
/** Windows status: true when lastResult indicates the last start failed. */
|
|
78
|
+
lastRunFailed?: boolean;
|
|
79
|
+
/** One plain line of operator context (Windows: logged-on-only visibility). */
|
|
80
|
+
notice?: string;
|
|
81
|
+
/**
|
|
82
|
+
* Windows status: HQ root read back from the registered task XML (else the
|
|
83
|
+
* persisted daemon config), never re-resolved from cwd. Null when unknown.
|
|
84
|
+
*/
|
|
85
|
+
recordedHqRoot?: string | null;
|
|
86
|
+
/** Windows status: whether recordedHqRoot still passes isInstallableHqRoot. */
|
|
87
|
+
rootValid?: boolean;
|
|
37
88
|
}
|
|
38
89
|
/** Best-effort restart result used after an in-place CLI upgrade. */
|
|
39
90
|
export interface RestartDaemonResult {
|
|
@@ -54,6 +105,8 @@ export declare function restartDaemonServiceIfInstalled(opts?: {
|
|
|
54
105
|
run?: (command: string, args: string[]) => {
|
|
55
106
|
status: number | null;
|
|
56
107
|
};
|
|
108
|
+
/** Windows: terminate the recorded daemon pid after `schtasks /End` (tests inject). */
|
|
109
|
+
kill?: (pid: number) => void;
|
|
57
110
|
}): RestartDaemonResult;
|
|
58
111
|
export declare function detectPlatform(platform?: NodeJS.Platform): DaemonInstallPlatform;
|
|
59
112
|
/**
|
|
@@ -76,7 +129,34 @@ export declare function buildInstallPaths(opts: {
|
|
|
76
129
|
argv?: string[];
|
|
77
130
|
execPath?: string;
|
|
78
131
|
hqRoot?: string;
|
|
132
|
+
hqRootSource?: "flag" | "env" | "ancestor";
|
|
133
|
+
/** Windows account (`DOMAIN\user`) the task runs as; defaults to the current user. */
|
|
134
|
+
windowsUser?: string;
|
|
135
|
+
/** Windows headless launcher override (tests); defaults to %SystemRoot%\System32\conhost.exe when present. */
|
|
136
|
+
windowsHeadlessLauncher?: string | null;
|
|
79
137
|
}): InstallPaths;
|
|
138
|
+
/** `DOMAIN\user` for the current account, as Task Scheduler expects. */
|
|
139
|
+
export declare function resolveWindowsUser(env?: NodeJS.ProcessEnv): string;
|
|
140
|
+
/**
|
|
141
|
+
* `conhost.exe --headless` is undocumented. It shipped with the ConPTY
|
|
142
|
+
* conhost in Windows 10 1809 (build 17763); older conhost builds reject the
|
|
143
|
+
* flag and the task would fail on every logon. Gate on the kernel build
|
|
144
|
+
* reported by os.release() ("10.0.<build>") plus a presence check.
|
|
145
|
+
*/
|
|
146
|
+
export declare const WINDOWS_CONHOST_HEADLESS_MIN_BUILD = 17763;
|
|
147
|
+
/** Build number from an os.release() string such as "10.0.26200"; null if unparsable. */
|
|
148
|
+
export declare function parseWindowsBuild(release: string): number | null;
|
|
149
|
+
/**
|
|
150
|
+
* `conhost.exe --headless` (Windows 10 1809+) runs a console app with no
|
|
151
|
+
* window. Returns the launcher path only when conhost.exe exists under
|
|
152
|
+
* SystemRoot and the host build is new enough to honour `--headless`.
|
|
153
|
+
*/
|
|
154
|
+
export declare function resolveWindowsHeadlessLauncher(env?: NodeJS.ProcessEnv, existsSync?: typeof fs.existsSync, release?: string): string | undefined;
|
|
155
|
+
export type WindowsLaunchMode = "conhost-headless" | "direct";
|
|
156
|
+
/** Which launch wrapper the rendered task uses (recorded in the install message). */
|
|
157
|
+
export declare function windowsLaunchMode(paths: Pick<InstallPaths, "windowsHeadlessLauncher">): WindowsLaunchMode;
|
|
158
|
+
/** The task uses the interactive token, so it exists only inside the user's logon session. */
|
|
159
|
+
export declare function windowsLogonNotice(paths: Pick<InstallPaths, "windowsUser">): string;
|
|
80
160
|
export declare function renderLaunchdPlist(paths: InstallPaths): string;
|
|
81
161
|
export declare function renderSystemdUserUnit(paths: InstallPaths): string;
|
|
82
162
|
/**
|
|
@@ -84,6 +164,55 @@ export declare function renderSystemdUserUnit(paths: InstallPaths): string;
|
|
|
84
164
|
* Documented fallback — written only when --system-user is passed.
|
|
85
165
|
*/
|
|
86
166
|
export declare function renderSystemdSystemUnit(paths: InstallPaths): string;
|
|
167
|
+
/**
|
|
168
|
+
* Windows Task Scheduler definition (schtasks /Create /XML). Runs at logon of
|
|
169
|
+
* the registering user, hidden, no execution time limit, one instance, and
|
|
170
|
+
* restarts every minute on failure. HOME/USERPROFILE come from the user's own
|
|
171
|
+
* session. Task Scheduler XML has no environment block, so the HQ root the
|
|
172
|
+
* install was run for is passed explicitly as `mesh daemon run --hq-root
|
|
173
|
+
* <path>` (the launchd/systemd equivalent of Environment=HQ_ROOT) and is also
|
|
174
|
+
* persisted in the daemon config as a trusted root.
|
|
175
|
+
*
|
|
176
|
+
* LogonType is InteractiveToken: S4U ("run whether user is logged on or not")
|
|
177
|
+
* needs elevation to register, and the interactive token is what an
|
|
178
|
+
* unprivileged `hq mesh daemon install` can create. As a consequence the task
|
|
179
|
+
* runs only while the registering user is logged on. The console window that
|
|
180
|
+
* an interactive-token task would otherwise open is avoided by launching
|
|
181
|
+
* through `conhost.exe --headless` when the host supports it (see
|
|
182
|
+
* resolveWindowsHeadlessLauncher); otherwise node is run directly.
|
|
183
|
+
*/
|
|
184
|
+
export declare function renderWindowsTaskXml(paths: InstallPaths): string;
|
|
185
|
+
/** schtasks stores task XML as UTF-16 LE with BOM; match it so /XML parses. */
|
|
186
|
+
export declare function encodeWindowsTaskXml(rendered: string): Buffer;
|
|
187
|
+
/** Status column of `schtasks /Query /FO CSV /NH`: "TaskName","Next Run Time","Status". */
|
|
188
|
+
export declare function parseSchtasksCsvStatus(stdout: string): string | null;
|
|
189
|
+
/**
|
|
190
|
+
* "Last Result" of `schtasks /Query /TN <name> /V /FO CSV`, normalized to an
|
|
191
|
+
* unsigned 32-bit code (schtasks prints HRESULT/NTSTATUS values as signed
|
|
192
|
+
* decimals, e.g. -2147024894 for 0x80070002). Prefers the English header
|
|
193
|
+
* when present; otherwise uses the fixed column position. Null when the
|
|
194
|
+
* output carries no task row or the cell is not numeric.
|
|
195
|
+
*/
|
|
196
|
+
export declare function parseSchtasksLastResult(stdout: string): number | null;
|
|
197
|
+
/**
|
|
198
|
+
* Task Scheduler informational results mean "no failed start": 0 is success,
|
|
199
|
+
* and every SCHED_S_* code (ready, running, not yet run, terminated, queued,
|
|
200
|
+
* ...) is a success-severity HRESULT (bit 31 clear) in FACILITY_ITF (0x4)
|
|
201
|
+
* with code 0x1300-0x13FF, i.e. 0x00041300-0x000413FF. Everything else
|
|
202
|
+
* (0x8007xxxx, 0xC000xxxx, process exit codes such as 1) is a failed start.
|
|
203
|
+
*/
|
|
204
|
+
export declare function schtasksLastResultFailed(code: number | null): boolean;
|
|
205
|
+
/** `--hq-root <path>` from a rendered task's <Arguments> (XML-escaped, optionally quoted). */
|
|
206
|
+
export declare function parseWindowsTaskHqRoot(xml: string): string | null;
|
|
207
|
+
/**
|
|
208
|
+
* The HQ root the installed Windows task actually carries: the task XML on
|
|
209
|
+
* disk first, else the first persisted trusted root. Reads state only; never
|
|
210
|
+
* resolves from cwd, so status reports what install recorded.
|
|
211
|
+
*/
|
|
212
|
+
export declare function readRecordedWindowsHqRoot(paths: Pick<InstallPaths, "windowsTaskXmlPath" | "daemonDir">, deps?: {
|
|
213
|
+
existsSync?: typeof fs.existsSync;
|
|
214
|
+
readFileSync?: typeof fs.readFileSync;
|
|
215
|
+
}): string | null;
|
|
87
216
|
export declare function installDaemonService(opts: {
|
|
88
217
|
platform?: DaemonInstallPlatform;
|
|
89
218
|
paths?: InstallPaths;
|
|
@@ -97,6 +226,10 @@ export declare function installDaemonService(opts: {
|
|
|
97
226
|
reloadSystemd?: () => {
|
|
98
227
|
status: number | null;
|
|
99
228
|
};
|
|
229
|
+
/** Windows: schtasks.exe runner (tests inject; never the real binary). */
|
|
230
|
+
schtasks?: SchtasksRunner;
|
|
231
|
+
/** Windows: terminate the recorded daemon pid when replacing a task (tests inject). */
|
|
232
|
+
kill?: (pid: number) => void;
|
|
100
233
|
}): InstallResult;
|
|
101
234
|
export declare function uninstallDaemonService(opts: {
|
|
102
235
|
platform?: DaemonInstallPlatform;
|
|
@@ -104,6 +237,10 @@ export declare function uninstallDaemonService(opts: {
|
|
|
104
237
|
unlinkSync?: typeof fs.unlinkSync;
|
|
105
238
|
existsSync?: typeof fs.existsSync;
|
|
106
239
|
deactivate?: (platform: DaemonInstallPlatform, dest: string) => void;
|
|
240
|
+
/** Windows: schtasks.exe runner (tests inject; never the real binary). */
|
|
241
|
+
schtasks?: SchtasksRunner;
|
|
242
|
+
/** Windows: terminate the recorded daemon pid after `schtasks /End` (tests inject). */
|
|
243
|
+
kill?: (pid: number) => void;
|
|
107
244
|
}): InstallResult;
|
|
108
245
|
export declare function daemonServiceStatus(opts: {
|
|
109
246
|
platform?: DaemonInstallPlatform;
|
|
@@ -111,6 +248,11 @@ export declare function daemonServiceStatus(opts: {
|
|
|
111
248
|
existsSync?: typeof fs.existsSync;
|
|
112
249
|
/** Optional live probe (launchctl/systemctl); tests inject. */
|
|
113
250
|
isLoaded?: (platform: DaemonInstallPlatform, dest: string) => boolean;
|
|
251
|
+
/** Windows: schtasks.exe runner (tests inject; never the real binary). */
|
|
252
|
+
schtasks?: SchtasksRunner;
|
|
253
|
+
/** Windows: liveness probe for the daemon pid lock (tests inject). */
|
|
254
|
+
isProcessAlive?: (pid: number) => boolean;
|
|
255
|
+
readFileSync?: typeof fs.readFileSync;
|
|
114
256
|
}): InstallResult;
|
|
115
257
|
/** Read baked template files from assets/ (optional; renderers are primary). */
|
|
116
258
|
export declare function readAssetTemplate(name: "launchd.plist.template" | "systemd.user.service.template" | "systemd.system.service.template"): string | null;
|