@optima-chat/optima-agent 0.8.91 → 0.8.93
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/.claude/skills/browser/SKILL.md +8 -0
- package/.claude/skills/homepage/SKILL.md +4 -3
- package/.claude/skills/kol-outreach/SKILL.md +371 -0
- package/.claude/skills/kol-outreach/template/campaign/CONFIG.md +60 -0
- package/.claude/skills/kol-outreach/template/campaign/CONVERSATIONS/.gitkeep +0 -0
- package/.claude/skills/kol-outreach/template/campaign/KOLS.md +6 -0
- package/.claude/skills/kol-outreach/template/campaign/PROGRESS.md +3 -0
- package/.claude/skills/kol-outreach/template/campaign/TEMPLATES.md +88 -0
- package/.claude/skills/kol-outreach/template/campaign/assets/.gitkeep +0 -0
- package/.claude/skills/kol-outreach/template/merchant/BRAND.md +36 -0
- package/.claude/skills/kol-outreach/template/merchant/CAMPAIGNS.md +6 -0
- package/.claude/skills/kol-outreach/template/merchant/MERCHANT_LIMITS.md +16 -0
- package/.claude/skills/kol-outreach/template/merchant/PROGRESS.md +4 -0
- package/.claude/skills/kol-outreach/template/merchant/README.md +20 -0
- package/.claude/skills/video-clone/SKILL.md +125 -217
- package/.claude/skills/video-clone/assets/phase-state-template.json +11 -0
- package/.claude/skills/video-clone/references/ffmpeg-commands.md +31 -34
- package/.claude/skills/video-clone/references/gate-enforcement.md +144 -0
- package/.claude/skills/video-clone/references/kling-api.md +75 -75
- package/.claude/skills/video-clone/references/url-parsing.md +32 -13
- package/.claude/skills/video-clone/scripts/_confirm.py +96 -0
- package/.claude/skills/video-clone/scripts/_confirm_test.py +125 -0
- package/.claude/skills/video-clone/scripts/_gate.py +162 -0
- package/.claude/skills/video-clone/scripts/_gate_e2e_test.py +226 -0
- package/.claude/skills/video-clone/scripts/_gate_test.py +148 -0
- package/.claude/skills/video-clone/scripts/_project.py +56 -0
- package/.claude/skills/video-clone/scripts/analyze_source.py +113 -0
- package/.claude/skills/video-clone/scripts/analyze_source_test.py +52 -0
- package/.claude/skills/video-clone/scripts/assemble.py +106 -0
- package/.claude/skills/video-clone/scripts/confirm.py +12 -0
- package/.claude/skills/video-clone/scripts/edit_first_frame.py +66 -0
- package/.claude/skills/video-clone/scripts/extract_frames.py +108 -0
- package/.claude/skills/video-clone/scripts/gen_video.py +59 -0
- package/.claude/skills/video-clone/scripts/init_project.py +103 -0
- package/.claude/skills/video-clone/scripts/init_project_test.py +106 -0
- package/.claude/skills/video-clone/scripts/kling_generate.py +262 -0
- package/.claude/skills/video-clone/scripts/kling_generate_test.py +191 -0
- package/.claude/skills/video-clone/scripts/preflight.py +102 -0
- package/.claude/skills/video-clone/scripts/preview.py +208 -0
- package/.claude/skills/video-clone/scripts/preview_test.py +169 -0
- package/.claude/skills/video-clone/scripts/save_workflow.py +129 -0
- package/.claude/skills/video-clone/scripts/save_workflow_test.py +106 -0
- package/.claude/skills/video-clone/scripts/status.py +202 -0
- package/.claude/skills/video-clone/scripts/status_test.py +174 -0
- package/package.json +2 -1
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""Cross-session resume helper: show project status and next action.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
python scripts/status.py --project <name>
|
|
5
|
+
|
|
6
|
+
Reads phase.json and scans the project directory to determine what has been
|
|
7
|
+
done and what the next step is. Exits 0 always (read-only, no side effects).
|
|
8
|
+
|
|
9
|
+
Decision tree (top to bottom, first match wins):
|
|
10
|
+
1. preview_confirmed gate set → show "ready to generate / already generated"
|
|
11
|
+
2. preview_v*.md exists → show "run confirm.py"
|
|
12
|
+
3. prompt.md non-empty AND
|
|
13
|
+
frame_v*.png exists (clone) → show "run preview.py"
|
|
14
|
+
4. analysis_v*.json exists → show "edit first frame / write prompt.md"
|
|
15
|
+
5. (nothing) → show "run analyze_source.py"
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import argparse
|
|
20
|
+
import json
|
|
21
|
+
import sys
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
from _project import resolve_project
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _latest(dir_path: Path, prefix: str, suffix: str) -> Path | None:
|
|
28
|
+
if not dir_path.is_dir():
|
|
29
|
+
return None
|
|
30
|
+
candidates = sorted(
|
|
31
|
+
dir_path.glob(f"{prefix}v*{suffix}"),
|
|
32
|
+
key=lambda p: int(p.stem.removeprefix(prefix).lstrip("v") or "0"),
|
|
33
|
+
)
|
|
34
|
+
return candidates[-1] if candidates else None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _latest_extract_dir(frames_dir: Path) -> Path | None:
|
|
38
|
+
if not frames_dir.is_dir():
|
|
39
|
+
return None
|
|
40
|
+
extracts = sorted(
|
|
41
|
+
[p for p in frames_dir.iterdir() if p.is_dir() and p.name.startswith("extract_v")],
|
|
42
|
+
key=lambda p: int(p.name.removeprefix("extract_v") or "0"),
|
|
43
|
+
)
|
|
44
|
+
return extracts[-1] if extracts else None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _gate_status(state: dict) -> bool:
|
|
48
|
+
return bool(
|
|
49
|
+
state.get("gates", {})
|
|
50
|
+
.get("preview_confirmed", {})
|
|
51
|
+
.get("status", False)
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _section(title: str, lines: list[str]) -> str:
|
|
56
|
+
body = "\n".join(f" {l}" for l in lines)
|
|
57
|
+
return f"{'='*60}\n{title}\n{'='*60}\n{body}\n"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def main() -> int:
|
|
61
|
+
ap = argparse.ArgumentParser(description="Show project status and next action.")
|
|
62
|
+
ap.add_argument("--project", required=True)
|
|
63
|
+
args = ap.parse_args()
|
|
64
|
+
|
|
65
|
+
project_dir = resolve_project(args.project)
|
|
66
|
+
state_path = project_dir / ".state" / "phase.json"
|
|
67
|
+
if not state_path.is_file():
|
|
68
|
+
print(f"ERROR: phase.json not found at {state_path}", file=sys.stderr)
|
|
69
|
+
return 1
|
|
70
|
+
state = json.loads(state_path.read_text(encoding="utf-8"))
|
|
71
|
+
task_type = state.get("task_type", "video_clone")
|
|
72
|
+
|
|
73
|
+
# --- Collect artifact presence ---
|
|
74
|
+
analysis_path = _latest(project_dir / "source", "analysis_", ".json")
|
|
75
|
+
extract_dir = _latest_extract_dir(project_dir / "frames")
|
|
76
|
+
grid_path = (
|
|
77
|
+
(extract_dir / "grid.jpg")
|
|
78
|
+
if extract_dir and (extract_dir / "grid.jpg").exists()
|
|
79
|
+
else None
|
|
80
|
+
)
|
|
81
|
+
prompt_path = project_dir / "prompt.md"
|
|
82
|
+
prompt_text = (
|
|
83
|
+
prompt_path.read_text(encoding="utf-8").strip()
|
|
84
|
+
if prompt_path.is_file()
|
|
85
|
+
else None
|
|
86
|
+
)
|
|
87
|
+
frame_path = _latest(project_dir / "frames", "frame_", ".png")
|
|
88
|
+
preview_path = _latest(project_dir, "preview_", ".md")
|
|
89
|
+
cost_path = project_dir / "cost.json"
|
|
90
|
+
videos = sorted((project_dir / "videos").glob("*.mp4"))
|
|
91
|
+
final_videos = [v for v in videos if v.name.startswith("final_")]
|
|
92
|
+
gate_set = _gate_status(state)
|
|
93
|
+
|
|
94
|
+
# --- Build checklist ---
|
|
95
|
+
def tick(condition: bool) -> str:
|
|
96
|
+
return "[x]" if condition else "[ ]"
|
|
97
|
+
|
|
98
|
+
checklist = []
|
|
99
|
+
if task_type == "video_clone":
|
|
100
|
+
checklist.append(f"{tick(analysis_path is not None)} source/analysis_v*.json")
|
|
101
|
+
checklist.append(f"{tick(grid_path is not None)} frames/extract_v*/grid.jpg")
|
|
102
|
+
checklist.append(f"{tick(bool(prompt_text))} prompt.md (non-empty)")
|
|
103
|
+
checklist.append(f"{tick(frame_path is not None)} frames/frame_v*.png")
|
|
104
|
+
else:
|
|
105
|
+
checklist.append(f"{tick(bool(prompt_text))} prompt.md (non-empty)")
|
|
106
|
+
checklist.append(f"{tick(cost_path.is_file())} cost.json (optional)")
|
|
107
|
+
checklist.append(f"{tick(preview_path is not None)} preview_v*.md")
|
|
108
|
+
checklist.append(f"{tick(gate_set)} preview_confirmed gate")
|
|
109
|
+
checklist.append(f"{tick(bool(final_videos))} videos/final_v*.mp4")
|
|
110
|
+
|
|
111
|
+
# --- Decision tree: next action ---
|
|
112
|
+
if gate_set and final_videos:
|
|
113
|
+
next_action = [
|
|
114
|
+
"Project complete.",
|
|
115
|
+
f"Final video: {final_videos[-1].name}",
|
|
116
|
+
"",
|
|
117
|
+
"Optional: capture as workflow:",
|
|
118
|
+
f' python scripts/save_workflow.py --project {args.project} \\',
|
|
119
|
+
' --name <slug> --scene "<scene>" --rating 5 --strategy "<strategy>"',
|
|
120
|
+
]
|
|
121
|
+
elif gate_set and not final_videos:
|
|
122
|
+
next_action = [
|
|
123
|
+
"Gate confirmed — ready to generate video.",
|
|
124
|
+
"",
|
|
125
|
+
"Run one of:",
|
|
126
|
+
f" python scripts/kling_generate.py --project {args.project} --frame frames/frame_v*.png",
|
|
127
|
+
f" python scripts/gen_video.py --project {args.project} --frame frames/frame_v*.png",
|
|
128
|
+
]
|
|
129
|
+
elif preview_path is not None:
|
|
130
|
+
next_action = [
|
|
131
|
+
f"Preview ready: {preview_path.name}",
|
|
132
|
+
"",
|
|
133
|
+
"Review it, then confirm:",
|
|
134
|
+
f' python scripts/confirm.py --project {args.project} --quote "<your words>"',
|
|
135
|
+
]
|
|
136
|
+
elif (
|
|
137
|
+
task_type == "video_gen"
|
|
138
|
+
and bool(prompt_text)
|
|
139
|
+
) or (
|
|
140
|
+
task_type == "video_clone"
|
|
141
|
+
and bool(prompt_text)
|
|
142
|
+
and frame_path is not None
|
|
143
|
+
and analysis_path is not None
|
|
144
|
+
and grid_path is not None
|
|
145
|
+
):
|
|
146
|
+
next_action = [
|
|
147
|
+
"All prep artifacts present — run preview:",
|
|
148
|
+
f" python scripts/preview.py --project {args.project}",
|
|
149
|
+
]
|
|
150
|
+
elif bool(prompt_text) and task_type == "video_clone":
|
|
151
|
+
# Have prompt but missing frame or grid
|
|
152
|
+
missing_parts = []
|
|
153
|
+
if frame_path is None:
|
|
154
|
+
missing_parts.append("frames/frame_v*.png (edit_first_frame.py)")
|
|
155
|
+
if grid_path is None:
|
|
156
|
+
missing_parts.append("frames/extract_v*/grid.jpg (extract_frames.py)")
|
|
157
|
+
next_action = ["Still needed:"] + missing_parts
|
|
158
|
+
elif analysis_path is not None:
|
|
159
|
+
next_action = [
|
|
160
|
+
"Analysis done — continue prep:",
|
|
161
|
+
f" python scripts/extract_frames.py --project {args.project} --source source/*.mp4",
|
|
162
|
+
f" # then edit first frame and write prompt.md",
|
|
163
|
+
]
|
|
164
|
+
else:
|
|
165
|
+
if task_type == "video_clone":
|
|
166
|
+
next_action = [
|
|
167
|
+
"Start here:",
|
|
168
|
+
f" python scripts/analyze_source.py --project {args.project} --source <video.mp4>",
|
|
169
|
+
]
|
|
170
|
+
else:
|
|
171
|
+
next_action = [
|
|
172
|
+
"Start here — write prompt.md:",
|
|
173
|
+
f" {project_dir / 'prompt.md'}",
|
|
174
|
+
]
|
|
175
|
+
|
|
176
|
+
# --- Log tail ---
|
|
177
|
+
log_path = project_dir / "log.md"
|
|
178
|
+
log_lines: list[str] = []
|
|
179
|
+
if log_path.is_file():
|
|
180
|
+
all_lines = log_path.read_text(encoding="utf-8").splitlines()
|
|
181
|
+
# skip header, take last 10 non-empty
|
|
182
|
+
entries = [l for l in all_lines if l.strip() and not l.startswith("#")]
|
|
183
|
+
log_lines = entries[-10:]
|
|
184
|
+
|
|
185
|
+
# --- Render ---
|
|
186
|
+
sections = [
|
|
187
|
+
_section(
|
|
188
|
+
f"Project: {args.project} [{task_type}]",
|
|
189
|
+
[f"Dir: {project_dir}"],
|
|
190
|
+
),
|
|
191
|
+
_section("Prep checklist", checklist),
|
|
192
|
+
_section("Next action", next_action),
|
|
193
|
+
]
|
|
194
|
+
if log_lines:
|
|
195
|
+
sections.append(_section("Recent log (last 10)", log_lines))
|
|
196
|
+
|
|
197
|
+
print("\n".join(sections))
|
|
198
|
+
return 0
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
if __name__ == "__main__":
|
|
202
|
+
sys.exit(main())
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""Tests for status.py — cross-session resume helper."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
import tempfile
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
SCRIPTS_DIR = Path(__file__).parent
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _make_project(tmp: Path, task_type: str = "video_clone", gate: bool = False) -> Path:
|
|
15
|
+
proj = tmp / "video-clone" / "test-proj"
|
|
16
|
+
for sub in ("source", "frames", "videos", ".state"):
|
|
17
|
+
(proj / sub).mkdir(parents=True)
|
|
18
|
+
state = {
|
|
19
|
+
"schema_version": 1,
|
|
20
|
+
"project": "test-proj",
|
|
21
|
+
"task_type": task_type,
|
|
22
|
+
"created_at": "2025-01-01T00:00:00Z",
|
|
23
|
+
"current_phase": 0,
|
|
24
|
+
"gates": {
|
|
25
|
+
"preview_confirmed": {
|
|
26
|
+
"status": gate,
|
|
27
|
+
"confirmed_at": "2025-01-01T00:01:00Z" if gate else None,
|
|
28
|
+
"user_quote": "go" if gate else None,
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"history": [],
|
|
32
|
+
}
|
|
33
|
+
(proj / ".state" / "phase.json").write_text(
|
|
34
|
+
json.dumps(state, indent=2), encoding="utf-8"
|
|
35
|
+
)
|
|
36
|
+
return proj
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _run(tmp: Path) -> subprocess.CompletedProcess:
|
|
40
|
+
env = os.environ.copy()
|
|
41
|
+
env["GEN_OUTPUT_ROOT"] = str(tmp)
|
|
42
|
+
return subprocess.run(
|
|
43
|
+
[sys.executable, str(SCRIPTS_DIR / "status.py"), "--project", "test-proj"],
|
|
44
|
+
capture_output=True,
|
|
45
|
+
text=True,
|
|
46
|
+
env=env,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# Helpers to add artifacts
|
|
51
|
+
def _add_analysis(proj: Path):
|
|
52
|
+
(proj / "source" / "analysis_v1.json").write_text(
|
|
53
|
+
json.dumps({"duration_s": 10, "width": 720, "height": 1280,
|
|
54
|
+
"fps": 30.0, "has_audio": False, "segments": 1,
|
|
55
|
+
"classification": "single", "scene_cuts": []}),
|
|
56
|
+
encoding="utf-8",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
def _add_grid(proj: Path):
|
|
60
|
+
extract = proj / "frames" / "extract_v1"
|
|
61
|
+
extract.mkdir(parents=True, exist_ok=True)
|
|
62
|
+
(extract / "grid.jpg").write_bytes(b"dummy")
|
|
63
|
+
|
|
64
|
+
def _add_prompt(proj: Path):
|
|
65
|
+
(proj / "prompt.md").write_text("A person walking.", encoding="utf-8")
|
|
66
|
+
|
|
67
|
+
def _add_frame(proj: Path):
|
|
68
|
+
(proj / "frames" / "frame_v1.png").write_bytes(b"dummy")
|
|
69
|
+
|
|
70
|
+
def _add_preview(proj: Path):
|
|
71
|
+
(proj / "preview_v1.md").write_text("# Preview: test-proj\n", encoding="utf-8")
|
|
72
|
+
|
|
73
|
+
def _add_final_video(proj: Path):
|
|
74
|
+
(proj / "videos" / "final_v1.mp4").write_bytes(b"dummy")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_status_exits_zero():
|
|
78
|
+
with tempfile.TemporaryDirectory() as td:
|
|
79
|
+
tmp = Path(td)
|
|
80
|
+
_make_project(tmp)
|
|
81
|
+
result = _run(tmp)
|
|
82
|
+
assert result.returncode == 0, f"Expected 0\nstderr: {result.stderr}"
|
|
83
|
+
print("PASS test_status_exits_zero")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_status_no_artifacts_suggests_analyze_source():
|
|
87
|
+
with tempfile.TemporaryDirectory() as td:
|
|
88
|
+
tmp = Path(td)
|
|
89
|
+
_make_project(tmp)
|
|
90
|
+
result = _run(tmp)
|
|
91
|
+
assert "analyze_source" in result.stdout
|
|
92
|
+
print("PASS test_status_no_artifacts_suggests_analyze_source")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_status_analysis_done_suggests_extract():
|
|
96
|
+
with tempfile.TemporaryDirectory() as td:
|
|
97
|
+
tmp = Path(td)
|
|
98
|
+
proj = _make_project(tmp)
|
|
99
|
+
_add_analysis(proj)
|
|
100
|
+
result = _run(tmp)
|
|
101
|
+
assert "extract_frames" in result.stdout
|
|
102
|
+
print("PASS test_status_analysis_done_suggests_extract")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def test_status_all_prep_done_suggests_preview():
|
|
106
|
+
with tempfile.TemporaryDirectory() as td:
|
|
107
|
+
tmp = Path(td)
|
|
108
|
+
proj = _make_project(tmp)
|
|
109
|
+
_add_analysis(proj)
|
|
110
|
+
_add_grid(proj)
|
|
111
|
+
_add_prompt(proj)
|
|
112
|
+
_add_frame(proj)
|
|
113
|
+
result = _run(tmp)
|
|
114
|
+
assert "preview.py" in result.stdout
|
|
115
|
+
print("PASS test_status_all_prep_done_suggests_preview")
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_status_preview_exists_suggests_confirm():
|
|
119
|
+
with tempfile.TemporaryDirectory() as td:
|
|
120
|
+
tmp = Path(td)
|
|
121
|
+
proj = _make_project(tmp)
|
|
122
|
+
_add_analysis(proj)
|
|
123
|
+
_add_grid(proj)
|
|
124
|
+
_add_prompt(proj)
|
|
125
|
+
_add_frame(proj)
|
|
126
|
+
_add_preview(proj)
|
|
127
|
+
result = _run(tmp)
|
|
128
|
+
assert "confirm.py" in result.stdout
|
|
129
|
+
print("PASS test_status_preview_exists_suggests_confirm")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def test_status_gate_confirmed_suggests_generate():
|
|
133
|
+
with tempfile.TemporaryDirectory() as td:
|
|
134
|
+
tmp = Path(td)
|
|
135
|
+
proj = _make_project(tmp, gate=True)
|
|
136
|
+
result = _run(tmp)
|
|
137
|
+
assert "kling_generate" in result.stdout or "gen_video" in result.stdout
|
|
138
|
+
print("PASS test_status_gate_confirmed_suggests_generate")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def test_status_gate_and_final_video_shows_complete():
|
|
142
|
+
with tempfile.TemporaryDirectory() as td:
|
|
143
|
+
tmp = Path(td)
|
|
144
|
+
proj = _make_project(tmp, gate=True)
|
|
145
|
+
_add_final_video(proj)
|
|
146
|
+
result = _run(tmp)
|
|
147
|
+
assert "complete" in result.stdout.lower() or "final_v1.mp4" in result.stdout
|
|
148
|
+
print("PASS test_status_gate_and_final_video_shows_complete")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def test_status_checklist_marks_present_artifacts():
|
|
152
|
+
with tempfile.TemporaryDirectory() as td:
|
|
153
|
+
tmp = Path(td)
|
|
154
|
+
proj = _make_project(tmp)
|
|
155
|
+
_add_analysis(proj)
|
|
156
|
+
# grid, prompt, frame missing
|
|
157
|
+
result = _run(tmp)
|
|
158
|
+
stdout = result.stdout
|
|
159
|
+
# analysis present → [x], others absent → [ ]
|
|
160
|
+
assert "[x]" in stdout
|
|
161
|
+
assert "[ ]" in stdout
|
|
162
|
+
print("PASS test_status_checklist_marks_present_artifacts")
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
if __name__ == "__main__":
|
|
166
|
+
test_status_exits_zero()
|
|
167
|
+
test_status_no_artifacts_suggests_analyze_source()
|
|
168
|
+
test_status_analysis_done_suggests_extract()
|
|
169
|
+
test_status_all_prep_done_suggests_preview()
|
|
170
|
+
test_status_preview_exists_suggests_confirm()
|
|
171
|
+
test_status_gate_confirmed_suggests_generate()
|
|
172
|
+
test_status_gate_and_final_video_shows_complete()
|
|
173
|
+
test_status_checklist_marks_present_artifacts()
|
|
174
|
+
print("\nAll 8 status tests passed.")
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@optima-chat/optima-agent",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.93",
|
|
4
4
|
"description": "基于 Claude Agent SDK 的电商运营 AI 助手",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -63,6 +63,7 @@
|
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@types/node": "^22.10.1",
|
|
65
65
|
"@types/ws": "^8.18.1",
|
|
66
|
+
"execa": "^9.6.1",
|
|
66
67
|
"tsx": "^4.19.2",
|
|
67
68
|
"typescript": "^5.7.2",
|
|
68
69
|
"vitest": "^4.0.14"
|