p-backlog 0.4.0 → 0.5.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/CHANGELOG.md +20 -0
- package/README.md +46 -30
- package/README.ru.md +46 -30
- package/dist/cli.js +586 -216
- package/dist/web/assets/{ChartTooltip-DWaDSRan.js → ChartTooltip-b-yZtIRd.js} +1 -1
- package/dist/web/assets/{CodeTab-C87LaJFD.js → CodeTab-BU7UU1i4.js} +1 -1
- package/dist/web/assets/{CostTab-D4ByOnHX.js → CostTab-Be8xR9DU.js} +1 -1
- package/dist/web/assets/{EffectTab-Dq06ghtZ.js → EffectTab-D0kaZiSl.js} +1 -1
- package/dist/web/assets/{Figure-BC1eGh3_.js → Figure-B6Ry1zXo.js} +1 -1
- package/dist/web/assets/{OverviewTab-BBmohHJR.js → OverviewTab-DqU7o9Ug.js} +1 -1
- package/dist/web/assets/{QualityTab-Rb7zRcGH.js → QualityTab-DtnTgxXj.js} +1 -1
- package/dist/web/assets/{StatsPage-Dtyc1gAc.js → StatsPage-BLZKtSL6.js} +1 -1
- package/dist/web/assets/{StatsTabState-Br-8FgKd.js → StatsTabState-CYkDWYp7.js} +1 -1
- package/dist/web/assets/{StatsTable-BXG0IS8p.js → StatsTable-C0coWtNM.js} +1 -1
- package/dist/web/assets/{UnavailableRepos-KiTvsm1X.js → UnavailableRepos-DDHmBHg0.js} +1 -1
- package/dist/web/assets/{cx-CNNf0q6V.js → cx-Cfec8QGz.js} +2 -2
- package/dist/web/assets/{index-AJ1Qzly1.js → index-DN4lSwOL.js} +2 -2
- package/dist/web/assets/{start-BGXTwsB0.js → start-sbZVqXmP.js} +3 -3
- package/dist/web/assets/{value-dot-W-8PsGpd.js → value-dot-CfFRNT_m.js} +1 -1
- package/dist/web/index.html +1 -1
- package/package.json +2 -1
- package/skill/backlog/SKILL.md +2 -2
- package/skill/backlog-en/SKILL.md +2 -2
package/dist/cli.js
CHANGED
|
@@ -555,7 +555,7 @@ var cliEn = {
|
|
|
555
555
|
runNotRecorded: (error) => `Could not record the run: ${error}`,
|
|
556
556
|
runsNotTrimmed: (error) => `Could not trim the run log: ${error}`,
|
|
557
557
|
settingsFileInvalid: (path) => `${path} could not be parsed, the language for this run was detected automatically. The file was not changed \u2014 fix it manually.`,
|
|
558
|
-
optionLabel: { status: "status", category: "category", priority: "priority", language: "language" },
|
|
558
|
+
optionLabel: { status: "status", category: "category", priority: "priority", language: "language", agent: "agent" },
|
|
559
559
|
skillForeign: (target) => `${target} is a foreign directory, the skill link was not moved`,
|
|
560
560
|
installSkillLinked: (target, source) => `Skill installed: ${target} \u2192 ${source}`,
|
|
561
561
|
installSkillKept: (target) => `Skill already installed: ${target}`,
|
|
@@ -563,8 +563,25 @@ var cliEn = {
|
|
|
563
563
|
installSkillLinkFailed: (target, detail) => `Could not create the symlink ${target} (${detail}).`,
|
|
564
564
|
installHookExists: (settingsPath) => `Stop hook is already present in ${settingsPath}`,
|
|
565
565
|
installHookAdded: (settingsPath) => `Stop hook added to ${settingsPath}`,
|
|
566
|
+
installHookUpdated: (settingsPath) => `Stop hook updated in ${settingsPath}`,
|
|
567
|
+
codexHookApproval: "approve the hook in Codex: /hooks",
|
|
566
568
|
installSettingsUnreadable: (settingsPath, detail) => `Could not read ${settingsPath} (${detail}), Stop hook not added.`,
|
|
567
569
|
installSettingsInvalid: (settingsPath) => `${settingsPath} is not a JSON object, Stop hook not added. Fix the file and try again.`,
|
|
570
|
+
agentNotFound: (dir) => `not found (${dir})`,
|
|
571
|
+
pluginManages: (plugin) => `the ${plugin} plugin provides the skill and the hook. Remove the manual ones with: backlog setup --remove-manual`,
|
|
572
|
+
pluginLanguageHint: (installed, wanted) => `the skill comes from the ${installed} plugin; for this language switch plugins: /plugin uninstall ${installed}, then /plugin install ${wanted}`,
|
|
573
|
+
removeManualWithService: "--remove-manual cannot be combined with --service: remove the service with backlog service uninstall",
|
|
574
|
+
manualSkillRemoval: {
|
|
575
|
+
removed: (target) => `skill link removed: ${target}`,
|
|
576
|
+
absent: (target) => `no skill link: ${target}`,
|
|
577
|
+
foreign: (target) => `${target} is not a p-backlog link, left as is`
|
|
578
|
+
},
|
|
579
|
+
manualHookRemoval: {
|
|
580
|
+
removed: (path) => `Stop hook removed from ${path}`,
|
|
581
|
+
absent: (path) => `no p-backlog Stop hook in ${path}`
|
|
582
|
+
},
|
|
583
|
+
hookConfigUnreadable: (path, detail) => `Could not read ${path} (${detail}), the hook was not removed.`,
|
|
584
|
+
hookConfigInvalid: (path) => `${path} is not a JSON object, the hook was not removed. Fix the file and try again.`,
|
|
568
585
|
serviceInstalled: (file) => `Service installed: ${file}`,
|
|
569
586
|
serviceLogs: (path) => `Logs: ${path}`,
|
|
570
587
|
serviceUninstalled: "Service removed",
|
|
@@ -659,11 +676,12 @@ var cliEn = {
|
|
|
659
676
|
alreadyInStatus: (id, status) => `${id} is already ${status}`,
|
|
660
677
|
blockedByOpenTasks: (id) => `${id} is blocked by open tasks:`,
|
|
661
678
|
noTakeableInProject: (id) => `Project ${id} has no tasks that can be taken`,
|
|
662
|
-
hookUsage: (stopEvent) => `${stopEvent} (for the Stop hook in Claude Code, reads the event from stdin)`,
|
|
679
|
+
hookUsage: (stopEvent, agents) => `${stopEvent} [--agent ${agents.join("|")}] (for the Stop hook in Claude Code, Codex or Cursor, reads the event from stdin)`,
|
|
663
680
|
sessionShownReadFailed: (error) => `Could not read the tasks already shown this session: ${error}`,
|
|
664
681
|
sessionShownWriteFailed: (error) => `Could not remember the tasks shown this session: ${error}`,
|
|
665
682
|
alertsComputeFailed: (error) => `Could not compute alerts: ${error}`,
|
|
666
683
|
alertsShownWriteFailed: (error) => `Could not save the shown alerts: ${error}`,
|
|
684
|
+
hookTurnClaimFailed: (error) => `Could not record the turn for the double-hook guard: ${error}`,
|
|
667
685
|
newUsage: (types, priorities, found) => [
|
|
668
686
|
`--title <title> [--type ${types}] [--priority ${priorities}] [--tags a,b]`,
|
|
669
687
|
`--category <category> (required for tasks) [--found ${found}]`,
|
|
@@ -713,7 +731,7 @@ var cliRu = {
|
|
|
713
731
|
runNotRecorded: (error) => `\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u043F\u0438\u0441\u0430\u0442\u044C \u0437\u0430\u043F\u0443\u0441\u043A: ${error}`,
|
|
714
732
|
runsNotTrimmed: (error) => `\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u0431\u0440\u0435\u0437\u0430\u0442\u044C \u0436\u0443\u0440\u043D\u0430\u043B \u0437\u0430\u043F\u0443\u0441\u043A\u043E\u0432: ${error}`,
|
|
715
733
|
settingsFileInvalid: (path) => `${path} \u043D\u0435 \u0440\u0430\u0437\u043E\u0431\u0440\u0430\u043D, \u044F\u0437\u044B\u043A \u0434\u043B\u044F \u044D\u0442\u043E\u0433\u043E \u0437\u0430\u043F\u0443\u0441\u043A\u0430 \u043E\u043F\u0440\u0435\u0434\u0435\u043B\u0451\u043D \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438. \u0424\u0430\u0439\u043B \u043D\u0435 \u0438\u0437\u043C\u0435\u043D\u0451\u043D \u2014 \u043F\u043E\u043F\u0440\u0430\u0432\u044C\u0442\u0435 \u0435\u0433\u043E \u0432\u0440\u0443\u0447\u043D\u0443\u044E.`,
|
|
716
|
-
optionLabel: { status: "\u0441\u0442\u0430\u0442\u0443\u0441", category: "\u043A\u0430\u0442\u0435\u0433\u043E\u0440\u0438\u044F", priority: "\u043F\u0440\u0438\u043E\u0440\u0438\u0442\u0435\u0442", language: "\u044F\u0437\u044B\u043A" },
|
|
734
|
+
optionLabel: { status: "\u0441\u0442\u0430\u0442\u0443\u0441", category: "\u043A\u0430\u0442\u0435\u0433\u043E\u0440\u0438\u044F", priority: "\u043F\u0440\u0438\u043E\u0440\u0438\u0442\u0435\u0442", language: "\u044F\u0437\u044B\u043A", agent: "\u0430\u0433\u0435\u043D\u0442" },
|
|
717
735
|
skillForeign: (target) => `${target} \u2014 \u0447\u0443\u0436\u043E\u0439 \u043A\u0430\u0442\u0430\u043B\u043E\u0433, \u0441\u043A\u0438\u043B\u043B \u043D\u0435 \u043F\u0435\u0440\u0435\u0441\u0442\u0430\u0432\u043B\u0435\u043D`,
|
|
718
736
|
installSkillLinked: (target, source) => `\u0421\u043A\u0438\u043B\u043B \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D: ${target} \u2192 ${source}`,
|
|
719
737
|
installSkillKept: (target) => `\u0421\u043A\u0438\u043B\u043B \u0443\u0436\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D: ${target}`,
|
|
@@ -721,8 +739,25 @@ var cliRu = {
|
|
|
721
739
|
installSkillLinkFailed: (target, detail) => `\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0441\u043E\u0437\u0434\u0430\u0442\u044C \u0441\u0441\u044B\u043B\u043A\u0443 ${target} (${detail}).`,
|
|
722
740
|
installHookExists: (settingsPath) => `\u0425\u0443\u043A Stop \u0443\u0436\u0435 \u0435\u0441\u0442\u044C \u0432 ${settingsPath}`,
|
|
723
741
|
installHookAdded: (settingsPath) => `\u0425\u0443\u043A Stop \u0434\u043E\u0431\u0430\u0432\u043B\u0435\u043D \u0432 ${settingsPath}`,
|
|
742
|
+
installHookUpdated: (settingsPath) => `\u0425\u0443\u043A Stop \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D \u0432 ${settingsPath}`,
|
|
743
|
+
codexHookApproval: "\u043E\u0434\u043E\u0431\u0440\u0438\u0442\u0435 \u0445\u0443\u043A \u0432 Codex: /hooks",
|
|
724
744
|
installSettingsUnreadable: (settingsPath, detail) => `${settingsPath} \u043D\u0435 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0442\u044C (${detail}), \u0445\u0443\u043A Stop \u043D\u0435 \u0434\u043E\u0431\u0430\u0432\u043B\u0435\u043D.`,
|
|
725
745
|
installSettingsInvalid: (settingsPath) => `${settingsPath} \u2014 \u043D\u0435 \u043E\u0431\u044A\u0435\u043A\u0442 JSON, \u0445\u0443\u043A Stop \u043D\u0435 \u0434\u043E\u0431\u0430\u0432\u043B\u0435\u043D. \u0418\u0441\u043F\u0440\u0430\u0432\u044C\u0442\u0435 \u0444\u0430\u0439\u043B \u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u0435.`,
|
|
746
|
+
agentNotFound: (dir) => `\u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D (${dir})`,
|
|
747
|
+
pluginManages: (plugin) => `\u0441\u043A\u0438\u043B\u043B \u0438 \u0445\u0443\u043A \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0430\u0435\u0442 \u043F\u043B\u0430\u0433\u0438\u043D ${plugin}. \u0420\u0443\u0447\u043D\u044B\u0435 \u043C\u043E\u0436\u043D\u043E \u0443\u0431\u0440\u0430\u0442\u044C: backlog setup --remove-manual`,
|
|
748
|
+
pluginLanguageHint: (installed, wanted) => `\u0441\u043A\u0438\u043B\u043B \u0434\u0430\u0451\u0442 \u043F\u043B\u0430\u0433\u0438\u043D ${installed}; \u0434\u043B\u044F \u044D\u0442\u043E\u0433\u043E \u044F\u0437\u044B\u043A\u0430 \u043F\u043E\u0441\u0442\u0430\u0432\u044C\u0442\u0435 \u0434\u0440\u0443\u0433\u043E\u0439: /plugin uninstall ${installed}, \u0437\u0430\u0442\u0435\u043C /plugin install ${wanted}`,
|
|
749
|
+
removeManualWithService: "--remove-manual \u043D\u0435 \u0441\u043E\u0447\u0435\u0442\u0430\u0435\u0442\u0441\u044F \u0441 --service: \u0441\u043B\u0443\u0436\u0431\u0443 \u0441\u043D\u0438\u043C\u0430\u0435\u0442 backlog service uninstall",
|
|
750
|
+
manualSkillRemoval: {
|
|
751
|
+
removed: (target) => `\u0441\u0441\u044B\u043B\u043A\u0430 \u043D\u0430 \u0441\u043A\u0438\u043B\u043B \u0441\u043D\u044F\u0442\u0430: ${target}`,
|
|
752
|
+
absent: (target) => `\u0441\u0441\u044B\u043B\u043A\u0438 \u043D\u0430 \u0441\u043A\u0438\u043B\u043B \u043D\u0435\u0442: ${target}`,
|
|
753
|
+
foreign: (target) => `${target} \u2014 \u043D\u0435 \u0441\u0441\u044B\u043B\u043A\u0430 p-backlog, \u043D\u0435 \u0442\u0440\u043E\u043D\u0443\u0442`
|
|
754
|
+
},
|
|
755
|
+
manualHookRemoval: {
|
|
756
|
+
removed: (path) => `\u0445\u0443\u043A Stop \u0441\u043D\u044F\u0442 \u0438\u0437 ${path}`,
|
|
757
|
+
absent: (path) => `\u0445\u0443\u043A\u0430 Stop p-backlog \u0432 ${path} \u043D\u0435\u0442`
|
|
758
|
+
},
|
|
759
|
+
hookConfigUnreadable: (path, detail) => `\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0442\u044C ${path} (${detail}), \u0445\u0443\u043A \u043D\u0435 \u0441\u043D\u044F\u0442.`,
|
|
760
|
+
hookConfigInvalid: (path) => `${path} \u2014 \u043D\u0435 \u043E\u0431\u044A\u0435\u043A\u0442 JSON, \u0445\u0443\u043A \u043D\u0435 \u0441\u043D\u044F\u0442. \u0418\u0441\u043F\u0440\u0430\u0432\u044C\u0442\u0435 \u0444\u0430\u0439\u043B \u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u0435.`,
|
|
726
761
|
serviceInstalled: (file) => `\u0421\u043B\u0443\u0436\u0431\u0430 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0430: ${file}`,
|
|
727
762
|
serviceLogs: (path) => `\u041B\u043E\u0433\u0438: ${path}`,
|
|
728
763
|
serviceUninstalled: "\u0421\u043B\u0443\u0436\u0431\u0430 \u0443\u0434\u0430\u043B\u0435\u043D\u0430",
|
|
@@ -829,11 +864,12 @@ var cliRu = {
|
|
|
829
864
|
alreadyInStatus: (id, status) => `${id} \u0443\u0436\u0435 \u0432 \u0441\u0442\u0430\u0442\u0443\u0441\u0435 ${status}`,
|
|
830
865
|
blockedByOpenTasks: (id) => `${id} \u0437\u0430\u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u0430\u043D\u0430 \u043E\u0442\u043A\u0440\u044B\u0442\u044B\u043C\u0438 \u0437\u0430\u0434\u0430\u0447\u0430\u043C\u0438:`,
|
|
831
866
|
noTakeableInProject: (id) => `\u0412 \u043F\u0440\u043E\u0435\u043A\u0442\u0435 ${id} \u043D\u0435\u0442 \u0437\u0430\u0434\u0430\u0447, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u043C\u043E\u0436\u043D\u043E \u0432\u0437\u044F\u0442\u044C \u0432 \u0440\u0430\u0431\u043E\u0442\u0443`,
|
|
832
|
-
hookUsage: (stopEvent) => `${stopEvent} (\u0434\u043B\u044F \u0445\u0443\u043A\u0430 Stop \u0432 Claude Code, \u0441\u043E\u0431\u044B\u0442\u0438\u0435 \u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F \u0438\u0437 stdin)`,
|
|
867
|
+
hookUsage: (stopEvent, agents) => `${stopEvent} [--agent ${agents.join("|")}] (\u0434\u043B\u044F \u0445\u0443\u043A\u0430 Stop \u0432 Claude Code, Codex \u0438\u043B\u0438 Cursor, \u0441\u043E\u0431\u044B\u0442\u0438\u0435 \u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F \u0438\u0437 stdin)`,
|
|
833
868
|
sessionShownReadFailed: (error) => `\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0442\u044C \u043F\u043E\u043A\u0430\u0437\u0430\u043D\u043D\u044B\u0435 \u0437\u0430\u0434\u0430\u0447\u0438 \u0441\u0435\u0441\u0441\u0438\u0438: ${error}`,
|
|
834
869
|
sessionShownWriteFailed: (error) => `\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u043F\u043E\u043C\u043D\u0438\u0442\u044C \u043F\u043E\u043A\u0430\u0437\u0430\u043D\u043D\u044B\u0435 \u0437\u0430\u0434\u0430\u0447\u0438 \u0441\u0435\u0441\u0441\u0438\u0438: ${error}`,
|
|
835
870
|
alertsComputeFailed: (error) => `\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u043E\u0441\u0447\u0438\u0442\u0430\u0442\u044C \u0442\u0440\u0435\u0432\u043E\u0433\u0438: ${error}`,
|
|
836
871
|
alertsShownWriteFailed: (error) => `\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0441\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C \u043F\u043E\u043A\u0430\u0437\u0430\u043D\u043D\u044B\u0435 \u0442\u0440\u0435\u0432\u043E\u0433\u0438: ${error}`,
|
|
872
|
+
hookTurnClaimFailed: (error) => `\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u0442\u043C\u0435\u0442\u0438\u0442\u044C \u0445\u043E\u0434 \u0432 \u0437\u0430\u0449\u0438\u0442\u0435 \u043E\u0442 \u0434\u0432\u043E\u0439\u043D\u043E\u0433\u043E \u0445\u0443\u043A\u0430: ${error}`,
|
|
837
873
|
newUsage: (types, priorities, found) => [
|
|
838
874
|
`--title <\u0437\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A> [--type ${types}] [--priority ${priorities}] [--tags a,b]`,
|
|
839
875
|
`--category <\u043A\u0430\u0442\u0435\u0433\u043E\u0440\u0438\u044F> (\u0434\u043B\u044F \u0437\u0430\u0434\u0430\u0447 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u0435\u043D) [--found ${found}]`,
|
|
@@ -3874,7 +3910,11 @@ async function fixCommitFound(loaded, task, reason, io) {
|
|
|
3874
3910
|
}
|
|
3875
3911
|
|
|
3876
3912
|
// src/cli/commands/config.ts
|
|
3877
|
-
import { join as
|
|
3913
|
+
import { join as join18 } from "node:path";
|
|
3914
|
+
|
|
3915
|
+
// src/cli/agents/agent.ts
|
|
3916
|
+
import { stat as stat4 } from "node:fs/promises";
|
|
3917
|
+
import { join as join16 } from "node:path";
|
|
3878
3918
|
|
|
3879
3919
|
// src/core/claude-dir.ts
|
|
3880
3920
|
import { join as join15 } from "node:path";
|
|
@@ -3891,18 +3931,67 @@ function claudeProjectsDir(env, home2) {
|
|
|
3891
3931
|
return join15(claudeDir(env, home2), "projects");
|
|
3892
3932
|
}
|
|
3893
3933
|
|
|
3934
|
+
// src/cli/agents/agent.ts
|
|
3935
|
+
var AGENTS = ["claude", "codex", "cursor"];
|
|
3936
|
+
var AGENT_LABELS = { claude: "Claude Code", codex: "Codex", cursor: "Cursor" };
|
|
3937
|
+
function agentHomeDir(agent, env, home2) {
|
|
3938
|
+
if (agent === "claude") return claudeDir(env, home2);
|
|
3939
|
+
if (agent === "codex") return env.CODEX_HOME || join16(home2, ".codex");
|
|
3940
|
+
return join16(home2, ".cursor");
|
|
3941
|
+
}
|
|
3942
|
+
function agentSkillsDir(agent, env, home2) {
|
|
3943
|
+
if (agent === "claude") return claudeSkillsDir(env, home2);
|
|
3944
|
+
if (agent === "codex") return join16(home2, ".agents", "skills");
|
|
3945
|
+
return join16(agentHomeDir(agent, env, home2), "skills");
|
|
3946
|
+
}
|
|
3947
|
+
function legacySkillsDirs(agent, env, home2) {
|
|
3948
|
+
return agent === "codex" ? [join16(agentHomeDir(agent, env, home2), "skills")] : [];
|
|
3949
|
+
}
|
|
3950
|
+
async function detectAgents(env, home2) {
|
|
3951
|
+
const detection = { found: [], missing: [] };
|
|
3952
|
+
for (const agent of AGENTS) {
|
|
3953
|
+
const dir = agentHomeDir(agent, env, home2);
|
|
3954
|
+
const present = agent === "claude" || ((await stat4(dir).catch(() => null))?.isDirectory() ?? false);
|
|
3955
|
+
if (present) detection.found.push(agent);
|
|
3956
|
+
else detection.missing.push({ agent, dir });
|
|
3957
|
+
}
|
|
3958
|
+
return detection;
|
|
3959
|
+
}
|
|
3960
|
+
|
|
3961
|
+
// src/cli/agents/claude-plugin.ts
|
|
3962
|
+
import { z as z8 } from "zod";
|
|
3963
|
+
var PLUGIN_BY_LANGUAGE = { en: "p-backlog", ru: "p-backlog-ru" };
|
|
3964
|
+
var PLUGIN_NAMES = new Set(Object.values(PLUGIN_BY_LANGUAGE));
|
|
3965
|
+
var MARKETPLACE_SEPARATOR = "@";
|
|
3966
|
+
var enabledPluginsSchema = z8.object({ enabledPlugins: z8.record(z8.string(), z8.unknown()).optional() });
|
|
3967
|
+
async function agentPlugin(agent, env, home2) {
|
|
3968
|
+
if (agent !== "claude") return null;
|
|
3969
|
+
const settings = await readJsonFile(claudeSettingsPath(env, home2), enabledPluginsSchema);
|
|
3970
|
+
const enabled = Object.entries(settings?.enabledPlugins ?? {}).find(([id, on]) => on === true && PLUGIN_NAMES.has(pluginName(id)));
|
|
3971
|
+
return enabled?.[0] ?? null;
|
|
3972
|
+
}
|
|
3973
|
+
function pluginToSwitchTo(pluginId, language) {
|
|
3974
|
+
const wanted = PLUGIN_BY_LANGUAGE[language];
|
|
3975
|
+
if (pluginName(pluginId) === wanted) return null;
|
|
3976
|
+
const marketplace = pluginId.slice(pluginId.indexOf(MARKETPLACE_SEPARATOR) + 1);
|
|
3977
|
+
return `${wanted}${MARKETPLACE_SEPARATOR}${marketplace}`;
|
|
3978
|
+
}
|
|
3979
|
+
function pluginName(pluginId) {
|
|
3980
|
+
return pluginId.split(MARKETPLACE_SEPARATOR)[0] ?? pluginId;
|
|
3981
|
+
}
|
|
3982
|
+
|
|
3894
3983
|
// src/cli/skill-link.ts
|
|
3895
3984
|
import { lstat, mkdir as mkdir4, readlink, symlink, unlink } from "node:fs/promises";
|
|
3896
|
-
import { basename as basename8, dirname as dirname5, join as
|
|
3897
|
-
import { z as
|
|
3985
|
+
import { basename as basename8, dirname as dirname5, join as join17, resolve as resolve3 } from "node:path";
|
|
3986
|
+
import { z as z9 } from "zod";
|
|
3898
3987
|
var SKILL_VARIANTS = /* @__PURE__ */ new Set(["backlog", "backlog-en"]);
|
|
3899
|
-
var packageManifestSchema =
|
|
3988
|
+
var packageManifestSchema = z9.object({ name: z9.string() });
|
|
3900
3989
|
function skillSourceDir(packageRoot2, language) {
|
|
3901
|
-
return
|
|
3990
|
+
return join17(packageRoot2, "skill", language === "ru" ? "backlog" : "backlog-en");
|
|
3902
3991
|
}
|
|
3903
3992
|
async function isPBacklogSkill(path) {
|
|
3904
3993
|
if (!SKILL_VARIANTS.has(basename8(path)) || basename8(dirname5(path)) !== "skill") return false;
|
|
3905
|
-
const manifest = await readTextOrNull(
|
|
3994
|
+
const manifest = await readTextOrNull(join17(dirname5(dirname5(path)), "package.json"));
|
|
3906
3995
|
if (manifest !== null) return parseJson(manifest, packageManifestSchema)?.name === "p-backlog";
|
|
3907
3996
|
return !await pathExists(path);
|
|
3908
3997
|
}
|
|
@@ -3910,7 +3999,7 @@ async function pathExists(path) {
|
|
|
3910
3999
|
return await lstat(path).catch(() => null) !== null;
|
|
3911
4000
|
}
|
|
3912
4001
|
async function linkSkillFor(language, { skillsDir, packageRoot: packageRoot2, platform }) {
|
|
3913
|
-
const target =
|
|
4002
|
+
const target = join17(skillsDir, "backlog");
|
|
3914
4003
|
const source = skillSourceDir(packageRoot2, language);
|
|
3915
4004
|
const existing = await lstat(target).catch(() => null);
|
|
3916
4005
|
if (existing !== null && !existing.isSymbolicLink()) return "foreign";
|
|
@@ -3924,6 +4013,18 @@ async function linkSkillFor(language, { skillsDir, packageRoot: packageRoot2, pl
|
|
|
3924
4013
|
await symlink(source, target, platform === "win32" ? "junction" : "dir");
|
|
3925
4014
|
return "linked";
|
|
3926
4015
|
}
|
|
4016
|
+
async function relinkExistingSkill(language, options) {
|
|
4017
|
+
if (!await pathExists(join17(options.skillsDir, "backlog"))) return "absent";
|
|
4018
|
+
return linkSkillFor(language, options);
|
|
4019
|
+
}
|
|
4020
|
+
async function unlinkOurSkill(skillsDir) {
|
|
4021
|
+
const target = join17(skillsDir, "backlog");
|
|
4022
|
+
const existing = await lstat(target).catch(() => null);
|
|
4023
|
+
if (existing === null) return "absent";
|
|
4024
|
+
if (!existing.isSymbolicLink() || !await isPBacklogSkill(resolve3(skillsDir, await readlink(target)))) return "foreign";
|
|
4025
|
+
await unlink(target);
|
|
4026
|
+
return "removed";
|
|
4027
|
+
}
|
|
3927
4028
|
|
|
3928
4029
|
// src/cli/commands/config.ts
|
|
3929
4030
|
var configCommand = {
|
|
@@ -3947,11 +4048,29 @@ async function runLanguage(positionals, io) {
|
|
|
3947
4048
|
const language = parseChoice(io.language, value, LANGUAGES, cliMessages(io.language).optionLabel.language);
|
|
3948
4049
|
await writeSettings(io.backlogRoot, { language });
|
|
3949
4050
|
io.print(`${io.language} \u2192 ${language}`);
|
|
3950
|
-
const
|
|
3951
|
-
const
|
|
3952
|
-
if (result === "foreign") io.warn(cliMessages(language).skillForeign(join17(skillsDir, "backlog")));
|
|
4051
|
+
const { found } = await detectAgents(io.env, io.home);
|
|
4052
|
+
for (const agent of found) await relinkSkill(agent, language, io);
|
|
3953
4053
|
return EXIT.ok;
|
|
3954
4054
|
}
|
|
4055
|
+
async function relinkSkill(agent, language, io) {
|
|
4056
|
+
const messages = cliMessages(language);
|
|
4057
|
+
const label = AGENT_LABELS[agent];
|
|
4058
|
+
const plugin = await agentPlugin(agent, io.env, io.home);
|
|
4059
|
+
if (plugin !== null) {
|
|
4060
|
+
const wanted = pluginToSwitchTo(plugin, language);
|
|
4061
|
+
if (wanted !== null) io.print(`${label}: ${messages.pluginLanguageHint(plugin, wanted)}`);
|
|
4062
|
+
return;
|
|
4063
|
+
}
|
|
4064
|
+
const skillsDir = agentSkillsDir(agent, io.env, io.home);
|
|
4065
|
+
const target = join18(skillsDir, "backlog");
|
|
4066
|
+
const options = { skillsDir, packageRoot: io.packageRoot, platform: io.platform };
|
|
4067
|
+
try {
|
|
4068
|
+
const result = agent === "claude" ? await linkSkillFor(language, options) : await relinkExistingSkill(language, options);
|
|
4069
|
+
if (result === "foreign") io.warn(`${label}: ${messages.skillForeign(target)}`);
|
|
4070
|
+
} catch (error) {
|
|
4071
|
+
io.warn(`${label}: ${messages.installSkillLinkFailed(target, errorCodeOrText(error))}`);
|
|
4072
|
+
}
|
|
4073
|
+
}
|
|
3955
4074
|
|
|
3956
4075
|
// src/cli/apply-all.ts
|
|
3957
4076
|
async function applyAll(items, apply) {
|
|
@@ -4004,31 +4123,47 @@ function epicWord(cli, epic) {
|
|
|
4004
4123
|
|
|
4005
4124
|
// src/cli/commands/hook.ts
|
|
4006
4125
|
import { dirname as dirname6 } from "node:path";
|
|
4007
|
-
|
|
4126
|
+
|
|
4127
|
+
// src/core/store/hook-turns.ts
|
|
4128
|
+
import { join as join19 } from "node:path";
|
|
4129
|
+
import { z as z10 } from "zod";
|
|
4130
|
+
var HOOK_TURNS_FILE = ".hook-turns.json";
|
|
4131
|
+
var CLAIM_WINDOW_MS = 6e4;
|
|
4132
|
+
var hookTurnsSchema = z10.record(z10.string(), z10.iso.datetime({ offset: true }));
|
|
4133
|
+
async function claimHookTurn(backlogRoot2, turnKey, now) {
|
|
4134
|
+
const path = join19(backlogRoot2, HOOK_TURNS_FILE);
|
|
4135
|
+
return withFileLock(path, async () => {
|
|
4136
|
+
const turns = await readJsonFile(path, hookTurnsSchema) ?? {};
|
|
4137
|
+
const claimed = Object.entries(turns).filter(([, at]) => now.getTime() - Date.parse(at) < CLAIM_WINDOW_MS);
|
|
4138
|
+
if (claimed.some(([key]) => key === turnKey)) return false;
|
|
4139
|
+
await writeJsonFile(path, { ...Object.fromEntries(claimed), [turnKey]: now.toISOString() });
|
|
4140
|
+
return true;
|
|
4141
|
+
});
|
|
4142
|
+
}
|
|
4008
4143
|
|
|
4009
4144
|
// src/core/store/signals-shown.ts
|
|
4010
|
-
import { join as
|
|
4011
|
-
import { z as
|
|
4145
|
+
import { join as join20 } from "node:path";
|
|
4146
|
+
import { z as z11 } from "zod";
|
|
4012
4147
|
var SIGNALS_SHOWN_FILE = ".signals-shown.json";
|
|
4013
|
-
var shownSchema =
|
|
4148
|
+
var shownSchema = z11.record(z11.string(), z11.string());
|
|
4014
4149
|
async function readSignalsShown(projectDir2) {
|
|
4015
|
-
return await readJsonFile(
|
|
4150
|
+
return await readJsonFile(join20(projectDir2, SIGNALS_SHOWN_FILE), shownSchema) ?? {};
|
|
4016
4151
|
}
|
|
4017
4152
|
async function writeSignalsShown(projectDir2, shown) {
|
|
4018
|
-
await writeJsonFile(
|
|
4153
|
+
await writeJsonFile(join20(projectDir2, SIGNALS_SHOWN_FILE), shown);
|
|
4019
4154
|
}
|
|
4020
4155
|
|
|
4021
4156
|
// src/core/store/session-shown.ts
|
|
4022
|
-
import { join as
|
|
4023
|
-
import { z as
|
|
4157
|
+
import { join as join21 } from "node:path";
|
|
4158
|
+
import { z as z12 } from "zod";
|
|
4024
4159
|
var SESSION_SHOWN_FILE = ".candidates-shown.json";
|
|
4025
4160
|
var SESSION_RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
4026
|
-
var sessionsShownSchema =
|
|
4161
|
+
var sessionsShownSchema = z12.record(z12.string(), z12.object({ tasks: z12.array(z12.string()), at: z12.iso.datetime({ offset: true }) }));
|
|
4027
4162
|
async function readSessionShown(projectDir2, session) {
|
|
4028
4163
|
return (await readSessions(projectDir2))[session]?.tasks ?? [];
|
|
4029
4164
|
}
|
|
4030
4165
|
async function rememberSessionShown(projectDir2, session, taskIds, now) {
|
|
4031
|
-
const path =
|
|
4166
|
+
const path = join21(projectDir2, SESSION_SHOWN_FILE);
|
|
4032
4167
|
await withFileLock(path, async () => {
|
|
4033
4168
|
const sessions = await readSessions(projectDir2);
|
|
4034
4169
|
const recent = Object.entries(sessions).filter(([, shown]) => now.getTime() - Date.parse(shown.at) < SESSION_RETENTION_MS);
|
|
@@ -4037,7 +4172,7 @@ async function rememberSessionShown(projectDir2, session, taskIds, now) {
|
|
|
4037
4172
|
});
|
|
4038
4173
|
}
|
|
4039
4174
|
async function readSessions(projectDir2) {
|
|
4040
|
-
return await readJsonFile(
|
|
4175
|
+
return await readJsonFile(join21(projectDir2, SESSION_SHOWN_FILE), sessionsShownSchema) ?? {};
|
|
4041
4176
|
}
|
|
4042
4177
|
|
|
4043
4178
|
// src/core/stats/numbers.ts
|
|
@@ -4315,6 +4450,56 @@ function markShown(shown, signals, today) {
|
|
|
4315
4450
|
return { ...shown, ...Object.fromEntries(signals.map((signal3) => [signal3.kind, today])) };
|
|
4316
4451
|
}
|
|
4317
4452
|
|
|
4453
|
+
// src/cli/agents/stop-event.ts
|
|
4454
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
4455
|
+
import { z as z13 } from "zod";
|
|
4456
|
+
var STOP_PROTOCOL = { claude: "claude", codex: "claude", cursor: "cursor" };
|
|
4457
|
+
var TURN_DIGEST_LENGTH = 16;
|
|
4458
|
+
var CURSOR_COMPLETED = "completed";
|
|
4459
|
+
var claudeEventSchema = z13.object({
|
|
4460
|
+
cwd: z13.string(),
|
|
4461
|
+
session_id: z13.string().optional(),
|
|
4462
|
+
turn_id: z13.string().optional(),
|
|
4463
|
+
stop_hook_active: z13.boolean().optional(),
|
|
4464
|
+
last_assistant_message: z13.string().nullish()
|
|
4465
|
+
});
|
|
4466
|
+
var cursorEventSchema = z13.object({
|
|
4467
|
+
workspace_roots: z13.tuple([z13.string()], z13.string()),
|
|
4468
|
+
conversation_id: z13.string().optional(),
|
|
4469
|
+
generation_id: z13.string().optional(),
|
|
4470
|
+
status: z13.string().optional(),
|
|
4471
|
+
loop_count: z13.number().optional()
|
|
4472
|
+
});
|
|
4473
|
+
function parseStopEvent(agent, text) {
|
|
4474
|
+
return STOP_PROTOCOL[agent] === "cursor" ? parseCursorEvent(text) : parseClaudeEvent(text);
|
|
4475
|
+
}
|
|
4476
|
+
function carriesSystemMessage(agent) {
|
|
4477
|
+
return STOP_PROTOCOL[agent] === "claude";
|
|
4478
|
+
}
|
|
4479
|
+
function formatStopAnswer(agent, answer) {
|
|
4480
|
+
if (STOP_PROTOCOL[agent] === "cursor") return answer.reason === null ? null : JSON.stringify({ followup_message: answer.reason });
|
|
4481
|
+
const response = {
|
|
4482
|
+
...answer.reason !== null ? { decision: "block", reason: answer.reason } : {},
|
|
4483
|
+
...answer.systemMessage !== null ? { systemMessage: answer.systemMessage } : {}
|
|
4484
|
+
};
|
|
4485
|
+
return Object.keys(response).length > 0 ? JSON.stringify(response) : null;
|
|
4486
|
+
}
|
|
4487
|
+
function parseClaudeEvent(text) {
|
|
4488
|
+
const event = parseJson(text, claudeEventSchema);
|
|
4489
|
+
if (event === null) return null;
|
|
4490
|
+
return { cwd: event.cwd, session: event.session_id, turn: event.turn_id ?? digest(event.last_assistant_message), skip: event.stop_hook_active === true };
|
|
4491
|
+
}
|
|
4492
|
+
function parseCursorEvent(text) {
|
|
4493
|
+
const event = parseJson(text, cursorEventSchema);
|
|
4494
|
+
if (event === null) return null;
|
|
4495
|
+
const followsOurFollowUp = (event.loop_count ?? 0) > 0;
|
|
4496
|
+
const interrupted = (event.status ?? CURSOR_COMPLETED) !== CURSOR_COMPLETED;
|
|
4497
|
+
return { cwd: event.workspace_roots[0], session: event.conversation_id, turn: event.generation_id, skip: followsOurFollowUp || interrupted };
|
|
4498
|
+
}
|
|
4499
|
+
function digest(text) {
|
|
4500
|
+
return text ? createHash4("sha256").update(text).digest("hex").slice(0, TURN_DIGEST_LENGTH) : void 0;
|
|
4501
|
+
}
|
|
4502
|
+
|
|
4318
4503
|
// src/cli/stop-reason.ts
|
|
4319
4504
|
var STOP_REASON_LIMIT = 500;
|
|
4320
4505
|
function stopReason(language, projectId, candidates) {
|
|
@@ -4343,37 +4528,50 @@ function briefEvidence(cli, candidate) {
|
|
|
4343
4528
|
}
|
|
4344
4529
|
|
|
4345
4530
|
// src/cli/commands/hook.ts
|
|
4346
|
-
var
|
|
4531
|
+
var DEFAULT_AGENT = "claude";
|
|
4347
4532
|
var hookCommand = {
|
|
4348
4533
|
name: "hook",
|
|
4349
|
-
usage: (language) => [cliMessages(language).hookUsage(HOOK_STOP_EVENT)],
|
|
4534
|
+
usage: (language) => [cliMessages(language).hookUsage(HOOK_STOP_EVENT, AGENTS)],
|
|
4350
4535
|
run: runHook,
|
|
4351
4536
|
failureExit: EXIT.ok
|
|
4352
4537
|
};
|
|
4353
4538
|
async function runHook(args, io) {
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4539
|
+
const { values, positionals } = parseCommandArgs(io.language, args, { agent: { type: "string" } });
|
|
4540
|
+
if (positionals.length !== 1 || positionals[0] !== HOOK_STOP_EVENT) throw usageError(hookCommand, io.language);
|
|
4541
|
+
const agent = values.agent === void 0 ? DEFAULT_AGENT : parseChoice(io.language, values.agent, AGENTS, cliMessages(io.language).optionLabel.agent);
|
|
4542
|
+
const event = parseStopEvent(agent, await io.readStdin());
|
|
4543
|
+
if (event === null || event.skip) return EXIT.ok;
|
|
4357
4544
|
const loaded = await loadBacklog(io.backlogRoot);
|
|
4358
4545
|
const project = findProjectForDir(loaded.projects, event.cwd, io.home);
|
|
4359
4546
|
if (!project) return EXIT.ok;
|
|
4547
|
+
if (!await isFirstHookOfTurn(agent, event, io)) return EXIT.ok;
|
|
4360
4548
|
const messages = coreMessages(io.language);
|
|
4361
4549
|
const { candidates } = await checkBacklog(io.backlogRoot, loaded, { projectIds: [project.id], mode: "changed", now: io.now(), home: io.home, messages, workingDir: event.cwd });
|
|
4362
4550
|
const lowPriority = new Set(loaded.tasks.filter((task) => task.priority === "low").map((task) => task.id));
|
|
4363
4551
|
const worthTelling = candidates.filter((candidate) => !lowPriority.has(candidate.task.id));
|
|
4364
4552
|
const lowCount = candidates.length - worthTelling.length;
|
|
4365
|
-
const session = await sessionMemory(project, event.
|
|
4553
|
+
const session = await sessionMemory(project, event.session, io);
|
|
4366
4554
|
const blocking = worthTelling.filter((candidate) => !session.told.has(candidate.task.id));
|
|
4367
|
-
const signals = await freshSignals(project, loaded.tasks.filter((task) => task.projectId === project.id), lowChangedSignals(lowCount), io);
|
|
4368
|
-
const
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
};
|
|
4372
|
-
if (
|
|
4555
|
+
const signals = carriesSystemMessage(agent) ? await freshSignals(project, loaded.tasks.filter((task) => task.projectId === project.id), lowChangedSignals(lowCount), io) : NO_SIGNALS;
|
|
4556
|
+
const answer = formatStopAnswer(agent, {
|
|
4557
|
+
reason: blocking.length > 0 ? stopReason(io.language, project.id, blocking) : null,
|
|
4558
|
+
systemMessage: signals.fresh.length > 0 ? hookMessage(io.language, project.id, signals.fresh.map((signal3) => messages.signal(signal3)).join("; ")) : null
|
|
4559
|
+
});
|
|
4560
|
+
if (answer !== null) io.print(answer);
|
|
4373
4561
|
await signals.remember();
|
|
4374
4562
|
await session.remember(blocking.map((candidate) => candidate.task.id));
|
|
4375
4563
|
return EXIT.ok;
|
|
4376
4564
|
}
|
|
4565
|
+
async function isFirstHookOfTurn(agent, event, io) {
|
|
4566
|
+
if (event.session === void 0 || event.turn === void 0) return true;
|
|
4567
|
+
try {
|
|
4568
|
+
return await claimHookTurn(io.backlogRoot, `${agent}:${event.session}:${event.turn}`, io.now());
|
|
4569
|
+
} catch (error) {
|
|
4570
|
+
if (error instanceof FileBusyError) return false;
|
|
4571
|
+
io.warn(cliMessages(io.language).hookTurnClaimFailed(errorText(error)));
|
|
4572
|
+
return true;
|
|
4573
|
+
}
|
|
4574
|
+
}
|
|
4377
4575
|
async function sessionMemory(project, session, io) {
|
|
4378
4576
|
if (session === void 0) return { told: /* @__PURE__ */ new Set(), remember: () => Promise.resolve() };
|
|
4379
4577
|
const projectDir2 = dirname6(project.path);
|
|
@@ -4389,6 +4587,7 @@ async function sessionMemory(project, session, io) {
|
|
|
4389
4587
|
return { told: /* @__PURE__ */ new Set(), remember: () => Promise.resolve() };
|
|
4390
4588
|
}
|
|
4391
4589
|
}
|
|
4590
|
+
var NO_SIGNALS = { fresh: [], remember: () => Promise.resolve() };
|
|
4392
4591
|
function lowChangedSignals(count3) {
|
|
4393
4592
|
if (count3 === 0) return [];
|
|
4394
4593
|
return [{ kind: "low-changed", params: { count: count3 } }];
|
|
@@ -4788,10 +4987,10 @@ function listenFailure(error, port, messages) {
|
|
|
4788
4987
|
// src/server/start.ts
|
|
4789
4988
|
import { serve } from "@hono/node-server";
|
|
4790
4989
|
import { mkdir as mkdir6, rm as rm4, writeFile as writeFile3 } from "node:fs/promises";
|
|
4791
|
-
import { join as
|
|
4990
|
+
import { join as join27 } from "node:path";
|
|
4792
4991
|
|
|
4793
4992
|
// src/core/store/sweep.ts
|
|
4794
|
-
import { dirname as dirname7, join as
|
|
4993
|
+
import { dirname as dirname7, join as join22 } from "node:path";
|
|
4795
4994
|
async function sweepClosed(root, now, messages) {
|
|
4796
4995
|
const initial = await loadBacklog(root);
|
|
4797
4996
|
const reopening = await reopenEpicsWithOpenTasks(initial, now);
|
|
@@ -4814,7 +5013,7 @@ async function sweepClosed(root, now, messages) {
|
|
|
4814
5013
|
}
|
|
4815
5014
|
async function removeAbandonedTemporaries(root, now) {
|
|
4816
5015
|
const cutoff = new Date(now.getTime() - DAY_MS);
|
|
4817
|
-
const projectDirs = (await listDir(root)).filter((entry) => entry.isDirectory()).map((entry) =>
|
|
5016
|
+
const projectDirs = (await listDir(root)).filter((entry) => entry.isDirectory()).map((entry) => join22(root, entry.name));
|
|
4818
5017
|
for (const dir of [root, ...projectDirs]) await removeTemporariesBefore(dir, cutoff);
|
|
4819
5018
|
}
|
|
4820
5019
|
async function removeWithReferences(tasks, removable, now) {
|
|
@@ -4938,49 +5137,49 @@ async function reserveNumbers(projects, expired) {
|
|
|
4938
5137
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
4939
5138
|
import { Hono as Hono3 } from "hono";
|
|
4940
5139
|
import { secureHeaders } from "hono/secure-headers";
|
|
4941
|
-
import { join as
|
|
5140
|
+
import { join as join24 } from "node:path";
|
|
4942
5141
|
|
|
4943
5142
|
// src/server/api.ts
|
|
4944
5143
|
import { Hono as Hono2 } from "hono";
|
|
4945
5144
|
import { streamSSE } from "hono/streaming";
|
|
4946
5145
|
|
|
4947
5146
|
// src/core/api/contract.ts
|
|
4948
|
-
import { z as
|
|
4949
|
-
var idList =
|
|
4950
|
-
var taskChangesSchema =
|
|
4951
|
-
title:
|
|
4952
|
-
type:
|
|
4953
|
-
status:
|
|
4954
|
-
priority:
|
|
4955
|
-
tags:
|
|
5147
|
+
import { z as z14 } from "zod";
|
|
5148
|
+
var idList = z14.array(taskIdSchema);
|
|
5149
|
+
var taskChangesSchema = z14.strictObject({
|
|
5150
|
+
title: z14.string().optional(),
|
|
5151
|
+
type: z14.enum(TASK_TYPES).optional(),
|
|
5152
|
+
status: z14.enum(TASK_STATUSES).optional(),
|
|
5153
|
+
priority: z14.enum(PRIORITIES).optional(),
|
|
5154
|
+
tags: z14.array(z14.string()).optional(),
|
|
4956
5155
|
epic: taskIdSchema.nullable().optional(),
|
|
4957
5156
|
blockedBy: idList.optional(),
|
|
4958
5157
|
related: idList.optional(),
|
|
4959
|
-
body:
|
|
4960
|
-
category:
|
|
5158
|
+
body: z14.string().optional(),
|
|
5159
|
+
category: z14.enum(TASK_CATEGORIES).nullable().optional()
|
|
4961
5160
|
});
|
|
4962
|
-
var projectActiveSchema =
|
|
4963
|
-
var projectDeleteSchema =
|
|
4964
|
-
var settingsRequestSchema =
|
|
4965
|
-
var updateTaskRequestSchema =
|
|
4966
|
-
version:
|
|
5161
|
+
var projectActiveSchema = z14.strictObject({ active: z14.boolean() });
|
|
5162
|
+
var projectDeleteSchema = z14.strictObject({ confirm: z14.string() });
|
|
5163
|
+
var settingsRequestSchema = z14.strictObject(settingsSchema.shape);
|
|
5164
|
+
var updateTaskRequestSchema = z14.strictObject({
|
|
5165
|
+
version: z14.string().min(1),
|
|
4967
5166
|
changes: taskChangesSchema
|
|
4968
5167
|
});
|
|
4969
|
-
var batchPreviousSchema =
|
|
4970
|
-
status:
|
|
4971
|
-
priority:
|
|
5168
|
+
var batchPreviousSchema = z14.strictObject({
|
|
5169
|
+
status: z14.enum(TASK_STATUSES),
|
|
5170
|
+
priority: z14.enum(PRIORITIES),
|
|
4972
5171
|
epic: taskIdSchema.nullable(),
|
|
4973
|
-
resolution:
|
|
4974
|
-
reason:
|
|
5172
|
+
resolution: z14.enum(RESOLUTIONS).nullable(),
|
|
5173
|
+
reason: z14.string().nullable()
|
|
4975
5174
|
});
|
|
4976
5175
|
var BATCH_TASKS_LIMIT = 500;
|
|
4977
|
-
var batchRequestSchema =
|
|
4978
|
-
tasks:
|
|
4979
|
-
action:
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
5176
|
+
var batchRequestSchema = z14.strictObject({
|
|
5177
|
+
tasks: z14.array(z14.strictObject({ id: taskIdSchema, version: z14.string().min(1) })).min(1).max(BATCH_TASKS_LIMIT).refine((tasks) => new Set(tasks.map((task) => task.id)).size === tasks.length),
|
|
5178
|
+
action: z14.discriminatedUnion("kind", [
|
|
5179
|
+
z14.strictObject({ kind: z14.literal("close"), reason: z14.string().transform((reason) => reason.replace(/\s*\n\s*/g, " ").trim()).pipe(z14.string().min(1)) }),
|
|
5180
|
+
z14.strictObject({ kind: z14.literal("priority"), priority: z14.enum(PRIORITIES) }),
|
|
5181
|
+
z14.strictObject({ kind: z14.literal("epic"), epic: taskIdSchema.nullable() }),
|
|
5182
|
+
z14.strictObject({ kind: z14.literal("restore"), changes: z14.record(taskIdSchema, batchPreviousSchema) })
|
|
4984
5183
|
])
|
|
4985
5184
|
});
|
|
4986
5185
|
|
|
@@ -5085,7 +5284,7 @@ function createReportCache({ ttlMs, now }) {
|
|
|
5085
5284
|
|
|
5086
5285
|
// src/server/revisions.ts
|
|
5087
5286
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
5088
|
-
import { stat as
|
|
5287
|
+
import { stat as stat5 } from "node:fs/promises";
|
|
5089
5288
|
import { basename as basename9 } from "node:path";
|
|
5090
5289
|
function createRevisions(boot = randomUUID3()) {
|
|
5091
5290
|
let seq = 0;
|
|
@@ -5117,7 +5316,7 @@ function createRevisions(boot = randomUUID3()) {
|
|
|
5117
5316
|
};
|
|
5118
5317
|
}
|
|
5119
5318
|
async function markOf(path) {
|
|
5120
|
-
const [text, stats] = await Promise.all([readTextOrNull(path),
|
|
5319
|
+
const [text, stats] = await Promise.all([readTextOrNull(path), stat5(path).catch(() => null)]);
|
|
5121
5320
|
return text === null || stats === null ? null : { version: contentVersion(text), mtimeMs: stats.mtimeMs };
|
|
5122
5321
|
}
|
|
5123
5322
|
|
|
@@ -5125,26 +5324,26 @@ async function markOf(path) {
|
|
|
5125
5324
|
import { Hono } from "hono";
|
|
5126
5325
|
|
|
5127
5326
|
// src/core/code/code-cache.ts
|
|
5128
|
-
import { join as
|
|
5129
|
-
import { z as
|
|
5327
|
+
import { join as join23 } from "node:path";
|
|
5328
|
+
import { z as z15 } from "zod";
|
|
5130
5329
|
var CODE_CACHE_FILE = ".code-cache.json";
|
|
5131
5330
|
var CODE_CACHE_VERSION = 1;
|
|
5132
|
-
var repoCodeSchema =
|
|
5133
|
-
commits:
|
|
5134
|
-
lines:
|
|
5135
|
-
units:
|
|
5331
|
+
var repoCodeSchema = z15.object({
|
|
5332
|
+
commits: z15.array(z15.array(z15.string())),
|
|
5333
|
+
lines: z15.array(z15.object({ path: z15.string(), lines: z15.number() })),
|
|
5334
|
+
units: z15.array(z15.object({ date: z15.string(), lines: z15.number() }))
|
|
5136
5335
|
});
|
|
5137
|
-
var fixCommitSchema =
|
|
5138
|
-
var snapshotSchema =
|
|
5139
|
-
version:
|
|
5140
|
-
repos:
|
|
5141
|
-
fixes:
|
|
5336
|
+
var fixCommitSchema = z15.object({ date: z15.string(), landedAt: z15.string().optional(), byAgent: z15.boolean(), lines: z15.number(), testLines: z15.number() });
|
|
5337
|
+
var snapshotSchema = z15.object({
|
|
5338
|
+
version: z15.literal(CODE_CACHE_VERSION),
|
|
5339
|
+
repos: z15.record(z15.string(), z15.object({ key: z15.string(), code: repoCodeSchema })),
|
|
5340
|
+
fixes: z15.record(z15.string(), fixCommitSchema)
|
|
5142
5341
|
});
|
|
5143
5342
|
function emptyCodeCache() {
|
|
5144
5343
|
return { repos: {}, fixes: {} };
|
|
5145
5344
|
}
|
|
5146
5345
|
function createCodeCacheFile(root) {
|
|
5147
|
-
const path =
|
|
5346
|
+
const path = join23(root, CODE_CACHE_FILE);
|
|
5148
5347
|
return {
|
|
5149
5348
|
read: async () => {
|
|
5150
5349
|
const snapshot = await readJsonFile(path, snapshotSchema);
|
|
@@ -5541,13 +5740,13 @@ function codeFixRequests(input, base = reportBase(input)) {
|
|
|
5541
5740
|
}
|
|
5542
5741
|
|
|
5543
5742
|
// src/core/stats/cost/token-counts.ts
|
|
5544
|
-
import { z as
|
|
5545
|
-
var tokenCountsSchema =
|
|
5546
|
-
input:
|
|
5547
|
-
cacheWrite5m:
|
|
5548
|
-
cacheWrite1h:
|
|
5549
|
-
cacheRead:
|
|
5550
|
-
output:
|
|
5743
|
+
import { z as z16 } from "zod";
|
|
5744
|
+
var tokenCountsSchema = z16.object({
|
|
5745
|
+
input: z16.number(),
|
|
5746
|
+
cacheWrite5m: z16.number(),
|
|
5747
|
+
cacheWrite1h: z16.number(),
|
|
5748
|
+
cacheRead: z16.number(),
|
|
5749
|
+
output: z16.number()
|
|
5551
5750
|
});
|
|
5552
5751
|
var ZERO_TOKENS = { input: 0, cacheWrite5m: 0, cacheWrite1h: 0, cacheRead: 0, output: 0 };
|
|
5553
5752
|
function addTokens(a, b) {
|
|
@@ -5562,24 +5761,24 @@ function totalTokens(tokens) {
|
|
|
5562
5761
|
}
|
|
5563
5762
|
|
|
5564
5763
|
// src/core/stats/cost/usage-state.ts
|
|
5565
|
-
import { z as
|
|
5764
|
+
import { z as z17 } from "zod";
|
|
5566
5765
|
var COST_REPORT_DAYS = 30;
|
|
5567
|
-
var estimatedKindSchema =
|
|
5568
|
-
var usageBucketSchema =
|
|
5569
|
-
slot:
|
|
5570
|
-
cwd:
|
|
5571
|
-
model:
|
|
5572
|
-
kind:
|
|
5766
|
+
var estimatedKindSchema = z17.enum(["cli", "skill"]);
|
|
5767
|
+
var usageBucketSchema = z17.object({
|
|
5768
|
+
slot: z17.string(),
|
|
5769
|
+
cwd: z17.string(),
|
|
5770
|
+
model: z17.string(),
|
|
5771
|
+
kind: z17.enum(["hook", ...estimatedKindSchema.options]),
|
|
5573
5772
|
tokens: tokenCountsSchema,
|
|
5574
|
-
hookTurns:
|
|
5773
|
+
hookTurns: z17.number()
|
|
5575
5774
|
});
|
|
5576
|
-
var transcriptStateSchema =
|
|
5577
|
-
hookOpen:
|
|
5578
|
-
lastModel:
|
|
5579
|
-
lastMessageId:
|
|
5775
|
+
var transcriptStateSchema = z17.object({
|
|
5776
|
+
hookOpen: z17.boolean(),
|
|
5777
|
+
lastModel: z17.string().nullable(),
|
|
5778
|
+
lastMessageId: z17.string().nullable(),
|
|
5580
5779
|
lastMessageTokens: tokenCountsSchema.nullable().optional(),
|
|
5581
|
-
pending:
|
|
5582
|
-
pendingEstimates:
|
|
5780
|
+
pending: z17.record(z17.string(), estimatedKindSchema),
|
|
5781
|
+
pendingEstimates: z17.array(z17.object({ kind: estimatedKindSchema, chars: z17.number(), slot: z17.string(), cwd: z17.string() }))
|
|
5583
5782
|
});
|
|
5584
5783
|
|
|
5585
5784
|
// src/core/stats/cost/pricing.ts
|
|
@@ -6289,7 +6488,7 @@ function createApp({ root, readLanguage: readLanguage2, changes, allowedHosts, h
|
|
|
6289
6488
|
});
|
|
6290
6489
|
if (staticDir !== void 0) {
|
|
6291
6490
|
app.use("*", serveStatic({ root: staticDir }));
|
|
6292
|
-
app.get("*", serveStatic({ path:
|
|
6491
|
+
app.get("*", serveStatic({ path: join24(staticDir, "index.html") }));
|
|
6293
6492
|
}
|
|
6294
6493
|
return app;
|
|
6295
6494
|
}
|
|
@@ -6401,12 +6600,12 @@ function logReport({ closedEpics, reopenedEpics, blockingFiles, deleted, conflic
|
|
|
6401
6600
|
}
|
|
6402
6601
|
|
|
6403
6602
|
// src/core/usage/transcripts.ts
|
|
6404
|
-
import { createHash as
|
|
6405
|
-
import { open as open2, stat as
|
|
6406
|
-
import { basename as basename10, join as
|
|
6603
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
6604
|
+
import { open as open2, stat as stat6 } from "node:fs/promises";
|
|
6605
|
+
import { basename as basename10, join as join26 } from "node:path";
|
|
6407
6606
|
|
|
6408
6607
|
// src/core/stats/cost/attribute.ts
|
|
6409
|
-
import { z as
|
|
6608
|
+
import { z as z18 } from "zod";
|
|
6410
6609
|
|
|
6411
6610
|
// src/core/stats/cost/shell-commands.ts
|
|
6412
6611
|
var SEPARATORS = ["&&", "||", ";", "|", "&", "\n", "`"];
|
|
@@ -6599,21 +6798,21 @@ var FAST_SPEED = "fast";
|
|
|
6599
6798
|
var CHARS_PER_TOKEN = 3;
|
|
6600
6799
|
var PENDING_TOOL_LIMIT = 64;
|
|
6601
6800
|
var ZONE_OFFSET_STEP_MS = 15 * 60 * 1e3;
|
|
6602
|
-
var usageSchema =
|
|
6603
|
-
input_tokens:
|
|
6604
|
-
output_tokens:
|
|
6605
|
-
cache_read_input_tokens:
|
|
6606
|
-
cache_creation_input_tokens:
|
|
6607
|
-
speed:
|
|
6608
|
-
cache_creation:
|
|
6801
|
+
var usageSchema = z18.object({
|
|
6802
|
+
input_tokens: z18.number().optional(),
|
|
6803
|
+
output_tokens: z18.number().optional(),
|
|
6804
|
+
cache_read_input_tokens: z18.number().optional(),
|
|
6805
|
+
cache_creation_input_tokens: z18.number().optional(),
|
|
6806
|
+
speed: z18.string().optional(),
|
|
6807
|
+
cache_creation: z18.object({ ephemeral_5m_input_tokens: z18.number().optional(), ephemeral_1h_input_tokens: z18.number().optional() }).passthrough().optional()
|
|
6609
6808
|
}).passthrough();
|
|
6610
|
-
var assistantMessageSchema =
|
|
6611
|
-
var userMessageSchema =
|
|
6612
|
-
var toolUseBlockSchema =
|
|
6613
|
-
var toolResultBlockSchema =
|
|
6614
|
-
var textBlockSchema =
|
|
6615
|
-
var bashInputSchema =
|
|
6616
|
-
var lineSchema =
|
|
6809
|
+
var assistantMessageSchema = z18.object({ id: z18.string().optional(), model: z18.string().optional(), usage: usageSchema.optional(), content: z18.array(z18.unknown()).optional() }).passthrough();
|
|
6810
|
+
var userMessageSchema = z18.object({ content: z18.unknown().optional() }).passthrough();
|
|
6811
|
+
var toolUseBlockSchema = z18.object({ type: z18.literal("tool_use"), id: z18.string(), name: z18.string(), input: z18.unknown().optional() }).passthrough();
|
|
6812
|
+
var toolResultBlockSchema = z18.object({ type: z18.literal("tool_result"), tool_use_id: z18.string().optional(), content: z18.unknown().optional() }).passthrough();
|
|
6813
|
+
var textBlockSchema = z18.object({ type: z18.literal("text"), text: z18.string() }).passthrough();
|
|
6814
|
+
var bashInputSchema = z18.object({ command: z18.string() }).passthrough();
|
|
6815
|
+
var lineSchema = z18.object({ type: z18.string().optional(), timestamp: z18.string().optional(), cwd: z18.string().optional(), isMeta: z18.boolean().optional(), message: z18.unknown().optional() }).passthrough();
|
|
6617
6816
|
function newTranscriptState() {
|
|
6618
6817
|
return { hookOpen: false, lastModel: null, lastMessageId: null, lastMessageTokens: null, pending: {}, pendingEstimates: [] };
|
|
6619
6818
|
}
|
|
@@ -6737,53 +6936,53 @@ function slotOf(timestamp) {
|
|
|
6737
6936
|
|
|
6738
6937
|
// src/core/usage/usage-cache.ts
|
|
6739
6938
|
import { mkdir as mkdir5 } from "node:fs/promises";
|
|
6740
|
-
import { join as
|
|
6741
|
-
import { z as
|
|
6939
|
+
import { join as join25 } from "node:path";
|
|
6940
|
+
import { z as z19 } from "zod";
|
|
6742
6941
|
var USAGE_CACHE_FILE = ".usage-cache.json";
|
|
6743
6942
|
var USAGE_CACHE_VERSION = 6;
|
|
6744
|
-
var usageCacheEntrySchema =
|
|
6745
|
-
size:
|
|
6746
|
-
mtimeMs:
|
|
6747
|
-
offset:
|
|
6748
|
-
fingerprint:
|
|
6943
|
+
var usageCacheEntrySchema = z19.object({
|
|
6944
|
+
size: z19.number(),
|
|
6945
|
+
mtimeMs: z19.number().optional(),
|
|
6946
|
+
offset: z19.number(),
|
|
6947
|
+
fingerprint: z19.string(),
|
|
6749
6948
|
state: transcriptStateSchema,
|
|
6750
|
-
buckets:
|
|
6949
|
+
buckets: z19.array(usageBucketSchema)
|
|
6751
6950
|
});
|
|
6752
|
-
var usageCacheSchema =
|
|
6753
|
-
version:
|
|
6754
|
-
files:
|
|
6951
|
+
var usageCacheSchema = z19.object({
|
|
6952
|
+
version: z19.literal(USAGE_CACHE_VERSION),
|
|
6953
|
+
files: z19.record(z19.string(), usageCacheEntrySchema)
|
|
6755
6954
|
});
|
|
6756
6955
|
function emptyUsageCache() {
|
|
6757
6956
|
return { version: USAGE_CACHE_VERSION, files: {} };
|
|
6758
6957
|
}
|
|
6759
6958
|
async function readUsageCache(root) {
|
|
6760
|
-
return await readJsonFile(
|
|
6959
|
+
return await readJsonFile(join25(root, USAGE_CACHE_FILE), usageCacheSchema) ?? emptyUsageCache();
|
|
6761
6960
|
}
|
|
6762
6961
|
async function writeUsageCache(root, cache) {
|
|
6763
6962
|
await mkdir5(root, { recursive: true });
|
|
6764
|
-
await writeFileAtomic(
|
|
6963
|
+
await writeFileAtomic(join25(root, USAGE_CACHE_FILE), JSON.stringify(cache));
|
|
6765
6964
|
}
|
|
6766
6965
|
|
|
6767
6966
|
// src/core/usage/transcripts.ts
|
|
6768
6967
|
var NEWLINE = 10;
|
|
6769
6968
|
var FINGERPRINT_BYTES = 256;
|
|
6770
6969
|
var ABANDONED_LINE_MS = 10 * 60 * 1e3;
|
|
6771
|
-
var EMPTY_FINGERPRINT =
|
|
6970
|
+
var EMPTY_FINGERPRINT = createHash5("sha1").digest("hex");
|
|
6772
6971
|
var COUNTED_LINE_MARKERS = ['"type":"assistant"', '"type":"user"'];
|
|
6773
6972
|
async function listTranscripts(claudeProjectsDir2) {
|
|
6774
6973
|
const files = [];
|
|
6775
6974
|
for (const projectEntry of await listDir(claudeProjectsDir2)) {
|
|
6776
6975
|
if (!projectEntry.isDirectory()) continue;
|
|
6777
|
-
const projectDir2 =
|
|
6976
|
+
const projectDir2 = join26(claudeProjectsDir2, projectEntry.name);
|
|
6778
6977
|
for (const entry of await listDir(projectDir2)) {
|
|
6779
|
-
const entryPath =
|
|
6978
|
+
const entryPath = join26(projectDir2, entry.name);
|
|
6780
6979
|
if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
6781
6980
|
files.push(...await listedFile(entryPath));
|
|
6782
6981
|
continue;
|
|
6783
6982
|
}
|
|
6784
6983
|
if (!entry.isDirectory()) continue;
|
|
6785
|
-
for (const subagentEntry of await listDir(
|
|
6786
|
-
if (subagentEntry.isFile() && subagentEntry.name.endsWith(".jsonl")) files.push(...await listedFile(
|
|
6984
|
+
for (const subagentEntry of await listDir(join26(entryPath, "subagents"), { recursive: true })) {
|
|
6985
|
+
if (subagentEntry.isFile() && subagentEntry.name.endsWith(".jsonl")) files.push(...await listedFile(join26(subagentEntry.parentPath, subagentEntry.name)));
|
|
6787
6986
|
}
|
|
6788
6987
|
}
|
|
6789
6988
|
}
|
|
@@ -6791,7 +6990,7 @@ async function listTranscripts(claudeProjectsDir2) {
|
|
|
6791
6990
|
}
|
|
6792
6991
|
async function listedFile(path) {
|
|
6793
6992
|
try {
|
|
6794
|
-
const { size, mtimeMs } = await
|
|
6993
|
+
const { size, mtimeMs } = await stat6(path);
|
|
6795
6994
|
return [{ path, size, mtimeMs }];
|
|
6796
6995
|
} catch {
|
|
6797
6996
|
return [];
|
|
@@ -6856,7 +7055,7 @@ async function fingerprintOf(path, offset) {
|
|
|
6856
7055
|
return withFile(path, async (handle) => {
|
|
6857
7056
|
const head = await readAt(handle, 0, edge);
|
|
6858
7057
|
const tail = await readAt(handle, offset - edge, edge);
|
|
6859
|
-
return
|
|
7058
|
+
return createHash5("sha1").update(head).update(tail).digest("hex");
|
|
6860
7059
|
});
|
|
6861
7060
|
}
|
|
6862
7061
|
function readChunk(path, position, length) {
|
|
@@ -6974,7 +7173,7 @@ function progressBefore(files, cache) {
|
|
|
6974
7173
|
// src/server/start.ts
|
|
6975
7174
|
var SWEEP_INTERVAL_MS = 60 * 60 * 1e3;
|
|
6976
7175
|
var STOP_SIGNALS = ["SIGTERM", "SIGINT", "SIGHUP"];
|
|
6977
|
-
var BUNDLED_WEB_DIR =
|
|
7176
|
+
var BUNDLED_WEB_DIR = join27(import.meta.dirname, "web");
|
|
6978
7177
|
var log = (line) => void process.stdout.write(`${line}
|
|
6979
7178
|
`);
|
|
6980
7179
|
var warn = (line) => void process.stderr.write(`${line}
|
|
@@ -7075,7 +7274,7 @@ async function runServe(args, io) {
|
|
|
7075
7274
|
|
|
7076
7275
|
// src/cli/service/launchd.ts
|
|
7077
7276
|
import { mkdir as mkdir7, rm as rm5, writeFile as writeFile4 } from "node:fs/promises";
|
|
7078
|
-
import { dirname as dirname8, join as
|
|
7277
|
+
import { dirname as dirname8, join as join28, posix } from "node:path";
|
|
7079
7278
|
import { setTimeout as sleep3 } from "node:timers/promises";
|
|
7080
7279
|
|
|
7081
7280
|
// src/cli/service/service.ts
|
|
@@ -7148,7 +7347,7 @@ ${entries}
|
|
|
7148
7347
|
`;
|
|
7149
7348
|
}
|
|
7150
7349
|
function launchdManager(context, delay = sleep3) {
|
|
7151
|
-
const file =
|
|
7350
|
+
const file = join28(context.home, "Library/LaunchAgents", `${LABEL}.plist`);
|
|
7152
7351
|
const domain = `gui/${context.uid}`;
|
|
7153
7352
|
const bootout = () => context.exec("launchctl", ["bootout", `${domain}/${LABEL}`]);
|
|
7154
7353
|
const bootstrap = () => context.exec("launchctl", ["bootstrap", domain, file]);
|
|
@@ -7183,7 +7382,7 @@ function launchdManager(context, delay = sleep3) {
|
|
|
7183
7382
|
|
|
7184
7383
|
// src/cli/service/startup-folder.ts
|
|
7185
7384
|
import { mkdir as mkdir8, rm as rm6, writeFile as writeFile5 } from "node:fs/promises";
|
|
7186
|
-
import { dirname as dirname9, join as
|
|
7385
|
+
import { dirname as dirname9, join as join29, win32 } from "node:path";
|
|
7187
7386
|
var SCRIPT_NAME = "p-backlog.vbs";
|
|
7188
7387
|
var COMMAND_LINE_ARGUMENT = /"[^"]*"|\S+/g;
|
|
7189
7388
|
var PROCESS_QUERY_TIMEOUT_MS = 15e3;
|
|
@@ -7191,16 +7390,16 @@ function vbsString(value) {
|
|
|
7191
7390
|
return `"${value.replaceAll('"', '""')}"`;
|
|
7192
7391
|
}
|
|
7193
7392
|
function appDataDir(context) {
|
|
7194
|
-
return
|
|
7393
|
+
return join29(context.env.LOCALAPPDATA ?? join29(context.home, "AppData", "Local"), "p-backlog");
|
|
7195
7394
|
}
|
|
7196
7395
|
function startupFolder(context) {
|
|
7197
|
-
return
|
|
7396
|
+
return join29(context.env.APPDATA ?? join29(context.home, "AppData", "Roaming"), "Microsoft", "Windows", "Start Menu", "Programs", "Startup");
|
|
7198
7397
|
}
|
|
7199
7398
|
function logPath2(context) {
|
|
7200
|
-
return
|
|
7399
|
+
return join29(appDataDir(context), "p-backlog.log");
|
|
7201
7400
|
}
|
|
7202
7401
|
function pidFilePath(context) {
|
|
7203
|
-
return
|
|
7402
|
+
return join29(appDataDir(context), "server.pid");
|
|
7204
7403
|
}
|
|
7205
7404
|
var NODE_PATH_ENV = "P_BACKLOG_NODE";
|
|
7206
7405
|
var CLI_PATH_ENV = "P_BACKLOG_CLI";
|
|
@@ -7251,7 +7450,7 @@ function sameWindowsPath(left, right) {
|
|
|
7251
7450
|
return win32.normalize(left).toLowerCase() === win32.normalize(right).toLowerCase();
|
|
7252
7451
|
}
|
|
7253
7452
|
function startupFolderManager(context) {
|
|
7254
|
-
const file =
|
|
7453
|
+
const file = join29(startupFolder(context), SCRIPT_NAME);
|
|
7255
7454
|
return {
|
|
7256
7455
|
file,
|
|
7257
7456
|
logs: logPath2(context),
|
|
@@ -7278,7 +7477,7 @@ function startupFolderManager(context) {
|
|
|
7278
7477
|
|
|
7279
7478
|
// src/cli/service/systemd.ts
|
|
7280
7479
|
import { mkdir as mkdir9, rm as rm7, writeFile as writeFile6 } from "node:fs/promises";
|
|
7281
|
-
import { dirname as dirname10, join as
|
|
7480
|
+
import { dirname as dirname10, join as join30 } from "node:path";
|
|
7282
7481
|
var UNIT = "p-backlog.service";
|
|
7283
7482
|
function quoted(value) {
|
|
7284
7483
|
return `"${value.replace(/[\\"]/g, "\\$&").replaceAll("%", "%%")}"`;
|
|
@@ -7312,7 +7511,7 @@ async function systemctlSteps(exec, steps) {
|
|
|
7312
7511
|
return "done";
|
|
7313
7512
|
}
|
|
7314
7513
|
function systemdManager(context) {
|
|
7315
|
-
const file =
|
|
7514
|
+
const file = join30(context.home, ".config/systemd/user", UNIT);
|
|
7316
7515
|
return {
|
|
7317
7516
|
file,
|
|
7318
7517
|
logs: "journalctl --user -u p-backlog",
|
|
@@ -7430,50 +7629,43 @@ function reportFailure({ failed, code, output }, io) {
|
|
|
7430
7629
|
}
|
|
7431
7630
|
|
|
7432
7631
|
// src/cli/commands/setup.ts
|
|
7433
|
-
import { join as
|
|
7632
|
+
import { join as join32 } from "node:path";
|
|
7434
7633
|
|
|
7435
|
-
// src/cli/
|
|
7436
|
-
import {
|
|
7634
|
+
// src/cli/agents/agent-hooks.ts
|
|
7635
|
+
import { join as join31 } from "node:path";
|
|
7636
|
+
|
|
7637
|
+
// src/cli/agents/grouped-stop-hooks.ts
|
|
7638
|
+
import { z as z20 } from "zod";
|
|
7639
|
+
|
|
7640
|
+
// src/cli/agents/json-config.ts
|
|
7641
|
+
import { mkdir as mkdir10, readFile as readFile5, readlink as readlink2, realpath as realpath2, stat as stat7 } from "node:fs/promises";
|
|
7437
7642
|
import { dirname as dirname11, resolve as resolve4 } from "node:path";
|
|
7438
|
-
import { z as z18 } from "zod";
|
|
7439
|
-
var POSIX_COMMAND = "command -v backlog >/dev/null && backlog hook stop || true";
|
|
7440
|
-
var POWERSHELL_COMMAND = "if (Get-Command backlog.cmd -ErrorAction SilentlyContinue) { backlog.cmd hook stop }";
|
|
7441
7643
|
var MAX_LINK_HOPS = 40;
|
|
7442
|
-
|
|
7443
|
-
|
|
7444
|
-
function stopHookFor(platform) {
|
|
7445
|
-
return platform === "win32" ? { type: "command", shell: "powershell", command: POWERSHELL_COMMAND } : { type: "command", command: POSIX_COMMAND };
|
|
7446
|
-
}
|
|
7447
|
-
function isOurStopHook(hook) {
|
|
7448
|
-
if (typeof hook !== "object" || hook === null) return false;
|
|
7449
|
-
const command = hook.command;
|
|
7450
|
-
return command === POSIX_COMMAND || command === POWERSHELL_COMMAND;
|
|
7451
|
-
}
|
|
7452
|
-
async function addStopHook(settingsPath, platform) {
|
|
7453
|
-
const text = await readSettingsText(settingsPath);
|
|
7644
|
+
async function readJsonConfig(path, schema) {
|
|
7645
|
+
const text = await readConfigText(path);
|
|
7454
7646
|
if (typeof text !== "string") return text;
|
|
7455
|
-
const settings = validatedSettings(text);
|
|
7456
|
-
if (settings === null) return { failed: "invalid" };
|
|
7457
|
-
const hooks = settings.hooks ??= {};
|
|
7458
|
-
const stopGroups = hooks.Stop ??= [];
|
|
7459
|
-
const installed = stopGroups.some((group) => group.hooks?.some(isOurStopHook) ?? false);
|
|
7460
|
-
if (installed) return "exists";
|
|
7461
|
-
stopGroups.push({ hooks: [stopHookFor(platform)] });
|
|
7462
|
-
await mkdir10(dirname11(settingsPath), { recursive: true });
|
|
7463
|
-
const target = await writeTargetOf(settingsPath);
|
|
7464
|
-
const mode = await stat6(target).then(({ mode: mode2 }) => mode2 & 511, () => void 0);
|
|
7465
|
-
await writeFileAtomic(target, `${JSON.stringify(settings, null, 2)}
|
|
7466
|
-
`, mode);
|
|
7467
|
-
return "added";
|
|
7468
|
-
}
|
|
7469
|
-
function validatedSettings(text) {
|
|
7470
7647
|
let value;
|
|
7471
7648
|
try {
|
|
7472
7649
|
value = JSON.parse(text);
|
|
7473
7650
|
} catch {
|
|
7474
|
-
return
|
|
7651
|
+
return { failed: "invalid" };
|
|
7652
|
+
}
|
|
7653
|
+
return schema.safeParse(value).success ? { config: value } : { failed: "invalid" };
|
|
7654
|
+
}
|
|
7655
|
+
async function writeJsonConfig(path, config) {
|
|
7656
|
+
await mkdir10(dirname11(path), { recursive: true });
|
|
7657
|
+
const target = await writeTargetOf(path);
|
|
7658
|
+
const mode = await stat7(target).then(({ mode: mode2 }) => mode2 & 511, () => void 0);
|
|
7659
|
+
await writeFileAtomic(target, `${JSON.stringify(config, null, 2)}
|
|
7660
|
+
`, mode);
|
|
7661
|
+
}
|
|
7662
|
+
async function readConfigText(path) {
|
|
7663
|
+
try {
|
|
7664
|
+
return await readFile5(path, "utf8");
|
|
7665
|
+
} catch (error) {
|
|
7666
|
+
if (hasErrorCode(error, "ENOENT")) return "{}";
|
|
7667
|
+
return { failed: "unreadable", code: errorCodeOrText(error) };
|
|
7475
7668
|
}
|
|
7476
|
-
return settingsSchema2.safeParse(value).success ? value : null;
|
|
7477
7669
|
}
|
|
7478
7670
|
async function writeTargetOf(path) {
|
|
7479
7671
|
try {
|
|
@@ -7492,62 +7684,240 @@ async function danglingLinkTarget(path) {
|
|
|
7492
7684
|
}
|
|
7493
7685
|
return current;
|
|
7494
7686
|
}
|
|
7495
|
-
|
|
7496
|
-
|
|
7497
|
-
|
|
7498
|
-
|
|
7499
|
-
|
|
7500
|
-
|
|
7687
|
+
|
|
7688
|
+
// src/cli/agents/grouped-stop-hooks.ts
|
|
7689
|
+
var stopGroupSchema = z20.object({ hooks: z20.array(z20.unknown()).optional() }).passthrough();
|
|
7690
|
+
var groupedConfigSchema = z20.object({ hooks: z20.object({ Stop: z20.array(stopGroupSchema).optional() }).passthrough().optional() }).passthrough();
|
|
7691
|
+
function refreshOurHook(lists, hook, { isOurs, isCurrent }) {
|
|
7692
|
+
const ours = lists.flatMap((list) => list.flatMap((entry, index) => isOurs(entry) ? [{ list, index, entry }] : []));
|
|
7693
|
+
if (ours.some(({ entry }) => isCurrent(entry))) return "exists";
|
|
7694
|
+
const stale = ours[0];
|
|
7695
|
+
if (stale === void 0) return "missing";
|
|
7696
|
+
stale.list[stale.index] = { ...stale.entry, ...hook };
|
|
7697
|
+
return "updated";
|
|
7698
|
+
}
|
|
7699
|
+
async function addGroupedStopHook(path, hook, ourHook) {
|
|
7700
|
+
const read = await readJsonConfig(path, groupedConfigSchema);
|
|
7701
|
+
if (!("config" in read)) return read;
|
|
7702
|
+
const stopGroups = (read.config.hooks ??= {}).Stop ??= [];
|
|
7703
|
+
const refreshed = refreshOurHook(stopGroups.flatMap((group) => group.hooks === void 0 ? [] : [group.hooks]), hook, ourHook);
|
|
7704
|
+
if (refreshed === "exists") return "exists";
|
|
7705
|
+
if (refreshed === "missing") stopGroups.push({ hooks: [hook] });
|
|
7706
|
+
await writeJsonConfig(path, read.config);
|
|
7707
|
+
return refreshed === "missing" ? "added" : "updated";
|
|
7708
|
+
}
|
|
7709
|
+
async function removeGroupedStopHook(path, isOurs) {
|
|
7710
|
+
const read = await readJsonConfig(path, groupedConfigSchema);
|
|
7711
|
+
if (!("config" in read)) return read;
|
|
7712
|
+
const hooks = read.config.hooks;
|
|
7713
|
+
if (hooks?.Stop === void 0 || !hooks.Stop.some((group) => hasOurHook(group, isOurs))) return "absent";
|
|
7714
|
+
const kept = hooks.Stop.flatMap((group) => {
|
|
7715
|
+
if (!hasOurHook(group, isOurs)) return [group];
|
|
7716
|
+
const others = (group.hooks ?? []).filter((hook) => !isOurs(hook));
|
|
7717
|
+
return others.length > 0 ? [{ ...group, hooks: others }] : [];
|
|
7718
|
+
});
|
|
7719
|
+
if (kept.length > 0) hooks.Stop = kept;
|
|
7720
|
+
else delete hooks.Stop;
|
|
7721
|
+
await writeJsonConfig(path, read.config);
|
|
7722
|
+
return "removed";
|
|
7723
|
+
}
|
|
7724
|
+
function hasOurHook(group, isOurs) {
|
|
7725
|
+
return group.hooks?.some(isOurs) ?? false;
|
|
7726
|
+
}
|
|
7727
|
+
|
|
7728
|
+
// src/cli/stop-hook.ts
|
|
7729
|
+
var POSIX_COMMAND = "command -v backlog >/dev/null && backlog hook stop || true";
|
|
7730
|
+
var POWERSHELL_COMMAND = "if (Get-Command backlog.cmd -ErrorAction SilentlyContinue) { backlog.cmd hook stop }";
|
|
7731
|
+
function stopHookFor(platform) {
|
|
7732
|
+
return platform === "win32" ? { type: "command", shell: "powershell", command: POWERSHELL_COMMAND } : { type: "command", command: POSIX_COMMAND };
|
|
7733
|
+
}
|
|
7734
|
+
function isOurStopHook(hook) {
|
|
7735
|
+
if (typeof hook !== "object" || hook === null) return false;
|
|
7736
|
+
const command = hook.command;
|
|
7737
|
+
return command === POSIX_COMMAND || command === POWERSHELL_COMMAND;
|
|
7738
|
+
}
|
|
7739
|
+
function addStopHook(settingsPath, platform) {
|
|
7740
|
+
return addGroupedStopHook(settingsPath, stopHookFor(platform), { isOurs: isOurStopHook, isCurrent: isOurStopHook });
|
|
7741
|
+
}
|
|
7742
|
+
function removeStopHook(settingsPath) {
|
|
7743
|
+
return removeGroupedStopHook(settingsPath, isOurStopHook);
|
|
7744
|
+
}
|
|
7745
|
+
|
|
7746
|
+
// src/cli/agents/cursor-hooks.ts
|
|
7747
|
+
import { z as z21 } from "zod";
|
|
7748
|
+
var CURSOR_HOOKS_VERSION = 1;
|
|
7749
|
+
var cursorConfigSchema = z21.object({ version: z21.number().optional(), hooks: z21.object({ stop: z21.array(z21.unknown()).optional() }).passthrough().optional() }).passthrough();
|
|
7750
|
+
async function addCursorStopHook(path, hook, ourHook) {
|
|
7751
|
+
const read = await readJsonConfig(path, cursorConfigSchema);
|
|
7752
|
+
if (!("config" in read)) return read;
|
|
7753
|
+
const stop = (read.config.hooks ??= {}).stop ??= [];
|
|
7754
|
+
const refreshed = refreshOurHook([stop], hook, ourHook);
|
|
7755
|
+
if (refreshed === "exists") return "exists";
|
|
7756
|
+
if (refreshed === "missing") stop.push(hook);
|
|
7757
|
+
read.config.version ??= CURSOR_HOOKS_VERSION;
|
|
7758
|
+
await writeJsonConfig(path, read.config);
|
|
7759
|
+
return refreshed === "missing" ? "added" : "updated";
|
|
7760
|
+
}
|
|
7761
|
+
async function removeCursorStopHook(path, isOurs) {
|
|
7762
|
+
const read = await readJsonConfig(path, cursorConfigSchema);
|
|
7763
|
+
if (!("config" in read)) return read;
|
|
7764
|
+
const hooks = read.config.hooks;
|
|
7765
|
+
if (hooks?.stop === void 0 || !hooks.stop.some(isOurs)) return "absent";
|
|
7766
|
+
const kept = hooks.stop.filter((hook) => !isOurs(hook));
|
|
7767
|
+
if (kept.length > 0) hooks.stop = kept;
|
|
7768
|
+
else delete hooks.stop;
|
|
7769
|
+
await writeJsonConfig(path, read.config);
|
|
7770
|
+
return "removed";
|
|
7771
|
+
}
|
|
7772
|
+
|
|
7773
|
+
// src/cli/agents/agent-hooks.ts
|
|
7774
|
+
var CODEX_HOOK_TIMEOUT_SECONDS = 30;
|
|
7775
|
+
var AGENT_HOOKS_FILE = "hooks.json";
|
|
7776
|
+
function agentHookConfigPath(agent, env, home2) {
|
|
7777
|
+
return agent === "claude" ? claudeSettingsPath(env, home2) : join31(agentHomeDir(agent, env, home2), AGENT_HOOKS_FILE);
|
|
7778
|
+
}
|
|
7779
|
+
function installAgentHook(agent, site) {
|
|
7780
|
+
const path = agentHookConfigPath(agent, site.env, site.home);
|
|
7781
|
+
switch (agent) {
|
|
7782
|
+
case "claude":
|
|
7783
|
+
return addStopHook(path, site.platform);
|
|
7784
|
+
case "codex": {
|
|
7785
|
+
const hook = { type: "command", command: posixCommand(agent), commandWindows: windowsCommand(agent, site.cliPath), timeout: CODEX_HOOK_TIMEOUT_SECONDS };
|
|
7786
|
+
return addGroupedStopHook(path, hook, ourCurrentHook(agent, site.cliPath, hook));
|
|
7787
|
+
}
|
|
7788
|
+
case "cursor": {
|
|
7789
|
+
const hook = { command: site.platform === "win32" ? windowsCommand(agent, site.cliPath) : posixCommand(agent) };
|
|
7790
|
+
return addCursorStopHook(path, hook, ourCurrentHook(agent, site.cliPath, hook));
|
|
7791
|
+
}
|
|
7792
|
+
}
|
|
7793
|
+
}
|
|
7794
|
+
function removeAgentHook(agent, site) {
|
|
7795
|
+
const path = agentHookConfigPath(agent, site.env, site.home);
|
|
7796
|
+
switch (agent) {
|
|
7797
|
+
case "claude":
|
|
7798
|
+
return removeStopHook(path);
|
|
7799
|
+
case "codex":
|
|
7800
|
+
return removeGroupedStopHook(path, ourHookOf(agent));
|
|
7801
|
+
case "cursor":
|
|
7802
|
+
return removeCursorStopHook(path, ourHookOf(agent));
|
|
7501
7803
|
}
|
|
7502
7804
|
}
|
|
7805
|
+
function agentStopCommand(agent) {
|
|
7806
|
+
return `${HOOK_STOP_COMMAND} --agent ${agent}`;
|
|
7807
|
+
}
|
|
7808
|
+
function posixCommand(agent) {
|
|
7809
|
+
return `command -v backlog >/dev/null && backlog ${agentStopCommand(agent)} || true`;
|
|
7810
|
+
}
|
|
7811
|
+
function windowsCommand(agent, cliPath2) {
|
|
7812
|
+
return `node "${cliPath2}" ${agentStopCommand(agent)}`;
|
|
7813
|
+
}
|
|
7814
|
+
function windowsCommandPattern(agent) {
|
|
7815
|
+
return new RegExp(`^node "[^"]*cli\\.js" ${agentStopCommand(agent)}$`);
|
|
7816
|
+
}
|
|
7817
|
+
function ourHookOf(agent, cliPath2) {
|
|
7818
|
+
return (hook) => {
|
|
7819
|
+
const command = typeof hook === "object" && hook !== null ? hook.command : void 0;
|
|
7820
|
+
if (typeof command !== "string") return false;
|
|
7821
|
+
return command === posixCommand(agent) || cliPath2 !== void 0 && command === windowsCommand(agent, cliPath2) || windowsCommandPattern(agent).test(command);
|
|
7822
|
+
};
|
|
7823
|
+
}
|
|
7824
|
+
function ourCurrentHook(agent, cliPath2, wanted) {
|
|
7825
|
+
return {
|
|
7826
|
+
isOurs: ourHookOf(agent, cliPath2),
|
|
7827
|
+
isCurrent: (hook) => Object.entries(wanted).every(([key, value]) => hook[key] === value)
|
|
7828
|
+
};
|
|
7829
|
+
}
|
|
7503
7830
|
|
|
7504
7831
|
// src/cli/commands/setup.ts
|
|
7505
7832
|
var setupCommand = {
|
|
7506
7833
|
name: "setup",
|
|
7507
|
-
usage: () => ["[--service]"],
|
|
7834
|
+
usage: () => [`[--agent ${AGENTS.join("|")}] [--service]`, `--remove-manual [--agent ${AGENTS.join("|")}]`],
|
|
7508
7835
|
run: runSetup
|
|
7509
7836
|
};
|
|
7510
7837
|
async function runSetup(args, io) {
|
|
7511
|
-
const
|
|
7512
|
-
|
|
7513
|
-
const
|
|
7514
|
-
|
|
7838
|
+
const options = parseOptions(io.language, args, { service: { type: "boolean" }, agent: { type: "string" }, "remove-manual": { type: "boolean" } });
|
|
7839
|
+
if (options["remove-manual"] && options.service) throw new UsageError(cliMessages(io.language).removeManualWithService);
|
|
7840
|
+
const agents = await targetAgents(options.agent, io);
|
|
7841
|
+
const step = options["remove-manual"] ? removeManualSetup : setUpAgent;
|
|
7842
|
+
const outcomes = [];
|
|
7843
|
+
for (const agent of agents) outcomes.push(await step(agent, io));
|
|
7844
|
+
const serviceCode = options.service ? await installService(io) : EXIT.ok;
|
|
7845
|
+
return outcomes.every(Boolean) ? serviceCode : EXIT.failed;
|
|
7846
|
+
}
|
|
7847
|
+
async function targetAgents(option, io) {
|
|
7848
|
+
const messages = cliMessages(io.language);
|
|
7849
|
+
if (option !== void 0) return [parseChoice(io.language, option, AGENTS, messages.optionLabel.agent)];
|
|
7850
|
+
const detection = await detectAgents(io.env, io.home);
|
|
7851
|
+
for (const { agent, dir } of detection.missing) agentVoice(agent, io).print(messages.agentNotFound(dir));
|
|
7852
|
+
return detection.found;
|
|
7853
|
+
}
|
|
7854
|
+
function agentVoice(agent, io) {
|
|
7855
|
+
const label = AGENT_LABELS[agent];
|
|
7856
|
+
return { print: (line) => io.print(`${label}: ${line}`), warn: (line) => io.warn(`${label}: ${line}`) };
|
|
7857
|
+
}
|
|
7858
|
+
async function setUpAgent(agent, io) {
|
|
7859
|
+
const voice = agentVoice(agent, io);
|
|
7860
|
+
const plugin = await agentPlugin(agent, io.env, io.home);
|
|
7861
|
+
if (plugin !== null) {
|
|
7862
|
+
voice.print(cliMessages(io.language).pluginManages(plugin));
|
|
7863
|
+
return true;
|
|
7864
|
+
}
|
|
7865
|
+
if (!await linkAgentSkill(agent, io, voice)) return false;
|
|
7866
|
+
const hook = await installAgentHook(agent, io);
|
|
7867
|
+
const reported = reportHook(hook, agentHookConfigPath(agent, io.env, io.home), io, voice);
|
|
7868
|
+
if (agent === "codex" && (hook === "added" || hook === "updated")) voice.print(cliMessages(io.language).codexHookApproval);
|
|
7869
|
+
return reported;
|
|
7515
7870
|
}
|
|
7516
|
-
async function
|
|
7871
|
+
async function linkAgentSkill(agent, io, voice) {
|
|
7517
7872
|
const messages = cliMessages(io.language);
|
|
7518
|
-
const skillsDir =
|
|
7519
|
-
const target =
|
|
7873
|
+
const skillsDir = agentSkillsDir(agent, io.env, io.home);
|
|
7874
|
+
const target = join32(skillsDir, "backlog");
|
|
7520
7875
|
const source = skillSourceDir(io.packageRoot, io.language);
|
|
7521
7876
|
let link2;
|
|
7522
7877
|
try {
|
|
7523
7878
|
link2 = await linkSkillFor(io.language, { skillsDir, packageRoot: io.packageRoot, platform: io.platform });
|
|
7524
7879
|
} catch (error) {
|
|
7525
|
-
|
|
7880
|
+
voice.warn(messages.installSkillLinkFailed(target, errorCodeOrText(error)));
|
|
7526
7881
|
return false;
|
|
7527
7882
|
}
|
|
7528
7883
|
if (link2 === "foreign") {
|
|
7529
|
-
|
|
7884
|
+
voice.warn(messages.installSkillForeign(target, source));
|
|
7530
7885
|
return false;
|
|
7531
7886
|
}
|
|
7532
|
-
|
|
7533
|
-
|
|
7534
|
-
return reportHook(await addStopHook(settingsPath, io.platform), settingsPath, io);
|
|
7887
|
+
voice.print(link2 === "linked" ? messages.installSkillLinked(target, source) : messages.installSkillKept(target));
|
|
7888
|
+
return true;
|
|
7535
7889
|
}
|
|
7536
|
-
function reportHook(result,
|
|
7890
|
+
function reportHook(result, configPath, io, voice) {
|
|
7537
7891
|
const messages = cliMessages(io.language);
|
|
7538
|
-
if (result === "
|
|
7539
|
-
|
|
7540
|
-
|
|
7541
|
-
}
|
|
7542
|
-
if (result === "exists") {
|
|
7543
|
-
io.print(messages.installHookExists(settingsPath));
|
|
7892
|
+
if (typeof result === "string") {
|
|
7893
|
+
const report = { added: messages.installHookAdded, exists: messages.installHookExists, updated: messages.installHookUpdated }[result];
|
|
7894
|
+
voice.print(report(configPath));
|
|
7544
7895
|
return true;
|
|
7545
7896
|
}
|
|
7546
7897
|
if (result.failed === "unreadable") {
|
|
7547
|
-
|
|
7898
|
+
voice.warn(messages.installSettingsUnreadable(configPath, result.code));
|
|
7548
7899
|
return false;
|
|
7549
7900
|
}
|
|
7550
|
-
|
|
7901
|
+
voice.warn(messages.installSettingsInvalid(configPath));
|
|
7902
|
+
return false;
|
|
7903
|
+
}
|
|
7904
|
+
async function removeManualSetup(agent, io) {
|
|
7905
|
+
const voice = agentVoice(agent, io);
|
|
7906
|
+
const messages = cliMessages(io.language);
|
|
7907
|
+
const skillsDir = agentSkillsDir(agent, io.env, io.home);
|
|
7908
|
+
voice.print(messages.manualSkillRemoval[await unlinkOurSkill(skillsDir)](join32(skillsDir, "backlog")));
|
|
7909
|
+
for (const legacyDir of legacySkillsDirs(agent, io.env, io.home)) {
|
|
7910
|
+
if (await unlinkOurSkill(legacyDir) === "removed") voice.print(messages.manualSkillRemoval.removed(join32(legacyDir, "backlog")));
|
|
7911
|
+
}
|
|
7912
|
+
return reportHookRemoval(await removeAgentHook(agent, io), agentHookConfigPath(agent, io.env, io.home), io, voice);
|
|
7913
|
+
}
|
|
7914
|
+
function reportHookRemoval(result, configPath, io, voice) {
|
|
7915
|
+
const messages = cliMessages(io.language);
|
|
7916
|
+
if (result === "removed" || result === "absent") {
|
|
7917
|
+
voice.print(messages.manualHookRemoval[result](configPath));
|
|
7918
|
+
return true;
|
|
7919
|
+
}
|
|
7920
|
+
voice.warn(result.failed === "unreadable" ? messages.hookConfigUnreadable(configPath, result.code) : messages.hookConfigInvalid(configPath));
|
|
7551
7921
|
return false;
|
|
7552
7922
|
}
|
|
7553
7923
|
|