@plainconceptsplatform/loop-task 2.6.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 (202) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +534 -0
  3. package/dist/app/App.js +132 -0
  4. package/dist/app/BoardLayout.js +5 -0
  5. package/dist/app/index.js +48 -0
  6. package/dist/app/providers/index.js +1 -0
  7. package/dist/app/router/index.js +16 -0
  8. package/dist/app/types.js +1 -0
  9. package/dist/cli/import-validator.js +152 -0
  10. package/dist/cli/import-writer.js +81 -0
  11. package/dist/cli.js +573 -0
  12. package/dist/client/cli-format.js +7 -0
  13. package/dist/client/commands.js +162 -0
  14. package/dist/client/ipc.js +96 -0
  15. package/dist/client/project-commands.js +164 -0
  16. package/dist/core/command/command-runner.js +300 -0
  17. package/dist/core/command/process-tree.js +73 -0
  18. package/dist/core/command/resolve-cwd.js +8 -0
  19. package/dist/core/command/stdout-capture-transform.js +73 -0
  20. package/dist/core/context/context-parser.js +65 -0
  21. package/dist/core/context/log-context.js +3 -0
  22. package/dist/core/context/template.js +20 -0
  23. package/dist/core/context/validate-context.js +19 -0
  24. package/dist/core/foreground/index.js +81 -0
  25. package/dist/core/logging/bounded-log-reader.js +368 -0
  26. package/dist/core/logging/log-follower.js +26 -0
  27. package/dist/core/logging/log-parser.js +29 -0
  28. package/dist/core/logging/log-rotator.js +34 -0
  29. package/dist/core/logging/rotating-log-stream.js +137 -0
  30. package/dist/core/loop/chain-executor.js +171 -0
  31. package/dist/core/loop/delay-utils.js +65 -0
  32. package/dist/core/loop/loop-controller.js +240 -0
  33. package/dist/core/loop/loop-runner.js +194 -0
  34. package/dist/core/loop/run-executor.js +144 -0
  35. package/dist/core/loop/types.js +1 -0
  36. package/dist/core/scheduling/index.js +16 -0
  37. package/dist/daemon/daemon-log.js +11 -0
  38. package/dist/daemon/diagnostics.js +85 -0
  39. package/dist/daemon/http/helpers.js +81 -0
  40. package/dist/daemon/http/openapi.js +119 -0
  41. package/dist/daemon/http/route-loops.js +311 -0
  42. package/dist/daemon/http/route-misc.js +60 -0
  43. package/dist/daemon/http/route-projects.js +55 -0
  44. package/dist/daemon/http/route-tasks.js +141 -0
  45. package/dist/daemon/http/routes.js +19 -0
  46. package/dist/daemon/http/server.js +82 -0
  47. package/dist/daemon/http/sse.js +36 -0
  48. package/dist/daemon/index.js +189 -0
  49. package/dist/daemon/ipc/send.js +5 -0
  50. package/dist/daemon/managers/loop-entry.js +27 -0
  51. package/dist/daemon/managers/loop-manager.js +346 -0
  52. package/dist/daemon/managers/loop-options.js +17 -0
  53. package/dist/daemon/managers/loop-serialization.js +53 -0
  54. package/dist/daemon/managers/project-manager.js +170 -0
  55. package/dist/daemon/managers/task-manager.js +94 -0
  56. package/dist/daemon/mcp/index.js +2 -0
  57. package/dist/daemon/mcp/openapi-sync.js +50 -0
  58. package/dist/daemon/mcp/server.js +167 -0
  59. package/dist/daemon/mcp/tools.js +382 -0
  60. package/dist/daemon/recipe/deferred-reload.js +32 -0
  61. package/dist/daemon/recipe/diagram-reader.js +26 -0
  62. package/dist/daemon/recipe/file-writer.js +52 -0
  63. package/dist/daemon/recipe/id-remapper.js +46 -0
  64. package/dist/daemon/recipe/runtime-state.js +23 -0
  65. package/dist/daemon/recipe/scanner.js +165 -0
  66. package/dist/daemon/recipe/task-store.js +33 -0
  67. package/dist/daemon/recipe/validator.js +35 -0
  68. package/dist/daemon/server/handlers/diagnostics-handlers.js +7 -0
  69. package/dist/daemon/server/handlers/index.js +101 -0
  70. package/dist/daemon/server/handlers/log-handlers.js +129 -0
  71. package/dist/daemon/server/handlers/loop-handlers.js +64 -0
  72. package/dist/daemon/server/handlers/project-handlers.js +42 -0
  73. package/dist/daemon/server/handlers/settings-handlers.js +8 -0
  74. package/dist/daemon/server/handlers/task-handlers.js +20 -0
  75. package/dist/daemon/server/handlers/telemetry-handlers.js +5 -0
  76. package/dist/daemon/server/index.js +117 -0
  77. package/dist/daemon/settings-manager.js +73 -0
  78. package/dist/daemon/spawner/index.js +204 -0
  79. package/dist/daemon/state/index.js +209 -0
  80. package/dist/daemon/telemetry/agent-integrations/agent-integration.js +1 -0
  81. package/dist/daemon/telemetry/agent-integrations/claude-code-integration.js +55 -0
  82. package/dist/daemon/telemetry/agent-integrations/detect-agent-integration.js +20 -0
  83. package/dist/daemon/telemetry/agent-integrations/index.js +3 -0
  84. package/dist/daemon/telemetry/agent-integrations/opencode-integration.js +72 -0
  85. package/dist/daemon/telemetry/index.js +6 -0
  86. package/dist/daemon/telemetry/noop-telemetry-adapter.js +53 -0
  87. package/dist/daemon/telemetry/open-telemetry-adapter.js +464 -0
  88. package/dist/daemon/telemetry/telemetry-manager.js +147 -0
  89. package/dist/daemon/telemetry/telemetry-redaction.js +67 -0
  90. package/dist/daemon/telemetry/telemetry-types.js +45 -0
  91. package/dist/daemon/telemetry/telemetry.js +1 -0
  92. package/dist/daemon/watcher/index.js +250 -0
  93. package/dist/duration.js +24 -0
  94. package/dist/entities/loops/filters.js +109 -0
  95. package/dist/entities/loops/index.js +1 -0
  96. package/dist/entities/projects/filters.js +69 -0
  97. package/dist/entities/projects/index.js +1 -0
  98. package/dist/entities/tasks/filters.js +41 -0
  99. package/dist/entities/tasks/index.js +1 -0
  100. package/dist/entry.js +16 -0
  101. package/dist/esm-loader.js +39 -0
  102. package/dist/features/chain-editor/ChainEditor.js +116 -0
  103. package/dist/features/chain-editor/mermaidToAscii.js +375 -0
  104. package/dist/features/chain-editor/renderChainDiagram.js +106 -0
  105. package/dist/features/code-editor/CodeEditorModal.js +84 -0
  106. package/dist/features/code-editor/CodeEditorPreview.js +14 -0
  107. package/dist/features/code-editor/useEditorKeyboard.js +150 -0
  108. package/dist/features/code-editor/useModalDimensions.js +9 -0
  109. package/dist/features/commands/commands.js +132 -0
  110. package/dist/features/commands/useCommandHandlers.js +280 -0
  111. package/dist/features/commands/useContextualActions.js +78 -0
  112. package/dist/features/commands/useGlobalShortcuts.js +154 -0
  113. package/dist/features/forms/FormRouter.js +46 -0
  114. package/dist/features/overlays/ContextHelpModal.js +8 -0
  115. package/dist/features/overlays/DiagramModal.js +45 -0
  116. package/dist/features/overlays/ExportModal.js +51 -0
  117. package/dist/features/overlays/HelpGuideModal.js +23 -0
  118. package/dist/features/overlays/HelpModal.js +59 -0
  119. package/dist/features/overlays/OverlayStack.js +10 -0
  120. package/dist/features/overlays/ProjectsModal.js +42 -0
  121. package/dist/features/overlays/TaskPickerModal.js +62 -0
  122. package/dist/features/overlays/WelcomeScreen.js +74 -0
  123. package/dist/features/overlays/useOverlayStack.js +55 -0
  124. package/dist/features/state/useAppState.js +151 -0
  125. package/dist/logger.js +25 -0
  126. package/dist/loop-config.js +152 -0
  127. package/dist/shared/clipboard.js +111 -0
  128. package/dist/shared/config/constants.js +104 -0
  129. package/dist/shared/config/paths.js +67 -0
  130. package/dist/shared/container/index.js +24 -0
  131. package/dist/shared/fs-utils.js +44 -0
  132. package/dist/shared/hooks/useBreakpoint.js +11 -0
  133. package/dist/shared/hooks/useDaemonSettings.js +41 -0
  134. package/dist/shared/hooks/useInject.js +6 -0
  135. package/dist/shared/hooks/useLogStream.js +37 -0
  136. package/dist/shared/hooks/useLoopFormValidation.js +131 -0
  137. package/dist/shared/hooks/useLoopPolling.js +30 -0
  138. package/dist/shared/hooks/useMouseScroll.js +56 -0
  139. package/dist/shared/hooks/useUndoRedo.js +77 -0
  140. package/dist/shared/i18n/en.json +709 -0
  141. package/dist/shared/i18n/index.js +11 -0
  142. package/dist/shared/providers/InversifyProvider.js +6 -0
  143. package/dist/shared/services/export-service.js +43 -0
  144. package/dist/shared/services/log-service.js +28 -0
  145. package/dist/shared/services/loop-service.js +78 -0
  146. package/dist/shared/services/project-service.js +39 -0
  147. package/dist/shared/services/settings-service.js +102 -0
  148. package/dist/shared/services/task-service.js +41 -0
  149. package/dist/shared/services/types.js +8 -0
  150. package/dist/shared/sleep.js +17 -0
  151. package/dist/shared/tail.js +4 -0
  152. package/dist/shared/ui/Button.js +18 -0
  153. package/dist/shared/ui/DebugPanel.js +9 -0
  154. package/dist/shared/ui/FocusableButton.js +18 -0
  155. package/dist/shared/ui/FocusableInput.js +72 -0
  156. package/dist/shared/ui/FocusableList.js +48 -0
  157. package/dist/shared/ui/Modal.js +13 -0
  158. package/dist/shared/ui/SelectModal.js +78 -0
  159. package/dist/shared/ui/Toast.js +37 -0
  160. package/dist/shared/ui/format.js +117 -0
  161. package/dist/shared/ui/hooks/useHoverState.js +11 -0
  162. package/dist/shared/ui/state.js +9 -0
  163. package/dist/shared/ui/theme.js +96 -0
  164. package/dist/shared/utils/log-lines.js +13 -0
  165. package/dist/shared/utils/paste.js +37 -0
  166. package/dist/shared/utils/syntax.js +128 -0
  167. package/dist/shared/utils/validation.js +56 -0
  168. package/dist/types.js +1 -0
  169. package/dist/visual-evidence/capture.js +51 -0
  170. package/dist/visual-evidence/cli.js +85 -0
  171. package/dist/visual-evidence/evidence-required.js +71 -0
  172. package/dist/visual-evidence/index.js +10 -0
  173. package/dist/visual-evidence/launch.js +61 -0
  174. package/dist/visual-evidence/manifest.js +32 -0
  175. package/dist/visual-evidence/openspec-resolver.js +45 -0
  176. package/dist/visual-evidence/publish.js +126 -0
  177. package/dist/visual-evidence/run.js +121 -0
  178. package/dist/visual-evidence/scenario-registry.js +132 -0
  179. package/dist/widgets/command-input/CommandDropdown.js +69 -0
  180. package/dist/widgets/command-input/CommandInput.js +133 -0
  181. package/dist/widgets/command-input/HintBar.js +9 -0
  182. package/dist/widgets/command-input/InputModes.js +72 -0
  183. package/dist/widgets/commands-browser/CommandsBrowserModal.js +85 -0
  184. package/dist/widgets/header/Header.js +54 -0
  185. package/dist/widgets/header/TabBar.js +20 -0
  186. package/dist/widgets/left-panel/LeftPanel.js +34 -0
  187. package/dist/widgets/left-panel/Navigator.js +107 -0
  188. package/dist/widgets/left-panel/ProjectsPage.js +76 -0
  189. package/dist/widgets/left-panel/ProjectsPageParts.js +34 -0
  190. package/dist/widgets/left-panel/TaskBrowser.js +132 -0
  191. package/dist/widgets/log-modal/LogModal.js +211 -0
  192. package/dist/widgets/loop-form/CreateForm.js +82 -0
  193. package/dist/widgets/loop-form/TextField.js +9 -0
  194. package/dist/widgets/loop-form/WizardForm.js +199 -0
  195. package/dist/widgets/loop-form/useCreateSteps.js +135 -0
  196. package/dist/widgets/loop-form/useHandleComplete.js +94 -0
  197. package/dist/widgets/project-form/ProjectForm.js +83 -0
  198. package/dist/widgets/right-panel/Inspector.js +41 -0
  199. package/dist/widgets/right-panel/RightPanel.js +38 -0
  200. package/dist/widgets/right-panel/RunHistory.js +187 -0
  201. package/dist/widgets/task-form/TaskForm.js +272 -0
  202. package/package.json +119 -0
@@ -0,0 +1,11 @@
1
+ import en from "./en.json" with { type: "json" };
2
+ const messages = en;
3
+ export function t(key, params) {
4
+ let result = messages[key] ?? key;
5
+ if (params) {
6
+ for (const name of Object.keys(params)) {
7
+ result = result.split(`{${name}}`).join(String(params[name]));
8
+ }
9
+ }
10
+ return result;
11
+ }
@@ -0,0 +1,6 @@
1
+ import React, { createContext } from "react";
2
+ import { container } from "../container/index.js";
3
+ export const InversifyContext = createContext({ container });
4
+ export function InversifyProvider(props) {
5
+ return React.createElement(InversifyContext.Provider, { value: { container } }, props.children);
6
+ }
@@ -0,0 +1,43 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { injectable } from "inversify";
8
+ import fs from "node:fs/promises";
9
+ import path from "node:path";
10
+ import { getDataDir } from "../config/paths.js";
11
+ let IpcExportService = class IpcExportService {
12
+ constructor(loopService, taskService, projectService) {
13
+ this.loopService = loopService;
14
+ this.taskService = taskService;
15
+ this.projectService = projectService;
16
+ }
17
+ async exportConfig() {
18
+ const [loops, tasks, projects] = await Promise.all([
19
+ this.loopService.list().catch(() => []),
20
+ this.taskService.list().catch(() => []),
21
+ this.projectService.list().catch(() => []),
22
+ ]);
23
+ const exportData = {
24
+ version: 2,
25
+ exportedAt: new Date().toISOString(),
26
+ loops,
27
+ tasks,
28
+ projects,
29
+ };
30
+ const json = JSON.stringify(exportData, null, 2);
31
+ const dataDir = getDataDir();
32
+ const exportsDir = path.join(dataDir, "exports");
33
+ await fs.mkdir(exportsDir, { recursive: true });
34
+ const ts = new Date().toISOString().replace(/[:.]/g, "-");
35
+ const filePath = path.join(exportsDir, `loop-export-${ts}.json`);
36
+ await fs.writeFile(filePath, json, "utf-8");
37
+ return { json, filePath };
38
+ }
39
+ };
40
+ IpcExportService = __decorate([
41
+ injectable()
42
+ ], IpcExportService);
43
+ export { IpcExportService };
@@ -0,0 +1,28 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { injectable } from "inversify";
8
+ import { sendRequest, streamRequest } from "../../client/ipc.js";
9
+ import { LOG_TAIL_DEFAULT } from "../config/constants.js";
10
+ let IpcLogService = class IpcLogService {
11
+ async fetchRunLog(id, runNumber) {
12
+ const response = await sendRequest({ type: "run-log", payload: { id, runNumber } });
13
+ if (response.type !== "ok") {
14
+ throw new Error(response.message ?? "Failed to fetch run log");
15
+ }
16
+ return response.data ?? "";
17
+ }
18
+ streamRunLog(id, runNumber, onLine, onEnd, onError) {
19
+ return streamRequest({ type: "run-log-stream", payload: { id, runNumber } }, onLine, onEnd, onError);
20
+ }
21
+ streamLogs(id, onLine, onError) {
22
+ return streamRequest({ type: "logs", payload: { id, follow: true, tail: LOG_TAIL_DEFAULT } }, onLine, () => { }, onError);
23
+ }
24
+ };
25
+ IpcLogService = __decorate([
26
+ injectable()
27
+ ], IpcLogService);
28
+ export { IpcLogService };
@@ -0,0 +1,78 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { injectable } from "inversify";
8
+ import { sendRequest } from "../../client/ipc.js";
9
+ import { t } from "../i18n/index.js";
10
+ let IpcLoopService = class IpcLoopService {
11
+ async list() {
12
+ const response = await sendRequest({ type: "list" });
13
+ if (response.type !== "ok") {
14
+ throw new Error(response.message);
15
+ }
16
+ return response.data;
17
+ }
18
+ async create(options, intervalHuman) {
19
+ const response = await sendRequest({
20
+ type: "start",
21
+ payload: { ...options, intervalHuman },
22
+ });
23
+ if (response.type !== "ok") {
24
+ throw new Error(response.message);
25
+ }
26
+ return response.data.id;
27
+ }
28
+ async update(id, options, intervalHuman) {
29
+ const response = await sendRequest({
30
+ type: "update",
31
+ payload: { id, ...options, intervalHuman },
32
+ });
33
+ if (response.type !== "ok") {
34
+ throw new Error(response.message);
35
+ }
36
+ return response.data.id;
37
+ }
38
+ async pause(id) {
39
+ const response = await sendRequest({ type: "pause", payload: { id } });
40
+ if (response.type !== "ok") {
41
+ throw new Error(response.message ?? t("errors.pauseFailed"));
42
+ }
43
+ }
44
+ async resume(id) {
45
+ const response = await sendRequest({ type: "resume", payload: { id } });
46
+ if (response.type !== "ok") {
47
+ throw new Error(response.message ?? t("errors.resumeFailed"));
48
+ }
49
+ }
50
+ async stop(id) {
51
+ const response = await sendRequest({ type: "stop-loop", payload: { id } });
52
+ if (response.type !== "ok") {
53
+ throw new Error(response.message ?? "Stop failed");
54
+ }
55
+ }
56
+ async play(id) {
57
+ const response = await sendRequest({ type: "play-loop", payload: { id } });
58
+ if (response.type !== "ok") {
59
+ throw new Error(response.message ?? "Play failed");
60
+ }
61
+ }
62
+ async trigger(id) {
63
+ const response = await sendRequest({ type: "trigger", payload: { id } });
64
+ if (response.type !== "ok") {
65
+ throw new Error(response.message ?? t("errors.forceRunFailed"));
66
+ }
67
+ }
68
+ async delete(id) {
69
+ const response = await sendRequest({ type: "delete", payload: { id } });
70
+ if (response.type !== "ok") {
71
+ throw new Error(response.message ?? t("errors.deleteFailed"));
72
+ }
73
+ }
74
+ };
75
+ IpcLoopService = __decorate([
76
+ injectable()
77
+ ], IpcLoopService);
78
+ export { IpcLoopService };
@@ -0,0 +1,39 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { injectable } from "inversify";
8
+ import { sendRequest } from "../../client/ipc.js";
9
+ import { t } from "../i18n/index.js";
10
+ let IpcProjectService = class IpcProjectService {
11
+ async list() {
12
+ const response = await sendRequest({ type: "project-list" });
13
+ if (response.type !== "ok")
14
+ throw new Error(response.message);
15
+ return response.data;
16
+ }
17
+ async create(name, color, directory, githubSource) {
18
+ const response = await sendRequest({ type: "project-create", payload: { name, color, directory, githubSource } });
19
+ if (response.type !== "ok")
20
+ throw new Error(response.message);
21
+ return response.data;
22
+ }
23
+ async update(id, name, color, directory, githubSource) {
24
+ const response = await sendRequest({ type: "project-update", payload: { id, name, color, directory, githubSource } });
25
+ if (response.type !== "ok") {
26
+ throw new Error(response.message ?? t("project.error.updateFailed"));
27
+ }
28
+ }
29
+ async delete(id) {
30
+ const response = await sendRequest({ type: "project-delete", payload: { id } });
31
+ if (response.type !== "ok") {
32
+ throw new Error(response.message ?? t("project.error.deleteFailed"));
33
+ }
34
+ }
35
+ };
36
+ IpcProjectService = __decorate([
37
+ injectable()
38
+ ], IpcProjectService);
39
+ export { IpcProjectService };
@@ -0,0 +1,102 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { injectable } from "inversify";
8
+ import { sendRequest } from "../../client/ipc.js";
9
+ let IpcSettingsService = class IpcSettingsService {
10
+ async getSettings() {
11
+ const response = await sendRequest({ type: "settings-get" });
12
+ if (response.type !== "ok") {
13
+ throw new Error(response.message ?? "Failed to get settings");
14
+ }
15
+ return response.data;
16
+ }
17
+ async getHttpApiEnabled() {
18
+ const response = await sendRequest({ type: "settings-get" });
19
+ if (response.type !== "ok") {
20
+ throw new Error(response.message ?? "Failed to get settings");
21
+ }
22
+ return response.data.httpApiEnabled;
23
+ }
24
+ async setHttpApiEnabled(enabled) {
25
+ const response = await sendRequest({ type: "settings-set", settings: { httpApiEnabled: enabled } });
26
+ if (response.type !== "ok") {
27
+ throw new Error(response.message ?? "Failed to set settings");
28
+ }
29
+ return response.data;
30
+ }
31
+ async getMcpApiEnabled() {
32
+ const response = await sendRequest({ type: "settings-get" });
33
+ if (response.type !== "ok") {
34
+ throw new Error(response.message ?? "Failed to get settings");
35
+ }
36
+ return response.data.mcpApiEnabled;
37
+ }
38
+ async setMcpApiEnabled(enabled) {
39
+ const response = await sendRequest({ type: "settings-set", settings: { mcpApiEnabled: enabled } });
40
+ if (response.type !== "ok") {
41
+ throw new Error(response.message ?? "Failed to set settings");
42
+ }
43
+ return response.data;
44
+ }
45
+ async getTelemetryEnabled() {
46
+ const response = await sendRequest({ type: "settings-get" });
47
+ if (response.type !== "ok") {
48
+ throw new Error(response.message ?? "Failed to get settings");
49
+ }
50
+ return response.data.telemetryEnabled;
51
+ }
52
+ async setTelemetryEnabled(enabled) {
53
+ const response = await sendRequest({ type: "settings-set", settings: { telemetryEnabled: enabled } });
54
+ if (response.type !== "ok") {
55
+ throw new Error(response.message ?? "Failed to set settings");
56
+ }
57
+ return response.data;
58
+ }
59
+ async getTelemetrySettings() {
60
+ const response = await sendRequest({ type: "settings-get" });
61
+ if (response.type !== "ok") {
62
+ throw new Error(response.message ?? "Failed to get settings");
63
+ }
64
+ const s = response.data;
65
+ return {
66
+ enabled: s.telemetryEnabled,
67
+ endpoint: s.telemetryEndpoint,
68
+ protocol: s.telemetryProtocol,
69
+ autoInstrumentAgents: s.telemetryAutoInstrumentAgents,
70
+ captureContent: s.telemetryCaptureContent,
71
+ captureCommandOutput: s.telemetryCaptureCommandOutput,
72
+ serviceName: s.telemetryServiceName,
73
+ };
74
+ }
75
+ async setTelemetrySettings(settings) {
76
+ const mapping = {
77
+ enabled: "telemetryEnabled",
78
+ endpoint: "telemetryEndpoint",
79
+ protocol: "telemetryProtocol",
80
+ autoInstrumentAgents: "telemetryAutoInstrumentAgents",
81
+ captureContent: "telemetryCaptureContent",
82
+ captureCommandOutput: "telemetryCaptureCommandOutput",
83
+ serviceName: "telemetryServiceName",
84
+ };
85
+ const daemonSettings = {};
86
+ for (const [key, value] of Object.entries(settings)) {
87
+ const daemonKey = mapping[key];
88
+ if (daemonKey) {
89
+ daemonSettings[daemonKey] = value;
90
+ }
91
+ }
92
+ const response = await sendRequest({ type: "settings-set", settings: daemonSettings });
93
+ if (response.type !== "ok") {
94
+ throw new Error(response.message ?? "Failed to set settings");
95
+ }
96
+ return response.data;
97
+ }
98
+ };
99
+ IpcSettingsService = __decorate([
100
+ injectable()
101
+ ], IpcSettingsService);
102
+ export { IpcSettingsService };
@@ -0,0 +1,41 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { injectable } from "inversify";
8
+ import { sendRequest } from "../../client/ipc.js";
9
+ let IpcTaskService = class IpcTaskService {
10
+ async list() {
11
+ const response = await sendRequest({ type: "task-list" });
12
+ if (response.type !== "ok") {
13
+ throw new Error(response.message);
14
+ }
15
+ return response.data;
16
+ }
17
+ async create(payload) {
18
+ const response = await sendRequest({ type: "task-create", payload });
19
+ if (response.type !== "ok") {
20
+ throw new Error(response.message);
21
+ }
22
+ return response.data;
23
+ }
24
+ async update(id, payload) {
25
+ const response = await sendRequest({ type: "task-update", payload: { id, ...payload } });
26
+ if (response.type !== "ok") {
27
+ throw new Error(response.message);
28
+ }
29
+ return response.data;
30
+ }
31
+ async delete(id) {
32
+ const response = await sendRequest({ type: "task-delete", payload: { id } });
33
+ if (response.type !== "ok") {
34
+ throw new Error(response.message ?? "Task delete failed");
35
+ }
36
+ }
37
+ };
38
+ IpcTaskService = __decorate([
39
+ injectable()
40
+ ], IpcTaskService);
41
+ export { IpcTaskService };
@@ -0,0 +1,8 @@
1
+ export const TYPES = {
2
+ LoopService: Symbol.for("LoopService"),
3
+ TaskService: Symbol.for("TaskService"),
4
+ ProjectService: Symbol.for("ProjectService"),
5
+ LogService: Symbol.for("LogService"),
6
+ ExportService: Symbol.for("ExportService"),
7
+ SettingsService: Symbol.for("SettingsService"),
8
+ };
@@ -0,0 +1,17 @@
1
+ export function sleep(ms, signal) {
2
+ return new Promise((resolve, reject) => {
3
+ if (signal.aborted) {
4
+ reject(signal.reason);
5
+ return;
6
+ }
7
+ const timer = setTimeout(() => {
8
+ signal.removeEventListener("abort", onAbort);
9
+ resolve();
10
+ }, ms);
11
+ const onAbort = () => {
12
+ clearTimeout(timer);
13
+ reject(signal.reason);
14
+ };
15
+ signal.addEventListener("abort", onAbort, { once: true });
16
+ });
17
+ }
@@ -0,0 +1,4 @@
1
+ export function tail(content, count) {
2
+ const lines = content.split("\n");
3
+ return count > 0 ? lines.slice(-count) : lines;
4
+ }
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box, Text } from "ink";
3
+ import { darkTheme as theme } from "./theme.js";
4
+ export function Button(props) {
5
+ const { label, focused, variant = "default" } = props;
6
+ const bgColor = focused
7
+ ? variant === "danger"
8
+ ? theme.semantic.danger
9
+ : theme.bg.active
10
+ : undefined;
11
+ const fgColor = focused
12
+ ? theme.text.inverse
13
+ : variant === "danger"
14
+ ? theme.semantic.danger
15
+ : theme.text.primary;
16
+ const indicator = focused ? "› " : " ";
17
+ return (_jsx(Box, { backgroundColor: bgColor, paddingX: 1, marginRight: 1, flexShrink: 0, children: _jsx(Text, { color: fgColor, bold: true, children: indicator + label }) }));
18
+ }
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from "ink";
3
+ import { darkTheme as theme } from "./theme.js";
4
+ const MAX_ENTRIES = 12;
5
+ export function DebugPanel(props) {
6
+ const panelWidth = props.breakpoint === "wide" || props.breakpoint === undefined ? "22%" : "100%";
7
+ return (_jsxs(Box, { flexDirection: "column", width: panelWidth, flexShrink: 0, borderStyle: "single", borderColor: theme.semantic.warning, backgroundColor: theme.bg.surface, children: [_jsx(Box, { paddingLeft: 1, children: _jsx(Text, { color: theme.semantic.warning, bold: true, children: "DEBUG" }) }), _jsx(Box, { paddingLeft: 1, flexDirection: "column", children: props.entries.length === 0 ? (_jsx(Text, { color: theme.text.muted, children: "press keys..." })) : (props.entries.map((e) => (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: theme.text.primary, children: ["len=", e.len, " codes=[", e.codes, "]"] }), _jsxs(Text, { color: theme.text.muted, children: ["c=", e.ctrl ? 1 : 0, " r=", e.return ? 1 : 0, " t=", e.tab ? 1 : 0, " e=", e.escape ? 1 : 0] })] }, e.id)))) })] }));
8
+ }
9
+ export { MAX_ENTRIES };
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box, Text, useFocus, useInput } from "ink";
3
+ import { darkTheme as theme } from "./theme.js";
4
+ export function FocusableButton(props) {
5
+ const { label, color, onPress, variant = "default" } = props;
6
+ const { isFocused } = useFocus();
7
+ useInput((_input, key) => {
8
+ if (_input.includes("[<"))
9
+ return;
10
+ if (key.return) {
11
+ onPress?.();
12
+ }
13
+ }, { isActive: isFocused });
14
+ const accent = variant === "danger" ? theme.semantic.danger : color;
15
+ const textColor = isFocused ? theme.text.inverse : accent;
16
+ const indicator = isFocused ? "› " : " ";
17
+ return (_jsx(Box, { backgroundColor: isFocused ? theme.bg.active : undefined, paddingX: 1, marginRight: 1, flexShrink: 0, children: _jsx(Text, { color: textColor, bold: true, children: indicator + label }) }));
18
+ }
@@ -0,0 +1,72 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useCallback } from "react";
3
+ import { Box, Text, useFocus, useInput } from "ink";
4
+ import { darkTheme as theme } from "./theme.js";
5
+ import { sanitizePaste } from "../utils/paste.js";
6
+ export function FocusableInput(props) {
7
+ const { value, onChange, onSubmit } = props;
8
+ const placeholder = props.placeholder ?? "type here...";
9
+ const { isFocused } = useFocus();
10
+ const [cursorOffset, setCursorOffset] = useState(value.length);
11
+ const insertText = useCallback((text) => {
12
+ const before = value.slice(0, cursorOffset);
13
+ const after = value.slice(cursorOffset);
14
+ const next = before + text + after;
15
+ onChange(next);
16
+ setCursorOffset(before.length + text.length);
17
+ }, [value, cursorOffset, onChange]);
18
+ useInput((input, key) => {
19
+ // Bracketed paste: content wrapped in ESC[200~ ... ESC[201~
20
+ if (input.includes("\x1b[200~")) {
21
+ insertText(sanitizePaste(input));
22
+ return;
23
+ }
24
+ // Swallow SGR mouse sequences
25
+ if (input.includes("[<"))
26
+ return;
27
+ if (key.ctrl || key.escape)
28
+ return;
29
+ // Multi-char containing CR/LF with no bracketed markers ignore
30
+ if (input.length > 1 && (input.includes("\r") || input.includes("\n")))
31
+ return;
32
+ if (key.return) {
33
+ onSubmit?.(value);
34
+ return;
35
+ }
36
+ if (key.backspace || key.delete) {
37
+ if (cursorOffset > 0) {
38
+ const next = value.slice(0, cursorOffset - 1) + value.slice(cursorOffset);
39
+ onChange(next);
40
+ setCursorOffset((c) => c - 1);
41
+ }
42
+ return;
43
+ }
44
+ if (key.leftArrow) {
45
+ if (cursorOffset > 0)
46
+ setCursorOffset((c) => c - 1);
47
+ return;
48
+ }
49
+ if (key.rightArrow) {
50
+ if (cursorOffset < value.length)
51
+ setCursorOffset((c) => c + 1);
52
+ return;
53
+ }
54
+ // Multi-char printable input = unbracketed single-line paste (e.g. right-click)
55
+ if (input.length > 1 && !key.meta) {
56
+ insertText(sanitizePaste(input));
57
+ return;
58
+ }
59
+ // Single printable char
60
+ if (input.length === 1 && input >= " " && input <= "~") {
61
+ insertText(input);
62
+ }
63
+ }, { isActive: isFocused });
64
+ const borderColor = isFocused ? theme.accent.brand : theme.border.dim;
65
+ const backgroundColor = isFocused ? theme.bg.input : undefined;
66
+ const showPlaceholder = value.length === 0;
67
+ // Render value with cursor
68
+ const before = value.slice(0, cursorOffset);
69
+ const cursorChar = cursorOffset < value.length ? value[cursorOffset] : " ";
70
+ const after = cursorOffset < value.length ? value.slice(cursorOffset + 1) : "";
71
+ return (_jsxs(Box, { borderStyle: "single", borderColor: borderColor, backgroundColor: backgroundColor, paddingLeft: 1, overflow: "hidden", children: [showPlaceholder ? (_jsx(Text, { color: theme.text.muted, children: placeholder })) : (_jsxs(Text, { color: theme.text.primary, children: [before, _jsx(Text, { inverse: true, children: cursorChar }), after] })), isFocused && showPlaceholder ? _jsx(Text, { inverse: true, children: " " }) : null] }));
72
+ }
@@ -0,0 +1,48 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useMemo } from "react";
3
+ import { Box, Text, useInput } from "ink";
4
+ import { darkTheme as theme } from "./theme.js";
5
+ export function FocusableList(props) {
6
+ const { items, renderItem, onSelect, onActivate, selectedIndex, isFocused, navActive = true, limit, } = props;
7
+ const n = items.length;
8
+ useInput((input, key) => {
9
+ if (n === 0)
10
+ return;
11
+ if (input.includes("[<"))
12
+ return;
13
+ if (key.upArrow || input === "k") {
14
+ const next = selectedIndex <= 0 ? n - 1 : selectedIndex - 1;
15
+ onSelect(next);
16
+ return;
17
+ }
18
+ if (key.downArrow || input === "j") {
19
+ const next = selectedIndex >= n - 1 ? 0 : selectedIndex + 1;
20
+ onSelect(next);
21
+ return;
22
+ }
23
+ if (key.return) {
24
+ onActivate(selectedIndex);
25
+ return;
26
+ }
27
+ }, { isActive: isFocused && navActive });
28
+ const { visible, localSelected } = useMemo(() => {
29
+ if (n === 0)
30
+ return { visible: [], localSelected: 0 };
31
+ const eff = Math.min(limit, n);
32
+ if (n <= eff) {
33
+ return { visible: items, localSelected: selectedIndex };
34
+ }
35
+ const offset = Math.floor(eff / 2);
36
+ const r = (((selectedIndex - offset) % n) + n) % n;
37
+ const rotated = [...items.slice(r), ...items.slice(0, r)];
38
+ return { visible: rotated.slice(0, eff), localSelected: offset };
39
+ }, [items, selectedIndex, limit, n]);
40
+ if (n === 0) {
41
+ throw new Error("FocusableList requires at least one item");
42
+ }
43
+ return (_jsx(Box, { flexDirection: "column", children: visible.map((item, i) => {
44
+ const isSelected = i === localSelected;
45
+ const indicator = isSelected ? "\u203a " : " ";
46
+ return (_jsxs(Box, { backgroundColor: isSelected ? (isFocused && navActive ? theme.bg.active : isFocused ? theme.bg.hover : undefined) : undefined, children: [_jsx(Text, { color: isSelected ? theme.text.inverse : theme.text.primary, children: indicator }), renderItem(item, isSelected)] }, i));
47
+ }) }));
48
+ }
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text, useInput } from "ink";
3
+ import { darkTheme as theme } from "./theme.js";
4
+ export function Modal(props) {
5
+ useInput((_input, key) => {
6
+ if (_input.includes("[<"))
7
+ return;
8
+ if (key.escape) {
9
+ props.onClose();
10
+ }
11
+ });
12
+ return (_jsx(Box, { position: "absolute", top: 0, left: 0, width: "100%", height: "100%", justifyContent: "center", alignItems: "center", children: _jsxs(Box, { borderStyle: "round", borderColor: theme.accent.brand, backgroundColor: theme.bg.elevated, paddingX: 2, paddingY: 1, flexDirection: "column", width: props.width ?? "60%", height: props.height, children: [_jsx(Text, { color: theme.accent.brand, bold: true, children: props.title }), _jsx(Box, { flexDirection: "column", marginTop: 1, flexGrow: 1, children: props.children })] }) }));
13
+ }
@@ -0,0 +1,78 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useMemo, useCallback } from "react";
3
+ import { Box, Text, useInput } from "ink";
4
+ import { darkTheme as theme } from "./theme.js";
5
+ import { t } from "../i18n/index.js";
6
+ const MAX_VISIBLE = 10;
7
+ /**
8
+ * Filterable modal picker: type to narrow, up/down to navigate, enter to
9
+ * select, esc to cancel. The single interaction pattern for every enumerated
10
+ * field in the board a fixed set of two options and a list of dozens both
11
+ * use this, so users only ever learn one gesture for "change this value".
12
+ */
13
+ export function SelectModal(props) {
14
+ const { title, options, initialValue, onSelect, onClose } = props;
15
+ const accent = props.accentColor ?? theme.accent.brand;
16
+ const [query, setQuery] = useState("");
17
+ const [cursor, setCursor] = useState(() => {
18
+ const idx = options.findIndex((o) => o.value === initialValue);
19
+ return idx >= 0 ? idx : 0;
20
+ });
21
+ const filtered = useMemo(() => {
22
+ const q = query.trim().toLowerCase();
23
+ if (!q)
24
+ return options;
25
+ return options.filter((o) => o.label.toLowerCase().includes(q));
26
+ }, [options, query]);
27
+ const clampedCursor = filtered.length > 0 ? cursor % filtered.length : 0;
28
+ const selectAt = useCallback((idx) => {
29
+ const option = filtered[idx];
30
+ if (option)
31
+ onSelect(option);
32
+ }, [filtered, onSelect]);
33
+ useInput((input, key) => {
34
+ if (input.includes("[<"))
35
+ return;
36
+ if (key.escape) {
37
+ onClose();
38
+ return;
39
+ }
40
+ if (key.return) {
41
+ selectAt(clampedCursor);
42
+ return;
43
+ }
44
+ if (key.upArrow) {
45
+ setCursor((prev) => (prev <= 0 ? Math.max(0, filtered.length - 1) : prev - 1));
46
+ return;
47
+ }
48
+ if (key.downArrow) {
49
+ setCursor((prev) => (prev >= filtered.length - 1 ? 0 : prev + 1));
50
+ return;
51
+ }
52
+ if (key.backspace || key.delete) {
53
+ setQuery((prev) => prev.slice(0, -1));
54
+ setCursor(0);
55
+ return;
56
+ }
57
+ if (key.ctrl)
58
+ return;
59
+ if (input.length === 1 && input >= " " && input <= "~") {
60
+ setQuery((prev) => prev + input);
61
+ setCursor(0);
62
+ }
63
+ });
64
+ const start = Math.max(0, clampedCursor - Math.floor(MAX_VISIBLE / 2));
65
+ const end = Math.min(filtered.length, start + MAX_VISIBLE);
66
+ const visible = filtered.slice(start, end);
67
+ return (_jsx(Box, { position: "absolute", top: 0, left: 0, width: "100%", height: "100%", justifyContent: "center", alignItems: "center", children: _jsxs(Box, { width: 50, height: Math.min(20, visible.length + 7), flexDirection: "column", backgroundColor: theme.bg.elevated, borderStyle: "round", borderColor: accent, paddingX: 1, paddingY: 0, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { color: accent, bold: true, children: title }), _jsx(Text, { color: theme.text.muted, children: "esc" })] }), _jsxs(Box, { marginTop: 0, children: [_jsx(Text, { color: theme.text.muted, children: "› " }), _jsx(Text, { color: query ? theme.text.primary : theme.text.muted, children: query || t("selectModal.searchPlaceholder") }), _jsx(Text, { inverse: true, children: " " })] }), _jsx(Box, { flexDirection: "column", marginTop: 0, children: visible.length === 0 ? (_jsx(Text, { color: theme.text.muted, children: t("selectModal.empty") })) : (visible.map((option, visIdx) => {
68
+ const realIdx = start + visIdx;
69
+ const isSelected = realIdx === clampedCursor;
70
+ const isCurrent = option.value === initialValue;
71
+ return (_jsxs(Box, { backgroundColor: isSelected ? theme.bg.active : undefined, children: [_jsx(Text, { color: isSelected ? theme.text.inverse : theme.text.muted, children: isSelected ? "❯ " : " " }), _jsx(Text, { color: isSelected ? theme.text.inverse : isCurrent ? accent : theme.text.primary, children: option.label })] }, option.value));
72
+ })) }), _jsxs(Box, { marginTop: 1, justifyContent: "space-between", children: [_jsxs(Text, { color: theme.text.muted, children: [filtered.length, "/", options.length] }), _jsx(Text, { color: theme.text.muted, children: t("selectModal.hint") })] })] }) }));
73
+ }
74
+ /** Bordered field showing the current value, matching TextField's focus styling. */
75
+ export function SelectValueField(props) {
76
+ const { label, placeholder, isActive } = props;
77
+ return (_jsxs(Box, { flexDirection: "column", width: "100%", children: [_jsx(Box, { borderStyle: "single", borderColor: isActive ? theme.accent.brand : theme.border.dim, backgroundColor: isActive ? theme.bg.input : undefined, paddingLeft: 1, overflow: "hidden", width: "100%", children: label ? (_jsx(Text, { color: theme.text.primary, children: label })) : (_jsx(Text, { color: theme.text.muted, children: placeholder })) }), isActive ? (_jsxs(Box, { marginTop: 0, children: [_jsx(Text, { color: theme.accent.brand, children: "› " }), _jsx(Text, { color: theme.text.muted, children: t("selectModal.fieldHint", { action: "enter" }) })] })) : null] }));
78
+ }