@task0/cli 0.5.0 → 0.7.0

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