feed-the-machine 1.0.0 → 1.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 (136) hide show
  1. package/bin/generate-manifest.mjs +253 -0
  2. package/bin/install.mjs +134 -4
  3. package/docs/HOOKS.md +243 -0
  4. package/docs/INBOX.md +233 -0
  5. package/ftm/SKILL.md +34 -0
  6. package/ftm-audit/SKILL.md +69 -0
  7. package/ftm-brainstorm/SKILL.md +51 -0
  8. package/ftm-browse/SKILL.md +39 -0
  9. package/ftm-capture/SKILL.md +370 -0
  10. package/ftm-capture.yml +4 -0
  11. package/ftm-codex-gate/SKILL.md +59 -0
  12. package/ftm-config/SKILL.md +35 -0
  13. package/ftm-council/SKILL.md +56 -0
  14. package/ftm-dashboard/SKILL.md +163 -0
  15. package/ftm-debug/SKILL.md +84 -0
  16. package/ftm-diagram/SKILL.md +44 -0
  17. package/ftm-executor/SKILL.md +97 -0
  18. package/ftm-git/SKILL.md +60 -0
  19. package/ftm-inbox/backend/__init__.py +0 -0
  20. package/ftm-inbox/backend/__pycache__/main.cpython-314.pyc +0 -0
  21. package/ftm-inbox/backend/adapters/__init__.py +0 -0
  22. package/ftm-inbox/backend/adapters/_retry.py +64 -0
  23. package/ftm-inbox/backend/adapters/base.py +230 -0
  24. package/ftm-inbox/backend/adapters/freshservice.py +104 -0
  25. package/ftm-inbox/backend/adapters/gmail.py +125 -0
  26. package/ftm-inbox/backend/adapters/jira.py +136 -0
  27. package/ftm-inbox/backend/adapters/registry.py +192 -0
  28. package/ftm-inbox/backend/adapters/slack.py +110 -0
  29. package/ftm-inbox/backend/db/__init__.py +0 -0
  30. package/ftm-inbox/backend/db/connection.py +54 -0
  31. package/ftm-inbox/backend/db/schema.py +78 -0
  32. package/ftm-inbox/backend/executor/__init__.py +7 -0
  33. package/ftm-inbox/backend/executor/engine.py +149 -0
  34. package/ftm-inbox/backend/executor/step_runner.py +98 -0
  35. package/ftm-inbox/backend/main.py +103 -0
  36. package/ftm-inbox/backend/models/__init__.py +1 -0
  37. package/ftm-inbox/backend/models/unified_task.py +36 -0
  38. package/ftm-inbox/backend/planner/__init__.py +6 -0
  39. package/ftm-inbox/backend/planner/__pycache__/__init__.cpython-314.pyc +0 -0
  40. package/ftm-inbox/backend/planner/__pycache__/generator.cpython-314.pyc +0 -0
  41. package/ftm-inbox/backend/planner/__pycache__/schema.cpython-314.pyc +0 -0
  42. package/ftm-inbox/backend/planner/generator.py +127 -0
  43. package/ftm-inbox/backend/planner/schema.py +34 -0
  44. package/ftm-inbox/backend/requirements.txt +5 -0
  45. package/ftm-inbox/backend/routes/__init__.py +0 -0
  46. package/ftm-inbox/backend/routes/__pycache__/plan.cpython-314.pyc +0 -0
  47. package/ftm-inbox/backend/routes/execute.py +186 -0
  48. package/ftm-inbox/backend/routes/health.py +52 -0
  49. package/ftm-inbox/backend/routes/inbox.py +68 -0
  50. package/ftm-inbox/backend/routes/plan.py +271 -0
  51. package/ftm-inbox/bin/launchagent.mjs +91 -0
  52. package/ftm-inbox/bin/setup.mjs +188 -0
  53. package/ftm-inbox/bin/start.sh +10 -0
  54. package/ftm-inbox/bin/status.sh +17 -0
  55. package/ftm-inbox/bin/stop.sh +8 -0
  56. package/ftm-inbox/config.example.yml +55 -0
  57. package/ftm-inbox/package-lock.json +2898 -0
  58. package/ftm-inbox/package.json +26 -0
  59. package/ftm-inbox/postcss.config.js +6 -0
  60. package/ftm-inbox/src/app.css +199 -0
  61. package/ftm-inbox/src/app.html +18 -0
  62. package/ftm-inbox/src/lib/api.ts +166 -0
  63. package/ftm-inbox/src/lib/components/ExecutionLog.svelte +81 -0
  64. package/ftm-inbox/src/lib/components/InboxFeed.svelte +143 -0
  65. package/ftm-inbox/src/lib/components/PlanStep.svelte +271 -0
  66. package/ftm-inbox/src/lib/components/PlanView.svelte +206 -0
  67. package/ftm-inbox/src/lib/components/StreamPanel.svelte +99 -0
  68. package/ftm-inbox/src/lib/components/TaskCard.svelte +190 -0
  69. package/ftm-inbox/src/lib/components/ui/EmptyState.svelte +63 -0
  70. package/ftm-inbox/src/lib/components/ui/KawaiiCard.svelte +86 -0
  71. package/ftm-inbox/src/lib/components/ui/PillButton.svelte +106 -0
  72. package/ftm-inbox/src/lib/components/ui/StatusBadge.svelte +67 -0
  73. package/ftm-inbox/src/lib/components/ui/StreamDrawer.svelte +149 -0
  74. package/ftm-inbox/src/lib/components/ui/ThemeToggle.svelte +80 -0
  75. package/ftm-inbox/src/lib/theme.ts +47 -0
  76. package/ftm-inbox/src/routes/+layout.svelte +76 -0
  77. package/ftm-inbox/src/routes/+page.svelte +401 -0
  78. package/ftm-inbox/static/favicon.png +0 -0
  79. package/ftm-inbox/svelte.config.js +12 -0
  80. package/ftm-inbox/tailwind.config.ts +63 -0
  81. package/ftm-inbox/tsconfig.json +13 -0
  82. package/ftm-inbox/vite.config.ts +6 -0
  83. package/ftm-intent/SKILL.md +44 -0
  84. package/ftm-manifest.json +3794 -0
  85. package/ftm-map/SKILL.md +259 -0
  86. package/ftm-map/scripts/db.py +391 -0
  87. package/ftm-map/scripts/index.py +341 -0
  88. package/ftm-map/scripts/parser.py +455 -0
  89. package/ftm-map/scripts/queries/.gitkeep +0 -0
  90. package/ftm-map/scripts/queries/javascript-tags.scm +23 -0
  91. package/ftm-map/scripts/queries/python-tags.scm +17 -0
  92. package/ftm-map/scripts/queries/typescript-tags.scm +29 -0
  93. package/ftm-map/scripts/query.py +149 -0
  94. package/ftm-map/scripts/requirements.txt +2 -0
  95. package/ftm-map/scripts/setup-hooks.sh +27 -0
  96. package/ftm-map/scripts/setup.sh +45 -0
  97. package/ftm-map/scripts/test_db.py +124 -0
  98. package/ftm-map/scripts/test_parser.py +106 -0
  99. package/ftm-map/scripts/test_query.py +66 -0
  100. package/ftm-map/scripts/tests/fixtures/__init__.py +0 -0
  101. package/ftm-map/scripts/tests/fixtures/sample_project/api.ts +16 -0
  102. package/ftm-map/scripts/tests/fixtures/sample_project/auth.py +15 -0
  103. package/ftm-map/scripts/tests/fixtures/sample_project/utils.js +16 -0
  104. package/ftm-map/scripts/views.py +545 -0
  105. package/ftm-mind/SKILL.md +173 -66
  106. package/ftm-pause/SKILL.md +43 -0
  107. package/ftm-researcher/SKILL.md +275 -0
  108. package/ftm-researcher/evals/agent-diversity.yaml +17 -0
  109. package/ftm-researcher/evals/synthesis-quality.yaml +12 -0
  110. package/ftm-researcher/evals/trigger-accuracy.yaml +39 -0
  111. package/ftm-researcher/references/adaptive-search.md +116 -0
  112. package/ftm-researcher/references/agent-prompts.md +193 -0
  113. package/ftm-researcher/references/council-integration.md +193 -0
  114. package/ftm-researcher/references/output-format.md +203 -0
  115. package/ftm-researcher/references/synthesis-pipeline.md +165 -0
  116. package/ftm-researcher/scripts/score_credibility.py +234 -0
  117. package/ftm-researcher/scripts/validate_research.py +92 -0
  118. package/ftm-resume/SKILL.md +47 -0
  119. package/ftm-retro/SKILL.md +54 -0
  120. package/ftm-routine/SKILL.md +170 -0
  121. package/ftm-state/blackboard/capabilities.json +5 -0
  122. package/ftm-state/blackboard/capabilities.schema.json +27 -0
  123. package/ftm-upgrade/SKILL.md +41 -0
  124. package/ftm-upgrade/scripts/check-version.sh +1 -1
  125. package/ftm-upgrade/scripts/upgrade.sh +1 -1
  126. package/hooks/ftm-blackboard-enforcer.sh +94 -0
  127. package/hooks/ftm-discovery-reminder.sh +90 -0
  128. package/hooks/ftm-drafts-gate.sh +61 -0
  129. package/hooks/ftm-event-logger.mjs +107 -0
  130. package/hooks/ftm-map-autodetect.sh +79 -0
  131. package/hooks/ftm-pending-sync-check.sh +22 -0
  132. package/hooks/ftm-plan-gate.sh +96 -0
  133. package/hooks/ftm-post-commit-trigger.sh +57 -0
  134. package/hooks/settings-template.json +81 -0
  135. package/install.sh +140 -11
  136. package/package.json +12 -2
@@ -0,0 +1,271 @@
1
+ <script lang="ts">
2
+ import { createEventDispatcher } from 'svelte';
3
+ import PillButton from '$lib/components/ui/PillButton.svelte';
4
+ import type { PlanStep as PlanStepType } from '$lib/api';
5
+
6
+ export let step: PlanStepType;
7
+ export let index: number;
8
+
9
+ const dispatch = createEventDispatcher<{
10
+ approve: { stepId: number };
11
+ reject: { stepId: number };
12
+ }>();
13
+
14
+ const riskColor: Record<string, string> = {
15
+ low: 'risk-low',
16
+ medium: 'risk-medium',
17
+ high: 'risk-high'
18
+ };
19
+
20
+ const riskEmoji: Record<string, string> = {
21
+ low: '🟢',
22
+ medium: '🟡',
23
+ high: '🔴'
24
+ };
25
+
26
+ const statusEmoji: Record<string, string> = {
27
+ pending: '⏳',
28
+ approved: '✅',
29
+ rejected: '❌',
30
+ running: '⚡',
31
+ completed: '🎉',
32
+ failed: '💥'
33
+ };
34
+
35
+ $: isPending = step.status === 'pending';
36
+ $: isApproved = step.status === 'approved';
37
+ $: isCompleted = step.status === 'completed';
38
+ $: isFailed = step.status === 'failed';
39
+ </script>
40
+
41
+ <div
42
+ class="plan-step"
43
+ class:step-approved={isApproved}
44
+ class:step-completed={isCompleted}
45
+ class:step-failed={isFailed}
46
+ role="listitem"
47
+ >
48
+ <!-- Step number bubble -->
49
+ <div class="step-num" aria-hidden="true">{index}</div>
50
+
51
+ <!-- Main content -->
52
+ <div class="step-content">
53
+ <div class="step-top">
54
+ <span class="step-title">{step.title}</span>
55
+ <span class="step-status" title="Status: {step.status}">
56
+ {statusEmoji[step.status] ?? '⏳'}
57
+ </span>
58
+ </div>
59
+
60
+ <div class="step-meta">
61
+ {#if step.target_system}
62
+ <span class="meta-badge badge-system">{step.target_system}</span>
63
+ {/if}
64
+ <span class="meta-badge {riskColor[step.risk_level] ?? 'risk-low'}">
65
+ {riskEmoji[step.risk_level] ?? '🟢'} {step.risk_level}
66
+ </span>
67
+ {#if step.approval_required}
68
+ <span class="meta-badge badge-approval">approval required</span>
69
+ {/if}
70
+ </div>
71
+
72
+ {#if step.method_primary}
73
+ <div class="step-method">
74
+ <span class="method-label">primary:</span>
75
+ <code class="method-value">{step.method_primary}</code>
76
+ {#if step.method_fallback}
77
+ <span class="method-label">fallback:</span>
78
+ <code class="method-value">{step.method_fallback}</code>
79
+ {/if}
80
+ </div>
81
+ {/if}
82
+
83
+ {#if step.rollback}
84
+ <div class="step-rollback">
85
+ <span class="rollback-label">↩</span>
86
+ <span class="rollback-text">{step.rollback}</span>
87
+ </div>
88
+ {/if}
89
+
90
+ {#if isPending}
91
+ <div class="step-actions">
92
+ <PillButton
93
+ variant="primary"
94
+ size="sm"
95
+ on:click={() => dispatch('approve', { stepId: step.id })}
96
+ >
97
+ Approve
98
+ </PillButton>
99
+ <PillButton
100
+ variant="danger"
101
+ size="sm"
102
+ on:click={() => dispatch('reject', { stepId: step.id })}
103
+ >
104
+ Reject
105
+ </PillButton>
106
+ </div>
107
+ {/if}
108
+ </div>
109
+ </div>
110
+
111
+ <style>
112
+ .plan-step {
113
+ display: flex;
114
+ gap: 0.75rem;
115
+ padding: 0.75rem;
116
+ border-radius: 12px;
117
+ background: var(--bg-secondary, #f8f9fa);
118
+ border: 1.5px solid var(--border-card, #e0e0e0);
119
+ transition:
120
+ border-color 0.2s ease,
121
+ background 0.2s ease,
122
+ transform 0.25s cubic-bezier(0.68, -0.55, 0.265, 1.55);
123
+ }
124
+
125
+ .plan-step:hover {
126
+ transform: translateY(-1px);
127
+ }
128
+
129
+ .step-approved {
130
+ border-color: #a5d6a7;
131
+ background: rgba(165, 214, 167, 0.10);
132
+ }
133
+
134
+ .step-completed {
135
+ border-color: #81c784;
136
+ background: rgba(129, 199, 132, 0.12);
137
+ opacity: 0.85;
138
+ }
139
+
140
+ .step-failed {
141
+ border-color: #ef9a9a;
142
+ background: rgba(239, 154, 154, 0.10);
143
+ }
144
+
145
+ /* Step number bubble */
146
+ .step-num {
147
+ display: flex;
148
+ align-items: center;
149
+ justify-content: center;
150
+ width: 26px;
151
+ height: 26px;
152
+ border-radius: 9999px;
153
+ background: var(--border-card, #e0e0e0);
154
+ font-size: 0.7rem;
155
+ font-weight: 800;
156
+ color: var(--text-secondary, #555);
157
+ flex-shrink: 0;
158
+ margin-top: 2px;
159
+ }
160
+
161
+ .step-approved .step-num { background: #c8e6c9; color: #1b5e20; }
162
+ .step-completed .step-num { background: #a5d6a7; color: #1b5e20; }
163
+ .step-failed .step-num { background: #ffcdd2; color: #b71c1c; }
164
+
165
+ /* Content */
166
+ .step-content {
167
+ flex: 1;
168
+ min-width: 0;
169
+ display: flex;
170
+ flex-direction: column;
171
+ gap: 0.35rem;
172
+ }
173
+
174
+ .step-top {
175
+ display: flex;
176
+ align-items: flex-start;
177
+ justify-content: space-between;
178
+ gap: 0.5rem;
179
+ }
180
+
181
+ .step-title {
182
+ font-size: 0.875rem;
183
+ font-weight: 700;
184
+ color: var(--text-primary, #222);
185
+ line-height: 1.35;
186
+ flex: 1;
187
+ }
188
+
189
+ .step-status {
190
+ font-size: 0.9rem;
191
+ flex-shrink: 0;
192
+ }
193
+
194
+ /* Meta badges */
195
+ .step-meta {
196
+ display: flex;
197
+ flex-wrap: wrap;
198
+ gap: 0.3rem;
199
+ }
200
+
201
+ .meta-badge {
202
+ display: inline-flex;
203
+ align-items: center;
204
+ gap: 0.2rem;
205
+ padding: 2px 8px;
206
+ border-radius: 9999px;
207
+ font-size: 0.65rem;
208
+ font-weight: 700;
209
+ letter-spacing: 0.04em;
210
+ text-transform: uppercase;
211
+ }
212
+
213
+ .badge-system {
214
+ background: rgba(66, 165, 245, 0.15);
215
+ color: #1565c0;
216
+ border: 1px solid rgba(66, 165, 245, 0.3);
217
+ }
218
+
219
+ .risk-low { background: #e8f5e9; color: #2e7d32; border: 1px solid #a5d6a7; }
220
+ .risk-medium { background: #fffde7; color: #f57f17; border: 1px solid #ffe082; }
221
+ .risk-high { background: #fce4ec; color: #c62828; border: 1px solid #ef9a9a; }
222
+
223
+ .badge-approval {
224
+ background: rgba(255, 152, 0, 0.12);
225
+ color: #e65100;
226
+ border: 1px solid rgba(255, 152, 0, 0.3);
227
+ }
228
+
229
+ /* Method row */
230
+ .step-method {
231
+ display: flex;
232
+ flex-wrap: wrap;
233
+ align-items: center;
234
+ gap: 0.3rem;
235
+ font-size: 0.72rem;
236
+ }
237
+
238
+ .method-label {
239
+ color: var(--text-muted, #888);
240
+ font-weight: 600;
241
+ }
242
+
243
+ .method-value {
244
+ background: var(--bg-card, #fff);
245
+ border: 1px solid var(--border-card, #e0e0e0);
246
+ border-radius: 4px;
247
+ padding: 1px 5px;
248
+ font-family: 'Menlo', monospace;
249
+ font-size: 0.68rem;
250
+ color: var(--text-secondary, #555);
251
+ }
252
+
253
+ /* Rollback */
254
+ .step-rollback {
255
+ display: flex;
256
+ align-items: center;
257
+ gap: 0.3rem;
258
+ font-size: 0.7rem;
259
+ color: var(--text-muted, #888);
260
+ }
261
+
262
+ .rollback-label { font-size: 0.8rem; }
263
+ .rollback-text { font-style: italic; }
264
+
265
+ /* Actions */
266
+ .step-actions {
267
+ display: flex;
268
+ gap: 0.4rem;
269
+ margin-top: 0.25rem;
270
+ }
271
+ </style>
@@ -0,0 +1,206 @@
1
+ <script lang="ts">
2
+ import { createEventDispatcher } from 'svelte';
3
+ import PlanStep from '$lib/components/PlanStep.svelte';
4
+ import PillButton from '$lib/components/ui/PillButton.svelte';
5
+ import EmptyState from '$lib/components/ui/EmptyState.svelte';
6
+ import type { Plan } from '$lib/api';
7
+ import { approveStep, approveAllSteps } from '$lib/api';
8
+
9
+ export let plan: Plan | null = null;
10
+ export let loading = false;
11
+
12
+ const dispatch = createEventDispatcher<{
13
+ planUpdated: Plan;
14
+ }>();
15
+
16
+ const statusLabel: Record<string, string> = {
17
+ draft: 'Draft',
18
+ approved: 'Approved',
19
+ executing: 'Executing',
20
+ completed: 'Completed',
21
+ failed: 'Failed'
22
+ };
23
+
24
+ const statusColor: Record<string, string> = {
25
+ draft: 'status-draft',
26
+ approved: 'status-approved',
27
+ executing: 'status-executing',
28
+ completed: 'status-completed',
29
+ failed: 'status-failed'
30
+ };
31
+
32
+ $: allLowRisk = plan?.steps.every(s => s.risk_level === 'low') ?? false;
33
+ $: hasPendingSteps = plan?.steps.some(s => s.status === 'pending') ?? false;
34
+ $: approveAllEnabled = hasPendingSteps && allLowRisk && plan?.status === 'draft';
35
+
36
+ async function handleApproveStep(e: CustomEvent<{ stepId: number }>) {
37
+ if (!plan) return;
38
+ try {
39
+ const updated = await approveStep(plan.task_id, e.detail.stepId);
40
+ dispatch('planUpdated', updated);
41
+ } catch (err) {
42
+ console.error('Failed to approve step:', err);
43
+ }
44
+ }
45
+
46
+ async function handleApproveAll() {
47
+ if (!plan) return;
48
+ try {
49
+ const updated = await approveAllSteps(plan.task_id);
50
+ dispatch('planUpdated', updated);
51
+ } catch (err) {
52
+ console.error('Failed to approve all steps:', err);
53
+ }
54
+ }
55
+ </script>
56
+
57
+ <div class="plan-view" aria-label="Execution plan">
58
+ {#if loading}
59
+ <div class="plan-loading">
60
+ <span class="loading-spinner" aria-hidden="true">✨</span>
61
+ <span>Generating plan…</span>
62
+ </div>
63
+
64
+ {:else if !plan || plan.steps.length === 0}
65
+ <EmptyState
66
+ emoji="📋"
67
+ title="No plan generated yet"
68
+ message="Click 'Generate Plan' on a task card to create an execution plan."
69
+ />
70
+
71
+ {:else}
72
+ <!-- Plan header -->
73
+ <div class="plan-header">
74
+ <div class="plan-header-row">
75
+ <span class="steps-count">{plan.steps.length} step{plan.steps.length === 1 ? '' : 's'}</span>
76
+ <span class="plan-status {statusColor[plan.status] ?? 'status-draft'}">
77
+ {statusLabel[plan.status] ?? plan.status}
78
+ </span>
79
+ </div>
80
+
81
+ {#if approveAllEnabled}
82
+ <div class="plan-global-actions">
83
+ <PillButton variant="primary" size="sm" on:click={handleApproveAll}>
84
+ Approve All Low-Risk
85
+ </PillButton>
86
+ </div>
87
+ {/if}
88
+ </div>
89
+
90
+ <!-- Step list -->
91
+ <ol class="steps-list" role="list">
92
+ {#each plan.steps as step, i (step.id)}
93
+ <li class="steps-list-item">
94
+ <PlanStep
95
+ {step}
96
+ index={i + 1}
97
+ on:approve={handleApproveStep}
98
+ on:reject
99
+ />
100
+ </li>
101
+ {/each}
102
+ </ol>
103
+ {/if}
104
+ </div>
105
+
106
+ <style>
107
+ .plan-view {
108
+ display: flex;
109
+ flex-direction: column;
110
+ gap: 0.75rem;
111
+ }
112
+
113
+ /* Loading state */
114
+ .plan-loading {
115
+ display: flex;
116
+ align-items: center;
117
+ justify-content: center;
118
+ gap: 0.6rem;
119
+ padding: 2rem;
120
+ font-size: 0.875rem;
121
+ color: var(--text-muted, #888);
122
+ font-weight: 600;
123
+ }
124
+
125
+ .loading-spinner {
126
+ font-size: 1.2rem;
127
+ animation: spin 1.4s linear infinite;
128
+ }
129
+
130
+ @keyframes spin {
131
+ from { transform: rotate(0deg); }
132
+ to { transform: rotate(360deg); }
133
+ }
134
+
135
+ /* Plan header */
136
+ .plan-header {
137
+ display: flex;
138
+ flex-direction: column;
139
+ gap: 0.5rem;
140
+ }
141
+
142
+ .plan-header-row {
143
+ display: flex;
144
+ align-items: center;
145
+ justify-content: space-between;
146
+ gap: 0.5rem;
147
+ }
148
+
149
+ .steps-count {
150
+ font-size: 0.72rem;
151
+ font-weight: 800;
152
+ text-transform: uppercase;
153
+ letter-spacing: 0.06em;
154
+ color: var(--text-muted, #888);
155
+ }
156
+
157
+ .plan-status {
158
+ display: inline-flex;
159
+ align-items: center;
160
+ padding: 2px 10px;
161
+ border-radius: 9999px;
162
+ font-size: 0.65rem;
163
+ font-weight: 800;
164
+ text-transform: uppercase;
165
+ letter-spacing: 0.05em;
166
+ }
167
+
168
+ .status-draft { background: #f5f5f5; color: #616161; border: 1px solid #e0e0e0; }
169
+ .status-approved { background: #e8f5e9; color: #2e7d32; border: 1px solid #a5d6a7; }
170
+ .status-executing { background: #e3f2fd; color: #1565c0; border: 1px solid #90caf9; }
171
+ .status-completed { background: #f1f8e9; color: #33691e; border: 1px solid #aed581; }
172
+ .status-failed { background: #fce4ec; color: #c62828; border: 1px solid #ef9a9a; }
173
+
174
+ .plan-global-actions {
175
+ display: flex;
176
+ gap: 0.4rem;
177
+ }
178
+
179
+ /* Steps list */
180
+ .steps-list {
181
+ list-style: none;
182
+ padding: 0;
183
+ margin: 0;
184
+ display: flex;
185
+ flex-direction: column;
186
+ gap: 0.5rem;
187
+ }
188
+
189
+ .steps-list-item {
190
+ animation: bounceIn 0.35s cubic-bezier(0.68, -0.55, 0.265, 1.55) both;
191
+ }
192
+
193
+ .steps-list-item:nth-child(1) { animation-delay: 0.04s; }
194
+ .steps-list-item:nth-child(2) { animation-delay: 0.08s; }
195
+ .steps-list-item:nth-child(3) { animation-delay: 0.12s; }
196
+ .steps-list-item:nth-child(4) { animation-delay: 0.16s; }
197
+ .steps-list-item:nth-child(5) { animation-delay: 0.20s; }
198
+ .steps-list-item:nth-child(6) { animation-delay: 0.24s; }
199
+ .steps-list-item:nth-child(7) { animation-delay: 0.28s; }
200
+ .steps-list-item:nth-child(8) { animation-delay: 0.32s; }
201
+
202
+ @keyframes bounceIn {
203
+ from { opacity: 0; transform: translateY(10px) scale(0.97); }
204
+ to { opacity: 1; transform: translateY(0) scale(1); }
205
+ }
206
+ </style>
@@ -0,0 +1,99 @@
1
+ <script lang="ts">
2
+ import { afterUpdate } from 'svelte';
3
+
4
+ export let lines: string[] = [];
5
+ export let progress: string = '';
6
+ export let autoScroll = true;
7
+
8
+ let container: HTMLDivElement;
9
+
10
+ afterUpdate(() => {
11
+ if (autoScroll && container) {
12
+ container.scrollTop = container.scrollHeight;
13
+ }
14
+ });
15
+ </script>
16
+
17
+ <div class="stream-panel">
18
+ {#if progress}
19
+ <div class="progress-bar">{progress}</div>
20
+ {/if}
21
+ <div class="stream-output" bind:this={container}>
22
+ {#each lines as line, i (i)}
23
+ <div class="stream-line">{line}</div>
24
+ {/each}
25
+ {#if lines.length === 0}
26
+ <div class="stream-empty">Waiting for output...</div>
27
+ {/if}
28
+ </div>
29
+ <div class="stream-controls">
30
+ <label class="scroll-toggle">
31
+ <input type="checkbox" bind:checked={autoScroll} />
32
+ <span>Auto-scroll</span>
33
+ </label>
34
+ <span class="line-count">{lines.length} lines</span>
35
+ </div>
36
+ </div>
37
+
38
+ <style>
39
+ .stream-panel {
40
+ display: flex;
41
+ flex-direction: column;
42
+ height: 100%;
43
+ font-family: 'Menlo', 'Courier New', monospace;
44
+ font-size: 0.75rem;
45
+ }
46
+
47
+ .progress-bar {
48
+ padding: 0.4rem 0.75rem;
49
+ background: rgba(76, 175, 80, 0.1);
50
+ border-bottom: 1px solid var(--border-card);
51
+ font-weight: 700;
52
+ color: var(--accent-primary);
53
+ font-size: 0.7rem;
54
+ }
55
+
56
+ .stream-output {
57
+ flex: 1;
58
+ overflow-y: auto;
59
+ padding: 0.5rem 0.75rem;
60
+ }
61
+
62
+ .stream-line {
63
+ padding: 1px 0;
64
+ color: var(--text-secondary);
65
+ white-space: pre-wrap;
66
+ word-break: break-all;
67
+ line-height: 1.5;
68
+ }
69
+
70
+ .stream-empty {
71
+ color: var(--text-muted);
72
+ font-style: italic;
73
+ }
74
+
75
+ .stream-controls {
76
+ display: flex;
77
+ align-items: center;
78
+ justify-content: space-between;
79
+ padding: 0.3rem 0.75rem;
80
+ border-top: 1px solid var(--border-card);
81
+ font-size: 0.65rem;
82
+ color: var(--text-muted);
83
+ }
84
+
85
+ .scroll-toggle {
86
+ display: flex;
87
+ align-items: center;
88
+ gap: 0.3rem;
89
+ cursor: pointer;
90
+ }
91
+
92
+ .scroll-toggle input {
93
+ margin: 0;
94
+ }
95
+
96
+ .line-count {
97
+ font-variant-numeric: tabular-nums;
98
+ }
99
+ </style>