@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.
Files changed (2) hide show
  1. package/dist/main.js +1134 -474
  2. 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/types/task.js
13
- function isActiveTaskStatus(status) {
14
- return ACTIVE_TASK_STATUS_SET.has(status);
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
- var TASK_STATUS_VALUES, ACTIVE_TASK_STATUSES, ATTENTION_TASK_STATUSES, ACTIVE_TASK_STATUS_SET, ATTENTION_TASK_STATUS_SET;
17
- var init_task = __esm({
18
- "../../packages/shared/dist/types/task.js"() {
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/node/yaml.js
120
- import fs5 from "fs";
121
- import path6 from "path";
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 init_yaml = __esm({
141
- "../../packages/shared/dist/node/yaml.js"() {
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 fs6 from "fs";
148
- import path7 from "path";
147
+ import fs2 from "fs";
148
+ import path2 from "path";
149
149
  function scanProject(projectPath, sourceName) {
150
- const absPath = path7.resolve(projectPath);
151
- const resolvedSourceName = sourceName ?? path7.basename(absPath);
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 = path7.join(absPath, "task0.yml");
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 = path7.join(absPath, projectConfig.tasks_dir);
162
- if (!fs6.existsSync(tasksDir))
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 = fs6.readdirSync(tasksDir, { withFileTypes: true });
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 = path7.join(tasksDir, entry.name);
169
- const taskYml = path7.join(taskDir, "task0.yml");
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 = fs6.statSync(taskYml);
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 fs7 from "fs";
249
- import path8 from "path";
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 = path8.join(taskDir, file);
257
- if (!fs7.existsSync(fullPath))
256
+ const fullPath = path3.join(taskDir, file);
257
+ if (!fs3.existsSync(fullPath))
258
258
  continue;
259
- const md = fs7.readFileSync(fullPath, "utf-8");
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 fs8 from "fs";
283
- import os3 from "os";
284
- import path9 from "path";
285
- import yaml4 from "js-yaml";
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 = path9.resolve(start);
287
+ let dir = path4.resolve(start);
288
288
  while (true) {
289
- const ymlPath = path9.join(dir, "task0.yml");
290
- if (fs8.existsSync(ymlPath)) {
289
+ const ymlPath = path4.join(dir, "task0.yml");
290
+ if (fs4.existsSync(ymlPath)) {
291
291
  try {
292
- const raw = yaml4.load(fs8.readFileSync(ymlPath, "utf-8"));
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 = path9.dirname(dir);
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 yaml4.load(fs8.readFileSync(path9.join(projectRoot, "task0.yml"), "utf-8"));
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 path9.isAbsolute(cfg.tasks_dir) ? cfg.tasks_dir : path9.join(projectRoot, cfg.tasks_dir);
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 = fs8.readdirSync(tasksDir, { withFileTypes: true });
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 = path9.join(tasksDir, entry.name);
325
- const taskYml = path9.join(taskDir, "task0.yml");
326
- if (!fs8.existsSync(taskYml))
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 = yaml4.load(fs8.readFileSync(taskYml, "utf-8"));
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 (!fs8.existsSync(taskYml))
339
+ if (!fs4.existsSync(taskYml))
340
340
  throw new Error(`task0.yml not found: ${taskYml}`);
341
- return yaml4.load(fs8.readFileSync(taskYml, "utf-8")) || {};
341
+ return yaml2.load(fs4.readFileSync(taskYml, "utf-8")) || {};
342
342
  }
343
343
  function writeTaskYaml(taskYml, data) {
344
- fs8.writeFileSync(taskYml, yaml4.dump(data, { lineWidth: 120 }), "utf-8");
344
+ fs4.writeFileSync(taskYml, yaml2.dump(data, { lineWidth: 120 }), "utf-8");
345
345
  }
346
346
  function taskYamlLockPath(taskDir) {
347
- return path9.join(taskDir, TASK_YAML_LOCKFILE);
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(fs8.readFileSync(file, "utf-8"));
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 = fs8.openSync(file, "wx");
365
+ const fd = fs4.openSync(file, "wx");
366
366
  try {
367
- fs8.writeFileSync(fd, JSON.stringify(info, null, 2), "utf-8");
367
+ fs4.writeFileSync(fd, JSON.stringify(info, null, 2), "utf-8");
368
368
  } finally {
369
- fs8.closeSync(fd);
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: os3.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
- fs8.unlinkSync(file);
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
- fs8.unlinkSync(file);
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 = path9.resolve(taskDir);
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 = path9.dirname(taskYml);
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 fs9 from "fs";
587
- import os4 from "os";
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 path10.join(task0Home(), "errors");
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 = path10.join(root, dirName);
700
- fs9.mkdirSync(dir, { recursive: true });
701
- const finalPath = path10.join(dir, REPORT_FILENAME);
702
- const tmpPath = path10.join(dir, TMP_FILENAME);
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
- fs9.writeFileSync(tmpPath, json, "utf-8");
705
- fs9.renameSync(tmpPath, finalPath);
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 = fs9.readdirSync(root, { withFileTypes: true });
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 = path10.join(root, entry.name);
735
- const file = path10.join(dir, REPORT_FILENAME);
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 = fs9.readFileSync(file, "utf-8");
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 = fs9.statSync(file).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 = fs9.readFileSync(summary.path, "utf-8");
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
- fs9.rmSync(dir, { recursive: true, force: true });
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 fs10 from "fs";
848
- import os5 from "os";
849
- import path11 from "path";
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 fs11 from "fs";
866
- import path12 from "path";
867
- import yaml5 from "js-yaml";
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 = yaml5.load(match[1]);
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 (!fs11.existsSync(filePath))
962
+ if (!fs8.existsSync(filePath))
885
963
  return null;
886
- return fs11.readFileSync(filePath, "utf-8");
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 = fs11.lstatSync(candidatePath);
972
+ const stat = fs8.lstatSync(candidatePath);
895
973
  if (!stat.isSymbolicLink())
896
974
  continue;
897
- const target = fs11.readlinkSync(candidatePath);
975
+ const target = fs8.readlinkSync(candidatePath);
898
976
  return {
899
977
  isSymlink: true,
900
- symlinkTarget: path12.isAbsolute(target) ? target : path12.resolve(path12.dirname(candidatePath), target)
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 = path12.basename(filePath), description2 = "", kind = "instruction") {
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 (!fs11.existsSync(skillsDir))
1028
+ if (!fs8.existsSync(skillsDir))
951
1029
  return;
952
- entries = fs11.readdirSync(skillsDir, { withFileTypes: true });
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 = path12.join(skillsDir, entry.name);
960
- const skillFilePath = path12.join(skillDirPath, "SKILL.md");
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 = path12.basename(filePath, ".mdc");
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 (!fs11.existsSync(rulesDir))
1075
+ if (!fs8.existsSync(rulesDir))
998
1076
  return;
999
- entries = fs11.readdirSync(rulesDir, { withFileTypes: true });
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, path12.join(rulesDir, entry.name), scope);
1086
+ scanCursorRuleFile(skills, path8.join(rulesDir, entry.name), scope);
1009
1087
  }
1010
1088
  }
1011
1089
  function getProjectAgentSkills(projectPath) {
1012
- const absProjectPath = path12.resolve(projectPath);
1090
+ const absProjectPath = path8.resolve(projectPath);
1013
1091
  let projectStat;
1014
1092
  try {
1015
- projectStat = fs11.statSync(absProjectPath);
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, path12.join(absProjectPath, ".claude", "skills"), "project");
1023
- pushInstructionIfExists(skills, "claude_code", "project", path12.join(absProjectPath, "CLAUDE.md"));
1024
- pushInstructionIfExists(skills, "claude_code", "project", path12.join(absProjectPath, "AGENTS.md"));
1025
- pushInstructionIfExists(skills, "codex", "project", path12.join(absProjectPath, "AGENTS.md"));
1026
- scanCursorRulesDir(skills, path12.join(absProjectPath, ".cursor", "rules"), "project");
1027
- pushInstructionIfExists(skills, "cursor", "project", path12.join(absProjectPath, ".cursorrules"), "Legacy Cursor Rules", "", "rule");
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, path12.join(homeDir, ".claude", "skills"), "global");
1036
- scanCursorRulesDir(skills, path12.join(homeDir, ".cursor", "rules"), "global");
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 fs12 from "fs";
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 = fs12.readdirSync(taskDir);
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 (!fs12.existsSync(taskDir)) return null;
1108
- const matches = fs12.readdirSync(taskDir).filter((name) => pattern.test(name));
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
- // src/main.ts
1121
- import { Command as Command23 } from "commander";
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 path3 from "path";
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
- import fs from "fs";
1133
- import os from "os";
1134
- import path from "path";
1135
- var CONFIG_DIR = path.join(os.homedir(), ".config", "task0");
1136
- var TOKEN_FILE = path.join(CONFIG_DIR, "admin.token");
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 ${TOKEN_FILE}.
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
- if (fs.existsSync(TOKEN_FILE)) {
1156
- const v = fs.readFileSync(TOKEN_FILE, "utf-8").trim();
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
- import fs2 from "fs";
1170
- import os2 from "os";
1171
- import path2 from "path";
1172
- var CONFIG_DIR2 = path2.join(os2.homedir(), ".config", "task0");
1173
- var CONFIG_FILE = path2.join(CONFIG_DIR2, "daemon.json");
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 CONFIG_FILE;
1572
+ return configFile2();
1176
1573
  }
1177
1574
  function readDaemonIdentity() {
1178
- if (!fs2.existsSync(CONFIG_FILE)) return null;
1575
+ const file = configFile2();
1576
+ if (!fs13.existsSync(file)) return null;
1179
1577
  try {
1180
- const raw = fs2.readFileSync(CONFIG_FILE, "utf-8");
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
- fs2.mkdirSync(CONFIG_DIR2, { recursive: true });
1188
- fs2.writeFileSync(CONFIG_FILE, JSON.stringify(identity, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
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
- fs2.chmodSync(CONFIG_FILE, 384);
1589
+ fs13.chmodSync(file, 384);
1191
1590
  } catch {
1192
1591
  }
1193
1592
  }
1194
1593
  function clearDaemonIdentity() {
1195
- if (!fs2.existsSync(CONFIG_FILE)) return false;
1196
- fs2.unlinkSync(CONFIG_FILE);
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" : "~/.config/task0/admin.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 = path3.resolve(inputPath);
1306
- const name = opts.name || path3.basename(absPath);
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 fs3 from "fs";
1405
- import path4 from "path";
1406
- import yaml from "js-yaml";
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 = path4.join(cwd, "task0.yml");
1445
- if (fs3.existsSync(ymlPath)) {
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
- fs3.writeFileSync(ymlPath, yaml.dump(config), "utf-8");
1451
- const tasksDir = path4.join(cwd, opts.tasksDir);
1452
- fs3.mkdirSync(tasksDir, { recursive: true });
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 fs17 from "fs";
1462
- import path15 from "path";
1463
- import yaml6 from "js-yaml";
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: (path29) => request("GET", path29),
1501
- post: (path29, body) => request("POST", path29, body ?? {}),
1502
- put: (path29, body) => request("PUT", path29, body ?? {}),
1503
- patch: (path29, body) => request("PATCH", path29, body ?? {}),
1504
- del: (path29) => request("DELETE", path29)
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 fs13 from "fs";
1511
- import path13 from "path";
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 = path13.join(projectRoot, ".claude", "skills", skillName, "SKILL.md");
1611
- return fs13.existsSync(p) ? p : null;
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
- fs13.rmSync(path13.join(loc.taskDir, name), { force: true });
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 = fs13.existsSync(path13.join(loc.taskDir, "ISSUE.md"));
1669
- const issueFiles = fs13.readdirSync(loc.taskDir).filter((name) => ISSUE_DETAIL_RE.test(name)).sort();
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 = fs13.readFileSync(path13.join(loc.taskDir, name), "utf-8");
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 (!fs13.existsSync(taskDir)) return [];
1708
- return fs13.readdirSync(taskDir).filter((name) => name === "ISSUE.md" || ISSUE_DETAIL_RE.test(name)).sort();
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 fs14 from "fs";
1723
- import path14 from "path";
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 = path14.join(projectRoot, ".claude", "skills", skillName, "SKILL.md");
1728
- return fs14.existsSync(p) ? p : null;
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 fs15 from "fs";
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 fs15.readFileSync(0, "utf-8");
1832
- if (opts.file) return fs15.readFileSync(opts.file, "utf-8");
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 fs16 from "fs";
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 fs16.readFileSync(0, "utf-8");
1942
- if (opts.file) return fs16.readFileSync(opts.file, "utf-8");
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 = path15.join(cwd, "task0.yml");
1992
- if (!fs17.existsSync(projectYml)) {
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 = yaml6.load(fs17.readFileSync(projectYml, "utf-8"));
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 = path15.join(cwd, "task0.yml");
2013
- if (!fs17.existsSync(projectYml)) {
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 = yaml6.load(fs17.readFileSync(projectYml, "utf-8"));
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 = path15.join(cwd, projectConfig.tasks_dir);
2023
- if (!fs17.existsSync(tasksDir)) {
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 = fs17.readdirSync(tasksDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
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 = path15.join(tasksDir, name);
2033
- const taskYml = path15.join(taskDir, "task0.yml");
2034
- if (fs17.existsSync(taskYml)) {
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 = yaml6.load(fs17.readFileSync(taskYml, "utf-8"));
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
- fs17.writeFileSync(taskYml, yaml6.dump(raw, { lineWidth: 120 }), "utf-8");
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
- fs17.writeFileSync(taskYml, yaml6.dump(taskConfig), "utf-8");
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 = yaml6.load(fs17.readFileSync(loc.taskYml, "utf-8"));
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
- fs17.writeFileSync(loc.taskYml, yaml6.dump(raw, { lineWidth: 120 }), "utf-8");
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
- fs17.writeFileSync(loc.taskYml, yaml6.dump(raw, { lineWidth: 120 }), "utf-8");
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 = path15.join(cwd, "task0.yml");
2162
- if (!fs17.existsSync(projectYml)) {
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 = yaml6.load(fs17.readFileSync(projectYml, "utf-8"));
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 = path15.join(cwd, projectConfig.tasks_dir);
2172
- const taskDir = path15.join(tasksDir, id);
2173
- if (!fs17.existsSync(taskDir)) {
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
- fs17.rmSync(taskDir, { recursive: true });
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 (fs17.existsSync(tarFile) && !fs17.statSync(tarFile).isFile()) {
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 (fs17.existsSync(tarFile)) {
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 path16 from "path";
2549
+ import path18 from "path";
2198
2550
  import chalk9 from "chalk";
2199
- var DASHBOARD_DIR = path16.resolve(
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 CODING_RUNTIME_AGENTS = ["claude-code", "codex", "cursor"];
2234
- var CODING_RUNTIME_AGENT_SET = new Set(CODING_RUNTIME_AGENTS);
2235
- function isCodingRuntimeAgent(value) {
2236
- return typeof value === "string" && CODING_RUNTIME_AGENT_SET.has(value);
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 = CODING_RUNTIME_AGENTS;
2243
- var isRuntimeAgent = isCodingRuntimeAgent;
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 fs18 from "fs";
2481
- import path17 from "path";
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 yaml7 from "js-yaml";
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(yaml7.dump(result.runtime.exec, { lineWidth: 100 }));
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 = yaml7.load(fs18.readFileSync(path17.resolve(opts.fromFile), "utf-8"));
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 = path17.join(process.env.TMPDIR || "/tmp", `task0-runtime-${result.runtime.slug}-${Date.now()}.yml`);
2636
- fs18.writeFileSync(tmp, yaml7.dump(result.runtime, { lineWidth: 100 }), "utf-8");
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 = yaml7.load(fs18.readFileSync(tmp, "utf-8"));
2992
+ const updated = yaml8.load(fs21.readFileSync(tmp, "utf-8"));
2641
2993
  await api.put(`/api/runtime-profiles/${encodeURIComponent(ref)}`, updated);
2642
- fs18.unlinkSync(tmp);
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 fs19 from "fs";
2663
- import path18 from "path";
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 = path18.join(projectRoot, ".claude", "skills", skillName, "SKILL.md");
2668
- return fs19.existsSync(p) ? p : null;
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 = fs19.readdirSync(loc.taskDir);
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 = path18.join(loc.taskDir, refinedFile);
2843
- const wrote = fs19.existsSync(refinedPath);
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 path19 from "path";
2996
- import fs20 from "fs";
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 = fs20.realpathSync(process.cwd());
3358
+ const cwd = fs23.realpathSync(process.cwd());
3007
3359
  for (const source2 of projects) {
3008
3360
  let sourceAbs;
3009
3361
  try {
3010
- sourceAbs = fs20.realpathSync(path19.resolve(source2.path));
3362
+ sourceAbs = fs23.realpathSync(path21.resolve(source2.path));
3011
3363
  } catch {
3012
- sourceAbs = path19.resolve(source2.path);
3364
+ sourceAbs = path21.resolve(source2.path);
3013
3365
  }
3014
- if (cwd === sourceAbs || cwd.startsWith(sourceAbs + path19.sep)) {
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 fs21 from "fs";
3655
- import path20 from "path";
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 = fs21.existsSync(taskDir) ? fs21.readdirSync(taskDir) : [];
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 = path20.join(taskDir, issueFile);
3764
- const consolidatedPath = path20.join(taskDir, consolidatedFile);
3765
- if (!fs21.existsSync(issuePath)) throw new Error(`${issueFile} not found in ${taskDir}`);
3766
- if (!fs21.existsSync(consolidatedPath)) throw new Error(`${consolidatedFile} not found in ${taskDir}`);
3767
- const issueMd = fs21.readFileSync(issuePath, "utf-8");
3768
- const consolidatedMd = fs21.readFileSync(consolidatedPath, "utf-8");
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
- fs21.writeFileSync(issuePath, next, "utf-8");
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 = path20.join(loc.taskDir, decisionFile);
3819
- if (fs21.existsSync(decisionPath) && !opts.force) {
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 (!fs21.existsSync(path20.join(loc.taskDir, k.decisionFile))) {
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 = fs21.readdirSync(loc.taskDir);
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 = fs21.existsSync(path20.join(loc.taskDir, consolidatedFile));
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 = fs21.readdirSync(taskDir);
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 fs22 from "fs";
4136
- import path21 from "path";
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 yaml8 from "js-yaml";
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.system ? chalk18.dim("(system)") : a.scope ? chalk18.dim(`(${a.scope})`) : "";
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(yaml8.dump(result.agent.spec, { lineWidth: 100 }));
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(yaml8.dump(result.agent.mcp_config, { lineWidth: 100 }));
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 = fs22.readFileSync(path21.resolve(opts.fromFile), "utf-8");
4215
- parsed = yaml8.load(raw);
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
- if (result.agent.system) fail5("cannot edit a system agent");
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
- fs22.writeFileSync(tmp, yaml8.dump(result.agent, { lineWidth: 100 }), "utf-8");
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 = yaml8.load(fs22.readFileSync(tmp, "utf-8"));
4590
+ const updated = yaml9.load(fs25.readFileSync(tmp, "utf-8"));
4240
4591
  await api.put(`/api/agents/${encodeURIComponent(ref)}`, updated);
4241
- fs22.unlinkSync(tmp);
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(fs22.readFileSync(path21.resolve(opts.fromFile), "utf-8"));
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 os8 from "os";
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 ~/.config/task0/admin.token).`
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 fs25 from "fs";
4461
- import path24 from "path";
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 fs24 from "fs";
4466
- import path23 from "path";
4817
+ import fs27 from "fs";
4818
+ import path25 from "path";
4467
4819
 
4468
4820
  // src/core/daemon-agent-run-dir.ts
4469
- import fs23 from "fs";
4470
- import os6 from "os";
4471
- import path22 from "path";
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 || path22.join(os6.homedir(), ".config", "task0", "agent-run");
4825
+ return process.env.TASK0_DAEMON_AGENT_RUN_DIR || path24.join(profileDir(), "agent-run");
4474
4826
  }
4475
4827
  function agentRunDir(runId) {
4476
- return path22.join(agentRunRoot(), runId);
4828
+ return path24.join(agentRunRoot(), runId);
4477
4829
  }
4478
4830
  function agentRunStatusPath(runId) {
4479
- return path22.join(agentRunDir(runId), "status.json");
4831
+ return path24.join(agentRunDir(runId), "status.json");
4480
4832
  }
4481
4833
  function ensureAgentRunDir(runId) {
4482
4834
  const dir = agentRunDir(runId);
4483
- fs23.mkdirSync(dir, { recursive: true });
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 (!fs23.existsSync(dir)) return;
4489
- fs23.rmSync(dir, { recursive: true, force: true });
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 (!fs24.existsSync(filePath)) return null;
4883
+ if (!fs27.existsSync(filePath)) return null;
4532
4884
  try {
4533
- const raw = fs24.readFileSync(filePath, "utf-8");
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 (!fs24.existsSync(params.workspace)) {
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 = path23.join(runDir, "script.sh");
4563
- fs24.writeFileSync(scriptPath, params.scriptContent, { mode: 493 });
4914
+ const scriptPath = path25.join(runDir, "script.sh");
4915
+ fs27.writeFileSync(scriptPath, params.scriptContent, { mode: 493 });
4564
4916
  if (params.promptContent !== void 0) {
4565
- fs24.writeFileSync(path23.join(runDir, "prompt.txt"), params.promptContent, "utf-8");
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
- fs24.writeFileSync(path23.join(runDir, name), content, "utf-8");
4923
+ fs27.writeFileSync(path25.join(runDir, name), content, "utf-8");
4572
4924
  }
4573
- fs24.writeFileSync(
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 : path24.basename(rootPath);
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 = fs25.statSync(filePath);
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 = fs25.readFileSync(filePath, "utf-8");
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 = path24.resolve(rawPath);
4727
- if (!fs25.existsSync(absPath)) {
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 = fs25.statSync(absPath);
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() || path24.basename(absPath);
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 = ensureString(params.taskId, "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 = path24.resolve(project2.path);
4784
- const projectConfig = readYaml(path24.join(projectAbs, "task0.yml"));
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 taskDir = path24.join(projectAbs, projectConfig.tasks_dir, taskId);
4789
- if (!fs25.existsSync(taskDir)) {
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 = path24.join(taskDir, "task0.yml");
4793
- const yaml9 = readYaml(taskYml);
4794
- if (!yaml9) {
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 = fs25.readdirSync(taskDir).filter((name) => name !== "task0.yml");
4798
- return { task_dir: taskDir, yaml: yaml9, files };
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 = ensureString(params.taskId, "taskId");
4806
- const yaml9 = params.yaml;
4807
- if (!yaml9 || typeof yaml9 !== "object") {
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 = path24.resolve(project2.path);
4816
- const projectConfig = readYaml(path24.join(projectAbs, "task0.yml"));
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 taskDir = path24.join(projectAbs, projectConfig.tasks_dir, taskId);
4821
- if (fs25.existsSync(taskDir)) {
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
- fs25.mkdirSync(taskDir, { recursive: true });
4825
- writeYaml(path24.join(taskDir, "task0.yml"), yaml9);
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
- fs25.writeFileSync(path24.join(taskDir, name), content, "utf-8");
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 = ensureString(params.taskId, "taskId");
4842
- const yaml9 = params.yaml;
4843
- if (!yaml9 || typeof yaml9 !== "object") {
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 = path24.resolve(project2.path);
4851
- const projectConfig = readYaml(path24.join(projectAbs, "task0.yml"));
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 taskDir = path24.join(projectAbs, projectConfig.tasks_dir, taskId);
4856
- const taskYml = path24.join(taskDir, "task0.yml");
4857
- if (!fs25.existsSync(taskYml)) {
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, yaml9);
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 = ensureString(params.taskId, "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 = path24.resolve(project2.path);
4874
- const projectConfig = readYaml(path24.join(projectAbs, "task0.yml"));
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 taskDir = path24.join(projectAbs, projectConfig.tasks_dir, taskId);
4879
- if (!fs25.existsSync(taskDir)) {
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
- fs25.rmSync(taskDir, { recursive: true, force: true });
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 = path24.resolve(filePath);
5287
+ const resolved = path26.resolve(filePath);
4914
5288
  const skill = all.find(
4915
- (s) => s.agent === agent2 && path24.resolve(s.filePath) === resolved
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 fs27 from "fs";
4949
- import path26 from "path";
5322
+ import fs30 from "fs";
5323
+ import path28 from "path";
4950
5324
 
4951
5325
  // src/core/daemon-service/binary.ts
4952
- import fs26 from "fs";
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 = fs26.realpathSync(argv1);
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
- import os7 from "os";
4974
- import path25 from "path";
5347
+ init_node();
5348
+ import os5 from "os";
5349
+ import path27 from "path";
4975
5350
 
4976
5351
  // src/core/daemon-service/types.ts
4977
- var SERVICE_LABEL = "cc.cy0.task0";
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" ? path25.join(os7.homedir(), "Library", "LaunchAgents", `${SERVICE_LABEL}.plist`) : path25.join("/", "Library", "LaunchDaemons", `${SERVICE_LABEL}.plist`);
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" ? path25.join(os7.homedir(), ".config", "systemd", "user", `${SERVICE_LABEL}.service`) : path25.join("/", "etc", "systemd", "system", `${SERVICE_LABEL}.service`);
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 path25.join(os7.homedir(), ".task0", "logs");
5378
+ return path27.join(profileDir(), "logs");
4992
5379
  }
4993
5380
  function logPaths() {
4994
5381
  const dir = logDir();
4995
5382
  return {
4996
- out: path25.join(dir, "daemon.out.log"),
4997
- err: path25.join(dir, "daemon.err.log")
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
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>${SERVICE_LABEL}</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
- <key>PATH</key>
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)}/${SERVICE_LABEL}`;
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
- fs27.mkdirSync(logDir(), { recursive: true });
5059
- fs27.mkdirSync(path26.dirname(file), { recursive: true });
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
- fs27.writeFileSync(file, body, { mode: 420 });
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 (fs27.existsSync(file)) {
5496
+ if (fs30.existsSync(file)) {
5095
5497
  run2("launchctl", ["unload", file]);
5096
- fs27.unlinkSync(file);
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 (!fs27.existsSync(file)) return "absent";
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 fs28 from "fs";
5139
- import path27 from "path";
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
- Environment=NODE_ENV=production
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 = `${SERVICE_LABEL}.service`;
5588
+ const unitName = `${serviceLabel()}.service`;
5176
5589
  async function install() {
5177
5590
  const inv = resolveTask0Invocation();
5178
- fs28.mkdirSync(logDir(), { recursive: true });
5179
- fs28.mkdirSync(path27.dirname(file), { recursive: true });
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
- fs28.writeFileSync(file, body, { mode: 420 });
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 (fs28.existsSync(file)) {
5198
- fs28.unlinkSync(file);
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 (!fs28.existsSync(file)) return "absent";
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 path28 from "path";
5665
+ import path30 from "path";
5252
5666
  var cached2 = null;
5253
5667
  function readCliVersion() {
5254
5668
  if (cached2 !== null) return cached2;
5255
- const here = path28.dirname(fileURLToPath2(import.meta.url));
5669
+ const here = path30.dirname(fileURLToPath2(import.meta.url));
5256
5670
  const candidates = [
5257
- path28.resolve(here, "..", "package.json"),
5258
- path28.resolve(here, "..", "..", "package.json")
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: os8.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 DEFAULT_BASE;
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(path29, init = {}) {
5700
- const url = `${serverBase2()}${path29}`;
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 Command23().name("task0").description("Task-centric control layer for agent workflow").version(TASK0_VERSION);
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);