loop-task 1.1.0 → 1.4.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 (130) hide show
  1. package/README.md +250 -113
  2. package/dist/board/App.js +340 -0
  3. package/dist/board/board-log.js +18 -0
  4. package/dist/board/components/ActionButtons.js +45 -0
  5. package/dist/board/components/BoardButton.js +58 -0
  6. package/dist/board/components/ConfirmModal.js +36 -0
  7. package/dist/board/components/CreateForm.js +309 -0
  8. package/dist/board/components/CreateProjectModal.js +104 -0
  9. package/dist/board/components/DeleteProjectConfirm.js +53 -0
  10. package/dist/board/components/DetailView.js +25 -0
  11. package/dist/board/components/EditProjectModal.js +106 -0
  12. package/dist/board/components/FilterBar.js +16 -0
  13. package/dist/board/components/Footer.js +52 -0
  14. package/dist/board/components/Header.js +46 -0
  15. package/dist/board/components/HelpModal.js +36 -0
  16. package/dist/board/components/Inspector.js +12 -0
  17. package/dist/board/components/LogModal.js +58 -0
  18. package/dist/board/components/Navigator.js +84 -0
  19. package/dist/board/components/ProjectsModal.js +70 -0
  20. package/dist/board/components/ProjectsPage.js +105 -0
  21. package/dist/board/components/RunHistory.js +83 -0
  22. package/dist/board/components/TaskBrowser.js +95 -0
  23. package/dist/board/components/TaskFilterBar.js +6 -0
  24. package/dist/board/components/TaskForm.js +177 -0
  25. package/dist/board/components/Timeline.js +5 -0
  26. package/dist/board/daemon.js +117 -0
  27. package/dist/board/format.js +102 -0
  28. package/dist/board/hooks/useBoardKeybindings.js +299 -0
  29. package/dist/board/hooks/useBreakpoint.js +6 -0
  30. package/dist/board/hooks/useHoverState.js +11 -0
  31. package/dist/board/hooks/useLogStream.js +32 -0
  32. package/dist/board/hooks/useLoopPolling.js +28 -0
  33. package/dist/board/hooks/useTaskKeybindings.js +114 -0
  34. package/dist/board/index.js +32 -0
  35. package/dist/board/router.js +16 -0
  36. package/dist/board/state.js +99 -0
  37. package/dist/board/toast.js +65 -0
  38. package/dist/board/types.js +1 -0
  39. package/dist/cli.js +134 -93
  40. package/dist/cli.js.map +1 -1
  41. package/dist/client/commands.d.ts +11 -0
  42. package/dist/client/commands.d.ts.map +1 -0
  43. package/dist/client/commands.js +317 -0
  44. package/dist/client/commands.js.map +1 -0
  45. package/dist/client/ipc.d.ts +5 -0
  46. package/dist/client/ipc.d.ts.map +1 -0
  47. package/dist/client/ipc.js +96 -0
  48. package/dist/client/ipc.js.map +1 -0
  49. package/dist/config/constants.js +29 -0
  50. package/dist/config/paths.js +49 -0
  51. package/dist/core/command-runner.js +82 -0
  52. package/dist/core/foreground-loop.js +81 -0
  53. package/dist/core/log-parser.js +29 -0
  54. package/dist/core/log-rotator.js +25 -0
  55. package/dist/core/loop-controller.js +429 -0
  56. package/dist/daemon/daemon-log.js +11 -0
  57. package/dist/daemon/index.d.ts +2 -0
  58. package/dist/daemon/index.d.ts.map +1 -0
  59. package/dist/daemon/index.js +43 -0
  60. package/dist/daemon/index.js.map +1 -0
  61. package/dist/daemon/manager.d.ts +17 -0
  62. package/dist/daemon/manager.d.ts.map +1 -0
  63. package/dist/daemon/manager.js +260 -0
  64. package/dist/daemon/manager.js.map +1 -0
  65. package/dist/daemon/projects.js +104 -0
  66. package/dist/daemon/server.d.ts +15 -0
  67. package/dist/daemon/server.d.ts.map +1 -0
  68. package/dist/daemon/server.js +345 -0
  69. package/dist/daemon/server.js.map +1 -0
  70. package/dist/daemon/spawner.d.ts +3 -0
  71. package/dist/daemon/spawner.d.ts.map +1 -0
  72. package/dist/daemon/spawner.js +82 -0
  73. package/dist/daemon/spawner.js.map +1 -0
  74. package/dist/daemon/state.d.ts +15 -0
  75. package/dist/daemon/state.d.ts.map +1 -0
  76. package/dist/daemon/state.js +151 -0
  77. package/dist/daemon/state.js.map +1 -0
  78. package/dist/daemon/task-manager.js +59 -0
  79. package/dist/duration.js +4 -4
  80. package/dist/entry.js +11 -0
  81. package/dist/esm-loader.js +36 -0
  82. package/dist/i18n/en.json +386 -0
  83. package/dist/i18n/index.js +11 -0
  84. package/dist/ipc/handlers/logs-stream.js +65 -0
  85. package/dist/ipc/send.js +5 -0
  86. package/dist/logger.js +0 -1
  87. package/dist/loop-config.d.ts +13 -0
  88. package/dist/loop-config.d.ts.map +1 -0
  89. package/dist/loop-config.js +83 -0
  90. package/dist/loop-config.js.map +1 -0
  91. package/dist/loop.d.ts +43 -2
  92. package/dist/loop.d.ts.map +1 -1
  93. package/dist/loop.js +247 -2
  94. package/dist/loop.js.map +1 -1
  95. package/dist/open-board.d.ts +2 -0
  96. package/dist/open-board.d.ts.map +1 -0
  97. package/dist/open-board.js +19 -0
  98. package/dist/open-board.js.map +1 -0
  99. package/dist/shared/clipboard.js +19 -0
  100. package/dist/shared/fs-utils.js +11 -0
  101. package/dist/shared/sleep.js +17 -0
  102. package/dist/shared/tail.js +4 -0
  103. package/dist/tui/app.d.ts +57 -0
  104. package/dist/tui/app.d.ts.map +1 -0
  105. package/dist/tui/app.js +167 -0
  106. package/dist/tui/app.js.map +1 -0
  107. package/dist/tui/create-loop.d.ts +3 -0
  108. package/dist/tui/create-loop.d.ts.map +1 -0
  109. package/dist/tui/create-loop.js +90 -0
  110. package/dist/tui/create-loop.js.map +1 -0
  111. package/dist/tui/dashboard.d.ts +2 -0
  112. package/dist/tui/dashboard.d.ts.map +1 -0
  113. package/dist/tui/dashboard.js +45 -0
  114. package/dist/tui/dashboard.js.map +1 -0
  115. package/dist/tui/events.d.ts +3 -0
  116. package/dist/tui/events.d.ts.map +1 -0
  117. package/dist/tui/events.js +244 -0
  118. package/dist/tui/events.js.map +1 -0
  119. package/dist/tui/popup.d.ts +3 -0
  120. package/dist/tui/popup.d.ts.map +1 -0
  121. package/dist/tui/popup.js +58 -0
  122. package/dist/tui/popup.js.map +1 -0
  123. package/dist/tui/render.d.ts +9 -0
  124. package/dist/tui/render.d.ts.map +1 -0
  125. package/dist/tui/render.js +305 -0
  126. package/dist/tui/render.js.map +1 -0
  127. package/dist/types.d.ts +74 -0
  128. package/dist/types.d.ts.map +1 -1
  129. package/dist/types.js +0 -1
  130. package/package.json +64 -55
@@ -0,0 +1,260 @@
1
+ import crypto from "node:crypto";
2
+ import { LoopController } from "../core/loop-controller.js";
3
+ import { ProjectManager } from "./projects.js";
4
+ import { saveLoop, loadAllLoops, deleteLoop as deleteLoopState, getLogPath, } from "./state.js";
5
+ import { daemonLog } from "./daemon-log.js";
6
+ export class LoopManager {
7
+ loops = new Map();
8
+ lastSerialized = new Map();
9
+ taskManager;
10
+ projectManager;
11
+ constructor(taskManager, projectManager) {
12
+ this.taskManager = taskManager;
13
+ this.projectManager = projectManager || new ProjectManager();
14
+ }
15
+ taskResolver = (taskId) => this.taskManager.get(taskId);
16
+ init() {
17
+ // Initialize projects first
18
+ this.projectManager.init();
19
+ const saved = loadAllLoops();
20
+ let restarted = 0;
21
+ let migrated = 0;
22
+ const shouldAutoStart = (s) => s !== "stopped" && s !== "idle";
23
+ for (const meta of saved) {
24
+ if (!meta.projectId) {
25
+ meta.projectId = "default";
26
+ saveLoop(meta);
27
+ }
28
+ let taskId = meta.taskId;
29
+ if (!taskId && meta.command) {
30
+ const task = this.taskManager.createInline(meta.command, meta.commandArgs, meta.cwd ?? "");
31
+ taskId = task.id;
32
+ meta.taskId = taskId;
33
+ saveLoop(meta);
34
+ migrated += 1;
35
+ }
36
+ const options = {
37
+ interval: meta.interval,
38
+ taskId: taskId ?? null,
39
+ command: meta.command,
40
+ commandArgs: meta.commandArgs,
41
+ cwd: meta.cwd ?? "",
42
+ immediate: false,
43
+ maxRuns: meta.maxRuns,
44
+ verbose: meta.verbose,
45
+ description: meta.description ?? "",
46
+ projectId: meta.projectId ?? "default",
47
+ };
48
+ const logPath = getLogPath(meta.id);
49
+ const controller = new LoopController(meta.id, options, logPath, this.taskResolver, {
50
+ status: meta.status,
51
+ createdAt: meta.createdAt,
52
+ runCount: meta.runCount,
53
+ sessionStartedAt: meta.sessionStartedAt,
54
+ lastRunAt: meta.lastRunAt,
55
+ lastExitCode: meta.lastExitCode,
56
+ lastDuration: meta.lastDuration,
57
+ nextRunAt: meta.nextRunAt,
58
+ remainingDelayMs: meta.remainingDelayMs,
59
+ runHistory: meta.runHistory,
60
+ });
61
+ this.loops.set(meta.id, {
62
+ controller,
63
+ options,
64
+ intervalHuman: meta.intervalHuman,
65
+ });
66
+ this.wireEvents(meta.id, controller, options, meta.intervalHuman);
67
+ if (shouldAutoStart(meta.status)) {
68
+ controller.start();
69
+ restarted += 1;
70
+ }
71
+ }
72
+ if (migrated > 0) {
73
+ daemonLog(`migrated ${migrated} loop(s) to task model`);
74
+ }
75
+ if (restarted > 0) {
76
+ daemonLog(`restarted ${restarted} loop(s) from persisted state`);
77
+ }
78
+ }
79
+ start(options, intervalHuman) {
80
+ const id = crypto.randomUUID().slice(0, 8);
81
+ const logPath = getLogPath(id);
82
+ const controller = new LoopController(id, options, logPath, this.taskResolver);
83
+ this.loops.set(id, { controller, options, intervalHuman });
84
+ controller.start();
85
+ this.wireEvents(id, controller, options, intervalHuman);
86
+ this.persist(id, controller, options, intervalHuman);
87
+ return id;
88
+ }
89
+ async update(id, options, intervalHuman) {
90
+ const entry = this.loops.get(id);
91
+ if (!entry)
92
+ return false;
93
+ const executionChanged = entry.options.interval !== options.interval ||
94
+ entry.options.taskId !== options.taskId ||
95
+ entry.options.immediate !== options.immediate ||
96
+ entry.options.maxRuns !== options.maxRuns ||
97
+ entry.options.verbose !== options.verbose;
98
+ const maxRunsChanged = entry.options.maxRuns !== options.maxRuns;
99
+ Object.assign(entry.options, options);
100
+ entry.intervalHuman = intervalHuman;
101
+ if (maxRunsChanged) {
102
+ entry.controller.clearMaxRunsReached();
103
+ }
104
+ if (entry.controller.status === "running") {
105
+ entry.controller.pause(true);
106
+ }
107
+ else if (executionChanged && entry.controller.status !== "paused" && entry.controller.status !== "idle") {
108
+ entry.controller.pause();
109
+ }
110
+ this.persist(id, entry.controller, entry.options, entry.intervalHuman);
111
+ return true;
112
+ }
113
+ list() {
114
+ const result = [];
115
+ for (const [id, entry] of this.loops) {
116
+ result.push(this.buildMeta(id, entry));
117
+ }
118
+ return result;
119
+ }
120
+ listProjects() {
121
+ return this.projectManager.getAll();
122
+ }
123
+ createProject(name, color) {
124
+ return this.projectManager.create(name, color);
125
+ }
126
+ updateProject(id, name, color) {
127
+ this.projectManager.update(id, name, color);
128
+ }
129
+ deleteProject(id) {
130
+ for (const [loopId, entry] of this.loops) {
131
+ if (entry.options.projectId === id) {
132
+ entry.options.projectId = "default";
133
+ this.persist(loopId, entry.controller, entry.options, entry.intervalHuman);
134
+ }
135
+ }
136
+ this.projectManager.delete(id);
137
+ }
138
+ status(id) {
139
+ const entry = this.loops.get(id);
140
+ if (!entry)
141
+ return null;
142
+ return this.buildMeta(id, entry);
143
+ }
144
+ pause(id) {
145
+ const entry = this.loops.get(id);
146
+ if (!entry)
147
+ return false;
148
+ entry.controller.pause();
149
+ this.persist(id, entry.controller, entry.options, entry.intervalHuman);
150
+ return true;
151
+ }
152
+ resume(id) {
153
+ const entry = this.loops.get(id);
154
+ if (!entry)
155
+ return false;
156
+ entry.controller.resume();
157
+ this.persist(id, entry.controller, entry.options, entry.intervalHuman);
158
+ return true;
159
+ }
160
+ stopLoop(id) {
161
+ const entry = this.loops.get(id);
162
+ if (!entry)
163
+ return false;
164
+ entry.controller.stopLoop();
165
+ this.persist(id, entry.controller, entry.options, entry.intervalHuman);
166
+ return true;
167
+ }
168
+ playLoop(id) {
169
+ const entry = this.loops.get(id);
170
+ if (!entry)
171
+ return false;
172
+ const ok = entry.controller.playLoop();
173
+ if (ok) {
174
+ this.persist(id, entry.controller, entry.options, entry.intervalHuman);
175
+ }
176
+ return ok;
177
+ }
178
+ trigger(id) {
179
+ const entry = this.loops.get(id);
180
+ if (!entry)
181
+ return false;
182
+ const ok = entry.controller.triggerNow();
183
+ if (ok) {
184
+ this.persist(id, entry.controller, entry.options, entry.intervalHuman);
185
+ }
186
+ return ok;
187
+ }
188
+ isMaxRunsBlocked(id) {
189
+ const entry = this.loops.get(id);
190
+ return !!entry?.controller.isMaxRunsReached();
191
+ }
192
+ isRunning(id) {
193
+ const entry = this.loops.get(id);
194
+ return entry?.controller.status === "running";
195
+ }
196
+ async delete(id) {
197
+ const entry = this.loops.get(id);
198
+ if (!entry)
199
+ return false;
200
+ await entry.controller.stop();
201
+ this.loops.delete(id);
202
+ this.lastSerialized.delete(id);
203
+ deleteLoopState(id);
204
+ return true;
205
+ }
206
+ getLogPath(id) {
207
+ if (!this.loops.has(id))
208
+ return null;
209
+ return getLogPath(id);
210
+ }
211
+ async shutdown() {
212
+ const stops = [];
213
+ for (const [, entry] of this.loops) {
214
+ stops.push(entry.controller.stop());
215
+ }
216
+ await Promise.all(stops);
217
+ this.loops.clear();
218
+ }
219
+ wireEvents(id, controller, options, intervalHuman) {
220
+ const persist = () => this.persist(id, controller, options, intervalHuman);
221
+ controller.on("run:start", persist);
222
+ controller.on("run:end", persist);
223
+ controller.on("paused", persist);
224
+ controller.on("resumed", persist);
225
+ controller.on("triggered", persist);
226
+ controller.on("waiting", persist);
227
+ controller.on("stopped", persist);
228
+ }
229
+ persist(id, controller, options, intervalHuman) {
230
+ const meta = this.toMeta(controller, options, intervalHuman);
231
+ const serialized = JSON.stringify(meta);
232
+ if (this.lastSerialized.get(id) === serialized) {
233
+ return;
234
+ }
235
+ this.lastSerialized.set(id, serialized);
236
+ saveLoop(meta);
237
+ }
238
+ buildMeta(id, entry) {
239
+ return this.toMeta(entry.controller, entry.options, entry.intervalHuman);
240
+ }
241
+ toMeta(controller, options, intervalHuman) {
242
+ const runtime = controller.getMeta();
243
+ const task = options.taskId ? this.taskManager.get(options.taskId) : null;
244
+ return {
245
+ ...runtime,
246
+ command: task?.command ?? options.command,
247
+ commandArgs: task?.commandArgs ?? options.commandArgs,
248
+ cwd: task?.cwd ?? options.cwd,
249
+ interval: options.interval,
250
+ intervalHuman,
251
+ immediate: options.immediate,
252
+ maxRuns: options.maxRuns,
253
+ verbose: options.verbose,
254
+ description: options.description,
255
+ remainingDelayMs: runtime.remainingDelayMs,
256
+ pid: process.pid,
257
+ projectId: options.projectId ?? "default",
258
+ };
259
+ }
260
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/daemon/manager.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C,OAAO,EACL,QAAQ,EACR,YAAY,EACZ,UAAU,IAAI,eAAe,EAC7B,UAAU,GACX,MAAM,YAAY,CAAC;AAQpB,MAAM,OAAO,WAAW;IACd,KAAK,GAAG,IAAI,GAAG,EAAsB,CAAC;IAE9C,IAAI;QACF,MAAM,KAAK,GAAG,YAAY,EAAE,CAAC;QAC7B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS;gBAAE,SAAS;YACxC,MAAM,OAAO,GAAgB;gBAC3B,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,WAAW,EAAE,IAAI,CAAC,WAAW;gBAC7B,SAAS,EAAE,KAAK;gBAChB,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,OAAO,EAAE,IAAI,CAAC,OAAO;aACtB,CAAC;YACF,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACpC,MAAM,UAAU,GAAG,IAAI,cAAc,CAAC,IAAI,CAAC,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE;gBAC/D,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,YAAY,EAAE,IAAI,CAAC,YAAY;gBAC/B,YAAY,EAAE,IAAI,CAAC,YAAY;gBAC/B,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;aACxC,CAAC,CAAC;YACH,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE;gBACtB,UAAU;gBACV,OAAO;gBACP,aAAa,EAAE,IAAI,CAAC,aAAa;aAClC,CAAC,CAAC;YACH,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC;YAClE,UAAU,CAAC,KAAK,EAAE,CAAC;QACrB,CAAC;IACH,CAAC;IAED,KAAK,CAAC,OAAoB,EAAE,aAAqB;QAC/C,MAAM,EAAE,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC3C,MAAM,OAAO,GAAG,UAAU,CAAC,EAAE,CAAC,CAAC;QAC/B,MAAM,UAAU,GAAG,IAAI,cAAc,CAAC,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;QAC5D,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC,CAAC;QAC3D,UAAU,CAAC,KAAK,EAAE,CAAC;QACnB,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;QACxD,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;QACrD,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,IAAI;QACF,MAAM,MAAM,GAAe,EAAE,CAAC;QAC9B,KAAK,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACrC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC;QACzC,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,CAAC,EAAU;QACf,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACjC,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACxB,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IACnC,CAAC;IAED,KAAK,CAAC,EAAU;QACd,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACjC,IAAI,CAAC,KAAK;YAAE,OAAO,KAAK,CAAC;QACzB,KAAK,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;QACvE,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,CAAC,EAAU;QACf,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACjC,IAAI,CAAC,KAAK;YAAE,OAAO,KAAK,CAAC;QACzB,KAAK,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC;QAC1B,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;QACvE,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,EAAU;QACrB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACjC,IAAI,CAAC,KAAK;YAAE,OAAO,KAAK,CAAC;QACzB,MAAM,KAAK,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;QAC9B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACtB,eAAe,CAAC,EAAE,CAAC,CAAC;QACpB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,UAAU,CAAC,EAAU;QACnB,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;YAAE,OAAO,IAAI,CAAC;QACrC,OAAO,UAAU,CAAC,EAAE,CAAC,CAAC;IACxB,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,MAAM,KAAK,GAAoB,EAAE,CAAC;QAClC,KAAK,MAAM,CAAC,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACnC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC;QACtC,CAAC;QACD,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACzB,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;IAEO,UAAU,CAChB,EAAU,EACV,UAA0B,EAC1B,OAAoB,EACpB,aAAqB;QAErB,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;QAC3E,UAAU,CAAC,EAAE,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QACpC,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QAClC,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACjC,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QAClC,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QACnC,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IACpC,CAAC;IAEO,OAAO,CACb,EAAU,EACV,UAA0B,EAC1B,OAAoB,EACpB,aAAqB;QAErB,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,EAAE,CAAC;QACrC,MAAM,IAAI,GAAa;YACrB,GAAG,OAAO;YACV,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,WAAW,EAAE,OAAO,CAAC,WAAW;YAChC,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,aAAa;YACb,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,gBAAgB,EAAE,OAAO,CAAC,gBAAgB;YAC1C,GAAG,EAAE,OAAO,CAAC,GAAG;SACjB,CAAC;QACF,QAAQ,CAAC,IAAI,CAAC,CAAC;IACjB,CAAC;IAEO,SAAS,CAAC,EAAU,EAAE,KAAiB;QAC7C,MAAM,OAAO,GAAG,KAAK,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;QAC3C,OAAO;YACL,GAAG,OAAO;YACV,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,OAAO;YAC9B,WAAW,EAAE,KAAK,CAAC,OAAO,CAAC,WAAW;YACtC,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,QAAQ;YAChC,aAAa,EAAE,KAAK,CAAC,aAAa;YAClC,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,SAAS;YAClC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,OAAO;YAC9B,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,OAAO;YAC9B,gBAAgB,EAAE,OAAO,CAAC,gBAAgB;YAC1C,GAAG,EAAE,OAAO,CAAC,GAAG;SACjB,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,104 @@
1
+ import fs from "node:fs";
2
+ import { writeFileAtomic } from "../shared/fs-utils.js";
3
+ import { homedir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { daemonLog } from "./daemon-log.js";
6
+ import crypto from "node:crypto";
7
+ export class ProjectManager {
8
+ projects = new Map();
9
+ projectsDir;
10
+ constructor(loopCliHome) {
11
+ const baseDir = loopCliHome || join(homedir(), ".loop-cli");
12
+ this.projectsDir = join(baseDir, "projects");
13
+ }
14
+ init() {
15
+ if (!fs.existsSync(this.projectsDir)) {
16
+ fs.mkdirSync(this.projectsDir, { recursive: true });
17
+ }
18
+ this.loadProjects();
19
+ if (!this.projects.has("default")) {
20
+ this.createDefaultProject();
21
+ }
22
+ }
23
+ loadProjects() {
24
+ this.projects.clear();
25
+ if (!fs.existsSync(this.projectsDir))
26
+ return;
27
+ try {
28
+ const files = fs.readdirSync(this.projectsDir);
29
+ for (const file of files) {
30
+ if (!file.endsWith(".json"))
31
+ continue;
32
+ try {
33
+ const raw = fs.readFileSync(join(this.projectsDir, file), "utf-8");
34
+ const content = JSON.parse(raw);
35
+ if (content.id)
36
+ this.projects.set(content.id, content);
37
+ }
38
+ catch (err) {
39
+ daemonLog(`Failed to load project ${file}: ${err}`);
40
+ }
41
+ }
42
+ }
43
+ catch (err) {
44
+ daemonLog(`Failed to load projects directory: ${err}`);
45
+ }
46
+ }
47
+ saveProject(project) {
48
+ const filePath = join(this.projectsDir, `${project.id}.json`);
49
+ writeFileAtomic(filePath, JSON.stringify(project, null, 2));
50
+ this.projects.set(project.id, project);
51
+ }
52
+ createDefaultProject() {
53
+ const defaultProject = {
54
+ id: "default",
55
+ name: "Default",
56
+ color: "#ffffff",
57
+ createdAt: new Date().toISOString(),
58
+ isSystem: true,
59
+ isDefault: true,
60
+ };
61
+ this.saveProject(defaultProject);
62
+ }
63
+ getAll() {
64
+ return Array.from(this.projects.values());
65
+ }
66
+ get(id) {
67
+ return this.projects.get(id);
68
+ }
69
+ create(name, color) {
70
+ const id = crypto.randomBytes(4).toString("hex");
71
+ const project = {
72
+ id,
73
+ name,
74
+ color,
75
+ createdAt: new Date().toISOString(),
76
+ isSystem: false,
77
+ isDefault: false,
78
+ };
79
+ this.saveProject(project);
80
+ return project;
81
+ }
82
+ update(id, name, color) {
83
+ const project = this.projects.get(id);
84
+ if (!project)
85
+ throw new Error(`Project ${id} not found`);
86
+ if (project.isSystem)
87
+ throw new Error("Cannot rename system project");
88
+ project.name = name;
89
+ if (color)
90
+ project.color = color;
91
+ this.saveProject(project);
92
+ }
93
+ delete(id) {
94
+ const project = this.projects.get(id);
95
+ if (!project)
96
+ throw new Error(`Project ${id} not found`);
97
+ if (project.isSystem)
98
+ throw new Error("Cannot delete system project");
99
+ const filePath = join(this.projectsDir, `${id}.json`);
100
+ if (fs.existsSync(filePath))
101
+ fs.rmSync(filePath);
102
+ this.projects.delete(id);
103
+ }
104
+ }
@@ -0,0 +1,15 @@
1
+ import { LoopManager } from "./manager.js";
2
+ export declare class IpcServer {
3
+ private server;
4
+ private manager;
5
+ private socketPath;
6
+ private clients;
7
+ constructor(manager: LoopManager);
8
+ listen(): Promise<void>;
9
+ close(): Promise<void>;
10
+ private handleConnection;
11
+ private handleRequest;
12
+ private streamLogFollow;
13
+ private send;
14
+ }
15
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/daemon/server.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAG3C,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,OAAO,CAAyB;gBAE5B,OAAO,EAAE,WAAW;IAM1B,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAWvB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAa5B,OAAO,CAAC,gBAAgB;YA8BV,aAAa;IAmG3B,OAAO,CAAC,eAAe;IAyDvB,OAAO,CAAC,IAAI;CAKb"}