@task0/cli 0.5.0 → 0.8.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 +1134 -474
- 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 || {};
|
|
@@ -492,6 +492,90 @@ var init_task_state = __esm({
|
|
|
492
492
|
}
|
|
493
493
|
});
|
|
494
494
|
|
|
495
|
+
// ../../packages/shared/dist/node/paths.js
|
|
496
|
+
import fs5 from "fs";
|
|
497
|
+
import os2 from "os";
|
|
498
|
+
import path5 from "path";
|
|
499
|
+
function task0Home() {
|
|
500
|
+
const override = process.env.TASK0_HOME;
|
|
501
|
+
if (override && override.length > 0)
|
|
502
|
+
return override;
|
|
503
|
+
return path5.join(os2.homedir(), ".task0");
|
|
504
|
+
}
|
|
505
|
+
function profilesRoot() {
|
|
506
|
+
return path5.join(task0Home(), "profiles");
|
|
507
|
+
}
|
|
508
|
+
function currentProfileName() {
|
|
509
|
+
const fromEnv = process.env.TASK0_PROFILE?.trim();
|
|
510
|
+
if (fromEnv && PROFILE_NAME_RE.test(fromEnv))
|
|
511
|
+
return fromEnv;
|
|
512
|
+
const fromFile = readCurrentProfileFile();
|
|
513
|
+
if (fromFile && PROFILE_NAME_RE.test(fromFile))
|
|
514
|
+
return fromFile;
|
|
515
|
+
return DEFAULT_PROFILE_NAME;
|
|
516
|
+
}
|
|
517
|
+
function readCurrentProfileFile() {
|
|
518
|
+
const file = path5.join(profilesRoot(), CURRENT_PROFILE_FILE);
|
|
519
|
+
if (!fs5.existsSync(file))
|
|
520
|
+
return null;
|
|
521
|
+
try {
|
|
522
|
+
return fs5.readFileSync(file, "utf-8").trim() || null;
|
|
523
|
+
} catch {
|
|
524
|
+
return null;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
function currentProfileFilePath() {
|
|
528
|
+
return path5.join(profilesRoot(), CURRENT_PROFILE_FILE);
|
|
529
|
+
}
|
|
530
|
+
function writeCurrentProfile(name) {
|
|
531
|
+
fs5.mkdirSync(profilesRoot(), { recursive: true });
|
|
532
|
+
const file = currentProfileFilePath();
|
|
533
|
+
if (name === null) {
|
|
534
|
+
if (fs5.existsSync(file))
|
|
535
|
+
fs5.unlinkSync(file);
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
if (!PROFILE_NAME_RE.test(name)) {
|
|
539
|
+
throw new Error(`Invalid profile name "${name}". Must match ${PROFILE_NAME_RE}.`);
|
|
540
|
+
}
|
|
541
|
+
fs5.writeFileSync(file, name + "\n", "utf-8");
|
|
542
|
+
}
|
|
543
|
+
function profileDir(name) {
|
|
544
|
+
const resolved = name ?? currentProfileName();
|
|
545
|
+
if (!PROFILE_NAME_RE.test(resolved)) {
|
|
546
|
+
throw new Error(`Invalid profile name "${resolved}". Must match ${PROFILE_NAME_RE}.`);
|
|
547
|
+
}
|
|
548
|
+
return path5.join(profilesRoot(), resolved);
|
|
549
|
+
}
|
|
550
|
+
function listProfileNames() {
|
|
551
|
+
const root = profilesRoot();
|
|
552
|
+
if (!fs5.existsSync(root))
|
|
553
|
+
return [];
|
|
554
|
+
try {
|
|
555
|
+
return fs5.readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && PROFILE_NAME_RE.test(entry.name)).map((entry) => entry.name).sort();
|
|
556
|
+
} catch {
|
|
557
|
+
return [];
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
function isValidProfileName(name) {
|
|
561
|
+
return PROFILE_NAME_RE.test(name);
|
|
562
|
+
}
|
|
563
|
+
function serverWorkingDir() {
|
|
564
|
+
const fromEnv = process.env.TASK0_SERVER_DIR?.trim();
|
|
565
|
+
if (fromEnv)
|
|
566
|
+
return fromEnv;
|
|
567
|
+
return path5.join(task0Home(), "server");
|
|
568
|
+
}
|
|
569
|
+
var DEFAULT_PROFILE_NAME, CURRENT_PROFILE_FILE, PROFILE_NAME_RE;
|
|
570
|
+
var init_paths = __esm({
|
|
571
|
+
"../../packages/shared/dist/node/paths.js"() {
|
|
572
|
+
"use strict";
|
|
573
|
+
DEFAULT_PROFILE_NAME = "default";
|
|
574
|
+
CURRENT_PROFILE_FILE = ".current";
|
|
575
|
+
PROFILE_NAME_RE = /^[a-zA-Z0-9_-]+$/;
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
|
|
495
579
|
// ../../packages/shared/dist/types/error-report.js
|
|
496
580
|
var ERROR_REPORT_SCHEMA_VERSION;
|
|
497
581
|
var init_error_report = __esm({
|
|
@@ -583,19 +667,12 @@ var init_redact = __esm({
|
|
|
583
667
|
});
|
|
584
668
|
|
|
585
669
|
// ../../packages/shared/dist/node/error-reports.js
|
|
586
|
-
import
|
|
587
|
-
import
|
|
588
|
-
import path10 from "path";
|
|
670
|
+
import fs6 from "fs";
|
|
671
|
+
import path6 from "path";
|
|
589
672
|
import { spawnSync } from "child_process";
|
|
590
673
|
import crypto from "crypto";
|
|
591
|
-
function task0Home() {
|
|
592
|
-
const override = process.env.TASK0_HOME;
|
|
593
|
-
if (override && override.length > 0)
|
|
594
|
-
return override;
|
|
595
|
-
return path10.join(os4.homedir(), ".task0");
|
|
596
|
-
}
|
|
597
674
|
function errorsRoot() {
|
|
598
|
-
return
|
|
675
|
+
return path6.join(profileDir(), "errors");
|
|
599
676
|
}
|
|
600
677
|
function createErrorReportId() {
|
|
601
678
|
return `err_${crypto.randomBytes(4).toString("hex")}`;
|
|
@@ -696,13 +773,13 @@ function buildErrorReport(input) {
|
|
|
696
773
|
function writeErrorReportSync(report, rootOverride) {
|
|
697
774
|
const root = rootOverride ?? errorsRoot();
|
|
698
775
|
const dirName = errorReportDirName(new Date(report.captured_at), report.id);
|
|
699
|
-
const dir =
|
|
700
|
-
|
|
701
|
-
const finalPath =
|
|
702
|
-
const tmpPath =
|
|
776
|
+
const dir = path6.join(root, dirName);
|
|
777
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
778
|
+
const finalPath = path6.join(dir, REPORT_FILENAME);
|
|
779
|
+
const tmpPath = path6.join(dir, TMP_FILENAME);
|
|
703
780
|
const json = JSON.stringify(report, null, 2);
|
|
704
|
-
|
|
705
|
-
|
|
781
|
+
fs6.writeFileSync(tmpPath, json, "utf-8");
|
|
782
|
+
fs6.renameSync(tmpPath, finalPath);
|
|
706
783
|
return { dir, path: finalPath };
|
|
707
784
|
}
|
|
708
785
|
function parseReportDir(name) {
|
|
@@ -719,7 +796,7 @@ function listErrorReports(rootOverride) {
|
|
|
719
796
|
};
|
|
720
797
|
let entries;
|
|
721
798
|
try {
|
|
722
|
-
entries =
|
|
799
|
+
entries = fs6.readdirSync(root, { withFileTypes: true });
|
|
723
800
|
} catch (err) {
|
|
724
801
|
if (err.code === "ENOENT")
|
|
725
802
|
return result;
|
|
@@ -731,11 +808,11 @@ function listErrorReports(rootOverride) {
|
|
|
731
808
|
const parsed = parseReportDir(entry.name);
|
|
732
809
|
if (!parsed)
|
|
733
810
|
continue;
|
|
734
|
-
const dir =
|
|
735
|
-
const file =
|
|
811
|
+
const dir = path6.join(root, entry.name);
|
|
812
|
+
const file = path6.join(dir, REPORT_FILENAME);
|
|
736
813
|
let raw;
|
|
737
814
|
try {
|
|
738
|
-
raw =
|
|
815
|
+
raw = fs6.readFileSync(file, "utf-8");
|
|
739
816
|
} catch {
|
|
740
817
|
result.skipped.unreadable += 1;
|
|
741
818
|
continue;
|
|
@@ -753,7 +830,7 @@ function listErrorReports(rootOverride) {
|
|
|
753
830
|
}
|
|
754
831
|
let size = 0;
|
|
755
832
|
try {
|
|
756
|
-
size =
|
|
833
|
+
size = fs6.statSync(file).size;
|
|
757
834
|
} catch {
|
|
758
835
|
}
|
|
759
836
|
result.reports.push({
|
|
@@ -788,7 +865,7 @@ function resolveErrorReport(query, rootOverride) {
|
|
|
788
865
|
return { kind: "miss", query };
|
|
789
866
|
}
|
|
790
867
|
function readErrorReport(summary) {
|
|
791
|
-
const raw =
|
|
868
|
+
const raw = fs6.readFileSync(summary.path, "utf-8");
|
|
792
869
|
return JSON.parse(raw);
|
|
793
870
|
}
|
|
794
871
|
function pruneErrorReports(opts, rootOverride) {
|
|
@@ -826,7 +903,7 @@ function pruneErrorReports(opts, rootOverride) {
|
|
|
826
903
|
}
|
|
827
904
|
function removeReportDir(dir) {
|
|
828
905
|
try {
|
|
829
|
-
|
|
906
|
+
fs6.rmSync(dir, { recursive: true, force: true });
|
|
830
907
|
return true;
|
|
831
908
|
} catch {
|
|
832
909
|
return false;
|
|
@@ -837,6 +914,7 @@ var init_error_reports = __esm({
|
|
|
837
914
|
"../../packages/shared/dist/node/error-reports.js"() {
|
|
838
915
|
"use strict";
|
|
839
916
|
init_error_report();
|
|
917
|
+
init_paths();
|
|
840
918
|
init_redact();
|
|
841
919
|
REPORT_FILENAME = "report.json";
|
|
842
920
|
TMP_FILENAME = "report.json.tmp";
|
|
@@ -844,9 +922,9 @@ var init_error_reports = __esm({
|
|
|
844
922
|
});
|
|
845
923
|
|
|
846
924
|
// ../../packages/shared/dist/node/file-lock.js
|
|
847
|
-
import
|
|
848
|
-
import
|
|
849
|
-
import
|
|
925
|
+
import fs7 from "fs";
|
|
926
|
+
import os3 from "os";
|
|
927
|
+
import path7 from "path";
|
|
850
928
|
var init_file_lock = __esm({
|
|
851
929
|
"../../packages/shared/dist/node/file-lock.js"() {
|
|
852
930
|
"use strict";
|
|
@@ -862,9 +940,9 @@ var init_tmux = __esm({
|
|
|
862
940
|
});
|
|
863
941
|
|
|
864
942
|
// ../../packages/shared/dist/node/agent-skills.js
|
|
865
|
-
import
|
|
866
|
-
import
|
|
867
|
-
import
|
|
943
|
+
import fs8 from "fs";
|
|
944
|
+
import path8 from "path";
|
|
945
|
+
import yaml3 from "js-yaml";
|
|
868
946
|
function isRecord(value) {
|
|
869
947
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
870
948
|
}
|
|
@@ -873,7 +951,7 @@ function extractFrontmatter(raw) {
|
|
|
873
951
|
if (!match)
|
|
874
952
|
return null;
|
|
875
953
|
try {
|
|
876
|
-
const parsed =
|
|
954
|
+
const parsed = yaml3.load(match[1]);
|
|
877
955
|
return isRecord(parsed) ? parsed : null;
|
|
878
956
|
} catch {
|
|
879
957
|
return null;
|
|
@@ -881,9 +959,9 @@ function extractFrontmatter(raw) {
|
|
|
881
959
|
}
|
|
882
960
|
function readTextIfExists(filePath) {
|
|
883
961
|
try {
|
|
884
|
-
if (!
|
|
962
|
+
if (!fs8.existsSync(filePath))
|
|
885
963
|
return null;
|
|
886
|
-
return
|
|
964
|
+
return fs8.readFileSync(filePath, "utf-8");
|
|
887
965
|
} catch {
|
|
888
966
|
return null;
|
|
889
967
|
}
|
|
@@ -891,13 +969,13 @@ function readTextIfExists(filePath) {
|
|
|
891
969
|
function getSymlinkInfo(...candidatePaths) {
|
|
892
970
|
for (const candidatePath of candidatePaths) {
|
|
893
971
|
try {
|
|
894
|
-
const stat =
|
|
972
|
+
const stat = fs8.lstatSync(candidatePath);
|
|
895
973
|
if (!stat.isSymbolicLink())
|
|
896
974
|
continue;
|
|
897
|
-
const target =
|
|
975
|
+
const target = fs8.readlinkSync(candidatePath);
|
|
898
976
|
return {
|
|
899
977
|
isSymlink: true,
|
|
900
|
-
symlinkTarget:
|
|
978
|
+
symlinkTarget: path8.isAbsolute(target) ? target : path8.resolve(path8.dirname(candidatePath), target)
|
|
901
979
|
};
|
|
902
980
|
} catch {
|
|
903
981
|
}
|
|
@@ -930,7 +1008,7 @@ function normalizeBoolean(value) {
|
|
|
930
1008
|
function sortSkills(skills) {
|
|
931
1009
|
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
1010
|
}
|
|
933
|
-
function pushInstructionIfExists(skills, agent2, scope, filePath, name =
|
|
1011
|
+
function pushInstructionIfExists(skills, agent2, scope, filePath, name = path8.basename(filePath), description2 = "", kind = "instruction") {
|
|
934
1012
|
const raw = readTextIfExists(filePath);
|
|
935
1013
|
if (raw === null)
|
|
936
1014
|
return;
|
|
@@ -947,17 +1025,17 @@ function pushInstructionIfExists(skills, agent2, scope, filePath, name = path12.
|
|
|
947
1025
|
function scanClaudeSkillDir(skills, skillsDir, scope) {
|
|
948
1026
|
let entries = [];
|
|
949
1027
|
try {
|
|
950
|
-
if (!
|
|
1028
|
+
if (!fs8.existsSync(skillsDir))
|
|
951
1029
|
return;
|
|
952
|
-
entries =
|
|
1030
|
+
entries = fs8.readdirSync(skillsDir, { withFileTypes: true });
|
|
953
1031
|
} catch {
|
|
954
1032
|
return;
|
|
955
1033
|
}
|
|
956
1034
|
for (const entry of entries) {
|
|
957
1035
|
if (!entry.isDirectory() && !entry.isSymbolicLink())
|
|
958
1036
|
continue;
|
|
959
|
-
const skillDirPath =
|
|
960
|
-
const skillFilePath =
|
|
1037
|
+
const skillDirPath = path8.join(skillsDir, entry.name);
|
|
1038
|
+
const skillFilePath = path8.join(skillDirPath, "SKILL.md");
|
|
961
1039
|
const raw = readTextIfExists(skillFilePath);
|
|
962
1040
|
if (raw === null)
|
|
963
1041
|
continue;
|
|
@@ -978,7 +1056,7 @@ function scanCursorRuleFile(skills, filePath, scope) {
|
|
|
978
1056
|
if (raw === null)
|
|
979
1057
|
return;
|
|
980
1058
|
const frontmatter = extractFrontmatter(raw);
|
|
981
|
-
const baseName =
|
|
1059
|
+
const baseName = path8.basename(filePath, ".mdc");
|
|
982
1060
|
skills.push({
|
|
983
1061
|
agent: "cursor",
|
|
984
1062
|
scope,
|
|
@@ -994,9 +1072,9 @@ function scanCursorRuleFile(skills, filePath, scope) {
|
|
|
994
1072
|
function scanCursorRulesDir(skills, rulesDir, scope) {
|
|
995
1073
|
let entries = [];
|
|
996
1074
|
try {
|
|
997
|
-
if (!
|
|
1075
|
+
if (!fs8.existsSync(rulesDir))
|
|
998
1076
|
return;
|
|
999
|
-
entries =
|
|
1077
|
+
entries = fs8.readdirSync(rulesDir, { withFileTypes: true });
|
|
1000
1078
|
} catch {
|
|
1001
1079
|
return;
|
|
1002
1080
|
}
|
|
@@ -1005,26 +1083,26 @@ function scanCursorRulesDir(skills, rulesDir, scope) {
|
|
|
1005
1083
|
continue;
|
|
1006
1084
|
if (!entry.isFile() && !entry.isSymbolicLink())
|
|
1007
1085
|
continue;
|
|
1008
|
-
scanCursorRuleFile(skills,
|
|
1086
|
+
scanCursorRuleFile(skills, path8.join(rulesDir, entry.name), scope);
|
|
1009
1087
|
}
|
|
1010
1088
|
}
|
|
1011
1089
|
function getProjectAgentSkills(projectPath) {
|
|
1012
|
-
const absProjectPath =
|
|
1090
|
+
const absProjectPath = path8.resolve(projectPath);
|
|
1013
1091
|
let projectStat;
|
|
1014
1092
|
try {
|
|
1015
|
-
projectStat =
|
|
1093
|
+
projectStat = fs8.statSync(absProjectPath);
|
|
1016
1094
|
} catch {
|
|
1017
1095
|
return [];
|
|
1018
1096
|
}
|
|
1019
1097
|
if (!projectStat.isDirectory())
|
|
1020
1098
|
return [];
|
|
1021
1099
|
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",
|
|
1100
|
+
scanClaudeSkillDir(skills, path8.join(absProjectPath, ".claude", "skills"), "project");
|
|
1101
|
+
pushInstructionIfExists(skills, "claude_code", "project", path8.join(absProjectPath, "CLAUDE.md"));
|
|
1102
|
+
pushInstructionIfExists(skills, "claude_code", "project", path8.join(absProjectPath, "AGENTS.md"));
|
|
1103
|
+
pushInstructionIfExists(skills, "codex", "project", path8.join(absProjectPath, "AGENTS.md"));
|
|
1104
|
+
scanCursorRulesDir(skills, path8.join(absProjectPath, ".cursor", "rules"), "project");
|
|
1105
|
+
pushInstructionIfExists(skills, "cursor", "project", path8.join(absProjectPath, ".cursorrules"), "Legacy Cursor Rules", "", "rule");
|
|
1028
1106
|
return sortSkills(skills);
|
|
1029
1107
|
}
|
|
1030
1108
|
function getGlobalAgentSkills() {
|
|
@@ -1032,8 +1110,8 @@ function getGlobalAgentSkills() {
|
|
|
1032
1110
|
if (!homeDir)
|
|
1033
1111
|
return [];
|
|
1034
1112
|
const skills = [];
|
|
1035
|
-
scanClaudeSkillDir(skills,
|
|
1036
|
-
scanCursorRulesDir(skills,
|
|
1113
|
+
scanClaudeSkillDir(skills, path8.join(homeDir, ".claude", "skills"), "global");
|
|
1114
|
+
scanCursorRulesDir(skills, path8.join(homeDir, ".cursor", "rules"), "global");
|
|
1037
1115
|
return sortSkills(skills);
|
|
1038
1116
|
}
|
|
1039
1117
|
var AGENT_ORDER, SCOPE_ORDER, KIND_ORDER;
|
|
@@ -1065,6 +1143,7 @@ var init_node = __esm({
|
|
|
1065
1143
|
init_scanner();
|
|
1066
1144
|
init_open_questions();
|
|
1067
1145
|
init_task_state();
|
|
1146
|
+
init_paths();
|
|
1068
1147
|
init_error_reports();
|
|
1069
1148
|
init_redact();
|
|
1070
1149
|
init_file_lock();
|
|
@@ -1089,7 +1168,7 @@ __export(task_state_exports, {
|
|
|
1089
1168
|
withTaskYamlLock: () => withTaskYamlLock,
|
|
1090
1169
|
writeTaskYaml: () => writeTaskYaml
|
|
1091
1170
|
});
|
|
1092
|
-
import
|
|
1171
|
+
import fs15 from "fs";
|
|
1093
1172
|
function readWorkflow(taskYml) {
|
|
1094
1173
|
return readTaskWorkflow(taskYml);
|
|
1095
1174
|
}
|
|
@@ -1098,48 +1177,364 @@ async function updateWorkflow(taskYml, patch) {
|
|
|
1098
1177
|
}
|
|
1099
1178
|
function nextArtifactIndex(taskDir, prefix, ext = "md") {
|
|
1100
1179
|
const pattern = new RegExp(`^${prefix}-(\\d+).*\\.${ext}$`);
|
|
1101
|
-
const entries =
|
|
1180
|
+
const entries = fs15.readdirSync(taskDir);
|
|
1102
1181
|
const indices = entries.map((name) => name.match(pattern)?.[1]).filter((v) => v != null).map(Number);
|
|
1103
1182
|
const next = indices.length > 0 ? Math.max(...indices) + 1 : 1;
|
|
1104
1183
|
return String(next).padStart(2, "0");
|
|
1105
1184
|
}
|
|
1106
1185
|
function latestArtifact(taskDir, pattern) {
|
|
1107
|
-
if (!
|
|
1108
|
-
const matches =
|
|
1186
|
+
if (!fs15.existsSync(taskDir)) return null;
|
|
1187
|
+
const matches = fs15.readdirSync(taskDir).filter((name) => pattern.test(name));
|
|
1109
1188
|
if (matches.length === 0) return null;
|
|
1110
1189
|
matches.sort();
|
|
1111
1190
|
return matches[matches.length - 1] || null;
|
|
1112
1191
|
}
|
|
1113
|
-
var init_task_state2 = __esm({
|
|
1114
|
-
"src/core/task-state.ts"() {
|
|
1115
|
-
"use strict";
|
|
1116
|
-
init_node();
|
|
1192
|
+
var init_task_state2 = __esm({
|
|
1193
|
+
"src/core/task-state.ts"() {
|
|
1194
|
+
"use strict";
|
|
1195
|
+
init_node();
|
|
1196
|
+
}
|
|
1197
|
+
});
|
|
1198
|
+
|
|
1199
|
+
// src/main.ts
|
|
1200
|
+
import { Command as Command25 } from "commander";
|
|
1201
|
+
import chalk25 from "chalk";
|
|
1202
|
+
|
|
1203
|
+
// src/core/profile.ts
|
|
1204
|
+
init_node();
|
|
1205
|
+
import fs10 from "fs";
|
|
1206
|
+
|
|
1207
|
+
// src/core/config.ts
|
|
1208
|
+
init_node();
|
|
1209
|
+
import fs9 from "fs";
|
|
1210
|
+
import path9 from "path";
|
|
1211
|
+
import yaml4 from "js-yaml";
|
|
1212
|
+
function configFile() {
|
|
1213
|
+
return path9.join(profileDir(), "config.yml");
|
|
1214
|
+
}
|
|
1215
|
+
function defaultConfig() {
|
|
1216
|
+
return { sources: [] };
|
|
1217
|
+
}
|
|
1218
|
+
function loadConfig() {
|
|
1219
|
+
const file = configFile();
|
|
1220
|
+
if (!fs9.existsSync(file)) return defaultConfig();
|
|
1221
|
+
try {
|
|
1222
|
+
const raw = fs9.readFileSync(file, "utf-8");
|
|
1223
|
+
const data = yaml4.load(raw) ?? {};
|
|
1224
|
+
return {
|
|
1225
|
+
...data,
|
|
1226
|
+
sources: Array.isArray(data?.sources) ? data.sources : []
|
|
1227
|
+
};
|
|
1228
|
+
} catch {
|
|
1229
|
+
return defaultConfig();
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
function saveConfig(config) {
|
|
1233
|
+
const file = configFile();
|
|
1234
|
+
fs9.mkdirSync(path9.dirname(file), { recursive: true });
|
|
1235
|
+
fs9.writeFileSync(file, yaml4.dump(config, { lineWidth: 120 }), "utf-8");
|
|
1236
|
+
}
|
|
1237
|
+
function addSource(entry) {
|
|
1238
|
+
const config = loadConfig();
|
|
1239
|
+
const existing = config.sources.findIndex((s) => s.name === entry.name);
|
|
1240
|
+
if (existing >= 0) {
|
|
1241
|
+
config.sources[existing] = entry;
|
|
1242
|
+
} else {
|
|
1243
|
+
config.sources.push(entry);
|
|
1244
|
+
}
|
|
1245
|
+
saveConfig(config);
|
|
1246
|
+
}
|
|
1247
|
+
function removeSource(name) {
|
|
1248
|
+
const config = loadConfig();
|
|
1249
|
+
const idx = config.sources.findIndex((s) => s.name === name);
|
|
1250
|
+
if (idx < 0) return false;
|
|
1251
|
+
config.sources.splice(idx, 1);
|
|
1252
|
+
saveConfig(config);
|
|
1253
|
+
return true;
|
|
1254
|
+
}
|
|
1255
|
+
function getApiUrl() {
|
|
1256
|
+
return loadConfig().api_url;
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
// src/core/profile.ts
|
|
1260
|
+
var ProfileNotFoundError = class extends Error {
|
|
1261
|
+
constructor(name, available) {
|
|
1262
|
+
const list = available.length > 0 ? available.join(", ") : "(none)";
|
|
1263
|
+
super(`Profile "${name}" not found. Available: ${list}. Run "task0 profile list" to inspect.`);
|
|
1264
|
+
this.name = "ProfileNotFoundError";
|
|
1265
|
+
}
|
|
1266
|
+
};
|
|
1267
|
+
function parseProfileFlag(argv) {
|
|
1268
|
+
for (let i = 2; i < argv.length; i++) {
|
|
1269
|
+
const arg = argv[i];
|
|
1270
|
+
if (arg === "--") return null;
|
|
1271
|
+
if (arg === "--profile") {
|
|
1272
|
+
const next = argv[i + 1];
|
|
1273
|
+
if (typeof next === "string" && !next.startsWith("-")) return next;
|
|
1274
|
+
return null;
|
|
1275
|
+
}
|
|
1276
|
+
if (typeof arg === "string" && arg.startsWith("--profile=")) {
|
|
1277
|
+
const value = arg.slice("--profile=".length);
|
|
1278
|
+
return value.length > 0 ? value : null;
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
return null;
|
|
1282
|
+
}
|
|
1283
|
+
function isProfileSubcommandInvocation(argv) {
|
|
1284
|
+
for (let i = 2; i < argv.length; i++) {
|
|
1285
|
+
const arg = argv[i];
|
|
1286
|
+
if (arg === "--") break;
|
|
1287
|
+
if (typeof arg !== "string") continue;
|
|
1288
|
+
if (arg.startsWith("-")) {
|
|
1289
|
+
if (arg === "--profile" || arg === "-C") i++;
|
|
1290
|
+
continue;
|
|
1291
|
+
}
|
|
1292
|
+
return arg === "profile";
|
|
1293
|
+
}
|
|
1294
|
+
return true;
|
|
1295
|
+
}
|
|
1296
|
+
function ensureDefaultProfile() {
|
|
1297
|
+
fs10.mkdirSync(profileDir(DEFAULT_PROFILE_NAME), { recursive: true });
|
|
1298
|
+
fs10.mkdirSync(profilesRoot(), { recursive: true });
|
|
1299
|
+
}
|
|
1300
|
+
function profileExists(name) {
|
|
1301
|
+
if (!isValidProfileName(name)) return false;
|
|
1302
|
+
try {
|
|
1303
|
+
return fs10.statSync(profileDir(name)).isDirectory();
|
|
1304
|
+
} catch {
|
|
1305
|
+
return false;
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
var activeProfileCache = null;
|
|
1309
|
+
function activateProfile(argv) {
|
|
1310
|
+
ensureDefaultProfile();
|
|
1311
|
+
const flagValue = parseProfileFlag(argv);
|
|
1312
|
+
let name;
|
|
1313
|
+
if (flagValue !== null) {
|
|
1314
|
+
if (!isValidProfileName(flagValue) || !profileExists(flagValue)) {
|
|
1315
|
+
if (!isProfileSubcommandInvocation(argv)) {
|
|
1316
|
+
throw new ProfileNotFoundError(flagValue, listProfileNames());
|
|
1317
|
+
}
|
|
1318
|
+
name = DEFAULT_PROFILE_NAME;
|
|
1319
|
+
} else {
|
|
1320
|
+
name = flagValue;
|
|
1321
|
+
}
|
|
1322
|
+
} else {
|
|
1323
|
+
name = currentProfileNameFromEnvOrFile();
|
|
1324
|
+
if (!profileExists(name)) {
|
|
1325
|
+
if (!isProfileSubcommandInvocation(argv)) {
|
|
1326
|
+
throw new ProfileNotFoundError(name, listProfileNames());
|
|
1327
|
+
}
|
|
1328
|
+
name = DEFAULT_PROFILE_NAME;
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
process.env.TASK0_PROFILE = name;
|
|
1332
|
+
const apiUrl = getApiUrl();
|
|
1333
|
+
activeProfileCache = { name, ...apiUrl ? { apiUrl } : {} };
|
|
1334
|
+
if (apiUrl && !process.env.TASK0_API_URL) {
|
|
1335
|
+
process.env.TASK0_API_URL = apiUrl;
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
function currentProfileNameFromEnvOrFile() {
|
|
1339
|
+
const fromEnv = process.env.TASK0_PROFILE?.trim();
|
|
1340
|
+
if (fromEnv && isValidProfileName(fromEnv)) return fromEnv;
|
|
1341
|
+
try {
|
|
1342
|
+
const file = `${profilesRoot()}/.current`;
|
|
1343
|
+
if (fs10.existsSync(file)) {
|
|
1344
|
+
const v = fs10.readFileSync(file, "utf-8").trim();
|
|
1345
|
+
if (v && isValidProfileName(v)) return v;
|
|
1346
|
+
}
|
|
1347
|
+
} catch {
|
|
1348
|
+
}
|
|
1349
|
+
return DEFAULT_PROFILE_NAME;
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
// src/core/migrate-layout.ts
|
|
1353
|
+
init_node();
|
|
1354
|
+
import fs11 from "fs";
|
|
1355
|
+
import os4 from "os";
|
|
1356
|
+
import path10 from "path";
|
|
1357
|
+
import yaml5 from "js-yaml";
|
|
1358
|
+
var MIGRATION_MARKER = ".migrated-v1";
|
|
1359
|
+
function legacyDaemonJson() {
|
|
1360
|
+
return path10.join(task0Home(), "daemon.json");
|
|
1361
|
+
}
|
|
1362
|
+
function legacyErrorsDir() {
|
|
1363
|
+
return path10.join(task0Home(), "errors");
|
|
1364
|
+
}
|
|
1365
|
+
function legacyAgentRunDir() {
|
|
1366
|
+
return path10.join(task0Home(), "agent-run");
|
|
1367
|
+
}
|
|
1368
|
+
function legacyLogsDir() {
|
|
1369
|
+
return path10.join(task0Home(), "logs");
|
|
1370
|
+
}
|
|
1371
|
+
function legacyServerConfigYml() {
|
|
1372
|
+
return path10.join(task0Home(), "config.yml");
|
|
1373
|
+
}
|
|
1374
|
+
function legacyRegistryDir() {
|
|
1375
|
+
return path10.join(process.env.HOME || process.env.USERPROFILE || os4.homedir(), ".config", "task0");
|
|
1376
|
+
}
|
|
1377
|
+
function legacyRegistryFile() {
|
|
1378
|
+
return path10.join(legacyRegistryDir(), "config.yml");
|
|
1379
|
+
}
|
|
1380
|
+
function migrationMarkerPath() {
|
|
1381
|
+
return path10.join(profilesRoot(), MIGRATION_MARKER);
|
|
1382
|
+
}
|
|
1383
|
+
function safeMv(from, to, steps, opts = {}) {
|
|
1384
|
+
if (!fs11.existsSync(from)) return false;
|
|
1385
|
+
if (fs11.existsSync(to)) {
|
|
1386
|
+
steps.push({ kind: "skip", from, to, detail: "destination already exists" });
|
|
1387
|
+
return false;
|
|
1388
|
+
}
|
|
1389
|
+
if (!opts.dryRun) {
|
|
1390
|
+
fs11.mkdirSync(path10.dirname(to), { recursive: true });
|
|
1391
|
+
fs11.renameSync(from, to);
|
|
1392
|
+
}
|
|
1393
|
+
steps.push({ kind: "mv", from, to });
|
|
1394
|
+
return true;
|
|
1395
|
+
}
|
|
1396
|
+
function readYamlSafe(file) {
|
|
1397
|
+
if (!fs11.existsSync(file)) return null;
|
|
1398
|
+
try {
|
|
1399
|
+
const raw = fs11.readFileSync(file, "utf-8");
|
|
1400
|
+
return yaml5.load(raw) ?? null;
|
|
1401
|
+
} catch {
|
|
1402
|
+
return null;
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
function extractCliKeysFromLegacyConfig() {
|
|
1406
|
+
const data = readYamlSafe(legacyServerConfigYml());
|
|
1407
|
+
if (!data || typeof data !== "object") return null;
|
|
1408
|
+
const out = {};
|
|
1409
|
+
if (Array.isArray(data.sources)) {
|
|
1410
|
+
const projects = data.sources.filter((s) => s?.type === "project");
|
|
1411
|
+
if (projects.length > 0) out.sources = projects;
|
|
1412
|
+
}
|
|
1413
|
+
if (data.agentModels && typeof data.agentModels === "object") {
|
|
1414
|
+
out.agentModels = data.agentModels;
|
|
1415
|
+
}
|
|
1416
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
1417
|
+
}
|
|
1418
|
+
function reconstructProfilesFromRegistry(steps, opts = {}) {
|
|
1419
|
+
const file = legacyRegistryFile();
|
|
1420
|
+
const data = readYamlSafe(file);
|
|
1421
|
+
if (!data?.profiles || typeof data.profiles !== "object") return;
|
|
1422
|
+
for (const [name, entry] of Object.entries(data.profiles)) {
|
|
1423
|
+
if (!isValidProfileName(name)) continue;
|
|
1424
|
+
const dir = profileDir(name);
|
|
1425
|
+
if (!opts.dryRun) fs11.mkdirSync(dir, { recursive: true });
|
|
1426
|
+
steps.push({ kind: "mkdir", to: dir });
|
|
1427
|
+
const profileConfig = {};
|
|
1428
|
+
if (entry.api_url) profileConfig.api_url = entry.api_url;
|
|
1429
|
+
const profileConfigFile = path10.join(dir, "config.yml");
|
|
1430
|
+
if (!fs11.existsSync(profileConfigFile) && Object.keys(profileConfig).length > 0) {
|
|
1431
|
+
if (!opts.dryRun) {
|
|
1432
|
+
fs11.writeFileSync(profileConfigFile, yaml5.dump(profileConfig, { lineWidth: 120 }), "utf-8");
|
|
1433
|
+
}
|
|
1434
|
+
steps.push({ kind: "write", to: profileConfigFile, detail: `api_url=${entry.api_url}` });
|
|
1435
|
+
}
|
|
1436
|
+
if (entry.task0_home) {
|
|
1437
|
+
const oldDaemonJson = path10.join(entry.task0_home, "daemon.json");
|
|
1438
|
+
const newDaemonJson = path10.join(dir, "daemon.json");
|
|
1439
|
+
if (fs11.existsSync(oldDaemonJson) && !fs11.existsSync(newDaemonJson)) {
|
|
1440
|
+
safeMv(oldDaemonJson, newDaemonJson, steps, opts);
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1117
1443
|
}
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1444
|
+
if (data.current_profile && isValidProfileName(data.current_profile)) {
|
|
1445
|
+
if (!opts.dryRun) writeCurrentProfile(data.current_profile);
|
|
1446
|
+
steps.push({ kind: "write", to: path10.join(profilesRoot(), ".current"), detail: data.current_profile });
|
|
1447
|
+
}
|
|
1448
|
+
const backup = file + ".migrated-bak";
|
|
1449
|
+
if (fs11.existsSync(file) && !fs11.existsSync(backup)) {
|
|
1450
|
+
if (!opts.dryRun) fs11.renameSync(file, backup);
|
|
1451
|
+
steps.push({ kind: "mv", from: file, to: backup, detail: "legacy v0.7.0 registry archived" });
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
function migrateCliLayout(opts = {}) {
|
|
1455
|
+
const result = { ran: false, steps: [], alreadyMigrated: false };
|
|
1456
|
+
if (fs11.existsSync(migrationMarkerPath())) {
|
|
1457
|
+
result.alreadyMigrated = true;
|
|
1458
|
+
return result;
|
|
1459
|
+
}
|
|
1460
|
+
if (!opts.dryRun) fs11.mkdirSync(profilesRoot(), { recursive: true });
|
|
1461
|
+
const defaultDir = profileDir(DEFAULT_PROFILE_NAME);
|
|
1462
|
+
if (!fs11.existsSync(defaultDir)) {
|
|
1463
|
+
if (!opts.dryRun) fs11.mkdirSync(defaultDir, { recursive: true });
|
|
1464
|
+
result.steps.push({ kind: "mkdir", to: defaultDir });
|
|
1465
|
+
}
|
|
1466
|
+
safeMv(legacyDaemonJson(), path10.join(defaultDir, "daemon.json"), result.steps, opts);
|
|
1467
|
+
safeMv(legacyErrorsDir(), path10.join(defaultDir, "errors"), result.steps, opts);
|
|
1468
|
+
safeMv(legacyAgentRunDir(), path10.join(defaultDir, "agent-run"), result.steps, opts);
|
|
1469
|
+
safeMv(legacyLogsDir(), path10.join(defaultDir, "logs"), result.steps, opts);
|
|
1470
|
+
const cliKeys = extractCliKeysFromLegacyConfig();
|
|
1471
|
+
if (cliKeys) {
|
|
1472
|
+
const target = path10.join(defaultDir, "config.yml");
|
|
1473
|
+
if (!fs11.existsSync(target)) {
|
|
1474
|
+
if (!opts.dryRun) {
|
|
1475
|
+
fs11.writeFileSync(target, yaml5.dump(cliKeys, { lineWidth: 120 }), "utf-8");
|
|
1476
|
+
}
|
|
1477
|
+
result.steps.push({ kind: "write", to: target, detail: "extracted CLI keys from legacy config.yml" });
|
|
1478
|
+
}
|
|
1479
|
+
const data = readYamlSafe(legacyServerConfigYml());
|
|
1480
|
+
if (data) {
|
|
1481
|
+
let dirty = false;
|
|
1482
|
+
if (Array.isArray(data.sources)) {
|
|
1483
|
+
const nonProjectOnly = data.sources.filter((s) => s?.type !== "project");
|
|
1484
|
+
if (nonProjectOnly.length !== data.sources.length) {
|
|
1485
|
+
data.sources = nonProjectOnly;
|
|
1486
|
+
dirty = true;
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
if (data.agentModels) {
|
|
1490
|
+
delete data.agentModels;
|
|
1491
|
+
dirty = true;
|
|
1492
|
+
}
|
|
1493
|
+
if (dirty) {
|
|
1494
|
+
if (!opts.dryRun) {
|
|
1495
|
+
fs11.writeFileSync(legacyServerConfigYml(), yaml5.dump(data, { lineWidth: 120 }), "utf-8");
|
|
1496
|
+
}
|
|
1497
|
+
result.steps.push({ kind: "extract", from: legacyServerConfigYml(), detail: "stripped CLI-owned keys" });
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
if (fs11.existsSync(legacyRegistryFile())) {
|
|
1502
|
+
reconstructProfilesFromRegistry(result.steps, opts);
|
|
1503
|
+
}
|
|
1504
|
+
if (!opts.dryRun) {
|
|
1505
|
+
fs11.writeFileSync(migrationMarkerPath(), (/* @__PURE__ */ new Date()).toISOString() + "\n", "utf-8");
|
|
1506
|
+
}
|
|
1507
|
+
result.ran = result.steps.length > 0;
|
|
1508
|
+
return result;
|
|
1509
|
+
}
|
|
1510
|
+
function resetMigrationMarker() {
|
|
1511
|
+
const marker = migrationMarkerPath();
|
|
1512
|
+
if (!fs11.existsSync(marker)) return false;
|
|
1513
|
+
fs11.unlinkSync(marker);
|
|
1514
|
+
return true;
|
|
1515
|
+
}
|
|
1122
1516
|
|
|
1123
1517
|
// src/commands/source.ts
|
|
1124
1518
|
import { Command } from "commander";
|
|
1125
|
-
import
|
|
1519
|
+
import path13 from "path";
|
|
1126
1520
|
import chalk from "chalk";
|
|
1127
1521
|
|
|
1128
1522
|
// src/types.ts
|
|
1129
1523
|
init_task();
|
|
1130
1524
|
|
|
1131
1525
|
// src/core/admin-token.ts
|
|
1132
|
-
|
|
1133
|
-
import
|
|
1134
|
-
import
|
|
1135
|
-
|
|
1136
|
-
|
|
1526
|
+
init_node();
|
|
1527
|
+
import fs12 from "fs";
|
|
1528
|
+
import path11 from "path";
|
|
1529
|
+
function tokenFile() {
|
|
1530
|
+
return path11.join(serverWorkingDir(), "admin.token");
|
|
1531
|
+
}
|
|
1137
1532
|
var cached = null;
|
|
1138
1533
|
var AdminTokenUnavailableError = class extends Error {
|
|
1139
1534
|
constructor() {
|
|
1140
1535
|
super(
|
|
1141
1536
|
`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 ${
|
|
1537
|
+
\u2022 If the task0 server runs on this host, run the task0-server binary once \u2014 the token will be generated at ${tokenFile()}.
|
|
1143
1538
|
\u2022 Otherwise, copy the token from the server host and set TASK0_ADMIN_TOKEN.`
|
|
1144
1539
|
);
|
|
1145
1540
|
this.name = "AdminTokenUnavailableError";
|
|
@@ -1152,8 +1547,9 @@ function readAdminToken() {
|
|
|
1152
1547
|
cached = fromEnv;
|
|
1153
1548
|
return cached;
|
|
1154
1549
|
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1550
|
+
const file = tokenFile();
|
|
1551
|
+
if (fs12.existsSync(file)) {
|
|
1552
|
+
const v = fs12.readFileSync(file, "utf-8").trim();
|
|
1157
1553
|
if (v) {
|
|
1158
1554
|
cached = v;
|
|
1159
1555
|
return cached;
|
|
@@ -1166,34 +1562,38 @@ function adminAuthHeader() {
|
|
|
1166
1562
|
}
|
|
1167
1563
|
|
|
1168
1564
|
// src/core/daemon-config.ts
|
|
1169
|
-
|
|
1170
|
-
import
|
|
1171
|
-
import
|
|
1172
|
-
|
|
1173
|
-
|
|
1565
|
+
init_node();
|
|
1566
|
+
import fs13 from "fs";
|
|
1567
|
+
import path12 from "path";
|
|
1568
|
+
function configFile2() {
|
|
1569
|
+
return path12.join(profileDir(), "daemon.json");
|
|
1570
|
+
}
|
|
1174
1571
|
function daemonConfigPath() {
|
|
1175
|
-
return
|
|
1572
|
+
return configFile2();
|
|
1176
1573
|
}
|
|
1177
1574
|
function readDaemonIdentity() {
|
|
1178
|
-
|
|
1575
|
+
const file = configFile2();
|
|
1576
|
+
if (!fs13.existsSync(file)) return null;
|
|
1179
1577
|
try {
|
|
1180
|
-
const raw =
|
|
1578
|
+
const raw = fs13.readFileSync(file, "utf-8");
|
|
1181
1579
|
return JSON.parse(raw);
|
|
1182
1580
|
} catch {
|
|
1183
1581
|
return null;
|
|
1184
1582
|
}
|
|
1185
1583
|
}
|
|
1186
1584
|
function writeDaemonIdentity(identity) {
|
|
1187
|
-
|
|
1188
|
-
|
|
1585
|
+
const file = configFile2();
|
|
1586
|
+
fs13.mkdirSync(path12.dirname(file), { recursive: true });
|
|
1587
|
+
fs13.writeFileSync(file, JSON.stringify(identity, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
|
|
1189
1588
|
try {
|
|
1190
|
-
|
|
1589
|
+
fs13.chmodSync(file, 384);
|
|
1191
1590
|
} catch {
|
|
1192
1591
|
}
|
|
1193
1592
|
}
|
|
1194
1593
|
function clearDaemonIdentity() {
|
|
1195
|
-
|
|
1196
|
-
|
|
1594
|
+
const file = configFile2();
|
|
1595
|
+
if (!fs13.existsSync(file)) return false;
|
|
1596
|
+
fs13.unlinkSync(file);
|
|
1197
1597
|
return true;
|
|
1198
1598
|
}
|
|
1199
1599
|
|
|
@@ -1231,7 +1631,7 @@ function resolveAuthHeader() {
|
|
|
1231
1631
|
} catch (error2) {
|
|
1232
1632
|
if (error2 instanceof AdminTokenUnavailableError) {
|
|
1233
1633
|
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" : "~/.
|
|
1634
|
+
`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
1635
|
);
|
|
1236
1636
|
}
|
|
1237
1637
|
throw error2;
|
|
@@ -1302,8 +1702,8 @@ function requireLocalDaemon() {
|
|
|
1302
1702
|
}
|
|
1303
1703
|
var source = new Command("source").description("Manage task sources");
|
|
1304
1704
|
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 ||
|
|
1705
|
+
const absPath = path13.resolve(inputPath);
|
|
1706
|
+
const name = opts.name || path13.basename(absPath);
|
|
1307
1707
|
const daemonId = requireLocalDaemon();
|
|
1308
1708
|
let resp;
|
|
1309
1709
|
try {
|
|
@@ -1401,9 +1801,9 @@ ${project2}`));
|
|
|
1401
1801
|
|
|
1402
1802
|
// src/commands/project.ts
|
|
1403
1803
|
import { Command as Command2 } from "commander";
|
|
1404
|
-
import
|
|
1405
|
-
import
|
|
1406
|
-
import
|
|
1804
|
+
import fs14 from "fs";
|
|
1805
|
+
import path14 from "path";
|
|
1806
|
+
import yaml6 from "js-yaml";
|
|
1407
1807
|
import chalk2 from "chalk";
|
|
1408
1808
|
|
|
1409
1809
|
// ../../packages/shared/dist/index.js
|
|
@@ -1441,15 +1841,15 @@ project.command("list").description("List registered projects (queries the hub)"
|
|
|
1441
1841
|
});
|
|
1442
1842
|
project.command("init").description("Initialize task0.yml in the current directory").option("-d, --tasks-dir <dir>", "Tasks directory", ".task0/tasks").action((opts) => {
|
|
1443
1843
|
const cwd = process.cwd();
|
|
1444
|
-
const ymlPath =
|
|
1445
|
-
if (
|
|
1844
|
+
const ymlPath = path14.join(cwd, "task0.yml");
|
|
1845
|
+
if (fs14.existsSync(ymlPath)) {
|
|
1446
1846
|
console.error(chalk2.yellow("task0.yml already exists"));
|
|
1447
1847
|
process.exit(1);
|
|
1448
1848
|
}
|
|
1449
1849
|
const config = { kind: "project", object_id: generateObjectId("project"), tasks_dir: opts.tasksDir };
|
|
1450
|
-
|
|
1451
|
-
const tasksDir =
|
|
1452
|
-
|
|
1850
|
+
fs14.writeFileSync(ymlPath, yaml6.dump(config), "utf-8");
|
|
1851
|
+
const tasksDir = path14.join(cwd, opts.tasksDir);
|
|
1852
|
+
fs14.mkdirSync(tasksDir, { recursive: true });
|
|
1453
1853
|
console.log(chalk2.green("Initialized task0 project"));
|
|
1454
1854
|
console.log(` ${ymlPath}`);
|
|
1455
1855
|
console.log(` ${tasksDir}/`);
|
|
@@ -1458,9 +1858,9 @@ project.command("init").description("Initialize task0.yml in the current directo
|
|
|
1458
1858
|
// src/commands/task.ts
|
|
1459
1859
|
import { Command as Command8 } from "commander";
|
|
1460
1860
|
import { execSync } from "child_process";
|
|
1461
|
-
import
|
|
1462
|
-
import
|
|
1463
|
-
import
|
|
1861
|
+
import fs20 from "fs";
|
|
1862
|
+
import path17 from "path";
|
|
1863
|
+
import yaml7 from "js-yaml";
|
|
1464
1864
|
import chalk8 from "chalk";
|
|
1465
1865
|
|
|
1466
1866
|
// src/lib/api.ts
|
|
@@ -1497,18 +1897,18 @@ async function request(method, pathname, body) {
|
|
|
1497
1897
|
}
|
|
1498
1898
|
}
|
|
1499
1899
|
var api = {
|
|
1500
|
-
get: (
|
|
1501
|
-
post: (
|
|
1502
|
-
put: (
|
|
1503
|
-
patch: (
|
|
1504
|
-
del: (
|
|
1900
|
+
get: (path32) => request("GET", path32),
|
|
1901
|
+
post: (path32, body) => request("POST", path32, body ?? {}),
|
|
1902
|
+
put: (path32, body) => request("PUT", path32, body ?? {}),
|
|
1903
|
+
patch: (path32, body) => request("PATCH", path32, body ?? {}),
|
|
1904
|
+
del: (path32) => request("DELETE", path32)
|
|
1505
1905
|
};
|
|
1506
1906
|
|
|
1507
1907
|
// src/commands/task/triage.ts
|
|
1508
1908
|
import { Command as Command3 } from "commander";
|
|
1509
1909
|
import chalk3 from "chalk";
|
|
1510
|
-
import
|
|
1511
|
-
import
|
|
1910
|
+
import fs16 from "fs";
|
|
1911
|
+
import path15 from "path";
|
|
1512
1912
|
|
|
1513
1913
|
// src/core/agent-run-wait.ts
|
|
1514
1914
|
async function getAgentRun(id) {
|
|
@@ -1532,54 +1932,6 @@ async function waitForAgentRun(id, opts = {}) {
|
|
|
1532
1932
|
}
|
|
1533
1933
|
}
|
|
1534
1934
|
|
|
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
1935
|
// src/core/model-options.ts
|
|
1584
1936
|
function resolveModelOptions(agent2, cli, warn = () => {
|
|
1585
1937
|
}) {
|
|
@@ -1607,8 +1959,8 @@ init_task_state2();
|
|
|
1607
1959
|
var ISSUE_DETAIL_RE = /^ISSUE-\d+\.md$/;
|
|
1608
1960
|
var TRIAGE_SKILL_NAME = "triage";
|
|
1609
1961
|
function resolveSkillFilePath(projectRoot, skillName) {
|
|
1610
|
-
const p =
|
|
1611
|
-
return
|
|
1962
|
+
const p = path15.join(projectRoot, ".claude", "skills", skillName, "SKILL.md");
|
|
1963
|
+
return fs16.existsSync(p) ? p : null;
|
|
1612
1964
|
}
|
|
1613
1965
|
var triage = new Command3("triage").description("Decompose IDEA into ISSUE.md + ISSUE-NN.md").argument("<objectId>", "Task object_id (tsk_XXXXX)").option("--agent <name>", "Agent (claude-code|codex)", "claude-code").option("--model <id>", "Model id or alias (see `task0 models refresh`)").option("--effort <level>", "Reasoning effort (e.g. low|medium|high)").option("--idea <file>", "IDEA file (default: latest IDEA-NN.md)").option("--force", "Overwrite existing ISSUE files").option("--wait", "Wait for completion").option("--json", "Output JSON").action(async (objectId, opts) => {
|
|
1614
1966
|
try {
|
|
@@ -1627,7 +1979,7 @@ var triage = new Command3("triage").description("Decompose IDEA into ISSUE.md +
|
|
|
1627
1979
|
process.exit(1);
|
|
1628
1980
|
}
|
|
1629
1981
|
for (const name of existingIssues) {
|
|
1630
|
-
|
|
1982
|
+
fs16.rmSync(path15.join(loc.taskDir, name), { force: true });
|
|
1631
1983
|
}
|
|
1632
1984
|
}
|
|
1633
1985
|
if (opts.model || opts.effort) {
|
|
@@ -1665,8 +2017,8 @@ var triage = new Command3("triage").description("Decompose IDEA into ISSUE.md +
|
|
|
1665
2017
|
console.error(chalk3.red(`triage failed: ${final.error || "unknown"}`));
|
|
1666
2018
|
process.exit(1);
|
|
1667
2019
|
}
|
|
1668
|
-
const hasOverview =
|
|
1669
|
-
const issueFiles =
|
|
2020
|
+
const hasOverview = fs16.existsSync(path15.join(loc.taskDir, "ISSUE.md"));
|
|
2021
|
+
const issueFiles = fs16.readdirSync(loc.taskDir).filter((name) => ISSUE_DETAIL_RE.test(name)).sort();
|
|
1670
2022
|
if (!hasOverview || issueFiles.length === 0) {
|
|
1671
2023
|
const missing = [];
|
|
1672
2024
|
if (!hasOverview) missing.push("ISSUE.md");
|
|
@@ -1676,7 +2028,7 @@ var triage = new Command3("triage").description("Decompose IDEA into ISSUE.md +
|
|
|
1676
2028
|
}
|
|
1677
2029
|
let blockingQuestionCount = 0;
|
|
1678
2030
|
for (const name of issueFiles) {
|
|
1679
|
-
const content =
|
|
2031
|
+
const content = fs16.readFileSync(path15.join(loc.taskDir, name), "utf-8");
|
|
1680
2032
|
blockingQuestionCount += countBlockingQuestions(content);
|
|
1681
2033
|
}
|
|
1682
2034
|
await updateWorkflow(loc.taskYml, {
|
|
@@ -1704,8 +2056,8 @@ var triage = new Command3("triage").description("Decompose IDEA into ISSUE.md +
|
|
|
1704
2056
|
}
|
|
1705
2057
|
});
|
|
1706
2058
|
function listIssueArtifacts(taskDir) {
|
|
1707
|
-
if (!
|
|
1708
|
-
return
|
|
2059
|
+
if (!fs16.existsSync(taskDir)) return [];
|
|
2060
|
+
return fs16.readdirSync(taskDir).filter((name) => name === "ISSUE.md" || ISSUE_DETAIL_RE.test(name)).sort();
|
|
1709
2061
|
}
|
|
1710
2062
|
function countBlockingQuestions(md) {
|
|
1711
2063
|
const match = md.match(/## Open Questions\s*\n([\s\S]*?)(\n## |\n*$)/i);
|
|
@@ -1719,13 +2071,13 @@ function countBlockingQuestions(md) {
|
|
|
1719
2071
|
// src/commands/task/exec.ts
|
|
1720
2072
|
import { Command as Command4 } from "commander";
|
|
1721
2073
|
import chalk4 from "chalk";
|
|
1722
|
-
import
|
|
1723
|
-
import
|
|
2074
|
+
import fs17 from "fs";
|
|
2075
|
+
import path16 from "path";
|
|
1724
2076
|
init_task_state2();
|
|
1725
2077
|
var PLAN_EXECUTE_SKILL_NAME = "plan-execute";
|
|
1726
2078
|
function resolveSkillFilePath2(projectRoot, skillName) {
|
|
1727
|
-
const p =
|
|
1728
|
-
return
|
|
2079
|
+
const p = path16.join(projectRoot, ".claude", "skills", skillName, "SKILL.md");
|
|
2080
|
+
return fs17.existsSync(p) ? p : null;
|
|
1729
2081
|
}
|
|
1730
2082
|
var exec = new Command4("exec").description("Execute a plan against the task (cwd = project root; agent sets up its own worktree if needed)").argument("<objectId>", "Task object_id (tsk_XXXXX)").option("--agent <name>", "Agent (claude-code|codex|cursor)", "claude-code").option("--model <id>", "Model id or alias (see `task0 models refresh`)").option("--effort <level>", "Reasoning effort (e.g. low|medium|high)").option("--plan <file>", "Plan file (default: refined plan, else latest PLAN)").option("--no-commit", "Skip commit").option("--wait", "Wait for completion").option("--json", "Output JSON").action(async (objectId, opts) => {
|
|
1731
2083
|
try {
|
|
@@ -1818,7 +2170,7 @@ var summarize = new Command5("summarize").description("Generate a concise title
|
|
|
1818
2170
|
});
|
|
1819
2171
|
|
|
1820
2172
|
// src/commands/task/comment.ts
|
|
1821
|
-
import
|
|
2173
|
+
import fs18 from "fs";
|
|
1822
2174
|
import { Command as Command6 } from "commander";
|
|
1823
2175
|
import chalk6 from "chalk";
|
|
1824
2176
|
var comment = new Command6("comment").description("Manage comments on a task");
|
|
@@ -1828,8 +2180,8 @@ function fail(err) {
|
|
|
1828
2180
|
}
|
|
1829
2181
|
function readBodyFromOpts(opts) {
|
|
1830
2182
|
if (opts.body !== void 0) return opts.body;
|
|
1831
|
-
if (opts.file === "-") return
|
|
1832
|
-
if (opts.file) return
|
|
2183
|
+
if (opts.file === "-") return fs18.readFileSync(0, "utf-8");
|
|
2184
|
+
if (opts.file) return fs18.readFileSync(opts.file, "utf-8");
|
|
1833
2185
|
throw new Error("Provide --body or --file");
|
|
1834
2186
|
}
|
|
1835
2187
|
function preview(body, width = 60) {
|
|
@@ -1928,7 +2280,7 @@ comment.command("delete <cmtId>").description("Delete a comment by its cmt_ id")
|
|
|
1928
2280
|
});
|
|
1929
2281
|
|
|
1930
2282
|
// src/commands/task/description.ts
|
|
1931
|
-
import
|
|
2283
|
+
import fs19 from "fs";
|
|
1932
2284
|
import { Command as Command7 } from "commander";
|
|
1933
2285
|
import chalk7 from "chalk";
|
|
1934
2286
|
var description = new Command7("description").description("Show or update the task description");
|
|
@@ -1938,8 +2290,8 @@ function fail2(err) {
|
|
|
1938
2290
|
}
|
|
1939
2291
|
function readBodyFromOpts2(opts) {
|
|
1940
2292
|
if (opts.body !== void 0) return opts.body;
|
|
1941
|
-
if (opts.file === "-") return
|
|
1942
|
-
if (opts.file) return
|
|
2293
|
+
if (opts.file === "-") return fs19.readFileSync(0, "utf-8");
|
|
2294
|
+
if (opts.file) return fs19.readFileSync(opts.file, "utf-8");
|
|
1943
2295
|
throw new Error("Provide --body or --file (use --file - to read stdin)");
|
|
1944
2296
|
}
|
|
1945
2297
|
description.command("show <taskId>").description("Print the current description (taskId is short id or tsk_)").option("--json", "Output JSON").action(async (taskId, opts) => {
|
|
@@ -1988,12 +2340,12 @@ task.addCommand(comment);
|
|
|
1988
2340
|
task.addCommand(description);
|
|
1989
2341
|
task.command("init <input>").description("Create a task from a description or Linear/GitHub issue URL").action(async (input) => {
|
|
1990
2342
|
const cwd = process.cwd();
|
|
1991
|
-
const projectYml =
|
|
1992
|
-
if (!
|
|
2343
|
+
const projectYml = path17.join(cwd, "task0.yml");
|
|
2344
|
+
if (!fs20.existsSync(projectYml)) {
|
|
1993
2345
|
console.error(chalk8.red("Not a task0 project (task0.yml not found). Run `task0 project init` first."));
|
|
1994
2346
|
process.exit(1);
|
|
1995
2347
|
}
|
|
1996
|
-
const projectConfig =
|
|
2348
|
+
const projectConfig = yaml7.load(fs20.readFileSync(projectYml, "utf-8"));
|
|
1997
2349
|
if (projectConfig.kind !== "project") {
|
|
1998
2350
|
console.error(chalk8.red('Invalid task0.yml: kind is not "project"'));
|
|
1999
2351
|
process.exit(1);
|
|
@@ -2009,38 +2361,38 @@ task.command("init <input>").description("Create a task from a description or Li
|
|
|
2009
2361
|
});
|
|
2010
2362
|
task.command("migrate").description("Add task0.yml to legacy task directories that lack one").option("--dry-run", "Show what would be created without writing").action((opts) => {
|
|
2011
2363
|
const cwd = process.cwd();
|
|
2012
|
-
const projectYml =
|
|
2013
|
-
if (!
|
|
2364
|
+
const projectYml = path17.join(cwd, "task0.yml");
|
|
2365
|
+
if (!fs20.existsSync(projectYml)) {
|
|
2014
2366
|
console.error(chalk8.red("Not a task0 project (task0.yml not found). Run `task0 project init` first."));
|
|
2015
2367
|
process.exit(1);
|
|
2016
2368
|
}
|
|
2017
|
-
const projectConfig =
|
|
2369
|
+
const projectConfig = yaml7.load(fs20.readFileSync(projectYml, "utf-8"));
|
|
2018
2370
|
if (projectConfig.kind !== "project") {
|
|
2019
2371
|
console.error(chalk8.red('Invalid task0.yml: kind is not "project"'));
|
|
2020
2372
|
process.exit(1);
|
|
2021
2373
|
}
|
|
2022
|
-
const tasksDir =
|
|
2023
|
-
if (!
|
|
2374
|
+
const tasksDir = path17.join(cwd, projectConfig.tasks_dir);
|
|
2375
|
+
if (!fs20.existsSync(tasksDir)) {
|
|
2024
2376
|
console.error(chalk8.red(`Tasks directory not found: ${tasksDir}`));
|
|
2025
2377
|
process.exit(1);
|
|
2026
2378
|
}
|
|
2027
|
-
const entries =
|
|
2379
|
+
const entries = fs20.readdirSync(tasksDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
2028
2380
|
let migrated = 0;
|
|
2029
2381
|
let skipped = 0;
|
|
2030
2382
|
let seededObjectIds = 0;
|
|
2031
2383
|
for (const name of entries) {
|
|
2032
|
-
const taskDir =
|
|
2033
|
-
const taskYml =
|
|
2034
|
-
if (
|
|
2384
|
+
const taskDir = path17.join(tasksDir, name);
|
|
2385
|
+
const taskYml = path17.join(taskDir, "task0.yml");
|
|
2386
|
+
if (fs20.existsSync(taskYml)) {
|
|
2035
2387
|
skipped++;
|
|
2036
2388
|
try {
|
|
2037
|
-
const raw =
|
|
2389
|
+
const raw = yaml7.load(fs20.readFileSync(taskYml, "utf-8"));
|
|
2038
2390
|
if (raw && raw.kind === "task" && !raw.object_id) {
|
|
2039
2391
|
raw.object_id = generateObjectId("task");
|
|
2040
2392
|
if (opts.dryRun) {
|
|
2041
2393
|
console.log(chalk8.dim(`[dry-run] seed object_id: ${taskYml}`));
|
|
2042
2394
|
} else {
|
|
2043
|
-
|
|
2395
|
+
fs20.writeFileSync(taskYml, yaml7.dump(raw, { lineWidth: 120 }), "utf-8");
|
|
2044
2396
|
console.log(chalk8.green(` seed object_id: ${taskYml}`));
|
|
2045
2397
|
}
|
|
2046
2398
|
seededObjectIds++;
|
|
@@ -2065,7 +2417,7 @@ task.command("migrate").description("Add task0.yml to legacy task directories th
|
|
|
2065
2417
|
if (opts.dryRun) {
|
|
2066
2418
|
console.log(chalk8.dim(`[dry-run] ${taskYml}`));
|
|
2067
2419
|
} else {
|
|
2068
|
-
|
|
2420
|
+
fs20.writeFileSync(taskYml, yaml7.dump(taskConfig), "utf-8");
|
|
2069
2421
|
console.log(chalk8.green(` ${taskYml}`));
|
|
2070
2422
|
}
|
|
2071
2423
|
migrated++;
|
|
@@ -2125,7 +2477,7 @@ task.command("done <id>").description("Write task lifecycle state to task0.yml \
|
|
|
2125
2477
|
try {
|
|
2126
2478
|
const { resolveTaskByObjectId: resolveTaskByObjectId2 } = await Promise.resolve().then(() => (init_task_state2(), task_state_exports));
|
|
2127
2479
|
const loc = resolveTaskByObjectId2(id);
|
|
2128
|
-
const raw =
|
|
2480
|
+
const raw = yaml7.load(fs20.readFileSync(loc.taskYml, "utf-8"));
|
|
2129
2481
|
if (opts.phase) {
|
|
2130
2482
|
const phase = opts.phase.trim();
|
|
2131
2483
|
if (!phase) {
|
|
@@ -2138,7 +2490,7 @@ task.command("done <id>").description("Write task lifecycle state to task0.yml \
|
|
|
2138
2490
|
return;
|
|
2139
2491
|
}
|
|
2140
2492
|
raw.workflow = { ...workflow2, phase };
|
|
2141
|
-
|
|
2493
|
+
fs20.writeFileSync(loc.taskYml, yaml7.dump(raw, { lineWidth: 120 }), "utf-8");
|
|
2142
2494
|
console.log(chalk8.green(`${id} phase: ${phase}`));
|
|
2143
2495
|
return;
|
|
2144
2496
|
}
|
|
@@ -2149,7 +2501,7 @@ task.command("done <id>").description("Write task lifecycle state to task0.yml \
|
|
|
2149
2501
|
raw.status = "done";
|
|
2150
2502
|
const workflow = raw.workflow ?? {};
|
|
2151
2503
|
raw.workflow = { ...workflow, phase: "completed" };
|
|
2152
|
-
|
|
2504
|
+
fs20.writeFileSync(loc.taskYml, yaml7.dump(raw, { lineWidth: 120 }), "utf-8");
|
|
2153
2505
|
console.log(chalk8.green(`${id} marked as done`));
|
|
2154
2506
|
} catch (err) {
|
|
2155
2507
|
console.error(chalk8.red(err.message));
|
|
@@ -2158,33 +2510,33 @@ task.command("done <id>").description("Write task lifecycle state to task0.yml \
|
|
|
2158
2510
|
});
|
|
2159
2511
|
task.command("archive <id>").description("Archive a task (append to tasks.tar, remove from tasks/)").action((id) => {
|
|
2160
2512
|
const cwd = process.cwd();
|
|
2161
|
-
const projectYml =
|
|
2162
|
-
if (!
|
|
2513
|
+
const projectYml = path17.join(cwd, "task0.yml");
|
|
2514
|
+
if (!fs20.existsSync(projectYml)) {
|
|
2163
2515
|
console.error(chalk8.red("Not a task0 project (task0.yml not found)."));
|
|
2164
2516
|
process.exit(1);
|
|
2165
2517
|
}
|
|
2166
|
-
const projectConfig =
|
|
2518
|
+
const projectConfig = yaml7.load(fs20.readFileSync(projectYml, "utf-8"));
|
|
2167
2519
|
if (projectConfig.kind !== "project") {
|
|
2168
2520
|
console.error(chalk8.red('Invalid task0.yml: kind is not "project"'));
|
|
2169
2521
|
process.exit(1);
|
|
2170
2522
|
}
|
|
2171
|
-
const tasksDir =
|
|
2172
|
-
const taskDir =
|
|
2173
|
-
if (!
|
|
2523
|
+
const tasksDir = path17.join(cwd, projectConfig.tasks_dir);
|
|
2524
|
+
const taskDir = path17.join(tasksDir, id);
|
|
2525
|
+
if (!fs20.existsSync(taskDir)) {
|
|
2174
2526
|
console.error(chalk8.red(`Task "${id}" not found at ${taskDir}`));
|
|
2175
2527
|
process.exit(1);
|
|
2176
2528
|
}
|
|
2177
2529
|
const tarFile = tasksDir + ".tar";
|
|
2178
2530
|
archiveTaskToTar(tasksDir, id, tarFile);
|
|
2179
|
-
|
|
2531
|
+
fs20.rmSync(taskDir, { recursive: true });
|
|
2180
2532
|
console.log(chalk8.green(`Archived "${id}" \u2192 ${tarFile}`));
|
|
2181
2533
|
});
|
|
2182
2534
|
function archiveTaskToTar(tasksDir, taskId, tarFile) {
|
|
2183
|
-
if (
|
|
2535
|
+
if (fs20.existsSync(tarFile) && !fs20.statSync(tarFile).isFile()) {
|
|
2184
2536
|
console.error(chalk8.red(`${tarFile} exists but is not a file (likely a leftover directory). Remove it manually first.`));
|
|
2185
2537
|
process.exit(1);
|
|
2186
2538
|
}
|
|
2187
|
-
if (
|
|
2539
|
+
if (fs20.existsSync(tarFile)) {
|
|
2188
2540
|
execSync(`tar -rf ${JSON.stringify(tarFile)} -C ${JSON.stringify(tasksDir)} ${JSON.stringify(taskId)}`);
|
|
2189
2541
|
} else {
|
|
2190
2542
|
execSync(`tar -cf ${JSON.stringify(tarFile)} -C ${JSON.stringify(tasksDir)} ${JSON.stringify(taskId)}`);
|
|
@@ -2194,9 +2546,9 @@ function archiveTaskToTar(tasksDir, taskId, tarFile) {
|
|
|
2194
2546
|
// src/commands/ui.ts
|
|
2195
2547
|
import { Command as Command9 } from "commander";
|
|
2196
2548
|
import { spawn } from "child_process";
|
|
2197
|
-
import
|
|
2549
|
+
import path18 from "path";
|
|
2198
2550
|
import chalk9 from "chalk";
|
|
2199
|
-
var DASHBOARD_DIR =
|
|
2551
|
+
var DASHBOARD_DIR = path18.resolve(
|
|
2200
2552
|
import.meta.dirname,
|
|
2201
2553
|
"..",
|
|
2202
2554
|
"..",
|
|
@@ -2230,17 +2582,17 @@ import chalk10 from "chalk";
|
|
|
2230
2582
|
// ../../packages/shared/dist/types/agent.js
|
|
2231
2583
|
var AGENT_KINDS = ["coding", "llm_api", "workflow"];
|
|
2232
2584
|
var AGENT_KIND_SET = new Set(AGENT_KINDS);
|
|
2233
|
-
var
|
|
2234
|
-
var
|
|
2235
|
-
function
|
|
2236
|
-
return typeof value === "string" &&
|
|
2585
|
+
var AGENT_PROVIDERS = ["claude-code", "codex", "cursor-agent"];
|
|
2586
|
+
var AGENT_PROVIDER_SET = new Set(AGENT_PROVIDERS);
|
|
2587
|
+
function isAgentProvider(value) {
|
|
2588
|
+
return typeof value === "string" && AGENT_PROVIDER_SET.has(value);
|
|
2237
2589
|
}
|
|
2238
2590
|
|
|
2239
2591
|
// ../../packages/shared/dist/types/runtime.js
|
|
2240
2592
|
var AGENT_RUN_STATUS_VALUES = ["starting", "running", "done", "error"];
|
|
2241
2593
|
var AGENT_RUN_STATUS_SET = new Set(AGENT_RUN_STATUS_VALUES);
|
|
2242
|
-
var RUNTIME_AGENTS =
|
|
2243
|
-
var isRuntimeAgent =
|
|
2594
|
+
var RUNTIME_AGENTS = AGENT_PROVIDERS;
|
|
2595
|
+
var isRuntimeAgent = isAgentProvider;
|
|
2244
2596
|
var AGENT_MODEL_DEFAULTS = {
|
|
2245
2597
|
"claude-code": [
|
|
2246
2598
|
{ id: "opus", label: "Opus" },
|
|
@@ -2252,7 +2604,7 @@ var AGENT_MODEL_DEFAULTS = {
|
|
|
2252
2604
|
{ id: "o3", label: "o3" },
|
|
2253
2605
|
{ id: "o4-mini", label: "o4-mini" }
|
|
2254
2606
|
],
|
|
2255
|
-
cursor: [
|
|
2607
|
+
"cursor-agent": [
|
|
2256
2608
|
{ id: "", label: "Default" },
|
|
2257
2609
|
{ id: "gpt-5", label: "GPT-5" },
|
|
2258
2610
|
{ id: "sonnet-4", label: "Sonnet 4" },
|
|
@@ -2260,12 +2612,12 @@ var AGENT_MODEL_DEFAULTS = {
|
|
|
2260
2612
|
]
|
|
2261
2613
|
};
|
|
2262
2614
|
function defaultAgentModelFetchCommand(agent2) {
|
|
2263
|
-
if (agent2 === "cursor")
|
|
2615
|
+
if (agent2 === "cursor-agent")
|
|
2264
2616
|
return "cursor-agent models";
|
|
2265
2617
|
return void 0;
|
|
2266
2618
|
}
|
|
2267
2619
|
function defaultAgentModelOutputFormat(agent2) {
|
|
2268
|
-
if (agent2 === "cursor")
|
|
2620
|
+
if (agent2 === "cursor-agent")
|
|
2269
2621
|
return "lines";
|
|
2270
2622
|
return void 0;
|
|
2271
2623
|
}
|
|
@@ -2477,12 +2829,12 @@ models.command("default <agent>").description("Get or set default model / effort
|
|
|
2477
2829
|
});
|
|
2478
2830
|
|
|
2479
2831
|
// src/commands/agent-run.ts
|
|
2480
|
-
import
|
|
2481
|
-
import
|
|
2832
|
+
import fs21 from "fs";
|
|
2833
|
+
import path19 from "path";
|
|
2482
2834
|
import { Command as Command11 } from "commander";
|
|
2483
2835
|
import chalk11 from "chalk";
|
|
2484
2836
|
import { spawn as spawn2, spawnSync as spawnSync3 } from "child_process";
|
|
2485
|
-
import
|
|
2837
|
+
import yaml8 from "js-yaml";
|
|
2486
2838
|
var agentRun = new Command11("agent-run").description("Inspect and control agent runs");
|
|
2487
2839
|
agentRun.command("list").description("List active agent runs").option("--json", "Output JSON").option("--task <id>", "Filter by task id").action(async (opts) => {
|
|
2488
2840
|
try {
|
|
@@ -2606,7 +2958,7 @@ profile.command("get <ref>").description("Show one runtime profile by slug or ob
|
|
|
2606
2958
|
console.log(formatProfile(result.runtime));
|
|
2607
2959
|
console.log();
|
|
2608
2960
|
console.log(chalk11.dim("--- exec ---"));
|
|
2609
|
-
console.log(
|
|
2961
|
+
console.log(yaml8.dump(result.runtime.exec, { lineWidth: 100 }));
|
|
2610
2962
|
} catch (err) {
|
|
2611
2963
|
const apiErr = err;
|
|
2612
2964
|
if (apiErr.status === 404) failProfile(`not found: ${ref}`);
|
|
@@ -2616,7 +2968,7 @@ profile.command("get <ref>").description("Show one runtime profile by slug or ob
|
|
|
2616
2968
|
profile.command("create").description("Create a runtime profile from a YAML spec file").requiredOption("--from-file <file>", "YAML spec file with kind, slug, exec").option("--scope <scope>", "Storage scope: user (default) or project", "user").option("--project-root <path>", "Project root (required for --scope project)").action(async (opts) => {
|
|
2617
2969
|
let parsed;
|
|
2618
2970
|
try {
|
|
2619
|
-
parsed =
|
|
2971
|
+
parsed = yaml8.load(fs21.readFileSync(path19.resolve(opts.fromFile), "utf-8"));
|
|
2620
2972
|
} catch (err) {
|
|
2621
2973
|
failProfile(`cannot read ${opts.fromFile}: ${err.message}`);
|
|
2622
2974
|
}
|
|
@@ -2632,14 +2984,14 @@ profile.command("edit <ref>").description("Open the runtime profile YAML in $EDI
|
|
|
2632
2984
|
try {
|
|
2633
2985
|
const result = await api.get(`/api/runtime-profiles/${encodeURIComponent(ref)}`);
|
|
2634
2986
|
if (result.runtime.system) failProfile("cannot edit a system runtime profile");
|
|
2635
|
-
const tmp =
|
|
2636
|
-
|
|
2987
|
+
const tmp = path19.join(process.env.TMPDIR || "/tmp", `task0-runtime-${result.runtime.slug}-${Date.now()}.yml`);
|
|
2988
|
+
fs21.writeFileSync(tmp, yaml8.dump(result.runtime, { lineWidth: 100 }), "utf-8");
|
|
2637
2989
|
const editor = process.env.EDITOR || "vi";
|
|
2638
2990
|
const r = spawnSync3(editor, [tmp], { stdio: "inherit" });
|
|
2639
2991
|
if (r.status !== 0) failProfile(`editor exited with status ${r.status}`);
|
|
2640
|
-
const updated =
|
|
2992
|
+
const updated = yaml8.load(fs21.readFileSync(tmp, "utf-8"));
|
|
2641
2993
|
await api.put(`/api/runtime-profiles/${encodeURIComponent(ref)}`, updated);
|
|
2642
|
-
|
|
2994
|
+
fs21.unlinkSync(tmp);
|
|
2643
2995
|
console.log(chalk11.green(`updated ${ref}`));
|
|
2644
2996
|
} catch (err) {
|
|
2645
2997
|
failProfile(err.message);
|
|
@@ -2659,13 +3011,13 @@ agentRun.addCommand(profile);
|
|
|
2659
3011
|
// src/commands/plan.ts
|
|
2660
3012
|
import { Command as Command12 } from "commander";
|
|
2661
3013
|
import chalk12 from "chalk";
|
|
2662
|
-
import
|
|
2663
|
-
import
|
|
3014
|
+
import fs22 from "fs";
|
|
3015
|
+
import path20 from "path";
|
|
2664
3016
|
init_task_state2();
|
|
2665
3017
|
var PLAN_GENERATE_SKILL_NAME = "plan-generate";
|
|
2666
3018
|
function resolveSkillFilePath3(projectRoot, skillName) {
|
|
2667
|
-
const p =
|
|
2668
|
-
return
|
|
3019
|
+
const p = path20.join(projectRoot, ".claude", "skills", skillName, "SKILL.md");
|
|
3020
|
+
return fs22.existsSync(p) ? p : null;
|
|
2669
3021
|
}
|
|
2670
3022
|
var plan = new Command12("plan").description("Generate and refine plans");
|
|
2671
3023
|
plan.command("generate <objectId>").description("Generate plan(s) from an IDEA file \u2014 supports agent fan-out").option("-a, --agents <list>", "Comma-separated agents (claude-code,codex,cursor)", "codex,claude-code").option("--model <id>", "Model id or alias \u2014 only with a single agent; use `task0 models default` for fan-out").option("--effort <level>", "Reasoning effort \u2014 only with a single agent; use `task0 models default` for fan-out").option("--idea <file>", "IDEA file name (default: latest IDEA-NN.md)").option("--additional-prompt <text>", "Extra prompt content").option("--wait", "Wait for completion").option("--force", "Overwrite existing plan files").option("--json", "Output JSON").action(async (objectId, opts) => {
|
|
@@ -2797,7 +3149,7 @@ var PLAN_REFINE_SKILL_NAME = "plan-refine";
|
|
|
2797
3149
|
plan.command("refine <objectId>").description("Synthesize plan files + ISSUE files into a refined plan").option("--agent <name>", "Agent to run refine (claude-code|codex)", "claude-code").option("--model <id>", "Model id or alias (see `task0 models refresh`)").option("--effort <level>", "Reasoning effort (e.g. low|medium|high)").option("--wait", "Wait for completion").option("--json", "Output JSON").action(async (objectId, opts) => {
|
|
2798
3150
|
try {
|
|
2799
3151
|
const loc = resolveTaskByObjectId(objectId);
|
|
2800
|
-
const files =
|
|
3152
|
+
const files = fs22.readdirSync(loc.taskDir);
|
|
2801
3153
|
const planFiles = files.filter((f) => /^PLAN-\d+-(codex|claude-code|cursor)\.md$/.test(f));
|
|
2802
3154
|
if (planFiles.length === 0) {
|
|
2803
3155
|
console.error(chalk12.red("No PLAN-NN-<agent>.md files found. Run `task0 plan generate` first."));
|
|
@@ -2839,8 +3191,8 @@ plan.command("refine <objectId>").description("Synthesize plan files + ISSUE fil
|
|
|
2839
3191
|
if (!opts.json) console.log(chalk12.dim(`[refine] ${s.status}${s.phase ? " " + s.phase : ""}`));
|
|
2840
3192
|
}
|
|
2841
3193
|
});
|
|
2842
|
-
const refinedPath =
|
|
2843
|
-
const wrote =
|
|
3194
|
+
const refinedPath = path20.join(loc.taskDir, refinedFile);
|
|
3195
|
+
const wrote = fs22.existsSync(refinedPath);
|
|
2844
3196
|
if (final.status === "done" && wrote) {
|
|
2845
3197
|
await updateWorkflow(loc.taskYml, {
|
|
2846
3198
|
phase: "refined",
|
|
@@ -2992,8 +3344,8 @@ import { Command as Command14 } from "commander";
|
|
|
2992
3344
|
import chalk14 from "chalk";
|
|
2993
3345
|
|
|
2994
3346
|
// src/lib/project.ts
|
|
2995
|
-
import
|
|
2996
|
-
import
|
|
3347
|
+
import path21 from "path";
|
|
3348
|
+
import fs23 from "fs";
|
|
2997
3349
|
function resolveProjectName(opts) {
|
|
2998
3350
|
if (opts.project && opts.project.length > 0) return opts.project;
|
|
2999
3351
|
const config = loadConfig();
|
|
@@ -3003,15 +3355,15 @@ function resolveProjectName(opts) {
|
|
|
3003
3355
|
"Cannot resolve project: no registered projects. Use `task0 source add <path>` first, or pass --project <name>."
|
|
3004
3356
|
);
|
|
3005
3357
|
}
|
|
3006
|
-
const cwd =
|
|
3358
|
+
const cwd = fs23.realpathSync(process.cwd());
|
|
3007
3359
|
for (const source2 of projects) {
|
|
3008
3360
|
let sourceAbs;
|
|
3009
3361
|
try {
|
|
3010
|
-
sourceAbs =
|
|
3362
|
+
sourceAbs = fs23.realpathSync(path21.resolve(source2.path));
|
|
3011
3363
|
} catch {
|
|
3012
|
-
sourceAbs =
|
|
3364
|
+
sourceAbs = path21.resolve(source2.path);
|
|
3013
3365
|
}
|
|
3014
|
-
if (cwd === sourceAbs || cwd.startsWith(sourceAbs +
|
|
3366
|
+
if (cwd === sourceAbs || cwd.startsWith(sourceAbs + path21.sep)) {
|
|
3015
3367
|
return source2.name;
|
|
3016
3368
|
}
|
|
3017
3369
|
}
|
|
@@ -3651,12 +4003,12 @@ import chalk17 from "chalk";
|
|
|
3651
4003
|
|
|
3652
4004
|
// src/core/issue/decision.ts
|
|
3653
4005
|
init_node();
|
|
3654
|
-
import
|
|
3655
|
-
import
|
|
4006
|
+
import fs24 from "fs";
|
|
4007
|
+
import path22 from "path";
|
|
3656
4008
|
init_task_state2();
|
|
3657
4009
|
var DECISION_FILE_RE = /^DECISION-(\d+)-([a-z0-9-]+)\.md$/;
|
|
3658
4010
|
function selectBlockingIssues(taskDir, explicitIssue) {
|
|
3659
|
-
const files =
|
|
4011
|
+
const files = fs24.existsSync(taskDir) ? fs24.readdirSync(taskDir) : [];
|
|
3660
4012
|
const issueFiles = files.filter((f) => /^ISSUE-\d+\.md$/.test(f)).sort();
|
|
3661
4013
|
const all = readOpenQuestions(taskDir, issueFiles);
|
|
3662
4014
|
if (!explicitIssue) return all;
|
|
@@ -3760,12 +4112,12 @@ function parseConsolidatedAnswers(md) {
|
|
|
3760
4112
|
return sections;
|
|
3761
4113
|
}
|
|
3762
4114
|
function rewriteIssueWithDecisions(taskDir, issueFile, consolidatedFile) {
|
|
3763
|
-
const issuePath =
|
|
3764
|
-
const consolidatedPath =
|
|
3765
|
-
if (!
|
|
3766
|
-
if (!
|
|
3767
|
-
const issueMd =
|
|
3768
|
-
const consolidatedMd =
|
|
4115
|
+
const issuePath = path22.join(taskDir, issueFile);
|
|
4116
|
+
const consolidatedPath = path22.join(taskDir, consolidatedFile);
|
|
4117
|
+
if (!fs24.existsSync(issuePath)) throw new Error(`${issueFile} not found in ${taskDir}`);
|
|
4118
|
+
if (!fs24.existsSync(consolidatedPath)) throw new Error(`${consolidatedFile} not found in ${taskDir}`);
|
|
4119
|
+
const issueMd = fs24.readFileSync(issuePath, "utf-8");
|
|
4120
|
+
const consolidatedMd = fs24.readFileSync(consolidatedPath, "utf-8");
|
|
3769
4121
|
const answers = parseConsolidatedAnswers(consolidatedMd);
|
|
3770
4122
|
if (answers.length === 0) {
|
|
3771
4123
|
throw new Error(`${consolidatedFile} has no "## Qn" sections; cannot derive Decisions`);
|
|
@@ -3787,7 +4139,7 @@ _Resolved from [${consolidatedFile}](${consolidatedFile}); see that file for rea
|
|
|
3787
4139
|
throw new Error(`${issueFile} has no "## Open Questions" section to replace`);
|
|
3788
4140
|
}
|
|
3789
4141
|
const next = issueMd.replace(openQRe, decisionsSection.trimEnd() + "\n");
|
|
3790
|
-
|
|
4142
|
+
fs24.writeFileSync(issuePath, next, "utf-8");
|
|
3791
4143
|
return { replaced: answers.length };
|
|
3792
4144
|
}
|
|
3793
4145
|
async function propose(opts) {
|
|
@@ -3815,8 +4167,8 @@ async function propose(opts) {
|
|
|
3815
4167
|
const kicks = [];
|
|
3816
4168
|
for (const agent2 of opts.agents) {
|
|
3817
4169
|
const decisionFile = decisionFileName(issue2, agent2);
|
|
3818
|
-
const decisionPath =
|
|
3819
|
-
if (
|
|
4170
|
+
const decisionPath = path22.join(loc.taskDir, decisionFile);
|
|
4171
|
+
if (fs24.existsSync(decisionPath) && !opts.force) {
|
|
3820
4172
|
if (opts.ifNeeded) {
|
|
3821
4173
|
kicks.push({
|
|
3822
4174
|
issue: issue2.file,
|
|
@@ -3891,7 +4243,7 @@ async function propose(opts) {
|
|
|
3891
4243
|
const final = await waitForAgentRun(k.agentRunId);
|
|
3892
4244
|
if (final.status !== "done") {
|
|
3893
4245
|
k.error = final.error || `runtime ${k.agentRunId} ended with ${final.status}`;
|
|
3894
|
-
} else if (!
|
|
4246
|
+
} else if (!fs24.existsSync(path22.join(loc.taskDir, k.decisionFile))) {
|
|
3895
4247
|
k.error = `runtime completed but ${k.decisionFile} was not written`;
|
|
3896
4248
|
}
|
|
3897
4249
|
} catch (err) {
|
|
@@ -3912,7 +4264,7 @@ async function consolidate(opts) {
|
|
|
3912
4264
|
const kicks = [];
|
|
3913
4265
|
const modelOpts = resolveModelOptions(opts.agent, { model: opts.model, effort: opts.effort }, opts.warn);
|
|
3914
4266
|
for (const issue2 of issues) {
|
|
3915
|
-
const files =
|
|
4267
|
+
const files = fs24.readdirSync(loc.taskDir);
|
|
3916
4268
|
const proposalFiles = files.filter((f) => {
|
|
3917
4269
|
const m = f.match(DECISION_FILE_RE);
|
|
3918
4270
|
return !!m && m[1] === issue2.index && m[2] !== "consolidated";
|
|
@@ -3958,7 +4310,7 @@ async function consolidate(opts) {
|
|
|
3958
4310
|
if (opts.wait) {
|
|
3959
4311
|
try {
|
|
3960
4312
|
const final = await waitForAgentRun(agentRunId);
|
|
3961
|
-
const wrote =
|
|
4313
|
+
const wrote = fs24.existsSync(path22.join(loc.taskDir, consolidatedFile));
|
|
3962
4314
|
if (final.status !== "done") {
|
|
3963
4315
|
kick.error = final.error || `runtime ${agentRunId} ended with ${final.status}`;
|
|
3964
4316
|
} else if (!wrote) {
|
|
@@ -4024,7 +4376,7 @@ async function approve(opts) {
|
|
|
4024
4376
|
return { updated };
|
|
4025
4377
|
}
|
|
4026
4378
|
function buildReferenceFiles(taskDir, issue2) {
|
|
4027
|
-
const names =
|
|
4379
|
+
const names = fs24.readdirSync(taskDir);
|
|
4028
4380
|
const refs = [issue2.file];
|
|
4029
4381
|
if (names.includes("ISSUE.md")) refs.push("ISSUE.md");
|
|
4030
4382
|
const ideaFile = `IDEA-${issue2.index}.md`;
|
|
@@ -4132,12 +4484,12 @@ issue.command("approve <taskId>").description("Apply the consolidated decisions
|
|
|
4132
4484
|
});
|
|
4133
4485
|
|
|
4134
4486
|
// src/commands/agent.ts
|
|
4135
|
-
import
|
|
4136
|
-
import
|
|
4487
|
+
import fs25 from "fs";
|
|
4488
|
+
import path23 from "path";
|
|
4137
4489
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
4138
4490
|
import { Command as Command18 } from "commander";
|
|
4139
4491
|
import chalk18 from "chalk";
|
|
4140
|
-
import
|
|
4492
|
+
import yaml9 from "js-yaml";
|
|
4141
4493
|
function statusBadge(s) {
|
|
4142
4494
|
if (s === "working") return chalk18.cyan("\u25CF");
|
|
4143
4495
|
if (s === "error") return chalk18.red("\u25CF");
|
|
@@ -4156,7 +4508,7 @@ function fail5(message, code = 1) {
|
|
|
4156
4508
|
process.exit(code);
|
|
4157
4509
|
}
|
|
4158
4510
|
function formatAgent(a, withDetails = false) {
|
|
4159
|
-
const tag = a.
|
|
4511
|
+
const tag = a.scope ? chalk18.dim(`(${a.scope})`) : "";
|
|
4160
4512
|
const dot = statusBadge(a.status);
|
|
4161
4513
|
const head = `${dot} ${chalk18.bold(a.slug.padEnd(24))} ${chalk18.dim(a.object_id.padEnd(16))} ${a.kind.padEnd(10)} ${tag}`;
|
|
4162
4514
|
if (!withDetails) return head;
|
|
@@ -4197,10 +4549,10 @@ agent.command("get <ref>").description("Show one agent by object_id or slug").op
|
|
|
4197
4549
|
}
|
|
4198
4550
|
console.log();
|
|
4199
4551
|
console.log(chalk18.dim("--- spec ---"));
|
|
4200
|
-
console.log(
|
|
4552
|
+
console.log(yaml9.dump(result.agent.spec, { lineWidth: 100 }));
|
|
4201
4553
|
if (result.agent.mcp_config) {
|
|
4202
4554
|
console.log(chalk18.dim("--- mcp_config ---"));
|
|
4203
|
-
console.log(
|
|
4555
|
+
console.log(yaml9.dump(result.agent.mcp_config, { lineWidth: 100 }));
|
|
4204
4556
|
}
|
|
4205
4557
|
} catch (err) {
|
|
4206
4558
|
const apiErr = err;
|
|
@@ -4211,8 +4563,8 @@ agent.command("get <ref>").description("Show one agent by object_id or slug").op
|
|
|
4211
4563
|
agent.command("create").description("Create an agent from a YAML spec file").requiredOption("--from-file <file>", "YAML spec file with at least kind, slug, spec").option("--scope <scope>", "Storage scope: user (default) or project", "user").option("--project-root <path>", "Project root (required for --scope project)").action(async (opts) => {
|
|
4212
4564
|
let parsed;
|
|
4213
4565
|
try {
|
|
4214
|
-
const raw =
|
|
4215
|
-
parsed =
|
|
4566
|
+
const raw = fs25.readFileSync(path23.resolve(opts.fromFile), "utf-8");
|
|
4567
|
+
parsed = yaml9.load(raw);
|
|
4216
4568
|
} catch (err) {
|
|
4217
4569
|
fail5(`cannot read ${opts.fromFile}: ${err.message}`);
|
|
4218
4570
|
}
|
|
@@ -4227,18 +4579,17 @@ agent.command("create").description("Create an agent from a YAML spec file").req
|
|
|
4227
4579
|
agent.command("edit <ref>").description("Open the agent YAML in $EDITOR and save changes").action(async (ref) => {
|
|
4228
4580
|
try {
|
|
4229
4581
|
const result = await api.get(`/api/agents/${encodeURIComponent(ref)}`);
|
|
4230
|
-
|
|
4231
|
-
const tmp = path21.join(
|
|
4582
|
+
const tmp = path23.join(
|
|
4232
4583
|
process.env.TMPDIR || "/tmp",
|
|
4233
4584
|
`task0-agent-${result.agent.slug}-${Date.now()}.yml`
|
|
4234
4585
|
);
|
|
4235
|
-
|
|
4586
|
+
fs25.writeFileSync(tmp, yaml9.dump(result.agent, { lineWidth: 100 }), "utf-8");
|
|
4236
4587
|
const editor = process.env.EDITOR || "vi";
|
|
4237
4588
|
const r = spawnSync5(editor, [tmp], { stdio: "inherit" });
|
|
4238
4589
|
if (r.status !== 0) fail5(`editor exited with status ${r.status}`);
|
|
4239
|
-
const updated =
|
|
4590
|
+
const updated = yaml9.load(fs25.readFileSync(tmp, "utf-8"));
|
|
4240
4591
|
await api.put(`/api/agents/${encodeURIComponent(ref)}`, updated);
|
|
4241
|
-
|
|
4592
|
+
fs25.unlinkSync(tmp);
|
|
4242
4593
|
console.log(chalk18.green(`updated ${ref}`));
|
|
4243
4594
|
} catch (err) {
|
|
4244
4595
|
fail5(err.message);
|
|
@@ -4309,7 +4660,7 @@ var mcp = new Command18("mcp").description("Manage per-agent MCP config (mcp_con
|
|
|
4309
4660
|
mcp.command("set <ref>").description("Set mcp_config from a JSON file (replaces existing config)").requiredOption("--from-file <file>", "JSON file containing the mcp_config object").action(async (ref, opts) => {
|
|
4310
4661
|
let parsed;
|
|
4311
4662
|
try {
|
|
4312
|
-
parsed = JSON.parse(
|
|
4663
|
+
parsed = JSON.parse(fs25.readFileSync(path23.resolve(opts.fromFile), "utf-8"));
|
|
4313
4664
|
} catch (err) {
|
|
4314
4665
|
fail5(`cannot parse ${opts.fromFile}: ${err.message}`);
|
|
4315
4666
|
}
|
|
@@ -4352,7 +4703,8 @@ async function streamOutput(ref, agentRunId) {
|
|
|
4352
4703
|
}
|
|
4353
4704
|
|
|
4354
4705
|
// src/commands/daemon.ts
|
|
4355
|
-
import
|
|
4706
|
+
import os6 from "os";
|
|
4707
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
4356
4708
|
import { Command as Command19 } from "commander";
|
|
4357
4709
|
import chalk19 from "chalk";
|
|
4358
4710
|
import WebSocket from "ws";
|
|
@@ -4431,7 +4783,7 @@ var RegisterAuthUnavailableError = class extends Error {
|
|
|
4431
4783
|
`No registration credential available.
|
|
4432
4784
|
\u2022 Pass --token <apit_...> with an API token created in the dashboard, or
|
|
4433
4785
|
\u2022 Set TASK0_API_TOKEN in the environment, or
|
|
4434
|
-
\u2022 Fall back to the server's admin token (TASK0_ADMIN_TOKEN or ~/.
|
|
4786
|
+
\u2022 Fall back to the server's admin token (TASK0_ADMIN_TOKEN or ~/.task0/admin.token).`
|
|
4435
4787
|
);
|
|
4436
4788
|
this.name = "RegisterAuthUnavailableError";
|
|
4437
4789
|
}
|
|
@@ -4457,36 +4809,36 @@ function pickRegisterAuth(flagToken) {
|
|
|
4457
4809
|
|
|
4458
4810
|
// src/core/daemon-rpc-handlers.ts
|
|
4459
4811
|
init_node();
|
|
4460
|
-
import
|
|
4461
|
-
import
|
|
4812
|
+
import fs28 from "fs";
|
|
4813
|
+
import path26 from "path";
|
|
4462
4814
|
|
|
4463
4815
|
// src/core/daemon-agent-run-runner.ts
|
|
4464
4816
|
import { execFileSync } from "child_process";
|
|
4465
|
-
import
|
|
4466
|
-
import
|
|
4817
|
+
import fs27 from "fs";
|
|
4818
|
+
import path25 from "path";
|
|
4467
4819
|
|
|
4468
4820
|
// src/core/daemon-agent-run-dir.ts
|
|
4469
|
-
|
|
4470
|
-
import
|
|
4471
|
-
import
|
|
4821
|
+
init_node();
|
|
4822
|
+
import fs26 from "fs";
|
|
4823
|
+
import path24 from "path";
|
|
4472
4824
|
function agentRunRoot() {
|
|
4473
|
-
return process.env.TASK0_DAEMON_AGENT_RUN_DIR ||
|
|
4825
|
+
return process.env.TASK0_DAEMON_AGENT_RUN_DIR || path24.join(profileDir(), "agent-run");
|
|
4474
4826
|
}
|
|
4475
4827
|
function agentRunDir(runId) {
|
|
4476
|
-
return
|
|
4828
|
+
return path24.join(agentRunRoot(), runId);
|
|
4477
4829
|
}
|
|
4478
4830
|
function agentRunStatusPath(runId) {
|
|
4479
|
-
return
|
|
4831
|
+
return path24.join(agentRunDir(runId), "status.json");
|
|
4480
4832
|
}
|
|
4481
4833
|
function ensureAgentRunDir(runId) {
|
|
4482
4834
|
const dir = agentRunDir(runId);
|
|
4483
|
-
|
|
4835
|
+
fs26.mkdirSync(dir, { recursive: true });
|
|
4484
4836
|
return dir;
|
|
4485
4837
|
}
|
|
4486
4838
|
function removeAgentRunDir(runId) {
|
|
4487
4839
|
const dir = agentRunDir(runId);
|
|
4488
|
-
if (!
|
|
4489
|
-
|
|
4840
|
+
if (!fs26.existsSync(dir)) return;
|
|
4841
|
+
fs26.rmSync(dir, { recursive: true, force: true });
|
|
4490
4842
|
}
|
|
4491
4843
|
|
|
4492
4844
|
// src/core/daemon-agent-run-runner.ts
|
|
@@ -4528,9 +4880,9 @@ function killSession2(sessionName) {
|
|
|
4528
4880
|
}
|
|
4529
4881
|
function readStatusFile(runId) {
|
|
4530
4882
|
const filePath = agentRunStatusPath(runId);
|
|
4531
|
-
if (!
|
|
4883
|
+
if (!fs27.existsSync(filePath)) return null;
|
|
4532
4884
|
try {
|
|
4533
|
-
const raw =
|
|
4885
|
+
const raw = fs27.readFileSync(filePath, "utf-8");
|
|
4534
4886
|
const parsed = JSON.parse(raw);
|
|
4535
4887
|
return { raw, parsed };
|
|
4536
4888
|
} catch {
|
|
@@ -4555,22 +4907,22 @@ function launchAgentRun(params) {
|
|
|
4555
4907
|
if (active.has(params.runId)) {
|
|
4556
4908
|
throw Object.assign(new Error(`agent run already active: ${params.runId}`), { code: "already_running" });
|
|
4557
4909
|
}
|
|
4558
|
-
if (!
|
|
4910
|
+
if (!fs27.existsSync(params.workspace)) {
|
|
4559
4911
|
throw Object.assign(new Error(`workspace not found: ${params.workspace}`), { code: "workspace_missing" });
|
|
4560
4912
|
}
|
|
4561
4913
|
const runDir = ensureAgentRunDir(params.runId);
|
|
4562
|
-
const scriptPath =
|
|
4563
|
-
|
|
4914
|
+
const scriptPath = path25.join(runDir, "script.sh");
|
|
4915
|
+
fs27.writeFileSync(scriptPath, params.scriptContent, { mode: 493 });
|
|
4564
4916
|
if (params.promptContent !== void 0) {
|
|
4565
|
-
|
|
4917
|
+
fs27.writeFileSync(path25.join(runDir, "prompt.txt"), params.promptContent, "utf-8");
|
|
4566
4918
|
}
|
|
4567
4919
|
for (const [name, content] of Object.entries(params.auxFiles ?? {})) {
|
|
4568
4920
|
if (name.includes("/") || name.includes("\\") || name === ".." || name === ".") {
|
|
4569
4921
|
throw Object.assign(new Error(`invalid aux file name: ${name}`), { code: "invalid_params" });
|
|
4570
4922
|
}
|
|
4571
|
-
|
|
4923
|
+
fs27.writeFileSync(path25.join(runDir, name), content, "utf-8");
|
|
4572
4924
|
}
|
|
4573
|
-
|
|
4925
|
+
fs27.writeFileSync(
|
|
4574
4926
|
agentRunStatusPath(params.runId),
|
|
4575
4927
|
JSON.stringify({ status: "starting", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2),
|
|
4576
4928
|
"utf-8"
|
|
@@ -4649,6 +5001,20 @@ function ensureString(value, name) {
|
|
|
4649
5001
|
}
|
|
4650
5002
|
return value;
|
|
4651
5003
|
}
|
|
5004
|
+
function ensureSafeTaskId(value) {
|
|
5005
|
+
const id = ensureString(value, "taskId");
|
|
5006
|
+
if (id.includes("/") || id.includes("\\") || id.includes("\0") || id === "." || id === ".." || path26.isAbsolute(id)) {
|
|
5007
|
+
throw Object.assign(new Error(`invalid taskId: ${id}`), { code: "invalid_params" });
|
|
5008
|
+
}
|
|
5009
|
+
return id;
|
|
5010
|
+
}
|
|
5011
|
+
function assertContained(rootAbs, resolvedAbs) {
|
|
5012
|
+
const root = path26.resolve(rootAbs);
|
|
5013
|
+
const target = path26.resolve(resolvedAbs);
|
|
5014
|
+
if (target !== root && !target.startsWith(root + path26.sep)) {
|
|
5015
|
+
throw Object.assign(new Error("path escapes containment root"), { code: "invalid_params" });
|
|
5016
|
+
}
|
|
5017
|
+
}
|
|
4652
5018
|
function optionalString(value) {
|
|
4653
5019
|
if (value === void 0 || value === null) return void 0;
|
|
4654
5020
|
if (typeof value !== "string") {
|
|
@@ -4687,7 +5053,7 @@ var rpcHandlers = {
|
|
|
4687
5053
|
// missing object_id / id mismatches applied in place on the daemon's FS.
|
|
4688
5054
|
async scan_project(params) {
|
|
4689
5055
|
const rootPath = ensureString(params.rootPath, "rootPath");
|
|
4690
|
-
const name = typeof params.name === "string" && params.name ? params.name :
|
|
5056
|
+
const name = typeof params.name === "string" && params.name ? params.name : path26.basename(rootPath);
|
|
4691
5057
|
const result = scanProjectWithRepair(rootPath, name);
|
|
4692
5058
|
return { tasks: result.tasks, errors: result.errors };
|
|
4693
5059
|
},
|
|
@@ -4698,7 +5064,7 @@ var rpcHandlers = {
|
|
|
4698
5064
|
const filePath = ensureString(params.path, "path");
|
|
4699
5065
|
let stat;
|
|
4700
5066
|
try {
|
|
4701
|
-
stat =
|
|
5067
|
+
stat = fs28.statSync(filePath);
|
|
4702
5068
|
} catch {
|
|
4703
5069
|
throw Object.assign(new Error("file not found"), { code: "not_found" });
|
|
4704
5070
|
}
|
|
@@ -4708,7 +5074,7 @@ var rpcHandlers = {
|
|
|
4708
5074
|
if (stat.size > MAX_FILE_BYTES) {
|
|
4709
5075
|
throw Object.assign(new Error(`file too large (${stat.size} bytes > ${MAX_FILE_BYTES})`), { code: "too_large" });
|
|
4710
5076
|
}
|
|
4711
|
-
const content =
|
|
5077
|
+
const content = fs28.readFileSync(filePath, "utf-8");
|
|
4712
5078
|
return { content, size: stat.size, modifiedAt: stat.mtime.toISOString() };
|
|
4713
5079
|
},
|
|
4714
5080
|
// ---------------------------------------------------------------------
|
|
@@ -4723,15 +5089,15 @@ var rpcHandlers = {
|
|
|
4723
5089
|
},
|
|
4724
5090
|
async project_add(params, ctx) {
|
|
4725
5091
|
const rawPath = ensureString(params.path, "path");
|
|
4726
|
-
const absPath =
|
|
4727
|
-
if (!
|
|
5092
|
+
const absPath = path26.resolve(rawPath);
|
|
5093
|
+
if (!fs28.existsSync(absPath)) {
|
|
4728
5094
|
throw Object.assign(new Error(`path does not exist: ${absPath}`), { code: "not_found" });
|
|
4729
5095
|
}
|
|
4730
|
-
const stat =
|
|
5096
|
+
const stat = fs28.statSync(absPath);
|
|
4731
5097
|
if (!stat.isDirectory()) {
|
|
4732
5098
|
throw Object.assign(new Error(`path is not a directory: ${absPath}`), { code: "invalid_target" });
|
|
4733
5099
|
}
|
|
4734
|
-
const name = optionalString(params.name)?.trim() ||
|
|
5100
|
+
const name = optionalString(params.name)?.trim() || path26.basename(absPath);
|
|
4735
5101
|
const result = scanProjectWithRepair(absPath, name);
|
|
4736
5102
|
addSource({ name, type: "project", path: absPath, enabled: true });
|
|
4737
5103
|
ctx.notifyManifestChanged();
|
|
@@ -4775,36 +5141,38 @@ var rpcHandlers = {
|
|
|
4775
5141
|
// in the same task directory. Path is resolved from (projectName, taskId).
|
|
4776
5142
|
async task_read(params) {
|
|
4777
5143
|
const projectName = ensureString(params.projectName, "projectName");
|
|
4778
|
-
const taskId =
|
|
5144
|
+
const taskId = ensureSafeTaskId(params.taskId);
|
|
4779
5145
|
const project2 = listProjects().find((p) => p.name === projectName);
|
|
4780
5146
|
if (!project2) {
|
|
4781
5147
|
throw Object.assign(new Error(`project not found: ${projectName}`), { code: "not_found" });
|
|
4782
5148
|
}
|
|
4783
|
-
const projectAbs =
|
|
4784
|
-
const projectConfig = readYaml(
|
|
5149
|
+
const projectAbs = path26.resolve(project2.path);
|
|
5150
|
+
const projectConfig = readYaml(path26.join(projectAbs, "task0.yml"));
|
|
4785
5151
|
if (!projectConfig) {
|
|
4786
5152
|
throw Object.assign(new Error(`invalid project: missing task0.yml at ${projectAbs}`), { code: "invalid_project" });
|
|
4787
5153
|
}
|
|
4788
|
-
const
|
|
4789
|
-
|
|
5154
|
+
const tasksRoot = path26.resolve(projectAbs, projectConfig.tasks_dir);
|
|
5155
|
+
const taskDir = path26.resolve(tasksRoot, taskId);
|
|
5156
|
+
assertContained(tasksRoot, taskDir);
|
|
5157
|
+
if (!fs28.existsSync(taskDir)) {
|
|
4790
5158
|
throw Object.assign(new Error(`task not found: ${taskId}`), { code: "not_found" });
|
|
4791
5159
|
}
|
|
4792
|
-
const taskYml =
|
|
4793
|
-
const
|
|
4794
|
-
if (!
|
|
5160
|
+
const taskYml = path26.join(taskDir, "task0.yml");
|
|
5161
|
+
const yaml11 = readYaml(taskYml);
|
|
5162
|
+
if (!yaml11) {
|
|
4795
5163
|
throw Object.assign(new Error(`task yaml missing or unreadable: ${taskYml}`), { code: "not_found" });
|
|
4796
5164
|
}
|
|
4797
|
-
const files =
|
|
4798
|
-
return { task_dir: taskDir, yaml:
|
|
5165
|
+
const files = fs28.readdirSync(taskDir).filter((name) => name !== "task0.yml");
|
|
5166
|
+
return { task_dir: taskDir, yaml: yaml11, files };
|
|
4799
5167
|
},
|
|
4800
5168
|
// Create a task directory + write task0.yml + write any additional named
|
|
4801
5169
|
// files. Hub decides the taskId and yaml content (slug/object_id are
|
|
4802
5170
|
// generated server-side); daemon just commits to disk.
|
|
4803
5171
|
async task_create(params, ctx) {
|
|
4804
5172
|
const projectName = ensureString(params.projectName, "projectName");
|
|
4805
|
-
const taskId =
|
|
4806
|
-
const
|
|
4807
|
-
if (!
|
|
5173
|
+
const taskId = ensureSafeTaskId(params.taskId);
|
|
5174
|
+
const yaml11 = params.yaml;
|
|
5175
|
+
if (!yaml11 || typeof yaml11 !== "object") {
|
|
4808
5176
|
throw Object.assign(new Error("yaml (object) is required"), { code: "invalid_params" });
|
|
4809
5177
|
}
|
|
4810
5178
|
const files = params.files ?? {};
|
|
@@ -4812,22 +5180,24 @@ var rpcHandlers = {
|
|
|
4812
5180
|
if (!project2) {
|
|
4813
5181
|
throw Object.assign(new Error(`project not found: ${projectName}`), { code: "not_found" });
|
|
4814
5182
|
}
|
|
4815
|
-
const projectAbs =
|
|
4816
|
-
const projectConfig = readYaml(
|
|
5183
|
+
const projectAbs = path26.resolve(project2.path);
|
|
5184
|
+
const projectConfig = readYaml(path26.join(projectAbs, "task0.yml"));
|
|
4817
5185
|
if (!projectConfig) {
|
|
4818
5186
|
throw Object.assign(new Error(`invalid project: missing task0.yml at ${projectAbs}`), { code: "invalid_project" });
|
|
4819
5187
|
}
|
|
4820
|
-
const
|
|
4821
|
-
|
|
5188
|
+
const tasksRoot = path26.resolve(projectAbs, projectConfig.tasks_dir);
|
|
5189
|
+
const taskDir = path26.resolve(tasksRoot, taskId);
|
|
5190
|
+
assertContained(tasksRoot, taskDir);
|
|
5191
|
+
if (fs28.existsSync(taskDir)) {
|
|
4822
5192
|
throw Object.assign(new Error(`task already exists: ${taskId}`), { code: "already_exists" });
|
|
4823
5193
|
}
|
|
4824
|
-
|
|
4825
|
-
writeYaml(
|
|
5194
|
+
fs28.mkdirSync(taskDir, { recursive: true });
|
|
5195
|
+
writeYaml(path26.join(taskDir, "task0.yml"), yaml11);
|
|
4826
5196
|
for (const [name, content] of Object.entries(files)) {
|
|
4827
5197
|
if (name.includes("/") || name.includes("\\") || name === ".." || name === ".") {
|
|
4828
5198
|
throw Object.assign(new Error(`invalid file name: ${name}`), { code: "invalid_params" });
|
|
4829
5199
|
}
|
|
4830
|
-
|
|
5200
|
+
fs28.writeFileSync(path26.join(taskDir, name), content, "utf-8");
|
|
4831
5201
|
}
|
|
4832
5202
|
ctx.notifyManifestChanged();
|
|
4833
5203
|
return { task_dir: taskDir };
|
|
@@ -4838,26 +5208,28 @@ var rpcHandlers = {
|
|
|
4838
5208
|
// partial writes.
|
|
4839
5209
|
async task_write_yaml(params, ctx) {
|
|
4840
5210
|
const projectName = ensureString(params.projectName, "projectName");
|
|
4841
|
-
const taskId =
|
|
4842
|
-
const
|
|
4843
|
-
if (!
|
|
5211
|
+
const taskId = ensureSafeTaskId(params.taskId);
|
|
5212
|
+
const yaml11 = params.yaml;
|
|
5213
|
+
if (!yaml11 || typeof yaml11 !== "object") {
|
|
4844
5214
|
throw Object.assign(new Error("yaml (object) is required"), { code: "invalid_params" });
|
|
4845
5215
|
}
|
|
4846
5216
|
const project2 = listProjects().find((p) => p.name === projectName);
|
|
4847
5217
|
if (!project2) {
|
|
4848
5218
|
throw Object.assign(new Error(`project not found: ${projectName}`), { code: "not_found" });
|
|
4849
5219
|
}
|
|
4850
|
-
const projectAbs =
|
|
4851
|
-
const projectConfig = readYaml(
|
|
5220
|
+
const projectAbs = path26.resolve(project2.path);
|
|
5221
|
+
const projectConfig = readYaml(path26.join(projectAbs, "task0.yml"));
|
|
4852
5222
|
if (!projectConfig) {
|
|
4853
5223
|
throw Object.assign(new Error(`invalid project: missing task0.yml at ${projectAbs}`), { code: "invalid_project" });
|
|
4854
5224
|
}
|
|
4855
|
-
const
|
|
4856
|
-
const
|
|
4857
|
-
|
|
5225
|
+
const tasksRoot = path26.resolve(projectAbs, projectConfig.tasks_dir);
|
|
5226
|
+
const taskDir = path26.resolve(tasksRoot, taskId);
|
|
5227
|
+
assertContained(tasksRoot, taskDir);
|
|
5228
|
+
const taskYml = path26.join(taskDir, "task0.yml");
|
|
5229
|
+
if (!fs28.existsSync(taskYml)) {
|
|
4858
5230
|
throw Object.assign(new Error(`task yaml not found: ${taskYml}`), { code: "not_found" });
|
|
4859
5231
|
}
|
|
4860
|
-
writeYaml(taskYml,
|
|
5232
|
+
writeYaml(taskYml, yaml11);
|
|
4861
5233
|
ctx.notifyManifestChanged();
|
|
4862
5234
|
return { task_dir: taskDir };
|
|
4863
5235
|
},
|
|
@@ -4865,21 +5237,23 @@ var rpcHandlers = {
|
|
|
4865
5237
|
// workflows). Returns the deleted path so the hub can emit an event.
|
|
4866
5238
|
async task_delete(params, ctx) {
|
|
4867
5239
|
const projectName = ensureString(params.projectName, "projectName");
|
|
4868
|
-
const taskId =
|
|
5240
|
+
const taskId = ensureSafeTaskId(params.taskId);
|
|
4869
5241
|
const project2 = listProjects().find((p) => p.name === projectName);
|
|
4870
5242
|
if (!project2) {
|
|
4871
5243
|
throw Object.assign(new Error(`project not found: ${projectName}`), { code: "not_found" });
|
|
4872
5244
|
}
|
|
4873
|
-
const projectAbs =
|
|
4874
|
-
const projectConfig = readYaml(
|
|
5245
|
+
const projectAbs = path26.resolve(project2.path);
|
|
5246
|
+
const projectConfig = readYaml(path26.join(projectAbs, "task0.yml"));
|
|
4875
5247
|
if (!projectConfig) {
|
|
4876
5248
|
throw Object.assign(new Error(`invalid project: missing task0.yml at ${projectAbs}`), { code: "invalid_project" });
|
|
4877
5249
|
}
|
|
4878
|
-
const
|
|
4879
|
-
|
|
5250
|
+
const tasksRoot = path26.resolve(projectAbs, projectConfig.tasks_dir);
|
|
5251
|
+
const taskDir = path26.resolve(tasksRoot, taskId);
|
|
5252
|
+
assertContained(tasksRoot, taskDir);
|
|
5253
|
+
if (!fs28.existsSync(taskDir)) {
|
|
4880
5254
|
throw Object.assign(new Error(`task not found: ${taskId}`), { code: "not_found" });
|
|
4881
5255
|
}
|
|
4882
|
-
|
|
5256
|
+
fs28.rmSync(taskDir, { recursive: true, force: true });
|
|
4883
5257
|
ctx.notifyManifestChanged();
|
|
4884
5258
|
return { task_dir: taskDir, deleted: true };
|
|
4885
5259
|
},
|
|
@@ -4910,9 +5284,9 @@ var rpcHandlers = {
|
|
|
4910
5284
|
const projectSkills = getProjectAgentSkills(workspace);
|
|
4911
5285
|
const globalSkills = getGlobalAgentSkills();
|
|
4912
5286
|
const all = [...projectSkills, ...globalSkills];
|
|
4913
|
-
const resolved =
|
|
5287
|
+
const resolved = path26.resolve(filePath);
|
|
4914
5288
|
const skill = all.find(
|
|
4915
|
-
(s) => s.agent === agent2 &&
|
|
5289
|
+
(s) => s.agent === agent2 && path26.resolve(s.filePath) === resolved
|
|
4916
5290
|
) ?? null;
|
|
4917
5291
|
return { skill };
|
|
4918
5292
|
},
|
|
@@ -4945,16 +5319,16 @@ var rpcHandlers = {
|
|
|
4945
5319
|
|
|
4946
5320
|
// src/core/daemon-service/launchd.ts
|
|
4947
5321
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
4948
|
-
import
|
|
4949
|
-
import
|
|
5322
|
+
import fs30 from "fs";
|
|
5323
|
+
import path28 from "path";
|
|
4950
5324
|
|
|
4951
5325
|
// src/core/daemon-service/binary.ts
|
|
4952
|
-
import
|
|
5326
|
+
import fs29 from "fs";
|
|
4953
5327
|
import { fileURLToPath } from "url";
|
|
4954
5328
|
function resolveTask0Invocation() {
|
|
4955
5329
|
const node = process.execPath;
|
|
4956
5330
|
const argv1 = process.argv[1] ?? fileURLToPath(import.meta.url);
|
|
4957
|
-
const main2 =
|
|
5331
|
+
const main2 = fs29.realpathSync(argv1);
|
|
4958
5332
|
if (!isInstalledBuild(main2) && process.env.TASK0_ALLOW_DEV_SERVICE !== "1") {
|
|
4959
5333
|
throw new Error(
|
|
4960
5334
|
`Refusing to install autostart service pointing at ${main2}.
|
|
@@ -4970,31 +5344,44 @@ function isInstalledBuild(p) {
|
|
|
4970
5344
|
}
|
|
4971
5345
|
|
|
4972
5346
|
// src/core/daemon-service/paths.ts
|
|
4973
|
-
|
|
4974
|
-
import
|
|
5347
|
+
init_node();
|
|
5348
|
+
import os5 from "os";
|
|
5349
|
+
import path27 from "path";
|
|
4975
5350
|
|
|
4976
5351
|
// src/core/daemon-service/types.ts
|
|
4977
|
-
|
|
5352
|
+
init_node();
|
|
5353
|
+
import crypto2 from "crypto";
|
|
5354
|
+
var BASE_SERVICE_LABEL = "cc.cy0.task0";
|
|
5355
|
+
function serviceLabel() {
|
|
5356
|
+
const home = process.env.TASK0_HOME ?? "";
|
|
5357
|
+
const profile3 = currentProfileName();
|
|
5358
|
+
if ((!home || home.length === 0) && profile3 === DEFAULT_PROFILE_NAME) {
|
|
5359
|
+
return BASE_SERVICE_LABEL;
|
|
5360
|
+
}
|
|
5361
|
+
const hash = crypto2.createHash("sha256").update(`${home}\0${profile3}`).digest("hex").slice(0, 8);
|
|
5362
|
+
return `${BASE_SERVICE_LABEL}.${hash}`;
|
|
5363
|
+
}
|
|
4978
5364
|
|
|
4979
5365
|
// src/core/daemon-service/paths.ts
|
|
4980
5366
|
function unitPath(scope) {
|
|
4981
5367
|
const platform = process.platform;
|
|
5368
|
+
const label = serviceLabel();
|
|
4982
5369
|
if (platform === "darwin") {
|
|
4983
|
-
return scope === "user" ?
|
|
5370
|
+
return scope === "user" ? path27.join(os5.homedir(), "Library", "LaunchAgents", `${label}.plist`) : path27.join("/", "Library", "LaunchDaemons", `${label}.plist`);
|
|
4984
5371
|
}
|
|
4985
5372
|
if (platform === "linux") {
|
|
4986
|
-
return scope === "user" ?
|
|
5373
|
+
return scope === "user" ? path27.join(os5.homedir(), ".config", "systemd", "user", `${label}.service`) : path27.join("/", "etc", "systemd", "system", `${label}.service`);
|
|
4987
5374
|
}
|
|
4988
5375
|
throw new Error(`Unsupported platform for service install: ${platform}`);
|
|
4989
5376
|
}
|
|
4990
5377
|
function logDir() {
|
|
4991
|
-
return
|
|
5378
|
+
return path27.join(profileDir(), "logs");
|
|
4992
5379
|
}
|
|
4993
5380
|
function logPaths() {
|
|
4994
5381
|
const dir = logDir();
|
|
4995
5382
|
return {
|
|
4996
|
-
out:
|
|
4997
|
-
err:
|
|
5383
|
+
out: path27.join(dir, "daemon.out.log"),
|
|
5384
|
+
err: path27.join(dir, "daemon.err.log")
|
|
4998
5385
|
};
|
|
4999
5386
|
}
|
|
5000
5387
|
|
|
@@ -5002,14 +5389,29 @@ function logPaths() {
|
|
|
5002
5389
|
function escapeXml(s) {
|
|
5003
5390
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
5004
5391
|
}
|
|
5392
|
+
function collectTask0Env() {
|
|
5393
|
+
const out = {};
|
|
5394
|
+
for (const [k, v] of Object.entries(process.env)) {
|
|
5395
|
+
if (k.startsWith("TASK0_") && typeof v === "string") out[k] = v;
|
|
5396
|
+
}
|
|
5397
|
+
return out;
|
|
5398
|
+
}
|
|
5005
5399
|
function renderPlist(opts) {
|
|
5006
5400
|
const programArgs = [opts.node, opts.main, ...opts.args].map((a) => ` <string>${escapeXml(a)}</string>`).join("\n");
|
|
5401
|
+
const envLines = [
|
|
5402
|
+
` <key>PATH</key>`,
|
|
5403
|
+
` <string>/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin</string>`,
|
|
5404
|
+
...Object.entries(opts.task0Env ?? {}).flatMap(([k, v]) => [
|
|
5405
|
+
` <key>${escapeXml(k)}</key>`,
|
|
5406
|
+
` <string>${escapeXml(v)}</string>`
|
|
5407
|
+
])
|
|
5408
|
+
].join("\n");
|
|
5007
5409
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
5008
5410
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
5009
5411
|
<plist version="1.0">
|
|
5010
5412
|
<dict>
|
|
5011
5413
|
<key>Label</key>
|
|
5012
|
-
<string>${
|
|
5414
|
+
<string>${serviceLabel()}</string>
|
|
5013
5415
|
<key>ProgramArguments</key>
|
|
5014
5416
|
<array>
|
|
5015
5417
|
${programArgs}
|
|
@@ -5031,8 +5433,7 @@ ${programArgs}
|
|
|
5031
5433
|
<string>${escapeXml(opts.home)}</string>
|
|
5032
5434
|
<key>EnvironmentVariables</key>
|
|
5033
5435
|
<dict>
|
|
5034
|
-
|
|
5035
|
-
<string>/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin</string>
|
|
5436
|
+
${envLines}
|
|
5036
5437
|
</dict>
|
|
5037
5438
|
</dict>
|
|
5038
5439
|
</plist>
|
|
@@ -5044,7 +5445,7 @@ function domainTarget(scope) {
|
|
|
5044
5445
|
return `gui/${uid}`;
|
|
5045
5446
|
}
|
|
5046
5447
|
function serviceTarget(scope) {
|
|
5047
|
-
return `${domainTarget(scope)}/${
|
|
5448
|
+
return `${domainTarget(scope)}/${serviceLabel()}`;
|
|
5048
5449
|
}
|
|
5049
5450
|
function run2(cmd, args) {
|
|
5050
5451
|
const res = spawnSync6(cmd, args, { encoding: "utf-8" });
|
|
@@ -5055,17 +5456,18 @@ function createLaunchdManager(scope) {
|
|
|
5055
5456
|
const logs = logPaths();
|
|
5056
5457
|
async function install() {
|
|
5057
5458
|
const inv = resolveTask0Invocation();
|
|
5058
|
-
|
|
5059
|
-
|
|
5459
|
+
fs30.mkdirSync(logDir(), { recursive: true });
|
|
5460
|
+
fs30.mkdirSync(path28.dirname(file), { recursive: true });
|
|
5060
5461
|
const body = renderPlist({
|
|
5061
5462
|
node: inv.node,
|
|
5062
5463
|
main: inv.main,
|
|
5063
5464
|
args: inv.args,
|
|
5064
5465
|
home: process.env.HOME ?? "/",
|
|
5065
5466
|
out: logs.out,
|
|
5066
|
-
err: logs.err
|
|
5467
|
+
err: logs.err,
|
|
5468
|
+
task0Env: collectTask0Env()
|
|
5067
5469
|
});
|
|
5068
|
-
|
|
5470
|
+
fs30.writeFileSync(file, body, { mode: 420 });
|
|
5069
5471
|
const bootstrap = run2("launchctl", ["bootstrap", domainTarget(scope), file]);
|
|
5070
5472
|
if (bootstrap.code !== 0) {
|
|
5071
5473
|
const already = /already loaded|service already bootstrapped/i.test(bootstrap.stderr);
|
|
@@ -5091,9 +5493,9 @@ function createLaunchdManager(scope) {
|
|
|
5091
5493
|
}
|
|
5092
5494
|
async function uninstall() {
|
|
5093
5495
|
run2("launchctl", ["bootout", serviceTarget(scope)]);
|
|
5094
|
-
if (
|
|
5496
|
+
if (fs30.existsSync(file)) {
|
|
5095
5497
|
run2("launchctl", ["unload", file]);
|
|
5096
|
-
|
|
5498
|
+
fs30.unlinkSync(file);
|
|
5097
5499
|
}
|
|
5098
5500
|
}
|
|
5099
5501
|
async function start() {
|
|
@@ -5112,7 +5514,7 @@ function createLaunchdManager(scope) {
|
|
|
5112
5514
|
}
|
|
5113
5515
|
}
|
|
5114
5516
|
async function status() {
|
|
5115
|
-
if (!
|
|
5517
|
+
if (!fs30.existsSync(file)) return "absent";
|
|
5116
5518
|
const printed = run2("launchctl", ["print", serviceTarget(scope)]);
|
|
5117
5519
|
if (printed.code !== 0) return "installed";
|
|
5118
5520
|
const out = printed.stdout;
|
|
@@ -5135,15 +5537,26 @@ function createLaunchdManager(scope) {
|
|
|
5135
5537
|
|
|
5136
5538
|
// src/core/daemon-service/systemd.ts
|
|
5137
5539
|
import { spawnSync as spawnSync7 } from "child_process";
|
|
5138
|
-
import
|
|
5139
|
-
import
|
|
5540
|
+
import fs31 from "fs";
|
|
5541
|
+
import path29 from "path";
|
|
5140
5542
|
function shellEscape(s) {
|
|
5141
5543
|
if (!/[\s"\\$]/.test(s)) return s;
|
|
5142
5544
|
return `"${s.replace(/[\\"]/g, (m) => `\\${m}`)}"`;
|
|
5143
5545
|
}
|
|
5546
|
+
function collectTask0Env2() {
|
|
5547
|
+
const out = {};
|
|
5548
|
+
for (const [k, v] of Object.entries(process.env)) {
|
|
5549
|
+
if (k.startsWith("TASK0_") && typeof v === "string") out[k] = v;
|
|
5550
|
+
}
|
|
5551
|
+
return out;
|
|
5552
|
+
}
|
|
5144
5553
|
function renderUnit(opts) {
|
|
5145
5554
|
const execStart = [opts.node, opts.main, ...opts.args].map(shellEscape).join(" ");
|
|
5146
5555
|
const wantedBy = opts.scope === "user" ? "default.target" : "multi-user.target";
|
|
5556
|
+
const envLines = ["Environment=NODE_ENV=production"];
|
|
5557
|
+
for (const [k, v] of Object.entries(opts.task0Env ?? {})) {
|
|
5558
|
+
envLines.push(`Environment=${k}=${shellEscape(v)}`);
|
|
5559
|
+
}
|
|
5147
5560
|
return `[Unit]
|
|
5148
5561
|
Description=task0 daemon \u2014 central-server bridge
|
|
5149
5562
|
After=network-online.target
|
|
@@ -5156,7 +5569,7 @@ Restart=on-failure
|
|
|
5156
5569
|
RestartSec=5s
|
|
5157
5570
|
StandardOutput=append:${opts.out}
|
|
5158
5571
|
StandardError=append:${opts.err}
|
|
5159
|
-
|
|
5572
|
+
${envLines.join("\n")}
|
|
5160
5573
|
|
|
5161
5574
|
[Install]
|
|
5162
5575
|
WantedBy=${wantedBy}
|
|
@@ -5172,20 +5585,21 @@ function run3(cmd, args) {
|
|
|
5172
5585
|
function createSystemdManager(scope) {
|
|
5173
5586
|
const file = unitPath(scope);
|
|
5174
5587
|
const logs = logPaths();
|
|
5175
|
-
const unitName = `${
|
|
5588
|
+
const unitName = `${serviceLabel()}.service`;
|
|
5176
5589
|
async function install() {
|
|
5177
5590
|
const inv = resolveTask0Invocation();
|
|
5178
|
-
|
|
5179
|
-
|
|
5591
|
+
fs31.mkdirSync(logDir(), { recursive: true });
|
|
5592
|
+
fs31.mkdirSync(path29.dirname(file), { recursive: true });
|
|
5180
5593
|
const body = renderUnit({
|
|
5181
5594
|
node: inv.node,
|
|
5182
5595
|
main: inv.main,
|
|
5183
5596
|
args: inv.args,
|
|
5184
5597
|
out: logs.out,
|
|
5185
5598
|
err: logs.err,
|
|
5186
|
-
scope
|
|
5599
|
+
scope,
|
|
5600
|
+
task0Env: collectTask0Env2()
|
|
5187
5601
|
});
|
|
5188
|
-
|
|
5602
|
+
fs31.writeFileSync(file, body, { mode: 420 });
|
|
5189
5603
|
const reload = run3("systemctl", [...scopeFlag(scope), "daemon-reload"]);
|
|
5190
5604
|
if (reload.code !== 0) {
|
|
5191
5605
|
throw new Error(`systemctl daemon-reload failed: ${reload.stderr}`);
|
|
@@ -5194,8 +5608,8 @@ function createSystemdManager(scope) {
|
|
|
5194
5608
|
}
|
|
5195
5609
|
async function uninstall() {
|
|
5196
5610
|
run3("systemctl", [...scopeFlag(scope), "disable", "--now", unitName]);
|
|
5197
|
-
if (
|
|
5198
|
-
|
|
5611
|
+
if (fs31.existsSync(file)) {
|
|
5612
|
+
fs31.unlinkSync(file);
|
|
5199
5613
|
}
|
|
5200
5614
|
run3("systemctl", [...scopeFlag(scope), "daemon-reload"]);
|
|
5201
5615
|
}
|
|
@@ -5212,7 +5626,7 @@ function createSystemdManager(scope) {
|
|
|
5212
5626
|
}
|
|
5213
5627
|
}
|
|
5214
5628
|
async function status() {
|
|
5215
|
-
if (!
|
|
5629
|
+
if (!fs31.existsSync(file)) return "absent";
|
|
5216
5630
|
const res = run3("systemctl", [...scopeFlag(scope), "is-active", unitName]);
|
|
5217
5631
|
const out = res.stdout.trim();
|
|
5218
5632
|
if (out === "active") return "running";
|
|
@@ -5248,14 +5662,14 @@ function getServiceManager(scope) {
|
|
|
5248
5662
|
// src/core/cli-version.ts
|
|
5249
5663
|
import { readFileSync } from "fs";
|
|
5250
5664
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5251
|
-
import
|
|
5665
|
+
import path30 from "path";
|
|
5252
5666
|
var cached2 = null;
|
|
5253
5667
|
function readCliVersion() {
|
|
5254
5668
|
if (cached2 !== null) return cached2;
|
|
5255
|
-
const here =
|
|
5669
|
+
const here = path30.dirname(fileURLToPath2(import.meta.url));
|
|
5256
5670
|
const candidates = [
|
|
5257
|
-
|
|
5258
|
-
|
|
5671
|
+
path30.resolve(here, "..", "package.json"),
|
|
5672
|
+
path30.resolve(here, "..", "..", "package.json")
|
|
5259
5673
|
];
|
|
5260
5674
|
for (const candidate of candidates) {
|
|
5261
5675
|
try {
|
|
@@ -5331,6 +5745,41 @@ function pushManifest(ws) {
|
|
|
5331
5745
|
const manifest = buildManifest();
|
|
5332
5746
|
ws.send(JSON.stringify(manifest));
|
|
5333
5747
|
}
|
|
5748
|
+
var AGENT_PROVIDER_BINARIES = {
|
|
5749
|
+
"claude-code": "claude",
|
|
5750
|
+
codex: "codex",
|
|
5751
|
+
"cursor-agent": "cursor-agent"
|
|
5752
|
+
};
|
|
5753
|
+
function detectAgentProvider(provider) {
|
|
5754
|
+
const binary = AGENT_PROVIDER_BINARIES[provider];
|
|
5755
|
+
let resolvedPath;
|
|
5756
|
+
try {
|
|
5757
|
+
resolvedPath = execFileSync2("which", [binary], {
|
|
5758
|
+
encoding: "utf-8",
|
|
5759
|
+
timeout: 2e3,
|
|
5760
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
5761
|
+
}).trim();
|
|
5762
|
+
} catch {
|
|
5763
|
+
return null;
|
|
5764
|
+
}
|
|
5765
|
+
if (!resolvedPath) return null;
|
|
5766
|
+
let version = null;
|
|
5767
|
+
try {
|
|
5768
|
+
version = execFileSync2(binary, ["--version"], {
|
|
5769
|
+
encoding: "utf-8",
|
|
5770
|
+
timeout: 3e3,
|
|
5771
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
5772
|
+
}).trim();
|
|
5773
|
+
} catch {
|
|
5774
|
+
version = null;
|
|
5775
|
+
}
|
|
5776
|
+
return { agent_provider: provider, path: resolvedPath, version };
|
|
5777
|
+
}
|
|
5778
|
+
function detectInstalledAgentProviders() {
|
|
5779
|
+
return AGENT_PROVIDERS.map(detectAgentProvider).filter(
|
|
5780
|
+
(entry) => entry !== null
|
|
5781
|
+
);
|
|
5782
|
+
}
|
|
5334
5783
|
function fail6(message, code = 1) {
|
|
5335
5784
|
console.error(chalk19.red(message));
|
|
5336
5785
|
process.exit(code);
|
|
@@ -5401,7 +5850,7 @@ daemonCmd.command("register").description("Register this host with a central ser
|
|
|
5401
5850
|
throw error2;
|
|
5402
5851
|
}
|
|
5403
5852
|
const body = {
|
|
5404
|
-
hostname:
|
|
5853
|
+
hostname: os6.hostname(),
|
|
5405
5854
|
platform: process.platform,
|
|
5406
5855
|
name: opts.name
|
|
5407
5856
|
};
|
|
@@ -5506,12 +5955,23 @@ daemonCmd.command("run").description("Run the daemon WebSocket loop in foregroun
|
|
|
5506
5955
|
ws.on("open", () => {
|
|
5507
5956
|
reconnectDelay = 1e3;
|
|
5508
5957
|
console.log(chalk19.green(`[${(/* @__PURE__ */ new Date()).toISOString()}] connected`));
|
|
5958
|
+
const installedAgentProviders = detectInstalledAgentProviders();
|
|
5959
|
+
if (installedAgentProviders.length > 0) {
|
|
5960
|
+
console.log(
|
|
5961
|
+
chalk19.dim(
|
|
5962
|
+
`detected agent providers: ${installedAgentProviders.map((p) => `${p.agent_provider}${p.version ? ` (${p.version})` : ""}`).join(", ")}`
|
|
5963
|
+
)
|
|
5964
|
+
);
|
|
5965
|
+
} else {
|
|
5966
|
+
console.log(chalk19.yellow("no agent providers detected on PATH (claude / codex / cursor-agent)"));
|
|
5967
|
+
}
|
|
5509
5968
|
const hello = {
|
|
5510
5969
|
type: "hello",
|
|
5511
5970
|
daemon_id: identity.daemon_id,
|
|
5512
5971
|
version: readCliVersion(),
|
|
5513
5972
|
hostname: identity.hostname,
|
|
5514
|
-
platform: identity.platform
|
|
5973
|
+
platform: identity.platform,
|
|
5974
|
+
installed_agent_providers: installedAgentProviders
|
|
5515
5975
|
};
|
|
5516
5976
|
ws.send(JSON.stringify(hello));
|
|
5517
5977
|
const manifest = buildManifest();
|
|
@@ -5688,16 +6148,15 @@ daemonCmd.command("logout").description("Stop and uninstall the autostart servic
|
|
|
5688
6148
|
// src/commands/user.ts
|
|
5689
6149
|
import { Command as Command20 } from "commander";
|
|
5690
6150
|
import chalk20 from "chalk";
|
|
5691
|
-
var DEFAULT_BASE = (process.env.TASK0_API_URL || "http://127.0.0.1:4318").replace(/\/$/, "");
|
|
5692
6151
|
function serverBase2() {
|
|
5693
|
-
return
|
|
6152
|
+
return (process.env.TASK0_API_URL || "http://127.0.0.1:4318").replace(/\/$/, "");
|
|
5694
6153
|
}
|
|
5695
6154
|
function fail7(message, code = 1) {
|
|
5696
6155
|
console.error(chalk20.red(message));
|
|
5697
6156
|
process.exit(code);
|
|
5698
6157
|
}
|
|
5699
|
-
async function callServer(
|
|
5700
|
-
const url = `${serverBase2()}${
|
|
6158
|
+
async function callServer(path32, init = {}) {
|
|
6159
|
+
const url = `${serverBase2()}${path32}`;
|
|
5701
6160
|
let auth;
|
|
5702
6161
|
try {
|
|
5703
6162
|
auth = adminAuthHeader();
|
|
@@ -6200,6 +6659,186 @@ automation.command("runs <id>").description("List recent runs for an automation"
|
|
|
6200
6659
|
for (const run4 of automation_runs) printRun(run4);
|
|
6201
6660
|
});
|
|
6202
6661
|
|
|
6662
|
+
// src/commands/profile.ts
|
|
6663
|
+
init_node();
|
|
6664
|
+
import fs32 from "fs";
|
|
6665
|
+
import path31 from "path";
|
|
6666
|
+
import { Command as Command23 } from "commander";
|
|
6667
|
+
import chalk23 from "chalk";
|
|
6668
|
+
import yaml10 from "js-yaml";
|
|
6669
|
+
function fail9(msg) {
|
|
6670
|
+
console.error(chalk23.red(msg));
|
|
6671
|
+
process.exit(1);
|
|
6672
|
+
}
|
|
6673
|
+
function readDaemonAt(dir) {
|
|
6674
|
+
const file = path31.join(dir, "daemon.json");
|
|
6675
|
+
if (!fs32.existsSync(file)) return null;
|
|
6676
|
+
try {
|
|
6677
|
+
return JSON.parse(fs32.readFileSync(file, "utf-8"));
|
|
6678
|
+
} catch {
|
|
6679
|
+
return null;
|
|
6680
|
+
}
|
|
6681
|
+
}
|
|
6682
|
+
function readProfileApiUrl(name) {
|
|
6683
|
+
const file = path31.join(profileDir(name), "config.yml");
|
|
6684
|
+
if (!fs32.existsSync(file)) return void 0;
|
|
6685
|
+
try {
|
|
6686
|
+
const data = yaml10.load(fs32.readFileSync(file, "utf-8"));
|
|
6687
|
+
return data?.api_url;
|
|
6688
|
+
} catch {
|
|
6689
|
+
return void 0;
|
|
6690
|
+
}
|
|
6691
|
+
}
|
|
6692
|
+
var profile2 = new Command23("profile").description("Manage CLI profiles (directories under ~/.task0/profiles/)");
|
|
6693
|
+
profile2.command("list").description("List configured profiles (one directory per profile)").action(() => {
|
|
6694
|
+
const names = listProfileNames();
|
|
6695
|
+
const current = currentProfileName();
|
|
6696
|
+
if (names.length === 0) {
|
|
6697
|
+
console.log("No profiles configured.");
|
|
6698
|
+
return;
|
|
6699
|
+
}
|
|
6700
|
+
for (const name of names) {
|
|
6701
|
+
const marker = name === current ? chalk23.green("\u25CF ") : " ";
|
|
6702
|
+
const apiUrl = readProfileApiUrl(name);
|
|
6703
|
+
const url = apiUrl ? chalk23.dim(` \u2192 ${apiUrl}`) : "";
|
|
6704
|
+
console.log(`${marker}${name}${url}`);
|
|
6705
|
+
}
|
|
6706
|
+
});
|
|
6707
|
+
profile2.command("add <name>").description("Create a new profile directory (use `task0 profile use <name>` to activate)").option("--api-url <url>", "API server URL the CLI should call when this profile is active").action((name, opts) => {
|
|
6708
|
+
if (!isValidProfileName(name)) {
|
|
6709
|
+
fail9(`Invalid profile name "${name}". Must match /^[a-zA-Z0-9_-]+$/.`);
|
|
6710
|
+
}
|
|
6711
|
+
if (name === DEFAULT_PROFILE_NAME) {
|
|
6712
|
+
fail9(`"${DEFAULT_PROFILE_NAME}" is reserved; it always exists.`);
|
|
6713
|
+
}
|
|
6714
|
+
const dir = profileDir(name);
|
|
6715
|
+
if (fs32.existsSync(dir)) {
|
|
6716
|
+
fail9(`Profile "${name}" already exists at ${dir}.`);
|
|
6717
|
+
}
|
|
6718
|
+
fs32.mkdirSync(dir, { recursive: true });
|
|
6719
|
+
const prev = process.env.TASK0_PROFILE;
|
|
6720
|
+
process.env.TASK0_PROFILE = name;
|
|
6721
|
+
try {
|
|
6722
|
+
const config = loadConfig();
|
|
6723
|
+
if (opts.apiUrl) config.api_url = opts.apiUrl;
|
|
6724
|
+
saveConfig(config);
|
|
6725
|
+
} finally {
|
|
6726
|
+
if (prev === void 0) delete process.env.TASK0_PROFILE;
|
|
6727
|
+
else process.env.TASK0_PROFILE = prev;
|
|
6728
|
+
}
|
|
6729
|
+
console.log(chalk23.green(`Added profile "${name}"`));
|
|
6730
|
+
console.log(` dir: ${dir}`);
|
|
6731
|
+
if (opts.apiUrl) console.log(` api_url: ${opts.apiUrl}`);
|
|
6732
|
+
console.log(chalk23.dim(`Use \`task0 profile use ${name}\` to activate.`));
|
|
6733
|
+
});
|
|
6734
|
+
profile2.command("remove <name>").description('Delete a profile directory (refuses to delete "default")').option("-f, --force", `Remove even if it's the current profile (resets current to "default")`).action((name, opts) => {
|
|
6735
|
+
if (!isValidProfileName(name)) {
|
|
6736
|
+
fail9(`Invalid profile name "${name}".`);
|
|
6737
|
+
}
|
|
6738
|
+
if (name === DEFAULT_PROFILE_NAME) {
|
|
6739
|
+
fail9(`"${DEFAULT_PROFILE_NAME}" cannot be removed.`);
|
|
6740
|
+
}
|
|
6741
|
+
const dir = profileDir(name);
|
|
6742
|
+
if (!fs32.existsSync(dir)) {
|
|
6743
|
+
fail9(`Profile "${name}" not found.`);
|
|
6744
|
+
}
|
|
6745
|
+
const current = currentProfileName();
|
|
6746
|
+
if (current === name && !opts.force) {
|
|
6747
|
+
fail9(`Profile "${name}" is current. Re-run with --force to remove it.`);
|
|
6748
|
+
}
|
|
6749
|
+
fs32.rmSync(dir, { recursive: true, force: true });
|
|
6750
|
+
if (current === name) {
|
|
6751
|
+
writeCurrentProfile(null);
|
|
6752
|
+
console.log(chalk23.dim('Current profile reset to "default".'));
|
|
6753
|
+
}
|
|
6754
|
+
console.log(chalk23.green(`Removed profile "${name}".`));
|
|
6755
|
+
});
|
|
6756
|
+
profile2.command("use <name>").description("Set the current profile").action((name) => {
|
|
6757
|
+
if (!isValidProfileName(name)) {
|
|
6758
|
+
fail9(`Invalid profile name "${name}".`);
|
|
6759
|
+
}
|
|
6760
|
+
if (!fs32.existsSync(profileDir(name))) {
|
|
6761
|
+
const names = listProfileNames();
|
|
6762
|
+
fail9(`Profile "${name}" not found. Available: ${names.join(", ")}.`);
|
|
6763
|
+
}
|
|
6764
|
+
writeCurrentProfile(name);
|
|
6765
|
+
console.log(chalk23.green(`Now using profile "${name}".`));
|
|
6766
|
+
});
|
|
6767
|
+
profile2.command("current").description('Print the current profile name (always succeeds; defaults to "default")').action(() => {
|
|
6768
|
+
console.log(currentProfileName());
|
|
6769
|
+
});
|
|
6770
|
+
profile2.command("show [name]").description("Show a profile's configuration and daemon registration").action((name) => {
|
|
6771
|
+
const target = name ?? currentProfileName();
|
|
6772
|
+
if (!isValidProfileName(target)) {
|
|
6773
|
+
fail9(`Invalid profile name "${target}".`);
|
|
6774
|
+
}
|
|
6775
|
+
const dir = profileDir(target);
|
|
6776
|
+
if (!fs32.existsSync(dir)) {
|
|
6777
|
+
fail9(`Profile "${target}" not found.`);
|
|
6778
|
+
}
|
|
6779
|
+
const current = currentProfileName();
|
|
6780
|
+
const apiUrl = readProfileApiUrl(target);
|
|
6781
|
+
const identity = readDaemonAt(dir);
|
|
6782
|
+
console.log(`${chalk23.bold(target)}${current === target ? chalk23.green(" (current)") : ""}`);
|
|
6783
|
+
console.log(` dir: ${dir}`);
|
|
6784
|
+
console.log(` config: ${path31.join(dir, "config.yml")}`);
|
|
6785
|
+
console.log(` api_url: ${apiUrl ?? chalk23.dim("(unset)")}`);
|
|
6786
|
+
if (!identity) {
|
|
6787
|
+
console.log(chalk23.dim(" daemon.json: (not registered yet)"));
|
|
6788
|
+
return;
|
|
6789
|
+
}
|
|
6790
|
+
console.log(` daemon_id: ${identity.daemon_id ?? chalk23.dim("(missing)")}`);
|
|
6791
|
+
console.log(` daemon.server_url: ${identity.server_url ?? chalk23.dim("(missing)")}`);
|
|
6792
|
+
if (apiUrl && identity.server_url && apiUrl !== identity.server_url) {
|
|
6793
|
+
console.log(chalk23.yellow(` warn: profile.api_url and daemon.json.server_url disagree.`));
|
|
6794
|
+
console.log(chalk23.dim(` Re-register the daemon to align: task0 --profile ${target} daemon register --server ${apiUrl}`));
|
|
6795
|
+
}
|
|
6796
|
+
});
|
|
6797
|
+
|
|
6798
|
+
// src/commands/migrate-layout.ts
|
|
6799
|
+
import { Command as Command24 } from "commander";
|
|
6800
|
+
import chalk24 from "chalk";
|
|
6801
|
+
var migrateLayout = new Command24("migrate-layout").description("Move legacy daemon-side files into the v0.8 ~/.task0/profiles/<name>/ layout").option("-n, --dry-run", "Show what would happen without modifying disk").option("-f, --force", "Reset the migration marker first; useful when the previous run was interrupted").action((opts) => {
|
|
6802
|
+
if (opts.force && !opts.dryRun) {
|
|
6803
|
+
const removed = resetMigrationMarker();
|
|
6804
|
+
if (removed) console.log(chalk24.dim("Reset migration marker."));
|
|
6805
|
+
}
|
|
6806
|
+
const result = migrateCliLayout({ dryRun: opts.dryRun });
|
|
6807
|
+
if (result.alreadyMigrated) {
|
|
6808
|
+
console.log(chalk24.dim("Already migrated \u2014 nothing to do."));
|
|
6809
|
+
console.log(chalk24.dim("Pass --force to rerun."));
|
|
6810
|
+
return;
|
|
6811
|
+
}
|
|
6812
|
+
if (result.steps.length === 0) {
|
|
6813
|
+
console.log("No legacy files found.");
|
|
6814
|
+
return;
|
|
6815
|
+
}
|
|
6816
|
+
if (opts.dryRun) {
|
|
6817
|
+
console.log(chalk24.bold("Dry run \u2014 no changes written:"));
|
|
6818
|
+
} else {
|
|
6819
|
+
console.log(chalk24.bold(`Migrated ${result.steps.length} item(s):`));
|
|
6820
|
+
}
|
|
6821
|
+
for (const step of result.steps) {
|
|
6822
|
+
console.log(` ${formatStep(step)}`);
|
|
6823
|
+
}
|
|
6824
|
+
});
|
|
6825
|
+
function formatStep(step) {
|
|
6826
|
+
switch (step.kind) {
|
|
6827
|
+
case "mv":
|
|
6828
|
+
return `mv ${step.from} \u2192 ${step.to}${step.detail ? ` (${step.detail})` : ""}`;
|
|
6829
|
+
case "mkdir":
|
|
6830
|
+
return `mkdir ${step.to}`;
|
|
6831
|
+
case "write":
|
|
6832
|
+
return `write ${step.to}${step.detail ? ` (${step.detail})` : ""}`;
|
|
6833
|
+
case "extract":
|
|
6834
|
+
return `update ${step.from}${step.detail ? ` (${step.detail})` : ""}`;
|
|
6835
|
+
case "skip":
|
|
6836
|
+
return `skip ${step.from ?? step.to}${step.detail ? ` (${step.detail})` : ""}`;
|
|
6837
|
+
case "note":
|
|
6838
|
+
return `note ${step.detail ?? ""}`;
|
|
6839
|
+
}
|
|
6840
|
+
}
|
|
6841
|
+
|
|
6203
6842
|
// src/core/error-capture.ts
|
|
6204
6843
|
init_node();
|
|
6205
6844
|
var DEFAULT_KEEP = 50;
|
|
@@ -6298,7 +6937,7 @@ function captureTopLevel(err, options) {
|
|
|
6298
6937
|
|
|
6299
6938
|
// src/main.ts
|
|
6300
6939
|
var TASK0_VERSION = readCliVersion();
|
|
6301
|
-
var program = new
|
|
6940
|
+
var program = new Command25().name("task0").description("Task-centric control layer for agent workflow").version(TASK0_VERSION).option("--profile <name>", "Use a named profile under ~/.task0/profiles/<name>/ (overrides .current pointer and TASK0_PROFILE env)");
|
|
6302
6941
|
program.addCommand(source);
|
|
6303
6942
|
program.addCommand(project);
|
|
6304
6943
|
program.addCommand(task);
|
|
@@ -6316,7 +6955,28 @@ program.addCommand(daemonCmd);
|
|
|
6316
6955
|
program.addCommand(userCmd);
|
|
6317
6956
|
program.addCommand(error);
|
|
6318
6957
|
program.addCommand(automation);
|
|
6958
|
+
program.addCommand(profile2);
|
|
6959
|
+
program.addCommand(migrateLayout);
|
|
6319
6960
|
async function main() {
|
|
6961
|
+
try {
|
|
6962
|
+
const result = migrateCliLayout();
|
|
6963
|
+
if (result.ran && process.env.TASK0_LOG_MIGRATION !== "off") {
|
|
6964
|
+
console.error(chalk25.dim(`task0: migrated CLI layout (${result.steps.length} step(s))`));
|
|
6965
|
+
}
|
|
6966
|
+
} catch (err) {
|
|
6967
|
+
console.error(chalk25.yellow(
|
|
6968
|
+
`task0: layout migration failed: ${err instanceof Error ? err.message : String(err)}. Continuing.`
|
|
6969
|
+
));
|
|
6970
|
+
}
|
|
6971
|
+
try {
|
|
6972
|
+
activateProfile(process.argv);
|
|
6973
|
+
} catch (err) {
|
|
6974
|
+
if (err instanceof ProfileNotFoundError) {
|
|
6975
|
+
console.error(chalk25.red(err.message));
|
|
6976
|
+
process.exit(1);
|
|
6977
|
+
}
|
|
6978
|
+
throw err;
|
|
6979
|
+
}
|
|
6320
6980
|
installErrorCapture({ version: TASK0_VERSION });
|
|
6321
6981
|
try {
|
|
6322
6982
|
await program.parseAsync(process.argv);
|