@task0/cli 0.7.0 → 0.9.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 (3) hide show
  1. package/README.md +14 -6
  2. package/dist/main.js +808 -619
  3. 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,238 @@ 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;
1309
+ function getActiveProfile() {
1310
+ return activeProfileCache;
1311
+ }
1297
1312
  function activateProfile(argv) {
1313
+ ensureDefaultProfile();
1298
1314
  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;
1315
+ let name;
1316
+ if (flagValue !== null) {
1317
+ if (!isValidProfileName(flagValue) || !profileExists(flagValue)) {
1318
+ if (!isProfileSubcommandInvocation(argv)) {
1319
+ throw new ProfileNotFoundError(flagValue, listProfileNames());
1320
+ }
1321
+ name = DEFAULT_PROFILE_NAME;
1305
1322
  } else {
1306
- throw error2;
1323
+ name = flagValue;
1307
1324
  }
1325
+ } else {
1326
+ name = currentProfileNameFromEnvOrFile();
1327
+ if (!profileExists(name)) {
1328
+ if (!isProfileSubcommandInvocation(argv)) {
1329
+ throw new ProfileNotFoundError(name, listProfileNames());
1330
+ }
1331
+ name = DEFAULT_PROFILE_NAME;
1332
+ }
1333
+ }
1334
+ process.env.TASK0_PROFILE = name;
1335
+ const envApiUrlOverride = process.env.TASK0_API_URL?.trim() || void 0;
1336
+ const apiUrl = getApiUrl();
1337
+ activeProfileCache = {
1338
+ name,
1339
+ ...apiUrl ? { apiUrl } : {},
1340
+ ...envApiUrlOverride ? { envApiUrlOverride } : {}
1341
+ };
1342
+ if (apiUrl && !envApiUrlOverride) {
1343
+ process.env.TASK0_API_URL = apiUrl;
1308
1344
  }
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;
1345
+ }
1346
+ function currentProfileNameFromEnvOrFile() {
1347
+ const fromEnv = process.env.TASK0_PROFILE?.trim();
1348
+ if (fromEnv && isValidProfileName(fromEnv)) return fromEnv;
1349
+ try {
1350
+ const file = `${profilesRoot()}/.current`;
1351
+ if (fs10.existsSync(file)) {
1352
+ const v = fs10.readFileSync(file, "utf-8").trim();
1353
+ if (v && isValidProfileName(v)) return v;
1313
1354
  }
1314
- if (active2.entry.api_url && !process.env.TASK0_API_URL) {
1315
- process.env.TASK0_API_URL = active2.entry.api_url;
1355
+ } catch {
1356
+ }
1357
+ return DEFAULT_PROFILE_NAME;
1358
+ }
1359
+
1360
+ // src/core/migrate-layout.ts
1361
+ init_node();
1362
+ import fs11 from "fs";
1363
+ import os4 from "os";
1364
+ import path10 from "path";
1365
+ import yaml5 from "js-yaml";
1366
+ var MIGRATION_MARKER = ".migrated-v1";
1367
+ function legacyDaemonJson() {
1368
+ return path10.join(task0Home(), "daemon.json");
1369
+ }
1370
+ function legacyErrorsDir() {
1371
+ return path10.join(task0Home(), "errors");
1372
+ }
1373
+ function legacyAgentRunDir() {
1374
+ return path10.join(task0Home(), "agent-run");
1375
+ }
1376
+ function legacyLogsDir() {
1377
+ return path10.join(task0Home(), "logs");
1378
+ }
1379
+ function legacyServerConfigYml() {
1380
+ return path10.join(task0Home(), "config.yml");
1381
+ }
1382
+ function legacyRegistryDir() {
1383
+ return path10.join(process.env.HOME || process.env.USERPROFILE || os4.homedir(), ".config", "task0");
1384
+ }
1385
+ function legacyRegistryFile() {
1386
+ return path10.join(legacyRegistryDir(), "config.yml");
1387
+ }
1388
+ function migrationMarkerPath() {
1389
+ return path10.join(profilesRoot(), MIGRATION_MARKER);
1390
+ }
1391
+ function safeMv(from, to, steps, opts = {}) {
1392
+ if (!fs11.existsSync(from)) return false;
1393
+ if (fs11.existsSync(to)) {
1394
+ steps.push({ kind: "skip", from, to, detail: "destination already exists" });
1395
+ return false;
1396
+ }
1397
+ if (!opts.dryRun) {
1398
+ fs11.mkdirSync(path10.dirname(to), { recursive: true });
1399
+ fs11.renameSync(from, to);
1400
+ }
1401
+ steps.push({ kind: "mv", from, to });
1402
+ return true;
1403
+ }
1404
+ function readYamlSafe(file) {
1405
+ if (!fs11.existsSync(file)) return null;
1406
+ try {
1407
+ const raw = fs11.readFileSync(file, "utf-8");
1408
+ return yaml5.load(raw) ?? null;
1409
+ } catch {
1410
+ return null;
1411
+ }
1412
+ }
1413
+ function extractCliKeysFromLegacyConfig() {
1414
+ const data = readYamlSafe(legacyServerConfigYml());
1415
+ if (!data || typeof data !== "object") return null;
1416
+ const out = {};
1417
+ if (Array.isArray(data.sources)) {
1418
+ const projects = data.sources.filter((s) => s?.type === "project");
1419
+ if (projects.length > 0) out.sources = projects;
1420
+ }
1421
+ if (data.agentModels && typeof data.agentModels === "object") {
1422
+ out.agentModels = data.agentModels;
1423
+ }
1424
+ return Object.keys(out).length > 0 ? out : null;
1425
+ }
1426
+ function reconstructProfilesFromRegistry(steps, opts = {}) {
1427
+ const file = legacyRegistryFile();
1428
+ const data = readYamlSafe(file);
1429
+ if (!data?.profiles || typeof data.profiles !== "object") return;
1430
+ for (const [name, entry] of Object.entries(data.profiles)) {
1431
+ if (!isValidProfileName(name)) continue;
1432
+ const dir = profileDir(name);
1433
+ if (!opts.dryRun) fs11.mkdirSync(dir, { recursive: true });
1434
+ steps.push({ kind: "mkdir", to: dir });
1435
+ const profileConfig = {};
1436
+ if (entry.api_url) profileConfig.api_url = entry.api_url;
1437
+ const profileConfigFile = path10.join(dir, "config.yml");
1438
+ if (!fs11.existsSync(profileConfigFile) && Object.keys(profileConfig).length > 0) {
1439
+ if (!opts.dryRun) {
1440
+ fs11.writeFileSync(profileConfigFile, yaml5.dump(profileConfig, { lineWidth: 120 }), "utf-8");
1441
+ }
1442
+ steps.push({ kind: "write", to: profileConfigFile, detail: `api_url=${entry.api_url}` });
1443
+ }
1444
+ if (entry.task0_home) {
1445
+ const oldDaemonJson = path10.join(entry.task0_home, "daemon.json");
1446
+ const newDaemonJson = path10.join(dir, "daemon.json");
1447
+ if (fs11.existsSync(oldDaemonJson) && !fs11.existsSync(newDaemonJson)) {
1448
+ safeMv(oldDaemonJson, newDaemonJson, steps, opts);
1449
+ }
1316
1450
  }
1317
1451
  }
1452
+ if (data.current_profile && isValidProfileName(data.current_profile)) {
1453
+ if (!opts.dryRun) writeCurrentProfile(data.current_profile);
1454
+ steps.push({ kind: "write", to: path10.join(profilesRoot(), ".current"), detail: data.current_profile });
1455
+ }
1456
+ const backup = file + ".migrated-bak";
1457
+ if (fs11.existsSync(file) && !fs11.existsSync(backup)) {
1458
+ if (!opts.dryRun) fs11.renameSync(file, backup);
1459
+ steps.push({ kind: "mv", from: file, to: backup, detail: "legacy v0.7.0 registry archived" });
1460
+ }
1461
+ }
1462
+ function migrateCliLayout(opts = {}) {
1463
+ const result = { ran: false, steps: [], alreadyMigrated: false };
1464
+ if (fs11.existsSync(migrationMarkerPath())) {
1465
+ result.alreadyMigrated = true;
1466
+ return result;
1467
+ }
1468
+ if (!opts.dryRun) fs11.mkdirSync(profilesRoot(), { recursive: true });
1469
+ const defaultDir = profileDir(DEFAULT_PROFILE_NAME);
1470
+ if (!fs11.existsSync(defaultDir)) {
1471
+ if (!opts.dryRun) fs11.mkdirSync(defaultDir, { recursive: true });
1472
+ result.steps.push({ kind: "mkdir", to: defaultDir });
1473
+ }
1474
+ safeMv(legacyDaemonJson(), path10.join(defaultDir, "daemon.json"), result.steps, opts);
1475
+ safeMv(legacyErrorsDir(), path10.join(defaultDir, "errors"), result.steps, opts);
1476
+ safeMv(legacyAgentRunDir(), path10.join(defaultDir, "agent-run"), result.steps, opts);
1477
+ safeMv(legacyLogsDir(), path10.join(defaultDir, "logs"), result.steps, opts);
1478
+ const cliKeys = extractCliKeysFromLegacyConfig();
1479
+ if (cliKeys) {
1480
+ const target = path10.join(defaultDir, "config.yml");
1481
+ if (!fs11.existsSync(target)) {
1482
+ if (!opts.dryRun) {
1483
+ fs11.writeFileSync(target, yaml5.dump(cliKeys, { lineWidth: 120 }), "utf-8");
1484
+ }
1485
+ result.steps.push({ kind: "write", to: target, detail: "extracted CLI keys from legacy config.yml" });
1486
+ }
1487
+ const data = readYamlSafe(legacyServerConfigYml());
1488
+ if (data) {
1489
+ let dirty = false;
1490
+ if (Array.isArray(data.sources)) {
1491
+ const nonProjectOnly = data.sources.filter((s) => s?.type !== "project");
1492
+ if (nonProjectOnly.length !== data.sources.length) {
1493
+ data.sources = nonProjectOnly;
1494
+ dirty = true;
1495
+ }
1496
+ }
1497
+ if (data.agentModels) {
1498
+ delete data.agentModels;
1499
+ dirty = true;
1500
+ }
1501
+ if (dirty) {
1502
+ if (!opts.dryRun) {
1503
+ fs11.writeFileSync(legacyServerConfigYml(), yaml5.dump(data, { lineWidth: 120 }), "utf-8");
1504
+ }
1505
+ result.steps.push({ kind: "extract", from: legacyServerConfigYml(), detail: "stripped CLI-owned keys" });
1506
+ }
1507
+ }
1508
+ }
1509
+ if (fs11.existsSync(legacyRegistryFile())) {
1510
+ reconstructProfilesFromRegistry(result.steps, opts);
1511
+ }
1512
+ if (!opts.dryRun) {
1513
+ fs11.writeFileSync(migrationMarkerPath(), (/* @__PURE__ */ new Date()).toISOString() + "\n", "utf-8");
1514
+ }
1515
+ result.ran = result.steps.length > 0;
1516
+ return result;
1517
+ }
1518
+ function resetMigrationMarker() {
1519
+ const marker = migrationMarkerPath();
1520
+ if (!fs11.existsSync(marker)) return false;
1521
+ fs11.unlinkSync(marker);
1522
+ return true;
1318
1523
  }
1319
1524
 
1320
1525
  // src/commands/source.ts
1321
1526
  import { Command } from "commander";
1322
- import path11 from "path";
1527
+ import path13 from "path";
1323
1528
  import chalk from "chalk";
1324
1529
 
1325
1530
  // src/types.ts
@@ -1327,10 +1532,10 @@ init_task();
1327
1532
 
1328
1533
  // src/core/admin-token.ts
1329
1534
  init_node();
1330
- import fs9 from "fs";
1331
- import path9 from "path";
1535
+ import fs12 from "fs";
1536
+ import path11 from "path";
1332
1537
  function tokenFile() {
1333
- return path9.join(task0Home(), "admin.token");
1538
+ return path11.join(serverWorkingDir(), "admin.token");
1334
1539
  }
1335
1540
  var cached = null;
1336
1541
  var AdminTokenUnavailableError = class extends Error {
@@ -1351,8 +1556,8 @@ function readAdminToken() {
1351
1556
  return cached;
1352
1557
  }
1353
1558
  const file = tokenFile();
1354
- if (fs9.existsSync(file)) {
1355
- const v = fs9.readFileSync(file, "utf-8").trim();
1559
+ if (fs12.existsSync(file)) {
1560
+ const v = fs12.readFileSync(file, "utf-8").trim();
1356
1561
  if (v) {
1357
1562
  cached = v;
1358
1563
  return cached;
@@ -1366,40 +1571,37 @@ function adminAuthHeader() {
1366
1571
 
1367
1572
  // src/core/daemon-config.ts
1368
1573
  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");
1574
+ import fs13 from "fs";
1575
+ import path12 from "path";
1576
+ function configFile2() {
1577
+ return path12.join(profileDir(), "daemon.json");
1376
1578
  }
1377
1579
  function daemonConfigPath() {
1378
- return configFile();
1580
+ return configFile2();
1379
1581
  }
1380
1582
  function readDaemonIdentity() {
1381
- const file = configFile();
1382
- if (!fs10.existsSync(file)) return null;
1583
+ const file = configFile2();
1584
+ if (!fs13.existsSync(file)) return null;
1383
1585
  try {
1384
- const raw = fs10.readFileSync(file, "utf-8");
1586
+ const raw = fs13.readFileSync(file, "utf-8");
1385
1587
  return JSON.parse(raw);
1386
1588
  } catch {
1387
1589
  return null;
1388
1590
  }
1389
1591
  }
1390
1592
  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 });
1593
+ const file = configFile2();
1594
+ fs13.mkdirSync(path12.dirname(file), { recursive: true });
1595
+ fs13.writeFileSync(file, JSON.stringify(identity, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
1394
1596
  try {
1395
- fs10.chmodSync(file, 384);
1597
+ fs13.chmodSync(file, 384);
1396
1598
  } catch {
1397
1599
  }
1398
1600
  }
1399
1601
  function clearDaemonIdentity() {
1400
- const file = configFile();
1401
- if (!fs10.existsSync(file)) return false;
1402
- fs10.unlinkSync(file);
1602
+ const file = configFile2();
1603
+ if (!fs13.existsSync(file)) return false;
1604
+ fs13.unlinkSync(file);
1403
1605
  return true;
1404
1606
  }
1405
1607
 
@@ -1501,15 +1703,15 @@ function requireLocalDaemon() {
1501
1703
  const id = localDaemonId();
1502
1704
  if (!id) {
1503
1705
  console.error(chalk.red("This host is not registered as a daemon."));
1504
- console.error(chalk.dim("Run `task0 daemon register --server <url>` first."));
1706
+ console.error(chalk.dim("Run `task0 daemon register` first (set api_url with `task0 profile set api_url <url>` if needed)."));
1505
1707
  process.exit(1);
1506
1708
  }
1507
1709
  return id;
1508
1710
  }
1509
1711
  var source = new Command("source").description("Manage task sources");
1510
1712
  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);
1713
+ const absPath = path13.resolve(inputPath);
1714
+ const name = opts.name || path13.basename(absPath);
1513
1715
  const daemonId = requireLocalDaemon();
1514
1716
  let resp;
1515
1717
  try {
@@ -1607,9 +1809,9 @@ ${project2}`));
1607
1809
 
1608
1810
  // src/commands/project.ts
1609
1811
  import { Command as Command2 } from "commander";
1610
- import fs11 from "fs";
1611
- import path12 from "path";
1612
- import yaml5 from "js-yaml";
1812
+ import fs14 from "fs";
1813
+ import path14 from "path";
1814
+ import yaml6 from "js-yaml";
1613
1815
  import chalk2 from "chalk";
1614
1816
 
1615
1817
  // ../../packages/shared/dist/index.js
@@ -1647,15 +1849,15 @@ project.command("list").description("List registered projects (queries the hub)"
1647
1849
  });
1648
1850
  project.command("init").description("Initialize task0.yml in the current directory").option("-d, --tasks-dir <dir>", "Tasks directory", ".task0/tasks").action((opts) => {
1649
1851
  const cwd = process.cwd();
1650
- const ymlPath = path12.join(cwd, "task0.yml");
1651
- if (fs11.existsSync(ymlPath)) {
1852
+ const ymlPath = path14.join(cwd, "task0.yml");
1853
+ if (fs14.existsSync(ymlPath)) {
1652
1854
  console.error(chalk2.yellow("task0.yml already exists"));
1653
1855
  process.exit(1);
1654
1856
  }
1655
1857
  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 });
1858
+ fs14.writeFileSync(ymlPath, yaml6.dump(config), "utf-8");
1859
+ const tasksDir = path14.join(cwd, opts.tasksDir);
1860
+ fs14.mkdirSync(tasksDir, { recursive: true });
1659
1861
  console.log(chalk2.green("Initialized task0 project"));
1660
1862
  console.log(` ${ymlPath}`);
1661
1863
  console.log(` ${tasksDir}/`);
@@ -1664,9 +1866,9 @@ project.command("init").description("Initialize task0.yml in the current directo
1664
1866
  // src/commands/task.ts
1665
1867
  import { Command as Command8 } from "commander";
1666
1868
  import { execSync } from "child_process";
1667
- import fs17 from "fs";
1668
- import path15 from "path";
1669
- import yaml6 from "js-yaml";
1869
+ import fs20 from "fs";
1870
+ import path17 from "path";
1871
+ import yaml7 from "js-yaml";
1670
1872
  import chalk8 from "chalk";
1671
1873
 
1672
1874
  // src/lib/api.ts
@@ -1703,18 +1905,18 @@ async function request(method, pathname, body) {
1703
1905
  }
1704
1906
  }
1705
1907
  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)
1908
+ get: (path32) => request("GET", path32),
1909
+ post: (path32, body) => request("POST", path32, body ?? {}),
1910
+ put: (path32, body) => request("PUT", path32, body ?? {}),
1911
+ patch: (path32, body) => request("PATCH", path32, body ?? {}),
1912
+ del: (path32) => request("DELETE", path32)
1711
1913
  };
1712
1914
 
1713
1915
  // src/commands/task/triage.ts
1714
1916
  import { Command as Command3 } from "commander";
1715
1917
  import chalk3 from "chalk";
1716
- import fs13 from "fs";
1717
- import path13 from "path";
1918
+ import fs16 from "fs";
1919
+ import path15 from "path";
1718
1920
 
1719
1921
  // src/core/agent-run-wait.ts
1720
1922
  async function getAgentRun(id) {
@@ -1765,8 +1967,8 @@ init_task_state2();
1765
1967
  var ISSUE_DETAIL_RE = /^ISSUE-\d+\.md$/;
1766
1968
  var TRIAGE_SKILL_NAME = "triage";
1767
1969
  function resolveSkillFilePath(projectRoot, skillName) {
1768
- const p = path13.join(projectRoot, ".claude", "skills", skillName, "SKILL.md");
1769
- return fs13.existsSync(p) ? p : null;
1970
+ const p = path15.join(projectRoot, ".claude", "skills", skillName, "SKILL.md");
1971
+ return fs16.existsSync(p) ? p : null;
1770
1972
  }
1771
1973
  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
1974
  try {
@@ -1785,7 +1987,7 @@ var triage = new Command3("triage").description("Decompose IDEA into ISSUE.md +
1785
1987
  process.exit(1);
1786
1988
  }
1787
1989
  for (const name of existingIssues) {
1788
- fs13.rmSync(path13.join(loc.taskDir, name), { force: true });
1990
+ fs16.rmSync(path15.join(loc.taskDir, name), { force: true });
1789
1991
  }
1790
1992
  }
1791
1993
  if (opts.model || opts.effort) {
@@ -1823,8 +2025,8 @@ var triage = new Command3("triage").description("Decompose IDEA into ISSUE.md +
1823
2025
  console.error(chalk3.red(`triage failed: ${final.error || "unknown"}`));
1824
2026
  process.exit(1);
1825
2027
  }
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();
2028
+ const hasOverview = fs16.existsSync(path15.join(loc.taskDir, "ISSUE.md"));
2029
+ const issueFiles = fs16.readdirSync(loc.taskDir).filter((name) => ISSUE_DETAIL_RE.test(name)).sort();
1828
2030
  if (!hasOverview || issueFiles.length === 0) {
1829
2031
  const missing = [];
1830
2032
  if (!hasOverview) missing.push("ISSUE.md");
@@ -1834,7 +2036,7 @@ var triage = new Command3("triage").description("Decompose IDEA into ISSUE.md +
1834
2036
  }
1835
2037
  let blockingQuestionCount = 0;
1836
2038
  for (const name of issueFiles) {
1837
- const content = fs13.readFileSync(path13.join(loc.taskDir, name), "utf-8");
2039
+ const content = fs16.readFileSync(path15.join(loc.taskDir, name), "utf-8");
1838
2040
  blockingQuestionCount += countBlockingQuestions(content);
1839
2041
  }
1840
2042
  await updateWorkflow(loc.taskYml, {
@@ -1862,8 +2064,8 @@ var triage = new Command3("triage").description("Decompose IDEA into ISSUE.md +
1862
2064
  }
1863
2065
  });
1864
2066
  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();
2067
+ if (!fs16.existsSync(taskDir)) return [];
2068
+ return fs16.readdirSync(taskDir).filter((name) => name === "ISSUE.md" || ISSUE_DETAIL_RE.test(name)).sort();
1867
2069
  }
1868
2070
  function countBlockingQuestions(md) {
1869
2071
  const match = md.match(/## Open Questions\s*\n([\s\S]*?)(\n## |\n*$)/i);
@@ -1877,13 +2079,13 @@ function countBlockingQuestions(md) {
1877
2079
  // src/commands/task/exec.ts
1878
2080
  import { Command as Command4 } from "commander";
1879
2081
  import chalk4 from "chalk";
1880
- import fs14 from "fs";
1881
- import path14 from "path";
2082
+ import fs17 from "fs";
2083
+ import path16 from "path";
1882
2084
  init_task_state2();
1883
2085
  var PLAN_EXECUTE_SKILL_NAME = "plan-execute";
1884
2086
  function resolveSkillFilePath2(projectRoot, skillName) {
1885
- const p = path14.join(projectRoot, ".claude", "skills", skillName, "SKILL.md");
1886
- return fs14.existsSync(p) ? p : null;
2087
+ const p = path16.join(projectRoot, ".claude", "skills", skillName, "SKILL.md");
2088
+ return fs17.existsSync(p) ? p : null;
1887
2089
  }
1888
2090
  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
2091
  try {
@@ -1976,7 +2178,7 @@ var summarize = new Command5("summarize").description("Generate a concise title
1976
2178
  });
1977
2179
 
1978
2180
  // src/commands/task/comment.ts
1979
- import fs15 from "fs";
2181
+ import fs18 from "fs";
1980
2182
  import { Command as Command6 } from "commander";
1981
2183
  import chalk6 from "chalk";
1982
2184
  var comment = new Command6("comment").description("Manage comments on a task");
@@ -1986,8 +2188,8 @@ function fail(err) {
1986
2188
  }
1987
2189
  function readBodyFromOpts(opts) {
1988
2190
  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");
2191
+ if (opts.file === "-") return fs18.readFileSync(0, "utf-8");
2192
+ if (opts.file) return fs18.readFileSync(opts.file, "utf-8");
1991
2193
  throw new Error("Provide --body or --file");
1992
2194
  }
1993
2195
  function preview(body, width = 60) {
@@ -2086,7 +2288,7 @@ comment.command("delete <cmtId>").description("Delete a comment by its cmt_ id")
2086
2288
  });
2087
2289
 
2088
2290
  // src/commands/task/description.ts
2089
- import fs16 from "fs";
2291
+ import fs19 from "fs";
2090
2292
  import { Command as Command7 } from "commander";
2091
2293
  import chalk7 from "chalk";
2092
2294
  var description = new Command7("description").description("Show or update the task description");
@@ -2096,8 +2298,8 @@ function fail2(err) {
2096
2298
  }
2097
2299
  function readBodyFromOpts2(opts) {
2098
2300
  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");
2301
+ if (opts.file === "-") return fs19.readFileSync(0, "utf-8");
2302
+ if (opts.file) return fs19.readFileSync(opts.file, "utf-8");
2101
2303
  throw new Error("Provide --body or --file (use --file - to read stdin)");
2102
2304
  }
2103
2305
  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 +2348,12 @@ task.addCommand(comment);
2146
2348
  task.addCommand(description);
2147
2349
  task.command("init <input>").description("Create a task from a description or Linear/GitHub issue URL").action(async (input) => {
2148
2350
  const cwd = process.cwd();
2149
- const projectYml = path15.join(cwd, "task0.yml");
2150
- if (!fs17.existsSync(projectYml)) {
2351
+ const projectYml = path17.join(cwd, "task0.yml");
2352
+ if (!fs20.existsSync(projectYml)) {
2151
2353
  console.error(chalk8.red("Not a task0 project (task0.yml not found). Run `task0 project init` first."));
2152
2354
  process.exit(1);
2153
2355
  }
2154
- const projectConfig = yaml6.load(fs17.readFileSync(projectYml, "utf-8"));
2356
+ const projectConfig = yaml7.load(fs20.readFileSync(projectYml, "utf-8"));
2155
2357
  if (projectConfig.kind !== "project") {
2156
2358
  console.error(chalk8.red('Invalid task0.yml: kind is not "project"'));
2157
2359
  process.exit(1);
@@ -2167,38 +2369,38 @@ task.command("init <input>").description("Create a task from a description or Li
2167
2369
  });
2168
2370
  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
2371
  const cwd = process.cwd();
2170
- const projectYml = path15.join(cwd, "task0.yml");
2171
- if (!fs17.existsSync(projectYml)) {
2372
+ const projectYml = path17.join(cwd, "task0.yml");
2373
+ if (!fs20.existsSync(projectYml)) {
2172
2374
  console.error(chalk8.red("Not a task0 project (task0.yml not found). Run `task0 project init` first."));
2173
2375
  process.exit(1);
2174
2376
  }
2175
- const projectConfig = yaml6.load(fs17.readFileSync(projectYml, "utf-8"));
2377
+ const projectConfig = yaml7.load(fs20.readFileSync(projectYml, "utf-8"));
2176
2378
  if (projectConfig.kind !== "project") {
2177
2379
  console.error(chalk8.red('Invalid task0.yml: kind is not "project"'));
2178
2380
  process.exit(1);
2179
2381
  }
2180
- const tasksDir = path15.join(cwd, projectConfig.tasks_dir);
2181
- if (!fs17.existsSync(tasksDir)) {
2382
+ const tasksDir = path17.join(cwd, projectConfig.tasks_dir);
2383
+ if (!fs20.existsSync(tasksDir)) {
2182
2384
  console.error(chalk8.red(`Tasks directory not found: ${tasksDir}`));
2183
2385
  process.exit(1);
2184
2386
  }
2185
- const entries = fs17.readdirSync(tasksDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
2387
+ const entries = fs20.readdirSync(tasksDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
2186
2388
  let migrated = 0;
2187
2389
  let skipped = 0;
2188
2390
  let seededObjectIds = 0;
2189
2391
  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)) {
2392
+ const taskDir = path17.join(tasksDir, name);
2393
+ const taskYml = path17.join(taskDir, "task0.yml");
2394
+ if (fs20.existsSync(taskYml)) {
2193
2395
  skipped++;
2194
2396
  try {
2195
- const raw = yaml6.load(fs17.readFileSync(taskYml, "utf-8"));
2397
+ const raw = yaml7.load(fs20.readFileSync(taskYml, "utf-8"));
2196
2398
  if (raw && raw.kind === "task" && !raw.object_id) {
2197
2399
  raw.object_id = generateObjectId("task");
2198
2400
  if (opts.dryRun) {
2199
2401
  console.log(chalk8.dim(`[dry-run] seed object_id: ${taskYml}`));
2200
2402
  } else {
2201
- fs17.writeFileSync(taskYml, yaml6.dump(raw, { lineWidth: 120 }), "utf-8");
2403
+ fs20.writeFileSync(taskYml, yaml7.dump(raw, { lineWidth: 120 }), "utf-8");
2202
2404
  console.log(chalk8.green(` seed object_id: ${taskYml}`));
2203
2405
  }
2204
2406
  seededObjectIds++;
@@ -2223,7 +2425,7 @@ task.command("migrate").description("Add task0.yml to legacy task directories th
2223
2425
  if (opts.dryRun) {
2224
2426
  console.log(chalk8.dim(`[dry-run] ${taskYml}`));
2225
2427
  } else {
2226
- fs17.writeFileSync(taskYml, yaml6.dump(taskConfig), "utf-8");
2428
+ fs20.writeFileSync(taskYml, yaml7.dump(taskConfig), "utf-8");
2227
2429
  console.log(chalk8.green(` ${taskYml}`));
2228
2430
  }
2229
2431
  migrated++;
@@ -2283,7 +2485,7 @@ task.command("done <id>").description("Write task lifecycle state to task0.yml \
2283
2485
  try {
2284
2486
  const { resolveTaskByObjectId: resolveTaskByObjectId2 } = await Promise.resolve().then(() => (init_task_state2(), task_state_exports));
2285
2487
  const loc = resolveTaskByObjectId2(id);
2286
- const raw = yaml6.load(fs17.readFileSync(loc.taskYml, "utf-8"));
2488
+ const raw = yaml7.load(fs20.readFileSync(loc.taskYml, "utf-8"));
2287
2489
  if (opts.phase) {
2288
2490
  const phase = opts.phase.trim();
2289
2491
  if (!phase) {
@@ -2296,7 +2498,7 @@ task.command("done <id>").description("Write task lifecycle state to task0.yml \
2296
2498
  return;
2297
2499
  }
2298
2500
  raw.workflow = { ...workflow2, phase };
2299
- fs17.writeFileSync(loc.taskYml, yaml6.dump(raw, { lineWidth: 120 }), "utf-8");
2501
+ fs20.writeFileSync(loc.taskYml, yaml7.dump(raw, { lineWidth: 120 }), "utf-8");
2300
2502
  console.log(chalk8.green(`${id} phase: ${phase}`));
2301
2503
  return;
2302
2504
  }
@@ -2307,7 +2509,7 @@ task.command("done <id>").description("Write task lifecycle state to task0.yml \
2307
2509
  raw.status = "done";
2308
2510
  const workflow = raw.workflow ?? {};
2309
2511
  raw.workflow = { ...workflow, phase: "completed" };
2310
- fs17.writeFileSync(loc.taskYml, yaml6.dump(raw, { lineWidth: 120 }), "utf-8");
2512
+ fs20.writeFileSync(loc.taskYml, yaml7.dump(raw, { lineWidth: 120 }), "utf-8");
2311
2513
  console.log(chalk8.green(`${id} marked as done`));
2312
2514
  } catch (err) {
2313
2515
  console.error(chalk8.red(err.message));
@@ -2316,33 +2518,33 @@ task.command("done <id>").description("Write task lifecycle state to task0.yml \
2316
2518
  });
2317
2519
  task.command("archive <id>").description("Archive a task (append to tasks.tar, remove from tasks/)").action((id) => {
2318
2520
  const cwd = process.cwd();
2319
- const projectYml = path15.join(cwd, "task0.yml");
2320
- if (!fs17.existsSync(projectYml)) {
2521
+ const projectYml = path17.join(cwd, "task0.yml");
2522
+ if (!fs20.existsSync(projectYml)) {
2321
2523
  console.error(chalk8.red("Not a task0 project (task0.yml not found)."));
2322
2524
  process.exit(1);
2323
2525
  }
2324
- const projectConfig = yaml6.load(fs17.readFileSync(projectYml, "utf-8"));
2526
+ const projectConfig = yaml7.load(fs20.readFileSync(projectYml, "utf-8"));
2325
2527
  if (projectConfig.kind !== "project") {
2326
2528
  console.error(chalk8.red('Invalid task0.yml: kind is not "project"'));
2327
2529
  process.exit(1);
2328
2530
  }
2329
- const tasksDir = path15.join(cwd, projectConfig.tasks_dir);
2330
- const taskDir = path15.join(tasksDir, id);
2331
- if (!fs17.existsSync(taskDir)) {
2531
+ const tasksDir = path17.join(cwd, projectConfig.tasks_dir);
2532
+ const taskDir = path17.join(tasksDir, id);
2533
+ if (!fs20.existsSync(taskDir)) {
2332
2534
  console.error(chalk8.red(`Task "${id}" not found at ${taskDir}`));
2333
2535
  process.exit(1);
2334
2536
  }
2335
2537
  const tarFile = tasksDir + ".tar";
2336
2538
  archiveTaskToTar(tasksDir, id, tarFile);
2337
- fs17.rmSync(taskDir, { recursive: true });
2539
+ fs20.rmSync(taskDir, { recursive: true });
2338
2540
  console.log(chalk8.green(`Archived "${id}" \u2192 ${tarFile}`));
2339
2541
  });
2340
2542
  function archiveTaskToTar(tasksDir, taskId, tarFile) {
2341
- if (fs17.existsSync(tarFile) && !fs17.statSync(tarFile).isFile()) {
2543
+ if (fs20.existsSync(tarFile) && !fs20.statSync(tarFile).isFile()) {
2342
2544
  console.error(chalk8.red(`${tarFile} exists but is not a file (likely a leftover directory). Remove it manually first.`));
2343
2545
  process.exit(1);
2344
2546
  }
2345
- if (fs17.existsSync(tarFile)) {
2547
+ if (fs20.existsSync(tarFile)) {
2346
2548
  execSync(`tar -rf ${JSON.stringify(tarFile)} -C ${JSON.stringify(tasksDir)} ${JSON.stringify(taskId)}`);
2347
2549
  } else {
2348
2550
  execSync(`tar -cf ${JSON.stringify(tarFile)} -C ${JSON.stringify(tasksDir)} ${JSON.stringify(taskId)}`);
@@ -2352,9 +2554,9 @@ function archiveTaskToTar(tasksDir, taskId, tarFile) {
2352
2554
  // src/commands/ui.ts
2353
2555
  import { Command as Command9 } from "commander";
2354
2556
  import { spawn } from "child_process";
2355
- import path16 from "path";
2557
+ import path18 from "path";
2356
2558
  import chalk9 from "chalk";
2357
- var DASHBOARD_DIR = path16.resolve(
2559
+ var DASHBOARD_DIR = path18.resolve(
2358
2560
  import.meta.dirname,
2359
2561
  "..",
2360
2562
  "..",
@@ -2417,31 +2619,6 @@ var AGENT_MODEL_DEFAULTS = {
2417
2619
  { id: "sonnet-4-thinking", label: "Sonnet 4 Thinking" }
2418
2620
  ]
2419
2621
  };
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
2622
  function defaultAgentModelFetchCommand(agent2) {
2446
2623
  if (agent2 === "cursor-agent")
2447
2624
  return "cursor-agent models";
@@ -2660,12 +2837,12 @@ models.command("default <agent>").description("Get or set default model / effort
2660
2837
  });
2661
2838
 
2662
2839
  // src/commands/agent-run.ts
2663
- import fs18 from "fs";
2664
- import path17 from "path";
2840
+ import fs21 from "fs";
2841
+ import path19 from "path";
2665
2842
  import { Command as Command11 } from "commander";
2666
2843
  import chalk11 from "chalk";
2667
2844
  import { spawn as spawn2, spawnSync as spawnSync3 } from "child_process";
2668
- import yaml7 from "js-yaml";
2845
+ import yaml8 from "js-yaml";
2669
2846
  var agentRun = new Command11("agent-run").description("Inspect and control agent runs");
2670
2847
  agentRun.command("list").description("List active agent runs").option("--json", "Output JSON").option("--task <id>", "Filter by task id").action(async (opts) => {
2671
2848
  try {
@@ -2789,7 +2966,7 @@ profile.command("get <ref>").description("Show one runtime profile by slug or ob
2789
2966
  console.log(formatProfile(result.runtime));
2790
2967
  console.log();
2791
2968
  console.log(chalk11.dim("--- exec ---"));
2792
- console.log(yaml7.dump(result.runtime.exec, { lineWidth: 100 }));
2969
+ console.log(yaml8.dump(result.runtime.exec, { lineWidth: 100 }));
2793
2970
  } catch (err) {
2794
2971
  const apiErr = err;
2795
2972
  if (apiErr.status === 404) failProfile(`not found: ${ref}`);
@@ -2799,7 +2976,7 @@ profile.command("get <ref>").description("Show one runtime profile by slug or ob
2799
2976
  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
2977
  let parsed;
2801
2978
  try {
2802
- parsed = yaml7.load(fs18.readFileSync(path17.resolve(opts.fromFile), "utf-8"));
2979
+ parsed = yaml8.load(fs21.readFileSync(path19.resolve(opts.fromFile), "utf-8"));
2803
2980
  } catch (err) {
2804
2981
  failProfile(`cannot read ${opts.fromFile}: ${err.message}`);
2805
2982
  }
@@ -2815,14 +2992,14 @@ profile.command("edit <ref>").description("Open the runtime profile YAML in $EDI
2815
2992
  try {
2816
2993
  const result = await api.get(`/api/runtime-profiles/${encodeURIComponent(ref)}`);
2817
2994
  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");
2995
+ const tmp = path19.join(process.env.TMPDIR || "/tmp", `task0-runtime-${result.runtime.slug}-${Date.now()}.yml`);
2996
+ fs21.writeFileSync(tmp, yaml8.dump(result.runtime, { lineWidth: 100 }), "utf-8");
2820
2997
  const editor = process.env.EDITOR || "vi";
2821
2998
  const r = spawnSync3(editor, [tmp], { stdio: "inherit" });
2822
2999
  if (r.status !== 0) failProfile(`editor exited with status ${r.status}`);
2823
- const updated = yaml7.load(fs18.readFileSync(tmp, "utf-8"));
3000
+ const updated = yaml8.load(fs21.readFileSync(tmp, "utf-8"));
2824
3001
  await api.put(`/api/runtime-profiles/${encodeURIComponent(ref)}`, updated);
2825
- fs18.unlinkSync(tmp);
3002
+ fs21.unlinkSync(tmp);
2826
3003
  console.log(chalk11.green(`updated ${ref}`));
2827
3004
  } catch (err) {
2828
3005
  failProfile(err.message);
@@ -2842,13 +3019,13 @@ agentRun.addCommand(profile);
2842
3019
  // src/commands/plan.ts
2843
3020
  import { Command as Command12 } from "commander";
2844
3021
  import chalk12 from "chalk";
2845
- import fs19 from "fs";
2846
- import path18 from "path";
3022
+ import fs22 from "fs";
3023
+ import path20 from "path";
2847
3024
  init_task_state2();
2848
3025
  var PLAN_GENERATE_SKILL_NAME = "plan-generate";
2849
3026
  function resolveSkillFilePath3(projectRoot, skillName) {
2850
- const p = path18.join(projectRoot, ".claude", "skills", skillName, "SKILL.md");
2851
- return fs19.existsSync(p) ? p : null;
3027
+ const p = path20.join(projectRoot, ".claude", "skills", skillName, "SKILL.md");
3028
+ return fs22.existsSync(p) ? p : null;
2852
3029
  }
2853
3030
  var plan = new Command12("plan").description("Generate and refine plans");
2854
3031
  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 +3157,7 @@ var PLAN_REFINE_SKILL_NAME = "plan-refine";
2980
3157
  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
3158
  try {
2982
3159
  const loc = resolveTaskByObjectId(objectId);
2983
- const files = fs19.readdirSync(loc.taskDir);
3160
+ const files = fs22.readdirSync(loc.taskDir);
2984
3161
  const planFiles = files.filter((f) => /^PLAN-\d+-(codex|claude-code|cursor)\.md$/.test(f));
2985
3162
  if (planFiles.length === 0) {
2986
3163
  console.error(chalk12.red("No PLAN-NN-<agent>.md files found. Run `task0 plan generate` first."));
@@ -3022,8 +3199,8 @@ plan.command("refine <objectId>").description("Synthesize plan files + ISSUE fil
3022
3199
  if (!opts.json) console.log(chalk12.dim(`[refine] ${s.status}${s.phase ? " " + s.phase : ""}`));
3023
3200
  }
3024
3201
  });
3025
- const refinedPath = path18.join(loc.taskDir, refinedFile);
3026
- const wrote = fs19.existsSync(refinedPath);
3202
+ const refinedPath = path20.join(loc.taskDir, refinedFile);
3203
+ const wrote = fs22.existsSync(refinedPath);
3027
3204
  if (final.status === "done" && wrote) {
3028
3205
  await updateWorkflow(loc.taskYml, {
3029
3206
  phase: "refined",
@@ -3175,8 +3352,8 @@ import { Command as Command14 } from "commander";
3175
3352
  import chalk14 from "chalk";
3176
3353
 
3177
3354
  // src/lib/project.ts
3178
- import path19 from "path";
3179
- import fs20 from "fs";
3355
+ import path21 from "path";
3356
+ import fs23 from "fs";
3180
3357
  function resolveProjectName(opts) {
3181
3358
  if (opts.project && opts.project.length > 0) return opts.project;
3182
3359
  const config = loadConfig();
@@ -3186,15 +3363,15 @@ function resolveProjectName(opts) {
3186
3363
  "Cannot resolve project: no registered projects. Use `task0 source add <path>` first, or pass --project <name>."
3187
3364
  );
3188
3365
  }
3189
- const cwd = fs20.realpathSync(process.cwd());
3366
+ const cwd = fs23.realpathSync(process.cwd());
3190
3367
  for (const source2 of projects) {
3191
3368
  let sourceAbs;
3192
3369
  try {
3193
- sourceAbs = fs20.realpathSync(path19.resolve(source2.path));
3370
+ sourceAbs = fs23.realpathSync(path21.resolve(source2.path));
3194
3371
  } catch {
3195
- sourceAbs = path19.resolve(source2.path);
3372
+ sourceAbs = path21.resolve(source2.path);
3196
3373
  }
3197
- if (cwd === sourceAbs || cwd.startsWith(sourceAbs + path19.sep)) {
3374
+ if (cwd === sourceAbs || cwd.startsWith(sourceAbs + path21.sep)) {
3198
3375
  return source2.name;
3199
3376
  }
3200
3377
  }
@@ -3834,12 +4011,12 @@ import chalk17 from "chalk";
3834
4011
 
3835
4012
  // src/core/issue/decision.ts
3836
4013
  init_node();
3837
- import fs21 from "fs";
3838
- import path20 from "path";
4014
+ import fs24 from "fs";
4015
+ import path22 from "path";
3839
4016
  init_task_state2();
3840
4017
  var DECISION_FILE_RE = /^DECISION-(\d+)-([a-z0-9-]+)\.md$/;
3841
4018
  function selectBlockingIssues(taskDir, explicitIssue) {
3842
- const files = fs21.existsSync(taskDir) ? fs21.readdirSync(taskDir) : [];
4019
+ const files = fs24.existsSync(taskDir) ? fs24.readdirSync(taskDir) : [];
3843
4020
  const issueFiles = files.filter((f) => /^ISSUE-\d+\.md$/.test(f)).sort();
3844
4021
  const all = readOpenQuestions(taskDir, issueFiles);
3845
4022
  if (!explicitIssue) return all;
@@ -3943,12 +4120,12 @@ function parseConsolidatedAnswers(md) {
3943
4120
  return sections;
3944
4121
  }
3945
4122
  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");
4123
+ const issuePath = path22.join(taskDir, issueFile);
4124
+ const consolidatedPath = path22.join(taskDir, consolidatedFile);
4125
+ if (!fs24.existsSync(issuePath)) throw new Error(`${issueFile} not found in ${taskDir}`);
4126
+ if (!fs24.existsSync(consolidatedPath)) throw new Error(`${consolidatedFile} not found in ${taskDir}`);
4127
+ const issueMd = fs24.readFileSync(issuePath, "utf-8");
4128
+ const consolidatedMd = fs24.readFileSync(consolidatedPath, "utf-8");
3952
4129
  const answers = parseConsolidatedAnswers(consolidatedMd);
3953
4130
  if (answers.length === 0) {
3954
4131
  throw new Error(`${consolidatedFile} has no "## Qn" sections; cannot derive Decisions`);
@@ -3970,7 +4147,7 @@ _Resolved from [${consolidatedFile}](${consolidatedFile}); see that file for rea
3970
4147
  throw new Error(`${issueFile} has no "## Open Questions" section to replace`);
3971
4148
  }
3972
4149
  const next = issueMd.replace(openQRe, decisionsSection.trimEnd() + "\n");
3973
- fs21.writeFileSync(issuePath, next, "utf-8");
4150
+ fs24.writeFileSync(issuePath, next, "utf-8");
3974
4151
  return { replaced: answers.length };
3975
4152
  }
3976
4153
  async function propose(opts) {
@@ -3998,8 +4175,8 @@ async function propose(opts) {
3998
4175
  const kicks = [];
3999
4176
  for (const agent2 of opts.agents) {
4000
4177
  const decisionFile = decisionFileName(issue2, agent2);
4001
- const decisionPath = path20.join(loc.taskDir, decisionFile);
4002
- if (fs21.existsSync(decisionPath) && !opts.force) {
4178
+ const decisionPath = path22.join(loc.taskDir, decisionFile);
4179
+ if (fs24.existsSync(decisionPath) && !opts.force) {
4003
4180
  if (opts.ifNeeded) {
4004
4181
  kicks.push({
4005
4182
  issue: issue2.file,
@@ -4074,7 +4251,7 @@ async function propose(opts) {
4074
4251
  const final = await waitForAgentRun(k.agentRunId);
4075
4252
  if (final.status !== "done") {
4076
4253
  k.error = final.error || `runtime ${k.agentRunId} ended with ${final.status}`;
4077
- } else if (!fs21.existsSync(path20.join(loc.taskDir, k.decisionFile))) {
4254
+ } else if (!fs24.existsSync(path22.join(loc.taskDir, k.decisionFile))) {
4078
4255
  k.error = `runtime completed but ${k.decisionFile} was not written`;
4079
4256
  }
4080
4257
  } catch (err) {
@@ -4095,7 +4272,7 @@ async function consolidate(opts) {
4095
4272
  const kicks = [];
4096
4273
  const modelOpts = resolveModelOptions(opts.agent, { model: opts.model, effort: opts.effort }, opts.warn);
4097
4274
  for (const issue2 of issues) {
4098
- const files = fs21.readdirSync(loc.taskDir);
4275
+ const files = fs24.readdirSync(loc.taskDir);
4099
4276
  const proposalFiles = files.filter((f) => {
4100
4277
  const m = f.match(DECISION_FILE_RE);
4101
4278
  return !!m && m[1] === issue2.index && m[2] !== "consolidated";
@@ -4141,7 +4318,7 @@ async function consolidate(opts) {
4141
4318
  if (opts.wait) {
4142
4319
  try {
4143
4320
  const final = await waitForAgentRun(agentRunId);
4144
- const wrote = fs21.existsSync(path20.join(loc.taskDir, consolidatedFile));
4321
+ const wrote = fs24.existsSync(path22.join(loc.taskDir, consolidatedFile));
4145
4322
  if (final.status !== "done") {
4146
4323
  kick.error = final.error || `runtime ${agentRunId} ended with ${final.status}`;
4147
4324
  } else if (!wrote) {
@@ -4207,7 +4384,7 @@ async function approve(opts) {
4207
4384
  return { updated };
4208
4385
  }
4209
4386
  function buildReferenceFiles(taskDir, issue2) {
4210
- const names = fs21.readdirSync(taskDir);
4387
+ const names = fs24.readdirSync(taskDir);
4211
4388
  const refs = [issue2.file];
4212
4389
  if (names.includes("ISSUE.md")) refs.push("ISSUE.md");
4213
4390
  const ideaFile = `IDEA-${issue2.index}.md`;
@@ -4315,12 +4492,12 @@ issue.command("approve <taskId>").description("Apply the consolidated decisions
4315
4492
  });
4316
4493
 
4317
4494
  // src/commands/agent.ts
4318
- import fs22 from "fs";
4319
- import path21 from "path";
4495
+ import fs25 from "fs";
4496
+ import path23 from "path";
4320
4497
  import { spawnSync as spawnSync5 } from "child_process";
4321
4498
  import { Command as Command18 } from "commander";
4322
4499
  import chalk18 from "chalk";
4323
- import yaml8 from "js-yaml";
4500
+ import yaml9 from "js-yaml";
4324
4501
  function statusBadge(s) {
4325
4502
  if (s === "working") return chalk18.cyan("\u25CF");
4326
4503
  if (s === "error") return chalk18.red("\u25CF");
@@ -4380,10 +4557,10 @@ agent.command("get <ref>").description("Show one agent by object_id or slug").op
4380
4557
  }
4381
4558
  console.log();
4382
4559
  console.log(chalk18.dim("--- spec ---"));
4383
- console.log(yaml8.dump(result.agent.spec, { lineWidth: 100 }));
4560
+ console.log(yaml9.dump(result.agent.spec, { lineWidth: 100 }));
4384
4561
  if (result.agent.mcp_config) {
4385
4562
  console.log(chalk18.dim("--- mcp_config ---"));
4386
- console.log(yaml8.dump(result.agent.mcp_config, { lineWidth: 100 }));
4563
+ console.log(yaml9.dump(result.agent.mcp_config, { lineWidth: 100 }));
4387
4564
  }
4388
4565
  } catch (err) {
4389
4566
  const apiErr = err;
@@ -4394,8 +4571,8 @@ agent.command("get <ref>").description("Show one agent by object_id or slug").op
4394
4571
  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
4572
  let parsed;
4396
4573
  try {
4397
- const raw = fs22.readFileSync(path21.resolve(opts.fromFile), "utf-8");
4398
- parsed = yaml8.load(raw);
4574
+ const raw = fs25.readFileSync(path23.resolve(opts.fromFile), "utf-8");
4575
+ parsed = yaml9.load(raw);
4399
4576
  } catch (err) {
4400
4577
  fail5(`cannot read ${opts.fromFile}: ${err.message}`);
4401
4578
  }
@@ -4410,17 +4587,17 @@ agent.command("create").description("Create an agent from a YAML spec file").req
4410
4587
  agent.command("edit <ref>").description("Open the agent YAML in $EDITOR and save changes").action(async (ref) => {
4411
4588
  try {
4412
4589
  const result = await api.get(`/api/agents/${encodeURIComponent(ref)}`);
4413
- const tmp = path21.join(
4590
+ const tmp = path23.join(
4414
4591
  process.env.TMPDIR || "/tmp",
4415
4592
  `task0-agent-${result.agent.slug}-${Date.now()}.yml`
4416
4593
  );
4417
- fs22.writeFileSync(tmp, yaml8.dump(result.agent, { lineWidth: 100 }), "utf-8");
4594
+ fs25.writeFileSync(tmp, yaml9.dump(result.agent, { lineWidth: 100 }), "utf-8");
4418
4595
  const editor = process.env.EDITOR || "vi";
4419
4596
  const r = spawnSync5(editor, [tmp], { stdio: "inherit" });
4420
4597
  if (r.status !== 0) fail5(`editor exited with status ${r.status}`);
4421
- const updated = yaml8.load(fs22.readFileSync(tmp, "utf-8"));
4598
+ const updated = yaml9.load(fs25.readFileSync(tmp, "utf-8"));
4422
4599
  await api.put(`/api/agents/${encodeURIComponent(ref)}`, updated);
4423
- fs22.unlinkSync(tmp);
4600
+ fs25.unlinkSync(tmp);
4424
4601
  console.log(chalk18.green(`updated ${ref}`));
4425
4602
  } catch (err) {
4426
4603
  fail5(err.message);
@@ -4491,7 +4668,7 @@ var mcp = new Command18("mcp").description("Manage per-agent MCP config (mcp_con
4491
4668
  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
4669
  let parsed;
4493
4670
  try {
4494
- parsed = JSON.parse(fs22.readFileSync(path21.resolve(opts.fromFile), "utf-8"));
4671
+ parsed = JSON.parse(fs25.readFileSync(path23.resolve(opts.fromFile), "utf-8"));
4495
4672
  } catch (err) {
4496
4673
  fail5(`cannot parse ${opts.fromFile}: ${err.message}`);
4497
4674
  }
@@ -4640,36 +4817,36 @@ function pickRegisterAuth(flagToken) {
4640
4817
 
4641
4818
  // src/core/daemon-rpc-handlers.ts
4642
4819
  init_node();
4643
- import fs25 from "fs";
4644
- import path24 from "path";
4820
+ import fs28 from "fs";
4821
+ import path26 from "path";
4645
4822
 
4646
4823
  // src/core/daemon-agent-run-runner.ts
4647
4824
  import { execFileSync } from "child_process";
4648
- import fs24 from "fs";
4649
- import path23 from "path";
4825
+ import fs27 from "fs";
4826
+ import path25 from "path";
4650
4827
 
4651
4828
  // src/core/daemon-agent-run-dir.ts
4652
4829
  init_node();
4653
- import fs23 from "fs";
4654
- import path22 from "path";
4830
+ import fs26 from "fs";
4831
+ import path24 from "path";
4655
4832
  function agentRunRoot() {
4656
- return process.env.TASK0_DAEMON_AGENT_RUN_DIR || path22.join(task0Home(), "agent-run");
4833
+ return process.env.TASK0_DAEMON_AGENT_RUN_DIR || path24.join(profileDir(), "agent-run");
4657
4834
  }
4658
4835
  function agentRunDir(runId) {
4659
- return path22.join(agentRunRoot(), runId);
4836
+ return path24.join(agentRunRoot(), runId);
4660
4837
  }
4661
4838
  function agentRunStatusPath(runId) {
4662
- return path22.join(agentRunDir(runId), "status.json");
4839
+ return path24.join(agentRunDir(runId), "status.json");
4663
4840
  }
4664
4841
  function ensureAgentRunDir(runId) {
4665
4842
  const dir = agentRunDir(runId);
4666
- fs23.mkdirSync(dir, { recursive: true });
4843
+ fs26.mkdirSync(dir, { recursive: true });
4667
4844
  return dir;
4668
4845
  }
4669
4846
  function removeAgentRunDir(runId) {
4670
4847
  const dir = agentRunDir(runId);
4671
- if (!fs23.existsSync(dir)) return;
4672
- fs23.rmSync(dir, { recursive: true, force: true });
4848
+ if (!fs26.existsSync(dir)) return;
4849
+ fs26.rmSync(dir, { recursive: true, force: true });
4673
4850
  }
4674
4851
 
4675
4852
  // src/core/daemon-agent-run-runner.ts
@@ -4711,9 +4888,9 @@ function killSession2(sessionName) {
4711
4888
  }
4712
4889
  function readStatusFile(runId) {
4713
4890
  const filePath = agentRunStatusPath(runId);
4714
- if (!fs24.existsSync(filePath)) return null;
4891
+ if (!fs27.existsSync(filePath)) return null;
4715
4892
  try {
4716
- const raw = fs24.readFileSync(filePath, "utf-8");
4893
+ const raw = fs27.readFileSync(filePath, "utf-8");
4717
4894
  const parsed = JSON.parse(raw);
4718
4895
  return { raw, parsed };
4719
4896
  } catch {
@@ -4738,22 +4915,22 @@ function launchAgentRun(params) {
4738
4915
  if (active.has(params.runId)) {
4739
4916
  throw Object.assign(new Error(`agent run already active: ${params.runId}`), { code: "already_running" });
4740
4917
  }
4741
- if (!fs24.existsSync(params.workspace)) {
4918
+ if (!fs27.existsSync(params.workspace)) {
4742
4919
  throw Object.assign(new Error(`workspace not found: ${params.workspace}`), { code: "workspace_missing" });
4743
4920
  }
4744
4921
  const runDir = ensureAgentRunDir(params.runId);
4745
- const scriptPath = path23.join(runDir, "script.sh");
4746
- fs24.writeFileSync(scriptPath, params.scriptContent, { mode: 493 });
4922
+ const scriptPath = path25.join(runDir, "script.sh");
4923
+ fs27.writeFileSync(scriptPath, params.scriptContent, { mode: 493 });
4747
4924
  if (params.promptContent !== void 0) {
4748
- fs24.writeFileSync(path23.join(runDir, "prompt.txt"), params.promptContent, "utf-8");
4925
+ fs27.writeFileSync(path25.join(runDir, "prompt.txt"), params.promptContent, "utf-8");
4749
4926
  }
4750
4927
  for (const [name, content] of Object.entries(params.auxFiles ?? {})) {
4751
4928
  if (name.includes("/") || name.includes("\\") || name === ".." || name === ".") {
4752
4929
  throw Object.assign(new Error(`invalid aux file name: ${name}`), { code: "invalid_params" });
4753
4930
  }
4754
- fs24.writeFileSync(path23.join(runDir, name), content, "utf-8");
4931
+ fs27.writeFileSync(path25.join(runDir, name), content, "utf-8");
4755
4932
  }
4756
- fs24.writeFileSync(
4933
+ fs27.writeFileSync(
4757
4934
  agentRunStatusPath(params.runId),
4758
4935
  JSON.stringify({ status: "starting", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2),
4759
4936
  "utf-8"
@@ -4834,15 +5011,15 @@ function ensureString(value, name) {
4834
5011
  }
4835
5012
  function ensureSafeTaskId(value) {
4836
5013
  const id = ensureString(value, "taskId");
4837
- if (id.includes("/") || id.includes("\\") || id.includes("\0") || id === "." || id === ".." || path24.isAbsolute(id)) {
5014
+ if (id.includes("/") || id.includes("\\") || id.includes("\0") || id === "." || id === ".." || path26.isAbsolute(id)) {
4838
5015
  throw Object.assign(new Error(`invalid taskId: ${id}`), { code: "invalid_params" });
4839
5016
  }
4840
5017
  return id;
4841
5018
  }
4842
5019
  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)) {
5020
+ const root = path26.resolve(rootAbs);
5021
+ const target = path26.resolve(resolvedAbs);
5022
+ if (target !== root && !target.startsWith(root + path26.sep)) {
4846
5023
  throw Object.assign(new Error("path escapes containment root"), { code: "invalid_params" });
4847
5024
  }
4848
5025
  }
@@ -4884,7 +5061,7 @@ var rpcHandlers = {
4884
5061
  // missing object_id / id mismatches applied in place on the daemon's FS.
4885
5062
  async scan_project(params) {
4886
5063
  const rootPath = ensureString(params.rootPath, "rootPath");
4887
- const name = typeof params.name === "string" && params.name ? params.name : path24.basename(rootPath);
5064
+ const name = typeof params.name === "string" && params.name ? params.name : path26.basename(rootPath);
4888
5065
  const result = scanProjectWithRepair(rootPath, name);
4889
5066
  return { tasks: result.tasks, errors: result.errors };
4890
5067
  },
@@ -4895,7 +5072,7 @@ var rpcHandlers = {
4895
5072
  const filePath = ensureString(params.path, "path");
4896
5073
  let stat;
4897
5074
  try {
4898
- stat = fs25.statSync(filePath);
5075
+ stat = fs28.statSync(filePath);
4899
5076
  } catch {
4900
5077
  throw Object.assign(new Error("file not found"), { code: "not_found" });
4901
5078
  }
@@ -4905,7 +5082,7 @@ var rpcHandlers = {
4905
5082
  if (stat.size > MAX_FILE_BYTES) {
4906
5083
  throw Object.assign(new Error(`file too large (${stat.size} bytes > ${MAX_FILE_BYTES})`), { code: "too_large" });
4907
5084
  }
4908
- const content = fs25.readFileSync(filePath, "utf-8");
5085
+ const content = fs28.readFileSync(filePath, "utf-8");
4909
5086
  return { content, size: stat.size, modifiedAt: stat.mtime.toISOString() };
4910
5087
  },
4911
5088
  // ---------------------------------------------------------------------
@@ -4920,15 +5097,15 @@ var rpcHandlers = {
4920
5097
  },
4921
5098
  async project_add(params, ctx) {
4922
5099
  const rawPath = ensureString(params.path, "path");
4923
- const absPath = path24.resolve(rawPath);
4924
- if (!fs25.existsSync(absPath)) {
5100
+ const absPath = path26.resolve(rawPath);
5101
+ if (!fs28.existsSync(absPath)) {
4925
5102
  throw Object.assign(new Error(`path does not exist: ${absPath}`), { code: "not_found" });
4926
5103
  }
4927
- const stat = fs25.statSync(absPath);
5104
+ const stat = fs28.statSync(absPath);
4928
5105
  if (!stat.isDirectory()) {
4929
5106
  throw Object.assign(new Error(`path is not a directory: ${absPath}`), { code: "invalid_target" });
4930
5107
  }
4931
- const name = optionalString(params.name)?.trim() || path24.basename(absPath);
5108
+ const name = optionalString(params.name)?.trim() || path26.basename(absPath);
4932
5109
  const result = scanProjectWithRepair(absPath, name);
4933
5110
  addSource({ name, type: "project", path: absPath, enabled: true });
4934
5111
  ctx.notifyManifestChanged();
@@ -4977,24 +5154,24 @@ var rpcHandlers = {
4977
5154
  if (!project2) {
4978
5155
  throw Object.assign(new Error(`project not found: ${projectName}`), { code: "not_found" });
4979
5156
  }
4980
- const projectAbs = path24.resolve(project2.path);
4981
- const projectConfig = readYaml(path24.join(projectAbs, "task0.yml"));
5157
+ const projectAbs = path26.resolve(project2.path);
5158
+ const projectConfig = readYaml(path26.join(projectAbs, "task0.yml"));
4982
5159
  if (!projectConfig) {
4983
5160
  throw Object.assign(new Error(`invalid project: missing task0.yml at ${projectAbs}`), { code: "invalid_project" });
4984
5161
  }
4985
- const tasksRoot = path24.resolve(projectAbs, projectConfig.tasks_dir);
4986
- const taskDir = path24.resolve(tasksRoot, taskId);
5162
+ const tasksRoot = path26.resolve(projectAbs, projectConfig.tasks_dir);
5163
+ const taskDir = path26.resolve(tasksRoot, taskId);
4987
5164
  assertContained(tasksRoot, taskDir);
4988
- if (!fs25.existsSync(taskDir)) {
5165
+ if (!fs28.existsSync(taskDir)) {
4989
5166
  throw Object.assign(new Error(`task not found: ${taskId}`), { code: "not_found" });
4990
5167
  }
4991
- const taskYml = path24.join(taskDir, "task0.yml");
4992
- const yaml10 = readYaml(taskYml);
4993
- if (!yaml10) {
5168
+ const taskYml = path26.join(taskDir, "task0.yml");
5169
+ const yaml11 = readYaml(taskYml);
5170
+ if (!yaml11) {
4994
5171
  throw Object.assign(new Error(`task yaml missing or unreadable: ${taskYml}`), { code: "not_found" });
4995
5172
  }
4996
- const files = fs25.readdirSync(taskDir).filter((name) => name !== "task0.yml");
4997
- return { task_dir: taskDir, yaml: yaml10, files };
5173
+ const files = fs28.readdirSync(taskDir).filter((name) => name !== "task0.yml");
5174
+ return { task_dir: taskDir, yaml: yaml11, files };
4998
5175
  },
4999
5176
  // Create a task directory + write task0.yml + write any additional named
5000
5177
  // files. Hub decides the taskId and yaml content (slug/object_id are
@@ -5002,8 +5179,8 @@ var rpcHandlers = {
5002
5179
  async task_create(params, ctx) {
5003
5180
  const projectName = ensureString(params.projectName, "projectName");
5004
5181
  const taskId = ensureSafeTaskId(params.taskId);
5005
- const yaml10 = params.yaml;
5006
- if (!yaml10 || typeof yaml10 !== "object") {
5182
+ const yaml11 = params.yaml;
5183
+ if (!yaml11 || typeof yaml11 !== "object") {
5007
5184
  throw Object.assign(new Error("yaml (object) is required"), { code: "invalid_params" });
5008
5185
  }
5009
5186
  const files = params.files ?? {};
@@ -5011,24 +5188,24 @@ var rpcHandlers = {
5011
5188
  if (!project2) {
5012
5189
  throw Object.assign(new Error(`project not found: ${projectName}`), { code: "not_found" });
5013
5190
  }
5014
- const projectAbs = path24.resolve(project2.path);
5015
- const projectConfig = readYaml(path24.join(projectAbs, "task0.yml"));
5191
+ const projectAbs = path26.resolve(project2.path);
5192
+ const projectConfig = readYaml(path26.join(projectAbs, "task0.yml"));
5016
5193
  if (!projectConfig) {
5017
5194
  throw Object.assign(new Error(`invalid project: missing task0.yml at ${projectAbs}`), { code: "invalid_project" });
5018
5195
  }
5019
- const tasksRoot = path24.resolve(projectAbs, projectConfig.tasks_dir);
5020
- const taskDir = path24.resolve(tasksRoot, taskId);
5196
+ const tasksRoot = path26.resolve(projectAbs, projectConfig.tasks_dir);
5197
+ const taskDir = path26.resolve(tasksRoot, taskId);
5021
5198
  assertContained(tasksRoot, taskDir);
5022
- if (fs25.existsSync(taskDir)) {
5199
+ if (fs28.existsSync(taskDir)) {
5023
5200
  throw Object.assign(new Error(`task already exists: ${taskId}`), { code: "already_exists" });
5024
5201
  }
5025
- fs25.mkdirSync(taskDir, { recursive: true });
5026
- writeYaml(path24.join(taskDir, "task0.yml"), yaml10);
5202
+ fs28.mkdirSync(taskDir, { recursive: true });
5203
+ writeYaml(path26.join(taskDir, "task0.yml"), yaml11);
5027
5204
  for (const [name, content] of Object.entries(files)) {
5028
5205
  if (name.includes("/") || name.includes("\\") || name === ".." || name === ".") {
5029
5206
  throw Object.assign(new Error(`invalid file name: ${name}`), { code: "invalid_params" });
5030
5207
  }
5031
- fs25.writeFileSync(path24.join(taskDir, name), content, "utf-8");
5208
+ fs28.writeFileSync(path26.join(taskDir, name), content, "utf-8");
5032
5209
  }
5033
5210
  ctx.notifyManifestChanged();
5034
5211
  return { task_dir: taskDir };
@@ -5040,27 +5217,27 @@ var rpcHandlers = {
5040
5217
  async task_write_yaml(params, ctx) {
5041
5218
  const projectName = ensureString(params.projectName, "projectName");
5042
5219
  const taskId = ensureSafeTaskId(params.taskId);
5043
- const yaml10 = params.yaml;
5044
- if (!yaml10 || typeof yaml10 !== "object") {
5220
+ const yaml11 = params.yaml;
5221
+ if (!yaml11 || typeof yaml11 !== "object") {
5045
5222
  throw Object.assign(new Error("yaml (object) is required"), { code: "invalid_params" });
5046
5223
  }
5047
5224
  const project2 = listProjects().find((p) => p.name === projectName);
5048
5225
  if (!project2) {
5049
5226
  throw Object.assign(new Error(`project not found: ${projectName}`), { code: "not_found" });
5050
5227
  }
5051
- const projectAbs = path24.resolve(project2.path);
5052
- const projectConfig = readYaml(path24.join(projectAbs, "task0.yml"));
5228
+ const projectAbs = path26.resolve(project2.path);
5229
+ const projectConfig = readYaml(path26.join(projectAbs, "task0.yml"));
5053
5230
  if (!projectConfig) {
5054
5231
  throw Object.assign(new Error(`invalid project: missing task0.yml at ${projectAbs}`), { code: "invalid_project" });
5055
5232
  }
5056
- const tasksRoot = path24.resolve(projectAbs, projectConfig.tasks_dir);
5057
- const taskDir = path24.resolve(tasksRoot, taskId);
5233
+ const tasksRoot = path26.resolve(projectAbs, projectConfig.tasks_dir);
5234
+ const taskDir = path26.resolve(tasksRoot, taskId);
5058
5235
  assertContained(tasksRoot, taskDir);
5059
- const taskYml = path24.join(taskDir, "task0.yml");
5060
- if (!fs25.existsSync(taskYml)) {
5236
+ const taskYml = path26.join(taskDir, "task0.yml");
5237
+ if (!fs28.existsSync(taskYml)) {
5061
5238
  throw Object.assign(new Error(`task yaml not found: ${taskYml}`), { code: "not_found" });
5062
5239
  }
5063
- writeYaml(taskYml, yaml10);
5240
+ writeYaml(taskYml, yaml11);
5064
5241
  ctx.notifyManifestChanged();
5065
5242
  return { task_dir: taskDir };
5066
5243
  },
@@ -5073,18 +5250,18 @@ var rpcHandlers = {
5073
5250
  if (!project2) {
5074
5251
  throw Object.assign(new Error(`project not found: ${projectName}`), { code: "not_found" });
5075
5252
  }
5076
- const projectAbs = path24.resolve(project2.path);
5077
- const projectConfig = readYaml(path24.join(projectAbs, "task0.yml"));
5253
+ const projectAbs = path26.resolve(project2.path);
5254
+ const projectConfig = readYaml(path26.join(projectAbs, "task0.yml"));
5078
5255
  if (!projectConfig) {
5079
5256
  throw Object.assign(new Error(`invalid project: missing task0.yml at ${projectAbs}`), { code: "invalid_project" });
5080
5257
  }
5081
- const tasksRoot = path24.resolve(projectAbs, projectConfig.tasks_dir);
5082
- const taskDir = path24.resolve(tasksRoot, taskId);
5258
+ const tasksRoot = path26.resolve(projectAbs, projectConfig.tasks_dir);
5259
+ const taskDir = path26.resolve(tasksRoot, taskId);
5083
5260
  assertContained(tasksRoot, taskDir);
5084
- if (!fs25.existsSync(taskDir)) {
5261
+ if (!fs28.existsSync(taskDir)) {
5085
5262
  throw Object.assign(new Error(`task not found: ${taskId}`), { code: "not_found" });
5086
5263
  }
5087
- fs25.rmSync(taskDir, { recursive: true, force: true });
5264
+ fs28.rmSync(taskDir, { recursive: true, force: true });
5088
5265
  ctx.notifyManifestChanged();
5089
5266
  return { task_dir: taskDir, deleted: true };
5090
5267
  },
@@ -5115,9 +5292,9 @@ var rpcHandlers = {
5115
5292
  const projectSkills = getProjectAgentSkills(workspace);
5116
5293
  const globalSkills = getGlobalAgentSkills();
5117
5294
  const all = [...projectSkills, ...globalSkills];
5118
- const resolved = path24.resolve(filePath);
5295
+ const resolved = path26.resolve(filePath);
5119
5296
  const skill = all.find(
5120
- (s) => s.agent === agent2 && path24.resolve(s.filePath) === resolved
5297
+ (s) => s.agent === agent2 && path26.resolve(s.filePath) === resolved
5121
5298
  ) ?? null;
5122
5299
  return { skill };
5123
5300
  },
@@ -5150,16 +5327,16 @@ var rpcHandlers = {
5150
5327
 
5151
5328
  // src/core/daemon-service/launchd.ts
5152
5329
  import { spawnSync as spawnSync6 } from "child_process";
5153
- import fs27 from "fs";
5154
- import path26 from "path";
5330
+ import fs30 from "fs";
5331
+ import path28 from "path";
5155
5332
 
5156
5333
  // src/core/daemon-service/binary.ts
5157
- import fs26 from "fs";
5334
+ import fs29 from "fs";
5158
5335
  import { fileURLToPath } from "url";
5159
5336
  function resolveTask0Invocation() {
5160
5337
  const node = process.execPath;
5161
5338
  const argv1 = process.argv[1] ?? fileURLToPath(import.meta.url);
5162
- const main2 = fs26.realpathSync(argv1);
5339
+ const main2 = fs29.realpathSync(argv1);
5163
5340
  if (!isInstalledBuild(main2) && process.env.TASK0_ALLOW_DEV_SERVICE !== "1") {
5164
5341
  throw new Error(
5165
5342
  `Refusing to install autostart service pointing at ${main2}.
@@ -5177,15 +5354,19 @@ function isInstalledBuild(p) {
5177
5354
  // src/core/daemon-service/paths.ts
5178
5355
  init_node();
5179
5356
  import os5 from "os";
5180
- import path25 from "path";
5357
+ import path27 from "path";
5181
5358
 
5182
5359
  // src/core/daemon-service/types.ts
5360
+ init_node();
5183
5361
  import crypto2 from "crypto";
5184
5362
  var BASE_SERVICE_LABEL = "cc.cy0.task0";
5185
5363
  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);
5364
+ const home = process.env.TASK0_HOME ?? "";
5365
+ const profile3 = currentProfileName();
5366
+ if ((!home || home.length === 0) && profile3 === DEFAULT_PROFILE_NAME) {
5367
+ return BASE_SERVICE_LABEL;
5368
+ }
5369
+ const hash = crypto2.createHash("sha256").update(`${home}\0${profile3}`).digest("hex").slice(0, 8);
5189
5370
  return `${BASE_SERVICE_LABEL}.${hash}`;
5190
5371
  }
5191
5372
 
@@ -5194,21 +5375,21 @@ function unitPath(scope) {
5194
5375
  const platform = process.platform;
5195
5376
  const label = serviceLabel();
5196
5377
  if (platform === "darwin") {
5197
- return scope === "user" ? path25.join(os5.homedir(), "Library", "LaunchAgents", `${label}.plist`) : path25.join("/", "Library", "LaunchDaemons", `${label}.plist`);
5378
+ return scope === "user" ? path27.join(os5.homedir(), "Library", "LaunchAgents", `${label}.plist`) : path27.join("/", "Library", "LaunchDaemons", `${label}.plist`);
5198
5379
  }
5199
5380
  if (platform === "linux") {
5200
- return scope === "user" ? path25.join(os5.homedir(), ".config", "systemd", "user", `${label}.service`) : path25.join("/", "etc", "systemd", "system", `${label}.service`);
5381
+ return scope === "user" ? path27.join(os5.homedir(), ".config", "systemd", "user", `${label}.service`) : path27.join("/", "etc", "systemd", "system", `${label}.service`);
5201
5382
  }
5202
5383
  throw new Error(`Unsupported platform for service install: ${platform}`);
5203
5384
  }
5204
5385
  function logDir() {
5205
- return path25.join(task0Home(), "logs");
5386
+ return path27.join(profileDir(), "logs");
5206
5387
  }
5207
5388
  function logPaths() {
5208
5389
  const dir = logDir();
5209
5390
  return {
5210
- out: path25.join(dir, "daemon.out.log"),
5211
- err: path25.join(dir, "daemon.err.log")
5391
+ out: path27.join(dir, "daemon.out.log"),
5392
+ err: path27.join(dir, "daemon.err.log")
5212
5393
  };
5213
5394
  }
5214
5395
 
@@ -5283,8 +5464,8 @@ function createLaunchdManager(scope) {
5283
5464
  const logs = logPaths();
5284
5465
  async function install() {
5285
5466
  const inv = resolveTask0Invocation();
5286
- fs27.mkdirSync(logDir(), { recursive: true });
5287
- fs27.mkdirSync(path26.dirname(file), { recursive: true });
5467
+ fs30.mkdirSync(logDir(), { recursive: true });
5468
+ fs30.mkdirSync(path28.dirname(file), { recursive: true });
5288
5469
  const body = renderPlist({
5289
5470
  node: inv.node,
5290
5471
  main: inv.main,
@@ -5294,7 +5475,7 @@ function createLaunchdManager(scope) {
5294
5475
  err: logs.err,
5295
5476
  task0Env: collectTask0Env()
5296
5477
  });
5297
- fs27.writeFileSync(file, body, { mode: 420 });
5478
+ fs30.writeFileSync(file, body, { mode: 420 });
5298
5479
  const bootstrap = run2("launchctl", ["bootstrap", domainTarget(scope), file]);
5299
5480
  if (bootstrap.code !== 0) {
5300
5481
  const already = /already loaded|service already bootstrapped/i.test(bootstrap.stderr);
@@ -5320,9 +5501,9 @@ function createLaunchdManager(scope) {
5320
5501
  }
5321
5502
  async function uninstall() {
5322
5503
  run2("launchctl", ["bootout", serviceTarget(scope)]);
5323
- if (fs27.existsSync(file)) {
5504
+ if (fs30.existsSync(file)) {
5324
5505
  run2("launchctl", ["unload", file]);
5325
- fs27.unlinkSync(file);
5506
+ fs30.unlinkSync(file);
5326
5507
  }
5327
5508
  }
5328
5509
  async function start() {
@@ -5341,7 +5522,7 @@ function createLaunchdManager(scope) {
5341
5522
  }
5342
5523
  }
5343
5524
  async function status() {
5344
- if (!fs27.existsSync(file)) return "absent";
5525
+ if (!fs30.existsSync(file)) return "absent";
5345
5526
  const printed = run2("launchctl", ["print", serviceTarget(scope)]);
5346
5527
  if (printed.code !== 0) return "installed";
5347
5528
  const out = printed.stdout;
@@ -5364,8 +5545,8 @@ function createLaunchdManager(scope) {
5364
5545
 
5365
5546
  // src/core/daemon-service/systemd.ts
5366
5547
  import { spawnSync as spawnSync7 } from "child_process";
5367
- import fs28 from "fs";
5368
- import path27 from "path";
5548
+ import fs31 from "fs";
5549
+ import path29 from "path";
5369
5550
  function shellEscape(s) {
5370
5551
  if (!/[\s"\\$]/.test(s)) return s;
5371
5552
  return `"${s.replace(/[\\"]/g, (m) => `\\${m}`)}"`;
@@ -5415,8 +5596,8 @@ function createSystemdManager(scope) {
5415
5596
  const unitName = `${serviceLabel()}.service`;
5416
5597
  async function install() {
5417
5598
  const inv = resolveTask0Invocation();
5418
- fs28.mkdirSync(logDir(), { recursive: true });
5419
- fs28.mkdirSync(path27.dirname(file), { recursive: true });
5599
+ fs31.mkdirSync(logDir(), { recursive: true });
5600
+ fs31.mkdirSync(path29.dirname(file), { recursive: true });
5420
5601
  const body = renderUnit({
5421
5602
  node: inv.node,
5422
5603
  main: inv.main,
@@ -5426,7 +5607,7 @@ function createSystemdManager(scope) {
5426
5607
  scope,
5427
5608
  task0Env: collectTask0Env2()
5428
5609
  });
5429
- fs28.writeFileSync(file, body, { mode: 420 });
5610
+ fs31.writeFileSync(file, body, { mode: 420 });
5430
5611
  const reload = run3("systemctl", [...scopeFlag(scope), "daemon-reload"]);
5431
5612
  if (reload.code !== 0) {
5432
5613
  throw new Error(`systemctl daemon-reload failed: ${reload.stderr}`);
@@ -5435,8 +5616,8 @@ function createSystemdManager(scope) {
5435
5616
  }
5436
5617
  async function uninstall() {
5437
5618
  run3("systemctl", [...scopeFlag(scope), "disable", "--now", unitName]);
5438
- if (fs28.existsSync(file)) {
5439
- fs28.unlinkSync(file);
5619
+ if (fs31.existsSync(file)) {
5620
+ fs31.unlinkSync(file);
5440
5621
  }
5441
5622
  run3("systemctl", [...scopeFlag(scope), "daemon-reload"]);
5442
5623
  }
@@ -5453,7 +5634,7 @@ function createSystemdManager(scope) {
5453
5634
  }
5454
5635
  }
5455
5636
  async function status() {
5456
- if (!fs28.existsSync(file)) return "absent";
5637
+ if (!fs31.existsSync(file)) return "absent";
5457
5638
  const res = run3("systemctl", [...scopeFlag(scope), "is-active", unitName]);
5458
5639
  const out = res.stdout.trim();
5459
5640
  if (out === "active") return "running";
@@ -5489,14 +5670,14 @@ function getServiceManager(scope) {
5489
5670
  // src/core/cli-version.ts
5490
5671
  import { readFileSync } from "fs";
5491
5672
  import { fileURLToPath as fileURLToPath2 } from "url";
5492
- import path28 from "path";
5673
+ import path30 from "path";
5493
5674
  var cached2 = null;
5494
5675
  function readCliVersion() {
5495
5676
  if (cached2 !== null) return cached2;
5496
- const here = path28.dirname(fileURLToPath2(import.meta.url));
5677
+ const here = path30.dirname(fileURLToPath2(import.meta.url));
5497
5678
  const candidates = [
5498
- path28.resolve(here, "..", "package.json"),
5499
- path28.resolve(here, "..", "..", "package.json")
5679
+ path30.resolve(here, "..", "package.json"),
5680
+ path30.resolve(here, "..", "..", "package.json")
5500
5681
  ];
5501
5682
  for (const candidate of candidates) {
5502
5683
  try {
@@ -5512,73 +5693,6 @@ function readCliVersion() {
5512
5693
  return cached2;
5513
5694
  }
5514
5695
 
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
5696
  // src/commands/daemon.ts
5583
5697
  async function dispatchRpc(ws, id, method, params) {
5584
5698
  const handler = rpcHandlers[method];
@@ -5681,10 +5795,20 @@ function fail6(message, code = 1) {
5681
5795
  function loadRequiredIdentity() {
5682
5796
  const identity = readDaemonIdentity();
5683
5797
  if (!identity) {
5684
- fail6(`No daemon identity at ${daemonConfigPath()}. Run \`task0 daemon register --server <url>\` first.`);
5798
+ fail6(`No daemon identity at ${daemonConfigPath()}. Run \`task0 daemon register\` first (set api_url with \`task0 profile set api_url <url>\` if it isn't already configured).`);
5685
5799
  }
5686
5800
  return identity;
5687
5801
  }
5802
+ function resolveTargetServerUrl() {
5803
+ const url = process.env.TASK0_API_URL?.trim();
5804
+ if (url) return url.replace(/\/$/, "");
5805
+ fail6(
5806
+ `No server URL configured for the active profile.
5807
+ Set it with:
5808
+ task0 profile set api_url <url>
5809
+ \u2026or export TASK0_API_URL for a one-off override.`
5810
+ );
5811
+ }
5688
5812
  function serverBase(identity) {
5689
5813
  if (identity) return identity.server_url.replace(/\/$/, "");
5690
5814
  return (process.env.TASK0_API_URL || "http://127.0.0.1:4318").replace(/\/$/, "");
@@ -5722,7 +5846,7 @@ function rerunArgv() {
5722
5846
  return ["task0", ...process.argv.slice(2)].join(" ");
5723
5847
  }
5724
5848
  var daemonCmd = new Command19("daemon").description("Manage this host as a task0 daemon registered with a central server");
5725
- daemonCmd.command("register").description("Register this host with a central server, install the autostart service, and start it").requiredOption("-s, --server <url>", "Central server URL (e.g. https://central.example.com:4318)").option("-n, --name <name>", "Display name for this daemon (defaults to hostname)").option("-t, --token <token>", "API token (apit_...) for registration. Falls back to TASK0_API_TOKEN, then admin token.").option("--force", "Overwrite existing identity if present").option("--system", "Install at the system layer (LaunchDaemons / /etc/systemd/system, requires sudo)").option("--no-install", "Skip installing the autostart service unit").option("--no-start", "Install the service unit but do not start it now").action(async (opts) => {
5849
+ daemonCmd.command("register").description("Register this host with the server configured in the active profile, install the autostart service, and start it").option("-n, --name <name>", "Display name for this daemon (defaults to hostname)").option("-t, --token <token>", "API token (apit_...) for registration. Falls back to TASK0_API_TOKEN, then admin token.").option("--force", "Overwrite existing identity if present").option("--system", "Install at the system layer (LaunchDaemons / /etc/systemd/system, requires sudo)").option("--no-install", "Skip installing the autostart service unit").option("--no-start", "Install the service unit but do not start it now").action(async (opts) => {
5726
5850
  const scope = opts.system ? "system" : "user";
5727
5851
  if (opts.install) {
5728
5852
  requireRootIfSystem(scope, rerunArgv());
@@ -5731,7 +5855,7 @@ daemonCmd.command("register").description("Register this host with a central ser
5731
5855
  if (existing && !opts.force) {
5732
5856
  fail6(`Already registered as ${existing.daemon_id}. Pass --force to re-register.`);
5733
5857
  }
5734
- const base = opts.server.replace(/\/$/, "");
5858
+ const base = resolveTargetServerUrl();
5735
5859
  let authHeader;
5736
5860
  try {
5737
5861
  const choice = pickRegisterAuth(opts.token);
@@ -5836,12 +5960,6 @@ daemonCmd.command("stop").description("Stop the autostart service via launchctl
5836
5960
  });
5837
5961
  daemonCmd.command("run").description("Run the daemon WebSocket loop in foreground (invoked by the service unit; useful for debugging)").action(async () => {
5838
5962
  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
5963
  const wsUrl = identity.server_url.replace(/^http/, "ws").replace(/\/$/, "") + "/ws/daemon";
5846
5964
  console.log(chalk19.green(`Starting daemon ${identity.daemon_id} \u2192 ${wsUrl}`));
5847
5965
  let reconnectDelay = 1e3;
@@ -6055,8 +6173,8 @@ function fail7(message, code = 1) {
6055
6173
  console.error(chalk20.red(message));
6056
6174
  process.exit(code);
6057
6175
  }
6058
- async function callServer(path31, init = {}) {
6059
- const url = `${serverBase2()}${path31}`;
6176
+ async function callServer(path32, init = {}) {
6177
+ const url = `${serverBase2()}${path32}`;
6060
6178
  let auth;
6061
6179
  try {
6062
6180
  auth = adminAuthHeader();
@@ -6560,159 +6678,219 @@ automation.command("runs <id>").description("List recent runs for an automation"
6560
6678
  });
6561
6679
 
6562
6680
  // src/commands/profile.ts
6563
- import fs30 from "fs";
6564
- import os7 from "os";
6565
- import path30 from "path";
6681
+ init_node();
6682
+ import fs32 from "fs";
6683
+ import path31 from "path";
6566
6684
  import { Command as Command23 } from "commander";
6567
6685
  import chalk23 from "chalk";
6568
- var VALID_NAME = /^[a-zA-Z0-9_-]+$/;
6686
+ import yaml10 from "js-yaml";
6687
+ var PROFILE_SCALAR_KEYS = ["api_url"];
6688
+ function isProfileScalarKey(key) {
6689
+ return PROFILE_SCALAR_KEYS.includes(key);
6690
+ }
6569
6691
  function fail9(msg) {
6570
6692
  console.error(chalk23.red(msg));
6571
6693
  process.exit(1);
6572
6694
  }
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;
6695
+ function readDaemonAt(dir) {
6696
+ const file = path31.join(dir, "daemon.json");
6697
+ if (!fs32.existsSync(file)) return null;
6579
6698
  try {
6580
- return JSON.parse(fs30.readFileSync(file, "utf-8"));
6699
+ return JSON.parse(fs32.readFileSync(file, "utf-8"));
6581
6700
  } catch {
6582
6701
  return null;
6583
6702
  }
6584
6703
  }
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);
6704
+ function readProfileApiUrl(name) {
6705
+ const file = path31.join(profileDir(name), "config.yml");
6706
+ if (!fs32.existsSync(file)) return void 0;
6707
+ try {
6708
+ const data = yaml10.load(fs32.readFileSync(file, "utf-8"));
6709
+ return data?.api_url;
6710
+ } catch {
6711
+ return void 0;
6712
+ }
6713
+ }
6714
+ var profile2 = new Command23("profile").description("Manage CLI profiles (directories under ~/.task0/profiles/)");
6715
+ profile2.command("list").description("List configured profiles (one directory per profile)").action(() => {
6716
+ const names = listProfileNames();
6717
+ const current = currentProfileName();
6590
6718
  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()})`));
6719
+ console.log("No profiles configured.");
6593
6720
  return;
6594
6721
  }
6595
6722
  for (const name of names) {
6596
- const entry = profiles[name];
6597
6723
  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."));
6724
+ const apiUrl = readProfileApiUrl(name);
6725
+ const url = apiUrl ? chalk23.dim(` \u2192 ${apiUrl}`) : "";
6726
+ console.log(`${marker}${name}${url}`);
6603
6727
  }
6604
6728
  });
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}.`);
6729
+ 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) => {
6730
+ if (!isValidProfileName(name)) {
6731
+ fail9(`Invalid profile name "${name}". Must match /^[a-zA-Z0-9_-]+$/.`);
6608
6732
  }
6609
- const existing = getProfile(name);
6610
- if (existing) {
6611
- fail9(`Profile "${name}" already exists.`);
6612
- }
6613
- const absHome = path30.resolve(opts.task0Home);
6614
- const profiles = listProfiles();
6615
- for (const [otherName, entry2] of Object.entries(profiles)) {
6616
- if (path30.resolve(entry2.task0_home) === absHome) {
6617
- fail9(`Profile "${otherName}" already uses task0_home "${absHome}". Two profiles cannot share a task0_home (service labels collide).`);
6618
- }
6733
+ if (name === DEFAULT_PROFILE_NAME) {
6734
+ fail9(`"${DEFAULT_PROFILE_NAME}" is reserved; it always exists.`);
6619
6735
  }
6620
- const parent = path30.dirname(absHome);
6621
- if (!fs30.existsSync(parent)) {
6622
- fail9(`Parent directory does not exist: ${parent}`);
6736
+ const dir = profileDir(name);
6737
+ if (fs32.existsSync(dir)) {
6738
+ fail9(`Profile "${name}" already exists at ${dir}.`);
6623
6739
  }
6740
+ fs32.mkdirSync(dir, { recursive: true });
6741
+ const prev = process.env.TASK0_PROFILE;
6742
+ process.env.TASK0_PROFILE = name;
6624
6743
  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.`));
6744
+ const config = loadConfig();
6745
+ if (opts.apiUrl) config.api_url = opts.apiUrl;
6746
+ saveConfig(config);
6747
+ } finally {
6748
+ if (prev === void 0) delete process.env.TASK0_PROFILE;
6749
+ else process.env.TASK0_PROFILE = prev;
6650
6750
  }
6651
6751
  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}`);
6752
+ console.log(` dir: ${dir}`);
6753
+ if (opts.apiUrl) console.log(` api_url: ${opts.apiUrl}`);
6654
6754
  console.log(chalk23.dim(`Use \`task0 profile use ${name}\` to activate.`));
6655
6755
  });
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) {
6756
+ 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) => {
6757
+ if (!isValidProfileName(name)) {
6758
+ fail9(`Invalid profile name "${name}".`);
6759
+ }
6760
+ if (name === DEFAULT_PROFILE_NAME) {
6761
+ fail9(`"${DEFAULT_PROFILE_NAME}" cannot be removed.`);
6762
+ }
6763
+ const dir = profileDir(name);
6764
+ if (!fs32.existsSync(dir)) {
6659
6765
  fail9(`Profile "${name}" not found.`);
6660
6766
  }
6661
- const current = getCurrentProfileName();
6767
+ const current = currentProfileName();
6662
6768
  if (current === name && !opts.force) {
6663
- fail9(`Profile "${name}" is current. Re-run with --force to remove it (this clears current_profile).`);
6769
+ fail9(`Profile "${name}" is current. Re-run with --force to remove it.`);
6664
6770
  }
6665
- removeProfile(name);
6666
- console.log(chalk23.green(`Removed profile "${name}".`));
6771
+ fs32.rmSync(dir, { recursive: true, force: true });
6667
6772
  if (current === name) {
6668
- console.log(chalk23.dim("current_profile cleared. Use `task0 profile use <name>` to pick another."));
6773
+ writeCurrentProfile(null);
6774
+ console.log(chalk23.dim('Current profile reset to "default".'));
6669
6775
  }
6670
- console.log(chalk23.dim(`Note: ${entry.task0_home} was not deleted.`));
6776
+ console.log(chalk23.green(`Removed profile "${name}".`));
6671
6777
  });
6672
6778
  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)"}.`);
6779
+ if (!isValidProfileName(name)) {
6780
+ fail9(`Invalid profile name "${name}".`);
6781
+ }
6782
+ if (!fs32.existsSync(profileDir(name))) {
6783
+ const names = listProfileNames();
6784
+ fail9(`Profile "${name}" not found. Available: ${names.join(", ")}.`);
6677
6785
  }
6678
- setCurrentProfile(name);
6786
+ writeCurrentProfile(name);
6679
6787
  console.log(chalk23.green(`Now using profile "${name}".`));
6680
6788
  });
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>`."));
6789
+ profile2.command("current").description('Print the current profile name (always succeeds; defaults to "default")').action(() => {
6790
+ console.log(currentProfileName());
6791
+ });
6792
+ profile2.command("set <key> <value>").description(`Set a config field on the active profile. Supported keys: ${PROFILE_SCALAR_KEYS.join(", ")}.`).action((key, value) => {
6793
+ if (!isProfileScalarKey(key)) {
6794
+ fail9(`Unknown key "${key}". Supported keys: ${PROFILE_SCALAR_KEYS.join(", ")}.`);
6795
+ }
6796
+ const config = loadConfig();
6797
+ config[key] = value;
6798
+ saveConfig(config);
6799
+ console.log(chalk23.green(`${currentProfileName()}.${key} = ${value}`));
6800
+ });
6801
+ profile2.command("get <key>").description(`Read a config field from the active profile. Supported keys: ${PROFILE_SCALAR_KEYS.join(", ")}.`).action((key) => {
6802
+ if (!isProfileScalarKey(key)) {
6803
+ fail9(`Unknown key "${key}". Supported keys: ${PROFILE_SCALAR_KEYS.join(", ")}.`);
6804
+ }
6805
+ const value = loadConfig()[key];
6806
+ if (typeof value !== "string" || value.length === 0) {
6685
6807
  process.exit(1);
6686
6808
  }
6687
- console.log(current);
6809
+ console.log(value);
6688
6810
  });
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.");
6811
+ profile2.command("show [name]").description("Show a profile's configuration and daemon registration").action((name) => {
6812
+ const target = name ?? currentProfileName();
6813
+ if (!isValidProfileName(target)) {
6814
+ fail9(`Invalid profile name "${target}".`);
6693
6815
  }
6694
- const entry = getProfile(target);
6695
- if (!entry) {
6816
+ const dir = profileDir(target);
6817
+ if (!fs32.existsSync(dir)) {
6696
6818
  fail9(`Profile "${target}" not found.`);
6697
6819
  }
6698
- const current = getCurrentProfileName();
6820
+ const current = currentProfileName();
6821
+ const apiUrl = readProfileApiUrl(target);
6822
+ const identity = readDaemonAt(dir);
6823
+ const envOverride = target === current ? getActiveProfile()?.envApiUrlOverride : void 0;
6824
+ const effective = envOverride ?? apiUrl;
6699
6825
  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);
6826
+ console.log(` dir: ${dir}`);
6827
+ console.log(` config: ${path31.join(dir, "config.yml")}`);
6828
+ if (envOverride && apiUrl && envOverride !== apiUrl) {
6829
+ console.log(` api_url: ${envOverride} ${chalk23.dim("(TASK0_API_URL env, overrides profile)")}`);
6830
+ console.log(` ${chalk23.dim(`profile: ${apiUrl}`)}`);
6831
+ } else if (envOverride && !apiUrl) {
6832
+ console.log(` api_url: ${envOverride} ${chalk23.dim("(TASK0_API_URL env; profile config.yml is unset)")}`);
6833
+ } else if (apiUrl) {
6834
+ console.log(` api_url: ${apiUrl} ${chalk23.dim("(profile config.yml)")}`);
6835
+ } else {
6836
+ console.log(` api_url: ${chalk23.dim("(unset \u2014 run `task0 profile set api_url <url>`)")}`);
6837
+ }
6704
6838
  if (!identity) {
6705
6839
  console.log(chalk23.dim(" daemon.json: (not registered yet)"));
6706
6840
  return;
6707
6841
  }
6708
6842
  console.log(` daemon_id: ${identity.daemon_id ?? chalk23.dim("(missing)")}`);
6709
6843
  console.log(` daemon.server_url: ${identity.server_url ?? chalk23.dim("(missing)")}`);
6710
- if (entry.api_url && identity.server_url && entry.api_url !== identity.server_url) {
6711
- console.log(chalk23.yellow(` warn: profile.api_url and daemon.json.server_url disagree.`));
6712
- console.log(chalk23.dim(` Re-register the daemon to align: task0 --profile ${target} daemon register --server ${entry.api_url}`));
6844
+ if (effective && identity.server_url && effective !== identity.server_url) {
6845
+ console.log(chalk23.yellow(` warn: effective api_url and daemon.json.server_url disagree.`));
6846
+ console.log(chalk23.dim(` Re-register the daemon to align: task0 --profile ${target} daemon register --force`));
6713
6847
  }
6714
6848
  });
6715
6849
 
6850
+ // src/commands/migrate-layout.ts
6851
+ import { Command as Command24 } from "commander";
6852
+ import chalk24 from "chalk";
6853
+ 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) => {
6854
+ if (opts.force && !opts.dryRun) {
6855
+ const removed = resetMigrationMarker();
6856
+ if (removed) console.log(chalk24.dim("Reset migration marker."));
6857
+ }
6858
+ const result = migrateCliLayout({ dryRun: opts.dryRun });
6859
+ if (result.alreadyMigrated) {
6860
+ console.log(chalk24.dim("Already migrated \u2014 nothing to do."));
6861
+ console.log(chalk24.dim("Pass --force to rerun."));
6862
+ return;
6863
+ }
6864
+ if (result.steps.length === 0) {
6865
+ console.log("No legacy files found.");
6866
+ return;
6867
+ }
6868
+ if (opts.dryRun) {
6869
+ console.log(chalk24.bold("Dry run \u2014 no changes written:"));
6870
+ } else {
6871
+ console.log(chalk24.bold(`Migrated ${result.steps.length} item(s):`));
6872
+ }
6873
+ for (const step of result.steps) {
6874
+ console.log(` ${formatStep(step)}`);
6875
+ }
6876
+ });
6877
+ function formatStep(step) {
6878
+ switch (step.kind) {
6879
+ case "mv":
6880
+ return `mv ${step.from} \u2192 ${step.to}${step.detail ? ` (${step.detail})` : ""}`;
6881
+ case "mkdir":
6882
+ return `mkdir ${step.to}`;
6883
+ case "write":
6884
+ return `write ${step.to}${step.detail ? ` (${step.detail})` : ""}`;
6885
+ case "extract":
6886
+ return `update ${step.from}${step.detail ? ` (${step.detail})` : ""}`;
6887
+ case "skip":
6888
+ return `skip ${step.from ?? step.to}${step.detail ? ` (${step.detail})` : ""}`;
6889
+ case "note":
6890
+ return `note ${step.detail ?? ""}`;
6891
+ }
6892
+ }
6893
+
6716
6894
  // src/core/error-capture.ts
6717
6895
  init_node();
6718
6896
  var DEFAULT_KEEP = 50;
@@ -6811,7 +6989,7 @@ function captureTopLevel(err, options) {
6811
6989
 
6812
6990
  // src/main.ts
6813
6991
  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)");
6992
+ 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
6993
  program.addCommand(source);
6816
6994
  program.addCommand(project);
6817
6995
  program.addCommand(task);
@@ -6830,12 +7008,23 @@ program.addCommand(userCmd);
6830
7008
  program.addCommand(error);
6831
7009
  program.addCommand(automation);
6832
7010
  program.addCommand(profile2);
7011
+ program.addCommand(migrateLayout);
6833
7012
  async function main() {
7013
+ try {
7014
+ const result = migrateCliLayout();
7015
+ if (result.ran && process.env.TASK0_LOG_MIGRATION !== "off") {
7016
+ console.error(chalk25.dim(`task0: migrated CLI layout (${result.steps.length} step(s))`));
7017
+ }
7018
+ } catch (err) {
7019
+ console.error(chalk25.yellow(
7020
+ `task0: layout migration failed: ${err instanceof Error ? err.message : String(err)}. Continuing.`
7021
+ ));
7022
+ }
6834
7023
  try {
6835
7024
  activateProfile(process.argv);
6836
7025
  } catch (err) {
6837
7026
  if (err instanceof ProfileNotFoundError) {
6838
- console.error(chalk24.red(err.message));
7027
+ console.error(chalk25.red(err.message));
6839
7028
  process.exit(1);
6840
7029
  }
6841
7030
  throw err;