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,190 @@
1
+ <script lang="ts">
2
+ import { createEventDispatcher } from 'svelte';
3
+ import { fly } from 'svelte/transition';
4
+ import StatusBadge from './ui/StatusBadge.svelte';
5
+ import PillButton from './ui/PillButton.svelte';
6
+ import type { UnifiedTask } from '$lib/api';
7
+
8
+ export let task: UnifiedTask;
9
+ export let selected = false;
10
+
11
+ const dispatch = createEventDispatcher<{
12
+ select: UnifiedTask;
13
+ generatePlan: UnifiedTask;
14
+ }>();
15
+
16
+ const sourceColors: Record<string, string> = {
17
+ jira: '#bbdefb',
18
+ freshservice: '#c8e6c9',
19
+ slack: '#e1bee7',
20
+ gmail: '#ffcdd2'
21
+ };
22
+
23
+ const sourceTextColors: Record<string, string> = {
24
+ jira: '#0d47a1',
25
+ freshservice: '#1b5e20',
26
+ slack: '#4a148c',
27
+ gmail: '#b71c1c'
28
+ };
29
+
30
+ function relativeTime(dateStr: string | null): string {
31
+ if (!dateStr) return '';
32
+ const now = Date.now();
33
+ const then = new Date(dateStr).getTime();
34
+ if (isNaN(then)) return '';
35
+ const diff = Math.floor((now - then) / 1000);
36
+ if (diff < 60) return 'just now';
37
+ if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
38
+ if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`;
39
+ return `${Math.floor(diff / 86400)}d ago`;
40
+ }
41
+
42
+ function mapStatus(s: string): 'pending' | 'planning' | 'approved' | 'executing' | 'complete' | 'failed' {
43
+ const map: Record<string, 'pending' | 'planning' | 'approved' | 'executing' | 'complete' | 'failed'> = {
44
+ open: 'pending',
45
+ pending: 'planning',
46
+ resolved: 'complete',
47
+ closed: 'complete',
48
+ };
49
+ return map[s] ?? 'pending';
50
+ }
51
+
52
+ const priorityIndicator: Record<string, string> = {
53
+ low: '○',
54
+ medium: '◑',
55
+ high: '●',
56
+ urgent: '◉'
57
+ };
58
+ </script>
59
+
60
+ <button
61
+ class="task-card"
62
+ class:selected
63
+ on:click={() => dispatch('select', task)}
64
+ transition:fly={{ x: -20, duration: 200 }}
65
+ >
66
+ <div class="card-top">
67
+ <span
68
+ class="source-badge"
69
+ style="background: {sourceColors[task.source] ?? '#e0e0e0'}; color: {sourceTextColors[task.source] ?? '#333'}"
70
+ >
71
+ {task.source}
72
+ </span>
73
+ <span class="card-time">{relativeTime(task.ingested_at ?? task.created_at)}</span>
74
+ </div>
75
+
76
+ <p class="card-title">{task.title}</p>
77
+
78
+ <div class="card-meta">
79
+ {#if task.priority}
80
+ <span class="priority" title={task.priority}>
81
+ {priorityIndicator[task.priority] ?? '○'} {task.priority}
82
+ </span>
83
+ {/if}
84
+ {#if task.assignee}
85
+ <span class="assignee" title="Assignee: {task.assignee}">
86
+ {task.assignee}
87
+ </span>
88
+ {/if}
89
+ </div>
90
+
91
+ <div class="card-bottom">
92
+ <StatusBadge status={mapStatus(task.status)} />
93
+ <PillButton
94
+ variant="primary"
95
+ size="sm"
96
+ on:click={(e) => { e.stopPropagation(); dispatch('generatePlan', task); }}
97
+ >
98
+
99
+ Generate Plan
100
+ </PillButton>
101
+ </div>
102
+ </button>
103
+
104
+ <style>
105
+ .task-card {
106
+ display: block;
107
+ width: 100%;
108
+ text-align: left;
109
+ background: var(--bg-card);
110
+ border: 2px solid var(--border-card);
111
+ border-radius: 12px;
112
+ padding: 0.65rem 0.75rem;
113
+ cursor: pointer;
114
+ transition:
115
+ border-color 0.15s ease,
116
+ box-shadow 0.15s ease,
117
+ transform 0.15s cubic-bezier(0.68, -0.55, 0.265, 1.55);
118
+ font-family: 'Nunito', sans-serif;
119
+ }
120
+
121
+ .task-card:hover {
122
+ border-color: var(--accent-primary);
123
+ transform: translateX(2px);
124
+ }
125
+
126
+ .task-card.selected {
127
+ border-color: var(--accent-primary);
128
+ box-shadow: var(--shadow-card-hover);
129
+ background: var(--bg-secondary);
130
+ }
131
+
132
+ .card-top {
133
+ display: flex;
134
+ align-items: center;
135
+ justify-content: space-between;
136
+ margin-bottom: 0.3rem;
137
+ }
138
+
139
+ .source-badge {
140
+ font-size: 0.65rem;
141
+ font-weight: 800;
142
+ text-transform: uppercase;
143
+ letter-spacing: 0.06em;
144
+ padding: 2px 8px;
145
+ border-radius: 9999px;
146
+ }
147
+
148
+ .card-time {
149
+ font-size: 0.68rem;
150
+ color: var(--text-muted);
151
+ }
152
+
153
+ .card-title {
154
+ font-size: 0.8rem;
155
+ font-weight: 700;
156
+ color: var(--text-primary);
157
+ margin: 0 0 0.3rem;
158
+ line-height: 1.35;
159
+ display: -webkit-box;
160
+ -webkit-line-clamp: 2;
161
+ -webkit-box-orient: vertical;
162
+ overflow: hidden;
163
+ }
164
+
165
+ .card-meta {
166
+ display: flex;
167
+ align-items: center;
168
+ gap: 0.5rem;
169
+ margin-bottom: 0.4rem;
170
+ font-size: 0.68rem;
171
+ color: var(--text-muted);
172
+ }
173
+
174
+ .priority {
175
+ font-weight: 700;
176
+ }
177
+
178
+ .assignee {
179
+ max-width: 120px;
180
+ overflow: hidden;
181
+ text-overflow: ellipsis;
182
+ white-space: nowrap;
183
+ }
184
+
185
+ .card-bottom {
186
+ display: flex;
187
+ align-items: center;
188
+ justify-content: space-between;
189
+ }
190
+ </style>
@@ -0,0 +1,63 @@
1
+ <script lang="ts">
2
+ export let emoji = '🌱';
3
+ export let title = 'Nothing here yet';
4
+ export let message = 'Items will appear here when they arrive.';
5
+ </script>
6
+
7
+ <div class="empty-state">
8
+ <div class="empty-emoji" aria-hidden="true">{emoji}</div>
9
+ <p class="empty-title">{title}</p>
10
+ <p class="empty-message">{message}</p>
11
+ {#if $$slots.action}
12
+ <div class="empty-action">
13
+ <slot name="action" />
14
+ </div>
15
+ {/if}
16
+ </div>
17
+
18
+ <style>
19
+ .empty-state {
20
+ display: flex;
21
+ flex-direction: column;
22
+ align-items: center;
23
+ justify-content: center;
24
+ gap: 0.5rem;
25
+ padding: 2.5rem 1rem;
26
+ text-align: center;
27
+ color: var(--text-muted);
28
+ min-height: 120px;
29
+ }
30
+
31
+ .empty-emoji {
32
+ font-size: 2.5rem;
33
+ line-height: 1;
34
+ margin-bottom: 0.25rem;
35
+ filter: saturate(0.7) opacity(0.8);
36
+ animation: gentle-float 3s ease-in-out infinite;
37
+ }
38
+
39
+ .empty-title {
40
+ font-family: 'Nunito', sans-serif;
41
+ font-weight: 700;
42
+ font-size: 0.95rem;
43
+ color: var(--text-secondary);
44
+ margin: 0;
45
+ }
46
+
47
+ .empty-message {
48
+ font-size: 0.8rem;
49
+ color: var(--text-muted);
50
+ margin: 0;
51
+ max-width: 200px;
52
+ line-height: 1.5;
53
+ }
54
+
55
+ .empty-action {
56
+ margin-top: 0.75rem;
57
+ }
58
+
59
+ @keyframes gentle-float {
60
+ 0%, 100% { transform: translateY(0px); }
61
+ 50% { transform: translateY(-5px); }
62
+ }
63
+ </style>
@@ -0,0 +1,86 @@
1
+ <script lang="ts">
2
+ export let accent: 'green' | 'yellow' | 'blue' | 'coral' | 'teal' | 'orange' = 'green';
3
+ export let hoverable = false;
4
+ export let compact = false;
5
+
6
+ const accentColors: Record<typeof accent, string> = {
7
+ green: '#4caf50',
8
+ yellow: '#ffd54f',
9
+ blue: '#42a5f5',
10
+ coral: '#ff7043',
11
+ teal: '#26a69a',
12
+ orange: '#ff9800'
13
+ };
14
+
15
+ $: borderColor = accentColors[accent];
16
+ </script>
17
+
18
+ <div
19
+ class="kawaii-card"
20
+ class:hoverable
21
+ class:compact
22
+ style="--card-accent: {borderColor}"
23
+ >
24
+ {#if $$slots.header}
25
+ <div class="card-header">
26
+ <slot name="header" />
27
+ </div>
28
+ {/if}
29
+
30
+ <div class="card-body">
31
+ <slot />
32
+ </div>
33
+
34
+ {#if $$slots.footer}
35
+ <div class="card-footer">
36
+ <slot name="footer" />
37
+ </div>
38
+ {/if}
39
+ </div>
40
+
41
+ <style>
42
+ .kawaii-card {
43
+ background: var(--bg-card);
44
+ border: 2px solid var(--border-card);
45
+ border-left: 4px solid var(--card-accent);
46
+ border-radius: 16px;
47
+ box-shadow: var(--shadow-card);
48
+ transition:
49
+ box-shadow 0.2s ease,
50
+ transform 0.2s cubic-bezier(0.68, -0.55, 0.265, 1.55),
51
+ border-color 0.2s ease;
52
+ overflow: hidden;
53
+ }
54
+
55
+ .kawaii-card.hoverable:hover {
56
+ box-shadow: var(--shadow-card-hover);
57
+ transform: translateY(-2px);
58
+ border-color: var(--card-accent);
59
+ }
60
+
61
+ .kawaii-card.hoverable:active {
62
+ transform: translateY(0);
63
+ }
64
+
65
+ .card-header {
66
+ padding: 12px 16px 8px;
67
+ border-bottom: 1px solid var(--border-card);
68
+ }
69
+
70
+ .card-body {
71
+ padding: 16px;
72
+ }
73
+
74
+ .compact .card-body {
75
+ padding: 10px 14px;
76
+ }
77
+
78
+ .compact .card-header {
79
+ padding: 8px 14px 6px;
80
+ }
81
+
82
+ .card-footer {
83
+ padding: 8px 16px 12px;
84
+ border-top: 1px solid var(--border-card);
85
+ }
86
+ </style>
@@ -0,0 +1,106 @@
1
+ <script lang="ts">
2
+ export let variant: 'primary' | 'ghost' | 'danger' = 'primary';
3
+ export let size: 'sm' | 'md' | 'lg' = 'md';
4
+ export let disabled = false;
5
+ export let type: 'button' | 'submit' | 'reset' = 'button';
6
+
7
+ const sizeClasses = {
8
+ sm: 'btn-sm',
9
+ md: 'btn-md',
10
+ lg: 'btn-lg'
11
+ };
12
+ </script>
13
+
14
+ <button
15
+ {type}
16
+ {disabled}
17
+ class="pill-btn pill-btn-{variant} {sizeClasses[size]}"
18
+ on:click
19
+ on:mouseenter
20
+ on:mouseleave
21
+ >
22
+ {#if $$slots.icon}
23
+ <span class="btn-icon" aria-hidden="true">
24
+ <slot name="icon" />
25
+ </span>
26
+ {/if}
27
+ <slot />
28
+ </button>
29
+
30
+ <style>
31
+ .pill-btn {
32
+ display: inline-flex;
33
+ align-items: center;
34
+ gap: 0.4rem;
35
+ border-radius: 9999px;
36
+ font-family: 'Nunito', sans-serif;
37
+ font-weight: 700;
38
+ cursor: pointer;
39
+ border: none;
40
+ transition:
41
+ transform 0.18s cubic-bezier(0.68, -0.55, 0.265, 1.55),
42
+ box-shadow 0.18s ease,
43
+ background-color 0.18s ease,
44
+ color 0.18s ease,
45
+ border-color 0.18s ease;
46
+ user-select: none;
47
+ white-space: nowrap;
48
+ line-height: 1;
49
+ }
50
+
51
+ .pill-btn:disabled {
52
+ opacity: 0.45;
53
+ cursor: not-allowed;
54
+ transform: none !important;
55
+ }
56
+
57
+ .pill-btn:not(:disabled):hover {
58
+ transform: scale(1.06) translateY(-1px);
59
+ }
60
+
61
+ .pill-btn:not(:disabled):active {
62
+ transform: scale(0.96) translateY(0);
63
+ }
64
+
65
+ /* Sizes */
66
+ .btn-sm { padding: 0.35rem 0.9rem; font-size: 0.75rem; }
67
+ .btn-md { padding: 0.5rem 1.25rem; font-size: 0.875rem; }
68
+ .btn-lg { padding: 0.65rem 1.6rem; font-size: 1rem; }
69
+
70
+ /* Variants */
71
+ .pill-btn-primary {
72
+ background-color: var(--accent-primary);
73
+ color: #fff;
74
+ box-shadow: 0 2px 8px rgba(76, 175, 80, 0.3);
75
+ }
76
+ .pill-btn-primary:not(:disabled):hover {
77
+ background-color: var(--accent-hover);
78
+ box-shadow: 0 4px 16px rgba(76, 175, 80, 0.4);
79
+ }
80
+
81
+ .pill-btn-ghost {
82
+ background-color: transparent;
83
+ color: var(--text-secondary);
84
+ border: 2px solid var(--border-card);
85
+ }
86
+ .pill-btn-ghost:not(:disabled):hover {
87
+ border-color: var(--accent-primary);
88
+ color: var(--accent-primary);
89
+ background-color: rgba(76, 175, 80, 0.06);
90
+ }
91
+
92
+ .pill-btn-danger {
93
+ background-color: #ef5350;
94
+ color: #fff;
95
+ box-shadow: 0 2px 8px rgba(239, 83, 80, 0.25);
96
+ }
97
+ .pill-btn-danger:not(:disabled):hover {
98
+ background-color: #c62828;
99
+ box-shadow: 0 4px 16px rgba(239, 83, 80, 0.35);
100
+ }
101
+
102
+ .btn-icon {
103
+ display: flex;
104
+ align-items: center;
105
+ }
106
+ </style>
@@ -0,0 +1,67 @@
1
+ <script context="module" lang="ts">
2
+ export type Status =
3
+ | 'pending'
4
+ | 'planning'
5
+ | 'approved'
6
+ | 'executing'
7
+ | 'complete'
8
+ | 'failed';
9
+ </script>
10
+
11
+ <script lang="ts">
12
+ export let status: Status = 'pending';
13
+ export let label: string | undefined = undefined;
14
+
15
+ const labels: Record<Status, string> = {
16
+ pending: 'Pending',
17
+ planning: 'Planning',
18
+ approved: 'Approved',
19
+ executing: 'Executing',
20
+ complete: 'Complete',
21
+ failed: 'Failed'
22
+ };
23
+
24
+ const dots: Record<Status, string> = {
25
+ pending: '○',
26
+ planning: '◑',
27
+ approved: '●',
28
+ executing: '◉',
29
+ complete: '✓',
30
+ failed: '✕'
31
+ };
32
+
33
+ $: displayLabel = label ?? labels[status];
34
+ </script>
35
+
36
+ <span class="badge badge-{status}" role="status">
37
+ <span class="dot" aria-hidden="true">{dots[status]}</span>
38
+ {displayLabel}
39
+ </span>
40
+
41
+ <style>
42
+ .badge {
43
+ display: inline-flex;
44
+ align-items: center;
45
+ gap: 0.3rem;
46
+ padding: 4px 12px;
47
+ border-radius: 12px;
48
+ font-family: 'Nunito', sans-serif;
49
+ font-weight: 700;
50
+ font-size: 0.75rem;
51
+ letter-spacing: 0.02em;
52
+ white-space: nowrap;
53
+ user-select: none;
54
+ }
55
+
56
+ .dot {
57
+ font-size: 0.7rem;
58
+ line-height: 1;
59
+ }
60
+
61
+ .badge-pending { background: #fff9c4; color: #5d4037; }
62
+ .badge-planning { background: #bbdefb; color: #0d47a1; }
63
+ .badge-approved { background: #c8e6c9; color: #1b5e20; }
64
+ .badge-executing { background: #ffe0b2; color: #bf360c; }
65
+ .badge-complete { background: #b2dfdb; color: #004d40; }
66
+ .badge-failed { background: #ffcdd2; color: #b71c1c; }
67
+ </style>
@@ -0,0 +1,149 @@
1
+ <script lang="ts">
2
+ export let open = false;
3
+ export let height = 200;
4
+ export let title = 'Agent Output';
5
+
6
+ export let lines: string[] = [];
7
+
8
+ function toggle() {
9
+ open = !open;
10
+ }
11
+
12
+ let logEl: HTMLDivElement;
13
+
14
+ $: if (logEl && lines) {
15
+ // Auto-scroll to bottom when new lines arrive
16
+ setTimeout(() => {
17
+ if (logEl) logEl.scrollTop = logEl.scrollHeight;
18
+ }, 0);
19
+ }
20
+ </script>
21
+
22
+ <div class="stream-drawer" class:open>
23
+ <button class="drawer-toggle" on:click={toggle} aria-expanded={open}>
24
+ <span class="toggle-icon" aria-hidden="true">{open ? '▼' : '▲'}</span>
25
+ <span class="toggle-title">{title}</span>
26
+ {#if lines.length > 0}
27
+ <span class="line-count">{lines.length} lines</span>
28
+ {/if}
29
+ </button>
30
+
31
+ {#if open}
32
+ <div class="drawer-body" style="height: {height}px" bind:this={logEl}>
33
+ {#if lines.length === 0}
34
+ <p class="drawer-empty">Waiting for agent output...</p>
35
+ {:else}
36
+ {#each lines as line, i (i)}
37
+ <div class="log-line" class:log-error={line.startsWith('ERROR')} class:log-success={line.startsWith('OK') || line.startsWith('✓')}>
38
+ <span class="log-index">{String(i + 1).padStart(3, '0')}</span>
39
+ <span class="log-text">{line}</span>
40
+ </div>
41
+ {/each}
42
+ {/if}
43
+ </div>
44
+ {/if}
45
+ </div>
46
+
47
+ <style>
48
+ .stream-drawer {
49
+ position: fixed;
50
+ bottom: 0;
51
+ left: 0;
52
+ right: 0;
53
+ background: var(--bg-drawer);
54
+ border-top: 2px solid var(--border-accent);
55
+ z-index: 100;
56
+ transition: box-shadow 0.2s ease;
57
+ }
58
+
59
+ .stream-drawer.open {
60
+ box-shadow: 0 -4px 24px rgba(76, 175, 80, 0.15);
61
+ }
62
+
63
+ .drawer-toggle {
64
+ display: flex;
65
+ align-items: center;
66
+ gap: 0.5rem;
67
+ width: 100%;
68
+ padding: 0.5rem 1rem;
69
+ background: none;
70
+ border: none;
71
+ cursor: pointer;
72
+ font-family: 'Nunito', sans-serif;
73
+ font-weight: 700;
74
+ font-size: 0.8rem;
75
+ color: var(--text-secondary);
76
+ transition: color 0.15s ease, background 0.15s ease;
77
+ }
78
+
79
+ .drawer-toggle:hover {
80
+ color: var(--accent-primary);
81
+ background: rgba(76, 175, 80, 0.04);
82
+ }
83
+
84
+ .toggle-icon {
85
+ font-size: 0.65rem;
86
+ }
87
+
88
+ .toggle-title {
89
+ flex: 1;
90
+ text-align: left;
91
+ letter-spacing: 0.05em;
92
+ text-transform: uppercase;
93
+ font-size: 0.7rem;
94
+ }
95
+
96
+ .line-count {
97
+ font-size: 0.7rem;
98
+ color: var(--text-muted);
99
+ background: var(--border-card);
100
+ padding: 2px 8px;
101
+ border-radius: 9999px;
102
+ }
103
+
104
+ .drawer-body {
105
+ overflow-y: auto;
106
+ font-family: 'Menlo', 'Monaco', 'Consolas', monospace;
107
+ font-size: 0.75rem;
108
+ line-height: 1.6;
109
+ padding: 0.5rem 0;
110
+ scrollbar-width: thin;
111
+ }
112
+
113
+ .drawer-empty {
114
+ padding: 0.75rem 1rem;
115
+ color: var(--text-muted);
116
+ font-family: 'Nunito', sans-serif;
117
+ font-style: italic;
118
+ font-size: 0.8rem;
119
+ margin: 0;
120
+ }
121
+
122
+ .log-line {
123
+ display: flex;
124
+ gap: 0.75rem;
125
+ padding: 0.1rem 1rem;
126
+ color: var(--text-secondary);
127
+ transition: background 0.1s;
128
+ }
129
+
130
+ .log-line:hover {
131
+ background: rgba(76, 175, 80, 0.04);
132
+ }
133
+
134
+ .log-index {
135
+ color: var(--text-muted);
136
+ user-select: none;
137
+ min-width: 2rem;
138
+ text-align: right;
139
+ }
140
+
141
+ .log-text {
142
+ white-space: pre-wrap;
143
+ word-break: break-word;
144
+ flex: 1;
145
+ }
146
+
147
+ .log-error .log-text { color: #ef5350; }
148
+ .log-success .log-text { color: var(--accent-primary); }
149
+ </style>