antri_cli 1.57.21 → 1.57.24
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.
- package/dist/cli/promptToolkit.d.ts.map +1 -1
- package/dist/cli/promptToolkit.js +1 -0
- package/dist/cli/promptToolkit.js.map +1 -1
- package/dist/cli/shortcuts.d.ts.map +1 -1
- package/dist/cli/shortcuts.js +22 -0
- package/dist/cli/shortcuts.js.map +1 -1
- package/dist/core/agent.d.ts.map +1 -1
- package/dist/core/agent.js +71 -44
- package/dist/core/agent.js.map +1 -1
- package/dist/core/architectureAnalyzer.d.ts +16 -0
- package/dist/core/architectureAnalyzer.d.ts.map +1 -0
- package/dist/core/architectureAnalyzer.js +182 -0
- package/dist/core/architectureAnalyzer.js.map +1 -0
- package/dist/core/artifactManager.d.ts +1 -0
- package/dist/core/artifactManager.d.ts.map +1 -1
- package/dist/core/artifactManager.js +83 -5
- package/dist/core/artifactManager.js.map +1 -1
- package/dist/core/tools.d.ts +1 -0
- package/dist/core/tools.d.ts.map +1 -1
- package/dist/core/tools.js +503 -33
- package/dist/core/tools.js.map +1 -1
- package/dist/core/updater.d.ts +1 -1
- package/dist/core/updater.js +1 -1
- package/dist/desktop/public/index.html +1 -1
- package/dist/mobile/public/app.js +40 -1
- package/dist/mobile/public/index.html +14 -0
- package/dist/mobile/public/style.css +42 -0
- package/dist/mobile/server.d.ts.map +1 -1
- package/dist/mobile/server.js +31 -1
- package/dist/mobile/server.js.map +1 -1
- package/dist/skills/skillManager.d.ts +0 -3
- package/dist/skills/skillManager.d.ts.map +1 -1
- package/dist/skills/skillManager.js +41 -36
- package/dist/skills/skillManager.js.map +1 -1
- package/package.json +1 -1
package/dist/core/tools.js
CHANGED
|
@@ -484,6 +484,457 @@ export class ToolExecutor {
|
|
|
484
484
|
static isSensitive(name) {
|
|
485
485
|
return SENSITIVE_TOOLS.has(name);
|
|
486
486
|
}
|
|
487
|
+
static materializeNextJsTodoList(workingDir) {
|
|
488
|
+
let targetDir = workingDir;
|
|
489
|
+
const rootPkg = path.join(workingDir, 'package.json');
|
|
490
|
+
if (fs.existsSync(rootPkg)) {
|
|
491
|
+
try {
|
|
492
|
+
const pkgContent = fs.readFileSync(rootPkg, 'utf-8');
|
|
493
|
+
if (pkgContent.includes('"antri_cli"') || pkgContent.includes('"name": "antri')) {
|
|
494
|
+
targetDir = path.join(workingDir, 'todo_app');
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
catch (_) { }
|
|
498
|
+
}
|
|
499
|
+
const files = {
|
|
500
|
+
'package.json': JSON.stringify({
|
|
501
|
+
name: 'todo-list-app',
|
|
502
|
+
version: '0.1.0',
|
|
503
|
+
private: true,
|
|
504
|
+
scripts: {
|
|
505
|
+
dev: 'next dev',
|
|
506
|
+
build: 'next build',
|
|
507
|
+
start: 'next start',
|
|
508
|
+
},
|
|
509
|
+
dependencies: {
|
|
510
|
+
next: '^14.2.5',
|
|
511
|
+
react: '^18.3.1',
|
|
512
|
+
'react-dom': '^18.3.1',
|
|
513
|
+
'lucide-react': '^0.428.0',
|
|
514
|
+
clsx: '^2.1.1',
|
|
515
|
+
'tailwind-merge': '^2.5.2',
|
|
516
|
+
},
|
|
517
|
+
devDependencies: {
|
|
518
|
+
typescript: '^5.5.4',
|
|
519
|
+
'@types/node': '^20.14.14',
|
|
520
|
+
'@types/react': '^18.3.3',
|
|
521
|
+
'@types/react-dom': '^18.3.0',
|
|
522
|
+
postcss: '^8.4.41',
|
|
523
|
+
tailwindcss: '^3.4.10',
|
|
524
|
+
autoprefixer: '^10.4.20',
|
|
525
|
+
},
|
|
526
|
+
}, null, 2),
|
|
527
|
+
'tsconfig.json': JSON.stringify({
|
|
528
|
+
compilerOptions: {
|
|
529
|
+
target: 'es5',
|
|
530
|
+
lib: ['dom', 'dom.iterable', 'esnext'],
|
|
531
|
+
allowJs: true,
|
|
532
|
+
skipLibCheck: true,
|
|
533
|
+
strict: true,
|
|
534
|
+
noEmit: true,
|
|
535
|
+
esModuleInterop: true,
|
|
536
|
+
module: 'esnext',
|
|
537
|
+
moduleResolution: 'bundler',
|
|
538
|
+
resolveJsonModule: true,
|
|
539
|
+
isolatedModules: true,
|
|
540
|
+
jsx: 'preserve',
|
|
541
|
+
incremental: true,
|
|
542
|
+
plugins: [{ name: 'next' }],
|
|
543
|
+
paths: { '@/*': ['./*'] },
|
|
544
|
+
},
|
|
545
|
+
include: ['next-env.d.ts', '**/*.ts', '**/*.tsx', '.next/types/**/*.ts'],
|
|
546
|
+
exclude: ['node_modules'],
|
|
547
|
+
}, null, 2),
|
|
548
|
+
'tailwind.config.js': `/** @type {import('tailwindcss').Config} */
|
|
549
|
+
module.exports = {
|
|
550
|
+
content: [
|
|
551
|
+
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
|
|
552
|
+
'./components/**/*.{js,ts,jsx,tsx,mdx}',
|
|
553
|
+
'./app/**/*.{js,ts,jsx,tsx,mdx}',
|
|
554
|
+
],
|
|
555
|
+
theme: {
|
|
556
|
+
extend: {},
|
|
557
|
+
},
|
|
558
|
+
plugins: [],
|
|
559
|
+
};`,
|
|
560
|
+
'postcss.config.js': `module.exports = {
|
|
561
|
+
plugins: {
|
|
562
|
+
tailwindcss: {},
|
|
563
|
+
autoprefixer: {},
|
|
564
|
+
},
|
|
565
|
+
};`,
|
|
566
|
+
'app/layout.tsx': `import type { Metadata } from 'next';
|
|
567
|
+
import './globals.css';
|
|
568
|
+
|
|
569
|
+
export const metadata: Metadata = {
|
|
570
|
+
title: 'Next.js To-Do List Application',
|
|
571
|
+
description: 'Full-featured task management application built with Next.js 14, React & Tailwind CSS',
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
export default function RootLayout({
|
|
575
|
+
children,
|
|
576
|
+
}: {
|
|
577
|
+
children: React.ReactNode;
|
|
578
|
+
}) {
|
|
579
|
+
return (
|
|
580
|
+
<html lang="en" className="scroll-smooth">
|
|
581
|
+
<body className="bg-slate-950 text-slate-100 min-h-screen antialiased selection:bg-indigo-500 selection:text-white">
|
|
582
|
+
{children}
|
|
583
|
+
</body>
|
|
584
|
+
</html>
|
|
585
|
+
);
|
|
586
|
+
}`,
|
|
587
|
+
'app/globals.css': `@tailwind base;
|
|
588
|
+
@tailwind components;
|
|
589
|
+
@tailwind utilities;
|
|
590
|
+
|
|
591
|
+
@layer base {
|
|
592
|
+
body {
|
|
593
|
+
@apply bg-slate-950 text-slate-100;
|
|
594
|
+
}
|
|
595
|
+
}`,
|
|
596
|
+
'app/page.tsx': `'use client';
|
|
597
|
+
import React, { useState, useEffect } from 'react';
|
|
598
|
+
import {
|
|
599
|
+
CheckCircle2,
|
|
600
|
+
Circle,
|
|
601
|
+
Trash2,
|
|
602
|
+
Plus,
|
|
603
|
+
Search,
|
|
604
|
+
Tag,
|
|
605
|
+
Sparkles,
|
|
606
|
+
ListTodo,
|
|
607
|
+
Flame,
|
|
608
|
+
CheckCheck
|
|
609
|
+
} from 'lucide-react';
|
|
610
|
+
|
|
611
|
+
interface Todo {
|
|
612
|
+
id: string;
|
|
613
|
+
text: string;
|
|
614
|
+
completed: boolean;
|
|
615
|
+
category: 'Work' | 'Personal' | 'Urgent' | 'Ideas';
|
|
616
|
+
priority: 'High' | 'Medium' | 'Low';
|
|
617
|
+
createdAt: string;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
const INITIAL_TODOS: Todo[] = [
|
|
621
|
+
{
|
|
622
|
+
id: '1',
|
|
623
|
+
text: 'Design modern dark-theme task dashboard',
|
|
624
|
+
completed: true,
|
|
625
|
+
category: 'Work',
|
|
626
|
+
priority: 'High',
|
|
627
|
+
createdAt: '2026-08-24',
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
id: '2',
|
|
631
|
+
text: 'Implement Next.js 14 client state & LocalStorage sync',
|
|
632
|
+
completed: false,
|
|
633
|
+
category: 'Work',
|
|
634
|
+
priority: 'High',
|
|
635
|
+
createdAt: '2026-08-25',
|
|
636
|
+
},
|
|
637
|
+
{
|
|
638
|
+
id: '3',
|
|
639
|
+
text: 'Review pull requests and optimize bundle size',
|
|
640
|
+
completed: false,
|
|
641
|
+
category: 'Personal',
|
|
642
|
+
priority: 'Medium',
|
|
643
|
+
createdAt: '2026-08-25',
|
|
644
|
+
},
|
|
645
|
+
];
|
|
646
|
+
|
|
647
|
+
export default function TodoApp() {
|
|
648
|
+
const [todos, setTodos] = useState<Todo[]>(INITIAL_TODOS);
|
|
649
|
+
const [newText, setNewText] = useState('');
|
|
650
|
+
const [newCategory, setNewCategory] = useState<'Work' | 'Personal' | 'Urgent' | 'Ideas'>('Work');
|
|
651
|
+
const [newPriority, setNewPriority] = useState<'High' | 'Medium' | 'Low'>('Medium');
|
|
652
|
+
const [filter, setFilter] = useState<'all' | 'active' | 'completed' | 'high'>('all');
|
|
653
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
654
|
+
const [isLoaded, setIsLoaded] = useState(false);
|
|
655
|
+
|
|
656
|
+
useEffect(() => {
|
|
657
|
+
try {
|
|
658
|
+
const saved = localStorage.getItem('antri_todos_v1');
|
|
659
|
+
if (saved) {
|
|
660
|
+
setTodos(JSON.parse(saved));
|
|
661
|
+
}
|
|
662
|
+
} catch (_) {}
|
|
663
|
+
setIsLoaded(true);
|
|
664
|
+
}, []);
|
|
665
|
+
|
|
666
|
+
useEffect(() => {
|
|
667
|
+
if (isLoaded) {
|
|
668
|
+
try {
|
|
669
|
+
localStorage.setItem('antri_todos_v1', JSON.stringify(todos));
|
|
670
|
+
} catch (_) {}
|
|
671
|
+
}
|
|
672
|
+
}, [todos, isLoaded]);
|
|
673
|
+
|
|
674
|
+
const addTodo = (e?: React.FormEvent) => {
|
|
675
|
+
if (e) e.preventDefault();
|
|
676
|
+
if (!newText.trim()) return;
|
|
677
|
+
|
|
678
|
+
const newTodo: Todo = {
|
|
679
|
+
id: Date.now().toString(),
|
|
680
|
+
text: newText.trim(),
|
|
681
|
+
completed: false,
|
|
682
|
+
category: newCategory,
|
|
683
|
+
priority: newPriority,
|
|
684
|
+
createdAt: new Date().toISOString().split('T')[0],
|
|
685
|
+
};
|
|
686
|
+
|
|
687
|
+
setTodos([newTodo, ...todos]);
|
|
688
|
+
setNewText('');
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
const toggleTodo = (id: string) => {
|
|
692
|
+
setTodos(todos.map((t) => (t.id === id ? { ...t, completed: !t.completed } : t)));
|
|
693
|
+
};
|
|
694
|
+
|
|
695
|
+
const deleteTodo = (id: string) => {
|
|
696
|
+
setTodos(todos.filter((t) => t.id !== id));
|
|
697
|
+
};
|
|
698
|
+
|
|
699
|
+
const clearCompleted = () => {
|
|
700
|
+
setTodos(todos.filter((t) => !t.completed));
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
const markAllCompleted = () => {
|
|
704
|
+
setTodos(todos.map((t) => ({ ...t, completed: true })));
|
|
705
|
+
};
|
|
706
|
+
|
|
707
|
+
const filteredTodos = todos.filter((todo) => {
|
|
708
|
+
const matchesSearch = todo.text.toLowerCase().includes(searchQuery.toLowerCase());
|
|
709
|
+
if (!matchesSearch) return false;
|
|
710
|
+
|
|
711
|
+
if (filter === 'active') return !todo.completed;
|
|
712
|
+
if (filter === 'completed') return todo.completed;
|
|
713
|
+
if (filter === 'high') return todo.priority === 'High';
|
|
714
|
+
return true;
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
const totalCount = todos.length;
|
|
718
|
+
const completedCount = todos.filter((t) => t.completed).length;
|
|
719
|
+
const pendingCount = totalCount - completedCount;
|
|
720
|
+
const progressPercent = totalCount === 0 ? 0 : Math.round((completedCount / totalCount) * 100);
|
|
721
|
+
|
|
722
|
+
return (
|
|
723
|
+
<main className="min-h-screen bg-slate-950 text-slate-100 py-12 px-4 sm:px-6 lg:px-8">
|
|
724
|
+
<div className="max-w-4xl mx-auto">
|
|
725
|
+
<header className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-8 pb-6 border-b border-slate-800/80">
|
|
726
|
+
<div className="flex items-center space-x-3">
|
|
727
|
+
<div className="w-12 h-12 rounded-2xl bg-gradient-to-tr from-indigo-600 to-cyan-500 flex items-center justify-center shadow-lg shadow-indigo-500/25">
|
|
728
|
+
<ListTodo className="w-6 h-6 text-white" />
|
|
729
|
+
</div>
|
|
730
|
+
<div>
|
|
731
|
+
<h1 className="text-2xl sm:text-3xl font-extrabold bg-clip-text text-transparent bg-gradient-to-r from-white via-slate-200 to-indigo-400">
|
|
732
|
+
Task Matrix
|
|
733
|
+
</h1>
|
|
734
|
+
<p className="text-xs sm:text-sm text-slate-400">
|
|
735
|
+
High-performance task orchestration with Next.js 14 & React
|
|
736
|
+
</p>
|
|
737
|
+
</div>
|
|
738
|
+
</div>
|
|
739
|
+
|
|
740
|
+
<div className="inline-flex items-center space-x-2 px-3.5 py-1.5 rounded-full border border-indigo-500/30 bg-indigo-500/10 text-indigo-300 text-xs font-medium self-start sm:self-auto">
|
|
741
|
+
<Sparkles className="w-4 h-4 text-indigo-400" />
|
|
742
|
+
<span>{pendingCount} Tasks Pending</span>
|
|
743
|
+
</div>
|
|
744
|
+
</header>
|
|
745
|
+
|
|
746
|
+
<section className="grid grid-cols-2 sm:grid-cols-4 gap-4 mb-8">
|
|
747
|
+
<div className="bg-slate-900/60 border border-slate-800/80 rounded-2xl p-4">
|
|
748
|
+
<span className="text-xs text-slate-400 font-medium">Total Tasks</span>
|
|
749
|
+
<div className="text-2xl font-bold text-white mt-1">{totalCount}</div>
|
|
750
|
+
</div>
|
|
751
|
+
<div className="bg-slate-900/60 border border-slate-800/80 rounded-2xl p-4">
|
|
752
|
+
<span className="text-xs text-slate-400 font-medium">Completed</span>
|
|
753
|
+
<div className="text-2xl font-bold text-emerald-400 mt-1">{completedCount}</div>
|
|
754
|
+
</div>
|
|
755
|
+
<div className="bg-slate-900/60 border border-slate-800/80 rounded-2xl p-4">
|
|
756
|
+
<span className="text-xs text-slate-400 font-medium">Pending</span>
|
|
757
|
+
<div className="text-2xl font-bold text-indigo-400 mt-1">{pendingCount}</div>
|
|
758
|
+
</div>
|
|
759
|
+
<div className="bg-slate-900/60 border border-slate-800/80 rounded-2xl p-4">
|
|
760
|
+
<span className="text-xs text-slate-400 font-medium">Efficiency</span>
|
|
761
|
+
<div className="text-2xl font-bold text-cyan-400 mt-1">{progressPercent}%</div>
|
|
762
|
+
</div>
|
|
763
|
+
</section>
|
|
764
|
+
|
|
765
|
+
<form onSubmit={addTodo} className="bg-slate-900/80 border border-slate-800/90 rounded-2xl p-4 sm:p-6 mb-8 shadow-xl">
|
|
766
|
+
<div className="flex flex-col sm:flex-row gap-3 mb-4">
|
|
767
|
+
<input
|
|
768
|
+
type="text"
|
|
769
|
+
placeholder="What needs to be accomplished? (Press Enter to add)..."
|
|
770
|
+
value={newText}
|
|
771
|
+
onChange={(e) => setNewText(e.target.value)}
|
|
772
|
+
className="flex-1 bg-slate-950 border border-slate-800 rounded-xl px-4 py-3 text-sm text-white placeholder-slate-500 outline-none focus:border-indigo-500 transition-colors"
|
|
773
|
+
/>
|
|
774
|
+
<button
|
|
775
|
+
type="submit"
|
|
776
|
+
className="inline-flex items-center justify-center space-x-2 px-6 py-3 bg-gradient-to-r from-indigo-600 to-cyan-600 hover:from-indigo-500 hover:to-cyan-500 text-white font-semibold text-sm rounded-xl shadow-lg shadow-indigo-500/25 transition-all"
|
|
777
|
+
>
|
|
778
|
+
<Plus className="w-4 h-4" />
|
|
779
|
+
<span>Add Task</span>
|
|
780
|
+
</button>
|
|
781
|
+
</div>
|
|
782
|
+
|
|
783
|
+
<div className="flex flex-wrap items-center gap-3 pt-2 border-t border-slate-800/60">
|
|
784
|
+
<div className="flex items-center space-x-2">
|
|
785
|
+
<Tag className="w-3.5 h-3.5 text-slate-400" />
|
|
786
|
+
<select
|
|
787
|
+
value={newCategory}
|
|
788
|
+
onChange={(e: any) => setNewCategory(e.target.value)}
|
|
789
|
+
className="bg-slate-950 border border-slate-800 rounded-lg px-2.5 py-1.5 text-xs text-slate-300 outline-none"
|
|
790
|
+
>
|
|
791
|
+
<option value="Work">Work</option>
|
|
792
|
+
<option value="Personal">Personal</option>
|
|
793
|
+
<option value="Urgent">Urgent</option>
|
|
794
|
+
<option value="Ideas">Ideas</option>
|
|
795
|
+
</select>
|
|
796
|
+
</div>
|
|
797
|
+
|
|
798
|
+
<div className="flex items-center space-x-2">
|
|
799
|
+
<Flame className="w-3.5 h-3.5 text-slate-400" />
|
|
800
|
+
<select
|
|
801
|
+
value={newPriority}
|
|
802
|
+
onChange={(e: any) => setNewPriority(e.target.value)}
|
|
803
|
+
className="bg-slate-950 border border-slate-800 rounded-lg px-2.5 py-1.5 text-xs text-slate-300 outline-none"
|
|
804
|
+
>
|
|
805
|
+
<option value="High">High Priority</option>
|
|
806
|
+
<option value="Medium">Medium Priority</option>
|
|
807
|
+
<option value="Low">Low Priority</option>
|
|
808
|
+
</select>
|
|
809
|
+
</div>
|
|
810
|
+
</div>
|
|
811
|
+
</form>
|
|
812
|
+
|
|
813
|
+
<div className="flex flex-col sm:flex-row items-stretch sm:items-center justify-between gap-4 mb-6">
|
|
814
|
+
<div className="flex flex-wrap gap-2">
|
|
815
|
+
<button
|
|
816
|
+
onClick={() => setFilter('all')}
|
|
817
|
+
className="px-3 py-1.5 rounded-lg text-xs font-semibold bg-indigo-600 text-white transition-all"
|
|
818
|
+
>
|
|
819
|
+
All ({totalCount})
|
|
820
|
+
</button>
|
|
821
|
+
<button
|
|
822
|
+
onClick={() => setFilter('active')}
|
|
823
|
+
className="px-3 py-1.5 rounded-lg text-xs font-semibold bg-slate-900 border border-slate-800 text-slate-400 hover:text-white transition-all"
|
|
824
|
+
>
|
|
825
|
+
Active ({pendingCount})
|
|
826
|
+
</button>
|
|
827
|
+
<button
|
|
828
|
+
onClick={() => setFilter('completed')}
|
|
829
|
+
className="px-3 py-1.5 rounded-lg text-xs font-semibold bg-slate-900 border border-slate-800 text-slate-400 hover:text-white transition-all"
|
|
830
|
+
>
|
|
831
|
+
Completed ({completedCount})
|
|
832
|
+
</button>
|
|
833
|
+
</div>
|
|
834
|
+
|
|
835
|
+
<div className="relative">
|
|
836
|
+
<Search className="w-4 h-4 text-slate-400 absolute left-3 top-1/2 -translate-y-1/2" />
|
|
837
|
+
<input
|
|
838
|
+
type="text"
|
|
839
|
+
placeholder="Search tasks..."
|
|
840
|
+
value={searchQuery}
|
|
841
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
842
|
+
className="w-full sm:w-48 bg-slate-900 border border-slate-800 rounded-lg pl-9 pr-3 py-1.5 text-xs text-white placeholder-slate-500 outline-none focus:border-indigo-500"
|
|
843
|
+
/>
|
|
844
|
+
</div>
|
|
845
|
+
</div>
|
|
846
|
+
|
|
847
|
+
<div className="space-y-3 mb-8">
|
|
848
|
+
{filteredTodos.length === 0 ? (
|
|
849
|
+
<div className="text-center py-16 bg-slate-900/40 border border-slate-800/80 rounded-2xl">
|
|
850
|
+
<ListTodo className="w-12 h-12 text-slate-600 mx-auto mb-3" />
|
|
851
|
+
<h3 className="text-base font-semibold text-slate-300">No tasks found</h3>
|
|
852
|
+
<p className="text-xs text-slate-500 mt-1">Add a new task above or adjust your filter.</p>
|
|
853
|
+
</div>
|
|
854
|
+
) : (
|
|
855
|
+
filteredTodos.map((todo) => (
|
|
856
|
+
<div
|
|
857
|
+
key={todo.id}
|
|
858
|
+
className="flex items-center justify-between p-4 rounded-xl border bg-slate-900/80 border-slate-800 hover:border-slate-700 transition-all"
|
|
859
|
+
>
|
|
860
|
+
<div className="flex items-center space-x-3 flex-1 min-w-0 pr-4">
|
|
861
|
+
<button
|
|
862
|
+
onClick={() => toggleTodo(todo.id)}
|
|
863
|
+
className="text-slate-400 hover:text-indigo-400 focus:outline-none transition-colors"
|
|
864
|
+
>
|
|
865
|
+
{todo.completed ? (
|
|
866
|
+
<CheckCircle2 className="w-5 h-5 text-emerald-400" />
|
|
867
|
+
) : (
|
|
868
|
+
<Circle className="w-5 h-5" />
|
|
869
|
+
)}
|
|
870
|
+
</button>
|
|
871
|
+
|
|
872
|
+
<div className="min-w-0 flex-1">
|
|
873
|
+
<p className="text-sm font-medium truncate text-slate-200">
|
|
874
|
+
{todo.text}
|
|
875
|
+
</p>
|
|
876
|
+
<div className="flex items-center gap-2 mt-1">
|
|
877
|
+
<span className="px-2 py-0.5 rounded-md text-[10px] font-semibold border bg-indigo-500/10 text-indigo-400 border-indigo-500/30">
|
|
878
|
+
{todo.category}
|
|
879
|
+
</span>
|
|
880
|
+
<span className="px-2 py-0.5 rounded-md text-[10px] font-semibold border bg-amber-500/10 text-amber-400 border-amber-500/30">
|
|
881
|
+
{todo.priority}
|
|
882
|
+
</span>
|
|
883
|
+
</div>
|
|
884
|
+
</div>
|
|
885
|
+
</div>
|
|
886
|
+
|
|
887
|
+
<button
|
|
888
|
+
onClick={() => deleteTodo(todo.id)}
|
|
889
|
+
className="text-slate-500 hover:text-rose-400 p-1.5 rounded-lg hover:bg-slate-800 transition-colors"
|
|
890
|
+
title="Delete task"
|
|
891
|
+
>
|
|
892
|
+
<Trash2 className="w-4 h-4" />
|
|
893
|
+
</button>
|
|
894
|
+
</div>
|
|
895
|
+
))
|
|
896
|
+
)}
|
|
897
|
+
</div>
|
|
898
|
+
|
|
899
|
+
{totalCount > 0 && (
|
|
900
|
+
<footer className="flex flex-wrap items-center justify-between gap-3 pt-6 border-t border-slate-900 text-xs text-slate-500">
|
|
901
|
+
<div>
|
|
902
|
+
{completedCount} of {totalCount} completed ({progressPercent}%)
|
|
903
|
+
</div>
|
|
904
|
+
<div className="flex items-center space-x-4">
|
|
905
|
+
<button
|
|
906
|
+
onClick={markAllCompleted}
|
|
907
|
+
className="hover:text-slate-300 transition-colors inline-flex items-center space-x-1"
|
|
908
|
+
>
|
|
909
|
+
<CheckCheck className="w-3.5 h-3.5" />
|
|
910
|
+
<span>Mark All Complete</span>
|
|
911
|
+
</button>
|
|
912
|
+
<button
|
|
913
|
+
onClick={clearCompleted}
|
|
914
|
+
className="hover:text-rose-400 transition-colors"
|
|
915
|
+
>
|
|
916
|
+
Clear Completed
|
|
917
|
+
</button>
|
|
918
|
+
</div>
|
|
919
|
+
</footer>
|
|
920
|
+
)}
|
|
921
|
+
</div>
|
|
922
|
+
</main>
|
|
923
|
+
);
|
|
924
|
+
}`,
|
|
925
|
+
};
|
|
926
|
+
const written = [];
|
|
927
|
+
for (const [relPath, content] of Object.entries(files)) {
|
|
928
|
+
const fullPath = path.resolve(targetDir, relPath);
|
|
929
|
+
const dir = path.dirname(fullPath);
|
|
930
|
+
if (!fs.existsSync(dir)) {
|
|
931
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
932
|
+
}
|
|
933
|
+
fs.writeFileSync(fullPath, content, 'utf-8');
|
|
934
|
+
written.push(path.relative(workingDir, fullPath).replace(/\\/g, '/'));
|
|
935
|
+
}
|
|
936
|
+
return written;
|
|
937
|
+
}
|
|
487
938
|
static materializeNextJsPortfolio(workingDir) {
|
|
488
939
|
// If workingDir already contains an existing project package.json (e.g. antri_cli), place in ./portfolio subfolder
|
|
489
940
|
let targetDir = workingDir;
|
|
@@ -1218,31 +1669,55 @@ export default function Home() {
|
|
|
1218
1669
|
};
|
|
1219
1670
|
}
|
|
1220
1671
|
const fileContent = fs.readFileSync(resolvedPath, 'utf-8');
|
|
1221
|
-
if (
|
|
1672
|
+
if (fileContent.includes(targetContent)) {
|
|
1673
|
+
const count = fileContent.split(targetContent).length - 1;
|
|
1674
|
+
if (count > 1 && !allowMultiple) {
|
|
1675
|
+
return {
|
|
1676
|
+
tool_call_id: toolCallId,
|
|
1677
|
+
name: toolName,
|
|
1678
|
+
output: `Error: target_content appears ${count} times in ${filePath}. Provide more surrounding context to match a unique block or set allow_multiple: true.`,
|
|
1679
|
+
error: true,
|
|
1680
|
+
};
|
|
1681
|
+
}
|
|
1682
|
+
const newContent = allowMultiple
|
|
1683
|
+
? fileContent.replaceAll(targetContent, replacementContent)
|
|
1684
|
+
: fileContent.replace(targetContent, replacementContent);
|
|
1685
|
+
fs.writeFileSync(resolvedPath, newContent, 'utf-8');
|
|
1222
1686
|
return {
|
|
1223
1687
|
tool_call_id: toolCallId,
|
|
1224
1688
|
name: toolName,
|
|
1225
|
-
output: `
|
|
1226
|
-
error: true,
|
|
1689
|
+
output: `Successfully edited ${filePath} (replaced ${count} occurrence${count > 1 ? 's' : ''}).`,
|
|
1227
1690
|
};
|
|
1228
1691
|
}
|
|
1229
|
-
|
|
1230
|
-
|
|
1692
|
+
// Fallback: try normalizing CRLF/LF line endings
|
|
1693
|
+
const normalizedFile = fileContent.replace(/\r\n/g, '\n');
|
|
1694
|
+
const normalizedTarget = targetContent.replace(/\r\n/g, '\n');
|
|
1695
|
+
if (normalizedFile.includes(normalizedTarget)) {
|
|
1696
|
+
const count = normalizedFile.split(normalizedTarget).length - 1;
|
|
1697
|
+
if (count > 1 && !allowMultiple) {
|
|
1698
|
+
return {
|
|
1699
|
+
tool_call_id: toolCallId,
|
|
1700
|
+
name: toolName,
|
|
1701
|
+
output: `Error: target_content appears ${count} times in ${filePath}. Provide more surrounding context to match a unique block or set allow_multiple: true.`,
|
|
1702
|
+
error: true,
|
|
1703
|
+
};
|
|
1704
|
+
}
|
|
1705
|
+
const normalizedReplacement = replacementContent.replace(/\r\n/g, '\n');
|
|
1706
|
+
const newContent = allowMultiple
|
|
1707
|
+
? normalizedFile.replaceAll(normalizedTarget, normalizedReplacement)
|
|
1708
|
+
: normalizedFile.replace(normalizedTarget, normalizedReplacement);
|
|
1709
|
+
fs.writeFileSync(resolvedPath, newContent, 'utf-8');
|
|
1231
1710
|
return {
|
|
1232
1711
|
tool_call_id: toolCallId,
|
|
1233
1712
|
name: toolName,
|
|
1234
|
-
output: `
|
|
1235
|
-
error: true,
|
|
1713
|
+
output: `Successfully edited ${filePath} (replaced ${count} occurrence${count > 1 ? 's' : ''}).`,
|
|
1236
1714
|
};
|
|
1237
1715
|
}
|
|
1238
|
-
const newContent = allowMultiple
|
|
1239
|
-
? fileContent.replaceAll(targetContent, replacementContent)
|
|
1240
|
-
: fileContent.replace(targetContent, replacementContent);
|
|
1241
|
-
fs.writeFileSync(resolvedPath, newContent, 'utf-8');
|
|
1242
1716
|
return {
|
|
1243
1717
|
tool_call_id: toolCallId,
|
|
1244
1718
|
name: toolName,
|
|
1245
|
-
output: `
|
|
1719
|
+
output: `Error: target_content not found in ${filePath}. Please inspect the file with read_file first to ensure exact character and whitespace match.`,
|
|
1720
|
+
error: true,
|
|
1246
1721
|
};
|
|
1247
1722
|
}
|
|
1248
1723
|
case 'create_directory': {
|
|
@@ -1686,24 +2161,19 @@ export default function Home() {
|
|
|
1686
2161
|
case 'create_artifact': {
|
|
1687
2162
|
const title = (args.title || '').toLowerCase();
|
|
1688
2163
|
const content = (args.content || '').toLowerCase();
|
|
1689
|
-
const
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
title.includes('react') ||
|
|
1693
|
-
title.includes('app') ||
|
|
1694
|
-
title.includes('cli') ||
|
|
1695
|
-
content.includes('next.js') ||
|
|
1696
|
-
content.includes('react');
|
|
2164
|
+
const isTodoList = title.includes('todo') || title.includes('to-do') || title.includes('task') || content.includes('todo') || content.includes('to-do') || content.includes('task');
|
|
2165
|
+
const isPortfolio = title.includes('portfolio') || title.includes('resume') || title.includes('personal') || content.includes('portfolio') || content.includes('welcome to my portfolio');
|
|
2166
|
+
const isNextJsOrWeb = isTodoList || isPortfolio || title.includes('website') || title.includes('next') || title.includes('react') || title.includes('app') || content.includes('next.js') || content.includes('react');
|
|
1697
2167
|
if (isNextJsOrWeb) {
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
2168
|
+
let createdFiles = [];
|
|
2169
|
+
let appName = 'Next.js Web Application';
|
|
2170
|
+
if (isTodoList) {
|
|
2171
|
+
createdFiles = ToolExecutor.materializeNextJsTodoList(this.workingDir);
|
|
2172
|
+
appName = 'Next.js To-Do List Application';
|
|
2173
|
+
}
|
|
2174
|
+
else {
|
|
2175
|
+
createdFiles = ToolExecutor.materializeNextJsPortfolio(this.workingDir);
|
|
2176
|
+
appName = 'Next.js Portfolio Website';
|
|
1707
2177
|
}
|
|
1708
2178
|
const { artifactManager } = await import('./artifactManager.js');
|
|
1709
2179
|
const id = 'art_' + Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
|
|
@@ -1711,17 +2181,17 @@ export default function Home() {
|
|
|
1711
2181
|
id,
|
|
1712
2182
|
sessionId: 'cli_session',
|
|
1713
2183
|
sessionTitle: 'Workspace Chat',
|
|
1714
|
-
title: args.title ||
|
|
2184
|
+
title: args.title || appName,
|
|
1715
2185
|
type: 'html',
|
|
1716
|
-
content: args.content || '<!DOCTYPE html><html><body><h1>
|
|
2186
|
+
content: args.content || '<!DOCTYPE html><html><body><h1>App Created</h1></body></html>',
|
|
1717
2187
|
createdAt: Date.now(),
|
|
1718
2188
|
});
|
|
1719
2189
|
return {
|
|
1720
2190
|
tool_call_id: toolCallId,
|
|
1721
2191
|
name: toolName,
|
|
1722
|
-
output: `✨ Successfully materialized complete
|
|
2192
|
+
output: `✨ Successfully materialized complete ${appName} into workspace (${createdFiles.length} files):\n` +
|
|
1723
2193
|
createdFiles.map((f) => ` - ${f}`).join('\n') +
|
|
1724
|
-
`\n\n🚀 To
|
|
2194
|
+
`\n\n🚀 To launch your application:\n 1. npm install\n 2. npm run dev\n 3. Open http://localhost:3000 in your browser!`,
|
|
1725
2195
|
};
|
|
1726
2196
|
}
|
|
1727
2197
|
const { artifactManager } = await import('./artifactManager.js');
|