intent-engine 0.11.1

A command-line database service for tracking strategic intent, tasks, and events
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useAppStore, type ApprovalResponse } from '../stores/appStore'
import { useI18n } from '../composables/useI18n'
import InPlaceEditor from './InPlaceEditor.vue'
import ModalDialog from './ModalDialog.vue'
import TaskForm from './TaskForm.vue'
import MarkdownRenderer from './MarkdownRenderer.vue'
import { Trash2, GitBranch, Play, Square, ChevronDown, RotateCcw, Signal, User, Bot, Key } from 'lucide-vue-next'
import DebugMenu from './DebugMenu.vue'

const store = useAppStore()
const { t } = useI18n()
const task = computed(() => store.currentTaskDetail)
const isStatusDropdownOpen = ref(false)
const isPriorityDropdownOpen = ref(false)
let priorityDropdownTimer: any = null
const isIdDropdownOpen = ref(false)
let idDropdownTimer: any = null
const showSubtaskModal = ref(false)
const showApprovalModal = ref(false)
const currentApproval = ref<ApprovalResponse | null>(null)
const isGeneratingApproval = ref(false)

function updateName(newName: string) {
  if (!task.value) return
  store.updateTask(task.value.id, { name: newName })
}

function updateSpec(newSpec: string) {
  if (!task.value) return
  store.updateTask(task.value.id, { spec: newSpec })
}



function closeDropdown() {
  isStatusDropdownOpen.value = false
}

async function onStartTask() {
  console.error('onStartTask TRIGGERED')
  if (!task.value) return

  if (task.value.status === 'done') {
    // Reset to todo first
    await store.updateTask(task.value.id, { status: 'todo' })
    
    // Wait a bit for backend to process the state change before starting
    setTimeout(async () => {
      if (!task.value) return
      console.error('Starting task after delay')
      await store.startTask(task.value.id)
      store.currentTaskId = task.value.id
      store.currentTaskId = task.value.id // Set focus
    }, 200)
  } else {
    await store.startTask(task.value.id)
    store.currentTaskId = task.value.id // Set focus
  }
  
  closeDropdown()
}

async function onDoneTask() {
  console.log('onDoneTask called')
  await store.doneTask()
  closeDropdown()
}

function updateStatus(newStatus: 'todo' | 'doing' | 'done') {
  console.log('updateStatus called with:', newStatus)
  if (!task.value) return
  store.updateTask(task.value.id, { status: newStatus })
  closeDropdown()
}

function updatePriority(newPriority: number | null) {
  if (!task.value) return
  store.updateTask(task.value.id, { priority: newPriority })
  isPriorityDropdownOpen.value = false
}

function openPriorityDropdown() {
  if (priorityDropdownTimer) clearTimeout(priorityDropdownTimer)
  isPriorityDropdownOpen.value = true
}

function closePriorityDropdown() {
  priorityDropdownTimer = setTimeout(() => {
    isPriorityDropdownOpen.value = false
  }, 150)
}

function openIdDropdown() {
  if (idDropdownTimer) clearTimeout(idDropdownTimer)
  isIdDropdownOpen.value = true
}

function closeIdDropdown() {
  idDropdownTimer = setTimeout(() => {
    isIdDropdownOpen.value = false
  }, 150)
}

function deleteTask() {
  if (!task.value) return
  if (confirm(t('DELETE_TASK_CONFIRM', { name: task.value.name }))) {
    store.deleteTask(task.value.id)
  }
}

async function createSubtask(data: { name: string, parentId: number | null, priority: number | null, spec: string }) {
  if (!task.value) return
  await store.addTask(data.name, task.value.id, data.priority, data.spec)
  showSubtaskModal.value = false
}

async function generateApproval() {
  if (!task.value) return
  isGeneratingApproval.value = true
  try {
    const approval = await store.createApproval(task.value.id)
    if (approval) {
      currentApproval.value = approval
      showApprovalModal.value = true
    }
  } finally {
    isGeneratingApproval.value = false
  }
}

function copyPassphrase() {
  if (currentApproval.value) {
    navigator.clipboard.writeText(currentApproval.value.passphrase)
  }
}
</script>

<template>
  <div v-if="task" class="flex-1 h-full overflow-y-auto bg-sci-base relative scrollbar-thin scrollbar-thumb-sci-border scrollbar-track-transparent animate-fade-in">
    <!-- Background Grid (Only in Dark Mode via CSS) -->
    <div class="absolute inset-0 pointer-events-none opacity-[0.03] bg-grid-pattern"></div>

    <div class="w-full h-full p-4 relative z-10 flex flex-col">
      <!-- Header -->
      <div class="mb-6 group/header">
        <!-- Metadata Row (ID, Status, Priority, Actions) -->
        <!-- Metadata Row (ID, Status, Priority, Actions) -->
        <!-- Metadata Row (ID, Status, Priority, Actions) -->
        <div class="flex items-center gap-8 mb-6 text-xs font-mono text-sci-text-dim bg-sci-panel/50 border border-sci-border rounded-md px-4 py-2">
          <!-- ID Dropdown -->
          <div class="relative" @mouseenter="openIdDropdown" @mouseleave="closeIdDropdown">
            <button 
              class="flex items-center gap-2 bg-sci-base border border-sci-border rounded-sm px-3 h-7 hover:bg-sci-panel-hover hover:border-sci-text-dim hover:text-sci-text-pri transition-all font-mono"
            >
              <span class="opacity-50">#</span>
              <span>{{ task.id.toString().padStart(4, '0') }}</span>
              <ChevronDown class="w-3 h-3 opacity-30" />
            </button>

            <div v-if="isIdDropdownOpen" class="absolute left-0 mt-1 w-40 bg-sci-base border border-sci-border shadow-xl rounded-sm z-50 py-1 backdrop-blur-sm flex flex-col">
              <button 
                @click="showSubtaskModal = true; closeIdDropdown()"
                class="w-full flex items-center gap-2 px-3 py-2 text-xs font-mono text-left hover:bg-sci-panel-hover hover:text-sci-cyan transition-colors"
              >
                <GitBranch class="w-3.5 h-3.5" />
                {{ t('SPAWN_SUBTASK') }}
              </button>
              <div class="h-px bg-sci-border my-1 opacity-50"></div>
              <button 
                @click="deleteTask(); closeIdDropdown()"
                class="w-full flex items-center gap-2 px-3 py-2 text-xs font-mono text-left hover:bg-sci-panel-hover hover:text-sci-danger transition-colors text-sci-danger"
              >
                <Trash2 class="w-3.5 h-3.5" />
                {{ t('DELETE') }}
              </button>
            </div>
          </div>
          
          <!-- Status -->
          <div class="relative" @mouseenter="isStatusDropdownOpen = true" @mouseleave="isStatusDropdownOpen = false">
            <button 
              class="flex items-center gap-2 bg-sci-base border border-sci-border rounded-sm px-3 h-7 hover:bg-sci-panel-hover hover:border-sci-text-dim hover:text-sci-text-pri transition-all uppercase tracking-wider font-bold"
              :class="{
                'text-sci-text-dim': task.status === 'todo' || task.status === 'done',
                'text-sci-cyan': task.status === 'doing'
              }"
            >
              <div v-if="task.status === 'doing'" class="animate-pulse">
                <Play class="w-[18px] h-[18px] fill-current" />
              </div>
              
              <div v-else-if="task.status === 'done'" class="relative w-[18px] h-[18px]">
                <Square class="w-full h-full" />
                <span class="absolute -top-[2px] right-0 flex items-center justify-center font-serif font-bold text-lg leading-none select-none">✓</span>
              </div>
              
              <Square v-else class="w-[18px] h-[18px]" />
              
              <span>{{ task.status }}</span>
              <ChevronDown class="w-3 h-3 opacity-30" />
            </button>

            <!-- Dropdown Menu (Keep existing logic, just styling tweaks if needed) -->
            <div v-if="isStatusDropdownOpen" class="absolute top-full left-0 w-48 z-30 pt-2">
              <div class="bg-sci-base border border-sci-border rounded-sm p-1 backdrop-blur-sm">
                 <!-- ... keep existing menu items ... -->
                  <template v-if="task.status === 'todo'">
                    <button @click="onStartTask" class="w-full flex items-center gap-2 px-2 py-2 text-xs font-mono text-left hover:bg-sci-panel-hover hover:text-sci-cyan rounded-sm transition-colors group">
                      <Play class="w-3 h-3 group-hover:fill-current" />
                      {{ t('START') }}
                    </button>
                    <button @click="updateStatus('done')" class="w-full flex items-center gap-2 px-2 py-2 text-xs font-mono text-left hover:bg-sci-panel-hover hover:text-sci-text-pri rounded-sm transition-colors">
                      <div class="relative w-[18px] h-[18px] text-sci-text-dim">
                        <Square class="w-full h-full" />
                        <span class="absolute -top-[2px] right-0 flex items-center justify-center font-serif font-bold text-lg leading-none select-none">✓</span>
                      </div>
                      {{ t('MARK_DONE') }}
                    </button>
                  </template>

                  <template v-if="task.status === 'doing'">
                    <button 
                      @click="task.id === store.currentTaskId ? onDoneTask() : updateStatus('done')" 
                      class="w-full flex items-center gap-2 px-2 py-2 text-xs font-mono text-left hover:bg-sci-panel-hover hover:text-sci-text-pri rounded-sm transition-colors"
                    >
                      <div class="relative w-[18px] h-[18px] text-sci-text-dim">
                        <Square class="w-full h-full" />
                        <span class="absolute -top-[2px] right-0 flex items-center justify-center font-serif font-bold text-lg leading-none select-none">✓</span>
                      </div>
                      {{ t('DONE') }}
                    </button>
                    <button @click="updateStatus('todo')" class="w-full flex items-center gap-2 px-2 py-2 text-xs font-mono text-left hover:bg-sci-panel-hover hover:text-sci-text-pri rounded-sm transition-colors">
                      <Square class="w-[18px] h-[18px] text-sci-text-dim" />
                      {{ t('STOP') }}
                    </button>
                  </template>

                  <template v-if="task.status === 'done'">
                    <button @click="onStartTask" class="w-full flex items-center gap-2 px-2 py-2 text-xs font-mono text-left hover:bg-sci-panel-hover hover:text-sci-cyan rounded-sm transition-colors group">
                      <Play class="w-3 h-3 group-hover:fill-current" />
                      {{ t('RESTART') }}
                    </button>
                    <button @click="updateStatus('todo')" class="w-full flex items-center gap-2 px-2 py-2 text-xs font-mono text-left hover:bg-sci-panel-hover hover:text-sci-text-main rounded-sm transition-colors">
                      <RotateCcw class="w-3 h-3" />
                      {{ t('MARK_TODO') }}
                    </button>
                  </template>
              </div>
            </div>
          </div>

          <!-- Priority -->
          <div class="flex items-center gap-2">
            <div class="relative" @mouseenter="openPriorityDropdown" @mouseleave="closePriorityDropdown">
              <button 
                class="flex items-center gap-2 text-xs font-mono bg-sci-base border border-sci-border rounded-sm px-3 h-7 hover:bg-sci-panel-hover hover:border-sci-text-dim hover:text-sci-text-pri transition-all uppercase tracking-wider font-bold"
              >
                <Signal class="w-[18px] h-[18px]" :class="{
                  'text-sci-danger': task.priority === 1,
                  'text-sci-orange': task.priority === 2,
                  'text-sci-cyan': task.priority === 3,
                  'text-sci-text-dim': !task.priority || task.priority > 3
                }" />
                <span :class="{
                  'text-sci-danger': task.priority === 1,
                  'text-sci-orange': task.priority === 2,
                  'text-sci-cyan': task.priority === 3,
                  'text-sci-text-dim': !task.priority || task.priority > 3
                }">
                  {{ 
                    task.priority === 1 ? t('CRITICAL') :
                    task.priority === 2 ? t('HIGH') :
                    task.priority === 3 ? t('MEDIUM') :
                    task.priority === 4 ? t('LOW') :
                    t('NONE')
                  }}
                </span>
                <ChevronDown class="w-3 h-3 opacity-30" />
              </button>

              <div v-if="isPriorityDropdownOpen" class="absolute left-0 mt-1 w-32 bg-sci-base border border-sci-border shadow-xl rounded-sm z-50 py-1 backdrop-blur-sm flex flex-col">
                <button 
                  @click="updatePriority(1)" 
                  class="w-full text-left px-3 py-1.5 text-xs font-mono text-sci-danger hover:bg-sci-panel-hover transition-colors"
                >
                  {{ t('CRITICAL') }}
                </button>
                <button 
                  @click="updatePriority(2)" 
                  class="w-full text-left px-3 py-1.5 text-xs font-mono text-sci-orange hover:bg-sci-panel-hover transition-colors"
                >
                  {{ t('HIGH') }}
                </button>
                <button 
                  @click="updatePriority(3)" 
                  class="w-full text-left px-3 py-1.5 text-xs font-mono text-sci-cyan hover:bg-sci-panel-hover transition-colors"
                >
                  {{ t('MEDIUM') }}
                </button>
                <button 
                  @click="updatePriority(4)" 
                  class="w-full text-left px-3 py-1.5 text-xs font-mono text-sci-text-dim hover:bg-sci-panel-hover transition-colors"
                >
                  {{ t('LOW') }}
                </button>
                <button 
                  @click="updatePriority(null)" 
                  class="w-full text-left px-3 py-1.5 text-xs font-mono text-sci-text-dim hover:bg-sci-panel-hover transition-colors"
                >
                  {{ t('NONE') }}
                </button>
              </div>
            </div>
          </div>

          <!-- Owner Badge -->
          <div class="flex items-center gap-2">
            <div
              class="flex items-center gap-1.5 text-xs font-mono bg-sci-base border border-sci-border rounded-sm px-3 h-7"
              :class="{
                'text-sci-cyan border-sci-cyan/30': task.owner === 'human',
                'text-sci-purple border-sci-purple/30': task.owner === 'ai'
              }"
            >
              <User v-if="task.owner === 'human'" class="w-3.5 h-3.5" />
              <Bot v-else class="w-3.5 h-3.5" />
              <span class="uppercase tracking-wider font-bold">{{ task.owner || 'HUMAN' }}</span>
            </div>

            <!-- Authorize AI Button (only for human-owned tasks) -->
            <button
              v-if="task.owner === 'human' || !task.owner"
              @click="generateApproval"
              :disabled="isGeneratingApproval"
              class="flex items-center gap-1.5 text-xs font-mono bg-sci-base border border-sci-border rounded-sm px-3 h-7 hover:bg-sci-panel-hover hover:border-sci-cyan hover:text-sci-cyan transition-all"
              :class="{ 'opacity-50 cursor-wait': isGeneratingApproval }"
            >
              <Key class="w-3.5 h-3.5" />
              <span class="uppercase tracking-wider">{{ isGeneratingApproval ? 'GENERATING...' : 'AUTHORIZE AI' }}</span>
            </button>
          </div>

          <!-- Spacer to push debug to right -->
          <div class="flex-1"></div>

          <!-- Debug Menu (only on port 3000) -->
          <DebugMenu v-if="task" :task-id="task.id" />

          </div>

        
        <InPlaceEditor 
          :model-value="task.name || ''" 
          @save="updateName"
          class="text-4xl font-display font-bold tracking-tight leading-tight transition-colors"
          :class="{
            'text-sci-danger': task.priority === 1,
            'text-sci-orange': task.priority === 2,
            'text-sci-cyan': task.priority === 3,
            'text-sci-text-pri': !task.priority || task.priority > 3
          }"
        />
      </div>

      <!-- Spec (Borderless, clean) -->
      <div class="mb-12 flex-1 flex flex-col">
        <InPlaceEditor 
          :model-value="task.spec || null" 
          multiline 
          markdown
          :placeholder="t('NO_SPEC')"
          @save="updateSpec"
          class="flex-1"
        >
          <template #display>
            <MarkdownRenderer 
              :source="task.spec || null" 
              :placeholder="t('NO_SPEC')"
            />
          </template>
        </InPlaceEditor>
      </div>
    </div>

    <!-- Subtask Modal -->
    <ModalDialog
      :is-open="showSubtaskModal"
      :title="t('SPAWN_SUBTASK')"
      size="4xl"
      @close="showSubtaskModal = false"
    >
      <TaskForm
        v-if="task"
        :parent-id="task.id"
        :parent-name="task.name"
        @submit="createSubtask"
        @cancel="showSubtaskModal = false"
      />
    </ModalDialog>

    <!-- Approval Passphrase Modal -->
    <ModalDialog
      :is-open="showApprovalModal"
      title="AI Authorization Passphrase"
      size="md"
      @close="showApprovalModal = false; currentApproval = null"
    >
      <div v-if="currentApproval" class="space-y-6">
        <p class="text-sm text-sci-text-dim">
          Share this passphrase with your AI agent to authorize task completion.
          The passphrase is one-time use and will be consumed upon successful verification.
        </p>

        <div class="bg-sci-panel border border-sci-border rounded-lg p-6 text-center">
          <p class="text-xs text-sci-text-dim uppercase tracking-wider mb-2">Passphrase</p>
          <p class="text-4xl font-mono font-bold text-sci-cyan tracking-[0.3em] select-all">
            {{ currentApproval.passphrase }}
          </p>
        </div>

        <div class="flex justify-center gap-4">
          <button
            @click="copyPassphrase"
            class="flex items-center gap-2 px-4 py-2 bg-sci-cyan/10 border border-sci-cyan/30 text-sci-cyan rounded-md hover:bg-sci-cyan/20 transition-colors font-mono text-sm"
          >
            <Key class="w-4 h-4" />
            Copy to Clipboard
          </button>
          <button
            @click="showApprovalModal = false; currentApproval = null"
            class="px-4 py-2 bg-sci-panel border border-sci-border text-sci-text-dim rounded-md hover:bg-sci-panel-hover transition-colors font-mono text-sm"
          >
            Close
          </button>
        </div>

        <p v-if="currentApproval.expires_at" class="text-xs text-sci-text-dim text-center">
          Expires: {{ new Date(currentApproval.expires_at).toLocaleString() }}
        </p>
      </div>
    </ModalDialog>
  </div>
  
  <div v-else class="flex-1 flex items-center justify-center bg-sci-base relative overflow-hidden">
    <!-- Empty State -->
    <div class="absolute inset-0 pointer-events-none opacity-[0.02]"
         style="background-image: linear-gradient(var(--text-main) 1px, transparent 1px), linear-gradient(90deg, var(--text-main) 1px, transparent 1px); background-size: 40px 40px;">
    </div>
    
    <div class="text-center text-sci-text-dim relative z-10">
      <div class="w-20 h-20 border border-sci-border border-t-sci-cyan rounded-full animate-spin mx-auto mb-6"></div>
      <p class="font-mono text-sm tracking-widest uppercase">{{ t('AWAITING_SELECTION') }}</p>
      <p class="text-xs mt-2 opacity-50">SELECT A NODE TO INITIALIZE</p>
    </div>
  </div>
</template>