@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.
- package/README.md +14 -6
- package/dist/main.js +808 -619
- 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
|
|
587
|
-
import
|
|
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
|
|
675
|
+
return path6.join(profileDir(), "errors");
|
|
599
676
|
}
|
|
600
677
|
function createErrorReportId() {
|
|
601
678
|
return `err_${crypto.randomBytes(4).toString("hex")}`;
|
|
@@ -696,13 +773,13 @@ function buildErrorReport(input) {
|
|
|
696
773
|
function writeErrorReportSync(report, rootOverride) {
|
|
697
774
|
const root = rootOverride ?? errorsRoot();
|
|
698
775
|
const dirName = errorReportDirName(new Date(report.captured_at), report.id);
|
|
699
|
-
const dir =
|
|
700
|
-
|
|
701
|
-
const finalPath =
|
|
702
|
-
const tmpPath =
|
|
776
|
+
const dir = path6.join(root, dirName);
|
|
777
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
778
|
+
const finalPath = path6.join(dir, REPORT_FILENAME);
|
|
779
|
+
const tmpPath = path6.join(dir, TMP_FILENAME);
|
|
703
780
|
const json = JSON.stringify(report, null, 2);
|
|
704
|
-
|
|
705
|
-
|
|
781
|
+
fs6.writeFileSync(tmpPath, json, "utf-8");
|
|
782
|
+
fs6.renameSync(tmpPath, finalPath);
|
|
706
783
|
return { dir, path: finalPath };
|
|
707
784
|
}
|
|
708
785
|
function parseReportDir(name) {
|
|
@@ -719,7 +796,7 @@ function listErrorReports(rootOverride) {
|
|
|
719
796
|
};
|
|
720
797
|
let entries;
|
|
721
798
|
try {
|
|
722
|
-
entries =
|
|
799
|
+
entries = fs6.readdirSync(root, { withFileTypes: true });
|
|
723
800
|
} catch (err) {
|
|
724
801
|
if (err.code === "ENOENT")
|
|
725
802
|
return result;
|
|
@@ -731,11 +808,11 @@ function listErrorReports(rootOverride) {
|
|
|
731
808
|
const parsed = parseReportDir(entry.name);
|
|
732
809
|
if (!parsed)
|
|
733
810
|
continue;
|
|
734
|
-
const dir =
|
|
735
|
-
const file =
|
|
811
|
+
const dir = path6.join(root, entry.name);
|
|
812
|
+
const file = path6.join(dir, REPORT_FILENAME);
|
|
736
813
|
let raw;
|
|
737
814
|
try {
|
|
738
|
-
raw =
|
|
815
|
+
raw = fs6.readFileSync(file, "utf-8");
|
|
739
816
|
} catch {
|
|
740
817
|
result.skipped.unreadable += 1;
|
|
741
818
|
continue;
|
|
@@ -753,7 +830,7 @@ function listErrorReports(rootOverride) {
|
|
|
753
830
|
}
|
|
754
831
|
let size = 0;
|
|
755
832
|
try {
|
|
756
|
-
size =
|
|
833
|
+
size = fs6.statSync(file).size;
|
|
757
834
|
} catch {
|
|
758
835
|
}
|
|
759
836
|
result.reports.push({
|
|
@@ -788,7 +865,7 @@ function resolveErrorReport(query, rootOverride) {
|
|
|
788
865
|
return { kind: "miss", query };
|
|
789
866
|
}
|
|
790
867
|
function readErrorReport(summary) {
|
|
791
|
-
const raw =
|
|
868
|
+
const raw = fs6.readFileSync(summary.path, "utf-8");
|
|
792
869
|
return JSON.parse(raw);
|
|
793
870
|
}
|
|
794
871
|
function pruneErrorReports(opts, rootOverride) {
|
|
@@ -826,7 +903,7 @@ function pruneErrorReports(opts, rootOverride) {
|
|
|
826
903
|
}
|
|
827
904
|
function removeReportDir(dir) {
|
|
828
905
|
try {
|
|
829
|
-
|
|
906
|
+
fs6.rmSync(dir, { recursive: true, force: true });
|
|
830
907
|
return true;
|
|
831
908
|
} catch {
|
|
832
909
|
return false;
|
|
@@ -837,6 +914,7 @@ var init_error_reports = __esm({
|
|
|
837
914
|
"../../packages/shared/dist/node/error-reports.js"() {
|
|
838
915
|
"use strict";
|
|
839
916
|
init_error_report();
|
|
917
|
+
init_paths();
|
|
840
918
|
init_redact();
|
|
841
919
|
REPORT_FILENAME = "report.json";
|
|
842
920
|
TMP_FILENAME = "report.json.tmp";
|
|
@@ -844,9 +922,9 @@ var init_error_reports = __esm({
|
|
|
844
922
|
});
|
|
845
923
|
|
|
846
924
|
// ../../packages/shared/dist/node/file-lock.js
|
|
847
|
-
import
|
|
925
|
+
import fs7 from "fs";
|
|
848
926
|
import os3 from "os";
|
|
849
|
-
import
|
|
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
|
|
866
|
-
import
|
|
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 (!
|
|
962
|
+
if (!fs8.existsSync(filePath))
|
|
885
963
|
return null;
|
|
886
|
-
return
|
|
964
|
+
return fs8.readFileSync(filePath, "utf-8");
|
|
887
965
|
} catch {
|
|
888
966
|
return null;
|
|
889
967
|
}
|
|
@@ -891,13 +969,13 @@ function readTextIfExists(filePath) {
|
|
|
891
969
|
function getSymlinkInfo(...candidatePaths) {
|
|
892
970
|
for (const candidatePath of candidatePaths) {
|
|
893
971
|
try {
|
|
894
|
-
const stat =
|
|
972
|
+
const stat = fs8.lstatSync(candidatePath);
|
|
895
973
|
if (!stat.isSymbolicLink())
|
|
896
974
|
continue;
|
|
897
|
-
const target =
|
|
975
|
+
const target = fs8.readlinkSync(candidatePath);
|
|
898
976
|
return {
|
|
899
977
|
isSymlink: true,
|
|
900
|
-
symlinkTarget:
|
|
978
|
+
symlinkTarget: path8.isAbsolute(target) ? target : path8.resolve(path8.dirname(candidatePath), target)
|
|
901
979
|
};
|
|
902
980
|
} catch {
|
|
903
981
|
}
|
|
@@ -930,7 +1008,7 @@ function normalizeBoolean(value) {
|
|
|
930
1008
|
function sortSkills(skills) {
|
|
931
1009
|
return skills.sort((a, b) => AGENT_ORDER[a.agent] - AGENT_ORDER[b.agent] || SCOPE_ORDER[a.scope] - SCOPE_ORDER[b.scope] || KIND_ORDER[a.kind] - KIND_ORDER[b.kind] || a.name.localeCompare(b.name) || a.filePath.localeCompare(b.filePath));
|
|
932
1010
|
}
|
|
933
|
-
function pushInstructionIfExists(skills, agent2, scope, filePath, name =
|
|
1011
|
+
function pushInstructionIfExists(skills, agent2, scope, filePath, name = path8.basename(filePath), description2 = "", kind = "instruction") {
|
|
934
1012
|
const raw = readTextIfExists(filePath);
|
|
935
1013
|
if (raw === null)
|
|
936
1014
|
return;
|
|
@@ -947,17 +1025,17 @@ function pushInstructionIfExists(skills, agent2, scope, filePath, name = path7.b
|
|
|
947
1025
|
function scanClaudeSkillDir(skills, skillsDir, scope) {
|
|
948
1026
|
let entries = [];
|
|
949
1027
|
try {
|
|
950
|
-
if (!
|
|
1028
|
+
if (!fs8.existsSync(skillsDir))
|
|
951
1029
|
return;
|
|
952
|
-
entries =
|
|
1030
|
+
entries = fs8.readdirSync(skillsDir, { withFileTypes: true });
|
|
953
1031
|
} catch {
|
|
954
1032
|
return;
|
|
955
1033
|
}
|
|
956
1034
|
for (const entry of entries) {
|
|
957
1035
|
if (!entry.isDirectory() && !entry.isSymbolicLink())
|
|
958
1036
|
continue;
|
|
959
|
-
const skillDirPath =
|
|
960
|
-
const skillFilePath =
|
|
1037
|
+
const skillDirPath = path8.join(skillsDir, entry.name);
|
|
1038
|
+
const skillFilePath = path8.join(skillDirPath, "SKILL.md");
|
|
961
1039
|
const raw = readTextIfExists(skillFilePath);
|
|
962
1040
|
if (raw === null)
|
|
963
1041
|
continue;
|
|
@@ -978,7 +1056,7 @@ function scanCursorRuleFile(skills, filePath, scope) {
|
|
|
978
1056
|
if (raw === null)
|
|
979
1057
|
return;
|
|
980
1058
|
const frontmatter = extractFrontmatter(raw);
|
|
981
|
-
const baseName =
|
|
1059
|
+
const baseName = path8.basename(filePath, ".mdc");
|
|
982
1060
|
skills.push({
|
|
983
1061
|
agent: "cursor",
|
|
984
1062
|
scope,
|
|
@@ -994,9 +1072,9 @@ function scanCursorRuleFile(skills, filePath, scope) {
|
|
|
994
1072
|
function scanCursorRulesDir(skills, rulesDir, scope) {
|
|
995
1073
|
let entries = [];
|
|
996
1074
|
try {
|
|
997
|
-
if (!
|
|
1075
|
+
if (!fs8.existsSync(rulesDir))
|
|
998
1076
|
return;
|
|
999
|
-
entries =
|
|
1077
|
+
entries = fs8.readdirSync(rulesDir, { withFileTypes: true });
|
|
1000
1078
|
} catch {
|
|
1001
1079
|
return;
|
|
1002
1080
|
}
|
|
@@ -1005,26 +1083,26 @@ function scanCursorRulesDir(skills, rulesDir, scope) {
|
|
|
1005
1083
|
continue;
|
|
1006
1084
|
if (!entry.isFile() && !entry.isSymbolicLink())
|
|
1007
1085
|
continue;
|
|
1008
|
-
scanCursorRuleFile(skills,
|
|
1086
|
+
scanCursorRuleFile(skills, path8.join(rulesDir, entry.name), scope);
|
|
1009
1087
|
}
|
|
1010
1088
|
}
|
|
1011
1089
|
function getProjectAgentSkills(projectPath) {
|
|
1012
|
-
const absProjectPath =
|
|
1090
|
+
const absProjectPath = path8.resolve(projectPath);
|
|
1013
1091
|
let projectStat;
|
|
1014
1092
|
try {
|
|
1015
|
-
projectStat =
|
|
1093
|
+
projectStat = fs8.statSync(absProjectPath);
|
|
1016
1094
|
} catch {
|
|
1017
1095
|
return [];
|
|
1018
1096
|
}
|
|
1019
1097
|
if (!projectStat.isDirectory())
|
|
1020
1098
|
return [];
|
|
1021
1099
|
const skills = [];
|
|
1022
|
-
scanClaudeSkillDir(skills,
|
|
1023
|
-
pushInstructionIfExists(skills, "claude_code", "project",
|
|
1024
|
-
pushInstructionIfExists(skills, "claude_code", "project",
|
|
1025
|
-
pushInstructionIfExists(skills, "codex", "project",
|
|
1026
|
-
scanCursorRulesDir(skills,
|
|
1027
|
-
pushInstructionIfExists(skills, "cursor", "project",
|
|
1100
|
+
scanClaudeSkillDir(skills, path8.join(absProjectPath, ".claude", "skills"), "project");
|
|
1101
|
+
pushInstructionIfExists(skills, "claude_code", "project", path8.join(absProjectPath, "CLAUDE.md"));
|
|
1102
|
+
pushInstructionIfExists(skills, "claude_code", "project", path8.join(absProjectPath, "AGENTS.md"));
|
|
1103
|
+
pushInstructionIfExists(skills, "codex", "project", path8.join(absProjectPath, "AGENTS.md"));
|
|
1104
|
+
scanCursorRulesDir(skills, path8.join(absProjectPath, ".cursor", "rules"), "project");
|
|
1105
|
+
pushInstructionIfExists(skills, "cursor", "project", path8.join(absProjectPath, ".cursorrules"), "Legacy Cursor Rules", "", "rule");
|
|
1028
1106
|
return sortSkills(skills);
|
|
1029
1107
|
}
|
|
1030
1108
|
function getGlobalAgentSkills() {
|
|
@@ -1032,8 +1110,8 @@ function getGlobalAgentSkills() {
|
|
|
1032
1110
|
if (!homeDir)
|
|
1033
1111
|
return [];
|
|
1034
1112
|
const skills = [];
|
|
1035
|
-
scanClaudeSkillDir(skills,
|
|
1036
|
-
scanCursorRulesDir(skills,
|
|
1113
|
+
scanClaudeSkillDir(skills, path8.join(homeDir, ".claude", "skills"), "global");
|
|
1114
|
+
scanCursorRulesDir(skills, path8.join(homeDir, ".cursor", "rules"), "global");
|
|
1037
1115
|
return sortSkills(skills);
|
|
1038
1116
|
}
|
|
1039
1117
|
var AGENT_ORDER, SCOPE_ORDER, KIND_ORDER;
|
|
@@ -1065,6 +1143,7 @@ var init_node = __esm({
|
|
|
1065
1143
|
init_scanner();
|
|
1066
1144
|
init_open_questions();
|
|
1067
1145
|
init_task_state();
|
|
1146
|
+
init_paths();
|
|
1068
1147
|
init_error_reports();
|
|
1069
1148
|
init_redact();
|
|
1070
1149
|
init_file_lock();
|
|
@@ -1089,7 +1168,7 @@ __export(task_state_exports, {
|
|
|
1089
1168
|
withTaskYamlLock: () => withTaskYamlLock,
|
|
1090
1169
|
writeTaskYaml: () => writeTaskYaml
|
|
1091
1170
|
});
|
|
1092
|
-
import
|
|
1171
|
+
import fs15 from "fs";
|
|
1093
1172
|
function readWorkflow(taskYml) {
|
|
1094
1173
|
return readTaskWorkflow(taskYml);
|
|
1095
1174
|
}
|
|
@@ -1098,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 =
|
|
1180
|
+
const entries = fs15.readdirSync(taskDir);
|
|
1102
1181
|
const indices = entries.map((name) => name.match(pattern)?.[1]).filter((v) => v != null).map(Number);
|
|
1103
1182
|
const next = indices.length > 0 ? Math.max(...indices) + 1 : 1;
|
|
1104
1183
|
return String(next).padStart(2, "0");
|
|
1105
1184
|
}
|
|
1106
1185
|
function latestArtifact(taskDir, pattern) {
|
|
1107
|
-
if (!
|
|
1108
|
-
const matches =
|
|
1186
|
+
if (!fs15.existsSync(taskDir)) return null;
|
|
1187
|
+
const matches = fs15.readdirSync(taskDir).filter((name) => pattern.test(name));
|
|
1109
1188
|
if (matches.length === 0) return null;
|
|
1110
1189
|
matches.sort();
|
|
1111
1190
|
return matches[matches.length - 1] || null;
|
|
@@ -1118,115 +1197,42 @@ var init_task_state2 = __esm({
|
|
|
1118
1197
|
});
|
|
1119
1198
|
|
|
1120
1199
|
// src/main.ts
|
|
1121
|
-
import { Command as
|
|
1122
|
-
import
|
|
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
|
|
1127
|
-
import
|
|
1128
|
-
import path8 from "path";
|
|
1209
|
+
import fs9 from "fs";
|
|
1210
|
+
import path9 from "path";
|
|
1129
1211
|
import yaml4 from "js-yaml";
|
|
1130
|
-
function
|
|
1131
|
-
return
|
|
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
|
|
1144
|
-
return
|
|
1215
|
+
function defaultConfig() {
|
|
1216
|
+
return { sources: [] };
|
|
1145
1217
|
}
|
|
1146
|
-
function
|
|
1147
|
-
|
|
1218
|
+
function loadConfig() {
|
|
1219
|
+
const file = configFile();
|
|
1220
|
+
if (!fs9.existsSync(file)) return defaultConfig();
|
|
1148
1221
|
try {
|
|
1149
|
-
const raw =
|
|
1150
|
-
const
|
|
1151
|
-
return
|
|
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
|
|
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
|
|
1188
|
-
|
|
1189
|
-
|
|
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
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
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
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
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
|
-
|
|
1315
|
-
|
|
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
|
|
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
|
|
1331
|
-
import
|
|
1535
|
+
import fs12 from "fs";
|
|
1536
|
+
import path11 from "path";
|
|
1332
1537
|
function tokenFile() {
|
|
1333
|
-
return
|
|
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 (
|
|
1355
|
-
const v =
|
|
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
|
|
1370
|
-
import
|
|
1371
|
-
function
|
|
1372
|
-
return
|
|
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
|
|
1580
|
+
return configFile2();
|
|
1379
1581
|
}
|
|
1380
1582
|
function readDaemonIdentity() {
|
|
1381
|
-
const file =
|
|
1382
|
-
if (!
|
|
1583
|
+
const file = configFile2();
|
|
1584
|
+
if (!fs13.existsSync(file)) return null;
|
|
1383
1585
|
try {
|
|
1384
|
-
const raw =
|
|
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 =
|
|
1392
|
-
|
|
1393
|
-
|
|
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
|
-
|
|
1597
|
+
fs13.chmodSync(file, 384);
|
|
1396
1598
|
} catch {
|
|
1397
1599
|
}
|
|
1398
1600
|
}
|
|
1399
1601
|
function clearDaemonIdentity() {
|
|
1400
|
-
const file =
|
|
1401
|
-
if (!
|
|
1402
|
-
|
|
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
|
|
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 =
|
|
1512
|
-
const name = opts.name ||
|
|
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
|
|
1611
|
-
import
|
|
1612
|
-
import
|
|
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 =
|
|
1651
|
-
if (
|
|
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
|
-
|
|
1657
|
-
const tasksDir =
|
|
1658
|
-
|
|
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
|
|
1668
|
-
import
|
|
1669
|
-
import
|
|
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: (
|
|
1707
|
-
post: (
|
|
1708
|
-
put: (
|
|
1709
|
-
patch: (
|
|
1710
|
-
del: (
|
|
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
|
|
1717
|
-
import
|
|
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 =
|
|
1769
|
-
return
|
|
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
|
-
|
|
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 =
|
|
1827
|
-
const issueFiles =
|
|
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 =
|
|
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 (!
|
|
1866
|
-
return
|
|
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
|
|
1881
|
-
import
|
|
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 =
|
|
1886
|
-
return
|
|
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
|
|
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
|
|
1990
|
-
if (opts.file) return
|
|
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
|
|
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
|
|
2100
|
-
if (opts.file) return
|
|
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 =
|
|
2150
|
-
if (!
|
|
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 =
|
|
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 =
|
|
2171
|
-
if (!
|
|
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 =
|
|
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 =
|
|
2181
|
-
if (!
|
|
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 =
|
|
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 =
|
|
2191
|
-
const taskYml =
|
|
2192
|
-
if (
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
2320
|
-
if (!
|
|
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 =
|
|
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 =
|
|
2330
|
-
const taskDir =
|
|
2331
|
-
if (!
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
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
|
|
2557
|
+
import path18 from "path";
|
|
2356
2558
|
import chalk9 from "chalk";
|
|
2357
|
-
var DASHBOARD_DIR =
|
|
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
|
|
2664
|
-
import
|
|
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
|
|
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(
|
|
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 =
|
|
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 =
|
|
2819
|
-
|
|
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 =
|
|
3000
|
+
const updated = yaml8.load(fs21.readFileSync(tmp, "utf-8"));
|
|
2824
3001
|
await api.put(`/api/runtime-profiles/${encodeURIComponent(ref)}`, updated);
|
|
2825
|
-
|
|
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
|
|
2846
|
-
import
|
|
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 =
|
|
2851
|
-
return
|
|
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 =
|
|
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 =
|
|
3026
|
-
const wrote =
|
|
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
|
|
3179
|
-
import
|
|
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 =
|
|
3366
|
+
const cwd = fs23.realpathSync(process.cwd());
|
|
3190
3367
|
for (const source2 of projects) {
|
|
3191
3368
|
let sourceAbs;
|
|
3192
3369
|
try {
|
|
3193
|
-
sourceAbs =
|
|
3370
|
+
sourceAbs = fs23.realpathSync(path21.resolve(source2.path));
|
|
3194
3371
|
} catch {
|
|
3195
|
-
sourceAbs =
|
|
3372
|
+
sourceAbs = path21.resolve(source2.path);
|
|
3196
3373
|
}
|
|
3197
|
-
if (cwd === sourceAbs || cwd.startsWith(sourceAbs +
|
|
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
|
|
3838
|
-
import
|
|
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 =
|
|
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 =
|
|
3947
|
-
const consolidatedPath =
|
|
3948
|
-
if (!
|
|
3949
|
-
if (!
|
|
3950
|
-
const issueMd =
|
|
3951
|
-
const consolidatedMd =
|
|
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
|
-
|
|
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 =
|
|
4002
|
-
if (
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
4319
|
-
import
|
|
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
|
|
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(
|
|
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(
|
|
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 =
|
|
4398
|
-
parsed =
|
|
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 =
|
|
4590
|
+
const tmp = path23.join(
|
|
4414
4591
|
process.env.TMPDIR || "/tmp",
|
|
4415
4592
|
`task0-agent-${result.agent.slug}-${Date.now()}.yml`
|
|
4416
4593
|
);
|
|
4417
|
-
|
|
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 =
|
|
4598
|
+
const updated = yaml9.load(fs25.readFileSync(tmp, "utf-8"));
|
|
4422
4599
|
await api.put(`/api/agents/${encodeURIComponent(ref)}`, updated);
|
|
4423
|
-
|
|
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(
|
|
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
|
|
4644
|
-
import
|
|
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
|
|
4649
|
-
import
|
|
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
|
|
4654
|
-
import
|
|
4830
|
+
import fs26 from "fs";
|
|
4831
|
+
import path24 from "path";
|
|
4655
4832
|
function agentRunRoot() {
|
|
4656
|
-
return process.env.TASK0_DAEMON_AGENT_RUN_DIR ||
|
|
4833
|
+
return process.env.TASK0_DAEMON_AGENT_RUN_DIR || path24.join(profileDir(), "agent-run");
|
|
4657
4834
|
}
|
|
4658
4835
|
function agentRunDir(runId) {
|
|
4659
|
-
return
|
|
4836
|
+
return path24.join(agentRunRoot(), runId);
|
|
4660
4837
|
}
|
|
4661
4838
|
function agentRunStatusPath(runId) {
|
|
4662
|
-
return
|
|
4839
|
+
return path24.join(agentRunDir(runId), "status.json");
|
|
4663
4840
|
}
|
|
4664
4841
|
function ensureAgentRunDir(runId) {
|
|
4665
4842
|
const dir = agentRunDir(runId);
|
|
4666
|
-
|
|
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 (!
|
|
4672
|
-
|
|
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 (!
|
|
4891
|
+
if (!fs27.existsSync(filePath)) return null;
|
|
4715
4892
|
try {
|
|
4716
|
-
const raw =
|
|
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 (!
|
|
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 =
|
|
4746
|
-
|
|
4922
|
+
const scriptPath = path25.join(runDir, "script.sh");
|
|
4923
|
+
fs27.writeFileSync(scriptPath, params.scriptContent, { mode: 493 });
|
|
4747
4924
|
if (params.promptContent !== void 0) {
|
|
4748
|
-
|
|
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
|
-
|
|
4931
|
+
fs27.writeFileSync(path25.join(runDir, name), content, "utf-8");
|
|
4755
4932
|
}
|
|
4756
|
-
|
|
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 === ".." ||
|
|
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 =
|
|
4844
|
-
const target =
|
|
4845
|
-
if (target !== root && !target.startsWith(root +
|
|
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 :
|
|
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 =
|
|
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 =
|
|
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 =
|
|
4924
|
-
if (!
|
|
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 =
|
|
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() ||
|
|
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 =
|
|
4981
|
-
const projectConfig = readYaml(
|
|
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 =
|
|
4986
|
-
const taskDir =
|
|
5162
|
+
const tasksRoot = path26.resolve(projectAbs, projectConfig.tasks_dir);
|
|
5163
|
+
const taskDir = path26.resolve(tasksRoot, taskId);
|
|
4987
5164
|
assertContained(tasksRoot, taskDir);
|
|
4988
|
-
if (!
|
|
5165
|
+
if (!fs28.existsSync(taskDir)) {
|
|
4989
5166
|
throw Object.assign(new Error(`task not found: ${taskId}`), { code: "not_found" });
|
|
4990
5167
|
}
|
|
4991
|
-
const taskYml =
|
|
4992
|
-
const
|
|
4993
|
-
if (!
|
|
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 =
|
|
4997
|
-
return { task_dir: taskDir, yaml:
|
|
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
|
|
5006
|
-
if (!
|
|
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 =
|
|
5015
|
-
const projectConfig = readYaml(
|
|
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 =
|
|
5020
|
-
const taskDir =
|
|
5196
|
+
const tasksRoot = path26.resolve(projectAbs, projectConfig.tasks_dir);
|
|
5197
|
+
const taskDir = path26.resolve(tasksRoot, taskId);
|
|
5021
5198
|
assertContained(tasksRoot, taskDir);
|
|
5022
|
-
if (
|
|
5199
|
+
if (fs28.existsSync(taskDir)) {
|
|
5023
5200
|
throw Object.assign(new Error(`task already exists: ${taskId}`), { code: "already_exists" });
|
|
5024
5201
|
}
|
|
5025
|
-
|
|
5026
|
-
writeYaml(
|
|
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
|
-
|
|
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
|
|
5044
|
-
if (!
|
|
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 =
|
|
5052
|
-
const projectConfig = readYaml(
|
|
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 =
|
|
5057
|
-
const taskDir =
|
|
5233
|
+
const tasksRoot = path26.resolve(projectAbs, projectConfig.tasks_dir);
|
|
5234
|
+
const taskDir = path26.resolve(tasksRoot, taskId);
|
|
5058
5235
|
assertContained(tasksRoot, taskDir);
|
|
5059
|
-
const taskYml =
|
|
5060
|
-
if (!
|
|
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,
|
|
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 =
|
|
5077
|
-
const projectConfig = readYaml(
|
|
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 =
|
|
5082
|
-
const taskDir =
|
|
5258
|
+
const tasksRoot = path26.resolve(projectAbs, projectConfig.tasks_dir);
|
|
5259
|
+
const taskDir = path26.resolve(tasksRoot, taskId);
|
|
5083
5260
|
assertContained(tasksRoot, taskDir);
|
|
5084
|
-
if (!
|
|
5261
|
+
if (!fs28.existsSync(taskDir)) {
|
|
5085
5262
|
throw Object.assign(new Error(`task not found: ${taskId}`), { code: "not_found" });
|
|
5086
5263
|
}
|
|
5087
|
-
|
|
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 =
|
|
5295
|
+
const resolved = path26.resolve(filePath);
|
|
5119
5296
|
const skill = all.find(
|
|
5120
|
-
(s) => s.agent === agent2 &&
|
|
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
|
|
5154
|
-
import
|
|
5330
|
+
import fs30 from "fs";
|
|
5331
|
+
import path28 from "path";
|
|
5155
5332
|
|
|
5156
5333
|
// src/core/daemon-service/binary.ts
|
|
5157
|
-
import
|
|
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 =
|
|
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
|
|
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
|
-
|
|
5188
|
-
|
|
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" ?
|
|
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" ?
|
|
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
|
|
5386
|
+
return path27.join(profileDir(), "logs");
|
|
5206
5387
|
}
|
|
5207
5388
|
function logPaths() {
|
|
5208
5389
|
const dir = logDir();
|
|
5209
5390
|
return {
|
|
5210
|
-
out:
|
|
5211
|
-
err:
|
|
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
|
-
|
|
5287
|
-
|
|
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
|
-
|
|
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 (
|
|
5504
|
+
if (fs30.existsSync(file)) {
|
|
5324
5505
|
run2("launchctl", ["unload", file]);
|
|
5325
|
-
|
|
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 (!
|
|
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
|
|
5368
|
-
import
|
|
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
|
-
|
|
5419
|
-
|
|
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
|
-
|
|
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 (
|
|
5439
|
-
|
|
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 (!
|
|
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
|
|
5673
|
+
import path30 from "path";
|
|
5493
5674
|
var cached2 = null;
|
|
5494
5675
|
function readCliVersion() {
|
|
5495
5676
|
if (cached2 !== null) return cached2;
|
|
5496
|
-
const here =
|
|
5677
|
+
const here = path30.dirname(fileURLToPath2(import.meta.url));
|
|
5497
5678
|
const candidates = [
|
|
5498
|
-
|
|
5499
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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(
|
|
6059
|
-
const url = `${serverBase2()}${
|
|
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
|
-
|
|
6564
|
-
import
|
|
6565
|
-
import
|
|
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
|
-
|
|
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
|
|
6574
|
-
|
|
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(
|
|
6699
|
+
return JSON.parse(fs32.readFileSync(file, "utf-8"));
|
|
6581
6700
|
} catch {
|
|
6582
6701
|
return null;
|
|
6583
6702
|
}
|
|
6584
6703
|
}
|
|
6585
|
-
|
|
6586
|
-
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
|
|
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.
|
|
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
|
|
6599
|
-
|
|
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("
|
|
6606
|
-
if (!
|
|
6607
|
-
fail9(`Invalid profile name "${name}". Must match
|
|
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
|
-
|
|
6610
|
-
|
|
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
|
|
6621
|
-
if (
|
|
6622
|
-
fail9(`
|
|
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
|
-
|
|
6626
|
-
|
|
6627
|
-
|
|
6628
|
-
}
|
|
6629
|
-
|
|
6630
|
-
|
|
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(`
|
|
6653
|
-
if (
|
|
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(
|
|
6657
|
-
|
|
6658
|
-
|
|
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 =
|
|
6767
|
+
const current = currentProfileName();
|
|
6662
6768
|
if (current === name && !opts.force) {
|
|
6663
|
-
fail9(`Profile "${name}" is current. Re-run with --force to remove it
|
|
6769
|
+
fail9(`Profile "${name}" is current. Re-run with --force to remove it.`);
|
|
6664
6770
|
}
|
|
6665
|
-
|
|
6666
|
-
console.log(chalk23.green(`Removed profile "${name}".`));
|
|
6771
|
+
fs32.rmSync(dir, { recursive: true, force: true });
|
|
6667
6772
|
if (current === name) {
|
|
6668
|
-
|
|
6773
|
+
writeCurrentProfile(null);
|
|
6774
|
+
console.log(chalk23.dim('Current profile reset to "default".'));
|
|
6669
6775
|
}
|
|
6670
|
-
console.log(chalk23.
|
|
6776
|
+
console.log(chalk23.green(`Removed profile "${name}".`));
|
|
6671
6777
|
});
|
|
6672
6778
|
profile2.command("use <name>").description("Set the current profile").action((name) => {
|
|
6673
|
-
|
|
6674
|
-
|
|
6675
|
-
|
|
6676
|
-
|
|
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
|
-
|
|
6786
|
+
writeCurrentProfile(name);
|
|
6679
6787
|
console.log(chalk23.green(`Now using profile "${name}".`));
|
|
6680
6788
|
});
|
|
6681
|
-
profile2.command("current").description(
|
|
6682
|
-
|
|
6683
|
-
|
|
6684
|
-
|
|
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(
|
|
6809
|
+
console.log(value);
|
|
6688
6810
|
});
|
|
6689
|
-
profile2.command("show [name]").description("Show a profile's configuration and
|
|
6690
|
-
const target = name ??
|
|
6691
|
-
if (!target) {
|
|
6692
|
-
fail9(
|
|
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
|
|
6695
|
-
if (!
|
|
6816
|
+
const dir = profileDir(target);
|
|
6817
|
+
if (!fs32.existsSync(dir)) {
|
|
6696
6818
|
fail9(`Profile "${target}" not found.`);
|
|
6697
6819
|
}
|
|
6698
|
-
const current =
|
|
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(`
|
|
6701
|
-
console.log(`
|
|
6702
|
-
|
|
6703
|
-
|
|
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 (
|
|
6711
|
-
console.log(chalk23.yellow(` warn:
|
|
6712
|
-
console.log(chalk23.dim(` Re-register the daemon to align: task0 --profile ${target} daemon register --
|
|
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
|
|
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(
|
|
7027
|
+
console.error(chalk25.red(err.message));
|
|
6839
7028
|
process.exit(1);
|
|
6840
7029
|
}
|
|
6841
7030
|
throw err;
|