@task0/cli 0.5.0 → 0.7.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 +808 -285
- package/package.json +11 -12
package/dist/main.js
CHANGED
|
@@ -9,38 +9,30 @@ var __export = (target, all) => {
|
|
|
9
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
// ../../packages/shared/dist/
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
// ../../packages/shared/dist/node/yaml.js
|
|
13
|
+
import fs from "fs";
|
|
14
|
+
import path from "path";
|
|
15
|
+
import yaml from "js-yaml";
|
|
16
|
+
function readYaml(filePath) {
|
|
17
|
+
if (!fs.existsSync(filePath))
|
|
18
|
+
return null;
|
|
19
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
20
|
+
return yaml.load(raw);
|
|
15
21
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
22
|
+
function writeYaml(filePath, data) {
|
|
23
|
+
fs.writeFileSync(filePath, yaml.dump(data, { lineWidth: 120 }), "utf-8");
|
|
24
|
+
}
|
|
25
|
+
function readContext(taskDir) {
|
|
26
|
+
const files = fs.readdirSync(taskDir).filter((f) => f.endsWith(".md"));
|
|
27
|
+
if (files.length === 0)
|
|
28
|
+
return void 0;
|
|
29
|
+
const content = fs.readFileSync(path.join(taskDir, files[0]), "utf-8");
|
|
30
|
+
const stripped = content.replace(/^---[\s\S]*?---\s*/, "");
|
|
31
|
+
return stripped.trim() || void 0;
|
|
32
|
+
}
|
|
33
|
+
var init_yaml = __esm({
|
|
34
|
+
"../../packages/shared/dist/node/yaml.js"() {
|
|
19
35
|
"use strict";
|
|
20
|
-
TASK_STATUS_VALUES = [
|
|
21
|
-
"todo",
|
|
22
|
-
"triaging",
|
|
23
|
-
"triaged",
|
|
24
|
-
"planning",
|
|
25
|
-
"planned",
|
|
26
|
-
"refining",
|
|
27
|
-
"refined",
|
|
28
|
-
"executing",
|
|
29
|
-
"blocked",
|
|
30
|
-
"done",
|
|
31
|
-
"archived"
|
|
32
|
-
];
|
|
33
|
-
ACTIVE_TASK_STATUSES = [
|
|
34
|
-
"triaging",
|
|
35
|
-
"planning",
|
|
36
|
-
"refining",
|
|
37
|
-
"executing"
|
|
38
|
-
];
|
|
39
|
-
ATTENTION_TASK_STATUSES = [
|
|
40
|
-
"blocked"
|
|
41
|
-
];
|
|
42
|
-
ACTIVE_TASK_STATUS_SET = new Set(ACTIVE_TASK_STATUSES);
|
|
43
|
-
ATTENTION_TASK_STATUS_SET = new Set(ATTENTION_TASK_STATUSES);
|
|
44
36
|
}
|
|
45
37
|
});
|
|
46
38
|
|
|
@@ -116,57 +108,65 @@ var init_object_id = __esm({
|
|
|
116
108
|
}
|
|
117
109
|
});
|
|
118
110
|
|
|
119
|
-
// ../../packages/shared/dist/
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
import yaml3 from "js-yaml";
|
|
123
|
-
function readYaml(filePath) {
|
|
124
|
-
if (!fs5.existsSync(filePath))
|
|
125
|
-
return null;
|
|
126
|
-
const raw = fs5.readFileSync(filePath, "utf-8");
|
|
127
|
-
return yaml3.load(raw);
|
|
128
|
-
}
|
|
129
|
-
function writeYaml(filePath, data) {
|
|
130
|
-
fs5.writeFileSync(filePath, yaml3.dump(data, { lineWidth: 120 }), "utf-8");
|
|
131
|
-
}
|
|
132
|
-
function readContext(taskDir) {
|
|
133
|
-
const files = fs5.readdirSync(taskDir).filter((f) => f.endsWith(".md"));
|
|
134
|
-
if (files.length === 0)
|
|
135
|
-
return void 0;
|
|
136
|
-
const content = fs5.readFileSync(path6.join(taskDir, files[0]), "utf-8");
|
|
137
|
-
const stripped = content.replace(/^---[\s\S]*?---\s*/, "");
|
|
138
|
-
return stripped.trim() || void 0;
|
|
111
|
+
// ../../packages/shared/dist/types/task.js
|
|
112
|
+
function isActiveTaskStatus(status) {
|
|
113
|
+
return ACTIVE_TASK_STATUS_SET.has(status);
|
|
139
114
|
}
|
|
140
|
-
var
|
|
141
|
-
|
|
115
|
+
var TASK_STATUS_VALUES, ACTIVE_TASK_STATUSES, ATTENTION_TASK_STATUSES, ACTIVE_TASK_STATUS_SET, ATTENTION_TASK_STATUS_SET;
|
|
116
|
+
var init_task = __esm({
|
|
117
|
+
"../../packages/shared/dist/types/task.js"() {
|
|
142
118
|
"use strict";
|
|
119
|
+
TASK_STATUS_VALUES = [
|
|
120
|
+
"todo",
|
|
121
|
+
"triaging",
|
|
122
|
+
"triaged",
|
|
123
|
+
"planning",
|
|
124
|
+
"planned",
|
|
125
|
+
"refining",
|
|
126
|
+
"refined",
|
|
127
|
+
"executing",
|
|
128
|
+
"blocked",
|
|
129
|
+
"done",
|
|
130
|
+
"archived"
|
|
131
|
+
];
|
|
132
|
+
ACTIVE_TASK_STATUSES = [
|
|
133
|
+
"triaging",
|
|
134
|
+
"planning",
|
|
135
|
+
"refining",
|
|
136
|
+
"executing"
|
|
137
|
+
];
|
|
138
|
+
ATTENTION_TASK_STATUSES = [
|
|
139
|
+
"blocked"
|
|
140
|
+
];
|
|
141
|
+
ACTIVE_TASK_STATUS_SET = new Set(ACTIVE_TASK_STATUSES);
|
|
142
|
+
ATTENTION_TASK_STATUS_SET = new Set(ATTENTION_TASK_STATUSES);
|
|
143
143
|
}
|
|
144
144
|
});
|
|
145
145
|
|
|
146
146
|
// ../../packages/shared/dist/node/scanner.js
|
|
147
|
-
import
|
|
148
|
-
import
|
|
147
|
+
import fs2 from "fs";
|
|
148
|
+
import path2 from "path";
|
|
149
149
|
function scanProject(projectPath, sourceName) {
|
|
150
|
-
const absPath =
|
|
151
|
-
const resolvedSourceName = sourceName ??
|
|
150
|
+
const absPath = path2.resolve(projectPath);
|
|
151
|
+
const resolvedSourceName = sourceName ?? path2.basename(absPath);
|
|
152
152
|
const errors = [];
|
|
153
153
|
const tasks = [];
|
|
154
154
|
const repairs = [];
|
|
155
|
-
const projectYml =
|
|
155
|
+
const projectYml = path2.join(absPath, "task0.yml");
|
|
156
156
|
const projectConfig = readYaml(projectYml);
|
|
157
157
|
if (!projectConfig)
|
|
158
158
|
return { tasks, errors: [`${projectYml} not found`], repairs };
|
|
159
159
|
if (projectConfig.kind !== "project")
|
|
160
160
|
return { tasks, errors: [`Invalid: kind="${projectConfig.kind}"`], repairs };
|
|
161
|
-
const tasksDir =
|
|
162
|
-
if (!
|
|
161
|
+
const tasksDir = path2.join(absPath, projectConfig.tasks_dir);
|
|
162
|
+
if (!fs2.existsSync(tasksDir))
|
|
163
163
|
return { tasks, errors: [`tasks_dir not found: ${tasksDir}`], repairs };
|
|
164
|
-
const entries =
|
|
164
|
+
const entries = fs2.readdirSync(tasksDir, { withFileTypes: true });
|
|
165
165
|
for (const entry of entries) {
|
|
166
166
|
if (!entry.isDirectory())
|
|
167
167
|
continue;
|
|
168
|
-
const taskDir =
|
|
169
|
-
const taskYml =
|
|
168
|
+
const taskDir = path2.join(tasksDir, entry.name);
|
|
169
|
+
const taskYml = path2.join(taskDir, "task0.yml");
|
|
170
170
|
const raw = readYaml(taskYml);
|
|
171
171
|
if (!raw || raw.kind !== "task")
|
|
172
172
|
continue;
|
|
@@ -202,7 +202,7 @@ function scanProject(projectPath, sourceName) {
|
|
|
202
202
|
continue;
|
|
203
203
|
}
|
|
204
204
|
const context = readContext(taskDir);
|
|
205
|
-
const stat =
|
|
205
|
+
const stat = fs2.statSync(taskYml);
|
|
206
206
|
const tags = raw.tags || [];
|
|
207
207
|
const summary = raw.summary || void 0;
|
|
208
208
|
const displayTitle = summary?.title || title;
|
|
@@ -245,18 +245,18 @@ var init_scanner = __esm({
|
|
|
245
245
|
});
|
|
246
246
|
|
|
247
247
|
// ../../packages/shared/dist/node/open-questions.js
|
|
248
|
-
import
|
|
249
|
-
import
|
|
248
|
+
import fs3 from "fs";
|
|
249
|
+
import path3 from "path";
|
|
250
250
|
function readOpenQuestions(taskDir, issueFiles) {
|
|
251
251
|
const result = [];
|
|
252
252
|
for (const file of issueFiles) {
|
|
253
253
|
const m = file.match(/^ISSUE-(\d+)\.md$/);
|
|
254
254
|
if (!m)
|
|
255
255
|
continue;
|
|
256
|
-
const fullPath =
|
|
257
|
-
if (!
|
|
256
|
+
const fullPath = path3.join(taskDir, file);
|
|
257
|
+
if (!fs3.existsSync(fullPath))
|
|
258
258
|
continue;
|
|
259
|
-
const md =
|
|
259
|
+
const md = fs3.readFileSync(fullPath, "utf-8");
|
|
260
260
|
const match = md.match(OPEN_QUESTIONS_SECTION_RE);
|
|
261
261
|
if (!match)
|
|
262
262
|
continue;
|
|
@@ -279,34 +279,34 @@ var init_open_questions = __esm({
|
|
|
279
279
|
});
|
|
280
280
|
|
|
281
281
|
// ../../packages/shared/dist/node/task-state.js
|
|
282
|
-
import
|
|
283
|
-
import
|
|
284
|
-
import
|
|
285
|
-
import
|
|
282
|
+
import fs4 from "fs";
|
|
283
|
+
import os from "os";
|
|
284
|
+
import path4 from "path";
|
|
285
|
+
import yaml2 from "js-yaml";
|
|
286
286
|
function findProjectRoot(start = process.cwd()) {
|
|
287
|
-
let dir =
|
|
287
|
+
let dir = path4.resolve(start);
|
|
288
288
|
while (true) {
|
|
289
|
-
const ymlPath =
|
|
290
|
-
if (
|
|
289
|
+
const ymlPath = path4.join(dir, "task0.yml");
|
|
290
|
+
if (fs4.existsSync(ymlPath)) {
|
|
291
291
|
try {
|
|
292
|
-
const raw =
|
|
292
|
+
const raw = yaml2.load(fs4.readFileSync(ymlPath, "utf-8"));
|
|
293
293
|
if (raw?.kind === "project")
|
|
294
294
|
return dir;
|
|
295
295
|
} catch {
|
|
296
296
|
}
|
|
297
297
|
}
|
|
298
|
-
const parent =
|
|
298
|
+
const parent = path4.dirname(dir);
|
|
299
299
|
if (parent === dir)
|
|
300
300
|
return null;
|
|
301
301
|
dir = parent;
|
|
302
302
|
}
|
|
303
303
|
}
|
|
304
304
|
function readProjectConfig(projectRoot) {
|
|
305
|
-
return
|
|
305
|
+
return yaml2.load(fs4.readFileSync(path4.join(projectRoot, "task0.yml"), "utf-8"));
|
|
306
306
|
}
|
|
307
307
|
function resolveTasksDir(projectRoot, projectConfig) {
|
|
308
308
|
const cfg = projectConfig ?? readProjectConfig(projectRoot);
|
|
309
|
-
return
|
|
309
|
+
return path4.isAbsolute(cfg.tasks_dir) ? cfg.tasks_dir : path4.join(projectRoot, cfg.tasks_dir);
|
|
310
310
|
}
|
|
311
311
|
function resolveTaskByObjectId(objectId, projectRoot) {
|
|
312
312
|
const root = projectRoot ?? findProjectRoot();
|
|
@@ -317,16 +317,16 @@ function resolveTaskByObjectId(objectId, projectRoot) {
|
|
|
317
317
|
throw new Error(`Expected a task object_id like 'tsk_XXXXX', got '${objectId}'. Directory names are no longer accepted; run 'task0 task list' to find the object_id.`);
|
|
318
318
|
}
|
|
319
319
|
const tasksDir = resolveTasksDir(root);
|
|
320
|
-
const entries =
|
|
320
|
+
const entries = fs4.readdirSync(tasksDir, { withFileTypes: true });
|
|
321
321
|
for (const entry of entries) {
|
|
322
322
|
if (!entry.isDirectory())
|
|
323
323
|
continue;
|
|
324
|
-
const taskDir =
|
|
325
|
-
const taskYml =
|
|
326
|
-
if (!
|
|
324
|
+
const taskDir = path4.join(tasksDir, entry.name);
|
|
325
|
+
const taskYml = path4.join(taskDir, "task0.yml");
|
|
326
|
+
if (!fs4.existsSync(taskYml))
|
|
327
327
|
continue;
|
|
328
328
|
try {
|
|
329
|
-
const raw =
|
|
329
|
+
const raw = yaml2.load(fs4.readFileSync(taskYml, "utf-8"));
|
|
330
330
|
if (raw && raw.object_id === objectId) {
|
|
331
331
|
return { projectRoot: root, tasksDir, taskDir, taskYml };
|
|
332
332
|
}
|
|
@@ -336,15 +336,15 @@ function resolveTaskByObjectId(objectId, projectRoot) {
|
|
|
336
336
|
throw new Error(`No task with object_id '${objectId}' found under ${tasksDir}. If tasks pre-date object_id, run 'task0 task migrate' to seed them.`);
|
|
337
337
|
}
|
|
338
338
|
function readTaskYaml(taskYml) {
|
|
339
|
-
if (!
|
|
339
|
+
if (!fs4.existsSync(taskYml))
|
|
340
340
|
throw new Error(`task0.yml not found: ${taskYml}`);
|
|
341
|
-
return
|
|
341
|
+
return yaml2.load(fs4.readFileSync(taskYml, "utf-8")) || {};
|
|
342
342
|
}
|
|
343
343
|
function writeTaskYaml(taskYml, data) {
|
|
344
|
-
|
|
344
|
+
fs4.writeFileSync(taskYml, yaml2.dump(data, { lineWidth: 120 }), "utf-8");
|
|
345
345
|
}
|
|
346
346
|
function taskYamlLockPath(taskDir) {
|
|
347
|
-
return
|
|
347
|
+
return path4.join(taskDir, TASK_YAML_LOCKFILE);
|
|
348
348
|
}
|
|
349
349
|
function isProcessAlive(pid) {
|
|
350
350
|
try {
|
|
@@ -356,17 +356,17 @@ function isProcessAlive(pid) {
|
|
|
356
356
|
}
|
|
357
357
|
function readTaskYamlLockInfo(file) {
|
|
358
358
|
try {
|
|
359
|
-
return JSON.parse(
|
|
359
|
+
return JSON.parse(fs4.readFileSync(file, "utf-8"));
|
|
360
360
|
} catch {
|
|
361
361
|
return null;
|
|
362
362
|
}
|
|
363
363
|
}
|
|
364
364
|
function writeTaskYamlLockInfo(file, info) {
|
|
365
|
-
const fd =
|
|
365
|
+
const fd = fs4.openSync(file, "wx");
|
|
366
366
|
try {
|
|
367
|
-
|
|
367
|
+
fs4.writeFileSync(fd, JSON.stringify(info, null, 2), "utf-8");
|
|
368
368
|
} finally {
|
|
369
|
-
|
|
369
|
+
fs4.closeSync(fd);
|
|
370
370
|
}
|
|
371
371
|
}
|
|
372
372
|
function sleep(ms) {
|
|
@@ -376,7 +376,7 @@ async function acquireTaskYamlLock(taskDir) {
|
|
|
376
376
|
const file = taskYamlLockPath(taskDir);
|
|
377
377
|
const info = {
|
|
378
378
|
pid: process.pid,
|
|
379
|
-
hostname:
|
|
379
|
+
hostname: os.hostname(),
|
|
380
380
|
started_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
381
381
|
};
|
|
382
382
|
const deadline = Date.now() + TASK_YAML_LOCK_TIMEOUT_MS;
|
|
@@ -392,7 +392,7 @@ async function acquireTaskYamlLock(taskDir) {
|
|
|
392
392
|
const existing = readTaskYamlLockInfo(file);
|
|
393
393
|
if (existing && existing.hostname === info.hostname && !isProcessAlive(existing.pid)) {
|
|
394
394
|
try {
|
|
395
|
-
|
|
395
|
+
fs4.unlinkSync(file);
|
|
396
396
|
continue;
|
|
397
397
|
} catch (err) {
|
|
398
398
|
if (err.code !== "ENOENT") {
|
|
@@ -414,7 +414,7 @@ function releaseTaskYamlLock(taskDir, info) {
|
|
|
414
414
|
return;
|
|
415
415
|
}
|
|
416
416
|
try {
|
|
417
|
-
|
|
417
|
+
fs4.unlinkSync(file);
|
|
418
418
|
} catch (err) {
|
|
419
419
|
if (err.code !== "ENOENT") {
|
|
420
420
|
throw err;
|
|
@@ -422,7 +422,7 @@ function releaseTaskYamlLock(taskDir, info) {
|
|
|
422
422
|
}
|
|
423
423
|
}
|
|
424
424
|
async function withTaskYamlLock(taskDir, fn) {
|
|
425
|
-
const key =
|
|
425
|
+
const key = path4.resolve(taskDir);
|
|
426
426
|
const prev = yamlLocks.get(key) ?? Promise.resolve();
|
|
427
427
|
const next = prev.then(async () => {
|
|
428
428
|
const lock = await acquireTaskYamlLock(taskDir);
|
|
@@ -453,7 +453,7 @@ function readTaskWorkflow(taskYml) {
|
|
|
453
453
|
return raw.workflow || {};
|
|
454
454
|
}
|
|
455
455
|
async function updateTaskWorkflow(taskYml, patch) {
|
|
456
|
-
const taskDir =
|
|
456
|
+
const taskDir = path4.dirname(taskYml);
|
|
457
457
|
return withTaskYamlLock(taskDir, () => {
|
|
458
458
|
const raw = readTaskYaml(taskYml);
|
|
459
459
|
const current = raw.workflow || {};
|
|
@@ -583,19 +583,19 @@ var init_redact = __esm({
|
|
|
583
583
|
});
|
|
584
584
|
|
|
585
585
|
// ../../packages/shared/dist/node/error-reports.js
|
|
586
|
-
import
|
|
587
|
-
import
|
|
588
|
-
import
|
|
586
|
+
import fs5 from "fs";
|
|
587
|
+
import os2 from "os";
|
|
588
|
+
import path5 from "path";
|
|
589
589
|
import { spawnSync } from "child_process";
|
|
590
590
|
import crypto from "crypto";
|
|
591
591
|
function task0Home() {
|
|
592
592
|
const override = process.env.TASK0_HOME;
|
|
593
593
|
if (override && override.length > 0)
|
|
594
594
|
return override;
|
|
595
|
-
return
|
|
595
|
+
return path5.join(os2.homedir(), ".task0");
|
|
596
596
|
}
|
|
597
597
|
function errorsRoot() {
|
|
598
|
-
return
|
|
598
|
+
return path5.join(task0Home(), "errors");
|
|
599
599
|
}
|
|
600
600
|
function createErrorReportId() {
|
|
601
601
|
return `err_${crypto.randomBytes(4).toString("hex")}`;
|
|
@@ -696,13 +696,13 @@ function buildErrorReport(input) {
|
|
|
696
696
|
function writeErrorReportSync(report, rootOverride) {
|
|
697
697
|
const root = rootOverride ?? errorsRoot();
|
|
698
698
|
const dirName = errorReportDirName(new Date(report.captured_at), report.id);
|
|
699
|
-
const dir =
|
|
700
|
-
|
|
701
|
-
const finalPath =
|
|
702
|
-
const tmpPath =
|
|
699
|
+
const dir = path5.join(root, dirName);
|
|
700
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
701
|
+
const finalPath = path5.join(dir, REPORT_FILENAME);
|
|
702
|
+
const tmpPath = path5.join(dir, TMP_FILENAME);
|
|
703
703
|
const json = JSON.stringify(report, null, 2);
|
|
704
|
-
|
|
705
|
-
|
|
704
|
+
fs5.writeFileSync(tmpPath, json, "utf-8");
|
|
705
|
+
fs5.renameSync(tmpPath, finalPath);
|
|
706
706
|
return { dir, path: finalPath };
|
|
707
707
|
}
|
|
708
708
|
function parseReportDir(name) {
|
|
@@ -719,7 +719,7 @@ function listErrorReports(rootOverride) {
|
|
|
719
719
|
};
|
|
720
720
|
let entries;
|
|
721
721
|
try {
|
|
722
|
-
entries =
|
|
722
|
+
entries = fs5.readdirSync(root, { withFileTypes: true });
|
|
723
723
|
} catch (err) {
|
|
724
724
|
if (err.code === "ENOENT")
|
|
725
725
|
return result;
|
|
@@ -731,11 +731,11 @@ function listErrorReports(rootOverride) {
|
|
|
731
731
|
const parsed = parseReportDir(entry.name);
|
|
732
732
|
if (!parsed)
|
|
733
733
|
continue;
|
|
734
|
-
const dir =
|
|
735
|
-
const file =
|
|
734
|
+
const dir = path5.join(root, entry.name);
|
|
735
|
+
const file = path5.join(dir, REPORT_FILENAME);
|
|
736
736
|
let raw;
|
|
737
737
|
try {
|
|
738
|
-
raw =
|
|
738
|
+
raw = fs5.readFileSync(file, "utf-8");
|
|
739
739
|
} catch {
|
|
740
740
|
result.skipped.unreadable += 1;
|
|
741
741
|
continue;
|
|
@@ -753,7 +753,7 @@ function listErrorReports(rootOverride) {
|
|
|
753
753
|
}
|
|
754
754
|
let size = 0;
|
|
755
755
|
try {
|
|
756
|
-
size =
|
|
756
|
+
size = fs5.statSync(file).size;
|
|
757
757
|
} catch {
|
|
758
758
|
}
|
|
759
759
|
result.reports.push({
|
|
@@ -788,7 +788,7 @@ function resolveErrorReport(query, rootOverride) {
|
|
|
788
788
|
return { kind: "miss", query };
|
|
789
789
|
}
|
|
790
790
|
function readErrorReport(summary) {
|
|
791
|
-
const raw =
|
|
791
|
+
const raw = fs5.readFileSync(summary.path, "utf-8");
|
|
792
792
|
return JSON.parse(raw);
|
|
793
793
|
}
|
|
794
794
|
function pruneErrorReports(opts, rootOverride) {
|
|
@@ -826,7 +826,7 @@ function pruneErrorReports(opts, rootOverride) {
|
|
|
826
826
|
}
|
|
827
827
|
function removeReportDir(dir) {
|
|
828
828
|
try {
|
|
829
|
-
|
|
829
|
+
fs5.rmSync(dir, { recursive: true, force: true });
|
|
830
830
|
return true;
|
|
831
831
|
} catch {
|
|
832
832
|
return false;
|
|
@@ -844,9 +844,9 @@ var init_error_reports = __esm({
|
|
|
844
844
|
});
|
|
845
845
|
|
|
846
846
|
// ../../packages/shared/dist/node/file-lock.js
|
|
847
|
-
import
|
|
848
|
-
import
|
|
849
|
-
import
|
|
847
|
+
import fs6 from "fs";
|
|
848
|
+
import os3 from "os";
|
|
849
|
+
import path6 from "path";
|
|
850
850
|
var init_file_lock = __esm({
|
|
851
851
|
"../../packages/shared/dist/node/file-lock.js"() {
|
|
852
852
|
"use strict";
|
|
@@ -862,9 +862,9 @@ var init_tmux = __esm({
|
|
|
862
862
|
});
|
|
863
863
|
|
|
864
864
|
// ../../packages/shared/dist/node/agent-skills.js
|
|
865
|
-
import
|
|
866
|
-
import
|
|
867
|
-
import
|
|
865
|
+
import fs7 from "fs";
|
|
866
|
+
import path7 from "path";
|
|
867
|
+
import yaml3 from "js-yaml";
|
|
868
868
|
function isRecord(value) {
|
|
869
869
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
870
870
|
}
|
|
@@ -873,7 +873,7 @@ function extractFrontmatter(raw) {
|
|
|
873
873
|
if (!match)
|
|
874
874
|
return null;
|
|
875
875
|
try {
|
|
876
|
-
const parsed =
|
|
876
|
+
const parsed = yaml3.load(match[1]);
|
|
877
877
|
return isRecord(parsed) ? parsed : null;
|
|
878
878
|
} catch {
|
|
879
879
|
return null;
|
|
@@ -881,9 +881,9 @@ function extractFrontmatter(raw) {
|
|
|
881
881
|
}
|
|
882
882
|
function readTextIfExists(filePath) {
|
|
883
883
|
try {
|
|
884
|
-
if (!
|
|
884
|
+
if (!fs7.existsSync(filePath))
|
|
885
885
|
return null;
|
|
886
|
-
return
|
|
886
|
+
return fs7.readFileSync(filePath, "utf-8");
|
|
887
887
|
} catch {
|
|
888
888
|
return null;
|
|
889
889
|
}
|
|
@@ -891,13 +891,13 @@ function readTextIfExists(filePath) {
|
|
|
891
891
|
function getSymlinkInfo(...candidatePaths) {
|
|
892
892
|
for (const candidatePath of candidatePaths) {
|
|
893
893
|
try {
|
|
894
|
-
const stat =
|
|
894
|
+
const stat = fs7.lstatSync(candidatePath);
|
|
895
895
|
if (!stat.isSymbolicLink())
|
|
896
896
|
continue;
|
|
897
|
-
const target =
|
|
897
|
+
const target = fs7.readlinkSync(candidatePath);
|
|
898
898
|
return {
|
|
899
899
|
isSymlink: true,
|
|
900
|
-
symlinkTarget:
|
|
900
|
+
symlinkTarget: path7.isAbsolute(target) ? target : path7.resolve(path7.dirname(candidatePath), target)
|
|
901
901
|
};
|
|
902
902
|
} catch {
|
|
903
903
|
}
|
|
@@ -930,7 +930,7 @@ function normalizeBoolean(value) {
|
|
|
930
930
|
function sortSkills(skills) {
|
|
931
931
|
return skills.sort((a, b) => AGENT_ORDER[a.agent] - AGENT_ORDER[b.agent] || SCOPE_ORDER[a.scope] - SCOPE_ORDER[b.scope] || KIND_ORDER[a.kind] - KIND_ORDER[b.kind] || a.name.localeCompare(b.name) || a.filePath.localeCompare(b.filePath));
|
|
932
932
|
}
|
|
933
|
-
function pushInstructionIfExists(skills, agent2, scope, filePath, name =
|
|
933
|
+
function pushInstructionIfExists(skills, agent2, scope, filePath, name = path7.basename(filePath), description2 = "", kind = "instruction") {
|
|
934
934
|
const raw = readTextIfExists(filePath);
|
|
935
935
|
if (raw === null)
|
|
936
936
|
return;
|
|
@@ -947,17 +947,17 @@ function pushInstructionIfExists(skills, agent2, scope, filePath, name = path12.
|
|
|
947
947
|
function scanClaudeSkillDir(skills, skillsDir, scope) {
|
|
948
948
|
let entries = [];
|
|
949
949
|
try {
|
|
950
|
-
if (!
|
|
950
|
+
if (!fs7.existsSync(skillsDir))
|
|
951
951
|
return;
|
|
952
|
-
entries =
|
|
952
|
+
entries = fs7.readdirSync(skillsDir, { withFileTypes: true });
|
|
953
953
|
} catch {
|
|
954
954
|
return;
|
|
955
955
|
}
|
|
956
956
|
for (const entry of entries) {
|
|
957
957
|
if (!entry.isDirectory() && !entry.isSymbolicLink())
|
|
958
958
|
continue;
|
|
959
|
-
const skillDirPath =
|
|
960
|
-
const skillFilePath =
|
|
959
|
+
const skillDirPath = path7.join(skillsDir, entry.name);
|
|
960
|
+
const skillFilePath = path7.join(skillDirPath, "SKILL.md");
|
|
961
961
|
const raw = readTextIfExists(skillFilePath);
|
|
962
962
|
if (raw === null)
|
|
963
963
|
continue;
|
|
@@ -978,7 +978,7 @@ function scanCursorRuleFile(skills, filePath, scope) {
|
|
|
978
978
|
if (raw === null)
|
|
979
979
|
return;
|
|
980
980
|
const frontmatter = extractFrontmatter(raw);
|
|
981
|
-
const baseName =
|
|
981
|
+
const baseName = path7.basename(filePath, ".mdc");
|
|
982
982
|
skills.push({
|
|
983
983
|
agent: "cursor",
|
|
984
984
|
scope,
|
|
@@ -994,9 +994,9 @@ function scanCursorRuleFile(skills, filePath, scope) {
|
|
|
994
994
|
function scanCursorRulesDir(skills, rulesDir, scope) {
|
|
995
995
|
let entries = [];
|
|
996
996
|
try {
|
|
997
|
-
if (!
|
|
997
|
+
if (!fs7.existsSync(rulesDir))
|
|
998
998
|
return;
|
|
999
|
-
entries =
|
|
999
|
+
entries = fs7.readdirSync(rulesDir, { withFileTypes: true });
|
|
1000
1000
|
} catch {
|
|
1001
1001
|
return;
|
|
1002
1002
|
}
|
|
@@ -1005,26 +1005,26 @@ function scanCursorRulesDir(skills, rulesDir, scope) {
|
|
|
1005
1005
|
continue;
|
|
1006
1006
|
if (!entry.isFile() && !entry.isSymbolicLink())
|
|
1007
1007
|
continue;
|
|
1008
|
-
scanCursorRuleFile(skills,
|
|
1008
|
+
scanCursorRuleFile(skills, path7.join(rulesDir, entry.name), scope);
|
|
1009
1009
|
}
|
|
1010
1010
|
}
|
|
1011
1011
|
function getProjectAgentSkills(projectPath) {
|
|
1012
|
-
const absProjectPath =
|
|
1012
|
+
const absProjectPath = path7.resolve(projectPath);
|
|
1013
1013
|
let projectStat;
|
|
1014
1014
|
try {
|
|
1015
|
-
projectStat =
|
|
1015
|
+
projectStat = fs7.statSync(absProjectPath);
|
|
1016
1016
|
} catch {
|
|
1017
1017
|
return [];
|
|
1018
1018
|
}
|
|
1019
1019
|
if (!projectStat.isDirectory())
|
|
1020
1020
|
return [];
|
|
1021
1021
|
const skills = [];
|
|
1022
|
-
scanClaudeSkillDir(skills,
|
|
1023
|
-
pushInstructionIfExists(skills, "claude_code", "project",
|
|
1024
|
-
pushInstructionIfExists(skills, "claude_code", "project",
|
|
1025
|
-
pushInstructionIfExists(skills, "codex", "project",
|
|
1026
|
-
scanCursorRulesDir(skills,
|
|
1027
|
-
pushInstructionIfExists(skills, "cursor", "project",
|
|
1022
|
+
scanClaudeSkillDir(skills, path7.join(absProjectPath, ".claude", "skills"), "project");
|
|
1023
|
+
pushInstructionIfExists(skills, "claude_code", "project", path7.join(absProjectPath, "CLAUDE.md"));
|
|
1024
|
+
pushInstructionIfExists(skills, "claude_code", "project", path7.join(absProjectPath, "AGENTS.md"));
|
|
1025
|
+
pushInstructionIfExists(skills, "codex", "project", path7.join(absProjectPath, "AGENTS.md"));
|
|
1026
|
+
scanCursorRulesDir(skills, path7.join(absProjectPath, ".cursor", "rules"), "project");
|
|
1027
|
+
pushInstructionIfExists(skills, "cursor", "project", path7.join(absProjectPath, ".cursorrules"), "Legacy Cursor Rules", "", "rule");
|
|
1028
1028
|
return sortSkills(skills);
|
|
1029
1029
|
}
|
|
1030
1030
|
function getGlobalAgentSkills() {
|
|
@@ -1032,8 +1032,8 @@ function getGlobalAgentSkills() {
|
|
|
1032
1032
|
if (!homeDir)
|
|
1033
1033
|
return [];
|
|
1034
1034
|
const skills = [];
|
|
1035
|
-
scanClaudeSkillDir(skills,
|
|
1036
|
-
scanCursorRulesDir(skills,
|
|
1035
|
+
scanClaudeSkillDir(skills, path7.join(homeDir, ".claude", "skills"), "global");
|
|
1036
|
+
scanCursorRulesDir(skills, path7.join(homeDir, ".cursor", "rules"), "global");
|
|
1037
1037
|
return sortSkills(skills);
|
|
1038
1038
|
}
|
|
1039
1039
|
var AGENT_ORDER, SCOPE_ORDER, KIND_ORDER;
|
|
@@ -1118,28 +1118,226 @@ var init_task_state2 = __esm({
|
|
|
1118
1118
|
});
|
|
1119
1119
|
|
|
1120
1120
|
// src/main.ts
|
|
1121
|
-
import { Command as
|
|
1121
|
+
import { Command as Command24 } from "commander";
|
|
1122
|
+
import chalk24 from "chalk";
|
|
1123
|
+
|
|
1124
|
+
// src/core/config.ts
|
|
1125
|
+
init_node();
|
|
1126
|
+
import fs8 from "fs";
|
|
1127
|
+
import os4 from "os";
|
|
1128
|
+
import path8 from "path";
|
|
1129
|
+
import yaml4 from "js-yaml";
|
|
1130
|
+
function registryDir() {
|
|
1131
|
+
return path8.join(
|
|
1132
|
+
process.env.HOME || process.env.USERPROFILE || os4.homedir(),
|
|
1133
|
+
".config",
|
|
1134
|
+
"task0"
|
|
1135
|
+
);
|
|
1136
|
+
}
|
|
1137
|
+
function registryFile() {
|
|
1138
|
+
return path8.join(registryDir(), "config.yml");
|
|
1139
|
+
}
|
|
1140
|
+
function homeStateFile() {
|
|
1141
|
+
return path8.join(task0Home(), "config.yml");
|
|
1142
|
+
}
|
|
1143
|
+
function configFilePath() {
|
|
1144
|
+
return registryFile();
|
|
1145
|
+
}
|
|
1146
|
+
function readYamlFile(file) {
|
|
1147
|
+
if (!fs8.existsSync(file)) return null;
|
|
1148
|
+
try {
|
|
1149
|
+
const raw = fs8.readFileSync(file, "utf-8");
|
|
1150
|
+
const parsed = yaml4.load(raw);
|
|
1151
|
+
return parsed ?? null;
|
|
1152
|
+
} catch {
|
|
1153
|
+
return null;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
function writeYamlFile(file, data) {
|
|
1157
|
+
fs8.mkdirSync(path8.dirname(file), { recursive: true });
|
|
1158
|
+
fs8.writeFileSync(file, yaml4.dump(data, { lineWidth: 120 }), "utf-8");
|
|
1159
|
+
}
|
|
1160
|
+
function loadRegistry() {
|
|
1161
|
+
return readYamlFile(registryFile()) ?? {};
|
|
1162
|
+
}
|
|
1163
|
+
function saveRegistry(data) {
|
|
1164
|
+
writeYamlFile(registryFile(), data);
|
|
1165
|
+
}
|
|
1166
|
+
function loadHomeState() {
|
|
1167
|
+
return readYamlFile(homeStateFile()) ?? {};
|
|
1168
|
+
}
|
|
1169
|
+
function saveHomeState(data) {
|
|
1170
|
+
writeYamlFile(homeStateFile(), data);
|
|
1171
|
+
}
|
|
1172
|
+
function loadConfig() {
|
|
1173
|
+
const registry = loadRegistry();
|
|
1174
|
+
const home = loadHomeState();
|
|
1175
|
+
const profiles = registry.profiles && typeof registry.profiles === "object" && !Array.isArray(registry.profiles) ? registry.profiles : void 0;
|
|
1176
|
+
const current = typeof registry.current_profile === "string" && registry.current_profile.length > 0 ? registry.current_profile : void 0;
|
|
1177
|
+
const sources = Array.isArray(home.sources) ? home.sources : Array.isArray(registry.sources) ? registry.sources : [];
|
|
1178
|
+
const agentModels = home.agentModels !== void 0 ? home.agentModels : registry.agentModels;
|
|
1179
|
+
return {
|
|
1180
|
+
sources,
|
|
1181
|
+
...agentModels !== void 0 ? { agentModels } : {},
|
|
1182
|
+
...profiles ? { profiles } : {},
|
|
1183
|
+
...current ? { current_profile: current } : {}
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
function saveConfig(config) {
|
|
1187
|
+
const { sources, agentModels, ...registryFields } = config;
|
|
1188
|
+
saveRegistry(registryFields);
|
|
1189
|
+
saveHomeState({
|
|
1190
|
+
...sources !== void 0 ? { sources } : {},
|
|
1191
|
+
...agentModels !== void 0 ? { agentModels } : {}
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
function listProfiles() {
|
|
1195
|
+
return loadConfig().profiles ?? {};
|
|
1196
|
+
}
|
|
1197
|
+
function getProfile(name) {
|
|
1198
|
+
return listProfiles()[name];
|
|
1199
|
+
}
|
|
1200
|
+
function getCurrentProfileName() {
|
|
1201
|
+
return loadConfig().current_profile;
|
|
1202
|
+
}
|
|
1203
|
+
function addProfile(name, entry) {
|
|
1204
|
+
const config = loadConfig();
|
|
1205
|
+
const profiles = { ...config.profiles ?? {} };
|
|
1206
|
+
profiles[name] = entry;
|
|
1207
|
+
config.profiles = profiles;
|
|
1208
|
+
saveConfig(config);
|
|
1209
|
+
}
|
|
1210
|
+
function removeProfile(name) {
|
|
1211
|
+
const config = loadConfig();
|
|
1212
|
+
if (!config.profiles || !(name in config.profiles)) return false;
|
|
1213
|
+
const profiles = { ...config.profiles };
|
|
1214
|
+
delete profiles[name];
|
|
1215
|
+
config.profiles = profiles;
|
|
1216
|
+
if (config.current_profile === name) {
|
|
1217
|
+
delete config.current_profile;
|
|
1218
|
+
}
|
|
1219
|
+
saveConfig(config);
|
|
1220
|
+
return true;
|
|
1221
|
+
}
|
|
1222
|
+
function setCurrentProfile(name) {
|
|
1223
|
+
const config = loadConfig();
|
|
1224
|
+
if (name === null) {
|
|
1225
|
+
delete config.current_profile;
|
|
1226
|
+
} else {
|
|
1227
|
+
config.current_profile = name;
|
|
1228
|
+
}
|
|
1229
|
+
saveConfig(config);
|
|
1230
|
+
}
|
|
1231
|
+
function addSource(entry) {
|
|
1232
|
+
const config = loadConfig();
|
|
1233
|
+
const existing = config.sources.findIndex((s) => s.name === entry.name);
|
|
1234
|
+
if (existing >= 0) {
|
|
1235
|
+
config.sources[existing] = entry;
|
|
1236
|
+
} else {
|
|
1237
|
+
config.sources.push(entry);
|
|
1238
|
+
}
|
|
1239
|
+
saveConfig(config);
|
|
1240
|
+
}
|
|
1241
|
+
function removeSource(name) {
|
|
1242
|
+
const config = loadConfig();
|
|
1243
|
+
const idx = config.sources.findIndex((s) => s.name === name);
|
|
1244
|
+
if (idx < 0) return false;
|
|
1245
|
+
config.sources.splice(idx, 1);
|
|
1246
|
+
saveConfig(config);
|
|
1247
|
+
return true;
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
// src/core/profile.ts
|
|
1251
|
+
var ProfileNotFoundError = class extends Error {
|
|
1252
|
+
constructor(name, available) {
|
|
1253
|
+
const list = available.length > 0 ? available.join(", ") : "(none)";
|
|
1254
|
+
super(`Profile "${name}" not found. Available: ${list}. Run "task0 profile list" to inspect.`);
|
|
1255
|
+
this.name = "ProfileNotFoundError";
|
|
1256
|
+
}
|
|
1257
|
+
};
|
|
1258
|
+
function parseProfileFlag(argv) {
|
|
1259
|
+
for (let i = 2; i < argv.length; i++) {
|
|
1260
|
+
const arg = argv[i];
|
|
1261
|
+
if (arg === "--") return null;
|
|
1262
|
+
if (arg === "--profile") {
|
|
1263
|
+
const next = argv[i + 1];
|
|
1264
|
+
if (typeof next === "string" && !next.startsWith("-")) return next;
|
|
1265
|
+
return null;
|
|
1266
|
+
}
|
|
1267
|
+
if (typeof arg === "string" && arg.startsWith("--profile=")) {
|
|
1268
|
+
const value = arg.slice("--profile=".length);
|
|
1269
|
+
return value.length > 0 ? value : null;
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
return null;
|
|
1273
|
+
}
|
|
1274
|
+
function isProfileSubcommandInvocation(argv) {
|
|
1275
|
+
for (let i = 2; i < argv.length; i++) {
|
|
1276
|
+
const arg = argv[i];
|
|
1277
|
+
if (arg === "--") break;
|
|
1278
|
+
if (typeof arg !== "string") continue;
|
|
1279
|
+
if (arg.startsWith("-")) {
|
|
1280
|
+
if (arg === "--profile" || arg === "-C") i++;
|
|
1281
|
+
continue;
|
|
1282
|
+
}
|
|
1283
|
+
return arg === "profile";
|
|
1284
|
+
}
|
|
1285
|
+
return true;
|
|
1286
|
+
}
|
|
1287
|
+
function resolveActiveProfile(flagValue) {
|
|
1288
|
+
const requested = flagValue ?? getCurrentProfileName() ?? null;
|
|
1289
|
+
if (!requested) return null;
|
|
1290
|
+
const entry = getProfile(requested);
|
|
1291
|
+
if (!entry) {
|
|
1292
|
+
throw new ProfileNotFoundError(requested, Object.keys(listProfiles()));
|
|
1293
|
+
}
|
|
1294
|
+
return { name: requested, entry };
|
|
1295
|
+
}
|
|
1296
|
+
var activeProfileCache = null;
|
|
1297
|
+
function activateProfile(argv) {
|
|
1298
|
+
const flagValue = parseProfileFlag(argv);
|
|
1299
|
+
let active2;
|
|
1300
|
+
try {
|
|
1301
|
+
active2 = resolveActiveProfile(flagValue);
|
|
1302
|
+
} catch (error2) {
|
|
1303
|
+
if (isProfileSubcommandInvocation(argv) && error2 instanceof ProfileNotFoundError && flagValue === null) {
|
|
1304
|
+
active2 = null;
|
|
1305
|
+
} else {
|
|
1306
|
+
throw error2;
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
if (active2) {
|
|
1310
|
+
activeProfileCache = active2;
|
|
1311
|
+
if (active2.entry.task0_home && active2.entry.task0_home.length > 0) {
|
|
1312
|
+
process.env.TASK0_HOME = active2.entry.task0_home;
|
|
1313
|
+
}
|
|
1314
|
+
if (active2.entry.api_url && !process.env.TASK0_API_URL) {
|
|
1315
|
+
process.env.TASK0_API_URL = active2.entry.api_url;
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1122
1319
|
|
|
1123
1320
|
// src/commands/source.ts
|
|
1124
1321
|
import { Command } from "commander";
|
|
1125
|
-
import
|
|
1322
|
+
import path11 from "path";
|
|
1126
1323
|
import chalk from "chalk";
|
|
1127
1324
|
|
|
1128
1325
|
// src/types.ts
|
|
1129
1326
|
init_task();
|
|
1130
1327
|
|
|
1131
1328
|
// src/core/admin-token.ts
|
|
1132
|
-
|
|
1133
|
-
import
|
|
1134
|
-
import
|
|
1135
|
-
|
|
1136
|
-
|
|
1329
|
+
init_node();
|
|
1330
|
+
import fs9 from "fs";
|
|
1331
|
+
import path9 from "path";
|
|
1332
|
+
function tokenFile() {
|
|
1333
|
+
return path9.join(task0Home(), "admin.token");
|
|
1334
|
+
}
|
|
1137
1335
|
var cached = null;
|
|
1138
1336
|
var AdminTokenUnavailableError = class extends Error {
|
|
1139
1337
|
constructor() {
|
|
1140
1338
|
super(
|
|
1141
1339
|
`Admin token not found.
|
|
1142
|
-
\u2022 If the task0 server runs on this host, run the task0-server binary once \u2014 the token will be generated at ${
|
|
1340
|
+
\u2022 If the task0 server runs on this host, run the task0-server binary once \u2014 the token will be generated at ${tokenFile()}.
|
|
1143
1341
|
\u2022 Otherwise, copy the token from the server host and set TASK0_ADMIN_TOKEN.`
|
|
1144
1342
|
);
|
|
1145
1343
|
this.name = "AdminTokenUnavailableError";
|
|
@@ -1152,8 +1350,9 @@ function readAdminToken() {
|
|
|
1152
1350
|
cached = fromEnv;
|
|
1153
1351
|
return cached;
|
|
1154
1352
|
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1353
|
+
const file = tokenFile();
|
|
1354
|
+
if (fs9.existsSync(file)) {
|
|
1355
|
+
const v = fs9.readFileSync(file, "utf-8").trim();
|
|
1157
1356
|
if (v) {
|
|
1158
1357
|
cached = v;
|
|
1159
1358
|
return cached;
|
|
@@ -1166,34 +1365,41 @@ function adminAuthHeader() {
|
|
|
1166
1365
|
}
|
|
1167
1366
|
|
|
1168
1367
|
// src/core/daemon-config.ts
|
|
1169
|
-
|
|
1170
|
-
import
|
|
1171
|
-
import
|
|
1172
|
-
|
|
1173
|
-
|
|
1368
|
+
init_node();
|
|
1369
|
+
import fs10 from "fs";
|
|
1370
|
+
import path10 from "path";
|
|
1371
|
+
function configDir() {
|
|
1372
|
+
return task0Home();
|
|
1373
|
+
}
|
|
1374
|
+
function configFile() {
|
|
1375
|
+
return path10.join(configDir(), "daemon.json");
|
|
1376
|
+
}
|
|
1174
1377
|
function daemonConfigPath() {
|
|
1175
|
-
return
|
|
1378
|
+
return configFile();
|
|
1176
1379
|
}
|
|
1177
1380
|
function readDaemonIdentity() {
|
|
1178
|
-
|
|
1381
|
+
const file = configFile();
|
|
1382
|
+
if (!fs10.existsSync(file)) return null;
|
|
1179
1383
|
try {
|
|
1180
|
-
const raw =
|
|
1384
|
+
const raw = fs10.readFileSync(file, "utf-8");
|
|
1181
1385
|
return JSON.parse(raw);
|
|
1182
1386
|
} catch {
|
|
1183
1387
|
return null;
|
|
1184
1388
|
}
|
|
1185
1389
|
}
|
|
1186
1390
|
function writeDaemonIdentity(identity) {
|
|
1187
|
-
|
|
1188
|
-
|
|
1391
|
+
const file = configFile();
|
|
1392
|
+
fs10.mkdirSync(configDir(), { recursive: true });
|
|
1393
|
+
fs10.writeFileSync(file, JSON.stringify(identity, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
|
|
1189
1394
|
try {
|
|
1190
|
-
|
|
1395
|
+
fs10.chmodSync(file, 384);
|
|
1191
1396
|
} catch {
|
|
1192
1397
|
}
|
|
1193
1398
|
}
|
|
1194
1399
|
function clearDaemonIdentity() {
|
|
1195
|
-
|
|
1196
|
-
|
|
1400
|
+
const file = configFile();
|
|
1401
|
+
if (!fs10.existsSync(file)) return false;
|
|
1402
|
+
fs10.unlinkSync(file);
|
|
1197
1403
|
return true;
|
|
1198
1404
|
}
|
|
1199
1405
|
|
|
@@ -1231,7 +1437,7 @@ function resolveAuthHeader() {
|
|
|
1231
1437
|
} catch (error2) {
|
|
1232
1438
|
if (error2 instanceof AdminTokenUnavailableError) {
|
|
1233
1439
|
throw new Error(
|
|
1234
|
-
`No CLI credential available. Set TASK0_API_TOKEN (preferred \u2014 issue one via the dashboard) or place a server admin token at ${error2.message.includes(".token") ? error2.message.split("at ")[1]?.split(".")[0] + ".token" : "~/.
|
|
1440
|
+
`No CLI credential available. Set TASK0_API_TOKEN (preferred \u2014 issue one via the dashboard) or place a server admin token at ${error2.message.includes(".token") ? error2.message.split("at ")[1]?.split(".")[0] + ".token" : "~/.task0/admin.token"}.`
|
|
1235
1441
|
);
|
|
1236
1442
|
}
|
|
1237
1443
|
throw error2;
|
|
@@ -1302,8 +1508,8 @@ function requireLocalDaemon() {
|
|
|
1302
1508
|
}
|
|
1303
1509
|
var source = new Command("source").description("Manage task sources");
|
|
1304
1510
|
source.command("add <path>").description("Register a local project on this host's daemon (via hub)").option("-n, --name <name>", "Source name (defaults to directory name)").action(async (inputPath, opts) => {
|
|
1305
|
-
const absPath =
|
|
1306
|
-
const name = opts.name ||
|
|
1511
|
+
const absPath = path11.resolve(inputPath);
|
|
1512
|
+
const name = opts.name || path11.basename(absPath);
|
|
1307
1513
|
const daemonId = requireLocalDaemon();
|
|
1308
1514
|
let resp;
|
|
1309
1515
|
try {
|
|
@@ -1401,9 +1607,9 @@ ${project2}`));
|
|
|
1401
1607
|
|
|
1402
1608
|
// src/commands/project.ts
|
|
1403
1609
|
import { Command as Command2 } from "commander";
|
|
1404
|
-
import
|
|
1405
|
-
import
|
|
1406
|
-
import
|
|
1610
|
+
import fs11 from "fs";
|
|
1611
|
+
import path12 from "path";
|
|
1612
|
+
import yaml5 from "js-yaml";
|
|
1407
1613
|
import chalk2 from "chalk";
|
|
1408
1614
|
|
|
1409
1615
|
// ../../packages/shared/dist/index.js
|
|
@@ -1441,15 +1647,15 @@ project.command("list").description("List registered projects (queries the hub)"
|
|
|
1441
1647
|
});
|
|
1442
1648
|
project.command("init").description("Initialize task0.yml in the current directory").option("-d, --tasks-dir <dir>", "Tasks directory", ".task0/tasks").action((opts) => {
|
|
1443
1649
|
const cwd = process.cwd();
|
|
1444
|
-
const ymlPath =
|
|
1445
|
-
if (
|
|
1650
|
+
const ymlPath = path12.join(cwd, "task0.yml");
|
|
1651
|
+
if (fs11.existsSync(ymlPath)) {
|
|
1446
1652
|
console.error(chalk2.yellow("task0.yml already exists"));
|
|
1447
1653
|
process.exit(1);
|
|
1448
1654
|
}
|
|
1449
1655
|
const config = { kind: "project", object_id: generateObjectId("project"), tasks_dir: opts.tasksDir };
|
|
1450
|
-
|
|
1451
|
-
const tasksDir =
|
|
1452
|
-
|
|
1656
|
+
fs11.writeFileSync(ymlPath, yaml5.dump(config), "utf-8");
|
|
1657
|
+
const tasksDir = path12.join(cwd, opts.tasksDir);
|
|
1658
|
+
fs11.mkdirSync(tasksDir, { recursive: true });
|
|
1453
1659
|
console.log(chalk2.green("Initialized task0 project"));
|
|
1454
1660
|
console.log(` ${ymlPath}`);
|
|
1455
1661
|
console.log(` ${tasksDir}/`);
|
|
@@ -1497,11 +1703,11 @@ async function request(method, pathname, body) {
|
|
|
1497
1703
|
}
|
|
1498
1704
|
}
|
|
1499
1705
|
var api = {
|
|
1500
|
-
get: (
|
|
1501
|
-
post: (
|
|
1502
|
-
put: (
|
|
1503
|
-
patch: (
|
|
1504
|
-
del: (
|
|
1706
|
+
get: (path31) => request("GET", path31),
|
|
1707
|
+
post: (path31, body) => request("POST", path31, body ?? {}),
|
|
1708
|
+
put: (path31, body) => request("PUT", path31, body ?? {}),
|
|
1709
|
+
patch: (path31, body) => request("PATCH", path31, body ?? {}),
|
|
1710
|
+
del: (path31) => request("DELETE", path31)
|
|
1505
1711
|
};
|
|
1506
1712
|
|
|
1507
1713
|
// src/commands/task/triage.ts
|
|
@@ -1532,54 +1738,6 @@ async function waitForAgentRun(id, opts = {}) {
|
|
|
1532
1738
|
}
|
|
1533
1739
|
}
|
|
1534
1740
|
|
|
1535
|
-
// src/core/config.ts
|
|
1536
|
-
import fs4 from "fs";
|
|
1537
|
-
import path5 from "path";
|
|
1538
|
-
import yaml2 from "js-yaml";
|
|
1539
|
-
var CONFIG_DIR3 = path5.join(
|
|
1540
|
-
process.env.HOME || process.env.USERPROFILE || "~",
|
|
1541
|
-
".config",
|
|
1542
|
-
"task0"
|
|
1543
|
-
);
|
|
1544
|
-
var CONFIG_FILE2 = path5.join(CONFIG_DIR3, "config.yml");
|
|
1545
|
-
function ensureConfigDir() {
|
|
1546
|
-
fs4.mkdirSync(CONFIG_DIR3, { recursive: true });
|
|
1547
|
-
}
|
|
1548
|
-
function defaultConfig() {
|
|
1549
|
-
return { sources: [] };
|
|
1550
|
-
}
|
|
1551
|
-
function loadConfig() {
|
|
1552
|
-
if (!fs4.existsSync(CONFIG_FILE2)) return defaultConfig();
|
|
1553
|
-
const raw = fs4.readFileSync(CONFIG_FILE2, "utf-8");
|
|
1554
|
-
const data = yaml2.load(raw);
|
|
1555
|
-
return {
|
|
1556
|
-
...data ?? {},
|
|
1557
|
-
sources: Array.isArray(data?.sources) ? data.sources : []
|
|
1558
|
-
};
|
|
1559
|
-
}
|
|
1560
|
-
function saveConfig(config) {
|
|
1561
|
-
ensureConfigDir();
|
|
1562
|
-
fs4.writeFileSync(CONFIG_FILE2, yaml2.dump(config, { lineWidth: 120 }), "utf-8");
|
|
1563
|
-
}
|
|
1564
|
-
function addSource(entry) {
|
|
1565
|
-
const config = loadConfig();
|
|
1566
|
-
const existing = config.sources.findIndex((s) => s.name === entry.name);
|
|
1567
|
-
if (existing >= 0) {
|
|
1568
|
-
config.sources[existing] = entry;
|
|
1569
|
-
} else {
|
|
1570
|
-
config.sources.push(entry);
|
|
1571
|
-
}
|
|
1572
|
-
saveConfig(config);
|
|
1573
|
-
}
|
|
1574
|
-
function removeSource(name) {
|
|
1575
|
-
const config = loadConfig();
|
|
1576
|
-
const idx = config.sources.findIndex((s) => s.name === name);
|
|
1577
|
-
if (idx < 0) return false;
|
|
1578
|
-
config.sources.splice(idx, 1);
|
|
1579
|
-
saveConfig(config);
|
|
1580
|
-
return true;
|
|
1581
|
-
}
|
|
1582
|
-
|
|
1583
1741
|
// src/core/model-options.ts
|
|
1584
1742
|
function resolveModelOptions(agent2, cli, warn = () => {
|
|
1585
1743
|
}) {
|
|
@@ -2230,17 +2388,17 @@ import chalk10 from "chalk";
|
|
|
2230
2388
|
// ../../packages/shared/dist/types/agent.js
|
|
2231
2389
|
var AGENT_KINDS = ["coding", "llm_api", "workflow"];
|
|
2232
2390
|
var AGENT_KIND_SET = new Set(AGENT_KINDS);
|
|
2233
|
-
var
|
|
2234
|
-
var
|
|
2235
|
-
function
|
|
2236
|
-
return typeof value === "string" &&
|
|
2391
|
+
var AGENT_PROVIDERS = ["claude-code", "codex", "cursor-agent"];
|
|
2392
|
+
var AGENT_PROVIDER_SET = new Set(AGENT_PROVIDERS);
|
|
2393
|
+
function isAgentProvider(value) {
|
|
2394
|
+
return typeof value === "string" && AGENT_PROVIDER_SET.has(value);
|
|
2237
2395
|
}
|
|
2238
2396
|
|
|
2239
2397
|
// ../../packages/shared/dist/types/runtime.js
|
|
2240
2398
|
var AGENT_RUN_STATUS_VALUES = ["starting", "running", "done", "error"];
|
|
2241
2399
|
var AGENT_RUN_STATUS_SET = new Set(AGENT_RUN_STATUS_VALUES);
|
|
2242
|
-
var RUNTIME_AGENTS =
|
|
2243
|
-
var isRuntimeAgent =
|
|
2400
|
+
var RUNTIME_AGENTS = AGENT_PROVIDERS;
|
|
2401
|
+
var isRuntimeAgent = isAgentProvider;
|
|
2244
2402
|
var AGENT_MODEL_DEFAULTS = {
|
|
2245
2403
|
"claude-code": [
|
|
2246
2404
|
{ id: "opus", label: "Opus" },
|
|
@@ -2252,20 +2410,45 @@ var AGENT_MODEL_DEFAULTS = {
|
|
|
2252
2410
|
{ id: "o3", label: "o3" },
|
|
2253
2411
|
{ id: "o4-mini", label: "o4-mini" }
|
|
2254
2412
|
],
|
|
2255
|
-
cursor: [
|
|
2413
|
+
"cursor-agent": [
|
|
2256
2414
|
{ id: "", label: "Default" },
|
|
2257
2415
|
{ id: "gpt-5", label: "GPT-5" },
|
|
2258
2416
|
{ id: "sonnet-4", label: "Sonnet 4" },
|
|
2259
2417
|
{ id: "sonnet-4-thinking", label: "Sonnet 4 Thinking" }
|
|
2260
2418
|
]
|
|
2261
2419
|
};
|
|
2420
|
+
var AGENT_EFFORT_DEFAULTS = {
|
|
2421
|
+
"claude-code": [
|
|
2422
|
+
{ id: "high", label: "High" },
|
|
2423
|
+
{ id: "max", label: "Max" },
|
|
2424
|
+
{ id: "medium", label: "Medium" },
|
|
2425
|
+
{ id: "low", label: "Low" }
|
|
2426
|
+
],
|
|
2427
|
+
codex: [
|
|
2428
|
+
{ id: "xhigh", label: "Extra High" },
|
|
2429
|
+
{ id: "high", label: "High" },
|
|
2430
|
+
{ id: "medium", label: "Medium" },
|
|
2431
|
+
{ id: "low", label: "Low" }
|
|
2432
|
+
],
|
|
2433
|
+
"cursor-agent": []
|
|
2434
|
+
};
|
|
2435
|
+
var AGENT_DEFAULT_MODEL = {
|
|
2436
|
+
"claude-code": "opus",
|
|
2437
|
+
codex: "gpt-5.4",
|
|
2438
|
+
"cursor-agent": ""
|
|
2439
|
+
};
|
|
2440
|
+
var AGENT_DEFAULT_EFFORT = {
|
|
2441
|
+
"claude-code": "high",
|
|
2442
|
+
codex: "xhigh",
|
|
2443
|
+
"cursor-agent": ""
|
|
2444
|
+
};
|
|
2262
2445
|
function defaultAgentModelFetchCommand(agent2) {
|
|
2263
|
-
if (agent2 === "cursor")
|
|
2446
|
+
if (agent2 === "cursor-agent")
|
|
2264
2447
|
return "cursor-agent models";
|
|
2265
2448
|
return void 0;
|
|
2266
2449
|
}
|
|
2267
2450
|
function defaultAgentModelOutputFormat(agent2) {
|
|
2268
|
-
if (agent2 === "cursor")
|
|
2451
|
+
if (agent2 === "cursor-agent")
|
|
2269
2452
|
return "lines";
|
|
2270
2453
|
return void 0;
|
|
2271
2454
|
}
|
|
@@ -4156,7 +4339,7 @@ function fail5(message, code = 1) {
|
|
|
4156
4339
|
process.exit(code);
|
|
4157
4340
|
}
|
|
4158
4341
|
function formatAgent(a, withDetails = false) {
|
|
4159
|
-
const tag = a.
|
|
4342
|
+
const tag = a.scope ? chalk18.dim(`(${a.scope})`) : "";
|
|
4160
4343
|
const dot = statusBadge(a.status);
|
|
4161
4344
|
const head = `${dot} ${chalk18.bold(a.slug.padEnd(24))} ${chalk18.dim(a.object_id.padEnd(16))} ${a.kind.padEnd(10)} ${tag}`;
|
|
4162
4345
|
if (!withDetails) return head;
|
|
@@ -4227,7 +4410,6 @@ agent.command("create").description("Create an agent from a YAML spec file").req
|
|
|
4227
4410
|
agent.command("edit <ref>").description("Open the agent YAML in $EDITOR and save changes").action(async (ref) => {
|
|
4228
4411
|
try {
|
|
4229
4412
|
const result = await api.get(`/api/agents/${encodeURIComponent(ref)}`);
|
|
4230
|
-
if (result.agent.system) fail5("cannot edit a system agent");
|
|
4231
4413
|
const tmp = path21.join(
|
|
4232
4414
|
process.env.TMPDIR || "/tmp",
|
|
4233
4415
|
`task0-agent-${result.agent.slug}-${Date.now()}.yml`
|
|
@@ -4352,7 +4534,8 @@ async function streamOutput(ref, agentRunId) {
|
|
|
4352
4534
|
}
|
|
4353
4535
|
|
|
4354
4536
|
// src/commands/daemon.ts
|
|
4355
|
-
import
|
|
4537
|
+
import os6 from "os";
|
|
4538
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
4356
4539
|
import { Command as Command19 } from "commander";
|
|
4357
4540
|
import chalk19 from "chalk";
|
|
4358
4541
|
import WebSocket from "ws";
|
|
@@ -4431,7 +4614,7 @@ var RegisterAuthUnavailableError = class extends Error {
|
|
|
4431
4614
|
`No registration credential available.
|
|
4432
4615
|
\u2022 Pass --token <apit_...> with an API token created in the dashboard, or
|
|
4433
4616
|
\u2022 Set TASK0_API_TOKEN in the environment, or
|
|
4434
|
-
\u2022 Fall back to the server's admin token (TASK0_ADMIN_TOKEN or ~/.
|
|
4617
|
+
\u2022 Fall back to the server's admin token (TASK0_ADMIN_TOKEN or ~/.task0/admin.token).`
|
|
4435
4618
|
);
|
|
4436
4619
|
this.name = "RegisterAuthUnavailableError";
|
|
4437
4620
|
}
|
|
@@ -4466,11 +4649,11 @@ import fs24 from "fs";
|
|
|
4466
4649
|
import path23 from "path";
|
|
4467
4650
|
|
|
4468
4651
|
// src/core/daemon-agent-run-dir.ts
|
|
4652
|
+
init_node();
|
|
4469
4653
|
import fs23 from "fs";
|
|
4470
|
-
import os6 from "os";
|
|
4471
4654
|
import path22 from "path";
|
|
4472
4655
|
function agentRunRoot() {
|
|
4473
|
-
return process.env.TASK0_DAEMON_AGENT_RUN_DIR || path22.join(
|
|
4656
|
+
return process.env.TASK0_DAEMON_AGENT_RUN_DIR || path22.join(task0Home(), "agent-run");
|
|
4474
4657
|
}
|
|
4475
4658
|
function agentRunDir(runId) {
|
|
4476
4659
|
return path22.join(agentRunRoot(), runId);
|
|
@@ -4649,6 +4832,20 @@ function ensureString(value, name) {
|
|
|
4649
4832
|
}
|
|
4650
4833
|
return value;
|
|
4651
4834
|
}
|
|
4835
|
+
function ensureSafeTaskId(value) {
|
|
4836
|
+
const id = ensureString(value, "taskId");
|
|
4837
|
+
if (id.includes("/") || id.includes("\\") || id.includes("\0") || id === "." || id === ".." || path24.isAbsolute(id)) {
|
|
4838
|
+
throw Object.assign(new Error(`invalid taskId: ${id}`), { code: "invalid_params" });
|
|
4839
|
+
}
|
|
4840
|
+
return id;
|
|
4841
|
+
}
|
|
4842
|
+
function assertContained(rootAbs, resolvedAbs) {
|
|
4843
|
+
const root = path24.resolve(rootAbs);
|
|
4844
|
+
const target = path24.resolve(resolvedAbs);
|
|
4845
|
+
if (target !== root && !target.startsWith(root + path24.sep)) {
|
|
4846
|
+
throw Object.assign(new Error("path escapes containment root"), { code: "invalid_params" });
|
|
4847
|
+
}
|
|
4848
|
+
}
|
|
4652
4849
|
function optionalString(value) {
|
|
4653
4850
|
if (value === void 0 || value === null) return void 0;
|
|
4654
4851
|
if (typeof value !== "string") {
|
|
@@ -4775,7 +4972,7 @@ var rpcHandlers = {
|
|
|
4775
4972
|
// in the same task directory. Path is resolved from (projectName, taskId).
|
|
4776
4973
|
async task_read(params) {
|
|
4777
4974
|
const projectName = ensureString(params.projectName, "projectName");
|
|
4778
|
-
const taskId =
|
|
4975
|
+
const taskId = ensureSafeTaskId(params.taskId);
|
|
4779
4976
|
const project2 = listProjects().find((p) => p.name === projectName);
|
|
4780
4977
|
if (!project2) {
|
|
4781
4978
|
throw Object.assign(new Error(`project not found: ${projectName}`), { code: "not_found" });
|
|
@@ -4785,26 +4982,28 @@ var rpcHandlers = {
|
|
|
4785
4982
|
if (!projectConfig) {
|
|
4786
4983
|
throw Object.assign(new Error(`invalid project: missing task0.yml at ${projectAbs}`), { code: "invalid_project" });
|
|
4787
4984
|
}
|
|
4788
|
-
const
|
|
4985
|
+
const tasksRoot = path24.resolve(projectAbs, projectConfig.tasks_dir);
|
|
4986
|
+
const taskDir = path24.resolve(tasksRoot, taskId);
|
|
4987
|
+
assertContained(tasksRoot, taskDir);
|
|
4789
4988
|
if (!fs25.existsSync(taskDir)) {
|
|
4790
4989
|
throw Object.assign(new Error(`task not found: ${taskId}`), { code: "not_found" });
|
|
4791
4990
|
}
|
|
4792
4991
|
const taskYml = path24.join(taskDir, "task0.yml");
|
|
4793
|
-
const
|
|
4794
|
-
if (!
|
|
4992
|
+
const yaml10 = readYaml(taskYml);
|
|
4993
|
+
if (!yaml10) {
|
|
4795
4994
|
throw Object.assign(new Error(`task yaml missing or unreadable: ${taskYml}`), { code: "not_found" });
|
|
4796
4995
|
}
|
|
4797
4996
|
const files = fs25.readdirSync(taskDir).filter((name) => name !== "task0.yml");
|
|
4798
|
-
return { task_dir: taskDir, yaml:
|
|
4997
|
+
return { task_dir: taskDir, yaml: yaml10, files };
|
|
4799
4998
|
},
|
|
4800
4999
|
// Create a task directory + write task0.yml + write any additional named
|
|
4801
5000
|
// files. Hub decides the taskId and yaml content (slug/object_id are
|
|
4802
5001
|
// generated server-side); daemon just commits to disk.
|
|
4803
5002
|
async task_create(params, ctx) {
|
|
4804
5003
|
const projectName = ensureString(params.projectName, "projectName");
|
|
4805
|
-
const taskId =
|
|
4806
|
-
const
|
|
4807
|
-
if (!
|
|
5004
|
+
const taskId = ensureSafeTaskId(params.taskId);
|
|
5005
|
+
const yaml10 = params.yaml;
|
|
5006
|
+
if (!yaml10 || typeof yaml10 !== "object") {
|
|
4808
5007
|
throw Object.assign(new Error("yaml (object) is required"), { code: "invalid_params" });
|
|
4809
5008
|
}
|
|
4810
5009
|
const files = params.files ?? {};
|
|
@@ -4817,12 +5016,14 @@ var rpcHandlers = {
|
|
|
4817
5016
|
if (!projectConfig) {
|
|
4818
5017
|
throw Object.assign(new Error(`invalid project: missing task0.yml at ${projectAbs}`), { code: "invalid_project" });
|
|
4819
5018
|
}
|
|
4820
|
-
const
|
|
5019
|
+
const tasksRoot = path24.resolve(projectAbs, projectConfig.tasks_dir);
|
|
5020
|
+
const taskDir = path24.resolve(tasksRoot, taskId);
|
|
5021
|
+
assertContained(tasksRoot, taskDir);
|
|
4821
5022
|
if (fs25.existsSync(taskDir)) {
|
|
4822
5023
|
throw Object.assign(new Error(`task already exists: ${taskId}`), { code: "already_exists" });
|
|
4823
5024
|
}
|
|
4824
5025
|
fs25.mkdirSync(taskDir, { recursive: true });
|
|
4825
|
-
writeYaml(path24.join(taskDir, "task0.yml"),
|
|
5026
|
+
writeYaml(path24.join(taskDir, "task0.yml"), yaml10);
|
|
4826
5027
|
for (const [name, content] of Object.entries(files)) {
|
|
4827
5028
|
if (name.includes("/") || name.includes("\\") || name === ".." || name === ".") {
|
|
4828
5029
|
throw Object.assign(new Error(`invalid file name: ${name}`), { code: "invalid_params" });
|
|
@@ -4838,9 +5039,9 @@ var rpcHandlers = {
|
|
|
4838
5039
|
// partial writes.
|
|
4839
5040
|
async task_write_yaml(params, ctx) {
|
|
4840
5041
|
const projectName = ensureString(params.projectName, "projectName");
|
|
4841
|
-
const taskId =
|
|
4842
|
-
const
|
|
4843
|
-
if (!
|
|
5042
|
+
const taskId = ensureSafeTaskId(params.taskId);
|
|
5043
|
+
const yaml10 = params.yaml;
|
|
5044
|
+
if (!yaml10 || typeof yaml10 !== "object") {
|
|
4844
5045
|
throw Object.assign(new Error("yaml (object) is required"), { code: "invalid_params" });
|
|
4845
5046
|
}
|
|
4846
5047
|
const project2 = listProjects().find((p) => p.name === projectName);
|
|
@@ -4852,12 +5053,14 @@ var rpcHandlers = {
|
|
|
4852
5053
|
if (!projectConfig) {
|
|
4853
5054
|
throw Object.assign(new Error(`invalid project: missing task0.yml at ${projectAbs}`), { code: "invalid_project" });
|
|
4854
5055
|
}
|
|
4855
|
-
const
|
|
5056
|
+
const tasksRoot = path24.resolve(projectAbs, projectConfig.tasks_dir);
|
|
5057
|
+
const taskDir = path24.resolve(tasksRoot, taskId);
|
|
5058
|
+
assertContained(tasksRoot, taskDir);
|
|
4856
5059
|
const taskYml = path24.join(taskDir, "task0.yml");
|
|
4857
5060
|
if (!fs25.existsSync(taskYml)) {
|
|
4858
5061
|
throw Object.assign(new Error(`task yaml not found: ${taskYml}`), { code: "not_found" });
|
|
4859
5062
|
}
|
|
4860
|
-
writeYaml(taskYml,
|
|
5063
|
+
writeYaml(taskYml, yaml10);
|
|
4861
5064
|
ctx.notifyManifestChanged();
|
|
4862
5065
|
return { task_dir: taskDir };
|
|
4863
5066
|
},
|
|
@@ -4865,7 +5068,7 @@ var rpcHandlers = {
|
|
|
4865
5068
|
// workflows). Returns the deleted path so the hub can emit an event.
|
|
4866
5069
|
async task_delete(params, ctx) {
|
|
4867
5070
|
const projectName = ensureString(params.projectName, "projectName");
|
|
4868
|
-
const taskId =
|
|
5071
|
+
const taskId = ensureSafeTaskId(params.taskId);
|
|
4869
5072
|
const project2 = listProjects().find((p) => p.name === projectName);
|
|
4870
5073
|
if (!project2) {
|
|
4871
5074
|
throw Object.assign(new Error(`project not found: ${projectName}`), { code: "not_found" });
|
|
@@ -4875,7 +5078,9 @@ var rpcHandlers = {
|
|
|
4875
5078
|
if (!projectConfig) {
|
|
4876
5079
|
throw Object.assign(new Error(`invalid project: missing task0.yml at ${projectAbs}`), { code: "invalid_project" });
|
|
4877
5080
|
}
|
|
4878
|
-
const
|
|
5081
|
+
const tasksRoot = path24.resolve(projectAbs, projectConfig.tasks_dir);
|
|
5082
|
+
const taskDir = path24.resolve(tasksRoot, taskId);
|
|
5083
|
+
assertContained(tasksRoot, taskDir);
|
|
4879
5084
|
if (!fs25.existsSync(taskDir)) {
|
|
4880
5085
|
throw Object.assign(new Error(`task not found: ${taskId}`), { code: "not_found" });
|
|
4881
5086
|
}
|
|
@@ -4970,25 +5175,34 @@ function isInstalledBuild(p) {
|
|
|
4970
5175
|
}
|
|
4971
5176
|
|
|
4972
5177
|
// src/core/daemon-service/paths.ts
|
|
4973
|
-
|
|
5178
|
+
init_node();
|
|
5179
|
+
import os5 from "os";
|
|
4974
5180
|
import path25 from "path";
|
|
4975
5181
|
|
|
4976
5182
|
// src/core/daemon-service/types.ts
|
|
4977
|
-
|
|
5183
|
+
import crypto2 from "crypto";
|
|
5184
|
+
var BASE_SERVICE_LABEL = "cc.cy0.task0";
|
|
5185
|
+
function serviceLabel() {
|
|
5186
|
+
const home = process.env.TASK0_HOME;
|
|
5187
|
+
if (!home || home.length === 0) return BASE_SERVICE_LABEL;
|
|
5188
|
+
const hash = crypto2.createHash("sha256").update(home).digest("hex").slice(0, 8);
|
|
5189
|
+
return `${BASE_SERVICE_LABEL}.${hash}`;
|
|
5190
|
+
}
|
|
4978
5191
|
|
|
4979
5192
|
// src/core/daemon-service/paths.ts
|
|
4980
5193
|
function unitPath(scope) {
|
|
4981
5194
|
const platform = process.platform;
|
|
5195
|
+
const label = serviceLabel();
|
|
4982
5196
|
if (platform === "darwin") {
|
|
4983
|
-
return scope === "user" ? path25.join(
|
|
5197
|
+
return scope === "user" ? path25.join(os5.homedir(), "Library", "LaunchAgents", `${label}.plist`) : path25.join("/", "Library", "LaunchDaemons", `${label}.plist`);
|
|
4984
5198
|
}
|
|
4985
5199
|
if (platform === "linux") {
|
|
4986
|
-
return scope === "user" ? path25.join(
|
|
5200
|
+
return scope === "user" ? path25.join(os5.homedir(), ".config", "systemd", "user", `${label}.service`) : path25.join("/", "etc", "systemd", "system", `${label}.service`);
|
|
4987
5201
|
}
|
|
4988
5202
|
throw new Error(`Unsupported platform for service install: ${platform}`);
|
|
4989
5203
|
}
|
|
4990
5204
|
function logDir() {
|
|
4991
|
-
return path25.join(
|
|
5205
|
+
return path25.join(task0Home(), "logs");
|
|
4992
5206
|
}
|
|
4993
5207
|
function logPaths() {
|
|
4994
5208
|
const dir = logDir();
|
|
@@ -5002,14 +5216,29 @@ function logPaths() {
|
|
|
5002
5216
|
function escapeXml(s) {
|
|
5003
5217
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
5004
5218
|
}
|
|
5219
|
+
function collectTask0Env() {
|
|
5220
|
+
const out = {};
|
|
5221
|
+
for (const [k, v] of Object.entries(process.env)) {
|
|
5222
|
+
if (k.startsWith("TASK0_") && typeof v === "string") out[k] = v;
|
|
5223
|
+
}
|
|
5224
|
+
return out;
|
|
5225
|
+
}
|
|
5005
5226
|
function renderPlist(opts) {
|
|
5006
5227
|
const programArgs = [opts.node, opts.main, ...opts.args].map((a) => ` <string>${escapeXml(a)}</string>`).join("\n");
|
|
5228
|
+
const envLines = [
|
|
5229
|
+
` <key>PATH</key>`,
|
|
5230
|
+
` <string>/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin</string>`,
|
|
5231
|
+
...Object.entries(opts.task0Env ?? {}).flatMap(([k, v]) => [
|
|
5232
|
+
` <key>${escapeXml(k)}</key>`,
|
|
5233
|
+
` <string>${escapeXml(v)}</string>`
|
|
5234
|
+
])
|
|
5235
|
+
].join("\n");
|
|
5007
5236
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
5008
5237
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
5009
5238
|
<plist version="1.0">
|
|
5010
5239
|
<dict>
|
|
5011
5240
|
<key>Label</key>
|
|
5012
|
-
<string>${
|
|
5241
|
+
<string>${serviceLabel()}</string>
|
|
5013
5242
|
<key>ProgramArguments</key>
|
|
5014
5243
|
<array>
|
|
5015
5244
|
${programArgs}
|
|
@@ -5031,8 +5260,7 @@ ${programArgs}
|
|
|
5031
5260
|
<string>${escapeXml(opts.home)}</string>
|
|
5032
5261
|
<key>EnvironmentVariables</key>
|
|
5033
5262
|
<dict>
|
|
5034
|
-
|
|
5035
|
-
<string>/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin</string>
|
|
5263
|
+
${envLines}
|
|
5036
5264
|
</dict>
|
|
5037
5265
|
</dict>
|
|
5038
5266
|
</plist>
|
|
@@ -5044,7 +5272,7 @@ function domainTarget(scope) {
|
|
|
5044
5272
|
return `gui/${uid}`;
|
|
5045
5273
|
}
|
|
5046
5274
|
function serviceTarget(scope) {
|
|
5047
|
-
return `${domainTarget(scope)}/${
|
|
5275
|
+
return `${domainTarget(scope)}/${serviceLabel()}`;
|
|
5048
5276
|
}
|
|
5049
5277
|
function run2(cmd, args) {
|
|
5050
5278
|
const res = spawnSync6(cmd, args, { encoding: "utf-8" });
|
|
@@ -5063,7 +5291,8 @@ function createLaunchdManager(scope) {
|
|
|
5063
5291
|
args: inv.args,
|
|
5064
5292
|
home: process.env.HOME ?? "/",
|
|
5065
5293
|
out: logs.out,
|
|
5066
|
-
err: logs.err
|
|
5294
|
+
err: logs.err,
|
|
5295
|
+
task0Env: collectTask0Env()
|
|
5067
5296
|
});
|
|
5068
5297
|
fs27.writeFileSync(file, body, { mode: 420 });
|
|
5069
5298
|
const bootstrap = run2("launchctl", ["bootstrap", domainTarget(scope), file]);
|
|
@@ -5141,9 +5370,20 @@ function shellEscape(s) {
|
|
|
5141
5370
|
if (!/[\s"\\$]/.test(s)) return s;
|
|
5142
5371
|
return `"${s.replace(/[\\"]/g, (m) => `\\${m}`)}"`;
|
|
5143
5372
|
}
|
|
5373
|
+
function collectTask0Env2() {
|
|
5374
|
+
const out = {};
|
|
5375
|
+
for (const [k, v] of Object.entries(process.env)) {
|
|
5376
|
+
if (k.startsWith("TASK0_") && typeof v === "string") out[k] = v;
|
|
5377
|
+
}
|
|
5378
|
+
return out;
|
|
5379
|
+
}
|
|
5144
5380
|
function renderUnit(opts) {
|
|
5145
5381
|
const execStart = [opts.node, opts.main, ...opts.args].map(shellEscape).join(" ");
|
|
5146
5382
|
const wantedBy = opts.scope === "user" ? "default.target" : "multi-user.target";
|
|
5383
|
+
const envLines = ["Environment=NODE_ENV=production"];
|
|
5384
|
+
for (const [k, v] of Object.entries(opts.task0Env ?? {})) {
|
|
5385
|
+
envLines.push(`Environment=${k}=${shellEscape(v)}`);
|
|
5386
|
+
}
|
|
5147
5387
|
return `[Unit]
|
|
5148
5388
|
Description=task0 daemon \u2014 central-server bridge
|
|
5149
5389
|
After=network-online.target
|
|
@@ -5156,7 +5396,7 @@ Restart=on-failure
|
|
|
5156
5396
|
RestartSec=5s
|
|
5157
5397
|
StandardOutput=append:${opts.out}
|
|
5158
5398
|
StandardError=append:${opts.err}
|
|
5159
|
-
|
|
5399
|
+
${envLines.join("\n")}
|
|
5160
5400
|
|
|
5161
5401
|
[Install]
|
|
5162
5402
|
WantedBy=${wantedBy}
|
|
@@ -5172,7 +5412,7 @@ function run3(cmd, args) {
|
|
|
5172
5412
|
function createSystemdManager(scope) {
|
|
5173
5413
|
const file = unitPath(scope);
|
|
5174
5414
|
const logs = logPaths();
|
|
5175
|
-
const unitName = `${
|
|
5415
|
+
const unitName = `${serviceLabel()}.service`;
|
|
5176
5416
|
async function install() {
|
|
5177
5417
|
const inv = resolveTask0Invocation();
|
|
5178
5418
|
fs28.mkdirSync(logDir(), { recursive: true });
|
|
@@ -5183,7 +5423,8 @@ function createSystemdManager(scope) {
|
|
|
5183
5423
|
args: inv.args,
|
|
5184
5424
|
out: logs.out,
|
|
5185
5425
|
err: logs.err,
|
|
5186
|
-
scope
|
|
5426
|
+
scope,
|
|
5427
|
+
task0Env: collectTask0Env2()
|
|
5187
5428
|
});
|
|
5188
5429
|
fs28.writeFileSync(file, body, { mode: 420 });
|
|
5189
5430
|
const reload = run3("systemctl", [...scopeFlag(scope), "daemon-reload"]);
|
|
@@ -5271,6 +5512,73 @@ function readCliVersion() {
|
|
|
5271
5512
|
return cached2;
|
|
5272
5513
|
}
|
|
5273
5514
|
|
|
5515
|
+
// src/core/scaffold-default-agents.ts
|
|
5516
|
+
init_node();
|
|
5517
|
+
import fs29 from "fs";
|
|
5518
|
+
import path29 from "path";
|
|
5519
|
+
import yaml9 from "js-yaml";
|
|
5520
|
+
function userAgentsDir() {
|
|
5521
|
+
return path29.join(task0Home(), "agents");
|
|
5522
|
+
}
|
|
5523
|
+
var NAMES = {
|
|
5524
|
+
"claude-code": "Claude Code",
|
|
5525
|
+
codex: "Codex",
|
|
5526
|
+
"cursor-agent": "Cursor"
|
|
5527
|
+
};
|
|
5528
|
+
var DESCRIPTIONS = {
|
|
5529
|
+
"claude-code": "Anthropic Claude Code CLI, launched inside a tmux session against the task workspace.",
|
|
5530
|
+
codex: "OpenAI Codex CLI, launched with `codex exec` against the task workspace.",
|
|
5531
|
+
"cursor-agent": "Cursor agent CLI (`cursor-agent`), launched against the task workspace."
|
|
5532
|
+
};
|
|
5533
|
+
var STABLE_OBJECT_IDS = {
|
|
5534
|
+
"claude-code": "agt_sysCC",
|
|
5535
|
+
codex: "agt_sysCX",
|
|
5536
|
+
"cursor-agent": "agt_sysCR"
|
|
5537
|
+
};
|
|
5538
|
+
function buildDefaultAgentYaml(provider) {
|
|
5539
|
+
const fetchCommand = defaultAgentModelFetchCommand(provider);
|
|
5540
|
+
const fetchFormat = defaultAgentModelOutputFormat(provider);
|
|
5541
|
+
return {
|
|
5542
|
+
object_id: STABLE_OBJECT_IDS[provider],
|
|
5543
|
+
slug: provider,
|
|
5544
|
+
name: NAMES[provider],
|
|
5545
|
+
description: DESCRIPTIONS[provider],
|
|
5546
|
+
kind: "coding",
|
|
5547
|
+
spec: {
|
|
5548
|
+
agent_provider: provider,
|
|
5549
|
+
model: AGENT_DEFAULT_MODEL[provider],
|
|
5550
|
+
effort: AGENT_DEFAULT_EFFORT[provider],
|
|
5551
|
+
available_models: AGENT_MODEL_DEFAULTS[provider].map((m) => ({ id: m.id, label: m.label })),
|
|
5552
|
+
available_efforts: AGENT_EFFORT_DEFAULTS[provider].map((m) => ({ id: m.id, label: m.label })),
|
|
5553
|
+
...fetchCommand ? { model_fetch_command: fetchCommand } : {},
|
|
5554
|
+
...fetchFormat ? { model_fetch_format: fetchFormat } : {}
|
|
5555
|
+
}
|
|
5556
|
+
};
|
|
5557
|
+
}
|
|
5558
|
+
function isDirectoryEmpty(dir) {
|
|
5559
|
+
if (!fs29.existsSync(dir)) return true;
|
|
5560
|
+
try {
|
|
5561
|
+
return fs29.readdirSync(dir).length === 0;
|
|
5562
|
+
} catch {
|
|
5563
|
+
return true;
|
|
5564
|
+
}
|
|
5565
|
+
}
|
|
5566
|
+
function scaffoldDefaultAgentsIfEmpty() {
|
|
5567
|
+
const dir = userAgentsDir();
|
|
5568
|
+
if (!isDirectoryEmpty(dir)) {
|
|
5569
|
+
return { scaffolded: false, written: [] };
|
|
5570
|
+
}
|
|
5571
|
+
fs29.mkdirSync(dir, { recursive: true });
|
|
5572
|
+
const written = [];
|
|
5573
|
+
for (const provider of AGENT_PROVIDERS) {
|
|
5574
|
+
const file = path29.join(dir, `${provider}.yml`);
|
|
5575
|
+
const body = yaml9.dump(buildDefaultAgentYaml(provider), { lineWidth: 100 });
|
|
5576
|
+
fs29.writeFileSync(file, body, "utf-8");
|
|
5577
|
+
written.push(file);
|
|
5578
|
+
}
|
|
5579
|
+
return { scaffolded: true, written };
|
|
5580
|
+
}
|
|
5581
|
+
|
|
5274
5582
|
// src/commands/daemon.ts
|
|
5275
5583
|
async function dispatchRpc(ws, id, method, params) {
|
|
5276
5584
|
const handler = rpcHandlers[method];
|
|
@@ -5331,6 +5639,41 @@ function pushManifest(ws) {
|
|
|
5331
5639
|
const manifest = buildManifest();
|
|
5332
5640
|
ws.send(JSON.stringify(manifest));
|
|
5333
5641
|
}
|
|
5642
|
+
var AGENT_PROVIDER_BINARIES = {
|
|
5643
|
+
"claude-code": "claude",
|
|
5644
|
+
codex: "codex",
|
|
5645
|
+
"cursor-agent": "cursor-agent"
|
|
5646
|
+
};
|
|
5647
|
+
function detectAgentProvider(provider) {
|
|
5648
|
+
const binary = AGENT_PROVIDER_BINARIES[provider];
|
|
5649
|
+
let resolvedPath;
|
|
5650
|
+
try {
|
|
5651
|
+
resolvedPath = execFileSync2("which", [binary], {
|
|
5652
|
+
encoding: "utf-8",
|
|
5653
|
+
timeout: 2e3,
|
|
5654
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
5655
|
+
}).trim();
|
|
5656
|
+
} catch {
|
|
5657
|
+
return null;
|
|
5658
|
+
}
|
|
5659
|
+
if (!resolvedPath) return null;
|
|
5660
|
+
let version = null;
|
|
5661
|
+
try {
|
|
5662
|
+
version = execFileSync2(binary, ["--version"], {
|
|
5663
|
+
encoding: "utf-8",
|
|
5664
|
+
timeout: 3e3,
|
|
5665
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
5666
|
+
}).trim();
|
|
5667
|
+
} catch {
|
|
5668
|
+
version = null;
|
|
5669
|
+
}
|
|
5670
|
+
return { agent_provider: provider, path: resolvedPath, version };
|
|
5671
|
+
}
|
|
5672
|
+
function detectInstalledAgentProviders() {
|
|
5673
|
+
return AGENT_PROVIDERS.map(detectAgentProvider).filter(
|
|
5674
|
+
(entry) => entry !== null
|
|
5675
|
+
);
|
|
5676
|
+
}
|
|
5334
5677
|
function fail6(message, code = 1) {
|
|
5335
5678
|
console.error(chalk19.red(message));
|
|
5336
5679
|
process.exit(code);
|
|
@@ -5401,7 +5744,7 @@ daemonCmd.command("register").description("Register this host with a central ser
|
|
|
5401
5744
|
throw error2;
|
|
5402
5745
|
}
|
|
5403
5746
|
const body = {
|
|
5404
|
-
hostname:
|
|
5747
|
+
hostname: os6.hostname(),
|
|
5405
5748
|
platform: process.platform,
|
|
5406
5749
|
name: opts.name
|
|
5407
5750
|
};
|
|
@@ -5493,6 +5836,12 @@ daemonCmd.command("stop").description("Stop the autostart service via launchctl
|
|
|
5493
5836
|
});
|
|
5494
5837
|
daemonCmd.command("run").description("Run the daemon WebSocket loop in foreground (invoked by the service unit; useful for debugging)").action(async () => {
|
|
5495
5838
|
const identity = loadRequiredIdentity();
|
|
5839
|
+
const scaffold = scaffoldDefaultAgentsIfEmpty();
|
|
5840
|
+
if (scaffold.scaffolded) {
|
|
5841
|
+
console.log(
|
|
5842
|
+
chalk19.green(`Scaffolded ${scaffold.written.length} default agent yml(s) under ~/.task0/agents/`)
|
|
5843
|
+
);
|
|
5844
|
+
}
|
|
5496
5845
|
const wsUrl = identity.server_url.replace(/^http/, "ws").replace(/\/$/, "") + "/ws/daemon";
|
|
5497
5846
|
console.log(chalk19.green(`Starting daemon ${identity.daemon_id} \u2192 ${wsUrl}`));
|
|
5498
5847
|
let reconnectDelay = 1e3;
|
|
@@ -5506,12 +5855,23 @@ daemonCmd.command("run").description("Run the daemon WebSocket loop in foregroun
|
|
|
5506
5855
|
ws.on("open", () => {
|
|
5507
5856
|
reconnectDelay = 1e3;
|
|
5508
5857
|
console.log(chalk19.green(`[${(/* @__PURE__ */ new Date()).toISOString()}] connected`));
|
|
5858
|
+
const installedAgentProviders = detectInstalledAgentProviders();
|
|
5859
|
+
if (installedAgentProviders.length > 0) {
|
|
5860
|
+
console.log(
|
|
5861
|
+
chalk19.dim(
|
|
5862
|
+
`detected agent providers: ${installedAgentProviders.map((p) => `${p.agent_provider}${p.version ? ` (${p.version})` : ""}`).join(", ")}`
|
|
5863
|
+
)
|
|
5864
|
+
);
|
|
5865
|
+
} else {
|
|
5866
|
+
console.log(chalk19.yellow("no agent providers detected on PATH (claude / codex / cursor-agent)"));
|
|
5867
|
+
}
|
|
5509
5868
|
const hello = {
|
|
5510
5869
|
type: "hello",
|
|
5511
5870
|
daemon_id: identity.daemon_id,
|
|
5512
5871
|
version: readCliVersion(),
|
|
5513
5872
|
hostname: identity.hostname,
|
|
5514
|
-
platform: identity.platform
|
|
5873
|
+
platform: identity.platform,
|
|
5874
|
+
installed_agent_providers: installedAgentProviders
|
|
5515
5875
|
};
|
|
5516
5876
|
ws.send(JSON.stringify(hello));
|
|
5517
5877
|
const manifest = buildManifest();
|
|
@@ -5688,16 +6048,15 @@ daemonCmd.command("logout").description("Stop and uninstall the autostart servic
|
|
|
5688
6048
|
// src/commands/user.ts
|
|
5689
6049
|
import { Command as Command20 } from "commander";
|
|
5690
6050
|
import chalk20 from "chalk";
|
|
5691
|
-
var DEFAULT_BASE = (process.env.TASK0_API_URL || "http://127.0.0.1:4318").replace(/\/$/, "");
|
|
5692
6051
|
function serverBase2() {
|
|
5693
|
-
return
|
|
6052
|
+
return (process.env.TASK0_API_URL || "http://127.0.0.1:4318").replace(/\/$/, "");
|
|
5694
6053
|
}
|
|
5695
6054
|
function fail7(message, code = 1) {
|
|
5696
6055
|
console.error(chalk20.red(message));
|
|
5697
6056
|
process.exit(code);
|
|
5698
6057
|
}
|
|
5699
|
-
async function callServer(
|
|
5700
|
-
const url = `${serverBase2()}${
|
|
6058
|
+
async function callServer(path31, init = {}) {
|
|
6059
|
+
const url = `${serverBase2()}${path31}`;
|
|
5701
6060
|
let auth;
|
|
5702
6061
|
try {
|
|
5703
6062
|
auth = adminAuthHeader();
|
|
@@ -6200,6 +6559,160 @@ automation.command("runs <id>").description("List recent runs for an automation"
|
|
|
6200
6559
|
for (const run4 of automation_runs) printRun(run4);
|
|
6201
6560
|
});
|
|
6202
6561
|
|
|
6562
|
+
// src/commands/profile.ts
|
|
6563
|
+
import fs30 from "fs";
|
|
6564
|
+
import os7 from "os";
|
|
6565
|
+
import path30 from "path";
|
|
6566
|
+
import { Command as Command23 } from "commander";
|
|
6567
|
+
import chalk23 from "chalk";
|
|
6568
|
+
var VALID_NAME = /^[a-zA-Z0-9_-]+$/;
|
|
6569
|
+
function fail9(msg) {
|
|
6570
|
+
console.error(chalk23.red(msg));
|
|
6571
|
+
process.exit(1);
|
|
6572
|
+
}
|
|
6573
|
+
function legacyTask0Home() {
|
|
6574
|
+
return path30.join(os7.homedir(), ".task0");
|
|
6575
|
+
}
|
|
6576
|
+
function readDaemonAt(home) {
|
|
6577
|
+
const file = path30.join(home, "daemon.json");
|
|
6578
|
+
if (!fs30.existsSync(file)) return null;
|
|
6579
|
+
try {
|
|
6580
|
+
return JSON.parse(fs30.readFileSync(file, "utf-8"));
|
|
6581
|
+
} catch {
|
|
6582
|
+
return null;
|
|
6583
|
+
}
|
|
6584
|
+
}
|
|
6585
|
+
var profile2 = new Command23("profile").description("Manage named CLI profiles (each isolates TASK0_HOME)");
|
|
6586
|
+
profile2.command("list").description("List configured profiles").action(() => {
|
|
6587
|
+
const profiles = listProfiles();
|
|
6588
|
+
const current = getCurrentProfileName();
|
|
6589
|
+
const names = Object.keys(profiles);
|
|
6590
|
+
if (names.length === 0) {
|
|
6591
|
+
console.log("No profiles configured. Add one with `task0 profile add <name> --task0-home <path>`.");
|
|
6592
|
+
console.log(chalk23.dim(`(currently running in legacy mode \u2014 TASK0_HOME=${process.env.TASK0_HOME ?? legacyTask0Home()})`));
|
|
6593
|
+
return;
|
|
6594
|
+
}
|
|
6595
|
+
for (const name of names) {
|
|
6596
|
+
const entry = profiles[name];
|
|
6597
|
+
const marker = name === current ? chalk23.green("\u25CF ") : " ";
|
|
6598
|
+
const url = entry.api_url ? chalk23.dim(` \u2192 ${entry.api_url}`) : "";
|
|
6599
|
+
console.log(`${marker}${name} ${chalk23.dim(entry.task0_home)}${url}`);
|
|
6600
|
+
}
|
|
6601
|
+
if (!current) {
|
|
6602
|
+
console.log(chalk23.dim("\nNo current profile selected. Use `task0 profile use <name>` to activate one."));
|
|
6603
|
+
}
|
|
6604
|
+
});
|
|
6605
|
+
profile2.command("add <name>").description("Register a new profile").requiredOption("--task0-home <path>", "Directory to hold this profile's daemon/admin state (will be created if missing)").option("--api-url <url>", "API server URL the CLI should call when this profile is active").action((name, opts) => {
|
|
6606
|
+
if (!VALID_NAME.test(name)) {
|
|
6607
|
+
fail9(`Invalid profile name "${name}". Must match ${VALID_NAME}.`);
|
|
6608
|
+
}
|
|
6609
|
+
const existing = getProfile(name);
|
|
6610
|
+
if (existing) {
|
|
6611
|
+
fail9(`Profile "${name}" already exists.`);
|
|
6612
|
+
}
|
|
6613
|
+
const absHome = path30.resolve(opts.task0Home);
|
|
6614
|
+
const profiles = listProfiles();
|
|
6615
|
+
for (const [otherName, entry2] of Object.entries(profiles)) {
|
|
6616
|
+
if (path30.resolve(entry2.task0_home) === absHome) {
|
|
6617
|
+
fail9(`Profile "${otherName}" already uses task0_home "${absHome}". Two profiles cannot share a task0_home (service labels collide).`);
|
|
6618
|
+
}
|
|
6619
|
+
}
|
|
6620
|
+
const parent = path30.dirname(absHome);
|
|
6621
|
+
if (!fs30.existsSync(parent)) {
|
|
6622
|
+
fail9(`Parent directory does not exist: ${parent}`);
|
|
6623
|
+
}
|
|
6624
|
+
try {
|
|
6625
|
+
fs30.accessSync(parent, fs30.constants.W_OK);
|
|
6626
|
+
} catch {
|
|
6627
|
+
fail9(`Parent directory is not writable: ${parent}`);
|
|
6628
|
+
}
|
|
6629
|
+
const isFirstAdd = Object.keys(profiles).length === 0;
|
|
6630
|
+
if (isFirstAdd && name !== "default") {
|
|
6631
|
+
const legacyHome = legacyTask0Home();
|
|
6632
|
+
const legacy = readDaemonAt(legacyHome);
|
|
6633
|
+
if (legacy) {
|
|
6634
|
+
addProfile("default", {
|
|
6635
|
+
task0_home: legacyHome,
|
|
6636
|
+
...legacy.server_url ? { api_url: legacy.server_url } : {}
|
|
6637
|
+
});
|
|
6638
|
+
console.log(chalk23.dim(`Auto-imported existing ~/.task0 state as profile "default" (use \`task0 profile use default\` to keep using it).`));
|
|
6639
|
+
}
|
|
6640
|
+
}
|
|
6641
|
+
const entry = {
|
|
6642
|
+
task0_home: absHome,
|
|
6643
|
+
...opts.apiUrl ? { api_url: opts.apiUrl } : {}
|
|
6644
|
+
};
|
|
6645
|
+
addProfile(name, entry);
|
|
6646
|
+
fs30.mkdirSync(absHome, { recursive: true });
|
|
6647
|
+
const adopted = readDaemonAt(absHome);
|
|
6648
|
+
if (adopted) {
|
|
6649
|
+
console.log(chalk23.yellow(`warn: ${absHome} already contains daemon state. Profile "${name}" will adopt it.`));
|
|
6650
|
+
}
|
|
6651
|
+
console.log(chalk23.green(`Added profile "${name}"`));
|
|
6652
|
+
console.log(` task0_home: ${absHome}`);
|
|
6653
|
+
if (entry.api_url) console.log(` api_url: ${entry.api_url}`);
|
|
6654
|
+
console.log(chalk23.dim(`Use \`task0 profile use ${name}\` to activate.`));
|
|
6655
|
+
});
|
|
6656
|
+
profile2.command("remove <name>").description("Remove a profile entry (does not delete the task0_home directory)").option("-f, --force", "Allow removing the current profile (clears current_profile)").action((name, opts) => {
|
|
6657
|
+
const entry = getProfile(name);
|
|
6658
|
+
if (!entry) {
|
|
6659
|
+
fail9(`Profile "${name}" not found.`);
|
|
6660
|
+
}
|
|
6661
|
+
const current = getCurrentProfileName();
|
|
6662
|
+
if (current === name && !opts.force) {
|
|
6663
|
+
fail9(`Profile "${name}" is current. Re-run with --force to remove it (this clears current_profile).`);
|
|
6664
|
+
}
|
|
6665
|
+
removeProfile(name);
|
|
6666
|
+
console.log(chalk23.green(`Removed profile "${name}".`));
|
|
6667
|
+
if (current === name) {
|
|
6668
|
+
console.log(chalk23.dim("current_profile cleared. Use `task0 profile use <name>` to pick another."));
|
|
6669
|
+
}
|
|
6670
|
+
console.log(chalk23.dim(`Note: ${entry.task0_home} was not deleted.`));
|
|
6671
|
+
});
|
|
6672
|
+
profile2.command("use <name>").description("Set the current profile").action((name) => {
|
|
6673
|
+
const entry = getProfile(name);
|
|
6674
|
+
if (!entry) {
|
|
6675
|
+
const names = Object.keys(listProfiles());
|
|
6676
|
+
fail9(`Profile "${name}" not found. Available: ${names.length > 0 ? names.join(", ") : "(none)"}.`);
|
|
6677
|
+
}
|
|
6678
|
+
setCurrentProfile(name);
|
|
6679
|
+
console.log(chalk23.green(`Now using profile "${name}".`));
|
|
6680
|
+
});
|
|
6681
|
+
profile2.command("current").description("Print the current profile name (exits non-zero if none)").action(() => {
|
|
6682
|
+
const current = getCurrentProfileName();
|
|
6683
|
+
if (!current) {
|
|
6684
|
+
console.error(chalk23.dim("No current profile. Set one with `task0 profile use <name>`."));
|
|
6685
|
+
process.exit(1);
|
|
6686
|
+
}
|
|
6687
|
+
console.log(current);
|
|
6688
|
+
});
|
|
6689
|
+
profile2.command("show [name]").description("Show a profile's configuration and detect drift vs daemon.json").action((name) => {
|
|
6690
|
+
const target = name ?? getCurrentProfileName();
|
|
6691
|
+
if (!target) {
|
|
6692
|
+
fail9("No profile name given and no current_profile set. Pass a name or run `task0 profile use <name>` first.");
|
|
6693
|
+
}
|
|
6694
|
+
const entry = getProfile(target);
|
|
6695
|
+
if (!entry) {
|
|
6696
|
+
fail9(`Profile "${target}" not found.`);
|
|
6697
|
+
}
|
|
6698
|
+
const current = getCurrentProfileName();
|
|
6699
|
+
console.log(`${chalk23.bold(target)}${current === target ? chalk23.green(" (current)") : ""}`);
|
|
6700
|
+
console.log(` task0_home: ${entry.task0_home}`);
|
|
6701
|
+
console.log(` api_url: ${entry.api_url ?? chalk23.dim("(unset)")}`);
|
|
6702
|
+
console.log(` config: ${configFilePath()}`);
|
|
6703
|
+
const identity = readDaemonAt(entry.task0_home);
|
|
6704
|
+
if (!identity) {
|
|
6705
|
+
console.log(chalk23.dim(" daemon.json: (not registered yet)"));
|
|
6706
|
+
return;
|
|
6707
|
+
}
|
|
6708
|
+
console.log(` daemon_id: ${identity.daemon_id ?? chalk23.dim("(missing)")}`);
|
|
6709
|
+
console.log(` daemon.server_url: ${identity.server_url ?? chalk23.dim("(missing)")}`);
|
|
6710
|
+
if (entry.api_url && identity.server_url && entry.api_url !== identity.server_url) {
|
|
6711
|
+
console.log(chalk23.yellow(` warn: profile.api_url and daemon.json.server_url disagree.`));
|
|
6712
|
+
console.log(chalk23.dim(` Re-register the daemon to align: task0 --profile ${target} daemon register --server ${entry.api_url}`));
|
|
6713
|
+
}
|
|
6714
|
+
});
|
|
6715
|
+
|
|
6203
6716
|
// src/core/error-capture.ts
|
|
6204
6717
|
init_node();
|
|
6205
6718
|
var DEFAULT_KEEP = 50;
|
|
@@ -6298,7 +6811,7 @@ function captureTopLevel(err, options) {
|
|
|
6298
6811
|
|
|
6299
6812
|
// src/main.ts
|
|
6300
6813
|
var TASK0_VERSION = readCliVersion();
|
|
6301
|
-
var program = new
|
|
6814
|
+
var program = new Command24().name("task0").description("Task-centric control layer for agent workflow").version(TASK0_VERSION).option("--profile <name>", "Use a named profile from ~/.config/task0/config.yml (overrides current_profile and TASK0_HOME)");
|
|
6302
6815
|
program.addCommand(source);
|
|
6303
6816
|
program.addCommand(project);
|
|
6304
6817
|
program.addCommand(task);
|
|
@@ -6316,7 +6829,17 @@ program.addCommand(daemonCmd);
|
|
|
6316
6829
|
program.addCommand(userCmd);
|
|
6317
6830
|
program.addCommand(error);
|
|
6318
6831
|
program.addCommand(automation);
|
|
6832
|
+
program.addCommand(profile2);
|
|
6319
6833
|
async function main() {
|
|
6834
|
+
try {
|
|
6835
|
+
activateProfile(process.argv);
|
|
6836
|
+
} catch (err) {
|
|
6837
|
+
if (err instanceof ProfileNotFoundError) {
|
|
6838
|
+
console.error(chalk24.red(err.message));
|
|
6839
|
+
process.exit(1);
|
|
6840
|
+
}
|
|
6841
|
+
throw err;
|
|
6842
|
+
}
|
|
6320
6843
|
installErrorCapture({ version: TASK0_VERSION });
|
|
6321
6844
|
try {
|
|
6322
6845
|
await program.parseAsync(process.argv);
|