jobforge 0.5.2 → 0.5.3
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 +3 -1
- package/bin/jobforge.js +169 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,7 +9,9 @@ JobForge package (`jobforge install ...`); the Node package only resolves the
|
|
|
9
9
|
runtime, collects choices, and calls the Python engine.
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npx jobforge
|
|
12
|
+
npx jobforge # install or update the runtime, then install into agents
|
|
13
|
+
npx jobforge --update # force a runtime update to this installer version
|
|
14
|
+
npx jobforge --no-update
|
|
13
15
|
```
|
|
14
16
|
|
|
15
17
|
Optional browser automation (Chromium for filling applications) is offered
|
package/bin/jobforge.js
CHANGED
|
@@ -29,14 +29,18 @@ function usage() {
|
|
|
29
29
|
|
|
30
30
|
Usage:
|
|
31
31
|
npx jobforge
|
|
32
|
+
npx jobforge --update
|
|
33
|
+
npx jobforge --no-update
|
|
32
34
|
|
|
33
35
|
No arguments are required. The wizard detects your local agents, shows the
|
|
34
|
-
exact files it will write, and installs the MCP server and Agent Skill.
|
|
36
|
+
exact files it will write, and installs the MCP server and Agent Skill. It
|
|
37
|
+
also updates a stale uv-managed JobForge runtime to this installer's version.
|
|
35
38
|
|
|
36
39
|
Environment:
|
|
37
|
-
JOBFORGE_BIN
|
|
38
|
-
JOBFORGE_PYTHON
|
|
39
|
-
JOBFORGE_CONFIG
|
|
40
|
+
JOBFORGE_BIN JobForge command to use (default: jobforge on PATH)
|
|
41
|
+
JOBFORGE_PYTHON Python executable to use with \`-m jobforge\`
|
|
42
|
+
JOBFORGE_CONFIG Path to config.yml
|
|
43
|
+
JOBFORGE_INSTALL_SPEC Override the runtime install source
|
|
40
44
|
`);
|
|
41
45
|
}
|
|
42
46
|
|
|
@@ -48,11 +52,17 @@ function guard(value) {
|
|
|
48
52
|
return value;
|
|
49
53
|
}
|
|
50
54
|
|
|
51
|
-
function
|
|
52
|
-
if (!name
|
|
55
|
+
function whichPath(name) {
|
|
56
|
+
if (!name) return "";
|
|
57
|
+
if (name.includes(path.sep)) return existsSync(name) ? name : "";
|
|
53
58
|
const finder = process.platform === "win32" ? "where" : "which";
|
|
54
|
-
const result = spawnSync(finder, [name], {
|
|
55
|
-
|
|
59
|
+
const result = spawnSync(finder, [name], { encoding: "utf8" });
|
|
60
|
+
if (result.status !== 0) return "";
|
|
61
|
+
return String(result.stdout || "").split("\n")[0].trim();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function commandExists(name) {
|
|
65
|
+
return Boolean(whichPath(name));
|
|
56
66
|
}
|
|
57
67
|
|
|
58
68
|
function isPythonJobForge(cmd, prefix) {
|
|
@@ -83,14 +93,21 @@ function repoRoot(start) {
|
|
|
83
93
|
function resolveJobForge() {
|
|
84
94
|
const envBin = (process.env.JOBFORGE_BIN || "").trim();
|
|
85
95
|
if (envBin && commandExists(envBin) && isPythonJobForge(envBin, [])) {
|
|
86
|
-
return {
|
|
96
|
+
return {
|
|
97
|
+
cmd: envBin,
|
|
98
|
+
prefix: [],
|
|
99
|
+
config: process.env.JOBFORGE_CONFIG || "",
|
|
100
|
+
source: "env-bin",
|
|
101
|
+
};
|
|
87
102
|
}
|
|
88
103
|
|
|
89
|
-
|
|
104
|
+
const pathJobforge = whichPath("jobforge");
|
|
105
|
+
if (pathJobforge && isPythonJobForge(pathJobforge, [])) {
|
|
90
106
|
return {
|
|
91
|
-
cmd:
|
|
107
|
+
cmd: pathJobforge,
|
|
92
108
|
prefix: [],
|
|
93
109
|
config: process.env.JOBFORGE_CONFIG || "",
|
|
110
|
+
source: "path",
|
|
94
111
|
};
|
|
95
112
|
}
|
|
96
113
|
|
|
@@ -100,6 +117,7 @@ function resolveJobForge() {
|
|
|
100
117
|
cmd: localShim,
|
|
101
118
|
prefix: [],
|
|
102
119
|
config: process.env.JOBFORGE_CONFIG || "",
|
|
120
|
+
source: "local-shim",
|
|
103
121
|
};
|
|
104
122
|
}
|
|
105
123
|
|
|
@@ -109,6 +127,7 @@ function resolveJobForge() {
|
|
|
109
127
|
cmd: envPython,
|
|
110
128
|
prefix: ["-m", "jobforge"],
|
|
111
129
|
config: process.env.JOBFORGE_CONFIG || "",
|
|
130
|
+
source: "env-python",
|
|
112
131
|
};
|
|
113
132
|
}
|
|
114
133
|
|
|
@@ -118,6 +137,7 @@ function resolveJobForge() {
|
|
|
118
137
|
cmd: python,
|
|
119
138
|
prefix: ["-m", "jobforge"],
|
|
120
139
|
config: process.env.JOBFORGE_CONFIG || "",
|
|
140
|
+
source: "system-python",
|
|
121
141
|
};
|
|
122
142
|
}
|
|
123
143
|
}
|
|
@@ -130,6 +150,7 @@ function resolveJobForge() {
|
|
|
130
150
|
cmd: python,
|
|
131
151
|
prefix: ["-m", "jobforge"],
|
|
132
152
|
config: process.env.JOBFORGE_CONFIG || (existsSync(config) ? config : ""),
|
|
153
|
+
source: "repo",
|
|
133
154
|
};
|
|
134
155
|
}
|
|
135
156
|
|
|
@@ -174,9 +195,122 @@ function runJson(target, args, options = {}) {
|
|
|
174
195
|
}
|
|
175
196
|
}
|
|
176
197
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
198
|
+
function installSpec() {
|
|
199
|
+
return (
|
|
200
|
+
process.env.JOBFORGE_INSTALL_SPEC ||
|
|
201
|
+
`git+https://github.com/Taf0711/jobforge@v${VERSION}`
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function versionParts(value) {
|
|
206
|
+
const base = String(value || "").trim().split(/[-+]/)[0];
|
|
207
|
+
return base.split(".").map((part) => Number.parseInt(part, 10));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function isOlderVersion(installed, expected) {
|
|
211
|
+
const left = versionParts(installed);
|
|
212
|
+
const right = versionParts(expected);
|
|
213
|
+
for (let index = 0; index < Math.max(left.length, right.length); index += 1) {
|
|
214
|
+
const a = Number.isFinite(left[index]) ? left[index] : 0;
|
|
215
|
+
const b = Number.isFinite(right[index]) ? right[index] : 0;
|
|
216
|
+
if (a < b) return true;
|
|
217
|
+
if (a > b) return false;
|
|
218
|
+
}
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function runtimeVersion(target) {
|
|
223
|
+
const direct = runCommand(target, ["--version"], { timeout: 15_000 });
|
|
224
|
+
const output = `${direct.stdout || ""}${direct.stderr || ""}`.trim();
|
|
225
|
+
const match = output.match(/\b(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)\b/);
|
|
226
|
+
if (direct.status === 0 && match) return match[1];
|
|
227
|
+
try {
|
|
228
|
+
const fallback = runCommand(target, ["doctor"], { timeout: 60_000 });
|
|
229
|
+
const report = JSON.parse(String(fallback.stdout || "").trim());
|
|
230
|
+
if (report.jobforge) return String(report.jobforge);
|
|
231
|
+
} catch {
|
|
232
|
+
// Older runtimes may not expose a cheap version command.
|
|
233
|
+
}
|
|
234
|
+
return "";
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function uvToolInstalled() {
|
|
238
|
+
if (!commandExists("uv")) return false;
|
|
239
|
+
const result = runRaw("uv", ["tool", "list"], { timeout: 30_000 });
|
|
240
|
+
if (result.status !== 0) return false;
|
|
241
|
+
return String(result.stdout || "")
|
|
242
|
+
.split("\n")
|
|
243
|
+
.some((line) => /^jobforge\s+v?\d/.test(line.trim()));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function uvToolJobforgePath() {
|
|
247
|
+
if (!commandExists("uv")) return "";
|
|
248
|
+
const result = runRaw("uv", ["tool", "dir", "--bin"], { timeout: 15_000 });
|
|
249
|
+
if (result.status !== 0) return "";
|
|
250
|
+
const base = String(result.stdout || "").trim().split("\n").pop() || "";
|
|
251
|
+
if (!base) return "";
|
|
252
|
+
const candidate = path.join(base, process.platform === "win32" ? "jobforge.exe" : "jobforge");
|
|
253
|
+
return existsSync(candidate) ? candidate : "";
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function updateRuntimeIfNeeded(target, { force = false } = {}) {
|
|
257
|
+
const source = target.source || "";
|
|
258
|
+
if (["env-bin", "env-python", "repo"].includes(source)) {
|
|
259
|
+
if (force) {
|
|
260
|
+
log.info("This JobForge runtime is a local or custom build; update it with its own installer.");
|
|
261
|
+
}
|
|
262
|
+
return target;
|
|
263
|
+
}
|
|
264
|
+
if (process.env.JOBFORGE_INSTALL_SPEC && !force) return target;
|
|
265
|
+
if (!uvToolInstalled()) {
|
|
266
|
+
if (force) {
|
|
267
|
+
log.warn("The installed JobForge runtime is not managed by uv; update it with its installer.");
|
|
268
|
+
}
|
|
269
|
+
return target;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const installed = runtimeVersion(target);
|
|
273
|
+
if (!force && installed && !isOlderVersion(installed, VERSION)) return target;
|
|
274
|
+
|
|
275
|
+
const message = installed
|
|
276
|
+
? `Update the JobForge runtime ${installed} → ${VERSION}?`
|
|
277
|
+
: `Update the JobForge runtime to ${VERSION}?`;
|
|
278
|
+
const proceed = force || guard(await confirm({ message, initialValue: true }));
|
|
279
|
+
if (!proceed) return target;
|
|
280
|
+
|
|
281
|
+
const progress = spinner();
|
|
282
|
+
progress.start(`Updating JobForge runtime to ${VERSION}...`);
|
|
283
|
+
const result = runRaw(
|
|
284
|
+
"uv",
|
|
285
|
+
["tool", "install", "--force", "--from", installSpec(), "jobforge"],
|
|
286
|
+
{ timeout: 10 * 60_000 },
|
|
287
|
+
);
|
|
288
|
+
if (result.status !== 0) {
|
|
289
|
+
progress.stop("Update failed");
|
|
290
|
+
log.error("The JobForge runtime update failed.");
|
|
291
|
+
log.message((result.stderr || result.stdout || "").trim().slice(0, 1200));
|
|
292
|
+
return target;
|
|
293
|
+
}
|
|
294
|
+
progress.stop("Runtime updated");
|
|
295
|
+
|
|
296
|
+
const updated = uvToolJobforgePath();
|
|
297
|
+
if (updated && isPythonJobForge(updated, [])) {
|
|
298
|
+
return {
|
|
299
|
+
cmd: updated,
|
|
300
|
+
prefix: [],
|
|
301
|
+
config: process.env.JOBFORGE_CONFIG || "",
|
|
302
|
+
source: "uv-tool",
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
return resolveJobForge() || target;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async function ensureRuntime({ forceUpdate = false, skipUpdate = false } = {}) {
|
|
309
|
+
let existing = resolveJobForge();
|
|
310
|
+
if (existing) {
|
|
311
|
+
if (!skipUpdate) existing = await updateRuntimeIfNeeded(existing, { force: forceUpdate });
|
|
312
|
+
return existing;
|
|
313
|
+
}
|
|
180
314
|
|
|
181
315
|
log.warn("JobForge runtime not found.");
|
|
182
316
|
if (!commandExists("uv")) {
|
|
@@ -197,9 +331,7 @@ async function ensureRuntime() {
|
|
|
197
331
|
);
|
|
198
332
|
if (!proceed) return null;
|
|
199
333
|
|
|
200
|
-
const spec =
|
|
201
|
-
process.env.JOBFORGE_INSTALL_SPEC ||
|
|
202
|
-
"git+https://github.com/Taf0711/jobforge@main";
|
|
334
|
+
const spec = installSpec();
|
|
203
335
|
const result = runRaw(
|
|
204
336
|
"uv",
|
|
205
337
|
["tool", "install", "--from", spec, "jobforge"],
|
|
@@ -210,6 +342,15 @@ async function ensureRuntime() {
|
|
|
210
342
|
log.message((result.stderr || result.stdout || "").trim().slice(0, 1200));
|
|
211
343
|
return null;
|
|
212
344
|
}
|
|
345
|
+
const installed = uvToolJobforgePath();
|
|
346
|
+
if (installed && isPythonJobForge(installed, [])) {
|
|
347
|
+
return {
|
|
348
|
+
cmd: installed,
|
|
349
|
+
prefix: [],
|
|
350
|
+
config: process.env.JOBFORGE_CONFIG || "",
|
|
351
|
+
source: "uv-tool",
|
|
352
|
+
};
|
|
353
|
+
}
|
|
213
354
|
return resolveJobForge();
|
|
214
355
|
}
|
|
215
356
|
|
|
@@ -327,12 +468,21 @@ async function main() {
|
|
|
327
468
|
return;
|
|
328
469
|
}
|
|
329
470
|
if (process.argv.includes("--check-runtime")) {
|
|
330
|
-
|
|
471
|
+
const resolved = resolveJobForge();
|
|
472
|
+
console.log(
|
|
473
|
+
JSON.stringify(
|
|
474
|
+
resolved ? { ...resolved, version: runtimeVersion(resolved) } : null,
|
|
475
|
+
null,
|
|
476
|
+
2,
|
|
477
|
+
),
|
|
478
|
+
);
|
|
331
479
|
return;
|
|
332
480
|
}
|
|
333
481
|
|
|
334
482
|
intro(`JobForge ${VERSION}`);
|
|
335
|
-
const
|
|
483
|
+
const forceUpdate = process.argv.includes("--update");
|
|
484
|
+
const skipUpdate = process.argv.includes("--no-update") && !forceUpdate;
|
|
485
|
+
const target = await ensureRuntime({ forceUpdate, skipUpdate });
|
|
336
486
|
if (!target) {
|
|
337
487
|
log.error("JobForge is not installed or its Python runtime is not available.");
|
|
338
488
|
note(
|