@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,59 @@
1
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
+ import type { ProjectSummary } from "../domain/task";
3
+ import type { ProjectService } from "../services/project-service";
4
+ interface ProjectListToolDetails {
5
+ kind: "project_list";
6
+ projects: ProjectSummary[];
7
+ total: number;
8
+ empty: boolean;
9
+ }
10
+ interface ProjectShowToolDetails {
11
+ kind: "project_show";
12
+ projectRef: string;
13
+ found: boolean;
14
+ project?: ProjectSummary;
15
+ }
16
+ interface ProjectShowToolParams {
17
+ projectRef: string;
18
+ }
19
+ interface ProjectReadToolDependencies {
20
+ getProjectService: () => Promise<ProjectService>;
21
+ }
22
+ declare const createProjectListToolDefinition: ({ getProjectService }: ProjectReadToolDependencies) => {
23
+ name: string;
24
+ label: string;
25
+ description: string;
26
+ promptSnippet: string;
27
+ promptGuidelines: string[];
28
+ parameters: import("@sinclair/typebox").TObject<{}>;
29
+ execute(_toolCallId: string, _params: Record<string, never>): Promise<{
30
+ content: {
31
+ type: "text";
32
+ text: string;
33
+ }[];
34
+ details: ProjectListToolDetails;
35
+ }>;
36
+ };
37
+ declare const createProjectShowToolDefinition: ({ getProjectService }: ProjectReadToolDependencies) => {
38
+ name: string;
39
+ label: string;
40
+ description: string;
41
+ promptSnippet: string;
42
+ promptGuidelines: string[];
43
+ parameters: import("@sinclair/typebox").TObject<{
44
+ projectRef: import("@sinclair/typebox").TString;
45
+ }>;
46
+ execute(_toolCallId: string, params: ProjectShowToolParams): Promise<{
47
+ content: {
48
+ type: "text";
49
+ text: string;
50
+ }[];
51
+ details: ProjectShowToolDetails;
52
+ }>;
53
+ };
54
+ declare const registerProjectReadTools: (pi: Pick<ExtensionAPI, "registerTool">, dependencies: ProjectReadToolDependencies) => void;
55
+ declare const resolveProjectByRef: (projectService: ProjectService, projectRef: string) => Promise<ProjectSummary | null>;
56
+ declare const formatProjectListContent: (details: ProjectListToolDetails) => string;
57
+ declare const formatProjectShowContent: (project: ProjectSummary) => string;
58
+ export type { ProjectListToolDetails, ProjectReadToolDependencies, ProjectShowToolDetails };
59
+ export { createProjectListToolDefinition, createProjectShowToolDefinition, formatProjectListContent, formatProjectShowContent, registerProjectReadTools, resolveProjectByRef, };
@@ -0,0 +1,131 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ const ProjectListParams = Type.Object({});
3
+ const ProjectShowParams = Type.Object({
4
+ projectRef: Type.String({ description: "Project ID or unique project name" }),
5
+ });
6
+ const createProjectListToolDefinition = ({ getProjectService }) => ({
7
+ name: "project_list",
8
+ label: "Project List",
9
+ description: "List projects.",
10
+ promptSnippet: "List projects using the native backend tool.",
11
+ promptGuidelines: [
12
+ "Use this tool when the user asks to list projects in normal chat.",
13
+ "Keep project_list unfiltered in V1.",
14
+ ],
15
+ parameters: ProjectListParams,
16
+ async execute(_toolCallId, _params) {
17
+ try {
18
+ const projectService = await getProjectService();
19
+ const projects = await projectService.listProjects();
20
+ const details = {
21
+ kind: "project_list",
22
+ projects,
23
+ total: projects.length,
24
+ empty: projects.length === 0,
25
+ };
26
+ return {
27
+ content: [{ type: "text", text: formatProjectListContent(details) }],
28
+ details,
29
+ };
30
+ }
31
+ catch (error) {
32
+ throw new Error(formatToolError(error, "project_list failed"), { cause: error });
33
+ }
34
+ },
35
+ });
36
+ const createProjectShowToolDefinition = ({ getProjectService }) => ({
37
+ name: "project_show",
38
+ label: "Project Show",
39
+ description: "Show project details by ID or unique name.",
40
+ promptSnippet: "Show details for a project by explicit ID or unique name.",
41
+ promptGuidelines: [
42
+ "Use this tool when the user asks for details about a known project.",
43
+ "Resolve by project ID first, then by unique name when needed.",
44
+ "If the project is missing or ambiguous, report that explicitly instead of guessing.",
45
+ ],
46
+ parameters: ProjectShowParams,
47
+ async execute(_toolCallId, params) {
48
+ const projectRef = normalizeRequiredText(params.projectRef, "projectRef");
49
+ try {
50
+ const projectService = await getProjectService();
51
+ const project = await resolveProjectByRef(projectService, projectRef);
52
+ if (!project) {
53
+ const details = {
54
+ kind: "project_show",
55
+ projectRef,
56
+ found: false,
57
+ };
58
+ return {
59
+ content: [{ type: "text", text: `Project not found: ${projectRef}` }],
60
+ details,
61
+ };
62
+ }
63
+ const details = {
64
+ kind: "project_show",
65
+ projectRef,
66
+ found: true,
67
+ project,
68
+ };
69
+ return {
70
+ content: [{ type: "text", text: formatProjectShowContent(project) }],
71
+ details,
72
+ };
73
+ }
74
+ catch (error) {
75
+ throw new Error(formatToolError(error, "project_show failed"), { cause: error });
76
+ }
77
+ },
78
+ });
79
+ const registerProjectReadTools = (pi, dependencies) => {
80
+ pi.registerTool(createProjectListToolDefinition(dependencies));
81
+ pi.registerTool(createProjectShowToolDefinition(dependencies));
82
+ };
83
+ const resolveProjectByRef = async (projectService, projectRef) => {
84
+ const project = await projectService.getProject(projectRef);
85
+ if (project) {
86
+ return project;
87
+ }
88
+ const projects = await projectService.listProjects();
89
+ const nameMatches = projects.filter((candidate) => candidate.name === projectRef);
90
+ if (nameMatches.length === 0) {
91
+ return null;
92
+ }
93
+ if (nameMatches.length > 1) {
94
+ throw new Error(`project_show found multiple projects named: ${projectRef}`);
95
+ }
96
+ return nameMatches[0] ?? null;
97
+ };
98
+ const formatProjectListContent = (details) => {
99
+ if (details.empty) {
100
+ return "No projects found.";
101
+ }
102
+ const lines = [`Projects (${details.total}):`];
103
+ for (const project of details.projects) {
104
+ lines.push(`- ${formatProjectSummaryLine(project)}`);
105
+ }
106
+ return lines.join("\n");
107
+ };
108
+ const formatProjectShowContent = (project) => [
109
+ `Project ${project.id}: ${project.name}`,
110
+ "",
111
+ `Status: ${project.status}`,
112
+ `Priority: ${project.priority}`,
113
+ "",
114
+ "Description:",
115
+ project.description?.trim().length ? project.description : "(none)",
116
+ ].join("\n");
117
+ const formatProjectSummaryLine = (project) => `${project.id} • ${project.name} • ${project.status} • ${project.priority}`;
118
+ const normalizeRequiredText = (value, fieldName) => {
119
+ const trimmedValue = value.trim();
120
+ if (trimmedValue.length === 0) {
121
+ throw new Error(`${fieldName} is required`);
122
+ }
123
+ return trimmedValue;
124
+ };
125
+ const formatToolError = (error, prefix) => {
126
+ if (error instanceof Error && error.message.trim().length > 0) {
127
+ return `${prefix}: ${error.message}`;
128
+ }
129
+ return prefix;
130
+ };
131
+ export { createProjectListToolDefinition, createProjectShowToolDefinition, formatProjectListContent, formatProjectShowContent, registerProjectReadTools, resolveProjectByRef, };
@@ -0,0 +1,130 @@
1
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
+ import type { RecurringMissPolicy, RecurringTemplateDetail } from "../domain/recurring";
3
+ import type { ProjectSummary, TaskPriority } from "../domain/task";
4
+ import type { ProjectService } from "../services/project-service";
5
+ import type { CreateRecurringInput, DeleteRecurringResult, RecurringService, UpdateRecurringInput } from "../services/recurring-service";
6
+ interface RecurringCreateToolParams {
7
+ title: string;
8
+ projectId: string;
9
+ schedule: string;
10
+ timezone: string;
11
+ startDate: string;
12
+ description?: string;
13
+ priority?: TaskPriority;
14
+ endDate?: string;
15
+ missPolicy?: RecurringMissPolicy;
16
+ }
17
+ interface RecurringUpdateToolParams {
18
+ recurringId: string;
19
+ title?: string;
20
+ projectId?: string;
21
+ schedule?: string;
22
+ timezone?: string;
23
+ startDate?: string;
24
+ description?: string;
25
+ priority?: TaskPriority;
26
+ endDate?: string;
27
+ missPolicy?: RecurringMissPolicy;
28
+ paused?: boolean;
29
+ }
30
+ interface RecurringDeleteToolParams {
31
+ recurringId: string;
32
+ }
33
+ interface RecurringCreateToolDetails {
34
+ kind: "recurring_create";
35
+ input: CreateRecurringInput;
36
+ template: RecurringTemplateDetail;
37
+ }
38
+ interface RecurringUpdateToolDetails {
39
+ kind: "recurring_update";
40
+ input: UpdateRecurringInput;
41
+ template: RecurringTemplateDetail;
42
+ }
43
+ interface RecurringDeleteToolDetails {
44
+ kind: "recurring_delete";
45
+ recurringId: string;
46
+ found: boolean;
47
+ deleted: boolean;
48
+ template?: DeleteRecurringResult;
49
+ }
50
+ interface RecurringMutationToolDependencies {
51
+ getRecurringService: () => Promise<RecurringService>;
52
+ getProjectService: () => Promise<ProjectService>;
53
+ }
54
+ declare const createRecurringCreateToolDefinition: ({ getRecurringService, getProjectService, }: RecurringMutationToolDependencies) => {
55
+ name: string;
56
+ label: string;
57
+ description: string;
58
+ promptSnippet: string;
59
+ promptGuidelines: string[];
60
+ parameters: import("@sinclair/typebox").TObject<{
61
+ title: import("@sinclair/typebox").TString;
62
+ projectId: import("@sinclair/typebox").TString;
63
+ schedule: import("@sinclair/typebox").TString;
64
+ timezone: import("@sinclair/typebox").TString;
65
+ startDate: import("@sinclair/typebox").TString;
66
+ description: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
67
+ priority: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnsafe<"low" | "medium" | "high">>;
68
+ endDate: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
69
+ missPolicy: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnsafe<"accumulate" | "rollForward">>;
70
+ }>;
71
+ execute(_toolCallId: string, params: RecurringCreateToolParams): Promise<{
72
+ content: {
73
+ type: "text";
74
+ text: string;
75
+ }[];
76
+ details: RecurringCreateToolDetails;
77
+ }>;
78
+ };
79
+ declare const createRecurringUpdateToolDefinition: ({ getRecurringService, getProjectService, }: RecurringMutationToolDependencies) => {
80
+ name: string;
81
+ label: string;
82
+ description: string;
83
+ promptSnippet: string;
84
+ promptGuidelines: string[];
85
+ parameters: import("@sinclair/typebox").TObject<{
86
+ recurringId: import("@sinclair/typebox").TString;
87
+ title: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
88
+ projectId: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
89
+ schedule: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
90
+ timezone: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
91
+ startDate: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
92
+ description: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
93
+ priority: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnsafe<"low" | "medium" | "high">>;
94
+ endDate: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
95
+ missPolicy: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnsafe<"accumulate" | "rollForward">>;
96
+ paused: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
97
+ }>;
98
+ execute(_toolCallId: string, params: RecurringUpdateToolParams): Promise<{
99
+ content: {
100
+ type: "text";
101
+ text: string;
102
+ }[];
103
+ details: RecurringUpdateToolDetails;
104
+ }>;
105
+ };
106
+ declare const createRecurringDeleteToolDefinition: ({ getRecurringService, }: RecurringMutationToolDependencies) => {
107
+ name: string;
108
+ label: string;
109
+ description: string;
110
+ promptSnippet: string;
111
+ promptGuidelines: string[];
112
+ parameters: import("@sinclair/typebox").TObject<{
113
+ recurringId: import("@sinclair/typebox").TString;
114
+ }>;
115
+ execute(_toolCallId: string, params: RecurringDeleteToolParams): Promise<{
116
+ content: {
117
+ type: "text";
118
+ text: string;
119
+ }[];
120
+ details: RecurringDeleteToolDetails;
121
+ }>;
122
+ };
123
+ declare const registerRecurringMutationTools: (pi: Pick<ExtensionAPI, "registerTool">, dependencies: RecurringMutationToolDependencies) => void;
124
+ declare const normalizeCreateRecurringInput: (params: RecurringCreateToolParams) => CreateRecurringInput;
125
+ declare const resolveCreateRecurringInput: (projectService: ProjectService, params: RecurringCreateToolParams) => Promise<CreateRecurringInput>;
126
+ declare const resolveUpdateRecurringInput: (projectService: ProjectService, params: RecurringUpdateToolParams) => Promise<UpdateRecurringInput>;
127
+ declare const normalizeUpdateRecurringInput: (params: RecurringUpdateToolParams) => UpdateRecurringInput;
128
+ declare const resolveProjectForRecurringMutation: (projectService: ProjectService, projectRef: string) => Promise<ProjectSummary>;
129
+ export type { RecurringCreateToolDetails, RecurringDeleteToolDetails, RecurringMutationToolDependencies, RecurringUpdateToolDetails, };
130
+ export { createRecurringCreateToolDefinition, createRecurringDeleteToolDefinition, createRecurringUpdateToolDefinition, normalizeCreateRecurringInput, normalizeUpdateRecurringInput, registerRecurringMutationTools, resolveCreateRecurringInput, resolveProjectForRecurringMutation, resolveUpdateRecurringInput, };
@@ -0,0 +1,317 @@
1
+ import { StringEnum } from "@mariozechner/pi-ai";
2
+ import { Type } from "@sinclair/typebox";
3
+ import { ToduRecurringServiceError } from "../services/todu/todu-recurring-service.js";
4
+ import { validateAndNormalizeScheduleRule } from "../utils/schedule.js";
5
+ const TASK_PRIORITY_VALUES = ["low", "medium", "high"];
6
+ const RECURRING_MISS_POLICY_VALUES = ["accumulate", "rollForward"];
7
+ const RecurringCreateParams = Type.Object({
8
+ title: Type.String({ description: "Recurring template title" }),
9
+ projectId: Type.String({ description: "Project ID or unique project name" }),
10
+ schedule: Type.String({ description: "Normalized RRULE schedule string" }),
11
+ timezone: Type.String({ description: "IANA timezone" }),
12
+ startDate: Type.String({ description: "Start date in YYYY-MM-DD format" }),
13
+ description: Type.Optional(Type.String({ description: "Optional recurring template description" })),
14
+ priority: Type.Optional(StringEnum(TASK_PRIORITY_VALUES, { description: "Optional recurring template priority" })),
15
+ endDate: Type.Optional(Type.String({
16
+ description: "Optional end date in YYYY-MM-DD format. Use an empty string to clear it.",
17
+ })),
18
+ missPolicy: Type.Optional(StringEnum(RECURRING_MISS_POLICY_VALUES, { description: "Optional recurring miss policy" })),
19
+ });
20
+ const RecurringUpdateParams = Type.Object({
21
+ recurringId: Type.String({ description: "Recurring template ID" }),
22
+ title: Type.Optional(Type.String({ description: "Optional replacement recurring template title" })),
23
+ projectId: Type.Optional(Type.String({ description: "Optional replacement project ID or unique project name" })),
24
+ schedule: Type.Optional(Type.String({ description: "Optional replacement normalized RRULE schedule string" })),
25
+ timezone: Type.Optional(Type.String({ description: "Optional replacement IANA timezone" })),
26
+ startDate: Type.Optional(Type.String({ description: "Optional replacement start date in YYYY-MM-DD format" })),
27
+ description: Type.Optional(Type.String({
28
+ description: "Optional replacement description. Use an empty string to clear it.",
29
+ })),
30
+ priority: Type.Optional(StringEnum(TASK_PRIORITY_VALUES, {
31
+ description: "Optional replacement recurring template priority",
32
+ })),
33
+ endDate: Type.Optional(Type.String({ description: "Optional replacement end date. Use an empty string to clear it." })),
34
+ missPolicy: Type.Optional(StringEnum(RECURRING_MISS_POLICY_VALUES, {
35
+ description: "Optional replacement recurring miss policy",
36
+ })),
37
+ paused: Type.Optional(Type.Boolean({ description: "Optional paused state" })),
38
+ });
39
+ const RecurringDeleteParams = Type.Object({
40
+ recurringId: Type.String({ description: "Recurring template ID" }),
41
+ });
42
+ const createRecurringCreateToolDefinition = ({ getRecurringService, getProjectService, }) => ({
43
+ name: "recurring_create",
44
+ label: "Recurring Create",
45
+ description: "Create a recurring task template.",
46
+ promptSnippet: "Create a recurring task template through the native recurring service.",
47
+ promptGuidelines: [
48
+ "Use this tool for backend recurring template creation in normal chat.",
49
+ "Provide an explicit project ID when known.",
50
+ "If only a project name is available, pass the exact unique project name so the tool can resolve it.",
51
+ "Provide normalized RRULE schedule strings instead of natural-language schedules.",
52
+ ],
53
+ parameters: RecurringCreateParams,
54
+ async execute(_toolCallId, params) {
55
+ try {
56
+ const projectService = await getProjectService();
57
+ const input = await resolveCreateRecurringInput(projectService, params);
58
+ const recurringService = await getRecurringService();
59
+ const template = await recurringService.createRecurring(input);
60
+ const details = {
61
+ kind: "recurring_create",
62
+ input,
63
+ template,
64
+ };
65
+ return {
66
+ content: [{ type: "text", text: formatRecurringCreateContent(template) }],
67
+ details,
68
+ };
69
+ }
70
+ catch (error) {
71
+ throw new Error(formatToolError(error, "recurring_create failed"), { cause: error });
72
+ }
73
+ },
74
+ });
75
+ const createRecurringUpdateToolDefinition = ({ getRecurringService, getProjectService, }) => ({
76
+ name: "recurring_update",
77
+ label: "Recurring Update",
78
+ description: "Update a recurring template's supported fields.",
79
+ promptSnippet: "Update a recurring template through the native recurring service.",
80
+ promptGuidelines: [
81
+ "Use this tool for backend recurring template updates in normal chat.",
82
+ "Do not use it for occurrence previews or forecast helpers.",
83
+ "Provide normalized RRULE schedule strings instead of natural-language schedules.",
84
+ ],
85
+ parameters: RecurringUpdateParams,
86
+ async execute(_toolCallId, params) {
87
+ try {
88
+ const projectService = await getProjectService();
89
+ const input = await resolveUpdateRecurringInput(projectService, params);
90
+ const recurringService = await getRecurringService();
91
+ const template = await recurringService.updateRecurring(input);
92
+ const details = {
93
+ kind: "recurring_update",
94
+ input,
95
+ template,
96
+ };
97
+ return {
98
+ content: [{ type: "text", text: formatRecurringUpdateContent(template, input) }],
99
+ details,
100
+ };
101
+ }
102
+ catch (error) {
103
+ throw new Error(formatToolError(error, "recurring_update failed"), { cause: error });
104
+ }
105
+ },
106
+ });
107
+ const createRecurringDeleteToolDefinition = ({ getRecurringService, }) => ({
108
+ name: "recurring_delete",
109
+ label: "Recurring Delete",
110
+ description: "Delete a recurring template by explicit ID.",
111
+ promptSnippet: "Delete a recurring template by explicit ID.",
112
+ promptGuidelines: [
113
+ "Use this tool for backend recurring template deletion in normal chat.",
114
+ "Do not use it for occurrence cleanup or generated-task deletion.",
115
+ ],
116
+ parameters: RecurringDeleteParams,
117
+ async execute(_toolCallId, params) {
118
+ const recurringId = normalizeRequiredText(params.recurringId, "recurringId");
119
+ try {
120
+ const recurringService = await getRecurringService();
121
+ const template = await recurringService.deleteRecurring(recurringId);
122
+ const details = {
123
+ kind: "recurring_delete",
124
+ recurringId,
125
+ found: true,
126
+ deleted: true,
127
+ template,
128
+ };
129
+ return {
130
+ content: [{ type: "text", text: formatRecurringDeleteContent(details) }],
131
+ details,
132
+ };
133
+ }
134
+ catch (error) {
135
+ if (isRecurringNotFoundError(error)) {
136
+ const details = {
137
+ kind: "recurring_delete",
138
+ recurringId,
139
+ found: false,
140
+ deleted: false,
141
+ };
142
+ return {
143
+ content: [{ type: "text", text: formatRecurringDeleteContent(details) }],
144
+ details,
145
+ };
146
+ }
147
+ throw new Error(formatToolError(error, "recurring_delete failed"), { cause: error });
148
+ }
149
+ },
150
+ });
151
+ const registerRecurringMutationTools = (pi, dependencies) => {
152
+ pi.registerTool(createRecurringCreateToolDefinition(dependencies));
153
+ pi.registerTool(createRecurringUpdateToolDefinition(dependencies));
154
+ pi.registerTool(createRecurringDeleteToolDefinition(dependencies));
155
+ };
156
+ const normalizeCreateRecurringInput = (params) => ({
157
+ title: normalizeRequiredText(params.title, "title"),
158
+ projectId: normalizeRequiredText(params.projectId, "projectId"),
159
+ schedule: normalizeScheduleInput(params.schedule),
160
+ timezone: normalizeRequiredText(params.timezone, "timezone"),
161
+ startDate: normalizeRequiredText(params.startDate, "startDate"),
162
+ description: normalizeOptionalDescription(params, "description"),
163
+ priority: params.priority,
164
+ endDate: normalizeOptionalDate(params, "endDate"),
165
+ missPolicy: params.missPolicy,
166
+ });
167
+ const resolveCreateRecurringInput = async (projectService, params) => {
168
+ const input = normalizeCreateRecurringInput(params);
169
+ const project = await resolveProjectForRecurringMutation(projectService, input.projectId);
170
+ return {
171
+ ...input,
172
+ projectId: project.id,
173
+ };
174
+ };
175
+ const resolveUpdateRecurringInput = async (projectService, params) => {
176
+ const input = normalizeUpdateRecurringInput(params);
177
+ if (input.projectId === undefined) {
178
+ return input;
179
+ }
180
+ const project = await resolveProjectForRecurringMutation(projectService, input.projectId);
181
+ return {
182
+ ...input,
183
+ projectId: project.id,
184
+ };
185
+ };
186
+ const normalizeUpdateRecurringInput = (params) => {
187
+ const input = {
188
+ recurringId: normalizeRequiredText(params.recurringId, "recurringId"),
189
+ priority: params.priority,
190
+ missPolicy: params.missPolicy,
191
+ paused: params.paused,
192
+ };
193
+ if (hasOwn(params, "title")) {
194
+ input.title = normalizeRequiredText(params.title ?? "", "title");
195
+ }
196
+ if (hasOwn(params, "projectId")) {
197
+ input.projectId = normalizeRequiredText(params.projectId ?? "", "projectId");
198
+ }
199
+ if (hasOwn(params, "schedule")) {
200
+ input.schedule = normalizeScheduleInput(params.schedule ?? "");
201
+ }
202
+ if (hasOwn(params, "timezone")) {
203
+ input.timezone = normalizeRequiredText(params.timezone ?? "", "timezone");
204
+ }
205
+ if (hasOwn(params, "startDate")) {
206
+ input.startDate = normalizeRequiredText(params.startDate ?? "", "startDate");
207
+ }
208
+ if (hasOwn(params, "description")) {
209
+ input.description = normalizeNullableText(params.description);
210
+ }
211
+ if (hasOwn(params, "endDate")) {
212
+ input.endDate = normalizeNullableText(params.endDate);
213
+ }
214
+ if (input.title === undefined &&
215
+ input.projectId === undefined &&
216
+ input.schedule === undefined &&
217
+ input.timezone === undefined &&
218
+ input.startDate === undefined &&
219
+ input.priority === undefined &&
220
+ input.missPolicy === undefined &&
221
+ input.paused === undefined &&
222
+ !hasOwn(input, "description") &&
223
+ !hasOwn(input, "endDate")) {
224
+ throw new Error("recurring_update requires at least one supported field: title, projectId, schedule, timezone, startDate, description, priority, endDate, missPolicy, or paused");
225
+ }
226
+ return input;
227
+ };
228
+ const resolveProjectForRecurringMutation = async (projectService, projectRef) => {
229
+ const project = await projectService.getProject(projectRef);
230
+ if (project) {
231
+ return project;
232
+ }
233
+ const projects = await projectService.listProjects();
234
+ const nameMatches = projects.filter((candidate) => candidate.name === projectRef);
235
+ if (nameMatches.length === 0) {
236
+ throw new Error(`project not found: ${projectRef}`);
237
+ }
238
+ if (nameMatches.length > 1) {
239
+ throw new Error(`multiple projects matched: ${projectRef}`);
240
+ }
241
+ const matchedProject = nameMatches[0];
242
+ if (!matchedProject) {
243
+ throw new Error(`project not found: ${projectRef}`);
244
+ }
245
+ return matchedProject;
246
+ };
247
+ const normalizeScheduleInput = (value) => {
248
+ const normalizedValue = normalizeRequiredText(value, "schedule");
249
+ const result = validateAndNormalizeScheduleRule(normalizedValue);
250
+ if (!result.ok) {
251
+ throw new Error(result.error.message);
252
+ }
253
+ return result.value.rule;
254
+ };
255
+ const normalizeOptionalDescription = (params, fieldName) => {
256
+ if (!hasOwn(params, fieldName)) {
257
+ return undefined;
258
+ }
259
+ return normalizeNullableText(params[fieldName]);
260
+ };
261
+ const normalizeOptionalDate = (params, fieldName) => {
262
+ if (!hasOwn(params, fieldName)) {
263
+ return undefined;
264
+ }
265
+ return normalizeNullableText(params[fieldName]);
266
+ };
267
+ const normalizeRequiredText = (value, fieldName) => {
268
+ const trimmedValue = value.trim();
269
+ if (trimmedValue.length === 0) {
270
+ throw new Error(`${fieldName} is required`);
271
+ }
272
+ return trimmedValue;
273
+ };
274
+ const normalizeNullableText = (value) => {
275
+ const trimmedValue = value?.trim() ?? "";
276
+ return trimmedValue.length > 0 ? trimmedValue : null;
277
+ };
278
+ const hasOwn = (value, property) => Object.prototype.hasOwnProperty.call(value, property);
279
+ const isRecurringNotFoundError = (error) => error instanceof ToduRecurringServiceError && error.causeCode === "not-found";
280
+ const formatRecurringCreateContent = (template) => [
281
+ `Created recurring template ${template.id}: ${template.title}`,
282
+ `Status: ${template.paused ? "paused" : "active"}`,
283
+ `Priority: ${template.priority}`,
284
+ `Project: ${template.projectName ?? template.projectId}`,
285
+ ].join("\n");
286
+ const formatRecurringUpdateContent = (template, input) => {
287
+ const changedFields = [
288
+ input.title !== undefined ? `title=${JSON.stringify(input.title)}` : null,
289
+ input.projectId !== undefined ? `projectId=${input.projectId}` : null,
290
+ input.schedule !== undefined ? `schedule=${input.schedule}` : null,
291
+ input.timezone !== undefined ? `timezone=${input.timezone}` : null,
292
+ input.startDate !== undefined ? `startDate=${input.startDate}` : null,
293
+ input.priority !== undefined ? `priority=${input.priority}` : null,
294
+ input.missPolicy !== undefined ? `missPolicy=${input.missPolicy}` : null,
295
+ input.paused !== undefined ? `paused=${input.paused}` : null,
296
+ hasOwn(input, "description")
297
+ ? `description=${input.description === null ? "cleared" : "updated"}`
298
+ : null,
299
+ hasOwn(input, "endDate")
300
+ ? `endDate=${input.endDate === null ? "cleared" : input.endDate}`
301
+ : null,
302
+ ].filter((value) => value !== null);
303
+ return [
304
+ `Updated recurring template ${template.id}: ${template.title}`,
305
+ `Changes: ${changedFields.join(", ")}`,
306
+ ].join("\n");
307
+ };
308
+ const formatRecurringDeleteContent = (details) => details.found
309
+ ? `Deleted recurring template ${details.recurringId}.`
310
+ : `Recurring template not found: ${details.recurringId}`;
311
+ const formatToolError = (error, prefix) => {
312
+ if (error instanceof Error && error.message.trim().length > 0) {
313
+ return `${prefix}: ${error.message}`;
314
+ }
315
+ return prefix;
316
+ };
317
+ export { createRecurringCreateToolDefinition, createRecurringDeleteToolDefinition, createRecurringUpdateToolDefinition, normalizeCreateRecurringInput, normalizeUpdateRecurringInput, registerRecurringMutationTools, resolveCreateRecurringInput, resolveProjectForRecurringMutation, resolveUpdateRecurringInput, };
@@ -0,0 +1,31 @@
1
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
+ import type { RecurringTemplateSummary } from "../domain/recurring";
3
+ import type { RecurringService } from "../services/recurring-service";
4
+ interface RecurringListToolDetails {
5
+ kind: "recurring_list";
6
+ templates: RecurringTemplateSummary[];
7
+ total: number;
8
+ empty: boolean;
9
+ }
10
+ interface RecurringReadToolDependencies {
11
+ getRecurringService: () => Promise<RecurringService>;
12
+ }
13
+ declare const createRecurringListToolDefinition: ({ getRecurringService, }: RecurringReadToolDependencies) => {
14
+ name: string;
15
+ label: string;
16
+ description: string;
17
+ promptSnippet: string;
18
+ promptGuidelines: string[];
19
+ parameters: import("@sinclair/typebox").TObject<{}>;
20
+ execute(_toolCallId: string, _params: Record<string, never>): Promise<{
21
+ content: {
22
+ type: "text";
23
+ text: string;
24
+ }[];
25
+ details: RecurringListToolDetails;
26
+ }>;
27
+ };
28
+ declare const registerRecurringReadTools: (pi: Pick<ExtensionAPI, "registerTool">, dependencies: RecurringReadToolDependencies) => void;
29
+ declare const formatRecurringListContent: (details: RecurringListToolDetails) => string;
30
+ export type { RecurringListToolDetails, RecurringReadToolDependencies };
31
+ export { createRecurringListToolDefinition, formatRecurringListContent, registerRecurringReadTools, };