icopilot 2.2.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 (203) hide show
  1. package/CHANGELOG.md +250 -0
  2. package/LICENSE +21 -0
  3. package/README.md +214 -0
  4. package/bin/icopilot.js +6 -0
  5. package/dist/acp/router.js +123 -0
  6. package/dist/acp/schema.js +53 -0
  7. package/dist/agents/aggregator.js +187 -0
  8. package/dist/agents/custom-agents.js +97 -0
  9. package/dist/agents/goal-driven.js +411 -0
  10. package/dist/agents/multi-repo.js +350 -0
  11. package/dist/agents/parallel-runner.js +181 -0
  12. package/dist/agents/router.js +144 -0
  13. package/dist/agents/self-heal.js +481 -0
  14. package/dist/agents/tdd-agent.js +278 -0
  15. package/dist/api/github-models.js +158 -0
  16. package/dist/bridge/ide-bridge.js +479 -0
  17. package/dist/cloud/routine-executor.js +34 -0
  18. package/dist/cloud/routine-scheduler.js +67 -0
  19. package/dist/cloud/routine-storage.js +297 -0
  20. package/dist/commands/acp-cmd.js +143 -0
  21. package/dist/commands/actions-cmd.js +624 -0
  22. package/dist/commands/agent-cmd.js +144 -0
  23. package/dist/commands/alias-cmd.js +132 -0
  24. package/dist/commands/bookmark-cmd.js +77 -0
  25. package/dist/commands/changelog-cmd.js +99 -0
  26. package/dist/commands/changes-cmd.js +120 -0
  27. package/dist/commands/clipboard-cmd.js +217 -0
  28. package/dist/commands/cloud-routine-cmd.js +265 -0
  29. package/dist/commands/codegen-cmd.js +544 -0
  30. package/dist/commands/compare-cmd.js +116 -0
  31. package/dist/commands/context-cmd.js +247 -0
  32. package/dist/commands/context-viz-cmd.js +43 -0
  33. package/dist/commands/conventions-cmd.js +116 -0
  34. package/dist/commands/cost-cmd.js +51 -0
  35. package/dist/commands/deps-cmd.js +294 -0
  36. package/dist/commands/diagram-cmd.js +658 -0
  37. package/dist/commands/diff-review-cmd.js +92 -0
  38. package/dist/commands/doc-cmd.js +412 -0
  39. package/dist/commands/doctor-cmd.js +152 -0
  40. package/dist/commands/editor-cmd.js +49 -0
  41. package/dist/commands/env-cmd.js +86 -0
  42. package/dist/commands/explain-cmd.js +78 -0
  43. package/dist/commands/explain-shell-cmd.js +22 -0
  44. package/dist/commands/explore-cmd.js +231 -0
  45. package/dist/commands/feedback-cmd.js +98 -0
  46. package/dist/commands/fix-cmd.js +17 -0
  47. package/dist/commands/generate-cmd.js +38 -0
  48. package/dist/commands/git-extra.js +197 -0
  49. package/dist/commands/git-log-cmd.js +98 -0
  50. package/dist/commands/git-undo-cmd.js +137 -0
  51. package/dist/commands/git.js +155 -0
  52. package/dist/commands/history-cmd.js +122 -0
  53. package/dist/commands/index-cmd.js +65 -0
  54. package/dist/commands/init-cmd.js +73 -0
  55. package/dist/commands/lint-cmd.js +133 -0
  56. package/dist/commands/memory-cmd.js +98 -0
  57. package/dist/commands/metrics-cmd.js +97 -0
  58. package/dist/commands/mode-prefix.js +30 -0
  59. package/dist/commands/multi-cmd.js +44 -0
  60. package/dist/commands/notify-cmd.js +204 -0
  61. package/dist/commands/profile-cmd.js +101 -0
  62. package/dist/commands/prompts.js +17 -0
  63. package/dist/commands/rag-cmd.js +60 -0
  64. package/dist/commands/readme-cmd.js +564 -0
  65. package/dist/commands/reasoning-cmd.js +34 -0
  66. package/dist/commands/refactor-cmd.js +96 -0
  67. package/dist/commands/release-cmd.js +450 -0
  68. package/dist/commands/repo-cmd.js +195 -0
  69. package/dist/commands/route-cmd.js +21 -0
  70. package/dist/commands/schedule-cmd.js +109 -0
  71. package/dist/commands/search-cmd.js +47 -0
  72. package/dist/commands/security-cmd.js +156 -0
  73. package/dist/commands/settings-cmd.js +238 -0
  74. package/dist/commands/skill-cmd.js +338 -0
  75. package/dist/commands/slash.js +2721 -0
  76. package/dist/commands/snippets-cmd.js +83 -0
  77. package/dist/commands/space-cmd.js +92 -0
  78. package/dist/commands/stash-cmd.js +156 -0
  79. package/dist/commands/stats-cmd.js +36 -0
  80. package/dist/commands/style-cmd.js +85 -0
  81. package/dist/commands/suggest-cmd.js +40 -0
  82. package/dist/commands/summary-cmd.js +138 -0
  83. package/dist/commands/task-cmd.js +58 -0
  84. package/dist/commands/team-memory-cmd.js +97 -0
  85. package/dist/commands/template-cmd.js +475 -0
  86. package/dist/commands/test-cmd.js +146 -0
  87. package/dist/commands/todo-cmd.js +172 -0
  88. package/dist/commands/tokens-cmd.js +277 -0
  89. package/dist/commands/trigger-cmd.js +147 -0
  90. package/dist/commands/undo-cmd.js +18 -0
  91. package/dist/commands/voice-cmd.js +89 -0
  92. package/dist/commands/watch-cmd.js +110 -0
  93. package/dist/commands/web-cmd.js +183 -0
  94. package/dist/commands/worktree-cmd.js +119 -0
  95. package/dist/config-profile.js +66 -0
  96. package/dist/config.js +288 -0
  97. package/dist/context/compactor.js +53 -0
  98. package/dist/context/dep-context.js +329 -0
  99. package/dist/context/file-refs.js +54 -0
  100. package/dist/context/git-context.js +229 -0
  101. package/dist/context/image-input.js +66 -0
  102. package/dist/context/memory.js +55 -0
  103. package/dist/context/persistent-memory.js +104 -0
  104. package/dist/context/pinned.js +96 -0
  105. package/dist/context/priority.js +150 -0
  106. package/dist/context/read-only.js +48 -0
  107. package/dist/context/smart-files.js +286 -0
  108. package/dist/context/team-memory.js +156 -0
  109. package/dist/extensions/loader.js +149 -0
  110. package/dist/extensions/marketplace.js +49 -0
  111. package/dist/extensions/slack-provider.js +181 -0
  112. package/dist/extensions/team.js +56 -0
  113. package/dist/extensions/teams-provider.js +222 -0
  114. package/dist/extensions/voice.js +18 -0
  115. package/dist/hooks/lifecycle.js +215 -0
  116. package/dist/hooks/precommit.js +463 -0
  117. package/dist/index/embeddings.js +23 -0
  118. package/dist/index/indexer.js +86 -0
  119. package/dist/index/retrieve.js +20 -0
  120. package/dist/index/store.js +95 -0
  121. package/dist/index.js +286 -0
  122. package/dist/intelligence/dead-code.js +457 -0
  123. package/dist/intelligence/error-watch.js +263 -0
  124. package/dist/intelligence/navigation.js +141 -0
  125. package/dist/intelligence/stack-trace.js +210 -0
  126. package/dist/intelligence/symbol-index.js +410 -0
  127. package/dist/knowledge/auto-memory.js +412 -0
  128. package/dist/knowledge/conventions.js +475 -0
  129. package/dist/knowledge/corrections.js +213 -0
  130. package/dist/knowledge/rag.js +450 -0
  131. package/dist/knowledge/style-learner.js +324 -0
  132. package/dist/logger.js +35 -0
  133. package/dist/mcp/client.js +144 -0
  134. package/dist/mcp/config.js +24 -0
  135. package/dist/mcp/index.js +89 -0
  136. package/dist/modes/auto-compact.js +20 -0
  137. package/dist/modes/autopilot.js +157 -0
  138. package/dist/modes/background.js +82 -0
  139. package/dist/modes/interactive.js +187 -0
  140. package/dist/modes/oneshot.js +36 -0
  141. package/dist/modes/tui.js +265 -0
  142. package/dist/modes/turn.js +342 -0
  143. package/dist/notifications/manager.js +107 -0
  144. package/dist/plugins/marketplace.js +244 -0
  145. package/dist/providers/custom-provider.js +298 -0
  146. package/dist/providers/local-model.js +121 -0
  147. package/dist/routing/profiles.js +44 -0
  148. package/dist/routing/router.js +18 -0
  149. package/dist/sandbox/container.js +151 -0
  150. package/dist/security/audit.js +237 -0
  151. package/dist/security/content-filter.js +449 -0
  152. package/dist/security/proxy.js +301 -0
  153. package/dist/security/retention.js +281 -0
  154. package/dist/security/roles.js +252 -0
  155. package/dist/server/api-server.js +679 -0
  156. package/dist/session/bookmarks.js +72 -0
  157. package/dist/session/cloud-session.js +291 -0
  158. package/dist/session/handoff.js +405 -0
  159. package/dist/session/manager.js +35 -0
  160. package/dist/session/session.js +296 -0
  161. package/dist/session/share.js +313 -0
  162. package/dist/session/undo-journal.js +91 -0
  163. package/dist/snippets/store.js +60 -0
  164. package/dist/spaces/space-config.js +156 -0
  165. package/dist/spaces/space.js +220 -0
  166. package/dist/stats/store.js +101 -0
  167. package/dist/tools/apply-patch.js +134 -0
  168. package/dist/tools/auto-check.js +218 -0
  169. package/dist/tools/diff-edit.js +150 -0
  170. package/dist/tools/diff-prompt.js +36 -0
  171. package/dist/tools/edit-file.js +66 -0
  172. package/dist/tools/file-ops.js +205 -0
  173. package/dist/tools/glob.js +17 -0
  174. package/dist/tools/grep.js +56 -0
  175. package/dist/tools/image.js +194 -0
  176. package/dist/tools/list-directory.js +228 -0
  177. package/dist/tools/memory.js +17 -0
  178. package/dist/tools/multi-edit.js +299 -0
  179. package/dist/tools/policy.js +95 -0
  180. package/dist/tools/registry.js +484 -0
  181. package/dist/tools/retry.js +74 -0
  182. package/dist/tools/run-in-terminal.js +162 -0
  183. package/dist/tools/safety.js +64 -0
  184. package/dist/tools/sandbox.js +15 -0
  185. package/dist/tools/search-symbols.js +212 -0
  186. package/dist/tools/shell.js +118 -0
  187. package/dist/tools/web.js +167 -0
  188. package/dist/ui/prompt.js +37 -0
  189. package/dist/ui/render.js +96 -0
  190. package/dist/ui/screen.js +13 -0
  191. package/dist/ui/theme.js +56 -0
  192. package/dist/util/browser.js +34 -0
  193. package/dist/util/completion.js +350 -0
  194. package/dist/util/cost.js +28 -0
  195. package/dist/util/keybindings.js +113 -0
  196. package/dist/util/lazy.js +26 -0
  197. package/dist/util/perf.js +25 -0
  198. package/dist/util/token-worker.js +11 -0
  199. package/dist/util/tokens.js +50 -0
  200. package/dist/workflows/builtins.js +128 -0
  201. package/dist/workflows/engine.js +496 -0
  202. package/dist/workflows/file-trigger.js +197 -0
  203. package/package.json +79 -0
@@ -0,0 +1,217 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { execFile, spawn, spawnSync } from 'node:child_process';
4
+ import { randomUUID } from 'node:crypto';
5
+ import { config } from '../config.js';
6
+ const CLIPBOARD_DIR = '.icopilot-clipboard';
7
+ const IMAGE_MARKER = '__ICLI_IMAGE__';
8
+ const MAX_BUFFER = 20 * 1024 * 1024;
9
+ export async function readClipboard() {
10
+ switch (process.platform) {
11
+ case 'win32':
12
+ return readClipboardWindows();
13
+ case 'darwin':
14
+ return readClipboardMacOS();
15
+ default:
16
+ return readClipboardLinux();
17
+ }
18
+ }
19
+ export async function pasteToChat() {
20
+ const clipboard = await readClipboard();
21
+ return clipboard.type === 'image' ? `"${clipboard.content}"` : clipboard.content;
22
+ }
23
+ export async function copyContextToClipboard(messages) {
24
+ await copyTextToClipboard(formatMessagesAsMarkdown(messages));
25
+ }
26
+ export async function copyTextToClipboard(text) {
27
+ if (process.platform === 'win32') {
28
+ try {
29
+ await writeClipboardText('clip.exe', [], text);
30
+ return;
31
+ }
32
+ catch {
33
+ await writeClipboardText('powershell.exe', ['-NoProfile', '-Command', 'Set-Clipboard -Value ([Console]::In.ReadToEnd())'], text);
34
+ return;
35
+ }
36
+ }
37
+ if (process.platform === 'darwin') {
38
+ await writeClipboardText('pbcopy', [], text);
39
+ return;
40
+ }
41
+ if (commandExists('xclip')) {
42
+ await writeClipboardText('xclip', ['-selection', 'clipboard'], text);
43
+ return;
44
+ }
45
+ if (commandExists('xsel')) {
46
+ await writeClipboardText('xsel', ['--clipboard', '--input'], text);
47
+ return;
48
+ }
49
+ throw new Error('No clipboard utility found (expected xclip or xsel).');
50
+ }
51
+ export function formatMessagesAsMarkdown(messages) {
52
+ const lines = [];
53
+ for (const message of messages) {
54
+ const role = capitalize(typeof message.role === 'string' ? message.role : 'message');
55
+ lines.push(`## ${role}`, '', contentToText(message.content).trim() || '_[no content]_', '', '---', '');
56
+ }
57
+ return lines.join('\n').trimEnd() + '\n';
58
+ }
59
+ async function readClipboardWindows() {
60
+ const imagePath = createClipboardArtifactPath('.png');
61
+ const script = [
62
+ 'Add-Type -AssemblyName System.Windows.Forms',
63
+ 'Add-Type -AssemblyName System.Drawing',
64
+ '$target = $args[0]',
65
+ 'if ([System.Windows.Forms.Clipboard]::ContainsImage()) {',
66
+ ' $image = [System.Windows.Forms.Clipboard]::GetImage()',
67
+ ' if ($null -ne $image) {',
68
+ ' $image.Save($target, [System.Drawing.Imaging.ImageFormat]::Png)',
69
+ ` Write-Output "${IMAGE_MARKER}$target"`,
70
+ ' exit 0',
71
+ ' }',
72
+ '}',
73
+ 'if ([System.Windows.Forms.Clipboard]::ContainsText()) {',
74
+ ' [Console]::OutputEncoding = [System.Text.Encoding]::UTF8',
75
+ ' Get-Clipboard -Raw',
76
+ ' exit 0',
77
+ '}',
78
+ 'exit 2',
79
+ ].join('; ');
80
+ const { stdout } = await runExecFile('powershell.exe', ['-NoProfile', '-STA', '-Command', script, imagePath], { encoding: 'utf8' });
81
+ return parseClipboardOutput(String(stdout));
82
+ }
83
+ async function readClipboardMacOS() {
84
+ if (commandExists('pngpaste')) {
85
+ const imagePath = createClipboardArtifactPath('.png');
86
+ try {
87
+ await runExecFile('pngpaste', [imagePath], { encoding: 'buffer' });
88
+ if (fs.existsSync(imagePath) && fs.statSync(imagePath).size > 0) {
89
+ return { type: 'image', content: imagePath };
90
+ }
91
+ }
92
+ catch {
93
+ cleanupFile(imagePath);
94
+ }
95
+ }
96
+ const { stdout } = await runExecFile('pbpaste', [], { encoding: 'utf8' });
97
+ return { type: 'text', content: trimTrailingNewline(String(stdout)) };
98
+ }
99
+ async function readClipboardLinux() {
100
+ if (commandExists('xclip')) {
101
+ const imagePath = createClipboardArtifactPath('.png');
102
+ try {
103
+ const { stdout } = await runExecFile('xclip', ['-selection', 'clipboard', '-t', 'image/png', '-o'], { encoding: 'buffer' });
104
+ const buffer = asBuffer(stdout);
105
+ if (buffer.length > 0) {
106
+ fs.writeFileSync(imagePath, buffer);
107
+ return { type: 'image', content: imagePath };
108
+ }
109
+ }
110
+ catch {
111
+ cleanupFile(imagePath);
112
+ }
113
+ }
114
+ if (commandExists('xclip')) {
115
+ const { stdout } = await runExecFile('xclip', ['-selection', 'clipboard', '-o'], {
116
+ encoding: 'utf8',
117
+ });
118
+ return { type: 'text', content: trimTrailingNewline(String(stdout)) };
119
+ }
120
+ if (commandExists('xsel')) {
121
+ const { stdout } = await runExecFile('xsel', ['--clipboard', '--output'], { encoding: 'utf8' });
122
+ return { type: 'text', content: trimTrailingNewline(String(stdout)) };
123
+ }
124
+ throw new Error('No clipboard utility found (expected xclip or xsel).');
125
+ }
126
+ function parseClipboardOutput(stdout) {
127
+ const trimmed = trimTrailingNewline(stdout);
128
+ if (trimmed.startsWith(IMAGE_MARKER)) {
129
+ return { type: 'image', content: trimmed.slice(IMAGE_MARKER.length).trim() };
130
+ }
131
+ return { type: 'text', content: trimmed };
132
+ }
133
+ function createClipboardArtifactPath(extension) {
134
+ const dir = path.join(config.cwd || process.cwd(), CLIPBOARD_DIR);
135
+ fs.mkdirSync(dir, { recursive: true });
136
+ return path.join(dir, `clipboard-${Date.now()}-${randomUUID()}${extension}`);
137
+ }
138
+ function trimTrailingNewline(value) {
139
+ return value.replace(/\r?\n$/, '');
140
+ }
141
+ function cleanupFile(filePath) {
142
+ try {
143
+ fs.rmSync(filePath, { force: true });
144
+ }
145
+ catch {
146
+ /* ignore cleanup errors */
147
+ }
148
+ }
149
+ function commandExists(command) {
150
+ const checker = process.platform === 'win32' ? 'where.exe' : 'which';
151
+ return spawnSync(checker, [command], { stdio: 'ignore' }).status === 0;
152
+ }
153
+ function writeClipboardText(command, args, text) {
154
+ return new Promise((resolve, reject) => {
155
+ const child = spawn(command, args, {
156
+ stdio: ['pipe', 'ignore', 'pipe'],
157
+ windowsHide: true,
158
+ });
159
+ let stderr = '';
160
+ child.stderr.on('data', (chunk) => {
161
+ stderr += chunk.toString();
162
+ });
163
+ child.on('error', reject);
164
+ child.on('close', (code) => {
165
+ if (code === 0) {
166
+ resolve();
167
+ }
168
+ else {
169
+ reject(new Error(stderr.trim() || `clipboard command exited with code ${code}`));
170
+ }
171
+ });
172
+ child.stdin.end(text);
173
+ });
174
+ }
175
+ function runExecFile(command, args, options = {}) {
176
+ return new Promise((resolve, reject) => {
177
+ execFile(command, args, {
178
+ windowsHide: true,
179
+ maxBuffer: MAX_BUFFER,
180
+ ...options,
181
+ }, (error, stdout, stderr) => {
182
+ if (error) {
183
+ reject(error);
184
+ return;
185
+ }
186
+ resolve({
187
+ stdout: stdout,
188
+ stderr: stderr,
189
+ });
190
+ });
191
+ });
192
+ }
193
+ function asBuffer(value) {
194
+ return Buffer.isBuffer(value) ? value : Buffer.from(value);
195
+ }
196
+ function contentToText(content) {
197
+ if (typeof content === 'string')
198
+ return content;
199
+ if (Array.isArray(content)) {
200
+ return content
201
+ .map((part) => {
202
+ if (typeof part?.text === 'string')
203
+ return part.text;
204
+ if (typeof part?.type === 'string')
205
+ return JSON.stringify(part);
206
+ return '';
207
+ })
208
+ .filter(Boolean)
209
+ .join('\n');
210
+ }
211
+ if (content == null)
212
+ return '';
213
+ return JSON.stringify(content, null, 2);
214
+ }
215
+ function capitalize(value) {
216
+ return value ? value[0].toUpperCase() + value.slice(1) : value;
217
+ }
@@ -0,0 +1,265 @@
1
+ import { getCloudRoutineStore } from '../cloud/routine-storage.js';
2
+ import { theme } from '../ui/theme.js';
3
+ export async function cloudRoutineCommand(arg) {
4
+ const store = getCloudRoutineStore();
5
+ const parts = arg.trim().split(/\s+/);
6
+ const subcommand = parts[0]?.toLowerCase();
7
+ switch (subcommand) {
8
+ case 'create':
9
+ return handleCreate(parts.slice(1), store);
10
+ case 'list':
11
+ return handleList(parts.slice(1), store);
12
+ case 'show':
13
+ return handleShow(parts.slice(1), store);
14
+ case 'update':
15
+ return handleUpdate(parts.slice(1), store);
16
+ case 'delete':
17
+ return handleDelete(parts.slice(1), store);
18
+ case 'run':
19
+ return handleRun(parts.slice(1), store);
20
+ case 'logs':
21
+ return handleLogs(parts.slice(1), store);
22
+ default:
23
+ return theme.err('Unknown cloud-routine subcommand. Use: create, list, show, update, delete, run, logs');
24
+ }
25
+ }
26
+ function handleCreate(args, store) {
27
+ if (args.length < 3) {
28
+ return theme.err('Usage: /cloud-routine create <name> <schedule> <prompt>\n' +
29
+ 'Examples:\n' +
30
+ ' /cloud-routine create "daily-standup" "daily 09:00" "generate standup"\n' +
31
+ ' /cloud-routine create "weekly-review" "weekly 1 18:00" "code review checklist"');
32
+ }
33
+ const name = args[0].replace(/^["']|["']$/g, '');
34
+ const scheduleStr = args[1].replace(/^["']|["']$/g, '');
35
+ const prompt = args
36
+ .slice(2)
37
+ .join(' ')
38
+ .replace(/^["']|["']$/g, '');
39
+ try {
40
+ const schedule = parseSchedule(scheduleStr);
41
+ const routine = store.create(name, schedule, prompt);
42
+ return theme.ok(`✔ Routine "${routine.name}" created (ID: ${routine.id})\n` +
43
+ ` Next run: ${formatDate(routine.nextRun)}`);
44
+ }
45
+ catch (err) {
46
+ return theme.err(`✘ Failed to create routine: ${err instanceof Error ? err.message : String(err)}`);
47
+ }
48
+ }
49
+ function handleList(args, store) {
50
+ const detail = args.includes('--detail');
51
+ const routines = store.list();
52
+ if (routines.length === 0) {
53
+ return theme.dim('No cloud routines configured.');
54
+ }
55
+ let output = `${theme.brand('Cloud Routines')}\n`;
56
+ if (detail) {
57
+ for (const routine of routines) {
58
+ output += `\n${theme.bold(routine.name)} (${routine.id})\n`;
59
+ output += ` Enabled: ${routine.enabled ? '✓' : '✗'}\n`;
60
+ output += ` Schedule: ${formatSchedule(routine.schedule)}\n`;
61
+ output += ` Next Run: ${formatDate(routine.nextRun)}\n`;
62
+ if (routine.lastRun) {
63
+ output += ` Last Run: ${formatDate(routine.lastRun)}\n`;
64
+ }
65
+ output += ` Prompt: ${routine.prompt.substring(0, 60)}${routine.prompt.length > 60 ? '...' : ''}\n`;
66
+ }
67
+ }
68
+ else {
69
+ output += `${theme.dim('Name'.padEnd(30) + 'Status'.padEnd(10) + 'Next Run')}\n`;
70
+ for (const routine of routines) {
71
+ const status = routine.enabled ? '✓ active' : '✗ disabled';
72
+ output += `${routine.name.padEnd(30)}${status.padEnd(10)}${formatDate(routine.nextRun)}\n`;
73
+ }
74
+ }
75
+ return output;
76
+ }
77
+ function handleShow(args, store) {
78
+ const id = args[0];
79
+ if (!id) {
80
+ return theme.err('Usage: /cloud-routine show <id>');
81
+ }
82
+ const routine = store.get(id);
83
+ if (!routine) {
84
+ return theme.err(`Routine with ID "${id}" not found.`);
85
+ }
86
+ let output = `${theme.bold(routine.name)}\n`;
87
+ output += ` ID: ${routine.id}\n`;
88
+ output += ` Enabled: ${routine.enabled ? '✓' : '✗'}\n`;
89
+ output += ` Schedule: ${formatSchedule(routine.schedule)}\n`;
90
+ output += ` Next Run: ${formatDate(routine.nextRun)}\n`;
91
+ output += ` Created: ${formatDate(routine.createdAt)}\n`;
92
+ if (routine.lastRun) {
93
+ output += ` Last Run: ${formatDate(routine.lastRun)}\n`;
94
+ }
95
+ output += `\n${theme.dim('Prompt:')}\n${routine.prompt}\n`;
96
+ const logs = store.getLogs(routine.id, 5);
97
+ if (logs.length > 0) {
98
+ output += `\n${theme.dim('Recent Executions:')}\n`;
99
+ for (const log of logs) {
100
+ const status = log.status === 'success' ? '✓' : '✘';
101
+ output += ` ${status} ${formatDate(log.timestamp)} (${log.duration}ms)\n`;
102
+ }
103
+ }
104
+ return output;
105
+ }
106
+ function handleUpdate(args, store) {
107
+ const id = args[0];
108
+ if (!id) {
109
+ return theme.err('Usage: /cloud-routine update <id> [--schedule <sched>] [--prompt <prompt>] [--enabled <true|false>]');
110
+ }
111
+ const routine = store.get(id);
112
+ if (!routine) {
113
+ return theme.err(`Routine with ID "${id}" not found.`);
114
+ }
115
+ const updates = {};
116
+ for (let i = 1; i < args.length; i += 2) {
117
+ const flag = args[i];
118
+ const value = args[i + 1];
119
+ if (!flag || !value)
120
+ continue;
121
+ if (flag === '--schedule') {
122
+ try {
123
+ updates.schedule = parseSchedule(value);
124
+ }
125
+ catch (err) {
126
+ return theme.err(`Invalid schedule: ${err instanceof Error ? err.message : String(err)}`);
127
+ }
128
+ }
129
+ else if (flag === '--prompt') {
130
+ updates.prompt = value.replace(/^["']|["']$/g, '');
131
+ }
132
+ else if (flag === '--enabled') {
133
+ updates.enabled = value.toLowerCase() === 'true';
134
+ }
135
+ }
136
+ if (Object.keys(updates).length === 0) {
137
+ return theme.err('No updates specified.');
138
+ }
139
+ try {
140
+ const updated = store.update(id, updates);
141
+ return theme.ok(`✔ Routine "${updated.name}" updated.`);
142
+ }
143
+ catch (err) {
144
+ return theme.err(`✘ Failed to update routine: ${err instanceof Error ? err.message : String(err)}`);
145
+ }
146
+ }
147
+ function handleDelete(args, store) {
148
+ const id = args[0];
149
+ if (!id) {
150
+ return theme.err('Usage: /cloud-routine delete <id>');
151
+ }
152
+ const routine = store.get(id);
153
+ if (!routine) {
154
+ return theme.err(`Routine with ID "${id}" not found.`);
155
+ }
156
+ if (store.delete(id)) {
157
+ return theme.ok(`✔ Routine "${routine.name}" deleted.`);
158
+ }
159
+ return theme.err('Failed to delete routine.');
160
+ }
161
+ function handleRun(args, store) {
162
+ const id = args[0];
163
+ if (!id) {
164
+ return theme.err('Usage: /cloud-routine run <id>');
165
+ }
166
+ const routine = store.get(id);
167
+ if (!routine) {
168
+ return theme.err(`Routine with ID "${id}" not found.`);
169
+ }
170
+ return theme.ok(`✔ Routine "${routine.name}" queued for immediate execution. (manual trigger)`);
171
+ }
172
+ function handleLogs(args, store) {
173
+ const id = args[0];
174
+ if (!id) {
175
+ return theme.err('Usage: /cloud-routine logs <id> [--last N]');
176
+ }
177
+ const routine = store.get(id);
178
+ if (!routine) {
179
+ return theme.err(`Routine with ID "${id}" not found.`);
180
+ }
181
+ let limit = 20;
182
+ const lastIdx = args.indexOf('--last');
183
+ if (lastIdx !== -1 && args[lastIdx + 1]) {
184
+ limit = parseInt(args[lastIdx + 1], 10) || 20;
185
+ }
186
+ const logs = store.getLogs(id, limit);
187
+ if (logs.length === 0) {
188
+ return theme.dim(`No execution logs for "${routine.name}".`);
189
+ }
190
+ let output = `${theme.bold(`Execution Logs: ${routine.name}`)}\n`;
191
+ for (const log of logs) {
192
+ const status = log.status === 'success' ? theme.ok('✓') : theme.err('✘');
193
+ output += `${status} ${formatDate(log.timestamp)} (${log.duration}ms)`;
194
+ if (log.error) {
195
+ output += ` - ${log.error}`;
196
+ }
197
+ output += '\n';
198
+ }
199
+ return output;
200
+ }
201
+ function parseSchedule(scheduleStr) {
202
+ const parts = scheduleStr.trim().toLowerCase().split(/\s+/);
203
+ const type = parts[0];
204
+ switch (type) {
205
+ case 'once': {
206
+ return { type: 'once' };
207
+ }
208
+ case 'daily': {
209
+ const time = parts[1] || '09:00';
210
+ return { type: 'daily', time };
211
+ }
212
+ case 'weekly': {
213
+ const dayOfWeek = parseInt(parts[1], 10);
214
+ const time = parts[2] || '09:00';
215
+ if (isNaN(dayOfWeek) || dayOfWeek < 0 || dayOfWeek > 6) {
216
+ throw new Error('day of week must be 0-6 (0=Sunday, 6=Saturday)');
217
+ }
218
+ return { type: 'weekly', dayOfWeek, time };
219
+ }
220
+ case 'monthly': {
221
+ const dayOfMonth = parseInt(parts[1], 10);
222
+ const time = parts[2] || '09:00';
223
+ if (isNaN(dayOfMonth) || dayOfMonth < 1 || dayOfMonth > 31) {
224
+ throw new Error('day of month must be 1-31');
225
+ }
226
+ return { type: 'monthly', dayOfMonth, time };
227
+ }
228
+ case 'custom': {
229
+ const expression = parts.slice(1).join(' ');
230
+ if (!expression)
231
+ throw new Error('custom schedule requires an expression');
232
+ return { type: 'custom', expression };
233
+ }
234
+ default:
235
+ throw new Error(`unknown schedule type: ${type}`);
236
+ }
237
+ }
238
+ function formatSchedule(schedule) {
239
+ switch (schedule.type) {
240
+ case 'once':
241
+ return 'once';
242
+ case 'daily':
243
+ return `daily at ${schedule.time || '09:00'}`;
244
+ case 'weekly': {
245
+ const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
246
+ return `weekly on ${days[schedule.dayOfWeek || 0]} at ${schedule.time || '09:00'}`;
247
+ }
248
+ case 'monthly':
249
+ return `monthly on day ${schedule.dayOfMonth || 1} at ${schedule.time || '09:00'}`;
250
+ case 'custom':
251
+ return `custom: ${schedule.expression || ''}`;
252
+ default:
253
+ return 'unknown';
254
+ }
255
+ }
256
+ function formatDate(date) {
257
+ if (!date)
258
+ return 'never';
259
+ try {
260
+ return new Date(date).toLocaleString();
261
+ }
262
+ catch {
263
+ return date;
264
+ }
265
+ }