@task0/cli 0.10.0 → 0.11.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/dist/main.js +149 -25
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -1909,11 +1909,11 @@ async function request(method, pathname, body) {
|
|
|
1909
1909
|
}
|
|
1910
1910
|
}
|
|
1911
1911
|
var api = {
|
|
1912
|
-
get: (
|
|
1913
|
-
post: (
|
|
1914
|
-
put: (
|
|
1915
|
-
patch: (
|
|
1916
|
-
del: (
|
|
1912
|
+
get: (path33) => request("GET", path33),
|
|
1913
|
+
post: (path33, body) => request("POST", path33, body ?? {}),
|
|
1914
|
+
put: (path33, body) => request("PUT", path33, body ?? {}),
|
|
1915
|
+
patch: (path33, body) => request("PATCH", path33, body ?? {}),
|
|
1916
|
+
del: (path33) => request("DELETE", path33)
|
|
1917
1917
|
};
|
|
1918
1918
|
|
|
1919
1919
|
// src/commands/task/triage.ts
|
|
@@ -6327,15 +6327,119 @@ function readCliVersion() {
|
|
|
6327
6327
|
return cached2;
|
|
6328
6328
|
}
|
|
6329
6329
|
|
|
6330
|
+
// src/core/project-watcher.ts
|
|
6331
|
+
init_node();
|
|
6332
|
+
import fs32 from "fs";
|
|
6333
|
+
import path31 from "path";
|
|
6334
|
+
var ProjectWatcher = class {
|
|
6335
|
+
perProject = /* @__PURE__ */ new Map();
|
|
6336
|
+
debounceTimers = /* @__PURE__ */ new Map();
|
|
6337
|
+
debounceMs;
|
|
6338
|
+
onChange;
|
|
6339
|
+
constructor(opts) {
|
|
6340
|
+
this.onChange = opts.onChange;
|
|
6341
|
+
this.debounceMs = opts.debounceMs ?? 300;
|
|
6342
|
+
}
|
|
6343
|
+
/**
|
|
6344
|
+
* Reconcile watchers against the desired project list. Idempotent — call it
|
|
6345
|
+
* whenever the daemon's project set changes (project_add / project_remove /
|
|
6346
|
+
* project_set_enabled RPCs, or on reconnect with the latest config).
|
|
6347
|
+
*/
|
|
6348
|
+
setProjects(projects) {
|
|
6349
|
+
const wanted = /* @__PURE__ */ new Map();
|
|
6350
|
+
for (const p of projects) {
|
|
6351
|
+
if (p.enabled) wanted.set(p.name, p);
|
|
6352
|
+
}
|
|
6353
|
+
for (const [name, entry] of this.perProject) {
|
|
6354
|
+
const target = wanted.get(name);
|
|
6355
|
+
if (!target || path31.resolve(target.path) !== entry.rootPath) {
|
|
6356
|
+
this.stopProject(name);
|
|
6357
|
+
}
|
|
6358
|
+
}
|
|
6359
|
+
for (const [name, proj] of wanted) {
|
|
6360
|
+
if (!this.perProject.has(name)) {
|
|
6361
|
+
this.startProject(proj);
|
|
6362
|
+
}
|
|
6363
|
+
}
|
|
6364
|
+
}
|
|
6365
|
+
close() {
|
|
6366
|
+
for (const name of [...this.perProject.keys()]) this.stopProject(name);
|
|
6367
|
+
for (const [, t] of this.debounceTimers) clearTimeout(t);
|
|
6368
|
+
this.debounceTimers.clear();
|
|
6369
|
+
}
|
|
6370
|
+
startProject(proj) {
|
|
6371
|
+
const rootPath = path31.resolve(proj.path);
|
|
6372
|
+
const projectYml = path31.join(rootPath, "task0.yml");
|
|
6373
|
+
const cfg = readYaml(projectYml);
|
|
6374
|
+
if (!cfg || typeof cfg.tasks_dir !== "string" || cfg.tasks_dir.length === 0) {
|
|
6375
|
+
return;
|
|
6376
|
+
}
|
|
6377
|
+
const tasksDir = path31.resolve(rootPath, cfg.tasks_dir);
|
|
6378
|
+
const watchers = [];
|
|
6379
|
+
try {
|
|
6380
|
+
const w = fs32.watch(tasksDir, { recursive: true }, () => this.notify(proj.name));
|
|
6381
|
+
w.on("error", () => {
|
|
6382
|
+
});
|
|
6383
|
+
watchers.push(w);
|
|
6384
|
+
} catch {
|
|
6385
|
+
}
|
|
6386
|
+
try {
|
|
6387
|
+
const parentDir = path31.dirname(tasksDir);
|
|
6388
|
+
const tasksDirName = path31.basename(tasksDir);
|
|
6389
|
+
const tarName = tasksDirName + ".tar";
|
|
6390
|
+
const w = fs32.watch(parentDir, (_event, filename) => {
|
|
6391
|
+
if (!filename) return;
|
|
6392
|
+
if (filename === tasksDirName || filename === tarName) {
|
|
6393
|
+
this.notify(proj.name);
|
|
6394
|
+
}
|
|
6395
|
+
});
|
|
6396
|
+
w.on("error", () => {
|
|
6397
|
+
});
|
|
6398
|
+
watchers.push(w);
|
|
6399
|
+
} catch {
|
|
6400
|
+
}
|
|
6401
|
+
this.perProject.set(proj.name, { watchers, rootPath });
|
|
6402
|
+
}
|
|
6403
|
+
stopProject(name) {
|
|
6404
|
+
const entry = this.perProject.get(name);
|
|
6405
|
+
if (!entry) return;
|
|
6406
|
+
for (const w of entry.watchers) {
|
|
6407
|
+
try {
|
|
6408
|
+
w.close();
|
|
6409
|
+
} catch {
|
|
6410
|
+
}
|
|
6411
|
+
}
|
|
6412
|
+
this.perProject.delete(name);
|
|
6413
|
+
const timer = this.debounceTimers.get(name);
|
|
6414
|
+
if (timer) {
|
|
6415
|
+
clearTimeout(timer);
|
|
6416
|
+
this.debounceTimers.delete(name);
|
|
6417
|
+
}
|
|
6418
|
+
}
|
|
6419
|
+
notify(projectName) {
|
|
6420
|
+
const prev = this.debounceTimers.get(projectName);
|
|
6421
|
+
if (prev) clearTimeout(prev);
|
|
6422
|
+
const t = setTimeout(() => {
|
|
6423
|
+
this.debounceTimers.delete(projectName);
|
|
6424
|
+
try {
|
|
6425
|
+
this.onChange(projectName);
|
|
6426
|
+
} catch (err) {
|
|
6427
|
+
console.error(`project watcher onChange threw for ${projectName}:`, err);
|
|
6428
|
+
}
|
|
6429
|
+
}, this.debounceMs);
|
|
6430
|
+
this.debounceTimers.set(projectName, t);
|
|
6431
|
+
}
|
|
6432
|
+
};
|
|
6433
|
+
|
|
6330
6434
|
// src/commands/daemon.ts
|
|
6331
|
-
async function dispatchRpc(ws, id, method, params) {
|
|
6435
|
+
async function dispatchRpc(ws, id, method, params, notifyManifestChanged) {
|
|
6332
6436
|
const handler = rpcHandlers[method];
|
|
6333
6437
|
if (!handler) {
|
|
6334
6438
|
sendRpc(ws, { type: "rpc_error", id, error: { code: "unknown_method", message: `unknown method: ${method}` } });
|
|
6335
6439
|
return;
|
|
6336
6440
|
}
|
|
6337
6441
|
try {
|
|
6338
|
-
const ctx = { notifyManifestChanged
|
|
6442
|
+
const ctx = { notifyManifestChanged };
|
|
6339
6443
|
const result = await handler(params ?? {}, ctx);
|
|
6340
6444
|
sendRpc(ws, { type: "rpc_response", id, result });
|
|
6341
6445
|
} catch (error2) {
|
|
@@ -6382,6 +6486,9 @@ function buildManifest() {
|
|
|
6382
6486
|
}
|
|
6383
6487
|
return { type: "manifest", projects, tasks, scan_errors: scanErrors };
|
|
6384
6488
|
}
|
|
6489
|
+
function currentWatchedProjects() {
|
|
6490
|
+
return loadConfig().sources.filter((source2) => source2.type === "project").map((source2) => ({ name: source2.name, path: source2.path, enabled: source2.enabled }));
|
|
6491
|
+
}
|
|
6385
6492
|
function pushManifest(ws) {
|
|
6386
6493
|
if (ws.readyState !== ws.OPEN) return;
|
|
6387
6494
|
const manifest = buildManifest();
|
|
@@ -6599,11 +6706,21 @@ daemonCmd.command("run").description("Run the daemon WebSocket loop in foregroun
|
|
|
6599
6706
|
let reconnectDelay = 1e3;
|
|
6600
6707
|
let shouldRun = true;
|
|
6601
6708
|
let activeWs = null;
|
|
6709
|
+
let activeWatcher = null;
|
|
6602
6710
|
function connect() {
|
|
6603
6711
|
const ws = new WebSocket(wsUrl, {
|
|
6604
6712
|
headers: { authorization: `Bearer ${identity.token}` }
|
|
6605
6713
|
});
|
|
6606
6714
|
activeWs = ws;
|
|
6715
|
+
const watcher = new ProjectWatcher({
|
|
6716
|
+
onChange: () => pushManifest(ws),
|
|
6717
|
+
debounceMs: 300
|
|
6718
|
+
});
|
|
6719
|
+
activeWatcher = watcher;
|
|
6720
|
+
const notifyManifestChanged = () => {
|
|
6721
|
+
pushManifest(ws);
|
|
6722
|
+
watcher.setProjects(currentWatchedProjects());
|
|
6723
|
+
};
|
|
6607
6724
|
ws.on("open", () => {
|
|
6608
6725
|
reconnectDelay = 1e3;
|
|
6609
6726
|
console.log(chalk19.green(`[${(/* @__PURE__ */ new Date()).toISOString()}] connected`));
|
|
@@ -6629,6 +6746,7 @@ daemonCmd.command("run").description("Run the daemon WebSocket loop in foregroun
|
|
|
6629
6746
|
const manifest = buildManifest();
|
|
6630
6747
|
ws.send(JSON.stringify(manifest));
|
|
6631
6748
|
console.log(chalk19.dim(`pushed manifest: ${manifest.projects.length} project(s)`));
|
|
6749
|
+
watcher.setProjects(currentWatchedProjects());
|
|
6632
6750
|
const sink = {
|
|
6633
6751
|
send: (frame) => {
|
|
6634
6752
|
if (ws.readyState === ws.OPEN) ws.send(JSON.stringify(frame));
|
|
@@ -6651,7 +6769,7 @@ daemonCmd.command("run").description("Run the daemon WebSocket loop in foregroun
|
|
|
6651
6769
|
} else if (msg.type === "error") {
|
|
6652
6770
|
console.error(chalk19.yellow(`server error: ${msg.message}`));
|
|
6653
6771
|
} else if (msg.type === "rpc_request") {
|
|
6654
|
-
void dispatchRpc(ws, msg.id, msg.method, msg.params);
|
|
6772
|
+
void dispatchRpc(ws, msg.id, msg.method, msg.params, notifyManifestChanged);
|
|
6655
6773
|
} else if (msg.type === "agent_run_resume_request") {
|
|
6656
6774
|
const sent = replayAfterRanges(msg.ranges);
|
|
6657
6775
|
if (sent > 0) {
|
|
@@ -6662,6 +6780,8 @@ daemonCmd.command("run").description("Run the daemon WebSocket loop in foregroun
|
|
|
6662
6780
|
ws.on("close", (code, reason) => {
|
|
6663
6781
|
activeWs = null;
|
|
6664
6782
|
bindAgentRunFrameSink(null);
|
|
6783
|
+
watcher.close();
|
|
6784
|
+
if (activeWatcher === watcher) activeWatcher = null;
|
|
6665
6785
|
const reasonText = reason.toString("utf-8") || "no reason";
|
|
6666
6786
|
console.log(chalk19.yellow(`[${(/* @__PURE__ */ new Date()).toISOString()}] disconnected (code=${code}, ${reasonText})`));
|
|
6667
6787
|
if (code === 4001) {
|
|
@@ -6679,6 +6799,10 @@ daemonCmd.command("run").description("Run the daemon WebSocket loop in foregroun
|
|
|
6679
6799
|
}
|
|
6680
6800
|
const shutdown = () => {
|
|
6681
6801
|
shouldRun = false;
|
|
6802
|
+
try {
|
|
6803
|
+
activeWatcher?.close();
|
|
6804
|
+
} catch {
|
|
6805
|
+
}
|
|
6682
6806
|
try {
|
|
6683
6807
|
activeWs?.close(1e3, "shutdown");
|
|
6684
6808
|
} catch {
|
|
@@ -6807,8 +6931,8 @@ function fail7(message, code = 1) {
|
|
|
6807
6931
|
console.error(chalk20.red(message));
|
|
6808
6932
|
process.exit(code);
|
|
6809
6933
|
}
|
|
6810
|
-
async function callServer(
|
|
6811
|
-
const url = `${serverBase2()}${
|
|
6934
|
+
async function callServer(path33, init = {}) {
|
|
6935
|
+
const url = `${serverBase2()}${path33}`;
|
|
6812
6936
|
let auth;
|
|
6813
6937
|
try {
|
|
6814
6938
|
auth = adminAuthHeader();
|
|
@@ -7313,8 +7437,8 @@ automation.command("runs <id>").description("List recent runs for an automation"
|
|
|
7313
7437
|
|
|
7314
7438
|
// src/commands/profile.ts
|
|
7315
7439
|
init_node();
|
|
7316
|
-
import
|
|
7317
|
-
import
|
|
7440
|
+
import fs33 from "fs";
|
|
7441
|
+
import path32 from "path";
|
|
7318
7442
|
import { Command as Command23 } from "commander";
|
|
7319
7443
|
import chalk23 from "chalk";
|
|
7320
7444
|
import yaml11 from "js-yaml";
|
|
@@ -7327,19 +7451,19 @@ function fail9(msg) {
|
|
|
7327
7451
|
process.exit(1);
|
|
7328
7452
|
}
|
|
7329
7453
|
function readDaemonAt(dir) {
|
|
7330
|
-
const file =
|
|
7331
|
-
if (!
|
|
7454
|
+
const file = path32.join(dir, "daemon.json");
|
|
7455
|
+
if (!fs33.existsSync(file)) return null;
|
|
7332
7456
|
try {
|
|
7333
|
-
return JSON.parse(
|
|
7457
|
+
return JSON.parse(fs33.readFileSync(file, "utf-8"));
|
|
7334
7458
|
} catch {
|
|
7335
7459
|
return null;
|
|
7336
7460
|
}
|
|
7337
7461
|
}
|
|
7338
7462
|
function readProfileApiUrl(name) {
|
|
7339
|
-
const file =
|
|
7340
|
-
if (!
|
|
7463
|
+
const file = path32.join(profileDir(name), "config.yml");
|
|
7464
|
+
if (!fs33.existsSync(file)) return void 0;
|
|
7341
7465
|
try {
|
|
7342
|
-
const data = yaml11.load(
|
|
7466
|
+
const data = yaml11.load(fs33.readFileSync(file, "utf-8"));
|
|
7343
7467
|
return data?.api_url;
|
|
7344
7468
|
} catch {
|
|
7345
7469
|
return void 0;
|
|
@@ -7368,10 +7492,10 @@ profile2.command("add <name>").description("Create a new profile directory (use
|
|
|
7368
7492
|
fail9(`"${DEFAULT_PROFILE_NAME}" is reserved; it always exists.`);
|
|
7369
7493
|
}
|
|
7370
7494
|
const dir = profileDir(name);
|
|
7371
|
-
if (
|
|
7495
|
+
if (fs33.existsSync(dir)) {
|
|
7372
7496
|
fail9(`Profile "${name}" already exists at ${dir}.`);
|
|
7373
7497
|
}
|
|
7374
|
-
|
|
7498
|
+
fs33.mkdirSync(dir, { recursive: true });
|
|
7375
7499
|
const prev = process.env.TASK0_PROFILE;
|
|
7376
7500
|
process.env.TASK0_PROFILE = name;
|
|
7377
7501
|
try {
|
|
@@ -7395,14 +7519,14 @@ profile2.command("remove <name>").description('Delete a profile directory (refus
|
|
|
7395
7519
|
fail9(`"${DEFAULT_PROFILE_NAME}" cannot be removed.`);
|
|
7396
7520
|
}
|
|
7397
7521
|
const dir = profileDir(name);
|
|
7398
|
-
if (!
|
|
7522
|
+
if (!fs33.existsSync(dir)) {
|
|
7399
7523
|
fail9(`Profile "${name}" not found.`);
|
|
7400
7524
|
}
|
|
7401
7525
|
const current = currentProfileName();
|
|
7402
7526
|
if (current === name && !opts.force) {
|
|
7403
7527
|
fail9(`Profile "${name}" is current. Re-run with --force to remove it.`);
|
|
7404
7528
|
}
|
|
7405
|
-
|
|
7529
|
+
fs33.rmSync(dir, { recursive: true, force: true });
|
|
7406
7530
|
if (current === name) {
|
|
7407
7531
|
writeCurrentProfile(null);
|
|
7408
7532
|
console.log(chalk23.dim('Current profile reset to "default".'));
|
|
@@ -7413,7 +7537,7 @@ profile2.command("use <name>").description("Set the current profile").action((na
|
|
|
7413
7537
|
if (!isValidProfileName(name)) {
|
|
7414
7538
|
fail9(`Invalid profile name "${name}".`);
|
|
7415
7539
|
}
|
|
7416
|
-
if (!
|
|
7540
|
+
if (!fs33.existsSync(profileDir(name))) {
|
|
7417
7541
|
const names = listProfileNames();
|
|
7418
7542
|
fail9(`Profile "${name}" not found. Available: ${names.join(", ")}.`);
|
|
7419
7543
|
}
|
|
@@ -7448,7 +7572,7 @@ profile2.command("show [name]").description("Show a profile's configuration and
|
|
|
7448
7572
|
fail9(`Invalid profile name "${target}".`);
|
|
7449
7573
|
}
|
|
7450
7574
|
const dir = profileDir(target);
|
|
7451
|
-
if (!
|
|
7575
|
+
if (!fs33.existsSync(dir)) {
|
|
7452
7576
|
fail9(`Profile "${target}" not found.`);
|
|
7453
7577
|
}
|
|
7454
7578
|
const current = currentProfileName();
|
|
@@ -7458,7 +7582,7 @@ profile2.command("show [name]").description("Show a profile's configuration and
|
|
|
7458
7582
|
const effective = envOverride ?? apiUrl;
|
|
7459
7583
|
console.log(`${chalk23.bold(target)}${current === target ? chalk23.green(" (current)") : ""}`);
|
|
7460
7584
|
console.log(` dir: ${dir}`);
|
|
7461
|
-
console.log(` config: ${
|
|
7585
|
+
console.log(` config: ${path32.join(dir, "config.yml")}`);
|
|
7462
7586
|
if (envOverride && apiUrl && envOverride !== apiUrl) {
|
|
7463
7587
|
console.log(` api_url: ${envOverride} ${chalk23.dim("(TASK0_API_URL env, overrides profile)")}`);
|
|
7464
7588
|
console.log(` ${chalk23.dim(`profile: ${apiUrl}`)}`);
|