@jive-ai/cli 0.0.31 → 0.0.33
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/README.md +109 -0
- package/dist/config-7rVDmj2u.mjs +3 -0
- package/dist/graphql-client-DtN1tbTC.mjs +3 -0
- package/dist/index.mjs +668 -116
- package/dist/service-DTf-SkmQ.mjs +347 -0
- package/dist/tasks-Py86q1u7.mjs +3 -0
- package/package.json +8 -5
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import { E as WS_URL, T as GRAPHQL_API_URL, w as API_URL } from "./index.mjs";
|
|
2
|
+
import fs from "fs/promises";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import os from "os";
|
|
5
|
+
import { exec, spawn } from "child_process";
|
|
6
|
+
import { promisify } from "util";
|
|
7
|
+
|
|
8
|
+
//#region src/lib/service/systemd.ts
|
|
9
|
+
const execAsync$1 = promisify(exec);
|
|
10
|
+
const SERVICE_TEMPLATE = `[Unit]
|
|
11
|
+
Description=Jive Task Runner
|
|
12
|
+
Documentation=https://getjive.app/docs
|
|
13
|
+
After=network-online.target
|
|
14
|
+
Wants=network-online.target
|
|
15
|
+
|
|
16
|
+
[Service]
|
|
17
|
+
Type=simple
|
|
18
|
+
ExecStart={{JIVE_BINARY_PATH}} task-runner start
|
|
19
|
+
Restart=on-failure
|
|
20
|
+
RestartSec=30
|
|
21
|
+
TimeoutStartSec=90
|
|
22
|
+
TimeoutStopSec=30
|
|
23
|
+
StartLimitBurst=5
|
|
24
|
+
StartLimitIntervalSec=10m
|
|
25
|
+
KillMode=mixed
|
|
26
|
+
Environment="PATH={{NODE_BIN_PATH}}:/usr/local/bin:/usr/bin:/bin"
|
|
27
|
+
Environment="JIVE_API_KEY={{JIVE_API_KEY}}"
|
|
28
|
+
Environment="ANTHROPIC_API_KEY={{ANTHROPIC_API_KEY}}"
|
|
29
|
+
Environment="JIVE_TEAM_ID={{JIVE_TEAM_ID}}"
|
|
30
|
+
Environment="JIVE_RUNNER_ID={{JIVE_RUNNER_ID}}"
|
|
31
|
+
Environment="JIVE_API_URL={{JIVE_API_URL}}"
|
|
32
|
+
Environment="JIVE_WS_URL={{JIVE_WS_URL}}"
|
|
33
|
+
Environment="JIVE_GRAPHQL_API_URL={{JIVE_GRAPHQL_API_URL}}"
|
|
34
|
+
|
|
35
|
+
StandardOutput=journal
|
|
36
|
+
StandardError=journal
|
|
37
|
+
SyslogIdentifier=jive-task-runner
|
|
38
|
+
|
|
39
|
+
# Security hardening (safe for Docker runners)
|
|
40
|
+
NoNewPrivileges=true
|
|
41
|
+
PrivateTmp=true
|
|
42
|
+
ProtectKernelTunables=true
|
|
43
|
+
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
|
|
44
|
+
|
|
45
|
+
[Install]
|
|
46
|
+
WantedBy=default.target
|
|
47
|
+
`;
|
|
48
|
+
var SystemdServiceManager = class {
|
|
49
|
+
servicePath;
|
|
50
|
+
serviceName = "jive-task-runner";
|
|
51
|
+
constructor() {
|
|
52
|
+
const homeDir = os.homedir();
|
|
53
|
+
this.servicePath = path.join(homeDir, ".config", "systemd", "user", `${this.serviceName}.service`);
|
|
54
|
+
}
|
|
55
|
+
async install() {
|
|
56
|
+
const { getRunnerConfig } = await import("./tasks-Py86q1u7.mjs");
|
|
57
|
+
const { getCredentials } = await import("./config-7rVDmj2u.mjs");
|
|
58
|
+
const runnerConfig = await getRunnerConfig();
|
|
59
|
+
const credentials = await getCredentials();
|
|
60
|
+
if (!credentials?.token) throw new Error("No API key found. Run `jive login` first.");
|
|
61
|
+
const { stdout: binaryPath } = await execAsync$1("which jive", { timeout: 3e4 });
|
|
62
|
+
const resolvedPath = binaryPath.trim();
|
|
63
|
+
if (!resolvedPath) throw new Error("Cannot locate jive binary in PATH");
|
|
64
|
+
const { stdout: nodePath } = await execAsync$1("readlink -f $(which node)", { timeout: 3e4 });
|
|
65
|
+
const resolvedNodePath = nodePath.trim();
|
|
66
|
+
const variables = {
|
|
67
|
+
JIVE_BINARY_PATH: resolvedPath,
|
|
68
|
+
NODE_BIN_PATH: path.dirname(resolvedNodePath),
|
|
69
|
+
JIVE_API_KEY: credentials.token,
|
|
70
|
+
ANTHROPIC_API_KEY: credentials.anthropicApiKey || "",
|
|
71
|
+
JIVE_TEAM_ID: runnerConfig.teamId,
|
|
72
|
+
JIVE_RUNNER_ID: runnerConfig.id.toString(),
|
|
73
|
+
JIVE_API_URL: process.env.JIVE_API_URL || API_URL,
|
|
74
|
+
JIVE_WS_URL: process.env.JIVE_WS_URL || WS_URL,
|
|
75
|
+
JIVE_GRAPHQL_API_URL: process.env.JIVE_GRAPHQL_API_URL || GRAPHQL_API_URL
|
|
76
|
+
};
|
|
77
|
+
let serviceContent = SERVICE_TEMPLATE;
|
|
78
|
+
for (const [key, value] of Object.entries(variables)) serviceContent = serviceContent.replace(new RegExp(`{{${key}}}`, "g"), value);
|
|
79
|
+
const serviceDir = path.dirname(this.servicePath);
|
|
80
|
+
await fs.mkdir(serviceDir, {
|
|
81
|
+
recursive: true,
|
|
82
|
+
mode: 493
|
|
83
|
+
});
|
|
84
|
+
const dirMode = (await fs.stat(serviceDir)).mode & 511;
|
|
85
|
+
if (dirMode > 493) throw new Error(`Systemd user directory has overly permissive permissions: ${dirMode.toString(8)}\nExpected 0755 or stricter. Fix with: chmod 755 ${serviceDir}`);
|
|
86
|
+
await fs.writeFile(this.servicePath, serviceContent, { mode: 384 });
|
|
87
|
+
try {
|
|
88
|
+
await execAsync$1("systemctl --user daemon-reload", { timeout: 3e4 });
|
|
89
|
+
await execAsync$1(`systemctl --user enable ${this.serviceName}`, { timeout: 3e4 });
|
|
90
|
+
} catch (error) {
|
|
91
|
+
try {
|
|
92
|
+
await fs.unlink(this.servicePath);
|
|
93
|
+
await execAsync$1("systemctl --user daemon-reload", { timeout: 3e4 });
|
|
94
|
+
} catch (cleanupError) {
|
|
95
|
+
console.error("Failed to clean up after installation failure:", cleanupError);
|
|
96
|
+
}
|
|
97
|
+
throw new Error(`Service installation failed: ${error.message}\nPartial installation has been rolled back.`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async uninstall() {
|
|
101
|
+
try {
|
|
102
|
+
await this.stop();
|
|
103
|
+
} catch (error) {
|
|
104
|
+
if (error.message?.includes("inactive") || error.message?.includes("not be found")) {} else if (error.code === "EACCES") console.warn("Warning: Permission denied when stopping service");
|
|
105
|
+
else console.warn(`Warning: Failed to stop service: ${error.message}`);
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
await execAsync$1(`systemctl --user disable ${this.serviceName}`, { timeout: 3e4 });
|
|
109
|
+
} catch (error) {
|
|
110
|
+
if (error.stderr?.includes("No such file") || error.message?.includes("not be found")) {} else if (error.code === "EACCES") console.warn("Warning: Permission denied when disabling service");
|
|
111
|
+
else console.warn(`Warning: Failed to disable service: ${error.message}`);
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
await fs.unlink(this.servicePath);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
if (error.code !== "ENOENT") throw new Error(`Failed to remove service file: ${error.message}`);
|
|
117
|
+
}
|
|
118
|
+
await execAsync$1("systemctl --user daemon-reload", { timeout: 3e4 });
|
|
119
|
+
}
|
|
120
|
+
async start() {
|
|
121
|
+
await execAsync$1(`systemctl --user start ${this.serviceName}`, { timeout: 3e4 });
|
|
122
|
+
}
|
|
123
|
+
async stop() {
|
|
124
|
+
await execAsync$1(`systemctl --user stop ${this.serviceName}`, { timeout: 3e4 });
|
|
125
|
+
}
|
|
126
|
+
async restart() {
|
|
127
|
+
await execAsync$1(`systemctl --user restart ${this.serviceName}`, { timeout: 3e4 });
|
|
128
|
+
}
|
|
129
|
+
async status() {
|
|
130
|
+
if (!await this.isInstalled()) return {
|
|
131
|
+
installed: false,
|
|
132
|
+
running: false,
|
|
133
|
+
enabled: false
|
|
134
|
+
};
|
|
135
|
+
try {
|
|
136
|
+
const { stdout } = await execAsync$1(`systemctl --user status ${this.serviceName} --no-pager`, { timeout: 3e4 });
|
|
137
|
+
const running = stdout.includes("Active: active (running)");
|
|
138
|
+
const pid = this.extractPid(stdout);
|
|
139
|
+
const uptime = this.extractUptime(stdout);
|
|
140
|
+
const { stdout: isEnabledOutput } = await execAsync$1(`systemctl --user is-enabled ${this.serviceName}`, { timeout: 3e4 });
|
|
141
|
+
return {
|
|
142
|
+
installed: true,
|
|
143
|
+
running,
|
|
144
|
+
enabled: isEnabledOutput.trim() === "enabled",
|
|
145
|
+
uptime,
|
|
146
|
+
pid
|
|
147
|
+
};
|
|
148
|
+
} catch (error) {
|
|
149
|
+
const { stdout: isEnabledOutput } = await execAsync$1(`systemctl --user is-enabled ${this.serviceName}`, { timeout: 3e4 }).catch(() => ({ stdout: "disabled" }));
|
|
150
|
+
return {
|
|
151
|
+
installed: true,
|
|
152
|
+
running: false,
|
|
153
|
+
enabled: isEnabledOutput.trim() === "enabled"
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async logs(options) {
|
|
158
|
+
const args = [
|
|
159
|
+
"--user",
|
|
160
|
+
"-u",
|
|
161
|
+
this.serviceName
|
|
162
|
+
];
|
|
163
|
+
if (options?.follow) args.push("-f");
|
|
164
|
+
if (options?.lines) args.push("-n", options.lines.toString());
|
|
165
|
+
const logsProcess = spawn("journalctl", args, { stdio: "inherit" });
|
|
166
|
+
return new Promise((resolve, reject) => {
|
|
167
|
+
logsProcess.on("exit", (code) => {
|
|
168
|
+
if (code === 0) resolve();
|
|
169
|
+
else reject(/* @__PURE__ */ new Error(`journalctl exited with code ${code}`));
|
|
170
|
+
});
|
|
171
|
+
logsProcess.on("error", (error) => {
|
|
172
|
+
reject(error);
|
|
173
|
+
});
|
|
174
|
+
if (options?.follow) process.on("SIGINT", () => {
|
|
175
|
+
logsProcess.kill("SIGTERM");
|
|
176
|
+
resolve();
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
async isInstalled() {
|
|
181
|
+
try {
|
|
182
|
+
await fs.access(this.servicePath);
|
|
183
|
+
return true;
|
|
184
|
+
} catch {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
extractPid(statusOutput) {
|
|
189
|
+
const pidMatch = statusOutput.match(/Main PID: (\d+)/);
|
|
190
|
+
return pidMatch ? parseInt(pidMatch[1], 10) : void 0;
|
|
191
|
+
}
|
|
192
|
+
extractUptime(statusOutput) {
|
|
193
|
+
const uptimeMatch = statusOutput.match(/Active: active \(running\) since (.+?);/);
|
|
194
|
+
if (!uptimeMatch) return void 0;
|
|
195
|
+
const sinceStr = uptimeMatch[1];
|
|
196
|
+
const sinceDate = new Date(sinceStr);
|
|
197
|
+
const diffMs = (/* @__PURE__ */ new Date()).getTime() - sinceDate.getTime();
|
|
198
|
+
const seconds = Math.floor(diffMs / 1e3);
|
|
199
|
+
const minutes = Math.floor(seconds / 60);
|
|
200
|
+
const hours = Math.floor(minutes / 60);
|
|
201
|
+
const days = Math.floor(hours / 24);
|
|
202
|
+
if (days > 0) return `${days}d ${hours % 24}h`;
|
|
203
|
+
if (hours > 0) return `${hours}h ${minutes % 60}m`;
|
|
204
|
+
if (minutes > 0) return `${minutes}m`;
|
|
205
|
+
return "Just now";
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
//#endregion
|
|
210
|
+
//#region src/lib/service/index.ts
|
|
211
|
+
const execAsync = promisify(exec);
|
|
212
|
+
/**
|
|
213
|
+
* Detect the current platform and service management system
|
|
214
|
+
*/
|
|
215
|
+
function detectPlatform() {
|
|
216
|
+
if (os.platform() === "linux") return "linux-systemd";
|
|
217
|
+
return "unsupported";
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Get a service manager instance for the current platform
|
|
221
|
+
*/
|
|
222
|
+
function getServiceManager() {
|
|
223
|
+
if (detectPlatform() === "unsupported") throw new Error(`Service installation is not supported on ${os.platform()}.\nCurrently supported platforms:
|
|
224
|
+
- Linux (systemd)`);
|
|
225
|
+
return new SystemdServiceManager();
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Validate that the system is ready for service installation
|
|
229
|
+
*/
|
|
230
|
+
async function validateServiceInstallation() {
|
|
231
|
+
const checks = [];
|
|
232
|
+
let canInstall = true;
|
|
233
|
+
let hasWarnings = false;
|
|
234
|
+
try {
|
|
235
|
+
const { getRunnerConfig } = await import("./tasks-Py86q1u7.mjs");
|
|
236
|
+
const runnerConfig = await getRunnerConfig();
|
|
237
|
+
checks.push({
|
|
238
|
+
name: "Runner config",
|
|
239
|
+
status: "success",
|
|
240
|
+
message: `${runnerConfig.name} (${runnerConfig.type})`
|
|
241
|
+
});
|
|
242
|
+
} catch (error) {
|
|
243
|
+
checks.push({
|
|
244
|
+
name: "Runner config",
|
|
245
|
+
status: "error",
|
|
246
|
+
message: "Not configured",
|
|
247
|
+
detail: "Run `jive task-runner setup` first"
|
|
248
|
+
});
|
|
249
|
+
canInstall = false;
|
|
250
|
+
}
|
|
251
|
+
try {
|
|
252
|
+
const { getCredentials } = await import("./config-7rVDmj2u.mjs");
|
|
253
|
+
const credentials = await getCredentials();
|
|
254
|
+
if (!credentials?.token) throw new Error("No API key found");
|
|
255
|
+
if (!credentials?.anthropicApiKey) {
|
|
256
|
+
checks.push({
|
|
257
|
+
name: "Credentials",
|
|
258
|
+
status: "warning",
|
|
259
|
+
message: "Anthropic API key not found",
|
|
260
|
+
detail: "Runner may fail to start without it"
|
|
261
|
+
});
|
|
262
|
+
hasWarnings = true;
|
|
263
|
+
} else checks.push({
|
|
264
|
+
name: "Credentials",
|
|
265
|
+
status: "success",
|
|
266
|
+
message: "API keys configured"
|
|
267
|
+
});
|
|
268
|
+
} catch (error) {
|
|
269
|
+
checks.push({
|
|
270
|
+
name: "Credentials",
|
|
271
|
+
status: "error",
|
|
272
|
+
message: "Not configured",
|
|
273
|
+
detail: "Run `jive login` first"
|
|
274
|
+
});
|
|
275
|
+
canInstall = false;
|
|
276
|
+
}
|
|
277
|
+
try {
|
|
278
|
+
const { getRunnerConfig } = await import("./tasks-Py86q1u7.mjs");
|
|
279
|
+
if ((await getRunnerConfig()).type === "docker") try {
|
|
280
|
+
await execAsync("docker ps", { timeout: 1e4 });
|
|
281
|
+
checks.push({
|
|
282
|
+
name: "Docker",
|
|
283
|
+
status: "success",
|
|
284
|
+
message: "Docker daemon running"
|
|
285
|
+
});
|
|
286
|
+
} catch {
|
|
287
|
+
checks.push({
|
|
288
|
+
name: "Docker",
|
|
289
|
+
status: "error",
|
|
290
|
+
message: "Docker daemon not running",
|
|
291
|
+
detail: "Start Docker daemon or install from https://docs.docker.com/get-docker/"
|
|
292
|
+
});
|
|
293
|
+
canInstall = false;
|
|
294
|
+
}
|
|
295
|
+
} catch {}
|
|
296
|
+
try {
|
|
297
|
+
if (await getServiceManager().isInstalled()) {
|
|
298
|
+
checks.push({
|
|
299
|
+
name: "Service status",
|
|
300
|
+
status: "error",
|
|
301
|
+
message: "Service already installed",
|
|
302
|
+
detail: "Run `jive task-runner uninstall-service` first"
|
|
303
|
+
});
|
|
304
|
+
canInstall = false;
|
|
305
|
+
} else checks.push({
|
|
306
|
+
name: "Service status",
|
|
307
|
+
status: "success",
|
|
308
|
+
message: "Not installed"
|
|
309
|
+
});
|
|
310
|
+
} catch (error) {
|
|
311
|
+
if (error.message.includes("not supported")) {
|
|
312
|
+
checks.push({
|
|
313
|
+
name: "Service status",
|
|
314
|
+
status: "error",
|
|
315
|
+
message: "Platform not supported",
|
|
316
|
+
detail: error.message
|
|
317
|
+
});
|
|
318
|
+
canInstall = false;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
try {
|
|
322
|
+
const { stdout } = await execAsync("which jive", { timeout: 3e4 });
|
|
323
|
+
const binaryPath = stdout.trim();
|
|
324
|
+
if (!binaryPath) throw new Error("Binary not in PATH");
|
|
325
|
+
checks.push({
|
|
326
|
+
name: "Binary path",
|
|
327
|
+
status: "success",
|
|
328
|
+
message: binaryPath
|
|
329
|
+
});
|
|
330
|
+
} catch (error) {
|
|
331
|
+
checks.push({
|
|
332
|
+
name: "Binary path",
|
|
333
|
+
status: "error",
|
|
334
|
+
message: "Cannot locate jive binary",
|
|
335
|
+
detail: "Ensure jive is installed and in PATH"
|
|
336
|
+
});
|
|
337
|
+
canInstall = false;
|
|
338
|
+
}
|
|
339
|
+
return {
|
|
340
|
+
checks,
|
|
341
|
+
canInstall,
|
|
342
|
+
hasWarnings
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
//#endregion
|
|
347
|
+
export { detectPlatform, getServiceManager, validateServiceInstallation };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { _ as taskRunnerCommands, a as installServiceCommand, c as runnerInfoCommand, d as serviceRestartCommand, f as serviceStatusCommand, g as stopRunnerCommand, h as startTaskCommand, i as initCommand, l as saveRunnerConfig, m as startRunnerCommand, n as ensureTasksConfig, o as logsCommand, p as setupRunnerCommand, r as getRunnerConfig, s as removeRunnerCommand, t as createTasksConfig, u as serviceLogsCommand, v as uninstallServiceCommand } from "./index.mjs";
|
|
2
|
+
|
|
3
|
+
export { getRunnerConfig };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"private": false,
|
|
3
3
|
"name": "@jive-ai/cli",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.33",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
7
7
|
"dist",
|
|
@@ -15,8 +15,11 @@
|
|
|
15
15
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
16
16
|
"typecheck": "tsc --noEmit",
|
|
17
17
|
"build": "tsdown && npm pack && npm install -g jive-ai-cli-*.tgz",
|
|
18
|
-
"docker:build": "touch jive-ai-cli-0.0.0.tgz && docker build -t
|
|
19
|
-
"docker:build:local": "bun run build && docker build --build-arg USE_LOCAL=true -t
|
|
18
|
+
"docker:build": "touch jive-ai-cli-0.0.0.tgz && docker build -t jiveai/task:latest . && rm -f jive-ai-cli-*.tgz",
|
|
19
|
+
"docker:build:local": "bun run build && docker build --build-arg USE_LOCAL=true -t jiveai/task:latest .",
|
|
20
|
+
"docker:build:public": "touch jive-ai-cli-0.0.0.tgz && docker buildx build --platform linux/amd64 -t jiveai/task:latest -t jiveai/task:$npm_package_version . && rm -f jive-ai-cli-*.tgz",
|
|
21
|
+
"docker:push": "docker buildx build --platform linux/amd64 --push -t jiveai/task:latest -t jiveai/task:$npm_package_version .",
|
|
22
|
+
"docker:publish": "touch jive-ai-cli-0.0.0.tgz && docker buildx build --platform linux/amd64 --push -t jiveai/task:latest -t jiveai/task:$npm_package_version . && rm -f jive-ai-cli-*.tgz",
|
|
20
23
|
"prepublishOnly": "npm run typecheck && npm run build"
|
|
21
24
|
},
|
|
22
25
|
"author": "",
|
|
@@ -31,7 +34,7 @@
|
|
|
31
34
|
"tsx": "^4.20.6"
|
|
32
35
|
},
|
|
33
36
|
"dependencies": {
|
|
34
|
-
"@anthropic-ai/claude-agent-sdk": "^0.
|
|
37
|
+
"@anthropic-ai/claude-agent-sdk": "^0.2.7",
|
|
35
38
|
"@modelcontextprotocol/sdk": "^1.22.0",
|
|
36
39
|
"chalk": "^5.6.2",
|
|
37
40
|
"change-case": "^5.4.4",
|
|
@@ -50,6 +53,6 @@
|
|
|
50
53
|
"typescript": "^5.9.3",
|
|
51
54
|
"uuid": "^13.0.0",
|
|
52
55
|
"ws": "^8.18.0",
|
|
53
|
-
"zod": "^3.
|
|
56
|
+
"zod": "^4.3.5"
|
|
54
57
|
}
|
|
55
58
|
}
|