@todu/pi-extensions 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +95 -0
  3. package/dist/domain/habit.d.ts +38 -0
  4. package/dist/domain/habit.js +1 -0
  5. package/dist/domain/note.d.ts +21 -0
  6. package/dist/domain/note.js +1 -0
  7. package/dist/domain/recurring.d.ts +29 -0
  8. package/dist/domain/recurring.js +1 -0
  9. package/dist/domain/task-actions.d.ts +24 -0
  10. package/dist/domain/task-actions.js +1 -0
  11. package/dist/domain/task.d.ts +50 -0
  12. package/dist/domain/task.js +1 -0
  13. package/dist/extension/current-task-context.d.ts +26 -0
  14. package/dist/extension/current-task-context.js +140 -0
  15. package/dist/extension/register-commands.d.ts +114 -0
  16. package/dist/extension/register-commands.js +1214 -0
  17. package/dist/extension/register-events.d.ts +3 -0
  18. package/dist/extension/register-events.js +30 -0
  19. package/dist/extension/register-tools.d.ts +17 -0
  20. package/dist/extension/register-tools.js +36 -0
  21. package/dist/extension/register-ui.d.ts +3 -0
  22. package/dist/extension/register-ui.js +7 -0
  23. package/dist/extension/sync-status-context.d.ts +26 -0
  24. package/dist/extension/sync-status-context.js +162 -0
  25. package/dist/extension/task-browse-filter-context.d.ts +16 -0
  26. package/dist/extension/task-browse-filter-context.js +40 -0
  27. package/dist/flows/browse-tasks.d.ts +7 -0
  28. package/dist/flows/browse-tasks.js +2 -0
  29. package/dist/flows/comment-on-task.d.ts +7 -0
  30. package/dist/flows/comment-on-task.js +2 -0
  31. package/dist/flows/create-task.d.ts +7 -0
  32. package/dist/flows/create-task.js +2 -0
  33. package/dist/flows/pick-current-task.d.ts +7 -0
  34. package/dist/flows/pick-current-task.js +4 -0
  35. package/dist/flows/show-task-detail.d.ts +7 -0
  36. package/dist/flows/show-task-detail.js +2 -0
  37. package/dist/flows/update-task.d.ts +7 -0
  38. package/dist/flows/update-task.js +2 -0
  39. package/dist/index.d.ts +4 -0
  40. package/dist/index.js +12 -0
  41. package/dist/services/habit-service.d.ts +38 -0
  42. package/dist/services/habit-service.js +1 -0
  43. package/dist/services/note-service.d.ts +5 -0
  44. package/dist/services/note-service.js +1 -0
  45. package/dist/services/project-integration-service.d.ts +109 -0
  46. package/dist/services/project-integration-service.js +122 -0
  47. package/dist/services/project-service.d.ts +27 -0
  48. package/dist/services/project-service.js +8 -0
  49. package/dist/services/recurring-service.d.ts +37 -0
  50. package/dist/services/recurring-service.js +1 -0
  51. package/dist/services/repo-context.d.ts +55 -0
  52. package/dist/services/repo-context.js +135 -0
  53. package/dist/services/task-browse-filter-store.d.ts +31 -0
  54. package/dist/services/task-browse-filter-store.js +47 -0
  55. package/dist/services/task-service.d.ts +42 -0
  56. package/dist/services/task-service.js +1 -0
  57. package/dist/services/task-session-store.d.ts +30 -0
  58. package/dist/services/task-session-store.js +55 -0
  59. package/dist/services/todu/daemon-client.d.ts +93 -0
  60. package/dist/services/todu/daemon-client.js +660 -0
  61. package/dist/services/todu/daemon-config.d.ts +17 -0
  62. package/dist/services/todu/daemon-config.js +38 -0
  63. package/dist/services/todu/daemon-connection.d.ts +61 -0
  64. package/dist/services/todu/daemon-connection.js +633 -0
  65. package/dist/services/todu/daemon-events.d.ts +11 -0
  66. package/dist/services/todu/daemon-events.js +1 -0
  67. package/dist/services/todu/default-task-service.d.ts +34 -0
  68. package/dist/services/todu/default-task-service.js +109 -0
  69. package/dist/services/todu/todu-habit-service.d.ts +24 -0
  70. package/dist/services/todu/todu-habit-service.js +80 -0
  71. package/dist/services/todu/todu-note-service.d.ts +20 -0
  72. package/dist/services/todu/todu-note-service.js +35 -0
  73. package/dist/services/todu/todu-project-integration-service.d.ts +27 -0
  74. package/dist/services/todu/todu-project-integration-service.js +45 -0
  75. package/dist/services/todu/todu-project-service.d.ts +24 -0
  76. package/dist/services/todu/todu-project-service.js +42 -0
  77. package/dist/services/todu/todu-recurring-service.d.ts +27 -0
  78. package/dist/services/todu/todu-recurring-service.js +72 -0
  79. package/dist/services/todu/todu-task-service.d.ts +23 -0
  80. package/dist/services/todu/todu-task-service.js +80 -0
  81. package/dist/tools/habit-mutation-tools.d.ts +170 -0
  82. package/dist/tools/habit-mutation-tools.js +363 -0
  83. package/dist/tools/habit-read-tools.d.ts +61 -0
  84. package/dist/tools/habit-read-tools.js +152 -0
  85. package/dist/tools/note-read-tools.d.ts +79 -0
  86. package/dist/tools/note-read-tools.js +148 -0
  87. package/dist/tools/project-integration-tools.d.ts +92 -0
  88. package/dist/tools/project-integration-tools.js +344 -0
  89. package/dist/tools/project-mutation-tools.d.ts +100 -0
  90. package/dist/tools/project-mutation-tools.js +205 -0
  91. package/dist/tools/project-read-tools.d.ts +59 -0
  92. package/dist/tools/project-read-tools.js +131 -0
  93. package/dist/tools/recurring-mutation-tools.d.ts +130 -0
  94. package/dist/tools/recurring-mutation-tools.js +317 -0
  95. package/dist/tools/recurring-read-tools.d.ts +31 -0
  96. package/dist/tools/recurring-read-tools.js +57 -0
  97. package/dist/tools/task-mutation-tools.d.ts +159 -0
  98. package/dist/tools/task-mutation-tools.js +340 -0
  99. package/dist/tools/task-read-tools.d.ts +91 -0
  100. package/dist/tools/task-read-tools.js +186 -0
  101. package/dist/ui/components/habit-table.d.ts +5 -0
  102. package/dist/ui/components/habit-table.js +34 -0
  103. package/dist/ui/components/loaders.d.ts +6 -0
  104. package/dist/ui/components/loaders.js +5 -0
  105. package/dist/ui/components/task-detail.d.ts +19 -0
  106. package/dist/ui/components/task-detail.js +74 -0
  107. package/dist/ui/components/task-list.d.ts +8 -0
  108. package/dist/ui/components/task-list.js +7 -0
  109. package/dist/ui/components/task-settings.d.ts +7 -0
  110. package/dist/ui/components/task-settings.js +12 -0
  111. package/dist/ui/renderers/task-tool-renderer.d.ts +4 -0
  112. package/dist/ui/renderers/task-tool-renderer.js +4 -0
  113. package/dist/ui/widgets/current-task-widget.d.ts +7 -0
  114. package/dist/ui/widgets/current-task-widget.js +20 -0
  115. package/dist/ui/widgets/next-actions-widget.d.ts +7 -0
  116. package/dist/ui/widgets/next-actions-widget.js +5 -0
  117. package/dist/utils/key-hints.d.ts +6 -0
  118. package/dist/utils/key-hints.js +2 -0
  119. package/dist/utils/schedule.d.ts +35 -0
  120. package/dist/utils/schedule.js +111 -0
  121. package/dist/utils/task-filters.d.ts +3 -0
  122. package/dist/utils/task-filters.js +7 -0
  123. package/dist/utils/task-format.d.ts +4 -0
  124. package/dist/utils/task-format.js +6 -0
  125. package/dist/utils/timezone.d.ts +2 -0
  126. package/dist/utils/timezone.js +9 -0
  127. package/package.json +79 -0
@@ -0,0 +1,170 @@
1
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
+ import type { HabitCheckResult, HabitDetail } from "../domain/habit";
3
+ import type { NoteSummary } from "../domain/note";
4
+ import type { ProjectSummary } from "../domain/task";
5
+ import type { AddHabitNoteInput, CreateHabitInput, DeleteHabitResult, HabitService, UpdateHabitInput } from "../services/habit-service";
6
+ import type { ProjectService } from "../services/project-service";
7
+ interface HabitCreateToolParams {
8
+ title: string;
9
+ projectId: string;
10
+ schedule: string;
11
+ timezone: string;
12
+ startDate: string;
13
+ description?: string;
14
+ endDate?: string;
15
+ }
16
+ interface HabitUpdateToolParams {
17
+ habitId: string;
18
+ title?: string;
19
+ schedule?: string;
20
+ timezone?: string;
21
+ description?: string;
22
+ endDate?: string;
23
+ }
24
+ interface HabitCheckToolParams {
25
+ habitId: string;
26
+ }
27
+ interface HabitDeleteToolParams {
28
+ habitId: string;
29
+ }
30
+ interface HabitNoteAddToolParams {
31
+ habitId: string;
32
+ content: string;
33
+ }
34
+ interface HabitCreateToolDetails {
35
+ kind: "habit_create";
36
+ input: CreateHabitInput;
37
+ habit: HabitDetail;
38
+ }
39
+ interface HabitUpdateToolDetails {
40
+ kind: "habit_update";
41
+ input: UpdateHabitInput;
42
+ habit: HabitDetail;
43
+ }
44
+ interface HabitCheckToolDetails {
45
+ kind: "habit_check";
46
+ habitId: string;
47
+ found: boolean;
48
+ result?: HabitCheckResult;
49
+ }
50
+ interface HabitDeleteToolDetails {
51
+ kind: "habit_delete";
52
+ habitId: string;
53
+ found: boolean;
54
+ deleted: boolean;
55
+ result?: DeleteHabitResult;
56
+ }
57
+ interface HabitNoteAddToolDetails {
58
+ kind: "habit_note_add";
59
+ habitId: string;
60
+ note: NoteSummary;
61
+ }
62
+ interface HabitMutationToolDependencies {
63
+ getHabitService: () => Promise<HabitService>;
64
+ getProjectService: () => Promise<ProjectService>;
65
+ }
66
+ declare const createHabitCreateToolDefinition: ({ getHabitService, getProjectService, }: HabitMutationToolDependencies) => {
67
+ name: string;
68
+ label: string;
69
+ description: string;
70
+ promptSnippet: string;
71
+ promptGuidelines: string[];
72
+ parameters: import("@sinclair/typebox").TObject<{
73
+ title: import("@sinclair/typebox").TString;
74
+ projectId: import("@sinclair/typebox").TString;
75
+ schedule: import("@sinclair/typebox").TString;
76
+ timezone: import("@sinclair/typebox").TString;
77
+ startDate: import("@sinclair/typebox").TString;
78
+ description: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
79
+ endDate: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
80
+ }>;
81
+ execute(_toolCallId: string, params: HabitCreateToolParams): Promise<{
82
+ content: {
83
+ type: "text";
84
+ text: string;
85
+ }[];
86
+ details: HabitCreateToolDetails;
87
+ }>;
88
+ };
89
+ declare const createHabitUpdateToolDefinition: ({ getHabitService }: HabitMutationToolDependencies) => {
90
+ name: string;
91
+ label: string;
92
+ description: string;
93
+ promptSnippet: string;
94
+ promptGuidelines: string[];
95
+ parameters: import("@sinclair/typebox").TObject<{
96
+ habitId: import("@sinclair/typebox").TString;
97
+ title: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
98
+ schedule: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
99
+ timezone: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
100
+ description: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
101
+ endDate: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
102
+ }>;
103
+ execute(_toolCallId: string, params: HabitUpdateToolParams): Promise<{
104
+ content: {
105
+ type: "text";
106
+ text: string;
107
+ }[];
108
+ details: HabitUpdateToolDetails;
109
+ }>;
110
+ };
111
+ declare const createHabitCheckToolDefinition: ({ getHabitService }: HabitMutationToolDependencies) => {
112
+ name: string;
113
+ label: string;
114
+ description: string;
115
+ promptSnippet: string;
116
+ promptGuidelines: string[];
117
+ parameters: import("@sinclair/typebox").TObject<{
118
+ habitId: import("@sinclair/typebox").TString;
119
+ }>;
120
+ execute(_toolCallId: string, params: HabitCheckToolParams): Promise<{
121
+ content: {
122
+ type: "text";
123
+ text: string;
124
+ }[];
125
+ details: HabitCheckToolDetails;
126
+ }>;
127
+ };
128
+ declare const createHabitDeleteToolDefinition: ({ getHabitService }: HabitMutationToolDependencies) => {
129
+ name: string;
130
+ label: string;
131
+ description: string;
132
+ promptSnippet: string;
133
+ promptGuidelines: string[];
134
+ parameters: import("@sinclair/typebox").TObject<{
135
+ habitId: import("@sinclair/typebox").TString;
136
+ }>;
137
+ execute(_toolCallId: string, params: HabitDeleteToolParams): Promise<{
138
+ content: {
139
+ type: "text";
140
+ text: string;
141
+ }[];
142
+ details: HabitDeleteToolDetails;
143
+ }>;
144
+ };
145
+ declare const createHabitNoteAddToolDefinition: ({ getHabitService }: HabitMutationToolDependencies) => {
146
+ name: string;
147
+ label: string;
148
+ description: string;
149
+ promptSnippet: string;
150
+ promptGuidelines: string[];
151
+ parameters: import("@sinclair/typebox").TObject<{
152
+ habitId: import("@sinclair/typebox").TString;
153
+ content: import("@sinclair/typebox").TString;
154
+ }>;
155
+ execute(_toolCallId: string, params: HabitNoteAddToolParams): Promise<{
156
+ content: {
157
+ type: "text";
158
+ text: string;
159
+ }[];
160
+ details: HabitNoteAddToolDetails;
161
+ }>;
162
+ };
163
+ declare const registerHabitMutationTools: (pi: Pick<ExtensionAPI, "registerTool">, dependencies: HabitMutationToolDependencies) => void;
164
+ declare const normalizeCreateHabitInput: (params: HabitCreateToolParams) => CreateHabitInput;
165
+ declare const resolveCreateHabitInput: (projectService: ProjectService, params: HabitCreateToolParams) => Promise<CreateHabitInput>;
166
+ declare const normalizeUpdateHabitInput: (params: HabitUpdateToolParams) => UpdateHabitInput;
167
+ declare const resolveProjectForHabitMutation: (projectService: ProjectService, projectRef: string) => Promise<ProjectSummary>;
168
+ declare const normalizeHabitNoteAddInput: (params: HabitNoteAddToolParams) => AddHabitNoteInput;
169
+ export type { HabitCheckToolDetails, HabitCreateToolDetails, HabitDeleteToolDetails, HabitMutationToolDependencies, HabitNoteAddToolDetails, HabitUpdateToolDetails, };
170
+ export { createHabitCheckToolDefinition, createHabitCreateToolDefinition, createHabitDeleteToolDefinition, createHabitNoteAddToolDefinition, createHabitUpdateToolDefinition, normalizeCreateHabitInput, normalizeHabitNoteAddInput, normalizeUpdateHabitInput, registerHabitMutationTools, resolveCreateHabitInput, resolveProjectForHabitMutation, };
@@ -0,0 +1,363 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import { ToduHabitServiceError } from "../services/todu/todu-habit-service.js";
3
+ import { validateAndNormalizeScheduleRule } from "../utils/schedule.js";
4
+ const HabitCreateParams = Type.Object({
5
+ title: Type.String({ description: "Habit title" }),
6
+ projectId: Type.String({ description: "Project ID or unique project name" }),
7
+ schedule: Type.String({ description: "Normalized RRULE schedule string" }),
8
+ timezone: Type.String({ description: "IANA timezone" }),
9
+ startDate: Type.String({ description: "Start date in YYYY-MM-DD format" }),
10
+ description: Type.Optional(Type.String({ description: "Optional habit description" })),
11
+ endDate: Type.Optional(Type.String({
12
+ description: "Optional end date in YYYY-MM-DD format. Use an empty string to clear it.",
13
+ })),
14
+ });
15
+ const HabitUpdateParams = Type.Object({
16
+ habitId: Type.String({ description: "Habit ID" }),
17
+ title: Type.Optional(Type.String({ description: "Optional replacement habit title" })),
18
+ schedule: Type.Optional(Type.String({ description: "Optional replacement normalized RRULE schedule string" })),
19
+ timezone: Type.Optional(Type.String({ description: "Optional replacement IANA timezone" })),
20
+ description: Type.Optional(Type.String({
21
+ description: "Optional replacement description. Use an empty string to clear it.",
22
+ })),
23
+ endDate: Type.Optional(Type.String({ description: "Optional replacement end date. Use an empty string to clear it." })),
24
+ });
25
+ const HabitCheckParams = Type.Object({
26
+ habitId: Type.String({ description: "Habit ID" }),
27
+ });
28
+ const HabitDeleteParams = Type.Object({
29
+ habitId: Type.String({ description: "Habit ID" }),
30
+ });
31
+ const HabitNoteAddParams = Type.Object({
32
+ habitId: Type.String({ description: "Habit ID" }),
33
+ content: Type.String({ description: "Note content" }),
34
+ });
35
+ const createHabitCreateToolDefinition = ({ getHabitService, getProjectService, }) => ({
36
+ name: "habit_create",
37
+ label: "Habit Create",
38
+ description: "Create a habit.",
39
+ promptSnippet: "Create a habit through the native habit service.",
40
+ promptGuidelines: [
41
+ "Use this tool for backend habit creation in normal chat.",
42
+ "Provide an explicit project ID when known.",
43
+ "If only a project name is available, pass the exact unique project name so the tool can resolve it.",
44
+ "Provide normalized RRULE schedule strings instead of natural-language schedules.",
45
+ ],
46
+ parameters: HabitCreateParams,
47
+ async execute(_toolCallId, params) {
48
+ try {
49
+ const projectService = await getProjectService();
50
+ const input = await resolveCreateHabitInput(projectService, params);
51
+ const habitService = await getHabitService();
52
+ const habit = await habitService.createHabit(input);
53
+ const details = {
54
+ kind: "habit_create",
55
+ input,
56
+ habit,
57
+ };
58
+ return {
59
+ content: [{ type: "text", text: formatHabitCreateContent(habit) }],
60
+ details,
61
+ };
62
+ }
63
+ catch (error) {
64
+ throw new Error(formatToolError(error, "habit_create failed"), { cause: error });
65
+ }
66
+ },
67
+ });
68
+ const createHabitUpdateToolDefinition = ({ getHabitService }) => ({
69
+ name: "habit_update",
70
+ label: "Habit Update",
71
+ description: "Update a habit's supported fields.",
72
+ promptSnippet: "Update a habit through the native habit service.",
73
+ promptGuidelines: [
74
+ "Use this tool for backend habit updates in normal chat.",
75
+ "Provide normalized RRULE schedule strings instead of natural-language schedules.",
76
+ ],
77
+ parameters: HabitUpdateParams,
78
+ async execute(_toolCallId, params) {
79
+ try {
80
+ const habitService = await getHabitService();
81
+ const input = normalizeUpdateHabitInput(params);
82
+ const habit = await habitService.updateHabit(input);
83
+ const details = {
84
+ kind: "habit_update",
85
+ input,
86
+ habit,
87
+ };
88
+ return {
89
+ content: [{ type: "text", text: formatHabitUpdateContent(habit, input) }],
90
+ details,
91
+ };
92
+ }
93
+ catch (error) {
94
+ throw new Error(formatToolError(error, "habit_update failed"), { cause: error });
95
+ }
96
+ },
97
+ });
98
+ const createHabitCheckToolDefinition = ({ getHabitService }) => ({
99
+ name: "habit_check",
100
+ label: "Habit Check",
101
+ description: "Check in a habit for today or toggle today's check-in.",
102
+ promptSnippet: "Check in a habit through the native habit service.",
103
+ promptGuidelines: [
104
+ "Use this tool to check in or toggle a habit for today.",
105
+ "Provide the habit ID explicitly.",
106
+ ],
107
+ parameters: HabitCheckParams,
108
+ async execute(_toolCallId, params) {
109
+ const habitId = normalizeRequiredText(params.habitId, "habitId");
110
+ try {
111
+ const habitService = await getHabitService();
112
+ const result = await habitService.checkHabit(habitId);
113
+ const details = {
114
+ kind: "habit_check",
115
+ habitId,
116
+ found: true,
117
+ result,
118
+ };
119
+ return {
120
+ content: [{ type: "text", text: formatHabitCheckContent(result) }],
121
+ details,
122
+ };
123
+ }
124
+ catch (error) {
125
+ if (isHabitNotFoundError(error)) {
126
+ const details = {
127
+ kind: "habit_check",
128
+ habitId,
129
+ found: false,
130
+ };
131
+ return {
132
+ content: [{ type: "text", text: `Habit not found: ${habitId}` }],
133
+ details,
134
+ };
135
+ }
136
+ throw new Error(formatToolError(error, "habit_check failed"), { cause: error });
137
+ }
138
+ },
139
+ });
140
+ const createHabitDeleteToolDefinition = ({ getHabitService }) => ({
141
+ name: "habit_delete",
142
+ label: "Habit Delete",
143
+ description: "Delete a habit by explicit ID.",
144
+ promptSnippet: "Delete a habit by explicit ID.",
145
+ promptGuidelines: [
146
+ "Use this tool for backend habit deletion in normal chat.",
147
+ "Provide the habit ID explicitly.",
148
+ ],
149
+ parameters: HabitDeleteParams,
150
+ async execute(_toolCallId, params) {
151
+ const habitId = normalizeRequiredText(params.habitId, "habitId");
152
+ try {
153
+ const habitService = await getHabitService();
154
+ const result = await habitService.deleteHabit(habitId);
155
+ const details = {
156
+ kind: "habit_delete",
157
+ habitId,
158
+ found: true,
159
+ deleted: true,
160
+ result,
161
+ };
162
+ return {
163
+ content: [{ type: "text", text: formatHabitDeleteContent(details) }],
164
+ details,
165
+ };
166
+ }
167
+ catch (error) {
168
+ if (isHabitNotFoundError(error)) {
169
+ const details = {
170
+ kind: "habit_delete",
171
+ habitId,
172
+ found: false,
173
+ deleted: false,
174
+ };
175
+ return {
176
+ content: [{ type: "text", text: formatHabitDeleteContent(details) }],
177
+ details,
178
+ };
179
+ }
180
+ throw new Error(formatToolError(error, "habit_delete failed"), { cause: error });
181
+ }
182
+ },
183
+ });
184
+ const createHabitNoteAddToolDefinition = ({ getHabitService }) => ({
185
+ name: "habit_note_add",
186
+ label: "Habit Note Add",
187
+ description: "Add a note to a habit.",
188
+ promptSnippet: "Attach a note to a habit by habit ID.",
189
+ promptGuidelines: [
190
+ "Use this tool to attach notes to habits in normal chat.",
191
+ "Provide the habit ID and note content explicitly.",
192
+ ],
193
+ parameters: HabitNoteAddParams,
194
+ async execute(_toolCallId, params) {
195
+ const input = normalizeHabitNoteAddInput(params);
196
+ try {
197
+ const habitService = await getHabitService();
198
+ const note = await habitService.addHabitNote(input);
199
+ const details = {
200
+ kind: "habit_note_add",
201
+ habitId: input.habitId,
202
+ note,
203
+ };
204
+ return {
205
+ content: [{ type: "text", text: formatHabitNoteAddContent(note, input.habitId) }],
206
+ details,
207
+ };
208
+ }
209
+ catch (error) {
210
+ throw new Error(formatToolError(error, "habit_note_add failed"), { cause: error });
211
+ }
212
+ },
213
+ });
214
+ const registerHabitMutationTools = (pi, dependencies) => {
215
+ pi.registerTool(createHabitCreateToolDefinition(dependencies));
216
+ pi.registerTool(createHabitUpdateToolDefinition(dependencies));
217
+ pi.registerTool(createHabitCheckToolDefinition(dependencies));
218
+ pi.registerTool(createHabitDeleteToolDefinition(dependencies));
219
+ pi.registerTool(createHabitNoteAddToolDefinition(dependencies));
220
+ };
221
+ const normalizeCreateHabitInput = (params) => ({
222
+ title: normalizeRequiredText(params.title, "title"),
223
+ projectId: normalizeRequiredText(params.projectId, "projectId"),
224
+ schedule: normalizeScheduleInput(params.schedule),
225
+ timezone: normalizeRequiredText(params.timezone, "timezone"),
226
+ startDate: normalizeRequiredText(params.startDate, "startDate"),
227
+ description: normalizeOptionalDescription(params, "description"),
228
+ endDate: normalizeOptionalDate(params, "endDate"),
229
+ });
230
+ const resolveCreateHabitInput = async (projectService, params) => {
231
+ const input = normalizeCreateHabitInput(params);
232
+ const project = await resolveProjectForHabitMutation(projectService, input.projectId);
233
+ return {
234
+ ...input,
235
+ projectId: project.id,
236
+ };
237
+ };
238
+ const normalizeUpdateHabitInput = (params) => {
239
+ const input = {
240
+ habitId: normalizeRequiredText(params.habitId, "habitId"),
241
+ };
242
+ if (hasOwn(params, "title")) {
243
+ input.title = normalizeRequiredText(params.title ?? "", "title");
244
+ }
245
+ if (hasOwn(params, "schedule")) {
246
+ input.schedule = normalizeScheduleInput(params.schedule ?? "");
247
+ }
248
+ if (hasOwn(params, "timezone")) {
249
+ input.timezone = normalizeRequiredText(params.timezone ?? "", "timezone");
250
+ }
251
+ if (hasOwn(params, "description")) {
252
+ input.description = normalizeNullableText(params.description);
253
+ }
254
+ if (hasOwn(params, "endDate")) {
255
+ input.endDate = normalizeNullableText(params.endDate);
256
+ }
257
+ if (input.title === undefined &&
258
+ input.schedule === undefined &&
259
+ input.timezone === undefined &&
260
+ !hasOwn(input, "description") &&
261
+ !hasOwn(input, "endDate")) {
262
+ throw new Error("habit_update requires at least one supported field: title, schedule, timezone, description, or endDate");
263
+ }
264
+ return input;
265
+ };
266
+ const resolveProjectForHabitMutation = async (projectService, projectRef) => {
267
+ const project = await projectService.getProject(projectRef);
268
+ if (project) {
269
+ return project;
270
+ }
271
+ const projects = await projectService.listProjects();
272
+ const nameMatches = projects.filter((candidate) => candidate.name === projectRef);
273
+ if (nameMatches.length === 0) {
274
+ throw new Error(`project not found: ${projectRef}`);
275
+ }
276
+ if (nameMatches.length > 1) {
277
+ throw new Error(`multiple projects matched: ${projectRef}`);
278
+ }
279
+ const matchedProject = nameMatches[0];
280
+ if (!matchedProject) {
281
+ throw new Error(`project not found: ${projectRef}`);
282
+ }
283
+ return matchedProject;
284
+ };
285
+ const normalizeScheduleInput = (value) => {
286
+ const normalizedValue = normalizeRequiredText(value, "schedule");
287
+ const result = validateAndNormalizeScheduleRule(normalizedValue);
288
+ if (!result.ok) {
289
+ throw new Error(result.error.message);
290
+ }
291
+ return result.value.rule;
292
+ };
293
+ const normalizeOptionalDescription = (params, fieldName) => {
294
+ if (!hasOwn(params, fieldName)) {
295
+ return undefined;
296
+ }
297
+ return normalizeNullableText(params[fieldName]);
298
+ };
299
+ const normalizeOptionalDate = (params, fieldName) => {
300
+ if (!hasOwn(params, fieldName)) {
301
+ return undefined;
302
+ }
303
+ return normalizeNullableText(params[fieldName]);
304
+ };
305
+ const normalizeRequiredText = (value, fieldName) => {
306
+ const trimmedValue = value.trim();
307
+ if (trimmedValue.length === 0) {
308
+ throw new Error(`${fieldName} is required`);
309
+ }
310
+ return trimmedValue;
311
+ };
312
+ const normalizeNullableText = (value) => {
313
+ const trimmedValue = value?.trim() ?? "";
314
+ return trimmedValue.length > 0 ? trimmedValue : null;
315
+ };
316
+ const hasOwn = (value, property) => Object.prototype.hasOwnProperty.call(value, property);
317
+ const isHabitNotFoundError = (error) => error instanceof ToduHabitServiceError && error.causeCode === "not-found";
318
+ const formatHabitCreateContent = (habit) => [
319
+ `Created habit ${habit.id}: ${habit.title}`,
320
+ `Status: ${habit.paused ? "paused" : "active"}`,
321
+ `Project: ${habit.projectName ?? habit.projectId}`,
322
+ ].join("\n");
323
+ const formatHabitUpdateContent = (habit, input) => {
324
+ const changedFields = [
325
+ input.title !== undefined ? `title=${JSON.stringify(input.title)}` : null,
326
+ input.schedule !== undefined ? `schedule=${input.schedule}` : null,
327
+ input.timezone !== undefined ? `timezone=${input.timezone}` : null,
328
+ hasOwn(input, "description")
329
+ ? `description=${input.description === null ? "cleared" : "updated"}`
330
+ : null,
331
+ hasOwn(input, "endDate")
332
+ ? `endDate=${input.endDate === null ? "cleared" : input.endDate}`
333
+ : null,
334
+ ].filter((value) => value !== null);
335
+ return [`Updated habit ${habit.id}: ${habit.title}`, `Changes: ${changedFields.join(", ")}`].join("\n");
336
+ };
337
+ const formatHabitCheckContent = (result) => {
338
+ const status = result.completed ? "checked in" : "unchecked";
339
+ const streakLine = formatHabitCheckStreakLine(result.streak);
340
+ return [`Habit ${result.habitId}: ${status} for ${result.date}`, streakLine].join("\n");
341
+ };
342
+ const formatHabitCheckStreakLine = (streak) => {
343
+ if (!streak) {
344
+ return "Streak: unavailable";
345
+ }
346
+ const current = typeof streak.current === "number" ? streak.current : "?";
347
+ const longest = typeof streak.longest === "number" ? streak.longest : "?";
348
+ const totalCheckins = typeof streak.totalCheckins === "number" ? streak.totalCheckins : "?";
349
+ return `Streak: ${current} current, ${longest} longest, ${totalCheckins} total`;
350
+ };
351
+ const formatHabitDeleteContent = (details) => details.found ? `Deleted habit ${details.habitId}.` : `Habit not found: ${details.habitId}`;
352
+ const normalizeHabitNoteAddInput = (params) => ({
353
+ habitId: normalizeRequiredText(params.habitId, "habitId"),
354
+ content: normalizeRequiredText(params.content, "content"),
355
+ });
356
+ const formatHabitNoteAddContent = (note, habitId) => `Added note ${note.id} to habit ${habitId}.`;
357
+ const formatToolError = (error, prefix) => {
358
+ if (error instanceof Error && error.message.trim().length > 0) {
359
+ return `${prefix}: ${error.message}`;
360
+ }
361
+ return prefix;
362
+ };
363
+ export { createHabitCheckToolDefinition, createHabitCreateToolDefinition, createHabitDeleteToolDefinition, createHabitNoteAddToolDefinition, createHabitUpdateToolDefinition, normalizeCreateHabitInput, normalizeHabitNoteAddInput, normalizeUpdateHabitInput, registerHabitMutationTools, resolveCreateHabitInput, resolveProjectForHabitMutation, };
@@ -0,0 +1,61 @@
1
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
+ import type { HabitDetail, HabitId, HabitStreak, HabitSummaryWithStreak } from "../domain/habit";
3
+ import type { HabitService } from "../services/habit-service";
4
+ interface HabitListToolDetails {
5
+ kind: "habit_list";
6
+ habits: HabitSummaryWithStreak[];
7
+ total: number;
8
+ empty: boolean;
9
+ }
10
+ interface HabitShowToolParams {
11
+ habitId: HabitId;
12
+ }
13
+ interface HabitShowToolDetails {
14
+ kind: "habit_show";
15
+ habitId: HabitId;
16
+ found: boolean;
17
+ habit?: HabitDetail;
18
+ streak?: HabitStreak;
19
+ }
20
+ interface HabitReadToolDependencies {
21
+ getHabitService: () => Promise<HabitService>;
22
+ }
23
+ declare const createHabitListToolDefinition: ({ getHabitService }: HabitReadToolDependencies) => {
24
+ name: string;
25
+ label: string;
26
+ description: string;
27
+ promptSnippet: string;
28
+ promptGuidelines: string[];
29
+ parameters: import("@sinclair/typebox").TObject<{}>;
30
+ execute(_toolCallId: string, _params: Record<string, never>): Promise<{
31
+ content: {
32
+ type: "text";
33
+ text: string;
34
+ }[];
35
+ details: HabitListToolDetails;
36
+ }>;
37
+ };
38
+ declare const createHabitShowToolDefinition: ({ getHabitService }: HabitReadToolDependencies) => {
39
+ name: string;
40
+ label: string;
41
+ description: string;
42
+ promptSnippet: string;
43
+ promptGuidelines: string[];
44
+ parameters: import("@sinclair/typebox").TObject<{
45
+ habitId: import("@sinclair/typebox").TString;
46
+ }>;
47
+ execute(_toolCallId: string, params: HabitShowToolParams): Promise<{
48
+ content: {
49
+ type: "text";
50
+ text: string;
51
+ }[];
52
+ details: HabitShowToolDetails;
53
+ }>;
54
+ };
55
+ declare const registerHabitReadTools: (pi: Pick<ExtensionAPI, "registerTool">, dependencies: HabitReadToolDependencies) => void;
56
+ declare const formatHabitListContent: (details: HabitListToolDetails) => string;
57
+ declare const formatStreakLabel: (streak: HabitStreak | null) => string;
58
+ declare const formatTodayLabel: (streak: HabitStreak | null) => string;
59
+ declare const formatHabitShowContent: (habit: HabitDetail, streak: HabitStreak | null) => string;
60
+ export type { HabitListToolDetails, HabitReadToolDependencies, HabitShowToolDetails };
61
+ export { createHabitListToolDefinition, createHabitShowToolDefinition, formatHabitListContent, formatHabitShowContent, formatStreakLabel, formatTodayLabel, registerHabitReadTools, };