@task0/cli 0.7.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 +753 -616
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -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 fs5 from "fs";
587
- import os2 from "os";
588
- import path5 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 path5.join(os2.homedir(), ".task0");
596
- }
597
674
  function errorsRoot() {
598
- return path5.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 = 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);
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
- fs5.writeFileSync(tmpPath, json, "utf-8");
705
- fs5.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 = fs5.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 = path5.join(root, entry.name);
735
- const file = path5.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 = fs5.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 = fs5.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 = fs5.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
- fs5.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 fs6 from "fs";
925
+ import fs7 from "fs";
848
926
  import os3 from "os";
849
- import path6 from "path";
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,8 +940,8 @@ var init_tmux = __esm({
862
940
  });
863
941
 
864
942
  // ../../packages/shared/dist/node/agent-skills.js
865
- import fs7 from "fs";
866
- import path7 from "path";
943
+ import fs8 from "fs";
944
+ import path8 from "path";
867
945
  import yaml3 from "js-yaml";
868
946
  function isRecord(value) {
869
947
  return !!value && typeof value === "object" && !Array.isArray(value);
@@ -881,9 +959,9 @@ function extractFrontmatter(raw) {
881
959
  }
882
960
  function readTextIfExists(filePath) {
883
961
  try {
884
- if (!fs7.existsSync(filePath))
962
+ if (!fs8.existsSync(filePath))
885
963
  return null;
886
- return fs7.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 = fs7.lstatSync(candidatePath);
972
+ const stat = fs8.lstatSync(candidatePath);
895
973
  if (!stat.isSymbolicLink())
896
974
  continue;
897
- const target = fs7.readlinkSync(candidatePath);
975
+ const target = fs8.readlinkSync(candidatePath);
898
976
  return {
899
977
  isSymlink: true,
900
- symlinkTarget: path7.isAbsolute(target) ? target : path7.resolve(path7.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 = path7.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 = path7.b
947
1025
  function scanClaudeSkillDir(skills, skillsDir, scope) {
948
1026
  let entries = [];
949
1027
  try {
950
- if (!fs7.existsSync(skillsDir))
1028
+ if (!fs8.existsSync(skillsDir))
951
1029
  return;
952
- entries = fs7.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 = path7.join(skillsDir, entry.name);
960
- const skillFilePath = path7.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 = path7.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 (!fs7.existsSync(rulesDir))
1075
+ if (!fs8.existsSync(rulesDir))
998
1076
  return;
999
- entries = fs7.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, path7.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 = path7.resolve(projectPath);
1090
+ const absProjectPath = path8.resolve(projectPath);
1013
1091
  let projectStat;
1014
1092
  try {
1015
- projectStat = fs7.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, 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");
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, path7.join(homeDir, ".claude", "skills"), "global");
1036
- scanCursorRulesDir(skills, path7.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,14 +1177,14 @@ 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;
@@ -1118,115 +1197,42 @@ var init_task_state2 = __esm({
1118
1197
  });
1119
1198
 
1120
1199
  // src/main.ts
1121
- import { Command as Command24 } from "commander";
1122
- import chalk24 from "chalk";
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";
1123
1206
 
1124
1207
  // src/core/config.ts
1125
1208
  init_node();
1126
- import fs8 from "fs";
1127
- import os4 from "os";
1128
- import path8 from "path";
1209
+ import fs9 from "fs";
1210
+ import path9 from "path";
1129
1211
  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");
1212
+ function configFile() {
1213
+ return path9.join(profileDir(), "config.yml");
1142
1214
  }
1143
- function configFilePath() {
1144
- return registryFile();
1215
+ function defaultConfig() {
1216
+ return { sources: [] };
1145
1217
  }
1146
- function readYamlFile(file) {
1147
- if (!fs8.existsSync(file)) return null;
1218
+ function loadConfig() {
1219
+ const file = configFile();
1220
+ if (!fs9.existsSync(file)) return defaultConfig();
1148
1221
  try {
1149
- const raw = fs8.readFileSync(file, "utf-8");
1150
- const parsed = yaml4.load(raw);
1151
- return parsed ?? null;
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
+ };
1152
1228
  } catch {
1153
- return null;
1229
+ return defaultConfig();
1154
1230
  }
1155
1231
  }
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
1232
  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);
1233
+ const file = configFile();
1234
+ fs9.mkdirSync(path9.dirname(file), { recursive: true });
1235
+ fs9.writeFileSync(file, yaml4.dump(config, { lineWidth: 120 }), "utf-8");
1230
1236
  }
1231
1237
  function addSource(entry) {
1232
1238
  const config = loadConfig();
@@ -1246,6 +1252,9 @@ function removeSource(name) {
1246
1252
  saveConfig(config);
1247
1253
  return true;
1248
1254
  }
1255
+ function getApiUrl() {
1256
+ return loadConfig().api_url;
1257
+ }
1249
1258
 
1250
1259
  // src/core/profile.ts
1251
1260
  var ProfileNotFoundError = class extends Error {
@@ -1284,42 +1293,230 @@ function isProfileSubcommandInvocation(argv) {
1284
1293
  }
1285
1294
  return true;
1286
1295
  }
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()));
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;
1293
1306
  }
1294
- return { name: requested, entry };
1295
1307
  }
1296
1308
  var activeProfileCache = null;
1297
1309
  function activateProfile(argv) {
1310
+ ensureDefaultProfile();
1298
1311
  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;
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;
1305
1319
  } else {
1306
- throw error2;
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;
1307
1329
  }
1308
1330
  }
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;
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;
1313
1346
  }
1314
- if (active2.entry.api_url && !process.env.TASK0_API_URL) {
1315
- process.env.TASK0_API_URL = active2.entry.api_url;
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
+ }
1443
+ }
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
+ }
1316
1499
  }
1317
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;
1318
1515
  }
1319
1516
 
1320
1517
  // src/commands/source.ts
1321
1518
  import { Command } from "commander";
1322
- import path11 from "path";
1519
+ import path13 from "path";
1323
1520
  import chalk from "chalk";
1324
1521
 
1325
1522
  // src/types.ts
@@ -1327,10 +1524,10 @@ init_task();
1327
1524
 
1328
1525
  // src/core/admin-token.ts
1329
1526
  init_node();
1330
- import fs9 from "fs";
1331
- import path9 from "path";
1527
+ import fs12 from "fs";
1528
+ import path11 from "path";
1332
1529
  function tokenFile() {
1333
- return path9.join(task0Home(), "admin.token");
1530
+ return path11.join(serverWorkingDir(), "admin.token");
1334
1531
  }
1335
1532
  var cached = null;
1336
1533
  var AdminTokenUnavailableError = class extends Error {
@@ -1351,8 +1548,8 @@ function readAdminToken() {
1351
1548
  return cached;
1352
1549
  }
1353
1550
  const file = tokenFile();
1354
- if (fs9.existsSync(file)) {
1355
- const v = fs9.readFileSync(file, "utf-8").trim();
1551
+ if (fs12.existsSync(file)) {
1552
+ const v = fs12.readFileSync(file, "utf-8").trim();
1356
1553
  if (v) {
1357
1554
  cached = v;
1358
1555
  return cached;
@@ -1366,40 +1563,37 @@ function adminAuthHeader() {
1366
1563
 
1367
1564
  // src/core/daemon-config.ts
1368
1565
  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");
1566
+ import fs13 from "fs";
1567
+ import path12 from "path";
1568
+ function configFile2() {
1569
+ return path12.join(profileDir(), "daemon.json");
1376
1570
  }
1377
1571
  function daemonConfigPath() {
1378
- return configFile();
1572
+ return configFile2();
1379
1573
  }
1380
1574
  function readDaemonIdentity() {
1381
- const file = configFile();
1382
- if (!fs10.existsSync(file)) return null;
1575
+ const file = configFile2();
1576
+ if (!fs13.existsSync(file)) return null;
1383
1577
  try {
1384
- const raw = fs10.readFileSync(file, "utf-8");
1578
+ const raw = fs13.readFileSync(file, "utf-8");
1385
1579
  return JSON.parse(raw);
1386
1580
  } catch {
1387
1581
  return null;
1388
1582
  }
1389
1583
  }
1390
1584
  function writeDaemonIdentity(identity) {
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 });
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 });
1394
1588
  try {
1395
- fs10.chmodSync(file, 384);
1589
+ fs13.chmodSync(file, 384);
1396
1590
  } catch {
1397
1591
  }
1398
1592
  }
1399
1593
  function clearDaemonIdentity() {
1400
- const file = configFile();
1401
- if (!fs10.existsSync(file)) return false;
1402
- fs10.unlinkSync(file);
1594
+ const file = configFile2();
1595
+ if (!fs13.existsSync(file)) return false;
1596
+ fs13.unlinkSync(file);
1403
1597
  return true;
1404
1598
  }
1405
1599
 
@@ -1508,8 +1702,8 @@ function requireLocalDaemon() {
1508
1702
  }
1509
1703
  var source = new Command("source").description("Manage task sources");
1510
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) => {
1511
- const absPath = path11.resolve(inputPath);
1512
- const name = opts.name || path11.basename(absPath);
1705
+ const absPath = path13.resolve(inputPath);
1706
+ const name = opts.name || path13.basename(absPath);
1513
1707
  const daemonId = requireLocalDaemon();
1514
1708
  let resp;
1515
1709
  try {
@@ -1607,9 +1801,9 @@ ${project2}`));
1607
1801
 
1608
1802
  // src/commands/project.ts
1609
1803
  import { Command as Command2 } from "commander";
1610
- import fs11 from "fs";
1611
- import path12 from "path";
1612
- import yaml5 from "js-yaml";
1804
+ import fs14 from "fs";
1805
+ import path14 from "path";
1806
+ import yaml6 from "js-yaml";
1613
1807
  import chalk2 from "chalk";
1614
1808
 
1615
1809
  // ../../packages/shared/dist/index.js
@@ -1647,15 +1841,15 @@ project.command("list").description("List registered projects (queries the hub)"
1647
1841
  });
1648
1842
  project.command("init").description("Initialize task0.yml in the current directory").option("-d, --tasks-dir <dir>", "Tasks directory", ".task0/tasks").action((opts) => {
1649
1843
  const cwd = process.cwd();
1650
- const ymlPath = path12.join(cwd, "task0.yml");
1651
- if (fs11.existsSync(ymlPath)) {
1844
+ const ymlPath = path14.join(cwd, "task0.yml");
1845
+ if (fs14.existsSync(ymlPath)) {
1652
1846
  console.error(chalk2.yellow("task0.yml already exists"));
1653
1847
  process.exit(1);
1654
1848
  }
1655
1849
  const config = { kind: "project", object_id: generateObjectId("project"), tasks_dir: opts.tasksDir };
1656
- fs11.writeFileSync(ymlPath, yaml5.dump(config), "utf-8");
1657
- const tasksDir = path12.join(cwd, opts.tasksDir);
1658
- fs11.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 });
1659
1853
  console.log(chalk2.green("Initialized task0 project"));
1660
1854
  console.log(` ${ymlPath}`);
1661
1855
  console.log(` ${tasksDir}/`);
@@ -1664,9 +1858,9 @@ project.command("init").description("Initialize task0.yml in the current directo
1664
1858
  // src/commands/task.ts
1665
1859
  import { Command as Command8 } from "commander";
1666
1860
  import { execSync } from "child_process";
1667
- import fs17 from "fs";
1668
- import path15 from "path";
1669
- import yaml6 from "js-yaml";
1861
+ import fs20 from "fs";
1862
+ import path17 from "path";
1863
+ import yaml7 from "js-yaml";
1670
1864
  import chalk8 from "chalk";
1671
1865
 
1672
1866
  // src/lib/api.ts
@@ -1703,18 +1897,18 @@ async function request(method, pathname, body) {
1703
1897
  }
1704
1898
  }
1705
1899
  var api = {
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)
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)
1711
1905
  };
1712
1906
 
1713
1907
  // src/commands/task/triage.ts
1714
1908
  import { Command as Command3 } from "commander";
1715
1909
  import chalk3 from "chalk";
1716
- import fs13 from "fs";
1717
- import path13 from "path";
1910
+ import fs16 from "fs";
1911
+ import path15 from "path";
1718
1912
 
1719
1913
  // src/core/agent-run-wait.ts
1720
1914
  async function getAgentRun(id) {
@@ -1765,8 +1959,8 @@ init_task_state2();
1765
1959
  var ISSUE_DETAIL_RE = /^ISSUE-\d+\.md$/;
1766
1960
  var TRIAGE_SKILL_NAME = "triage";
1767
1961
  function resolveSkillFilePath(projectRoot, skillName) {
1768
- const p = path13.join(projectRoot, ".claude", "skills", skillName, "SKILL.md");
1769
- return fs13.existsSync(p) ? p : null;
1962
+ const p = path15.join(projectRoot, ".claude", "skills", skillName, "SKILL.md");
1963
+ return fs16.existsSync(p) ? p : null;
1770
1964
  }
1771
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) => {
1772
1966
  try {
@@ -1785,7 +1979,7 @@ var triage = new Command3("triage").description("Decompose IDEA into ISSUE.md +
1785
1979
  process.exit(1);
1786
1980
  }
1787
1981
  for (const name of existingIssues) {
1788
- fs13.rmSync(path13.join(loc.taskDir, name), { force: true });
1982
+ fs16.rmSync(path15.join(loc.taskDir, name), { force: true });
1789
1983
  }
1790
1984
  }
1791
1985
  if (opts.model || opts.effort) {
@@ -1823,8 +2017,8 @@ var triage = new Command3("triage").description("Decompose IDEA into ISSUE.md +
1823
2017
  console.error(chalk3.red(`triage failed: ${final.error || "unknown"}`));
1824
2018
  process.exit(1);
1825
2019
  }
1826
- const hasOverview = fs13.existsSync(path13.join(loc.taskDir, "ISSUE.md"));
1827
- 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();
1828
2022
  if (!hasOverview || issueFiles.length === 0) {
1829
2023
  const missing = [];
1830
2024
  if (!hasOverview) missing.push("ISSUE.md");
@@ -1834,7 +2028,7 @@ var triage = new Command3("triage").description("Decompose IDEA into ISSUE.md +
1834
2028
  }
1835
2029
  let blockingQuestionCount = 0;
1836
2030
  for (const name of issueFiles) {
1837
- const content = fs13.readFileSync(path13.join(loc.taskDir, name), "utf-8");
2031
+ const content = fs16.readFileSync(path15.join(loc.taskDir, name), "utf-8");
1838
2032
  blockingQuestionCount += countBlockingQuestions(content);
1839
2033
  }
1840
2034
  await updateWorkflow(loc.taskYml, {
@@ -1862,8 +2056,8 @@ var triage = new Command3("triage").description("Decompose IDEA into ISSUE.md +
1862
2056
  }
1863
2057
  });
1864
2058
  function listIssueArtifacts(taskDir) {
1865
- if (!fs13.existsSync(taskDir)) return [];
1866
- 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();
1867
2061
  }
1868
2062
  function countBlockingQuestions(md) {
1869
2063
  const match = md.match(/## Open Questions\s*\n([\s\S]*?)(\n## |\n*$)/i);
@@ -1877,13 +2071,13 @@ function countBlockingQuestions(md) {
1877
2071
  // src/commands/task/exec.ts
1878
2072
  import { Command as Command4 } from "commander";
1879
2073
  import chalk4 from "chalk";
1880
- import fs14 from "fs";
1881
- import path14 from "path";
2074
+ import fs17 from "fs";
2075
+ import path16 from "path";
1882
2076
  init_task_state2();
1883
2077
  var PLAN_EXECUTE_SKILL_NAME = "plan-execute";
1884
2078
  function resolveSkillFilePath2(projectRoot, skillName) {
1885
- const p = path14.join(projectRoot, ".claude", "skills", skillName, "SKILL.md");
1886
- return fs14.existsSync(p) ? p : null;
2079
+ const p = path16.join(projectRoot, ".claude", "skills", skillName, "SKILL.md");
2080
+ return fs17.existsSync(p) ? p : null;
1887
2081
  }
1888
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) => {
1889
2083
  try {
@@ -1976,7 +2170,7 @@ var summarize = new Command5("summarize").description("Generate a concise title
1976
2170
  });
1977
2171
 
1978
2172
  // src/commands/task/comment.ts
1979
- import fs15 from "fs";
2173
+ import fs18 from "fs";
1980
2174
  import { Command as Command6 } from "commander";
1981
2175
  import chalk6 from "chalk";
1982
2176
  var comment = new Command6("comment").description("Manage comments on a task");
@@ -1986,8 +2180,8 @@ function fail(err) {
1986
2180
  }
1987
2181
  function readBodyFromOpts(opts) {
1988
2182
  if (opts.body !== void 0) return opts.body;
1989
- if (opts.file === "-") return fs15.readFileSync(0, "utf-8");
1990
- 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");
1991
2185
  throw new Error("Provide --body or --file");
1992
2186
  }
1993
2187
  function preview(body, width = 60) {
@@ -2086,7 +2280,7 @@ comment.command("delete <cmtId>").description("Delete a comment by its cmt_ id")
2086
2280
  });
2087
2281
 
2088
2282
  // src/commands/task/description.ts
2089
- import fs16 from "fs";
2283
+ import fs19 from "fs";
2090
2284
  import { Command as Command7 } from "commander";
2091
2285
  import chalk7 from "chalk";
2092
2286
  var description = new Command7("description").description("Show or update the task description");
@@ -2096,8 +2290,8 @@ function fail2(err) {
2096
2290
  }
2097
2291
  function readBodyFromOpts2(opts) {
2098
2292
  if (opts.body !== void 0) return opts.body;
2099
- if (opts.file === "-") return fs16.readFileSync(0, "utf-8");
2100
- 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");
2101
2295
  throw new Error("Provide --body or --file (use --file - to read stdin)");
2102
2296
  }
2103
2297
  description.command("show <taskId>").description("Print the current description (taskId is short id or tsk_)").option("--json", "Output JSON").action(async (taskId, opts) => {
@@ -2146,12 +2340,12 @@ task.addCommand(comment);
2146
2340
  task.addCommand(description);
2147
2341
  task.command("init <input>").description("Create a task from a description or Linear/GitHub issue URL").action(async (input) => {
2148
2342
  const cwd = process.cwd();
2149
- const projectYml = path15.join(cwd, "task0.yml");
2150
- if (!fs17.existsSync(projectYml)) {
2343
+ const projectYml = path17.join(cwd, "task0.yml");
2344
+ if (!fs20.existsSync(projectYml)) {
2151
2345
  console.error(chalk8.red("Not a task0 project (task0.yml not found). Run `task0 project init` first."));
2152
2346
  process.exit(1);
2153
2347
  }
2154
- const projectConfig = yaml6.load(fs17.readFileSync(projectYml, "utf-8"));
2348
+ const projectConfig = yaml7.load(fs20.readFileSync(projectYml, "utf-8"));
2155
2349
  if (projectConfig.kind !== "project") {
2156
2350
  console.error(chalk8.red('Invalid task0.yml: kind is not "project"'));
2157
2351
  process.exit(1);
@@ -2167,38 +2361,38 @@ task.command("init <input>").description("Create a task from a description or Li
2167
2361
  });
2168
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) => {
2169
2363
  const cwd = process.cwd();
2170
- const projectYml = path15.join(cwd, "task0.yml");
2171
- if (!fs17.existsSync(projectYml)) {
2364
+ const projectYml = path17.join(cwd, "task0.yml");
2365
+ if (!fs20.existsSync(projectYml)) {
2172
2366
  console.error(chalk8.red("Not a task0 project (task0.yml not found). Run `task0 project init` first."));
2173
2367
  process.exit(1);
2174
2368
  }
2175
- const projectConfig = yaml6.load(fs17.readFileSync(projectYml, "utf-8"));
2369
+ const projectConfig = yaml7.load(fs20.readFileSync(projectYml, "utf-8"));
2176
2370
  if (projectConfig.kind !== "project") {
2177
2371
  console.error(chalk8.red('Invalid task0.yml: kind is not "project"'));
2178
2372
  process.exit(1);
2179
2373
  }
2180
- const tasksDir = path15.join(cwd, projectConfig.tasks_dir);
2181
- if (!fs17.existsSync(tasksDir)) {
2374
+ const tasksDir = path17.join(cwd, projectConfig.tasks_dir);
2375
+ if (!fs20.existsSync(tasksDir)) {
2182
2376
  console.error(chalk8.red(`Tasks directory not found: ${tasksDir}`));
2183
2377
  process.exit(1);
2184
2378
  }
2185
- 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);
2186
2380
  let migrated = 0;
2187
2381
  let skipped = 0;
2188
2382
  let seededObjectIds = 0;
2189
2383
  for (const name of entries) {
2190
- const taskDir = path15.join(tasksDir, name);
2191
- const taskYml = path15.join(taskDir, "task0.yml");
2192
- if (fs17.existsSync(taskYml)) {
2384
+ const taskDir = path17.join(tasksDir, name);
2385
+ const taskYml = path17.join(taskDir, "task0.yml");
2386
+ if (fs20.existsSync(taskYml)) {
2193
2387
  skipped++;
2194
2388
  try {
2195
- const raw = yaml6.load(fs17.readFileSync(taskYml, "utf-8"));
2389
+ const raw = yaml7.load(fs20.readFileSync(taskYml, "utf-8"));
2196
2390
  if (raw && raw.kind === "task" && !raw.object_id) {
2197
2391
  raw.object_id = generateObjectId("task");
2198
2392
  if (opts.dryRun) {
2199
2393
  console.log(chalk8.dim(`[dry-run] seed object_id: ${taskYml}`));
2200
2394
  } else {
2201
- fs17.writeFileSync(taskYml, yaml6.dump(raw, { lineWidth: 120 }), "utf-8");
2395
+ fs20.writeFileSync(taskYml, yaml7.dump(raw, { lineWidth: 120 }), "utf-8");
2202
2396
  console.log(chalk8.green(` seed object_id: ${taskYml}`));
2203
2397
  }
2204
2398
  seededObjectIds++;
@@ -2223,7 +2417,7 @@ task.command("migrate").description("Add task0.yml to legacy task directories th
2223
2417
  if (opts.dryRun) {
2224
2418
  console.log(chalk8.dim(`[dry-run] ${taskYml}`));
2225
2419
  } else {
2226
- fs17.writeFileSync(taskYml, yaml6.dump(taskConfig), "utf-8");
2420
+ fs20.writeFileSync(taskYml, yaml7.dump(taskConfig), "utf-8");
2227
2421
  console.log(chalk8.green(` ${taskYml}`));
2228
2422
  }
2229
2423
  migrated++;
@@ -2283,7 +2477,7 @@ task.command("done <id>").description("Write task lifecycle state to task0.yml \
2283
2477
  try {
2284
2478
  const { resolveTaskByObjectId: resolveTaskByObjectId2 } = await Promise.resolve().then(() => (init_task_state2(), task_state_exports));
2285
2479
  const loc = resolveTaskByObjectId2(id);
2286
- const raw = yaml6.load(fs17.readFileSync(loc.taskYml, "utf-8"));
2480
+ const raw = yaml7.load(fs20.readFileSync(loc.taskYml, "utf-8"));
2287
2481
  if (opts.phase) {
2288
2482
  const phase = opts.phase.trim();
2289
2483
  if (!phase) {
@@ -2296,7 +2490,7 @@ task.command("done <id>").description("Write task lifecycle state to task0.yml \
2296
2490
  return;
2297
2491
  }
2298
2492
  raw.workflow = { ...workflow2, phase };
2299
- fs17.writeFileSync(loc.taskYml, yaml6.dump(raw, { lineWidth: 120 }), "utf-8");
2493
+ fs20.writeFileSync(loc.taskYml, yaml7.dump(raw, { lineWidth: 120 }), "utf-8");
2300
2494
  console.log(chalk8.green(`${id} phase: ${phase}`));
2301
2495
  return;
2302
2496
  }
@@ -2307,7 +2501,7 @@ task.command("done <id>").description("Write task lifecycle state to task0.yml \
2307
2501
  raw.status = "done";
2308
2502
  const workflow = raw.workflow ?? {};
2309
2503
  raw.workflow = { ...workflow, phase: "completed" };
2310
- fs17.writeFileSync(loc.taskYml, yaml6.dump(raw, { lineWidth: 120 }), "utf-8");
2504
+ fs20.writeFileSync(loc.taskYml, yaml7.dump(raw, { lineWidth: 120 }), "utf-8");
2311
2505
  console.log(chalk8.green(`${id} marked as done`));
2312
2506
  } catch (err) {
2313
2507
  console.error(chalk8.red(err.message));
@@ -2316,33 +2510,33 @@ task.command("done <id>").description("Write task lifecycle state to task0.yml \
2316
2510
  });
2317
2511
  task.command("archive <id>").description("Archive a task (append to tasks.tar, remove from tasks/)").action((id) => {
2318
2512
  const cwd = process.cwd();
2319
- const projectYml = path15.join(cwd, "task0.yml");
2320
- if (!fs17.existsSync(projectYml)) {
2513
+ const projectYml = path17.join(cwd, "task0.yml");
2514
+ if (!fs20.existsSync(projectYml)) {
2321
2515
  console.error(chalk8.red("Not a task0 project (task0.yml not found)."));
2322
2516
  process.exit(1);
2323
2517
  }
2324
- const projectConfig = yaml6.load(fs17.readFileSync(projectYml, "utf-8"));
2518
+ const projectConfig = yaml7.load(fs20.readFileSync(projectYml, "utf-8"));
2325
2519
  if (projectConfig.kind !== "project") {
2326
2520
  console.error(chalk8.red('Invalid task0.yml: kind is not "project"'));
2327
2521
  process.exit(1);
2328
2522
  }
2329
- const tasksDir = path15.join(cwd, projectConfig.tasks_dir);
2330
- const taskDir = path15.join(tasksDir, id);
2331
- 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)) {
2332
2526
  console.error(chalk8.red(`Task "${id}" not found at ${taskDir}`));
2333
2527
  process.exit(1);
2334
2528
  }
2335
2529
  const tarFile = tasksDir + ".tar";
2336
2530
  archiveTaskToTar(tasksDir, id, tarFile);
2337
- fs17.rmSync(taskDir, { recursive: true });
2531
+ fs20.rmSync(taskDir, { recursive: true });
2338
2532
  console.log(chalk8.green(`Archived "${id}" \u2192 ${tarFile}`));
2339
2533
  });
2340
2534
  function archiveTaskToTar(tasksDir, taskId, tarFile) {
2341
- if (fs17.existsSync(tarFile) && !fs17.statSync(tarFile).isFile()) {
2535
+ if (fs20.existsSync(tarFile) && !fs20.statSync(tarFile).isFile()) {
2342
2536
  console.error(chalk8.red(`${tarFile} exists but is not a file (likely a leftover directory). Remove it manually first.`));
2343
2537
  process.exit(1);
2344
2538
  }
2345
- if (fs17.existsSync(tarFile)) {
2539
+ if (fs20.existsSync(tarFile)) {
2346
2540
  execSync(`tar -rf ${JSON.stringify(tarFile)} -C ${JSON.stringify(tasksDir)} ${JSON.stringify(taskId)}`);
2347
2541
  } else {
2348
2542
  execSync(`tar -cf ${JSON.stringify(tarFile)} -C ${JSON.stringify(tasksDir)} ${JSON.stringify(taskId)}`);
@@ -2352,9 +2546,9 @@ function archiveTaskToTar(tasksDir, taskId, tarFile) {
2352
2546
  // src/commands/ui.ts
2353
2547
  import { Command as Command9 } from "commander";
2354
2548
  import { spawn } from "child_process";
2355
- import path16 from "path";
2549
+ import path18 from "path";
2356
2550
  import chalk9 from "chalk";
2357
- var DASHBOARD_DIR = path16.resolve(
2551
+ var DASHBOARD_DIR = path18.resolve(
2358
2552
  import.meta.dirname,
2359
2553
  "..",
2360
2554
  "..",
@@ -2417,31 +2611,6 @@ var AGENT_MODEL_DEFAULTS = {
2417
2611
  { id: "sonnet-4-thinking", label: "Sonnet 4 Thinking" }
2418
2612
  ]
2419
2613
  };
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
- };
2445
2614
  function defaultAgentModelFetchCommand(agent2) {
2446
2615
  if (agent2 === "cursor-agent")
2447
2616
  return "cursor-agent models";
@@ -2660,12 +2829,12 @@ models.command("default <agent>").description("Get or set default model / effort
2660
2829
  });
2661
2830
 
2662
2831
  // src/commands/agent-run.ts
2663
- import fs18 from "fs";
2664
- import path17 from "path";
2832
+ import fs21 from "fs";
2833
+ import path19 from "path";
2665
2834
  import { Command as Command11 } from "commander";
2666
2835
  import chalk11 from "chalk";
2667
2836
  import { spawn as spawn2, spawnSync as spawnSync3 } from "child_process";
2668
- import yaml7 from "js-yaml";
2837
+ import yaml8 from "js-yaml";
2669
2838
  var agentRun = new Command11("agent-run").description("Inspect and control agent runs");
2670
2839
  agentRun.command("list").description("List active agent runs").option("--json", "Output JSON").option("--task <id>", "Filter by task id").action(async (opts) => {
2671
2840
  try {
@@ -2789,7 +2958,7 @@ profile.command("get <ref>").description("Show one runtime profile by slug or ob
2789
2958
  console.log(formatProfile(result.runtime));
2790
2959
  console.log();
2791
2960
  console.log(chalk11.dim("--- exec ---"));
2792
- console.log(yaml7.dump(result.runtime.exec, { lineWidth: 100 }));
2961
+ console.log(yaml8.dump(result.runtime.exec, { lineWidth: 100 }));
2793
2962
  } catch (err) {
2794
2963
  const apiErr = err;
2795
2964
  if (apiErr.status === 404) failProfile(`not found: ${ref}`);
@@ -2799,7 +2968,7 @@ profile.command("get <ref>").description("Show one runtime profile by slug or ob
2799
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) => {
2800
2969
  let parsed;
2801
2970
  try {
2802
- parsed = yaml7.load(fs18.readFileSync(path17.resolve(opts.fromFile), "utf-8"));
2971
+ parsed = yaml8.load(fs21.readFileSync(path19.resolve(opts.fromFile), "utf-8"));
2803
2972
  } catch (err) {
2804
2973
  failProfile(`cannot read ${opts.fromFile}: ${err.message}`);
2805
2974
  }
@@ -2815,14 +2984,14 @@ profile.command("edit <ref>").description("Open the runtime profile YAML in $EDI
2815
2984
  try {
2816
2985
  const result = await api.get(`/api/runtime-profiles/${encodeURIComponent(ref)}`);
2817
2986
  if (result.runtime.system) failProfile("cannot edit a system runtime profile");
2818
- const tmp = path17.join(process.env.TMPDIR || "/tmp", `task0-runtime-${result.runtime.slug}-${Date.now()}.yml`);
2819
- 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");
2820
2989
  const editor = process.env.EDITOR || "vi";
2821
2990
  const r = spawnSync3(editor, [tmp], { stdio: "inherit" });
2822
2991
  if (r.status !== 0) failProfile(`editor exited with status ${r.status}`);
2823
- const updated = yaml7.load(fs18.readFileSync(tmp, "utf-8"));
2992
+ const updated = yaml8.load(fs21.readFileSync(tmp, "utf-8"));
2824
2993
  await api.put(`/api/runtime-profiles/${encodeURIComponent(ref)}`, updated);
2825
- fs18.unlinkSync(tmp);
2994
+ fs21.unlinkSync(tmp);
2826
2995
  console.log(chalk11.green(`updated ${ref}`));
2827
2996
  } catch (err) {
2828
2997
  failProfile(err.message);
@@ -2842,13 +3011,13 @@ agentRun.addCommand(profile);
2842
3011
  // src/commands/plan.ts
2843
3012
  import { Command as Command12 } from "commander";
2844
3013
  import chalk12 from "chalk";
2845
- import fs19 from "fs";
2846
- import path18 from "path";
3014
+ import fs22 from "fs";
3015
+ import path20 from "path";
2847
3016
  init_task_state2();
2848
3017
  var PLAN_GENERATE_SKILL_NAME = "plan-generate";
2849
3018
  function resolveSkillFilePath3(projectRoot, skillName) {
2850
- const p = path18.join(projectRoot, ".claude", "skills", skillName, "SKILL.md");
2851
- return fs19.existsSync(p) ? p : null;
3019
+ const p = path20.join(projectRoot, ".claude", "skills", skillName, "SKILL.md");
3020
+ return fs22.existsSync(p) ? p : null;
2852
3021
  }
2853
3022
  var plan = new Command12("plan").description("Generate and refine plans");
2854
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) => {
@@ -2980,7 +3149,7 @@ var PLAN_REFINE_SKILL_NAME = "plan-refine";
2980
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) => {
2981
3150
  try {
2982
3151
  const loc = resolveTaskByObjectId(objectId);
2983
- const files = fs19.readdirSync(loc.taskDir);
3152
+ const files = fs22.readdirSync(loc.taskDir);
2984
3153
  const planFiles = files.filter((f) => /^PLAN-\d+-(codex|claude-code|cursor)\.md$/.test(f));
2985
3154
  if (planFiles.length === 0) {
2986
3155
  console.error(chalk12.red("No PLAN-NN-<agent>.md files found. Run `task0 plan generate` first."));
@@ -3022,8 +3191,8 @@ plan.command("refine <objectId>").description("Synthesize plan files + ISSUE fil
3022
3191
  if (!opts.json) console.log(chalk12.dim(`[refine] ${s.status}${s.phase ? " " + s.phase : ""}`));
3023
3192
  }
3024
3193
  });
3025
- const refinedPath = path18.join(loc.taskDir, refinedFile);
3026
- const wrote = fs19.existsSync(refinedPath);
3194
+ const refinedPath = path20.join(loc.taskDir, refinedFile);
3195
+ const wrote = fs22.existsSync(refinedPath);
3027
3196
  if (final.status === "done" && wrote) {
3028
3197
  await updateWorkflow(loc.taskYml, {
3029
3198
  phase: "refined",
@@ -3175,8 +3344,8 @@ import { Command as Command14 } from "commander";
3175
3344
  import chalk14 from "chalk";
3176
3345
 
3177
3346
  // src/lib/project.ts
3178
- import path19 from "path";
3179
- import fs20 from "fs";
3347
+ import path21 from "path";
3348
+ import fs23 from "fs";
3180
3349
  function resolveProjectName(opts) {
3181
3350
  if (opts.project && opts.project.length > 0) return opts.project;
3182
3351
  const config = loadConfig();
@@ -3186,15 +3355,15 @@ function resolveProjectName(opts) {
3186
3355
  "Cannot resolve project: no registered projects. Use `task0 source add <path>` first, or pass --project <name>."
3187
3356
  );
3188
3357
  }
3189
- const cwd = fs20.realpathSync(process.cwd());
3358
+ const cwd = fs23.realpathSync(process.cwd());
3190
3359
  for (const source2 of projects) {
3191
3360
  let sourceAbs;
3192
3361
  try {
3193
- sourceAbs = fs20.realpathSync(path19.resolve(source2.path));
3362
+ sourceAbs = fs23.realpathSync(path21.resolve(source2.path));
3194
3363
  } catch {
3195
- sourceAbs = path19.resolve(source2.path);
3364
+ sourceAbs = path21.resolve(source2.path);
3196
3365
  }
3197
- if (cwd === sourceAbs || cwd.startsWith(sourceAbs + path19.sep)) {
3366
+ if (cwd === sourceAbs || cwd.startsWith(sourceAbs + path21.sep)) {
3198
3367
  return source2.name;
3199
3368
  }
3200
3369
  }
@@ -3834,12 +4003,12 @@ import chalk17 from "chalk";
3834
4003
 
3835
4004
  // src/core/issue/decision.ts
3836
4005
  init_node();
3837
- import fs21 from "fs";
3838
- import path20 from "path";
4006
+ import fs24 from "fs";
4007
+ import path22 from "path";
3839
4008
  init_task_state2();
3840
4009
  var DECISION_FILE_RE = /^DECISION-(\d+)-([a-z0-9-]+)\.md$/;
3841
4010
  function selectBlockingIssues(taskDir, explicitIssue) {
3842
- const files = fs21.existsSync(taskDir) ? fs21.readdirSync(taskDir) : [];
4011
+ const files = fs24.existsSync(taskDir) ? fs24.readdirSync(taskDir) : [];
3843
4012
  const issueFiles = files.filter((f) => /^ISSUE-\d+\.md$/.test(f)).sort();
3844
4013
  const all = readOpenQuestions(taskDir, issueFiles);
3845
4014
  if (!explicitIssue) return all;
@@ -3943,12 +4112,12 @@ function parseConsolidatedAnswers(md) {
3943
4112
  return sections;
3944
4113
  }
3945
4114
  function rewriteIssueWithDecisions(taskDir, issueFile, consolidatedFile) {
3946
- const issuePath = path20.join(taskDir, issueFile);
3947
- const consolidatedPath = path20.join(taskDir, consolidatedFile);
3948
- if (!fs21.existsSync(issuePath)) throw new Error(`${issueFile} not found in ${taskDir}`);
3949
- if (!fs21.existsSync(consolidatedPath)) throw new Error(`${consolidatedFile} not found in ${taskDir}`);
3950
- const issueMd = fs21.readFileSync(issuePath, "utf-8");
3951
- 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");
3952
4121
  const answers = parseConsolidatedAnswers(consolidatedMd);
3953
4122
  if (answers.length === 0) {
3954
4123
  throw new Error(`${consolidatedFile} has no "## Qn" sections; cannot derive Decisions`);
@@ -3970,7 +4139,7 @@ _Resolved from [${consolidatedFile}](${consolidatedFile}); see that file for rea
3970
4139
  throw new Error(`${issueFile} has no "## Open Questions" section to replace`);
3971
4140
  }
3972
4141
  const next = issueMd.replace(openQRe, decisionsSection.trimEnd() + "\n");
3973
- fs21.writeFileSync(issuePath, next, "utf-8");
4142
+ fs24.writeFileSync(issuePath, next, "utf-8");
3974
4143
  return { replaced: answers.length };
3975
4144
  }
3976
4145
  async function propose(opts) {
@@ -3998,8 +4167,8 @@ async function propose(opts) {
3998
4167
  const kicks = [];
3999
4168
  for (const agent2 of opts.agents) {
4000
4169
  const decisionFile = decisionFileName(issue2, agent2);
4001
- const decisionPath = path20.join(loc.taskDir, decisionFile);
4002
- if (fs21.existsSync(decisionPath) && !opts.force) {
4170
+ const decisionPath = path22.join(loc.taskDir, decisionFile);
4171
+ if (fs24.existsSync(decisionPath) && !opts.force) {
4003
4172
  if (opts.ifNeeded) {
4004
4173
  kicks.push({
4005
4174
  issue: issue2.file,
@@ -4074,7 +4243,7 @@ async function propose(opts) {
4074
4243
  const final = await waitForAgentRun(k.agentRunId);
4075
4244
  if (final.status !== "done") {
4076
4245
  k.error = final.error || `runtime ${k.agentRunId} ended with ${final.status}`;
4077
- } else if (!fs21.existsSync(path20.join(loc.taskDir, k.decisionFile))) {
4246
+ } else if (!fs24.existsSync(path22.join(loc.taskDir, k.decisionFile))) {
4078
4247
  k.error = `runtime completed but ${k.decisionFile} was not written`;
4079
4248
  }
4080
4249
  } catch (err) {
@@ -4095,7 +4264,7 @@ async function consolidate(opts) {
4095
4264
  const kicks = [];
4096
4265
  const modelOpts = resolveModelOptions(opts.agent, { model: opts.model, effort: opts.effort }, opts.warn);
4097
4266
  for (const issue2 of issues) {
4098
- const files = fs21.readdirSync(loc.taskDir);
4267
+ const files = fs24.readdirSync(loc.taskDir);
4099
4268
  const proposalFiles = files.filter((f) => {
4100
4269
  const m = f.match(DECISION_FILE_RE);
4101
4270
  return !!m && m[1] === issue2.index && m[2] !== "consolidated";
@@ -4141,7 +4310,7 @@ async function consolidate(opts) {
4141
4310
  if (opts.wait) {
4142
4311
  try {
4143
4312
  const final = await waitForAgentRun(agentRunId);
4144
- const wrote = fs21.existsSync(path20.join(loc.taskDir, consolidatedFile));
4313
+ const wrote = fs24.existsSync(path22.join(loc.taskDir, consolidatedFile));
4145
4314
  if (final.status !== "done") {
4146
4315
  kick.error = final.error || `runtime ${agentRunId} ended with ${final.status}`;
4147
4316
  } else if (!wrote) {
@@ -4207,7 +4376,7 @@ async function approve(opts) {
4207
4376
  return { updated };
4208
4377
  }
4209
4378
  function buildReferenceFiles(taskDir, issue2) {
4210
- const names = fs21.readdirSync(taskDir);
4379
+ const names = fs24.readdirSync(taskDir);
4211
4380
  const refs = [issue2.file];
4212
4381
  if (names.includes("ISSUE.md")) refs.push("ISSUE.md");
4213
4382
  const ideaFile = `IDEA-${issue2.index}.md`;
@@ -4315,12 +4484,12 @@ issue.command("approve <taskId>").description("Apply the consolidated decisions
4315
4484
  });
4316
4485
 
4317
4486
  // src/commands/agent.ts
4318
- import fs22 from "fs";
4319
- import path21 from "path";
4487
+ import fs25 from "fs";
4488
+ import path23 from "path";
4320
4489
  import { spawnSync as spawnSync5 } from "child_process";
4321
4490
  import { Command as Command18 } from "commander";
4322
4491
  import chalk18 from "chalk";
4323
- import yaml8 from "js-yaml";
4492
+ import yaml9 from "js-yaml";
4324
4493
  function statusBadge(s) {
4325
4494
  if (s === "working") return chalk18.cyan("\u25CF");
4326
4495
  if (s === "error") return chalk18.red("\u25CF");
@@ -4380,10 +4549,10 @@ agent.command("get <ref>").description("Show one agent by object_id or slug").op
4380
4549
  }
4381
4550
  console.log();
4382
4551
  console.log(chalk18.dim("--- spec ---"));
4383
- console.log(yaml8.dump(result.agent.spec, { lineWidth: 100 }));
4552
+ console.log(yaml9.dump(result.agent.spec, { lineWidth: 100 }));
4384
4553
  if (result.agent.mcp_config) {
4385
4554
  console.log(chalk18.dim("--- mcp_config ---"));
4386
- console.log(yaml8.dump(result.agent.mcp_config, { lineWidth: 100 }));
4555
+ console.log(yaml9.dump(result.agent.mcp_config, { lineWidth: 100 }));
4387
4556
  }
4388
4557
  } catch (err) {
4389
4558
  const apiErr = err;
@@ -4394,8 +4563,8 @@ agent.command("get <ref>").description("Show one agent by object_id or slug").op
4394
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) => {
4395
4564
  let parsed;
4396
4565
  try {
4397
- const raw = fs22.readFileSync(path21.resolve(opts.fromFile), "utf-8");
4398
- parsed = yaml8.load(raw);
4566
+ const raw = fs25.readFileSync(path23.resolve(opts.fromFile), "utf-8");
4567
+ parsed = yaml9.load(raw);
4399
4568
  } catch (err) {
4400
4569
  fail5(`cannot read ${opts.fromFile}: ${err.message}`);
4401
4570
  }
@@ -4410,17 +4579,17 @@ agent.command("create").description("Create an agent from a YAML spec file").req
4410
4579
  agent.command("edit <ref>").description("Open the agent YAML in $EDITOR and save changes").action(async (ref) => {
4411
4580
  try {
4412
4581
  const result = await api.get(`/api/agents/${encodeURIComponent(ref)}`);
4413
- const tmp = path21.join(
4582
+ const tmp = path23.join(
4414
4583
  process.env.TMPDIR || "/tmp",
4415
4584
  `task0-agent-${result.agent.slug}-${Date.now()}.yml`
4416
4585
  );
4417
- fs22.writeFileSync(tmp, yaml8.dump(result.agent, { lineWidth: 100 }), "utf-8");
4586
+ fs25.writeFileSync(tmp, yaml9.dump(result.agent, { lineWidth: 100 }), "utf-8");
4418
4587
  const editor = process.env.EDITOR || "vi";
4419
4588
  const r = spawnSync5(editor, [tmp], { stdio: "inherit" });
4420
4589
  if (r.status !== 0) fail5(`editor exited with status ${r.status}`);
4421
- const updated = yaml8.load(fs22.readFileSync(tmp, "utf-8"));
4590
+ const updated = yaml9.load(fs25.readFileSync(tmp, "utf-8"));
4422
4591
  await api.put(`/api/agents/${encodeURIComponent(ref)}`, updated);
4423
- fs22.unlinkSync(tmp);
4592
+ fs25.unlinkSync(tmp);
4424
4593
  console.log(chalk18.green(`updated ${ref}`));
4425
4594
  } catch (err) {
4426
4595
  fail5(err.message);
@@ -4491,7 +4660,7 @@ var mcp = new Command18("mcp").description("Manage per-agent MCP config (mcp_con
4491
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) => {
4492
4661
  let parsed;
4493
4662
  try {
4494
- parsed = JSON.parse(fs22.readFileSync(path21.resolve(opts.fromFile), "utf-8"));
4663
+ parsed = JSON.parse(fs25.readFileSync(path23.resolve(opts.fromFile), "utf-8"));
4495
4664
  } catch (err) {
4496
4665
  fail5(`cannot parse ${opts.fromFile}: ${err.message}`);
4497
4666
  }
@@ -4640,36 +4809,36 @@ function pickRegisterAuth(flagToken) {
4640
4809
 
4641
4810
  // src/core/daemon-rpc-handlers.ts
4642
4811
  init_node();
4643
- import fs25 from "fs";
4644
- import path24 from "path";
4812
+ import fs28 from "fs";
4813
+ import path26 from "path";
4645
4814
 
4646
4815
  // src/core/daemon-agent-run-runner.ts
4647
4816
  import { execFileSync } from "child_process";
4648
- import fs24 from "fs";
4649
- import path23 from "path";
4817
+ import fs27 from "fs";
4818
+ import path25 from "path";
4650
4819
 
4651
4820
  // src/core/daemon-agent-run-dir.ts
4652
4821
  init_node();
4653
- import fs23 from "fs";
4654
- import path22 from "path";
4822
+ import fs26 from "fs";
4823
+ import path24 from "path";
4655
4824
  function agentRunRoot() {
4656
- return process.env.TASK0_DAEMON_AGENT_RUN_DIR || path22.join(task0Home(), "agent-run");
4825
+ return process.env.TASK0_DAEMON_AGENT_RUN_DIR || path24.join(profileDir(), "agent-run");
4657
4826
  }
4658
4827
  function agentRunDir(runId) {
4659
- return path22.join(agentRunRoot(), runId);
4828
+ return path24.join(agentRunRoot(), runId);
4660
4829
  }
4661
4830
  function agentRunStatusPath(runId) {
4662
- return path22.join(agentRunDir(runId), "status.json");
4831
+ return path24.join(agentRunDir(runId), "status.json");
4663
4832
  }
4664
4833
  function ensureAgentRunDir(runId) {
4665
4834
  const dir = agentRunDir(runId);
4666
- fs23.mkdirSync(dir, { recursive: true });
4835
+ fs26.mkdirSync(dir, { recursive: true });
4667
4836
  return dir;
4668
4837
  }
4669
4838
  function removeAgentRunDir(runId) {
4670
4839
  const dir = agentRunDir(runId);
4671
- if (!fs23.existsSync(dir)) return;
4672
- fs23.rmSync(dir, { recursive: true, force: true });
4840
+ if (!fs26.existsSync(dir)) return;
4841
+ fs26.rmSync(dir, { recursive: true, force: true });
4673
4842
  }
4674
4843
 
4675
4844
  // src/core/daemon-agent-run-runner.ts
@@ -4711,9 +4880,9 @@ function killSession2(sessionName) {
4711
4880
  }
4712
4881
  function readStatusFile(runId) {
4713
4882
  const filePath = agentRunStatusPath(runId);
4714
- if (!fs24.existsSync(filePath)) return null;
4883
+ if (!fs27.existsSync(filePath)) return null;
4715
4884
  try {
4716
- const raw = fs24.readFileSync(filePath, "utf-8");
4885
+ const raw = fs27.readFileSync(filePath, "utf-8");
4717
4886
  const parsed = JSON.parse(raw);
4718
4887
  return { raw, parsed };
4719
4888
  } catch {
@@ -4738,22 +4907,22 @@ function launchAgentRun(params) {
4738
4907
  if (active.has(params.runId)) {
4739
4908
  throw Object.assign(new Error(`agent run already active: ${params.runId}`), { code: "already_running" });
4740
4909
  }
4741
- if (!fs24.existsSync(params.workspace)) {
4910
+ if (!fs27.existsSync(params.workspace)) {
4742
4911
  throw Object.assign(new Error(`workspace not found: ${params.workspace}`), { code: "workspace_missing" });
4743
4912
  }
4744
4913
  const runDir = ensureAgentRunDir(params.runId);
4745
- const scriptPath = path23.join(runDir, "script.sh");
4746
- fs24.writeFileSync(scriptPath, params.scriptContent, { mode: 493 });
4914
+ const scriptPath = path25.join(runDir, "script.sh");
4915
+ fs27.writeFileSync(scriptPath, params.scriptContent, { mode: 493 });
4747
4916
  if (params.promptContent !== void 0) {
4748
- fs24.writeFileSync(path23.join(runDir, "prompt.txt"), params.promptContent, "utf-8");
4917
+ fs27.writeFileSync(path25.join(runDir, "prompt.txt"), params.promptContent, "utf-8");
4749
4918
  }
4750
4919
  for (const [name, content] of Object.entries(params.auxFiles ?? {})) {
4751
4920
  if (name.includes("/") || name.includes("\\") || name === ".." || name === ".") {
4752
4921
  throw Object.assign(new Error(`invalid aux file name: ${name}`), { code: "invalid_params" });
4753
4922
  }
4754
- fs24.writeFileSync(path23.join(runDir, name), content, "utf-8");
4923
+ fs27.writeFileSync(path25.join(runDir, name), content, "utf-8");
4755
4924
  }
4756
- fs24.writeFileSync(
4925
+ fs27.writeFileSync(
4757
4926
  agentRunStatusPath(params.runId),
4758
4927
  JSON.stringify({ status: "starting", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2),
4759
4928
  "utf-8"
@@ -4834,15 +5003,15 @@ function ensureString(value, name) {
4834
5003
  }
4835
5004
  function ensureSafeTaskId(value) {
4836
5005
  const id = ensureString(value, "taskId");
4837
- if (id.includes("/") || id.includes("\\") || id.includes("\0") || id === "." || id === ".." || path24.isAbsolute(id)) {
5006
+ if (id.includes("/") || id.includes("\\") || id.includes("\0") || id === "." || id === ".." || path26.isAbsolute(id)) {
4838
5007
  throw Object.assign(new Error(`invalid taskId: ${id}`), { code: "invalid_params" });
4839
5008
  }
4840
5009
  return id;
4841
5010
  }
4842
5011
  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)) {
5012
+ const root = path26.resolve(rootAbs);
5013
+ const target = path26.resolve(resolvedAbs);
5014
+ if (target !== root && !target.startsWith(root + path26.sep)) {
4846
5015
  throw Object.assign(new Error("path escapes containment root"), { code: "invalid_params" });
4847
5016
  }
4848
5017
  }
@@ -4884,7 +5053,7 @@ var rpcHandlers = {
4884
5053
  // missing object_id / id mismatches applied in place on the daemon's FS.
4885
5054
  async scan_project(params) {
4886
5055
  const rootPath = ensureString(params.rootPath, "rootPath");
4887
- 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);
4888
5057
  const result = scanProjectWithRepair(rootPath, name);
4889
5058
  return { tasks: result.tasks, errors: result.errors };
4890
5059
  },
@@ -4895,7 +5064,7 @@ var rpcHandlers = {
4895
5064
  const filePath = ensureString(params.path, "path");
4896
5065
  let stat;
4897
5066
  try {
4898
- stat = fs25.statSync(filePath);
5067
+ stat = fs28.statSync(filePath);
4899
5068
  } catch {
4900
5069
  throw Object.assign(new Error("file not found"), { code: "not_found" });
4901
5070
  }
@@ -4905,7 +5074,7 @@ var rpcHandlers = {
4905
5074
  if (stat.size > MAX_FILE_BYTES) {
4906
5075
  throw Object.assign(new Error(`file too large (${stat.size} bytes > ${MAX_FILE_BYTES})`), { code: "too_large" });
4907
5076
  }
4908
- const content = fs25.readFileSync(filePath, "utf-8");
5077
+ const content = fs28.readFileSync(filePath, "utf-8");
4909
5078
  return { content, size: stat.size, modifiedAt: stat.mtime.toISOString() };
4910
5079
  },
4911
5080
  // ---------------------------------------------------------------------
@@ -4920,15 +5089,15 @@ var rpcHandlers = {
4920
5089
  },
4921
5090
  async project_add(params, ctx) {
4922
5091
  const rawPath = ensureString(params.path, "path");
4923
- const absPath = path24.resolve(rawPath);
4924
- if (!fs25.existsSync(absPath)) {
5092
+ const absPath = path26.resolve(rawPath);
5093
+ if (!fs28.existsSync(absPath)) {
4925
5094
  throw Object.assign(new Error(`path does not exist: ${absPath}`), { code: "not_found" });
4926
5095
  }
4927
- const stat = fs25.statSync(absPath);
5096
+ const stat = fs28.statSync(absPath);
4928
5097
  if (!stat.isDirectory()) {
4929
5098
  throw Object.assign(new Error(`path is not a directory: ${absPath}`), { code: "invalid_target" });
4930
5099
  }
4931
- const name = optionalString(params.name)?.trim() || path24.basename(absPath);
5100
+ const name = optionalString(params.name)?.trim() || path26.basename(absPath);
4932
5101
  const result = scanProjectWithRepair(absPath, name);
4933
5102
  addSource({ name, type: "project", path: absPath, enabled: true });
4934
5103
  ctx.notifyManifestChanged();
@@ -4977,24 +5146,24 @@ var rpcHandlers = {
4977
5146
  if (!project2) {
4978
5147
  throw Object.assign(new Error(`project not found: ${projectName}`), { code: "not_found" });
4979
5148
  }
4980
- const projectAbs = path24.resolve(project2.path);
4981
- const projectConfig = readYaml(path24.join(projectAbs, "task0.yml"));
5149
+ const projectAbs = path26.resolve(project2.path);
5150
+ const projectConfig = readYaml(path26.join(projectAbs, "task0.yml"));
4982
5151
  if (!projectConfig) {
4983
5152
  throw Object.assign(new Error(`invalid project: missing task0.yml at ${projectAbs}`), { code: "invalid_project" });
4984
5153
  }
4985
- const tasksRoot = path24.resolve(projectAbs, projectConfig.tasks_dir);
4986
- const taskDir = path24.resolve(tasksRoot, taskId);
5154
+ const tasksRoot = path26.resolve(projectAbs, projectConfig.tasks_dir);
5155
+ const taskDir = path26.resolve(tasksRoot, taskId);
4987
5156
  assertContained(tasksRoot, taskDir);
4988
- if (!fs25.existsSync(taskDir)) {
5157
+ if (!fs28.existsSync(taskDir)) {
4989
5158
  throw Object.assign(new Error(`task not found: ${taskId}`), { code: "not_found" });
4990
5159
  }
4991
- const taskYml = path24.join(taskDir, "task0.yml");
4992
- const yaml10 = readYaml(taskYml);
4993
- if (!yaml10) {
5160
+ const taskYml = path26.join(taskDir, "task0.yml");
5161
+ const yaml11 = readYaml(taskYml);
5162
+ if (!yaml11) {
4994
5163
  throw Object.assign(new Error(`task yaml missing or unreadable: ${taskYml}`), { code: "not_found" });
4995
5164
  }
4996
- const files = fs25.readdirSync(taskDir).filter((name) => name !== "task0.yml");
4997
- return { task_dir: taskDir, yaml: yaml10, files };
5165
+ const files = fs28.readdirSync(taskDir).filter((name) => name !== "task0.yml");
5166
+ return { task_dir: taskDir, yaml: yaml11, files };
4998
5167
  },
4999
5168
  // Create a task directory + write task0.yml + write any additional named
5000
5169
  // files. Hub decides the taskId and yaml content (slug/object_id are
@@ -5002,8 +5171,8 @@ var rpcHandlers = {
5002
5171
  async task_create(params, ctx) {
5003
5172
  const projectName = ensureString(params.projectName, "projectName");
5004
5173
  const taskId = ensureSafeTaskId(params.taskId);
5005
- const yaml10 = params.yaml;
5006
- if (!yaml10 || typeof yaml10 !== "object") {
5174
+ const yaml11 = params.yaml;
5175
+ if (!yaml11 || typeof yaml11 !== "object") {
5007
5176
  throw Object.assign(new Error("yaml (object) is required"), { code: "invalid_params" });
5008
5177
  }
5009
5178
  const files = params.files ?? {};
@@ -5011,24 +5180,24 @@ var rpcHandlers = {
5011
5180
  if (!project2) {
5012
5181
  throw Object.assign(new Error(`project not found: ${projectName}`), { code: "not_found" });
5013
5182
  }
5014
- const projectAbs = path24.resolve(project2.path);
5015
- const projectConfig = readYaml(path24.join(projectAbs, "task0.yml"));
5183
+ const projectAbs = path26.resolve(project2.path);
5184
+ const projectConfig = readYaml(path26.join(projectAbs, "task0.yml"));
5016
5185
  if (!projectConfig) {
5017
5186
  throw Object.assign(new Error(`invalid project: missing task0.yml at ${projectAbs}`), { code: "invalid_project" });
5018
5187
  }
5019
- const tasksRoot = path24.resolve(projectAbs, projectConfig.tasks_dir);
5020
- const taskDir = path24.resolve(tasksRoot, taskId);
5188
+ const tasksRoot = path26.resolve(projectAbs, projectConfig.tasks_dir);
5189
+ const taskDir = path26.resolve(tasksRoot, taskId);
5021
5190
  assertContained(tasksRoot, taskDir);
5022
- if (fs25.existsSync(taskDir)) {
5191
+ if (fs28.existsSync(taskDir)) {
5023
5192
  throw Object.assign(new Error(`task already exists: ${taskId}`), { code: "already_exists" });
5024
5193
  }
5025
- fs25.mkdirSync(taskDir, { recursive: true });
5026
- writeYaml(path24.join(taskDir, "task0.yml"), yaml10);
5194
+ fs28.mkdirSync(taskDir, { recursive: true });
5195
+ writeYaml(path26.join(taskDir, "task0.yml"), yaml11);
5027
5196
  for (const [name, content] of Object.entries(files)) {
5028
5197
  if (name.includes("/") || name.includes("\\") || name === ".." || name === ".") {
5029
5198
  throw Object.assign(new Error(`invalid file name: ${name}`), { code: "invalid_params" });
5030
5199
  }
5031
- fs25.writeFileSync(path24.join(taskDir, name), content, "utf-8");
5200
+ fs28.writeFileSync(path26.join(taskDir, name), content, "utf-8");
5032
5201
  }
5033
5202
  ctx.notifyManifestChanged();
5034
5203
  return { task_dir: taskDir };
@@ -5040,27 +5209,27 @@ var rpcHandlers = {
5040
5209
  async task_write_yaml(params, ctx) {
5041
5210
  const projectName = ensureString(params.projectName, "projectName");
5042
5211
  const taskId = ensureSafeTaskId(params.taskId);
5043
- const yaml10 = params.yaml;
5044
- if (!yaml10 || typeof yaml10 !== "object") {
5212
+ const yaml11 = params.yaml;
5213
+ if (!yaml11 || typeof yaml11 !== "object") {
5045
5214
  throw Object.assign(new Error("yaml (object) is required"), { code: "invalid_params" });
5046
5215
  }
5047
5216
  const project2 = listProjects().find((p) => p.name === projectName);
5048
5217
  if (!project2) {
5049
5218
  throw Object.assign(new Error(`project not found: ${projectName}`), { code: "not_found" });
5050
5219
  }
5051
- const projectAbs = path24.resolve(project2.path);
5052
- const projectConfig = readYaml(path24.join(projectAbs, "task0.yml"));
5220
+ const projectAbs = path26.resolve(project2.path);
5221
+ const projectConfig = readYaml(path26.join(projectAbs, "task0.yml"));
5053
5222
  if (!projectConfig) {
5054
5223
  throw Object.assign(new Error(`invalid project: missing task0.yml at ${projectAbs}`), { code: "invalid_project" });
5055
5224
  }
5056
- const tasksRoot = path24.resolve(projectAbs, projectConfig.tasks_dir);
5057
- const taskDir = path24.resolve(tasksRoot, taskId);
5225
+ const tasksRoot = path26.resolve(projectAbs, projectConfig.tasks_dir);
5226
+ const taskDir = path26.resolve(tasksRoot, taskId);
5058
5227
  assertContained(tasksRoot, taskDir);
5059
- const taskYml = path24.join(taskDir, "task0.yml");
5060
- if (!fs25.existsSync(taskYml)) {
5228
+ const taskYml = path26.join(taskDir, "task0.yml");
5229
+ if (!fs28.existsSync(taskYml)) {
5061
5230
  throw Object.assign(new Error(`task yaml not found: ${taskYml}`), { code: "not_found" });
5062
5231
  }
5063
- writeYaml(taskYml, yaml10);
5232
+ writeYaml(taskYml, yaml11);
5064
5233
  ctx.notifyManifestChanged();
5065
5234
  return { task_dir: taskDir };
5066
5235
  },
@@ -5073,18 +5242,18 @@ var rpcHandlers = {
5073
5242
  if (!project2) {
5074
5243
  throw Object.assign(new Error(`project not found: ${projectName}`), { code: "not_found" });
5075
5244
  }
5076
- const projectAbs = path24.resolve(project2.path);
5077
- const projectConfig = readYaml(path24.join(projectAbs, "task0.yml"));
5245
+ const projectAbs = path26.resolve(project2.path);
5246
+ const projectConfig = readYaml(path26.join(projectAbs, "task0.yml"));
5078
5247
  if (!projectConfig) {
5079
5248
  throw Object.assign(new Error(`invalid project: missing task0.yml at ${projectAbs}`), { code: "invalid_project" });
5080
5249
  }
5081
- const tasksRoot = path24.resolve(projectAbs, projectConfig.tasks_dir);
5082
- const taskDir = path24.resolve(tasksRoot, taskId);
5250
+ const tasksRoot = path26.resolve(projectAbs, projectConfig.tasks_dir);
5251
+ const taskDir = path26.resolve(tasksRoot, taskId);
5083
5252
  assertContained(tasksRoot, taskDir);
5084
- if (!fs25.existsSync(taskDir)) {
5253
+ if (!fs28.existsSync(taskDir)) {
5085
5254
  throw Object.assign(new Error(`task not found: ${taskId}`), { code: "not_found" });
5086
5255
  }
5087
- fs25.rmSync(taskDir, { recursive: true, force: true });
5256
+ fs28.rmSync(taskDir, { recursive: true, force: true });
5088
5257
  ctx.notifyManifestChanged();
5089
5258
  return { task_dir: taskDir, deleted: true };
5090
5259
  },
@@ -5115,9 +5284,9 @@ var rpcHandlers = {
5115
5284
  const projectSkills = getProjectAgentSkills(workspace);
5116
5285
  const globalSkills = getGlobalAgentSkills();
5117
5286
  const all = [...projectSkills, ...globalSkills];
5118
- const resolved = path24.resolve(filePath);
5287
+ const resolved = path26.resolve(filePath);
5119
5288
  const skill = all.find(
5120
- (s) => s.agent === agent2 && path24.resolve(s.filePath) === resolved
5289
+ (s) => s.agent === agent2 && path26.resolve(s.filePath) === resolved
5121
5290
  ) ?? null;
5122
5291
  return { skill };
5123
5292
  },
@@ -5150,16 +5319,16 @@ var rpcHandlers = {
5150
5319
 
5151
5320
  // src/core/daemon-service/launchd.ts
5152
5321
  import { spawnSync as spawnSync6 } from "child_process";
5153
- import fs27 from "fs";
5154
- import path26 from "path";
5322
+ import fs30 from "fs";
5323
+ import path28 from "path";
5155
5324
 
5156
5325
  // src/core/daemon-service/binary.ts
5157
- import fs26 from "fs";
5326
+ import fs29 from "fs";
5158
5327
  import { fileURLToPath } from "url";
5159
5328
  function resolveTask0Invocation() {
5160
5329
  const node = process.execPath;
5161
5330
  const argv1 = process.argv[1] ?? fileURLToPath(import.meta.url);
5162
- const main2 = fs26.realpathSync(argv1);
5331
+ const main2 = fs29.realpathSync(argv1);
5163
5332
  if (!isInstalledBuild(main2) && process.env.TASK0_ALLOW_DEV_SERVICE !== "1") {
5164
5333
  throw new Error(
5165
5334
  `Refusing to install autostart service pointing at ${main2}.
@@ -5177,15 +5346,19 @@ function isInstalledBuild(p) {
5177
5346
  // src/core/daemon-service/paths.ts
5178
5347
  init_node();
5179
5348
  import os5 from "os";
5180
- import path25 from "path";
5349
+ import path27 from "path";
5181
5350
 
5182
5351
  // src/core/daemon-service/types.ts
5352
+ init_node();
5183
5353
  import crypto2 from "crypto";
5184
5354
  var BASE_SERVICE_LABEL = "cc.cy0.task0";
5185
5355
  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);
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);
5189
5362
  return `${BASE_SERVICE_LABEL}.${hash}`;
5190
5363
  }
5191
5364
 
@@ -5194,21 +5367,21 @@ function unitPath(scope) {
5194
5367
  const platform = process.platform;
5195
5368
  const label = serviceLabel();
5196
5369
  if (platform === "darwin") {
5197
- return scope === "user" ? path25.join(os5.homedir(), "Library", "LaunchAgents", `${label}.plist`) : path25.join("/", "Library", "LaunchDaemons", `${label}.plist`);
5370
+ return scope === "user" ? path27.join(os5.homedir(), "Library", "LaunchAgents", `${label}.plist`) : path27.join("/", "Library", "LaunchDaemons", `${label}.plist`);
5198
5371
  }
5199
5372
  if (platform === "linux") {
5200
- return scope === "user" ? path25.join(os5.homedir(), ".config", "systemd", "user", `${label}.service`) : path25.join("/", "etc", "systemd", "system", `${label}.service`);
5373
+ return scope === "user" ? path27.join(os5.homedir(), ".config", "systemd", "user", `${label}.service`) : path27.join("/", "etc", "systemd", "system", `${label}.service`);
5201
5374
  }
5202
5375
  throw new Error(`Unsupported platform for service install: ${platform}`);
5203
5376
  }
5204
5377
  function logDir() {
5205
- return path25.join(task0Home(), "logs");
5378
+ return path27.join(profileDir(), "logs");
5206
5379
  }
5207
5380
  function logPaths() {
5208
5381
  const dir = logDir();
5209
5382
  return {
5210
- out: path25.join(dir, "daemon.out.log"),
5211
- err: path25.join(dir, "daemon.err.log")
5383
+ out: path27.join(dir, "daemon.out.log"),
5384
+ err: path27.join(dir, "daemon.err.log")
5212
5385
  };
5213
5386
  }
5214
5387
 
@@ -5283,8 +5456,8 @@ function createLaunchdManager(scope) {
5283
5456
  const logs = logPaths();
5284
5457
  async function install() {
5285
5458
  const inv = resolveTask0Invocation();
5286
- fs27.mkdirSync(logDir(), { recursive: true });
5287
- fs27.mkdirSync(path26.dirname(file), { recursive: true });
5459
+ fs30.mkdirSync(logDir(), { recursive: true });
5460
+ fs30.mkdirSync(path28.dirname(file), { recursive: true });
5288
5461
  const body = renderPlist({
5289
5462
  node: inv.node,
5290
5463
  main: inv.main,
@@ -5294,7 +5467,7 @@ function createLaunchdManager(scope) {
5294
5467
  err: logs.err,
5295
5468
  task0Env: collectTask0Env()
5296
5469
  });
5297
- fs27.writeFileSync(file, body, { mode: 420 });
5470
+ fs30.writeFileSync(file, body, { mode: 420 });
5298
5471
  const bootstrap = run2("launchctl", ["bootstrap", domainTarget(scope), file]);
5299
5472
  if (bootstrap.code !== 0) {
5300
5473
  const already = /already loaded|service already bootstrapped/i.test(bootstrap.stderr);
@@ -5320,9 +5493,9 @@ function createLaunchdManager(scope) {
5320
5493
  }
5321
5494
  async function uninstall() {
5322
5495
  run2("launchctl", ["bootout", serviceTarget(scope)]);
5323
- if (fs27.existsSync(file)) {
5496
+ if (fs30.existsSync(file)) {
5324
5497
  run2("launchctl", ["unload", file]);
5325
- fs27.unlinkSync(file);
5498
+ fs30.unlinkSync(file);
5326
5499
  }
5327
5500
  }
5328
5501
  async function start() {
@@ -5341,7 +5514,7 @@ function createLaunchdManager(scope) {
5341
5514
  }
5342
5515
  }
5343
5516
  async function status() {
5344
- if (!fs27.existsSync(file)) return "absent";
5517
+ if (!fs30.existsSync(file)) return "absent";
5345
5518
  const printed = run2("launchctl", ["print", serviceTarget(scope)]);
5346
5519
  if (printed.code !== 0) return "installed";
5347
5520
  const out = printed.stdout;
@@ -5364,8 +5537,8 @@ function createLaunchdManager(scope) {
5364
5537
 
5365
5538
  // src/core/daemon-service/systemd.ts
5366
5539
  import { spawnSync as spawnSync7 } from "child_process";
5367
- import fs28 from "fs";
5368
- import path27 from "path";
5540
+ import fs31 from "fs";
5541
+ import path29 from "path";
5369
5542
  function shellEscape(s) {
5370
5543
  if (!/[\s"\\$]/.test(s)) return s;
5371
5544
  return `"${s.replace(/[\\"]/g, (m) => `\\${m}`)}"`;
@@ -5415,8 +5588,8 @@ function createSystemdManager(scope) {
5415
5588
  const unitName = `${serviceLabel()}.service`;
5416
5589
  async function install() {
5417
5590
  const inv = resolveTask0Invocation();
5418
- fs28.mkdirSync(logDir(), { recursive: true });
5419
- fs28.mkdirSync(path27.dirname(file), { recursive: true });
5591
+ fs31.mkdirSync(logDir(), { recursive: true });
5592
+ fs31.mkdirSync(path29.dirname(file), { recursive: true });
5420
5593
  const body = renderUnit({
5421
5594
  node: inv.node,
5422
5595
  main: inv.main,
@@ -5426,7 +5599,7 @@ function createSystemdManager(scope) {
5426
5599
  scope,
5427
5600
  task0Env: collectTask0Env2()
5428
5601
  });
5429
- fs28.writeFileSync(file, body, { mode: 420 });
5602
+ fs31.writeFileSync(file, body, { mode: 420 });
5430
5603
  const reload = run3("systemctl", [...scopeFlag(scope), "daemon-reload"]);
5431
5604
  if (reload.code !== 0) {
5432
5605
  throw new Error(`systemctl daemon-reload failed: ${reload.stderr}`);
@@ -5435,8 +5608,8 @@ function createSystemdManager(scope) {
5435
5608
  }
5436
5609
  async function uninstall() {
5437
5610
  run3("systemctl", [...scopeFlag(scope), "disable", "--now", unitName]);
5438
- if (fs28.existsSync(file)) {
5439
- fs28.unlinkSync(file);
5611
+ if (fs31.existsSync(file)) {
5612
+ fs31.unlinkSync(file);
5440
5613
  }
5441
5614
  run3("systemctl", [...scopeFlag(scope), "daemon-reload"]);
5442
5615
  }
@@ -5453,7 +5626,7 @@ function createSystemdManager(scope) {
5453
5626
  }
5454
5627
  }
5455
5628
  async function status() {
5456
- if (!fs28.existsSync(file)) return "absent";
5629
+ if (!fs31.existsSync(file)) return "absent";
5457
5630
  const res = run3("systemctl", [...scopeFlag(scope), "is-active", unitName]);
5458
5631
  const out = res.stdout.trim();
5459
5632
  if (out === "active") return "running";
@@ -5489,14 +5662,14 @@ function getServiceManager(scope) {
5489
5662
  // src/core/cli-version.ts
5490
5663
  import { readFileSync } from "fs";
5491
5664
  import { fileURLToPath as fileURLToPath2 } from "url";
5492
- import path28 from "path";
5665
+ import path30 from "path";
5493
5666
  var cached2 = null;
5494
5667
  function readCliVersion() {
5495
5668
  if (cached2 !== null) return cached2;
5496
- const here = path28.dirname(fileURLToPath2(import.meta.url));
5669
+ const here = path30.dirname(fileURLToPath2(import.meta.url));
5497
5670
  const candidates = [
5498
- path28.resolve(here, "..", "package.json"),
5499
- path28.resolve(here, "..", "..", "package.json")
5671
+ path30.resolve(here, "..", "package.json"),
5672
+ path30.resolve(here, "..", "..", "package.json")
5500
5673
  ];
5501
5674
  for (const candidate of candidates) {
5502
5675
  try {
@@ -5512,73 +5685,6 @@ function readCliVersion() {
5512
5685
  return cached2;
5513
5686
  }
5514
5687
 
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
-
5582
5688
  // src/commands/daemon.ts
5583
5689
  async function dispatchRpc(ws, id, method, params) {
5584
5690
  const handler = rpcHandlers[method];
@@ -5836,12 +5942,6 @@ daemonCmd.command("stop").description("Stop the autostart service via launchctl
5836
5942
  });
5837
5943
  daemonCmd.command("run").description("Run the daemon WebSocket loop in foreground (invoked by the service unit; useful for debugging)").action(async () => {
5838
5944
  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
- }
5845
5945
  const wsUrl = identity.server_url.replace(/^http/, "ws").replace(/\/$/, "") + "/ws/daemon";
5846
5946
  console.log(chalk19.green(`Starting daemon ${identity.daemon_id} \u2192 ${wsUrl}`));
5847
5947
  let reconnectDelay = 1e3;
@@ -6055,8 +6155,8 @@ function fail7(message, code = 1) {
6055
6155
  console.error(chalk20.red(message));
6056
6156
  process.exit(code);
6057
6157
  }
6058
- async function callServer(path31, init = {}) {
6059
- const url = `${serverBase2()}${path31}`;
6158
+ async function callServer(path32, init = {}) {
6159
+ const url = `${serverBase2()}${path32}`;
6060
6160
  let auth;
6061
6161
  try {
6062
6162
  auth = adminAuthHeader();
@@ -6560,158 +6660,184 @@ automation.command("runs <id>").description("List recent runs for an automation"
6560
6660
  });
6561
6661
 
6562
6662
  // src/commands/profile.ts
6563
- import fs30 from "fs";
6564
- import os7 from "os";
6565
- import path30 from "path";
6663
+ init_node();
6664
+ import fs32 from "fs";
6665
+ import path31 from "path";
6566
6666
  import { Command as Command23 } from "commander";
6567
6667
  import chalk23 from "chalk";
6568
- var VALID_NAME = /^[a-zA-Z0-9_-]+$/;
6668
+ import yaml10 from "js-yaml";
6569
6669
  function fail9(msg) {
6570
6670
  console.error(chalk23.red(msg));
6571
6671
  process.exit(1);
6572
6672
  }
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;
6673
+ function readDaemonAt(dir) {
6674
+ const file = path31.join(dir, "daemon.json");
6675
+ if (!fs32.existsSync(file)) return null;
6579
6676
  try {
6580
- return JSON.parse(fs30.readFileSync(file, "utf-8"));
6677
+ return JSON.parse(fs32.readFileSync(file, "utf-8"));
6581
6678
  } catch {
6582
6679
  return null;
6583
6680
  }
6584
6681
  }
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);
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();
6590
6696
  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()})`));
6697
+ console.log("No profiles configured.");
6593
6698
  return;
6594
6699
  }
6595
6700
  for (const name of names) {
6596
- const entry = profiles[name];
6597
6701
  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."));
6702
+ const apiUrl = readProfileApiUrl(name);
6703
+ const url = apiUrl ? chalk23.dim(` \u2192 ${apiUrl}`) : "";
6704
+ console.log(`${marker}${name}${url}`);
6603
6705
  }
6604
6706
  });
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}.`);
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_-]+$/.`);
6608
6710
  }
6609
- const existing = getProfile(name);
6610
- if (existing) {
6611
- fail9(`Profile "${name}" already exists.`);
6711
+ if (name === DEFAULT_PROFILE_NAME) {
6712
+ fail9(`"${DEFAULT_PROFILE_NAME}" is reserved; it always exists.`);
6612
6713
  }
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}`);
6714
+ const dir = profileDir(name);
6715
+ if (fs32.existsSync(dir)) {
6716
+ fail9(`Profile "${name}" already exists at ${dir}.`);
6623
6717
  }
6718
+ fs32.mkdirSync(dir, { recursive: true });
6719
+ const prev = process.env.TASK0_PROFILE;
6720
+ process.env.TASK0_PROFILE = name;
6624
6721
  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.`));
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;
6650
6728
  }
6651
6729
  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}`);
6730
+ console.log(` dir: ${dir}`);
6731
+ if (opts.apiUrl) console.log(` api_url: ${opts.apiUrl}`);
6654
6732
  console.log(chalk23.dim(`Use \`task0 profile use ${name}\` to activate.`));
6655
6733
  });
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) {
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)) {
6659
6743
  fail9(`Profile "${name}" not found.`);
6660
6744
  }
6661
- const current = getCurrentProfileName();
6745
+ const current = currentProfileName();
6662
6746
  if (current === name && !opts.force) {
6663
- fail9(`Profile "${name}" is current. Re-run with --force to remove it (this clears current_profile).`);
6747
+ fail9(`Profile "${name}" is current. Re-run with --force to remove it.`);
6664
6748
  }
6665
- removeProfile(name);
6666
- console.log(chalk23.green(`Removed profile "${name}".`));
6749
+ fs32.rmSync(dir, { recursive: true, force: true });
6667
6750
  if (current === name) {
6668
- console.log(chalk23.dim("current_profile cleared. Use `task0 profile use <name>` to pick another."));
6751
+ writeCurrentProfile(null);
6752
+ console.log(chalk23.dim('Current profile reset to "default".'));
6669
6753
  }
6670
- console.log(chalk23.dim(`Note: ${entry.task0_home} was not deleted.`));
6754
+ console.log(chalk23.green(`Removed profile "${name}".`));
6671
6755
  });
6672
6756
  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)"}.`);
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(", ")}.`);
6677
6763
  }
6678
- setCurrentProfile(name);
6764
+ writeCurrentProfile(name);
6679
6765
  console.log(chalk23.green(`Now using profile "${name}".`));
6680
6766
  });
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);
6767
+ profile2.command("current").description('Print the current profile name (always succeeds; defaults to "default")').action(() => {
6768
+ console.log(currentProfileName());
6688
6769
  });
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.");
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}".`);
6693
6774
  }
6694
- const entry = getProfile(target);
6695
- if (!entry) {
6775
+ const dir = profileDir(target);
6776
+ if (!fs32.existsSync(dir)) {
6696
6777
  fail9(`Profile "${target}" not found.`);
6697
6778
  }
6698
- const current = getCurrentProfileName();
6779
+ const current = currentProfileName();
6780
+ const apiUrl = readProfileApiUrl(target);
6781
+ const identity = readDaemonAt(dir);
6699
6782
  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);
6783
+ console.log(` dir: ${dir}`);
6784
+ console.log(` config: ${path31.join(dir, "config.yml")}`);
6785
+ console.log(` api_url: ${apiUrl ?? chalk23.dim("(unset)")}`);
6704
6786
  if (!identity) {
6705
6787
  console.log(chalk23.dim(" daemon.json: (not registered yet)"));
6706
6788
  return;
6707
6789
  }
6708
6790
  console.log(` daemon_id: ${identity.daemon_id ?? chalk23.dim("(missing)")}`);
6709
6791
  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) {
6792
+ if (apiUrl && identity.server_url && apiUrl !== identity.server_url) {
6711
6793
  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}`));
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)}`);
6713
6823
  }
6714
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
+ }
6715
6841
 
6716
6842
  // src/core/error-capture.ts
6717
6843
  init_node();
@@ -6811,7 +6937,7 @@ function captureTopLevel(err, options) {
6811
6937
 
6812
6938
  // src/main.ts
6813
6939
  var TASK0_VERSION = readCliVersion();
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)");
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)");
6815
6941
  program.addCommand(source);
6816
6942
  program.addCommand(project);
6817
6943
  program.addCommand(task);
@@ -6830,12 +6956,23 @@ program.addCommand(userCmd);
6830
6956
  program.addCommand(error);
6831
6957
  program.addCommand(automation);
6832
6958
  program.addCommand(profile2);
6959
+ program.addCommand(migrateLayout);
6833
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
+ }
6834
6971
  try {
6835
6972
  activateProfile(process.argv);
6836
6973
  } catch (err) {
6837
6974
  if (err instanceof ProfileNotFoundError) {
6838
- console.error(chalk24.red(err.message));
6975
+ console.error(chalk25.red(err.message));
6839
6976
  process.exit(1);
6840
6977
  }
6841
6978
  throw err;