@jive-ai/cli 0.0.45 → 0.0.47
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/dist/index.mjs +265 -79
- package/dist/{service-4H4YceKv.mjs → service-MMjLsA9C.mjs} +158 -42
- package/package.json +3 -2
package/dist/index.mjs
CHANGED
|
@@ -28,9 +28,16 @@ import httpProxy from "http-proxy";
|
|
|
28
28
|
|
|
29
29
|
//#region src/lib/config.ts
|
|
30
30
|
/**
|
|
31
|
+
* Get the effective home directory - uses SUDO_USER's home when running with sudo
|
|
32
|
+
*/
|
|
33
|
+
function getEffectiveHomeDir() {
|
|
34
|
+
if (process.env.SUDO_USER) return `/home/${process.env.SUDO_USER}`;
|
|
35
|
+
return os.homedir();
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
31
38
|
* `~/.jive/credentials.json`
|
|
32
39
|
*/
|
|
33
|
-
const CREDENTIALS_PATH = path.join(
|
|
40
|
+
const CREDENTIALS_PATH = path.join(getEffectiveHomeDir(), ".jive", "credentials.json");
|
|
34
41
|
/**
|
|
35
42
|
* `<cwd>/.jive/config.json`
|
|
36
43
|
*/
|
|
@@ -1023,6 +1030,48 @@ const mutations = {
|
|
|
1023
1030
|
}
|
|
1024
1031
|
}
|
|
1025
1032
|
}
|
|
1033
|
+
`),
|
|
1034
|
+
CreateIssue: graphql(`
|
|
1035
|
+
mutation CreateIssue($input: CreateIssueInput!) {
|
|
1036
|
+
createIssue(input: $input) {
|
|
1037
|
+
success
|
|
1038
|
+
issue {
|
|
1039
|
+
id
|
|
1040
|
+
number
|
|
1041
|
+
title
|
|
1042
|
+
htmlUrl
|
|
1043
|
+
labels {
|
|
1044
|
+
name
|
|
1045
|
+
color
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
errors {
|
|
1049
|
+
message
|
|
1050
|
+
code
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
`),
|
|
1055
|
+
UpdateIssue: graphql(`
|
|
1056
|
+
mutation UpdateIssue($input: UpdateIssueInput!) {
|
|
1057
|
+
updateIssue(input: $input) {
|
|
1058
|
+
success
|
|
1059
|
+
issue {
|
|
1060
|
+
id
|
|
1061
|
+
number
|
|
1062
|
+
title
|
|
1063
|
+
htmlUrl
|
|
1064
|
+
labels {
|
|
1065
|
+
name
|
|
1066
|
+
color
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
errors {
|
|
1070
|
+
message
|
|
1071
|
+
code
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1026
1075
|
`),
|
|
1027
1076
|
RegisterRunner: graphql(`
|
|
1028
1077
|
mutation RegisterRunner($input: RegisterRunnerInput!) {
|
|
@@ -1563,6 +1612,46 @@ var ApiClient = class {
|
|
|
1563
1612
|
}
|
|
1564
1613
|
};
|
|
1565
1614
|
}
|
|
1615
|
+
async createIssue(projectId, data) {
|
|
1616
|
+
const result = await (await getGraphQLClient()).request(mutations.CreateIssue, { input: {
|
|
1617
|
+
projectId: String(projectId),
|
|
1618
|
+
title: data.title,
|
|
1619
|
+
body: data.body,
|
|
1620
|
+
labels: data.labels
|
|
1621
|
+
} });
|
|
1622
|
+
if (result.createIssue.errors?.length) throw this.formatError({ message: result.createIssue.errors[0].message });
|
|
1623
|
+
return {
|
|
1624
|
+
success: result.createIssue.success,
|
|
1625
|
+
issue: {
|
|
1626
|
+
id: result.createIssue.issue.id,
|
|
1627
|
+
number: result.createIssue.issue.number,
|
|
1628
|
+
title: result.createIssue.issue.title,
|
|
1629
|
+
htmlUrl: result.createIssue.issue.htmlUrl,
|
|
1630
|
+
labels: result.createIssue.issue.labels
|
|
1631
|
+
}
|
|
1632
|
+
};
|
|
1633
|
+
}
|
|
1634
|
+
async updateIssue(projectId, data) {
|
|
1635
|
+
const result = await (await getGraphQLClient()).request(mutations.UpdateIssue, { input: {
|
|
1636
|
+
projectId: String(projectId),
|
|
1637
|
+
number: data.number,
|
|
1638
|
+
title: data.title,
|
|
1639
|
+
body: data.body,
|
|
1640
|
+
state: data.state,
|
|
1641
|
+
labels: data.labels
|
|
1642
|
+
} });
|
|
1643
|
+
if (result.updateIssue.errors?.length) throw this.formatError({ message: result.updateIssue.errors[0].message });
|
|
1644
|
+
return {
|
|
1645
|
+
success: result.updateIssue.success,
|
|
1646
|
+
issue: {
|
|
1647
|
+
id: result.updateIssue.issue.id,
|
|
1648
|
+
number: result.updateIssue.issue.number,
|
|
1649
|
+
title: result.updateIssue.issue.title,
|
|
1650
|
+
htmlUrl: result.updateIssue.issue.htmlUrl,
|
|
1651
|
+
labels: result.updateIssue.issue.labels
|
|
1652
|
+
}
|
|
1653
|
+
};
|
|
1654
|
+
}
|
|
1566
1655
|
async getTaskSteps(id) {
|
|
1567
1656
|
const data = await (await getGraphQLClient()).request(queries.GetPlan, { id: String(id) });
|
|
1568
1657
|
if (!data.plan?.steps) return [];
|
|
@@ -1961,7 +2050,7 @@ async function createGraphQLClient() {
|
|
|
1961
2050
|
|
|
1962
2051
|
//#endregion
|
|
1963
2052
|
//#region package.json
|
|
1964
|
-
var version = "0.0.
|
|
2053
|
+
var version = "0.0.47";
|
|
1965
2054
|
|
|
1966
2055
|
//#endregion
|
|
1967
2056
|
//#region src/runner/index.ts
|
|
@@ -2184,7 +2273,6 @@ var TaskRunner = class {
|
|
|
2184
2273
|
const url = new URL(WS_URL);
|
|
2185
2274
|
url.searchParams.set("apiKey", jiveApiKey);
|
|
2186
2275
|
url.searchParams.set("runnerId", this.config.id.toString());
|
|
2187
|
-
console.log(chalk.dim(`Connecting to ${url.toString()}...`));
|
|
2188
2276
|
this.ws = new WebSocket(url.toString());
|
|
2189
2277
|
this.ws.on("open", () => this.handleOpen());
|
|
2190
2278
|
this.ws.on("message", (data) => this.handleMessage(data));
|
|
@@ -2419,17 +2507,10 @@ async function queryClaude(prompt, mcpServer, opts) {
|
|
|
2419
2507
|
permissionMode: mapPermissionMode(permissionMode),
|
|
2420
2508
|
...betaFlags.length > 0 && { betas: betaFlags },
|
|
2421
2509
|
canUseTool: async (toolName, input) => {
|
|
2422
|
-
if (toolName === "AskUserQuestion") {
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
updatedInput: result
|
|
2427
|
-
};
|
|
2428
|
-
else return {
|
|
2429
|
-
behavior: "deny",
|
|
2430
|
-
message: "User did not answer questions in time"
|
|
2431
|
-
};
|
|
2432
|
-
}
|
|
2510
|
+
if (toolName === "AskUserQuestion") return {
|
|
2511
|
+
behavior: "deny",
|
|
2512
|
+
message: "AskUserQuestion is not allowed. Use the ask_user_question tool from the jive-tasks MCP server instead."
|
|
2513
|
+
};
|
|
2433
2514
|
if (await task.requestToolPermission(toolName, input)) return {
|
|
2434
2515
|
behavior: "allow",
|
|
2435
2516
|
updatedInput: input
|
|
@@ -2439,6 +2520,11 @@ async function queryClaude(prompt, mcpServer, opts) {
|
|
|
2439
2520
|
message: "User denied this action"
|
|
2440
2521
|
};
|
|
2441
2522
|
},
|
|
2523
|
+
systemPrompt: {
|
|
2524
|
+
type: "preset",
|
|
2525
|
+
preset: "claude_code",
|
|
2526
|
+
append: "When you want to use AskUserQuestion, use the ask_user_question tool from the jive-tasks MCP server instead. DO NOT USE THE AskUserQuestion tool directly."
|
|
2527
|
+
},
|
|
2442
2528
|
stderr: (data) => {
|
|
2443
2529
|
console.error(data.toString());
|
|
2444
2530
|
}
|
|
@@ -2714,6 +2800,136 @@ function createTasksSdkServer(task) {
|
|
|
2714
2800
|
isError: true
|
|
2715
2801
|
};
|
|
2716
2802
|
}
|
|
2803
|
+
}),
|
|
2804
|
+
tool("create_issue", "Create an issue on GitHub or GitLab for the current project. Use this to track bugs, feature requests, or other work items.", {
|
|
2805
|
+
title: z.string().describe("Issue title - a concise summary of the issue"),
|
|
2806
|
+
body: z.string().describe("Issue body/description - detailed explanation of the issue"),
|
|
2807
|
+
labels: z.array(z.string()).optional().describe("Optional labels to apply to the issue")
|
|
2808
|
+
}, async (args) => {
|
|
2809
|
+
if (!context) return {
|
|
2810
|
+
content: [{
|
|
2811
|
+
type: "text",
|
|
2812
|
+
text: "Error: Task context not initialized"
|
|
2813
|
+
}],
|
|
2814
|
+
isError: true
|
|
2815
|
+
};
|
|
2816
|
+
try {
|
|
2817
|
+
const apiClient$1 = getApiClient();
|
|
2818
|
+
const { project } = await apiClient$1.getTask(context.taskId);
|
|
2819
|
+
if (!project) return {
|
|
2820
|
+
content: [{
|
|
2821
|
+
type: "text",
|
|
2822
|
+
text: "Error: Project not found for this task"
|
|
2823
|
+
}],
|
|
2824
|
+
isError: true
|
|
2825
|
+
};
|
|
2826
|
+
task.debugLog(`Creating issue: ${args.title}`);
|
|
2827
|
+
const result = await apiClient$1.createIssue(project.id, {
|
|
2828
|
+
title: args.title,
|
|
2829
|
+
body: args.body,
|
|
2830
|
+
labels: args.labels
|
|
2831
|
+
});
|
|
2832
|
+
task.debugLog(`Issue created: ${JSON.stringify(result, null, 2)}`);
|
|
2833
|
+
return { content: [{
|
|
2834
|
+
type: "text",
|
|
2835
|
+
text: JSON.stringify({
|
|
2836
|
+
success: true,
|
|
2837
|
+
projectId: project.id,
|
|
2838
|
+
issue: result.issue
|
|
2839
|
+
}, null, 2)
|
|
2840
|
+
}] };
|
|
2841
|
+
} catch (error$1) {
|
|
2842
|
+
return {
|
|
2843
|
+
content: [{
|
|
2844
|
+
type: "text",
|
|
2845
|
+
text: `Error creating issue: ${error$1.message}`
|
|
2846
|
+
}],
|
|
2847
|
+
isError: true
|
|
2848
|
+
};
|
|
2849
|
+
}
|
|
2850
|
+
}),
|
|
2851
|
+
tool("update_issue", "Update an existing issue on GitHub or GitLab. Use this to modify issue title, body, state (open/closed), or labels.", {
|
|
2852
|
+
number: z.number().describe("Issue number to update"),
|
|
2853
|
+
title: z.string().optional().describe("New title for the issue"),
|
|
2854
|
+
body: z.string().optional().describe("New body/description for the issue"),
|
|
2855
|
+
state: z.enum(["open", "closed"]).optional().describe("Issue state - \"open\" or \"closed\""),
|
|
2856
|
+
labels: z.array(z.string()).optional().describe("Labels to set on the issue (replaces existing labels)")
|
|
2857
|
+
}, async (args) => {
|
|
2858
|
+
if (!context) return {
|
|
2859
|
+
content: [{
|
|
2860
|
+
type: "text",
|
|
2861
|
+
text: "Error: Task context not initialized"
|
|
2862
|
+
}],
|
|
2863
|
+
isError: true
|
|
2864
|
+
};
|
|
2865
|
+
try {
|
|
2866
|
+
const apiClient$1 = getApiClient();
|
|
2867
|
+
const { project } = await apiClient$1.getTask(context.taskId);
|
|
2868
|
+
if (!project) return {
|
|
2869
|
+
content: [{
|
|
2870
|
+
type: "text",
|
|
2871
|
+
text: "Error: Project not found for this task"
|
|
2872
|
+
}],
|
|
2873
|
+
isError: true
|
|
2874
|
+
};
|
|
2875
|
+
task.debugLog(`Updating issue #${args.number}`);
|
|
2876
|
+
const result = await apiClient$1.updateIssue(project.id, {
|
|
2877
|
+
number: args.number,
|
|
2878
|
+
title: args.title,
|
|
2879
|
+
body: args.body,
|
|
2880
|
+
state: args.state,
|
|
2881
|
+
labels: args.labels
|
|
2882
|
+
});
|
|
2883
|
+
task.debugLog(`Issue updated: ${JSON.stringify(result, null, 2)}`);
|
|
2884
|
+
return { content: [{
|
|
2885
|
+
type: "text",
|
|
2886
|
+
text: JSON.stringify({
|
|
2887
|
+
success: true,
|
|
2888
|
+
projectId: project.id,
|
|
2889
|
+
issue: result.issue
|
|
2890
|
+
}, null, 2)
|
|
2891
|
+
}] };
|
|
2892
|
+
} catch (error$1) {
|
|
2893
|
+
return {
|
|
2894
|
+
content: [{
|
|
2895
|
+
type: "text",
|
|
2896
|
+
text: `Error updating issue: ${error$1.message}`
|
|
2897
|
+
}],
|
|
2898
|
+
isError: true
|
|
2899
|
+
};
|
|
2900
|
+
}
|
|
2901
|
+
}),
|
|
2902
|
+
tool("ask_user_question", "Ask the user clarifying questions. Returns immediately - answers will be provided in a follow-up message.", { questions: z.array(z.object({
|
|
2903
|
+
question: z.string().describe("The question to ask"),
|
|
2904
|
+
header: z.string().describe("Short header/label for the question"),
|
|
2905
|
+
options: z.array(z.object({
|
|
2906
|
+
label: z.string(),
|
|
2907
|
+
description: z.string()
|
|
2908
|
+
})).describe("Options for the user to choose from"),
|
|
2909
|
+
multiSelect: z.boolean().describe("Allow multiple selections")
|
|
2910
|
+
})).describe("Questions to ask the user") }, async (args) => {
|
|
2911
|
+
if (!context) return {
|
|
2912
|
+
content: [{
|
|
2913
|
+
type: "text",
|
|
2914
|
+
text: "Error: Task context not initialized"
|
|
2915
|
+
}],
|
|
2916
|
+
isError: true
|
|
2917
|
+
};
|
|
2918
|
+
try {
|
|
2919
|
+
task.sendUserQuestionRequest(args.questions);
|
|
2920
|
+
return { content: [{
|
|
2921
|
+
type: "text",
|
|
2922
|
+
text: `Questions sent to user. Their answers will be provided in a follow-up message. You may continue with other work while waiting.`
|
|
2923
|
+
}] };
|
|
2924
|
+
} catch (error$1) {
|
|
2925
|
+
return {
|
|
2926
|
+
content: [{
|
|
2927
|
+
type: "text",
|
|
2928
|
+
text: `Error: ${error$1.message}`
|
|
2929
|
+
}],
|
|
2930
|
+
isError: true
|
|
2931
|
+
};
|
|
2932
|
+
}
|
|
2717
2933
|
})
|
|
2718
2934
|
]
|
|
2719
2935
|
});
|
|
@@ -2882,7 +3098,6 @@ var Task = class {
|
|
|
2882
3098
|
heartbeatInterval = null;
|
|
2883
3099
|
pendingAcks = /* @__PURE__ */ new Map();
|
|
2884
3100
|
pendingPermissions = /* @__PURE__ */ new Map();
|
|
2885
|
-
pendingQuestions = /* @__PURE__ */ new Map();
|
|
2886
3101
|
tunnels = /* @__PURE__ */ new Map();
|
|
2887
3102
|
proxies = /* @__PURE__ */ new Map();
|
|
2888
3103
|
primaryTunnelHost = null;
|
|
@@ -3057,10 +3272,6 @@ var Task = class {
|
|
|
3057
3272
|
this.handleToolPermissionResponse(inputMessage.payload);
|
|
3058
3273
|
continue;
|
|
3059
3274
|
}
|
|
3060
|
-
if (inputMessage.type === "user_question_response") {
|
|
3061
|
-
this.handleUserQuestionResponse(inputMessage.payload);
|
|
3062
|
-
continue;
|
|
3063
|
-
}
|
|
3064
3275
|
if (this.status !== "idle") {
|
|
3065
3276
|
this.debugLog(`Queueing message (status: ${this.status}): ${inputMessage.type}`);
|
|
3066
3277
|
this.queuedMessages.push(inputMessage);
|
|
@@ -3115,43 +3326,21 @@ var Task = class {
|
|
|
3115
3326
|
return approved;
|
|
3116
3327
|
}
|
|
3117
3328
|
/**
|
|
3118
|
-
*
|
|
3119
|
-
*
|
|
3329
|
+
* Send user questions to the server (non-blocking).
|
|
3330
|
+
* Answers will come back as a user message via promptSendToRunner.
|
|
3120
3331
|
*/
|
|
3121
|
-
|
|
3332
|
+
sendUserQuestionRequest(questions) {
|
|
3122
3333
|
const requestId = crypto.randomUUID();
|
|
3123
|
-
const TIMEOUT_MS = 6e4 * 60 * 24;
|
|
3124
|
-
const questionPromise = new Promise((resolve) => {
|
|
3125
|
-
const timeout = setTimeout(() => {
|
|
3126
|
-
this.debugLog(`User question request ${requestId} timed out (24h)`);
|
|
3127
|
-
this.pendingQuestions.delete(requestId);
|
|
3128
|
-
resolve(null);
|
|
3129
|
-
}, TIMEOUT_MS);
|
|
3130
|
-
this.pendingQuestions.set(requestId, {
|
|
3131
|
-
resolve,
|
|
3132
|
-
timeout
|
|
3133
|
-
});
|
|
3134
|
-
});
|
|
3135
3334
|
this.sendToTaskRunner({
|
|
3136
3335
|
type: "user_question_request",
|
|
3137
3336
|
payload: {
|
|
3138
3337
|
requestId,
|
|
3139
3338
|
sessionId: this.ctx.sessionId,
|
|
3140
|
-
questions
|
|
3141
|
-
expiresAt: new Date(Date.now() +
|
|
3339
|
+
questions,
|
|
3340
|
+
expiresAt: new Date(Date.now() + 6e4 * 60 * 24).toISOString()
|
|
3142
3341
|
}
|
|
3143
3342
|
});
|
|
3144
|
-
this.debugLog(`
|
|
3145
|
-
const answers = await questionPromise;
|
|
3146
|
-
if (!answers) {
|
|
3147
|
-
this.debugLog(`User did not answer questions in time`);
|
|
3148
|
-
return null;
|
|
3149
|
-
}
|
|
3150
|
-
this.debugLog(`User answered questions`);
|
|
3151
|
-
return {
|
|
3152
|
-
questions: input.questions,
|
|
3153
|
-
answers
|
|
3154
|
-
};
|
|
3343
|
+
this.debugLog(`Sent ${questions.length} question(s) to user (requestId: ${requestId})`);
|
|
3155
3344
|
}
|
|
3156
3345
|
/**
|
|
3157
3346
|
* Handle tool permission response from runner
|
|
@@ -3167,20 +3356,6 @@ var Task = class {
|
|
|
3167
3356
|
pending.resolve(payload.approved);
|
|
3168
3357
|
this.pendingPermissions.delete(payload.requestId);
|
|
3169
3358
|
}
|
|
3170
|
-
/**
|
|
3171
|
-
* Handle user question response from runner
|
|
3172
|
-
*/
|
|
3173
|
-
handleUserQuestionResponse(payload) {
|
|
3174
|
-
const pending = this.pendingQuestions.get(payload.requestId);
|
|
3175
|
-
if (!pending) {
|
|
3176
|
-
this.debugLog(`Received question response for unknown request: ${payload.requestId}`);
|
|
3177
|
-
return;
|
|
3178
|
-
}
|
|
3179
|
-
this.updateLastActivity();
|
|
3180
|
-
clearTimeout(pending.timeout);
|
|
3181
|
-
pending.resolve(payload.answers);
|
|
3182
|
-
this.pendingQuestions.delete(payload.requestId);
|
|
3183
|
-
}
|
|
3184
3359
|
processMessage(inputMessage) {
|
|
3185
3360
|
this.updateLastActivity();
|
|
3186
3361
|
switch (inputMessage.type) {
|
|
@@ -3193,9 +3368,6 @@ var Task = class {
|
|
|
3193
3368
|
case "tool_permission_response":
|
|
3194
3369
|
this.handleToolPermissionResponse(inputMessage.payload);
|
|
3195
3370
|
break;
|
|
3196
|
-
case "user_question_response":
|
|
3197
|
-
this.handleUserQuestionResponse(inputMessage.payload);
|
|
3198
|
-
break;
|
|
3199
3371
|
}
|
|
3200
3372
|
}
|
|
3201
3373
|
processQueuedMessages() {
|
|
@@ -3732,6 +3904,17 @@ Host gitlab.com
|
|
|
3732
3904
|
this.claudeAbortController = null;
|
|
3733
3905
|
this.sendStatusUpdate("idle");
|
|
3734
3906
|
}
|
|
3907
|
+
reportUsage(message) {
|
|
3908
|
+
if (!("total_cost_usd" in message)) return;
|
|
3909
|
+
this.sendToTaskRunner({
|
|
3910
|
+
type: "usage",
|
|
3911
|
+
payload: { usage: {
|
|
3912
|
+
total_cost_usd: message.total_cost_usd,
|
|
3913
|
+
total_input_tokens: message.usage.input_tokens,
|
|
3914
|
+
total_output_tokens: message.usage.output_tokens
|
|
3915
|
+
} }
|
|
3916
|
+
});
|
|
3917
|
+
}
|
|
3735
3918
|
async queryClaude(prompt, mode = "BYPASS_PERMISSIONS") {
|
|
3736
3919
|
if (this.status !== "idle") {
|
|
3737
3920
|
this.debugLog(`WARNING: queryClaude called while status is '${this.status}'`);
|
|
@@ -3750,7 +3933,7 @@ Host gitlab.com
|
|
|
3750
3933
|
permissionMode: mode,
|
|
3751
3934
|
task: this
|
|
3752
3935
|
});
|
|
3753
|
-
for await (const
|
|
3936
|
+
for await (const message of result) if (message.type === "result") this.reportUsage(message);
|
|
3754
3937
|
const finalLines = await this.readNewSessionLines();
|
|
3755
3938
|
for (const line of finalLines) try {
|
|
3756
3939
|
const parsed = JSON.parse(line);
|
|
@@ -4139,7 +4322,7 @@ async function installServiceCommand() {
|
|
|
4139
4322
|
console.log(chalk.bold("\nJive Task Runner Service Installation"));
|
|
4140
4323
|
console.log(chalk.dim("=========================================\n"));
|
|
4141
4324
|
try {
|
|
4142
|
-
const { getServiceManager, validateServiceInstallation, detectPlatform } = await import("./service-
|
|
4325
|
+
const { getServiceManager, validateServiceInstallation, detectPlatform } = await import("./service-MMjLsA9C.mjs");
|
|
4143
4326
|
if (detectPlatform() === "unsupported") {
|
|
4144
4327
|
console.error(chalk.red("Service installation is not supported on this platform."));
|
|
4145
4328
|
console.log(chalk.dim("\nCurrently supported platforms:"));
|
|
@@ -4171,7 +4354,8 @@ async function installServiceCommand() {
|
|
|
4171
4354
|
}
|
|
4172
4355
|
if (validation.hasWarnings) console.log(chalk.yellow("\n⚠ Installation can proceed, but there are warnings."));
|
|
4173
4356
|
console.log(chalk.dim(`\nPlatform: Linux (systemd)`));
|
|
4174
|
-
console.log(chalk.dim(`Service file:
|
|
4357
|
+
console.log(chalk.dim(`Service file: /etc/systemd/system/jive-task-runner.service`));
|
|
4358
|
+
console.log(chalk.dim(`Credentials: /etc/jive/runner.env\n`));
|
|
4175
4359
|
const { confirm } = await prompts({
|
|
4176
4360
|
type: "confirm",
|
|
4177
4361
|
name: "confirm",
|
|
@@ -4182,8 +4366,9 @@ async function installServiceCommand() {
|
|
|
4182
4366
|
console.log(chalk.yellow("Installation cancelled"));
|
|
4183
4367
|
return;
|
|
4184
4368
|
}
|
|
4185
|
-
spinner.start("Creating service file...");
|
|
4186
4369
|
const manager = getServiceManager();
|
|
4370
|
+
await manager.checkAndMigrateLegacyService();
|
|
4371
|
+
spinner.start("Creating service file...");
|
|
4187
4372
|
await manager.install();
|
|
4188
4373
|
spinner.succeed("Service installed successfully!");
|
|
4189
4374
|
const { startNow } = await prompts({
|
|
@@ -4201,9 +4386,10 @@ async function installServiceCommand() {
|
|
|
4201
4386
|
if (status.uptime) console.log(chalk.dim(`Uptime: ${status.uptime}`));
|
|
4202
4387
|
}
|
|
4203
4388
|
console.log(chalk.bold("\nNext steps:"));
|
|
4204
|
-
console.log(chalk.dim(" • View logs: ") + chalk.cyan("jive
|
|
4205
|
-
console.log(chalk.dim(" • Check status: ") + chalk.cyan("jive
|
|
4206
|
-
console.log(chalk.dim(" • Restart: ") + chalk.cyan("jive
|
|
4389
|
+
console.log(chalk.dim(" • View logs: ") + chalk.cyan("journalctl -u jive-task-runner -f"));
|
|
4390
|
+
console.log(chalk.dim(" • Check status: ") + chalk.cyan("systemctl status jive-task-runner"));
|
|
4391
|
+
console.log(chalk.dim(" • Restart: ") + chalk.cyan("sudo systemctl restart jive-task-runner"));
|
|
4392
|
+
console.log(chalk.dim(" • Uninstall: ") + chalk.cyan("jive task-runner uninstall-service"));
|
|
4207
4393
|
} catch (error$1) {
|
|
4208
4394
|
console.error(chalk.red(`\n✗ Installation failed: ${error$1.message}`));
|
|
4209
4395
|
process.exit(1);
|
|
@@ -4214,7 +4400,7 @@ async function installServiceCommand() {
|
|
|
4214
4400
|
*/
|
|
4215
4401
|
async function uninstallServiceCommand() {
|
|
4216
4402
|
try {
|
|
4217
|
-
const { getServiceManager } = await import("./service-
|
|
4403
|
+
const { getServiceManager } = await import("./service-MMjLsA9C.mjs");
|
|
4218
4404
|
const manager = getServiceManager();
|
|
4219
4405
|
if (!await manager.isInstalled()) {
|
|
4220
4406
|
console.log(chalk.yellow("Service is not installed."));
|
|
@@ -4244,7 +4430,7 @@ async function uninstallServiceCommand() {
|
|
|
4244
4430
|
*/
|
|
4245
4431
|
async function serviceStatusCommand() {
|
|
4246
4432
|
try {
|
|
4247
|
-
const { getServiceManager } = await import("./service-
|
|
4433
|
+
const { getServiceManager } = await import("./service-MMjLsA9C.mjs");
|
|
4248
4434
|
const manager = getServiceManager();
|
|
4249
4435
|
if (!await manager.isInstalled()) {
|
|
4250
4436
|
console.log(chalk.dim("Service is not installed."));
|
|
@@ -4276,10 +4462,10 @@ async function serviceStatusCommand() {
|
|
|
4276
4462
|
}
|
|
4277
4463
|
console.log(chalk.bold("\nCommands:"));
|
|
4278
4464
|
console.log(chalk.dim("─".repeat(60)));
|
|
4279
|
-
console.log(chalk.dim(" Logs: ") + chalk.cyan("jive
|
|
4280
|
-
console.log(chalk.dim(" Restart: ") + chalk.cyan("jive
|
|
4281
|
-
if (status.running) console.log(chalk.dim(" Stop: ") + chalk.cyan("systemctl
|
|
4282
|
-
else console.log(chalk.dim(" Start: ") + chalk.cyan("systemctl
|
|
4465
|
+
console.log(chalk.dim(" Logs: ") + chalk.cyan("journalctl -u jive-task-runner -f"));
|
|
4466
|
+
console.log(chalk.dim(" Restart: ") + chalk.cyan("sudo systemctl restart jive-task-runner"));
|
|
4467
|
+
if (status.running) console.log(chalk.dim(" Stop: ") + chalk.cyan("sudo systemctl stop jive-task-runner"));
|
|
4468
|
+
else console.log(chalk.dim(" Start: ") + chalk.cyan("sudo systemctl start jive-task-runner"));
|
|
4283
4469
|
console.log(chalk.dim(" Uninstall: ") + chalk.cyan("jive task-runner uninstall-service"));
|
|
4284
4470
|
console.log();
|
|
4285
4471
|
} catch (error$1) {
|
|
@@ -4292,7 +4478,7 @@ async function serviceStatusCommand() {
|
|
|
4292
4478
|
*/
|
|
4293
4479
|
async function serviceLogsCommand(options) {
|
|
4294
4480
|
try {
|
|
4295
|
-
const { getServiceManager } = await import("./service-
|
|
4481
|
+
const { getServiceManager } = await import("./service-MMjLsA9C.mjs");
|
|
4296
4482
|
const manager = getServiceManager();
|
|
4297
4483
|
if (!await manager.isInstalled()) {
|
|
4298
4484
|
console.log(chalk.dim("Service is not installed."));
|
|
@@ -4314,7 +4500,7 @@ async function serviceLogsCommand(options) {
|
|
|
4314
4500
|
*/
|
|
4315
4501
|
async function serviceRestartCommand() {
|
|
4316
4502
|
try {
|
|
4317
|
-
const { getServiceManager } = await import("./service-
|
|
4503
|
+
const { getServiceManager } = await import("./service-MMjLsA9C.mjs");
|
|
4318
4504
|
const manager = getServiceManager();
|
|
4319
4505
|
if (!await manager.isInstalled()) {
|
|
4320
4506
|
console.log(chalk.dim("Service is not installed."));
|
|
@@ -2,21 +2,31 @@ import { E as WS_URL, T as GRAPHQL_API_URL, w as API_URL } from "./index.mjs";
|
|
|
2
2
|
import fs from "fs/promises";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import os from "os";
|
|
5
|
-
import { exec, spawn } from "child_process";
|
|
5
|
+
import { exec, spawn, spawnSync } from "child_process";
|
|
6
6
|
import { promisify } from "util";
|
|
7
7
|
|
|
8
8
|
//#region src/lib/service/systemd.ts
|
|
9
9
|
const execAsync$1 = promisify(exec);
|
|
10
|
+
const SERVICE_NAME = "jive-task-runner";
|
|
11
|
+
const SYSTEM_SERVICE_PATH = `/etc/systemd/system/${SERVICE_NAME}.service`;
|
|
12
|
+
const USER_SERVICE_DIR = path.join(os.homedir(), ".config", "systemd", "user");
|
|
13
|
+
const LEGACY_USER_SERVICE_PATH = path.join(USER_SERVICE_DIR, `${SERVICE_NAME}.service`);
|
|
14
|
+
const ENV_DIR = "/etc/jive";
|
|
15
|
+
const ENV_FILE_PATH = `${ENV_DIR}/runner.env`;
|
|
10
16
|
const SERVICE_TEMPLATE = `[Unit]
|
|
11
17
|
Description=Jive Task Runner
|
|
12
18
|
Documentation=https://getjive.app/docs
|
|
13
|
-
After=network-online.target
|
|
19
|
+
After=network-online.target docker.service
|
|
14
20
|
Wants=network-online.target
|
|
21
|
+
Requires=docker.service
|
|
15
22
|
|
|
16
23
|
[Service]
|
|
17
24
|
Type=simple
|
|
25
|
+
User={{SERVICE_USER}}
|
|
26
|
+
Group={{SERVICE_GROUP}}
|
|
27
|
+
WorkingDirectory={{WORKING_DIRECTORY}}
|
|
18
28
|
ExecStart={{JIVE_BINARY_PATH}} task-runner start
|
|
19
|
-
Restart=
|
|
29
|
+
Restart=always
|
|
20
30
|
RestartSec=30
|
|
21
31
|
TimeoutStartSec=90
|
|
22
32
|
TimeoutStopSec=30
|
|
@@ -24,13 +34,10 @@ StartLimitBurst=5
|
|
|
24
34
|
StartLimitIntervalSec=10m
|
|
25
35
|
KillMode=mixed
|
|
26
36
|
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
37
|
Environment="JIVE_API_URL={{JIVE_API_URL}}"
|
|
32
38
|
Environment="JIVE_WS_URL={{JIVE_WS_URL}}"
|
|
33
39
|
Environment="JIVE_GRAPHQL_API_URL={{JIVE_GRAPHQL_API_URL}}"
|
|
40
|
+
EnvironmentFile={{ENV_FILE_PATH}}
|
|
34
41
|
|
|
35
42
|
StandardOutput=journal
|
|
36
43
|
StandardError=journal
|
|
@@ -43,16 +50,64 @@ ProtectKernelTunables=true
|
|
|
43
50
|
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
|
|
44
51
|
|
|
45
52
|
[Install]
|
|
46
|
-
WantedBy=
|
|
53
|
+
WantedBy=multi-user.target
|
|
47
54
|
`;
|
|
55
|
+
/**
|
|
56
|
+
* Check if the current process is running as root (UID 0)
|
|
57
|
+
*/
|
|
58
|
+
function isRunningAsRoot() {
|
|
59
|
+
return process.getuid?.() === 0;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Re-execute the current command with sudo, preserving PATH and environment.
|
|
63
|
+
* This allows users to just run `jive task-runner install-service` without
|
|
64
|
+
* needing to manually invoke sudo with the correct environment.
|
|
65
|
+
*/
|
|
66
|
+
function reExecWithSudo() {
|
|
67
|
+
const nodePath = process.execPath;
|
|
68
|
+
const scriptPath = process.argv[1];
|
|
69
|
+
const args = process.argv.slice(2);
|
|
70
|
+
console.log("Root privileges required. Re-running with sudo...\n");
|
|
71
|
+
const result = spawnSync("sudo", [
|
|
72
|
+
"--preserve-env=PATH,HOME",
|
|
73
|
+
`SUDO_USER=${process.env.USER}`,
|
|
74
|
+
`SUDO_UID=${process.getuid?.() || 1e3}`,
|
|
75
|
+
`SUDO_GID=${process.getgid?.() || 1e3}`,
|
|
76
|
+
nodePath,
|
|
77
|
+
scriptPath,
|
|
78
|
+
...args
|
|
79
|
+
], {
|
|
80
|
+
stdio: "inherit",
|
|
81
|
+
env: process.env
|
|
82
|
+
});
|
|
83
|
+
process.exit(result.status ?? 1);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get the original user info when running with sudo
|
|
87
|
+
* Returns the user who invoked sudo, not root
|
|
88
|
+
*/
|
|
89
|
+
function getCurrentUser() {
|
|
90
|
+
return {
|
|
91
|
+
uid: parseInt(process.env.SUDO_UID || String(process.getuid?.() || 1e3), 10),
|
|
92
|
+
gid: parseInt(process.env.SUDO_GID || String(process.getgid?.() || 1e3), 10),
|
|
93
|
+
username: process.env.SUDO_USER || process.env.USER || "jive",
|
|
94
|
+
homeDir: process.env.SUDO_USER ? `/home/${process.env.SUDO_USER}` : os.homedir()
|
|
95
|
+
};
|
|
96
|
+
}
|
|
48
97
|
var SystemdServiceManager = class {
|
|
49
|
-
servicePath;
|
|
50
|
-
serviceName =
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
98
|
+
servicePath = SYSTEM_SERVICE_PATH;
|
|
99
|
+
serviceName = SERVICE_NAME;
|
|
100
|
+
/**
|
|
101
|
+
* Check for legacy user service and handle migration.
|
|
102
|
+
* Call this BEFORE starting any spinners since it may prompt the user.
|
|
103
|
+
*/
|
|
104
|
+
async checkAndMigrateLegacyService() {
|
|
105
|
+
if (!isRunningAsRoot()) reExecWithSudo();
|
|
106
|
+
await this.migrateFromUserService();
|
|
54
107
|
}
|
|
55
108
|
async install() {
|
|
109
|
+
if (!isRunningAsRoot()) reExecWithSudo();
|
|
110
|
+
const user = getCurrentUser();
|
|
56
111
|
const { getRunnerConfig } = await import("./tasks-Py86q1u7.mjs");
|
|
57
112
|
const { getCredentials } = await import("./config-7rVDmj2u.mjs");
|
|
58
113
|
const runnerConfig = await getRunnerConfig();
|
|
@@ -72,41 +127,86 @@ var SystemdServiceManager = class {
|
|
|
72
127
|
if (resolvedPath.includes(pattern)) throw new Error(`Detected unstable path for jive binary: ${resolvedPath}\nThis path contains '${pattern}' which may not persist across reboots.\n\nIf you're using fnm, try:\n 1. Run: fnm exec --using=default -- npm install -g @jive-ai/cli\n 2. Then run install-service again from a fresh terminal`);
|
|
73
128
|
if (resolvedNodePath.includes(pattern)) throw new Error(`Detected unstable path for node binary: ${resolvedNodePath}\nThis path contains '${pattern}' which may not persist across reboots.\n\nIf you're using fnm, ensure you have a default node version set:\n fnm default <version>`);
|
|
74
129
|
}
|
|
75
|
-
|
|
76
|
-
JIVE_BINARY_PATH: resolvedPath,
|
|
77
|
-
NODE_BIN_PATH: nodeBinDir,
|
|
130
|
+
await this.createEnvironmentFile({
|
|
78
131
|
JIVE_API_KEY: credentials.token,
|
|
79
132
|
ANTHROPIC_API_KEY: credentials.anthropicApiKey || "",
|
|
80
133
|
JIVE_TEAM_ID: runnerConfig.teamId,
|
|
81
|
-
JIVE_RUNNER_ID: runnerConfig.id.toString()
|
|
134
|
+
JIVE_RUNNER_ID: runnerConfig.id.toString()
|
|
135
|
+
});
|
|
136
|
+
const variables = {
|
|
137
|
+
SERVICE_USER: user.username,
|
|
138
|
+
SERVICE_GROUP: user.username,
|
|
139
|
+
WORKING_DIRECTORY: user.homeDir,
|
|
140
|
+
JIVE_BINARY_PATH: resolvedPath,
|
|
141
|
+
NODE_BIN_PATH: nodeBinDir,
|
|
82
142
|
JIVE_API_URL: process.env.JIVE_API_URL || API_URL,
|
|
83
143
|
JIVE_WS_URL: process.env.JIVE_WS_URL || WS_URL,
|
|
84
|
-
JIVE_GRAPHQL_API_URL: process.env.JIVE_GRAPHQL_API_URL || GRAPHQL_API_URL
|
|
144
|
+
JIVE_GRAPHQL_API_URL: process.env.JIVE_GRAPHQL_API_URL || GRAPHQL_API_URL,
|
|
145
|
+
ENV_FILE_PATH
|
|
85
146
|
};
|
|
86
147
|
let serviceContent = SERVICE_TEMPLATE;
|
|
87
148
|
for (const [key, value] of Object.entries(variables)) serviceContent = serviceContent.replace(new RegExp(`{{${key}}}`, "g"), value);
|
|
88
|
-
|
|
89
|
-
await fs.mkdir(serviceDir, {
|
|
90
|
-
recursive: true,
|
|
91
|
-
mode: 493
|
|
92
|
-
});
|
|
93
|
-
const dirMode = (await fs.stat(serviceDir)).mode & 511;
|
|
94
|
-
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}`);
|
|
95
|
-
await fs.writeFile(this.servicePath, serviceContent, { mode: 384 });
|
|
149
|
+
await fs.writeFile(this.servicePath, serviceContent, { mode: 420 });
|
|
96
150
|
try {
|
|
97
|
-
await execAsync$1("systemctl
|
|
98
|
-
await execAsync$1(`systemctl
|
|
151
|
+
await execAsync$1("systemctl daemon-reload", { timeout: 3e4 });
|
|
152
|
+
await execAsync$1(`systemctl enable ${this.serviceName}`, { timeout: 3e4 });
|
|
99
153
|
} catch (error) {
|
|
100
154
|
try {
|
|
101
155
|
await fs.unlink(this.servicePath);
|
|
102
|
-
await
|
|
156
|
+
await fs.unlink(ENV_FILE_PATH);
|
|
157
|
+
await execAsync$1("systemctl daemon-reload", { timeout: 3e4 });
|
|
103
158
|
} catch (cleanupError) {
|
|
104
159
|
console.error("Failed to clean up after installation failure:", cleanupError);
|
|
105
160
|
}
|
|
106
161
|
throw new Error(`Service installation failed: ${error.message}\nPartial installation has been rolled back.`);
|
|
107
162
|
}
|
|
108
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* Create the environment file with sensitive credentials
|
|
166
|
+
*/
|
|
167
|
+
async createEnvironmentFile(vars) {
|
|
168
|
+
await fs.mkdir(ENV_DIR, {
|
|
169
|
+
recursive: true,
|
|
170
|
+
mode: 493
|
|
171
|
+
});
|
|
172
|
+
const content = Object.entries(vars).map(([key, value]) => `${key}=${value}`).join("\n") + "\n";
|
|
173
|
+
await fs.writeFile(ENV_FILE_PATH, content, { mode: 384 });
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Check for and migrate from legacy user service
|
|
177
|
+
*/
|
|
178
|
+
async migrateFromUserService() {
|
|
179
|
+
try {
|
|
180
|
+
await fs.access(LEGACY_USER_SERVICE_PATH);
|
|
181
|
+
} catch {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const { default: prompts } = await import("prompts");
|
|
185
|
+
console.log("\n⚠ Found existing user service at:");
|
|
186
|
+
console.log(` ${LEGACY_USER_SERVICE_PATH}\n`);
|
|
187
|
+
const { migrate } = await prompts({
|
|
188
|
+
type: "confirm",
|
|
189
|
+
name: "migrate",
|
|
190
|
+
message: "Migrate to system service? (This will remove the old user service)",
|
|
191
|
+
initial: true
|
|
192
|
+
});
|
|
193
|
+
if (!migrate) throw new Error("Migration cancelled. Remove the user service first or choose to migrate.");
|
|
194
|
+
const user = getCurrentUser();
|
|
195
|
+
console.log("Stopping and removing legacy user service...");
|
|
196
|
+
try {
|
|
197
|
+
await execAsync$1(`sudo -u ${user.username} systemctl --user stop ${this.serviceName}`, { timeout: 3e4 });
|
|
198
|
+
} catch {}
|
|
199
|
+
try {
|
|
200
|
+
await execAsync$1(`sudo -u ${user.username} systemctl --user disable ${this.serviceName}`, { timeout: 3e4 });
|
|
201
|
+
} catch {}
|
|
202
|
+
await fs.unlink(LEGACY_USER_SERVICE_PATH);
|
|
203
|
+
try {
|
|
204
|
+
await execAsync$1(`sudo -u ${user.username} systemctl --user daemon-reload`, { timeout: 3e4 });
|
|
205
|
+
} catch {}
|
|
206
|
+
console.log("Legacy user service removed successfully.\n");
|
|
207
|
+
}
|
|
109
208
|
async uninstall() {
|
|
209
|
+
if (!isRunningAsRoot()) reExecWithSudo();
|
|
110
210
|
try {
|
|
111
211
|
await this.stop();
|
|
112
212
|
} catch (error) {
|
|
@@ -114,7 +214,7 @@ var SystemdServiceManager = class {
|
|
|
114
214
|
else console.warn(`Warning: Failed to stop service: ${error.message}`);
|
|
115
215
|
}
|
|
116
216
|
try {
|
|
117
|
-
await execAsync$1(`systemctl
|
|
217
|
+
await execAsync$1(`systemctl disable ${this.serviceName}`, { timeout: 3e4 });
|
|
118
218
|
} catch (error) {
|
|
119
219
|
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");
|
|
120
220
|
else console.warn(`Warning: Failed to disable service: ${error.message}`);
|
|
@@ -124,16 +224,24 @@ var SystemdServiceManager = class {
|
|
|
124
224
|
} catch (error) {
|
|
125
225
|
if (error.code !== "ENOENT") throw new Error(`Failed to remove service file: ${error.message}`);
|
|
126
226
|
}
|
|
127
|
-
|
|
227
|
+
try {
|
|
228
|
+
await fs.unlink(ENV_FILE_PATH);
|
|
229
|
+
} catch (error) {
|
|
230
|
+
if (error.code !== "ENOENT") console.warn(`Warning: Failed to remove environment file: ${error.message}`);
|
|
231
|
+
}
|
|
232
|
+
try {
|
|
233
|
+
await fs.rmdir(ENV_DIR);
|
|
234
|
+
} catch {}
|
|
235
|
+
await execAsync$1("systemctl daemon-reload", { timeout: 3e4 });
|
|
128
236
|
}
|
|
129
237
|
async start() {
|
|
130
|
-
await execAsync$1(`systemctl
|
|
238
|
+
await execAsync$1(`systemctl start ${this.serviceName}`, { timeout: 3e4 });
|
|
131
239
|
}
|
|
132
240
|
async stop() {
|
|
133
|
-
await execAsync$1(`systemctl
|
|
241
|
+
await execAsync$1(`systemctl stop ${this.serviceName}`, { timeout: 3e4 });
|
|
134
242
|
}
|
|
135
243
|
async restart() {
|
|
136
|
-
await execAsync$1(`systemctl
|
|
244
|
+
await execAsync$1(`systemctl restart ${this.serviceName}`, { timeout: 3e4 });
|
|
137
245
|
}
|
|
138
246
|
async status() {
|
|
139
247
|
if (!await this.isInstalled()) return {
|
|
@@ -142,11 +250,11 @@ var SystemdServiceManager = class {
|
|
|
142
250
|
enabled: false
|
|
143
251
|
};
|
|
144
252
|
try {
|
|
145
|
-
const { stdout } = await execAsync$1(`systemctl
|
|
253
|
+
const { stdout } = await execAsync$1(`systemctl status ${this.serviceName} --no-pager`, { timeout: 3e4 });
|
|
146
254
|
const running = stdout.includes("Active: active (running)");
|
|
147
255
|
const pid = this.extractPid(stdout);
|
|
148
256
|
const uptime = this.extractUptime(stdout);
|
|
149
|
-
const { stdout: isEnabledOutput } = await execAsync$1(`systemctl
|
|
257
|
+
const { stdout: isEnabledOutput } = await execAsync$1(`systemctl is-enabled ${this.serviceName}`, { timeout: 3e4 });
|
|
150
258
|
return {
|
|
151
259
|
installed: true,
|
|
152
260
|
running,
|
|
@@ -155,7 +263,7 @@ var SystemdServiceManager = class {
|
|
|
155
263
|
pid
|
|
156
264
|
};
|
|
157
265
|
} catch (error) {
|
|
158
|
-
const { stdout: isEnabledOutput } = await execAsync$1(`systemctl
|
|
266
|
+
const { stdout: isEnabledOutput } = await execAsync$1(`systemctl is-enabled ${this.serviceName}`, { timeout: 3e4 }).catch(() => ({ stdout: "disabled" }));
|
|
159
267
|
return {
|
|
160
268
|
installed: true,
|
|
161
269
|
running: false,
|
|
@@ -164,11 +272,7 @@ var SystemdServiceManager = class {
|
|
|
164
272
|
}
|
|
165
273
|
}
|
|
166
274
|
async logs(options) {
|
|
167
|
-
const args = [
|
|
168
|
-
"--user",
|
|
169
|
-
"-u",
|
|
170
|
-
this.serviceName
|
|
171
|
-
];
|
|
275
|
+
const args = ["-u", this.serviceName];
|
|
172
276
|
if (options?.follow) args.push("-f");
|
|
173
277
|
if (options?.lines) args.push("-n", options.lines.toString());
|
|
174
278
|
const logsProcess = spawn("journalctl", args, { stdio: "inherit" });
|
|
@@ -240,6 +344,18 @@ async function validateServiceInstallation() {
|
|
|
240
344
|
const checks = [];
|
|
241
345
|
let canInstall = true;
|
|
242
346
|
let hasWarnings = false;
|
|
347
|
+
if (!isRunningAsRoot()) {
|
|
348
|
+
checks.push({
|
|
349
|
+
name: "Root privileges",
|
|
350
|
+
status: "warning",
|
|
351
|
+
message: "Will prompt for sudo password"
|
|
352
|
+
});
|
|
353
|
+
hasWarnings = true;
|
|
354
|
+
} else checks.push({
|
|
355
|
+
name: "Root privileges",
|
|
356
|
+
status: "success",
|
|
357
|
+
message: "Running as root"
|
|
358
|
+
});
|
|
243
359
|
try {
|
|
244
360
|
const { getRunnerConfig } = await import("./tasks-Py86q1u7.mjs");
|
|
245
361
|
const runnerConfig = await 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.47",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
7
7
|
"dist",
|
|
@@ -15,7 +15,8 @@
|
|
|
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:
|
|
18
|
+
"docker:clean": "docker rmi jiveai/task:latest jiveai/task:$npm_package_version",
|
|
19
|
+
"docker:build": "bun run build && npm run docker:clean && .docker/build.sh",
|
|
19
20
|
"docker:push": ".docker/build.sh --push",
|
|
20
21
|
"prepublishOnly": "npm run typecheck && npm run build"
|
|
21
22
|
},
|