@indigoai-us/hq-cli 5.115.6 → 5.116.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 +72 -11
- package/dist/command-catalog.generated.d.ts +162 -2
- package/dist/command-catalog.generated.js +205 -2
- package/dist/command-registration-plan.d.ts +6 -0
- package/dist/command-registration-plan.js +1 -0
- package/dist/commands/agent-enroll.d.ts +105 -0
- package/dist/commands/agent-enroll.js +273 -0
- package/dist/commands/agent-kit.d.ts +53 -0
- package/dist/commands/agent-kit.js +260 -0
- package/dist/commands/agent-mcp.d.ts +22 -0
- package/dist/commands/agent-mcp.js +104 -0
- package/dist/commands/agent-probe.d.ts +71 -0
- package/dist/commands/agent-probe.js +294 -0
- package/dist/commands/agent.d.ts +12 -0
- package/dist/commands/agent.js +23 -0
- package/dist/commands/agents.d.ts +27 -0
- package/dist/commands/agents.js +280 -6
- package/dist/commands/secrets.js +17 -5
- package/dist/lib/agent-kit/creds.d.ts +60 -0
- package/dist/lib/agent-kit/creds.js +123 -0
- package/dist/lib/agent-kit/kit-config.d.ts +29 -0
- package/dist/lib/agent-kit/kit-config.js +54 -0
- package/dist/lib/agent-kit/log.d.ts +17 -0
- package/dist/lib/agent-kit/log.js +46 -0
- package/dist/lib/agent-kit/mcp/jsonrpc.d.ts +84 -0
- package/dist/lib/agent-kit/mcp/jsonrpc.js +164 -0
- package/dist/lib/agent-kit/mcp/tools.d.ts +45 -0
- package/dist/lib/agent-kit/mcp/tools.js +280 -0
- package/dist/lib/agent-kit/paths.d.ts +42 -0
- package/dist/lib/agent-kit/paths.js +56 -0
- package/dist/lib/agent-kit/run/heartbeat.d.ts +52 -0
- package/dist/lib/agent-kit/run/heartbeat.js +97 -0
- package/dist/lib/agent-kit/run/inbox.d.ts +59 -0
- package/dist/lib/agent-kit/run/inbox.js +152 -0
- package/dist/lib/agent-kit/run/mesh-listener.d.ts +58 -0
- package/dist/lib/agent-kit/run/mesh-listener.js +193 -0
- package/dist/lib/agent-kit/run/sync.d.ts +33 -0
- package/dist/lib/agent-kit/run/sync.js +58 -0
- package/dist/lib/agent-kit/services.d.ts +21 -0
- package/dist/lib/agent-kit/services.js +46 -0
- package/dist/lib/agent-kit/skills.d.ts +18 -0
- package/dist/lib/agent-kit/skills.js +149 -0
- package/dist/lib/service-manager/index.d.ts +43 -0
- package/dist/lib/service-manager/index.js +114 -0
- package/dist/lib/service-manager/launchd.d.ts +23 -0
- package/dist/lib/service-manager/launchd.js +81 -0
- package/dist/lib/service-manager/systemd.d.ts +19 -0
- package/dist/lib/service-manager/systemd.js +72 -0
- package/dist/lib/service-manager/types.d.ts +32 -0
- package/dist/lib/service-manager/types.js +26 -0
- package/dist/utils/self-update.js +2 -30
- package/dist/utils/update-command-supervisor.cjs +194 -0
- package/dist/utils/version-gate.d.ts +18 -0
- package/dist/utils/version-gate.js +126 -7
- package/package.json +2 -2
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kit service: work-mesh doorbell listener.
|
|
3
|
+
*
|
|
4
|
+
* Policy hq-work-mesh-source-of-truth: the work mesh (REST) is the source of
|
|
5
|
+
* truth; MQTT carries IDS-ONLY doorbells on the agent's personal topics
|
|
6
|
+
* `hq/{agt_*}/{dm,work,sessions,notifications}`. This listener is the cache
|
|
7
|
+
* writer: on any doorbell it refetches through the REST API into
|
|
8
|
+
* ~/.hq/work-mesh/cache (warmMeshConversationCache) — message bodies are
|
|
9
|
+
* never taken from MQTT. A periodic refresh covers missed doorbells.
|
|
10
|
+
*
|
|
11
|
+
* Credentials come from the same contract-3 vend the mesh daemon uses
|
|
12
|
+
* (POST /v1/realtime/credentials); the WSS URL is SigV4-presigned. The
|
|
13
|
+
* connection is rebuilt before the vended credentials expire. `component-mesh`
|
|
14
|
+
* is stamped ok while subscribed and refreshing, error otherwise.
|
|
15
|
+
*/
|
|
16
|
+
import mqtt from "mqtt";
|
|
17
|
+
import { warmMeshConversationCache } from "../../mesh/api.js";
|
|
18
|
+
import { createContract3Fetcher, MQTT_KEEPALIVE_SECONDS, renewalDelayMs, } from "../../mesh/live/daemon/credentials.js";
|
|
19
|
+
import { presignIotWssUrl } from "../../mesh/live/daemon/presign.js";
|
|
20
|
+
import { writeComponentStatus } from "../creds.js";
|
|
21
|
+
export const DOORBELL_KINDS = ["dm", "work", "sessions", "notifications"];
|
|
22
|
+
export const DOORBELL_DEBOUNCE_MS = 2_000;
|
|
23
|
+
export const RECONNECT_BASE_MS = 1_000;
|
|
24
|
+
export const RECONNECT_MAX_MS = 60_000;
|
|
25
|
+
export function doorbellTopics(actorUid) {
|
|
26
|
+
return DOORBELL_KINDS.map((k) => `hq/${actorUid}/${k}`);
|
|
27
|
+
}
|
|
28
|
+
function backoff(attempt, random) {
|
|
29
|
+
const cap = Math.min(RECONNECT_MAX_MS, RECONNECT_BASE_MS * 2 ** attempt);
|
|
30
|
+
return Math.max(RECONNECT_BASE_MS, Math.floor(random() * cap));
|
|
31
|
+
}
|
|
32
|
+
export async function startMeshListener(deps) {
|
|
33
|
+
const now = deps.now ?? (() => new Date());
|
|
34
|
+
const random = deps.random ?? Math.random;
|
|
35
|
+
const setT = deps.setTimeout ?? ((fn, ms) => setTimeout(fn, ms));
|
|
36
|
+
const clearT = deps.clearTimeout ?? ((h) => clearTimeout(h));
|
|
37
|
+
const connect = deps.connect ?? ((url, opts) => mqtt.connect(url, opts));
|
|
38
|
+
const refetch = deps.refetch ?? ((token, uid) => warmMeshConversationCache(token, uid));
|
|
39
|
+
const fetchCredentials = deps.fetchCredentials ??
|
|
40
|
+
(async () => {
|
|
41
|
+
const token = await deps.getToken();
|
|
42
|
+
return createContract3Fetcher({ token, baseUrl: deps.apiBaseUrl })();
|
|
43
|
+
});
|
|
44
|
+
let state = "idle";
|
|
45
|
+
let client = null;
|
|
46
|
+
let stopped = false;
|
|
47
|
+
let attempt = 0;
|
|
48
|
+
let debounce = null;
|
|
49
|
+
let renewal = null;
|
|
50
|
+
let periodic = null;
|
|
51
|
+
let refetching = null;
|
|
52
|
+
let pendingReason = null;
|
|
53
|
+
const doRefetch = async (reason) => {
|
|
54
|
+
if (refetching) {
|
|
55
|
+
pendingReason = reason;
|
|
56
|
+
return refetching;
|
|
57
|
+
}
|
|
58
|
+
refetching = (async () => {
|
|
59
|
+
try {
|
|
60
|
+
const token = await deps.getToken();
|
|
61
|
+
await refetch(token, deps.agentUid);
|
|
62
|
+
writeComponentStatus(deps.paths, "mesh", state === "subscribed" ? "ok" : "error");
|
|
63
|
+
deps.log("info", `cache refetched (${reason})`);
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
writeComponentStatus(deps.paths, "mesh", "error");
|
|
67
|
+
deps.log("error", `refetch failed (${reason}): ${err instanceof Error ? err.message : String(err)}`);
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
refetching = null;
|
|
71
|
+
}
|
|
72
|
+
if (pendingReason) {
|
|
73
|
+
const next = pendingReason;
|
|
74
|
+
pendingReason = null;
|
|
75
|
+
await doRefetch(next);
|
|
76
|
+
}
|
|
77
|
+
})();
|
|
78
|
+
return refetching;
|
|
79
|
+
};
|
|
80
|
+
const ring = (topic) => {
|
|
81
|
+
if (debounce)
|
|
82
|
+
clearT(debounce);
|
|
83
|
+
debounce = setT(() => {
|
|
84
|
+
debounce = null;
|
|
85
|
+
void doRefetch(`doorbell ${topic}`);
|
|
86
|
+
}, DOORBELL_DEBOUNCE_MS);
|
|
87
|
+
};
|
|
88
|
+
const schedulePeriodic = () => {
|
|
89
|
+
if (periodic)
|
|
90
|
+
clearT(periodic);
|
|
91
|
+
periodic = setT(() => {
|
|
92
|
+
periodic = null;
|
|
93
|
+
void doRefetch("periodic").then(schedulePeriodic);
|
|
94
|
+
}, deps.refreshMs);
|
|
95
|
+
};
|
|
96
|
+
const scheduleReconnect = () => {
|
|
97
|
+
if (stopped)
|
|
98
|
+
return;
|
|
99
|
+
const delay = backoff(attempt, random);
|
|
100
|
+
attempt += 1;
|
|
101
|
+
deps.log("warn", `mqtt reconnect in ${delay}ms (attempt ${attempt})`);
|
|
102
|
+
setT(() => void connectOnce(), delay);
|
|
103
|
+
};
|
|
104
|
+
const connectOnce = async () => {
|
|
105
|
+
if (stopped)
|
|
106
|
+
return;
|
|
107
|
+
state = "connecting";
|
|
108
|
+
let bundle;
|
|
109
|
+
try {
|
|
110
|
+
bundle = await fetchCredentials();
|
|
111
|
+
if (bundle.actorUid !== deps.agentUid) {
|
|
112
|
+
throw new Error(`realtime vend is for ${bundle.actorUid}, expected ${deps.agentUid}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
writeComponentStatus(deps.paths, "mesh", "error");
|
|
117
|
+
deps.log("error", `credential vend failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
118
|
+
scheduleReconnect();
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const url = presignIotWssUrl(bundle.credentials, bundle.iotEndpoint, bundle.region, now());
|
|
122
|
+
const c = connect(url, {
|
|
123
|
+
clientId: bundle.clientId,
|
|
124
|
+
keepalive: MQTT_KEEPALIVE_SECONDS,
|
|
125
|
+
clean: true,
|
|
126
|
+
reconnectPeriod: 0,
|
|
127
|
+
protocolVersion: 4,
|
|
128
|
+
});
|
|
129
|
+
client = c;
|
|
130
|
+
let settled = false;
|
|
131
|
+
c.on("connect", () => {
|
|
132
|
+
c.subscribe(doorbellTopics(bundle.actorUid), { qos: 1 }, (err) => {
|
|
133
|
+
if (err) {
|
|
134
|
+
deps.log("error", `subscribe failed: ${err.message}`);
|
|
135
|
+
c.end(true);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
settled = true;
|
|
139
|
+
attempt = 0;
|
|
140
|
+
state = "subscribed";
|
|
141
|
+
writeComponentStatus(deps.paths, "mesh", "ok");
|
|
142
|
+
deps.log("info", `subscribed to ${doorbellTopics(bundle.actorUid).length} doorbell topics`);
|
|
143
|
+
void doRefetch("connect");
|
|
144
|
+
if (renewal)
|
|
145
|
+
clearT(renewal);
|
|
146
|
+
renewal = setT(() => {
|
|
147
|
+
deps.log("info", "renewing realtime credentials");
|
|
148
|
+
c.end(true);
|
|
149
|
+
}, renewalDelayMs(now().getTime(), bundle.expiresAt));
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
c.on("message", (topic) => {
|
|
153
|
+
// Ids-only doorbell: the payload is never parsed for content.
|
|
154
|
+
ring(topic);
|
|
155
|
+
});
|
|
156
|
+
c.on("error", (err) => {
|
|
157
|
+
deps.log("warn", `mqtt error: ${err.message}`);
|
|
158
|
+
});
|
|
159
|
+
c.on("close", () => {
|
|
160
|
+
if (client !== c)
|
|
161
|
+
return;
|
|
162
|
+
client = null;
|
|
163
|
+
const wasSubscribed = state === "subscribed";
|
|
164
|
+
state = stopped ? "closed" : "idle";
|
|
165
|
+
writeComponentStatus(deps.paths, "mesh", "error");
|
|
166
|
+
if (!settled || !wasSubscribed)
|
|
167
|
+
deps.log("warn", "mqtt closed before subscribe settled");
|
|
168
|
+
scheduleReconnect();
|
|
169
|
+
});
|
|
170
|
+
};
|
|
171
|
+
schedulePeriodic();
|
|
172
|
+
await connectOnce();
|
|
173
|
+
return {
|
|
174
|
+
stop: async () => {
|
|
175
|
+
stopped = true;
|
|
176
|
+
state = "closed";
|
|
177
|
+
if (debounce)
|
|
178
|
+
clearT(debounce);
|
|
179
|
+
if (renewal)
|
|
180
|
+
clearT(renewal);
|
|
181
|
+
if (periodic)
|
|
182
|
+
clearT(periodic);
|
|
183
|
+
const c = client;
|
|
184
|
+
client = null;
|
|
185
|
+
if (c)
|
|
186
|
+
await new Promise((r) => c.end(true, undefined, () => r()));
|
|
187
|
+
},
|
|
188
|
+
refetchNow: doRefetch,
|
|
189
|
+
ring,
|
|
190
|
+
state: () => state,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=mesh-listener.js.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kit service: company vault sync loop.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the hosted box's hq-agent-sync.service, which runs hq-cloud's
|
|
5
|
+
* `hq-sync-runner --companies --direction both --on-conflict keep` on a
|
|
6
|
+
* loop and writes `component-sync`. Here the equivalent is the CLI's own
|
|
7
|
+
* `hq sync pull --all --on-conflict keep --hq-root <kit hqRoot>`, run as a
|
|
8
|
+
* child of the same node + hq binary this service was launched with, so the
|
|
9
|
+
* machine identity, creds path and version are exactly the service's own.
|
|
10
|
+
*
|
|
11
|
+
* Honest health: exit 0 = ok, anything else = error (the heartbeat turns a
|
|
12
|
+
* stale stamp into error on its own).
|
|
13
|
+
*/
|
|
14
|
+
import type { KitLogger } from "../log.js";
|
|
15
|
+
import type { AgentKitPaths } from "../paths.js";
|
|
16
|
+
export interface SyncRunDeps {
|
|
17
|
+
paths: AgentKitPaths;
|
|
18
|
+
hqRoot: string;
|
|
19
|
+
intervalMs: number;
|
|
20
|
+
log: KitLogger;
|
|
21
|
+
nodeBinary?: string;
|
|
22
|
+
hqBinary?: string;
|
|
23
|
+
/** Injected child runner (tests). Resolves with the exit code. */
|
|
24
|
+
runPull?: (args: string[]) => Promise<number>;
|
|
25
|
+
sleep?: (ms: number) => Promise<void>;
|
|
26
|
+
/** Stop after this many passes (tests); default runs forever. */
|
|
27
|
+
maxPasses?: number;
|
|
28
|
+
}
|
|
29
|
+
export declare function syncPullArgs(hqRoot: string): string[];
|
|
30
|
+
export declare function defaultRunPull(nodeBinary: string, hqBinary: string, env: NodeJS.ProcessEnv): (args: string[]) => Promise<number>;
|
|
31
|
+
export declare function runSyncOnce(deps: SyncRunDeps): Promise<boolean>;
|
|
32
|
+
export declare function runSyncLoop(deps: SyncRunDeps): Promise<void>;
|
|
33
|
+
//# sourceMappingURL=sync.d.ts.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kit service: company vault sync loop.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the hosted box's hq-agent-sync.service, which runs hq-cloud's
|
|
5
|
+
* `hq-sync-runner --companies --direction both --on-conflict keep` on a
|
|
6
|
+
* loop and writes `component-sync`. Here the equivalent is the CLI's own
|
|
7
|
+
* `hq sync pull --all --on-conflict keep --hq-root <kit hqRoot>`, run as a
|
|
8
|
+
* child of the same node + hq binary this service was launched with, so the
|
|
9
|
+
* machine identity, creds path and version are exactly the service's own.
|
|
10
|
+
*
|
|
11
|
+
* Honest health: exit 0 = ok, anything else = error (the heartbeat turns a
|
|
12
|
+
* stale stamp into error on its own).
|
|
13
|
+
*/
|
|
14
|
+
import { spawn } from "node:child_process";
|
|
15
|
+
import { writeComponentStatus } from "../creds.js";
|
|
16
|
+
export function syncPullArgs(hqRoot) {
|
|
17
|
+
return ["sync", "pull", "--all", "--on-conflict", "keep", "--hq-root", hqRoot];
|
|
18
|
+
}
|
|
19
|
+
export function defaultRunPull(nodeBinary, hqBinary, env) {
|
|
20
|
+
return (args) => new Promise((resolve) => {
|
|
21
|
+
const child = spawn(nodeBinary, [hqBinary, ...args], {
|
|
22
|
+
env,
|
|
23
|
+
stdio: ["ignore", "inherit", "inherit"],
|
|
24
|
+
});
|
|
25
|
+
child.on("error", () => resolve(127));
|
|
26
|
+
child.on("exit", (code) => resolve(code ?? 1));
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
export async function runSyncOnce(deps) {
|
|
30
|
+
const runPull = deps.runPull ??
|
|
31
|
+
defaultRunPull(deps.nodeBinary ?? process.execPath, deps.hqBinary ?? process.argv[1], process.env);
|
|
32
|
+
const args = syncPullArgs(deps.hqRoot);
|
|
33
|
+
deps.log("info", `sync start hq-root=${deps.hqRoot}`);
|
|
34
|
+
let code;
|
|
35
|
+
try {
|
|
36
|
+
code = await runPull(args);
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
deps.log("error", `sync spawn failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
40
|
+
code = 1;
|
|
41
|
+
}
|
|
42
|
+
const ok = code === 0;
|
|
43
|
+
writeComponentStatus(deps.paths, "sync", ok ? "ok" : "error");
|
|
44
|
+
deps.log(ok ? "info" : "error", `sync ${ok ? "ok" : `failed exit=${code}`}`);
|
|
45
|
+
return ok;
|
|
46
|
+
}
|
|
47
|
+
export async function runSyncLoop(deps) {
|
|
48
|
+
const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
49
|
+
let passes = 0;
|
|
50
|
+
for (;;) {
|
|
51
|
+
await runSyncOnce(deps);
|
|
52
|
+
passes += 1;
|
|
53
|
+
if (deps.maxPasses !== undefined && passes >= deps.maxPasses)
|
|
54
|
+
return;
|
|
55
|
+
await sleep(deps.intervalMs);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=sync.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The four kit services and how each is launched. Every unit runs
|
|
3
|
+
* `node hq agent kit run <service>`; the service reads kit.json and the
|
|
4
|
+
* machine-creds file itself, so the units carry paths and nothing else.
|
|
5
|
+
*/
|
|
6
|
+
import type { ServiceSpec } from "../service-manager/types.js";
|
|
7
|
+
import type { AgentKitPaths } from "./paths.js";
|
|
8
|
+
export type KitService = "sync" | "mesh" | "inbox" | "heartbeat";
|
|
9
|
+
export declare const KIT_SERVICES: readonly KitService[];
|
|
10
|
+
export declare const KIT_LABEL_PREFIX = "ai.getindigo.hq-agent";
|
|
11
|
+
export declare function isKitService(value: string): value is KitService;
|
|
12
|
+
/**
|
|
13
|
+
* Environment every service inherits. HQ_MACHINE_CREDS_FILE pins hq-cloud's
|
|
14
|
+
* mint to the kit's creds file; HQ_REQUIRE_MACHINE_IDENTITY makes a missing
|
|
15
|
+
* file a loud error instead of a browser-login hang; HQ_AGENT_DIR keeps a
|
|
16
|
+
* relocated kit consistent across restarts.
|
|
17
|
+
*/
|
|
18
|
+
export declare function kitServiceEnv(paths: AgentKitPaths): Record<string, string>;
|
|
19
|
+
export declare function kitServiceSpec(service: KitService, paths: AgentKitPaths): ServiceSpec;
|
|
20
|
+
export declare function kitServiceSpecs(paths: AgentKitPaths): ServiceSpec[];
|
|
21
|
+
//# sourceMappingURL=services.d.ts.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The four kit services and how each is launched. Every unit runs
|
|
3
|
+
* `node hq agent kit run <service>`; the service reads kit.json and the
|
|
4
|
+
* machine-creds file itself, so the units carry paths and nothing else.
|
|
5
|
+
*/
|
|
6
|
+
import { serviceLogPath } from "./paths.js";
|
|
7
|
+
export const KIT_SERVICES = ["sync", "mesh", "inbox", "heartbeat"];
|
|
8
|
+
export const KIT_LABEL_PREFIX = "ai.getindigo.hq-agent";
|
|
9
|
+
export function isKitService(value) {
|
|
10
|
+
return KIT_SERVICES.includes(value);
|
|
11
|
+
}
|
|
12
|
+
const DESCRIPTIONS = {
|
|
13
|
+
sync: "HQ agent kit: company vault sync loop",
|
|
14
|
+
mesh: "HQ agent kit: work-mesh doorbell listener",
|
|
15
|
+
inbox: "HQ agent kit: inbox poller",
|
|
16
|
+
heartbeat: "HQ agent kit: heartbeat reporter",
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Environment every service inherits. HQ_MACHINE_CREDS_FILE pins hq-cloud's
|
|
20
|
+
* mint to the kit's creds file; HQ_REQUIRE_MACHINE_IDENTITY makes a missing
|
|
21
|
+
* file a loud error instead of a browser-login hang; HQ_AGENT_DIR keeps a
|
|
22
|
+
* relocated kit consistent across restarts.
|
|
23
|
+
*/
|
|
24
|
+
export function kitServiceEnv(paths) {
|
|
25
|
+
return {
|
|
26
|
+
HQ_AGENT_DIR: paths.agentDir,
|
|
27
|
+
HQ_MACHINE_CREDS_FILE: paths.machineCredsPath,
|
|
28
|
+
HQ_REQUIRE_MACHINE_IDENTITY: "1",
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export function kitServiceSpec(service, paths) {
|
|
32
|
+
return {
|
|
33
|
+
name: service,
|
|
34
|
+
label: `${KIT_LABEL_PREFIX}.${service}`,
|
|
35
|
+
description: DESCRIPTIONS[service],
|
|
36
|
+
args: ["agent", "kit", "run", service],
|
|
37
|
+
logPath: serviceLogPath(paths, service),
|
|
38
|
+
workingDir: paths.agentDir,
|
|
39
|
+
env: kitServiceEnv(paths),
|
|
40
|
+
restartSec: 10,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export function kitServiceSpecs(paths) {
|
|
44
|
+
return KIT_SERVICES.map((s) => kitServiceSpec(s, paths));
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=services.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The skills directory the kit ships to ~/.hq-agent/skills/<name>/SKILL.md.
|
|
3
|
+
*
|
|
4
|
+
* agentskills.io format: YAML frontmatter with `name` and `description`, then
|
|
5
|
+
* markdown the bot's framework loads as an instruction. Each skill drives
|
|
6
|
+
* the `hq` CLI, which authenticates as the machine identity on its own, so
|
|
7
|
+
* no skill ever embeds or asks for a token or secret value.
|
|
8
|
+
*/
|
|
9
|
+
import type { AgentKitPaths } from "./paths.js";
|
|
10
|
+
export interface KitSkill {
|
|
11
|
+
name: string;
|
|
12
|
+
description: string;
|
|
13
|
+
body: string;
|
|
14
|
+
}
|
|
15
|
+
export declare const KIT_SKILLS: readonly KitSkill[];
|
|
16
|
+
export declare function renderSkillMarkdown(skill: KitSkill): string;
|
|
17
|
+
export declare function writeKitSkills(paths: Pick<AgentKitPaths, "skillsDir">): string[];
|
|
18
|
+
//# sourceMappingURL=skills.d.ts.map
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The skills directory the kit ships to ~/.hq-agent/skills/<name>/SKILL.md.
|
|
3
|
+
*
|
|
4
|
+
* agentskills.io format: YAML frontmatter with `name` and `description`, then
|
|
5
|
+
* markdown the bot's framework loads as an instruction. Each skill drives
|
|
6
|
+
* the `hq` CLI, which authenticates as the machine identity on its own, so
|
|
7
|
+
* no skill ever embeds or asks for a token or secret value.
|
|
8
|
+
*/
|
|
9
|
+
import * as fs from "node:fs";
|
|
10
|
+
import * as path from "node:path";
|
|
11
|
+
export const KIT_SKILLS = [
|
|
12
|
+
{
|
|
13
|
+
name: "dm",
|
|
14
|
+
description: "Send and read HQ direct messages and channel messages as this agent using the hq CLI.",
|
|
15
|
+
body: `# HQ direct messages
|
|
16
|
+
|
|
17
|
+
Use the \`hq dm\` command to talk to teammates (people and other agents) in HQ.
|
|
18
|
+
The CLI authenticates as this agent automatically — never ask for a token.
|
|
19
|
+
|
|
20
|
+
## Send
|
|
21
|
+
|
|
22
|
+
\`\`\`bash
|
|
23
|
+
hq dm <email-or-uid> "message text" # 1:1 to a person (prs_…) or agent (agt_…)
|
|
24
|
+
hq dm <uid1>,<uid2> "message text" # group DM
|
|
25
|
+
hq dm '#channel-name' "message text" # channel
|
|
26
|
+
\`\`\`
|
|
27
|
+
|
|
28
|
+
## Read
|
|
29
|
+
|
|
30
|
+
\`\`\`bash
|
|
31
|
+
hq dm inbox --unread # unread DMs addressed to you
|
|
32
|
+
hq dm thread <email-or-uid> # the 1:1 conversation
|
|
33
|
+
hq dm inbox --mark-read # mark what you have read
|
|
34
|
+
hq dm channel <name> # read a channel
|
|
35
|
+
\`\`\`
|
|
36
|
+
|
|
37
|
+
New inbound messages are also mirrored by the kit's inbox poller to
|
|
38
|
+
\`~/.hq-agent/inbox/<id>.json\` (and \`inbox.jsonl\`) — check there when
|
|
39
|
+
you are asked whether anyone has messaged you.
|
|
40
|
+
|
|
41
|
+
## Rules
|
|
42
|
+
|
|
43
|
+
- Reply in the same thread you were messaged in.
|
|
44
|
+
- Keep messages short; link to files in the vault instead of pasting them.
|
|
45
|
+
- Never include secrets or credentials in a message.
|
|
46
|
+
`,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: "search",
|
|
50
|
+
description: "Search the company vault (documents, knowledge, meeting notes) with the hq CLI's local index.",
|
|
51
|
+
body: `# HQ search
|
|
52
|
+
|
|
53
|
+
The company vault is synced to the kit's HQ root (see \`~/.hq-agent/kit.json\`,
|
|
54
|
+
\`hqRoot\`). Search it with:
|
|
55
|
+
|
|
56
|
+
\`\`\`bash
|
|
57
|
+
hq search "<query>" # ranked results across the synced vault
|
|
58
|
+
hq search "<query>" --mode hybrid # keyword + semantic
|
|
59
|
+
hq search "<query>" --json # machine-readable
|
|
60
|
+
hq files search "<query>" --company <slug> # server-side vault search (no local index needed)
|
|
61
|
+
\`\`\`
|
|
62
|
+
|
|
63
|
+
If results look stale, the sync loop may not have run yet:
|
|
64
|
+
\`hq sync pull --all --on-conflict keep --hq-root <hqRoot>\` pulls now.
|
|
65
|
+
|
|
66
|
+
Cite the file path of anything you quote so a teammate can open it.
|
|
67
|
+
`,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: "files",
|
|
71
|
+
description: "Read, list and write files in the company vault through the hq CLI (hq files).",
|
|
72
|
+
body: `# HQ vault files
|
|
73
|
+
|
|
74
|
+
\`\`\`bash
|
|
75
|
+
hq files browse companies/<slug>/<folder> # list a vault folder without syncing it
|
|
76
|
+
hq files cat companies/<slug>/<file> # print one file
|
|
77
|
+
hq files get companies/<slug>/<file> # fetch one file into the local HQ tree
|
|
78
|
+
hq files share companies/<slug>/<path> # share a path with a teammate
|
|
79
|
+
hq files versions companies/<slug>/<file> # version history
|
|
80
|
+
\`\`\`
|
|
81
|
+
|
|
82
|
+
Paths are vault-relative and start with \`companies/<slug>/\`. The synced copy
|
|
83
|
+
lives under the kit's HQ root (\`~/.hq-agent/kit.json\` → \`hqRoot\`); edit files
|
|
84
|
+
there and run \`hq sync push --hq-root <hqRoot>\` to publish. Writes are
|
|
85
|
+
audited under this agent's identity — only write where you were asked to,
|
|
86
|
+
and prefer creating a new file over overwriting one you did not author.
|
|
87
|
+
`,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: "secrets-exec",
|
|
91
|
+
description: "Run a command with company secrets injected as environment variables, without ever printing them.",
|
|
92
|
+
body: `# Run with HQ secrets
|
|
93
|
+
|
|
94
|
+
Secrets never appear in chat, logs or files. Inject them into a child
|
|
95
|
+
process instead:
|
|
96
|
+
|
|
97
|
+
\`\`\`bash
|
|
98
|
+
hq secrets list # names only, never values
|
|
99
|
+
hq secrets exec --only <NAME>[,<NAME>] -- <cmd> # run <cmd> with those vars set
|
|
100
|
+
hq run <script> # run a vault script with its declared secrets
|
|
101
|
+
\`\`\`
|
|
102
|
+
|
|
103
|
+
Rules:
|
|
104
|
+
|
|
105
|
+
- Never \`echo\`, \`printenv\` or otherwise print a secret, even to "check" it.
|
|
106
|
+
- Request the narrowest \`--only\` set the command needs.
|
|
107
|
+
- If a secret is missing, say which NAME is missing and ask a human admin to
|
|
108
|
+
add it with \`hq secrets set\` — do not ask for the value in chat.
|
|
109
|
+
`,
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: "work-mesh-status",
|
|
113
|
+
description: "Report and update this agent's live work status and read the team's work mesh via the hq CLI.",
|
|
114
|
+
body: `# Work mesh status
|
|
115
|
+
|
|
116
|
+
The work mesh is HQ's live board of who is working on what. It is the source
|
|
117
|
+
of truth for project stories and active sessions; the kit's listener keeps a
|
|
118
|
+
local cache under \`~/.hq/work-mesh/cache/\`.
|
|
119
|
+
|
|
120
|
+
\`\`\`bash
|
|
121
|
+
hq mesh session status --company <slug> # who is working on what right now
|
|
122
|
+
hq mesh start --company <slug> --project <slug> --summary "<what you are starting>"
|
|
123
|
+
hq mesh progress --company <slug> --project <slug> --summary "<what changed>"
|
|
124
|
+
hq mesh blocked --company <slug> --project <slug> --summary "<what blocks you>"
|
|
125
|
+
hq mesh done --company <slug> --project <slug> --summary "<what you finished>"
|
|
126
|
+
hq mesh note --company <slug> --project <slug> --summary "<short note>"
|
|
127
|
+
\`\`\`
|
|
128
|
+
|
|
129
|
+
Post \`start\` when you pick up a piece of work and \`done\` when you finish it.
|
|
130
|
+
Keep entries to one line. Presence (online / stale / offline) is derived
|
|
131
|
+
from the kit heartbeat automatically — you do not need to report it.
|
|
132
|
+
`,
|
|
133
|
+
},
|
|
134
|
+
];
|
|
135
|
+
export function renderSkillMarkdown(skill) {
|
|
136
|
+
return `---\nname: ${skill.name}\ndescription: ${JSON.stringify(skill.description)}\n---\n\n${skill.body}`;
|
|
137
|
+
}
|
|
138
|
+
export function writeKitSkills(paths) {
|
|
139
|
+
const written = [];
|
|
140
|
+
for (const skill of KIT_SKILLS) {
|
|
141
|
+
const dir = path.join(paths.skillsDir, skill.name);
|
|
142
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o755 });
|
|
143
|
+
const dest = path.join(dir, "SKILL.md");
|
|
144
|
+
fs.writeFileSync(dest, renderSkillMarkdown(skill), { mode: 0o644 });
|
|
145
|
+
written.push(dest);
|
|
146
|
+
}
|
|
147
|
+
return written;
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=skills.js.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Install / uninstall / status for a SET of user-level services, dispatching
|
|
3
|
+
* to launchd or systemd. All filesystem and process-control effects are
|
|
4
|
+
* injectable so the kit tests exercise the real rendering and dispatch
|
|
5
|
+
* without touching the host's service manager.
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from "node:fs";
|
|
8
|
+
import { type RunCommand } from "./launchd.js";
|
|
9
|
+
import { type ServiceHostPaths, type ServicePlatform, type ServiceSpec } from "./types.js";
|
|
10
|
+
export type { ServiceSpec, ServiceHostPaths, ServicePlatform } from "./types.js";
|
|
11
|
+
export { detectServicePlatform } from "./types.js";
|
|
12
|
+
export { renderLaunchdPlist } from "./launchd.js";
|
|
13
|
+
export { renderSystemdUserUnit, systemdUnitName } from "./systemd.js";
|
|
14
|
+
export declare const defaultRunCommand: RunCommand;
|
|
15
|
+
export interface ServiceManagerDeps {
|
|
16
|
+
platform?: ServicePlatform;
|
|
17
|
+
run?: RunCommand;
|
|
18
|
+
writeFileSync?: typeof fs.writeFileSync;
|
|
19
|
+
mkdirSync?: typeof fs.mkdirSync;
|
|
20
|
+
unlinkSync?: typeof fs.unlinkSync;
|
|
21
|
+
existsSync?: typeof fs.existsSync;
|
|
22
|
+
/** When false, render + write units but do not (de)activate them. */
|
|
23
|
+
activate?: boolean;
|
|
24
|
+
}
|
|
25
|
+
export interface ServiceUnitResult {
|
|
26
|
+
name: string;
|
|
27
|
+
unitPath: string;
|
|
28
|
+
rendered?: string;
|
|
29
|
+
installed: boolean;
|
|
30
|
+
running?: boolean;
|
|
31
|
+
error?: string;
|
|
32
|
+
}
|
|
33
|
+
export interface ServiceSetResult {
|
|
34
|
+
platform: ServicePlatform;
|
|
35
|
+
units: ServiceUnitResult[];
|
|
36
|
+
/** Human hint when the platform has no service manager integration. */
|
|
37
|
+
manualCommands?: string[];
|
|
38
|
+
}
|
|
39
|
+
export declare function renderUnit(platform: ServicePlatform, spec: ServiceSpec, host: ServiceHostPaths): string;
|
|
40
|
+
export declare function installServices(specs: ServiceSpec[], host: ServiceHostPaths, deps?: ServiceManagerDeps): ServiceSetResult;
|
|
41
|
+
export declare function uninstallServices(specs: ServiceSpec[], host: ServiceHostPaths, deps?: ServiceManagerDeps): ServiceSetResult;
|
|
42
|
+
export declare function servicesStatus(specs: ServiceSpec[], host: ServiceHostPaths, deps?: ServiceManagerDeps): ServiceSetResult;
|
|
43
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Install / uninstall / status for a SET of user-level services, dispatching
|
|
3
|
+
* to launchd or systemd. All filesystem and process-control effects are
|
|
4
|
+
* injectable so the kit tests exercise the real rendering and dispatch
|
|
5
|
+
* without touching the host's service manager.
|
|
6
|
+
*/
|
|
7
|
+
import { spawnSync } from "node:child_process";
|
|
8
|
+
import * as fs from "node:fs";
|
|
9
|
+
import { activateLaunchd, deactivateLaunchd, launchAgentsDir, launchdIsLoaded, launchdPlistPath, renderLaunchdPlist, } from "./launchd.js";
|
|
10
|
+
import { activateSystemd, deactivateSystemd, renderSystemdUserUnit, systemdIsActive, systemdUnitName, systemdUnitPath, systemdUserDir, } from "./systemd.js";
|
|
11
|
+
import { detectServicePlatform, } from "./types.js";
|
|
12
|
+
export { detectServicePlatform } from "./types.js";
|
|
13
|
+
export { renderLaunchdPlist } from "./launchd.js";
|
|
14
|
+
export { renderSystemdUserUnit, systemdUnitName } from "./systemd.js";
|
|
15
|
+
export const defaultRunCommand = (cmd, args) => {
|
|
16
|
+
const r = spawnSync(cmd, args, { encoding: "utf8" });
|
|
17
|
+
return {
|
|
18
|
+
status: r.status,
|
|
19
|
+
stdout: r.stdout ?? "",
|
|
20
|
+
stderr: r.error ? r.error.message : (r.stderr ?? ""),
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
function unitPathFor(platform, home, spec) {
|
|
24
|
+
return platform === "darwin" ? launchdPlistPath(home, spec) : systemdUnitPath(home, spec);
|
|
25
|
+
}
|
|
26
|
+
export function renderUnit(platform, spec, host) {
|
|
27
|
+
return platform === "darwin"
|
|
28
|
+
? renderLaunchdPlist(spec, host)
|
|
29
|
+
: renderSystemdUserUnit(spec, host);
|
|
30
|
+
}
|
|
31
|
+
export function installServices(specs, host, deps = {}) {
|
|
32
|
+
const platform = deps.platform ?? detectServicePlatform();
|
|
33
|
+
const run = deps.run ?? defaultRunCommand;
|
|
34
|
+
const writeFileSync = deps.writeFileSync ?? fs.writeFileSync;
|
|
35
|
+
const mkdirSync = deps.mkdirSync ?? fs.mkdirSync;
|
|
36
|
+
const activate = deps.activate ?? true;
|
|
37
|
+
if (platform === "other") {
|
|
38
|
+
return {
|
|
39
|
+
platform,
|
|
40
|
+
units: specs.map((s) => ({ name: s.name, unitPath: "", installed: false })),
|
|
41
|
+
manualCommands: specs.map((s) => `${host.nodeBinary} ${host.hqBinary} ${s.args.join(" ")} # >> ${s.logPath}`),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const dir = platform === "darwin" ? launchAgentsDir(host.home) : systemdUserDir(host.home);
|
|
45
|
+
mkdirSync(dir, { recursive: true });
|
|
46
|
+
const units = [];
|
|
47
|
+
for (const spec of specs) {
|
|
48
|
+
const unitPath = unitPathFor(platform, host.home, spec);
|
|
49
|
+
const rendered = renderUnit(platform, spec, host);
|
|
50
|
+
writeFileSync(unitPath, rendered, { mode: 0o600 });
|
|
51
|
+
const unit = { name: spec.name, unitPath, rendered, installed: true };
|
|
52
|
+
if (activate) {
|
|
53
|
+
try {
|
|
54
|
+
if (platform === "darwin")
|
|
55
|
+
activateLaunchd(unitPath, run);
|
|
56
|
+
else
|
|
57
|
+
activateSystemd(systemdUnitName(spec), run);
|
|
58
|
+
unit.running = true;
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
unit.running = false;
|
|
62
|
+
unit.error = err instanceof Error ? err.message : String(err);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
units.push(unit);
|
|
66
|
+
}
|
|
67
|
+
return { platform, units };
|
|
68
|
+
}
|
|
69
|
+
export function uninstallServices(specs, host, deps = {}) {
|
|
70
|
+
const platform = deps.platform ?? detectServicePlatform();
|
|
71
|
+
const run = deps.run ?? defaultRunCommand;
|
|
72
|
+
const unlinkSync = deps.unlinkSync ?? fs.unlinkSync;
|
|
73
|
+
const existsSync = deps.existsSync ?? fs.existsSync;
|
|
74
|
+
const activate = deps.activate ?? true;
|
|
75
|
+
if (platform === "other") {
|
|
76
|
+
return { platform, units: specs.map((s) => ({ name: s.name, unitPath: "", installed: false })) };
|
|
77
|
+
}
|
|
78
|
+
const units = [];
|
|
79
|
+
for (const spec of specs) {
|
|
80
|
+
const unitPath = unitPathFor(platform, host.home, spec);
|
|
81
|
+
const present = existsSync(unitPath);
|
|
82
|
+
if (present) {
|
|
83
|
+
if (activate) {
|
|
84
|
+
if (platform === "darwin")
|
|
85
|
+
deactivateLaunchd(unitPath, run);
|
|
86
|
+
else
|
|
87
|
+
deactivateSystemd(systemdUnitName(spec), run);
|
|
88
|
+
}
|
|
89
|
+
unlinkSync(unitPath);
|
|
90
|
+
}
|
|
91
|
+
units.push({ name: spec.name, unitPath, installed: false, running: false });
|
|
92
|
+
}
|
|
93
|
+
return { platform, units };
|
|
94
|
+
}
|
|
95
|
+
export function servicesStatus(specs, host, deps = {}) {
|
|
96
|
+
const platform = deps.platform ?? detectServicePlatform();
|
|
97
|
+
const run = deps.run ?? defaultRunCommand;
|
|
98
|
+
const existsSync = deps.existsSync ?? fs.existsSync;
|
|
99
|
+
if (platform === "other") {
|
|
100
|
+
return { platform, units: specs.map((s) => ({ name: s.name, unitPath: "", installed: false })) };
|
|
101
|
+
}
|
|
102
|
+
const units = specs.map((spec) => {
|
|
103
|
+
const unitPath = unitPathFor(platform, host.home, spec);
|
|
104
|
+
const installed = existsSync(unitPath);
|
|
105
|
+
const running = installed
|
|
106
|
+
? platform === "darwin"
|
|
107
|
+
? launchdIsLoaded(spec.label, run)
|
|
108
|
+
: systemdIsActive(systemdUnitName(spec), run)
|
|
109
|
+
: false;
|
|
110
|
+
return { name: spec.name, unitPath, installed, running };
|
|
111
|
+
});
|
|
112
|
+
return { platform, units };
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=index.js.map
|