lightcone-cli 0.2.0__py3-none-any.whl
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.
- lightcone/cli/__init__.py +16 -0
- lightcone/cli/claude/lightcone/agents/lc-extractor.md +114 -0
- lightcone/cli/claude/lightcone/guides/astra-reference.md +290 -0
- lightcone/cli/claude/lightcone/guides/lightcone-cli-reference.md +75 -0
- lightcone/cli/claude/lightcone/guides/ui-brand.md +86 -0
- lightcone/cli/claude/lightcone/hooks/langfuse_git_commit_hook.py +303 -0
- lightcone/cli/claude/lightcone/hooks/langfuse_hook.py +894 -0
- lightcone/cli/claude/lightcone/hooks/langfuse_prepare_commit_msg.py +142 -0
- lightcone/cli/claude/lightcone/hooks/langfuse_session_init_hook.py +83 -0
- lightcone/cli/claude/lightcone/hooks/langfuse_utils.py +457 -0
- lightcone/cli/claude/lightcone/scripts/activate-venv.sh +44 -0
- lightcone/cli/claude/lightcone/scripts/check-lc-run.sh +140 -0
- lightcone/cli/claude/lightcone/scripts/session-start.sh +140 -0
- lightcone/cli/claude/lightcone/scripts/validate-on-save.sh +77 -0
- lightcone/cli/claude/lightcone/skills/lc-build/SKILL.md +92 -0
- lightcone/cli/claude/lightcone/skills/lc-build/assets/loop-prompt.md +92 -0
- lightcone/cli/claude/lightcone/skills/lc-build/scripts/setup-lc-build.sh +240 -0
- lightcone/cli/claude/lightcone/skills/lc-feedback/SKILL.md +94 -0
- lightcone/cli/claude/lightcone/skills/lc-migrate/SKILL.md +98 -0
- lightcone/cli/claude/lightcone/skills/lc-new/SKILL.md +183 -0
- lightcone/cli/claude/lightcone/skills/lc-verify/SKILL.md +53 -0
- lightcone/cli/claude/lightcone/templates/CLAUDE.md +32 -0
- lightcone/cli/commands.py +2327 -0
- lightcone/cli/plugin.py +34 -0
- lightcone/engine/__init__.py +42 -0
- lightcone/engine/assets.py +418 -0
- lightcone/engine/container.py +370 -0
- lightcone/engine/io_manager.py +27 -0
- lightcone/engine/runner.py +1017 -0
- lightcone/engine/site_registry.py +142 -0
- lightcone/engine/status.py +135 -0
- lightcone/engine/targets.py +68 -0
- lightcone/engine/tree.py +245 -0
- lightcone/eval/__init__.py +25 -0
- lightcone/eval/build.py +148 -0
- lightcone/eval/cli.py +176 -0
- lightcone/eval/graders.py +192 -0
- lightcone/eval/harness.py +265 -0
- lightcone/eval/models.py +117 -0
- lightcone/eval/report.py +214 -0
- lightcone/eval/sandbox.py +394 -0
- lightcone_cli-0.2.0.dist-info/METADATA +16 -0
- lightcone_cli-0.2.0.dist-info/RECORD +46 -0
- lightcone_cli-0.2.0.dist-info/WHEEL +4 -0
- lightcone_cli-0.2.0.dist-info/entry_points.txt +2 -0
- lightcone_cli-0.2.0.dist-info/licenses/LICENSE +29 -0
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
"""Daytona sandbox lifecycle management for eval trials."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
import shlex
|
|
9
|
+
import time
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
BUILD_COMPLETE_MARKER = "BUILD_COMPLETE"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class ExecuteResult:
|
|
21
|
+
"""Result from running a command in the sandbox."""
|
|
22
|
+
|
|
23
|
+
exit_code: int
|
|
24
|
+
output: str
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class ClaudeResult:
|
|
29
|
+
"""Parsed result from a claude -p invocation."""
|
|
30
|
+
|
|
31
|
+
cost_usd: float = 0.0
|
|
32
|
+
num_turns: int = 0
|
|
33
|
+
duration_ms: int = 0
|
|
34
|
+
result_text: str = ""
|
|
35
|
+
is_error: bool = False
|
|
36
|
+
raw_jsonl: str = ""
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class EvalSandbox:
|
|
41
|
+
"""Manages an ephemeral Daytona sandbox for one eval trial.
|
|
42
|
+
|
|
43
|
+
Wraps daytona_sdk.Daytona to provide create/setup/exec/teardown lifecycle.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
WORK_DIR = "/home/evaluser/project"
|
|
47
|
+
|
|
48
|
+
task_id: str = ""
|
|
49
|
+
trial_id: str = ""
|
|
50
|
+
sandbox_image: str | Any = None # str for pre-built, Image for dynamic, None for default
|
|
51
|
+
env_vars: dict[str, str] = field(default_factory=dict)
|
|
52
|
+
|
|
53
|
+
_daytona: Any = field(default=None, repr=False)
|
|
54
|
+
_sandbox: Any = field(default=None, repr=False)
|
|
55
|
+
|
|
56
|
+
def create(self) -> None:
|
|
57
|
+
"""Create an ephemeral Daytona sandbox.
|
|
58
|
+
|
|
59
|
+
Uses Daytona's Image API for the sandbox environment:
|
|
60
|
+
- None (default): Image.debian_slim("3.12") with git and Claude Code pre-installed
|
|
61
|
+
- str: Pre-built Docker image reference (e.g. "python:3.12-slim")
|
|
62
|
+
- Image: A daytona_sdk.Image object for custom dynamic builds
|
|
63
|
+
"""
|
|
64
|
+
from daytona_sdk import (
|
|
65
|
+
CreateSandboxFromImageParams,
|
|
66
|
+
Daytona,
|
|
67
|
+
Image,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
self._daytona = Daytona()
|
|
71
|
+
|
|
72
|
+
# Build the sandbox image
|
|
73
|
+
if self.sandbox_image is None:
|
|
74
|
+
# Direct deps from astra + lightcone-cli pyproject.toml (pip resolves transitive)
|
|
75
|
+
deps = (
|
|
76
|
+
"click httpx jsonschema pydantic pypdf pyyaml rapidfuzz rich"
|
|
77
|
+
" dagster dagster-webserver dagster-docker langfuse"
|
|
78
|
+
)
|
|
79
|
+
image = (
|
|
80
|
+
Image.debian_slim("3.12")
|
|
81
|
+
.run_commands(
|
|
82
|
+
# System deps + non-root user (as root)
|
|
83
|
+
"apt-get update && apt-get install -y git curl bash"
|
|
84
|
+
" && rm -rf /var/lib/apt/lists/*",
|
|
85
|
+
"useradd -m -s /bin/bash evaluser",
|
|
86
|
+
# Install Claude Code globally (as root)
|
|
87
|
+
"curl -fsSL https://claude.ai/install.sh | bash"
|
|
88
|
+
" && cp /root/.local/bin/claude /usr/local/bin/claude",
|
|
89
|
+
# Pre-install all third-party Python deps (as root → system-wide)
|
|
90
|
+
f"pip install --no-cache-dir {deps}",
|
|
91
|
+
)
|
|
92
|
+
# Switch to non-root user for runtime
|
|
93
|
+
# (Claude Code refuses bypassPermissions as root)
|
|
94
|
+
.dockerfile_commands(["USER evaluser", "WORKDIR /home/evaluser"])
|
|
95
|
+
.env({"PATH": "/home/evaluser/.local/bin:/usr/local/bin:/usr/bin:/bin"})
|
|
96
|
+
.run_commands(
|
|
97
|
+
# Skip Claude Code onboarding
|
|
98
|
+
"mkdir -p ~/.claude"
|
|
99
|
+
" && echo '{\"hasCompletedOnboarding\": true}' > ~/.claude.json",
|
|
100
|
+
)
|
|
101
|
+
)
|
|
102
|
+
elif isinstance(self.sandbox_image, str):
|
|
103
|
+
image = Image.base(self.sandbox_image)
|
|
104
|
+
else:
|
|
105
|
+
image = self.sandbox_image
|
|
106
|
+
|
|
107
|
+
labels = {
|
|
108
|
+
"lightcone-eval": "true",
|
|
109
|
+
"task": self.task_id,
|
|
110
|
+
"trial": self.trial_id,
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
# Merge env vars: host ANTHROPIC_API_KEY + Langfuse creds + eval metadata
|
|
114
|
+
sandbox_env = {
|
|
115
|
+
"LIGHTCONE_EVAL": "true",
|
|
116
|
+
"LIGHTCONE_EVAL_TRIAL_ID": self.trial_id,
|
|
117
|
+
"LIGHTCONE_EVAL_TASK_ID": self.task_id,
|
|
118
|
+
}
|
|
119
|
+
# Pass through host API keys, OAuth token, and Langfuse config
|
|
120
|
+
for key in (
|
|
121
|
+
"ANTHROPIC_API_KEY",
|
|
122
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
123
|
+
"LANGFUSE_PUBLIC_KEY",
|
|
124
|
+
"LANGFUSE_SECRET_KEY",
|
|
125
|
+
"LANGFUSE_HOST",
|
|
126
|
+
):
|
|
127
|
+
val = os.environ.get(key)
|
|
128
|
+
if val:
|
|
129
|
+
sandbox_env[key] = val
|
|
130
|
+
sandbox_env.update(self.env_vars)
|
|
131
|
+
|
|
132
|
+
params = CreateSandboxFromImageParams(
|
|
133
|
+
image=image,
|
|
134
|
+
labels=labels,
|
|
135
|
+
env_vars=sandbox_env,
|
|
136
|
+
auto_stop_interval=0, # disable auto-stop; sandbox is deleted in teardown
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
def _on_build_log(line: str) -> None:
|
|
140
|
+
logger.info("[sandbox build] %s", line.rstrip())
|
|
141
|
+
|
|
142
|
+
self._sandbox = self._daytona.create(
|
|
143
|
+
params,
|
|
144
|
+
on_snapshot_create_logs=_on_build_log,
|
|
145
|
+
)
|
|
146
|
+
logger.info("Created sandbox %s for trial %s", self._sandbox.id, self.trial_id)
|
|
147
|
+
|
|
148
|
+
def setup(
|
|
149
|
+
self,
|
|
150
|
+
seed_dir: Path,
|
|
151
|
+
universe: str,
|
|
152
|
+
loop_prompt_template: str,
|
|
153
|
+
wheels: list[Path] | None = None,
|
|
154
|
+
) -> None:
|
|
155
|
+
"""Upload seed project and template the loop prompt."""
|
|
156
|
+
assert self._sandbox is not None, "Call create() first"
|
|
157
|
+
|
|
158
|
+
# Upload and install dependency wheels
|
|
159
|
+
if wheels:
|
|
160
|
+
self._install_wheels(wheels)
|
|
161
|
+
|
|
162
|
+
# Upload seed project files
|
|
163
|
+
self._upload_directory(seed_dir, self.WORK_DIR)
|
|
164
|
+
|
|
165
|
+
# Template the loop prompt
|
|
166
|
+
prompt = loop_prompt_template.replace("{{UNIVERSE}}", universe)
|
|
167
|
+
self.upload_file("/tmp/loop-prompt.md", prompt.encode())
|
|
168
|
+
|
|
169
|
+
# Upload lightcone-cli plugin files (.claude/ with skills, hooks, scripts, agents)
|
|
170
|
+
# We can't use `lc init` because the seed project already has astra.yaml,
|
|
171
|
+
# and lc init refuses to run in that case. Instead, upload directly.
|
|
172
|
+
self._install_claude_plugins()
|
|
173
|
+
|
|
174
|
+
# Configure lightcone-cli default target so `lc status` works without setup wizard
|
|
175
|
+
self.exec(
|
|
176
|
+
"mkdir -p ~/.lightcone"
|
|
177
|
+
" && echo 'default_target: local' > ~/.lightcone/config.yaml"
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# Git init (exec runs as evaluser via Dockerfile USER directive)
|
|
181
|
+
self.exec(
|
|
182
|
+
f"cd {self.WORK_DIR}"
|
|
183
|
+
" && git config --global user.name Eval"
|
|
184
|
+
" && git config --global user.email eval@lightcone"
|
|
185
|
+
" && git init && git add -A && git commit -m 'seed'"
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
def exec(self, cmd: str, timeout: int = 300, cwd: str | None = None) -> ExecuteResult:
|
|
189
|
+
"""Execute a command in the sandbox."""
|
|
190
|
+
assert self._sandbox is not None, "Call create() first"
|
|
191
|
+
|
|
192
|
+
result = self._sandbox.process.exec(cmd, cwd=cwd, timeout=timeout)
|
|
193
|
+
return ExecuteResult(
|
|
194
|
+
exit_code=result.exit_code,
|
|
195
|
+
output=result.result or "",
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
def exec_claude(
|
|
199
|
+
self,
|
|
200
|
+
max_turns: int = 25,
|
|
201
|
+
timeout: int = 600,
|
|
202
|
+
model: str | None = None,
|
|
203
|
+
) -> ClaudeResult:
|
|
204
|
+
"""Run claude -p with the loop prompt and parse JSON output.
|
|
205
|
+
|
|
206
|
+
Uses Daytona's session API with async execution + polling to avoid
|
|
207
|
+
HTTP connection timeouts on long-running Claude Code invocations.
|
|
208
|
+
"""
|
|
209
|
+
assert self._sandbox is not None, "Call create() first"
|
|
210
|
+
|
|
211
|
+
model_flag = f"--model {shlex.quote(model)}" if model else ""
|
|
212
|
+
cmd = (
|
|
213
|
+
f"cd {self.WORK_DIR} && "
|
|
214
|
+
f"claude -p \"$(cat /tmp/loop-prompt.md)\" "
|
|
215
|
+
f"--output-format stream-json --verbose "
|
|
216
|
+
f"--dangerously-skip-permissions "
|
|
217
|
+
f"--max-turns {max_turns} "
|
|
218
|
+
f"{model_flag}"
|
|
219
|
+
).strip()
|
|
220
|
+
|
|
221
|
+
start = time.monotonic()
|
|
222
|
+
result = self._exec_async_poll(cmd, timeout=timeout)
|
|
223
|
+
duration_ms = int((time.monotonic() - start) * 1000)
|
|
224
|
+
|
|
225
|
+
return _parse_claude_output(result.output, result.exit_code, duration_ms)
|
|
226
|
+
|
|
227
|
+
def _exec_async_poll(
|
|
228
|
+
self, cmd: str, timeout: int = 600, poll_interval: int = 10
|
|
229
|
+
) -> ExecuteResult:
|
|
230
|
+
"""Execute a command asynchronously via Daytona sessions and poll for completion.
|
|
231
|
+
|
|
232
|
+
Unlike process.exec() which makes a single blocking HTTP call (and drops
|
|
233
|
+
on long-running commands due to gateway timeouts), this uses:
|
|
234
|
+
1. create_session — establish a persistent session
|
|
235
|
+
2. execute_session_command(run_async=True) — fire-and-forget
|
|
236
|
+
3. get_session_command — poll until exit_code is set
|
|
237
|
+
4. get_session_command_logs — retrieve output
|
|
238
|
+
"""
|
|
239
|
+
from daytona_sdk._sync.process import SessionExecuteRequest
|
|
240
|
+
|
|
241
|
+
session_id = f"claude-{self.trial_id}"
|
|
242
|
+
proc = self._sandbox.process
|
|
243
|
+
|
|
244
|
+
proc.create_session(session_id)
|
|
245
|
+
try:
|
|
246
|
+
req = SessionExecuteRequest(command=cmd, run_async=True)
|
|
247
|
+
resp = proc.execute_session_command(session_id, req, timeout=60)
|
|
248
|
+
cmd_id = resp.cmd_id
|
|
249
|
+
|
|
250
|
+
deadline = time.monotonic() + timeout
|
|
251
|
+
while time.monotonic() < deadline:
|
|
252
|
+
time.sleep(poll_interval)
|
|
253
|
+
cmd_info = proc.get_session_command(session_id, cmd_id)
|
|
254
|
+
if cmd_info.exit_code is not None:
|
|
255
|
+
logs = proc.get_session_command_logs(session_id, cmd_id)
|
|
256
|
+
return ExecuteResult(
|
|
257
|
+
exit_code=cmd_info.exit_code,
|
|
258
|
+
output=logs.stdout or logs.output or "",
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
# Timeout reached — kill and return error
|
|
262
|
+
logger.warning("Command timed out after %ds in trial %s", timeout, self.trial_id)
|
|
263
|
+
return ExecuteResult(exit_code=1, output=f"Timed out after {timeout}s")
|
|
264
|
+
finally:
|
|
265
|
+
try:
|
|
266
|
+
proc.delete_session(session_id)
|
|
267
|
+
except Exception:
|
|
268
|
+
logger.debug("Failed to delete session %s", session_id, exc_info=True)
|
|
269
|
+
|
|
270
|
+
def upload_file(self, remote_path: str, content: bytes) -> None:
|
|
271
|
+
"""Upload a file to the sandbox."""
|
|
272
|
+
assert self._sandbox is not None, "Call create() first"
|
|
273
|
+
self._sandbox.fs.upload_file(content, remote_path)
|
|
274
|
+
|
|
275
|
+
def teardown(self) -> None:
|
|
276
|
+
"""Delete the sandbox."""
|
|
277
|
+
if self._sandbox is not None and self._daytona is not None:
|
|
278
|
+
try:
|
|
279
|
+
self._daytona.delete(self._sandbox)
|
|
280
|
+
logger.info("Deleted sandbox for trial %s", self.trial_id)
|
|
281
|
+
except Exception:
|
|
282
|
+
logger.warning(
|
|
283
|
+
"Failed to delete sandbox for trial %s", self.trial_id, exc_info=True
|
|
284
|
+
)
|
|
285
|
+
self._sandbox = None
|
|
286
|
+
|
|
287
|
+
def _install_claude_plugins(self) -> None:
|
|
288
|
+
"""Upload lightcone-cli's Claude Code plugin files (skills, hooks, scripts, agents)."""
|
|
289
|
+
from lightcone.cli.plugin import get_plugin_source_dir
|
|
290
|
+
|
|
291
|
+
plugin_source = get_plugin_source_dir()
|
|
292
|
+
if plugin_source is None:
|
|
293
|
+
logger.warning("Could not find lightcone-cli plugin source files — skipping")
|
|
294
|
+
return
|
|
295
|
+
|
|
296
|
+
claude_dir = f"{self.WORK_DIR}/.claude"
|
|
297
|
+
|
|
298
|
+
# Upload plugin subdirectories
|
|
299
|
+
for subdir in ("skills", "hooks", "scripts", "agents"):
|
|
300
|
+
src = plugin_source / subdir
|
|
301
|
+
if src.exists():
|
|
302
|
+
self._upload_directory(src, f"{claude_dir}/{subdir}")
|
|
303
|
+
|
|
304
|
+
# Upload CLAUDE.md template as project CLAUDE.md if not already present
|
|
305
|
+
template = plugin_source / "templates" / "CLAUDE.md"
|
|
306
|
+
if template.exists():
|
|
307
|
+
self.upload_file(f"{self.WORK_DIR}/CLAUDE.md", template.read_bytes())
|
|
308
|
+
|
|
309
|
+
# Make scripts executable
|
|
310
|
+
self.exec(
|
|
311
|
+
f"chmod -R +x {claude_dir}/scripts/ {claude_dir}/hooks/ 2>/dev/null || true"
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
# Create minimal settings.json (bypassPermissions, no hooks that need
|
|
315
|
+
# absolute paths or venvs — the eval sandbox is ephemeral)
|
|
316
|
+
settings = json.dumps({
|
|
317
|
+
"permissions": {"allow": ["Bash", "Read", "Edit", "Write", "WebSearch", "WebFetch"]},
|
|
318
|
+
})
|
|
319
|
+
self.upload_file(
|
|
320
|
+
f"{claude_dir}/settings.json",
|
|
321
|
+
settings.encode(),
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
def _install_wheels(self, wheels: list[Path]) -> None:
|
|
325
|
+
"""Upload and install wheel files into the sandbox.
|
|
326
|
+
|
|
327
|
+
Third-party deps are pre-installed in the sandbox image.
|
|
328
|
+
Only the local astra/lightcone-cli wheels need to be uploaded and installed here.
|
|
329
|
+
"""
|
|
330
|
+
self.exec("mkdir -p /tmp/deps")
|
|
331
|
+
|
|
332
|
+
remote_paths: list[str] = []
|
|
333
|
+
for whl in wheels:
|
|
334
|
+
remote_path = f"/tmp/deps/{whl.name}"
|
|
335
|
+
self.upload_file(remote_path, whl.read_bytes())
|
|
336
|
+
remote_paths.append(remote_path)
|
|
337
|
+
|
|
338
|
+
whl_cmd = "pip install --no-deps " + " ".join(shlex.quote(p) for p in remote_paths)
|
|
339
|
+
result = self.exec(whl_cmd, timeout=120)
|
|
340
|
+
if result.exit_code != 0:
|
|
341
|
+
logger.warning(
|
|
342
|
+
"Failed to install wheels (exit %d):\n...%s",
|
|
343
|
+
result.exit_code,
|
|
344
|
+
result.output[-2000:],
|
|
345
|
+
)
|
|
346
|
+
else:
|
|
347
|
+
logger.info("Installed wheels: %s", [w.name for w in wheels])
|
|
348
|
+
|
|
349
|
+
def _upload_directory(self, local_dir: Path, remote_dir: str) -> None:
|
|
350
|
+
"""Upload a local directory tree to the sandbox."""
|
|
351
|
+
for local_path in local_dir.rglob("*"):
|
|
352
|
+
if local_path.is_file():
|
|
353
|
+
rel = local_path.relative_to(local_dir)
|
|
354
|
+
remote_path = f"{remote_dir}/{rel}"
|
|
355
|
+
self.upload_file(remote_path, local_path.read_bytes())
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def _parse_claude_output(
|
|
359
|
+
raw_output: str, exit_code: int, duration_ms: int
|
|
360
|
+
) -> ClaudeResult:
|
|
361
|
+
"""Parse JSONL output from claude -p --output-format stream-json.
|
|
362
|
+
|
|
363
|
+
The stream-json format emits one JSON object per line. The final line
|
|
364
|
+
with ``{"type": "result", ...}`` contains the aggregate metrics.
|
|
365
|
+
"""
|
|
366
|
+
result = ClaudeResult(duration_ms=duration_ms, raw_jsonl=raw_output)
|
|
367
|
+
|
|
368
|
+
if exit_code != 0:
|
|
369
|
+
result.is_error = True
|
|
370
|
+
result.result_text = raw_output
|
|
371
|
+
return result
|
|
372
|
+
|
|
373
|
+
for raw_line in reversed(raw_output.strip().splitlines()):
|
|
374
|
+
stripped = raw_line.strip()
|
|
375
|
+
if not stripped or not stripped.startswith("{"):
|
|
376
|
+
continue
|
|
377
|
+
try:
|
|
378
|
+
data = json.loads(stripped)
|
|
379
|
+
if data.get("type") == "result":
|
|
380
|
+
result.cost_usd = float(
|
|
381
|
+
data.get("cost_usd", data.get("total_cost_usd", 0.0))
|
|
382
|
+
)
|
|
383
|
+
result.num_turns = int(data.get("num_turns", 0))
|
|
384
|
+
result.duration_ms = int(data.get("duration_ms", duration_ms))
|
|
385
|
+
result.result_text = str(data.get("result", ""))
|
|
386
|
+
result.is_error = bool(data.get("is_error", False))
|
|
387
|
+
return result
|
|
388
|
+
except (json.JSONDecodeError, ValueError):
|
|
389
|
+
continue
|
|
390
|
+
|
|
391
|
+
# No result line found
|
|
392
|
+
result.result_text = raw_output
|
|
393
|
+
result.is_error = True
|
|
394
|
+
return result
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lightcone-cli
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Lightcone command-line toolchain
|
|
5
|
+
Author: Lightcone Research
|
|
6
|
+
License: BSD-3-Clause
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Requires-Dist: astra-tools>=0.2.2
|
|
10
|
+
Requires-Dist: click>=8.0
|
|
11
|
+
Requires-Dist: dagster-docker>=0.25
|
|
12
|
+
Requires-Dist: dagster-webserver>=1.9
|
|
13
|
+
Requires-Dist: dagster>=1.9
|
|
14
|
+
Requires-Dist: langfuse>=2.0
|
|
15
|
+
Requires-Dist: pyyaml>=6.0
|
|
16
|
+
Requires-Dist: rich>=13.0
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
lightcone/cli/__init__.py,sha256=2S0lpX7D22ZTi4-pg_XvyTEXapNmLiTZSyKAnQyQrxk,343
|
|
2
|
+
lightcone/cli/commands.py,sha256=IXuo5H-U-K30XzlmjVRrSzBJ-XyK2PUx4dmgx22ZZdY,83250
|
|
3
|
+
lightcone/cli/plugin.py,sha256=paFhnEJC8dT7_flncGzLQ6Iczf0TcreyJsiU3vUV8Wg,1144
|
|
4
|
+
lightcone/engine/__init__.py,sha256=4FbltwapwRQb_Ee98qURGMk2hkuhTzReKMfOLuuX3Oo,1473
|
|
5
|
+
lightcone/engine/assets.py,sha256=dbXw5ZGAb7oCs6Ag8hAoOQ4agB6CKQzy6ZELrI96wFI,15388
|
|
6
|
+
lightcone/engine/container.py,sha256=hyTjk_3inIbl0bW3gLy8PCTAbPZPod6l4MfVcAhindE,11434
|
|
7
|
+
lightcone/engine/io_manager.py,sha256=PPneCHjbc7ig9dc7wfhmwYsakrKerWQoehDi5dXcpAQ,906
|
|
8
|
+
lightcone/engine/runner.py,sha256=QK_KjaHFHw3WSrl3-eeX0BZUeBncA5a10JOoB3fmVjI,36589
|
|
9
|
+
lightcone/engine/site_registry.py,sha256=gDeA9nODv41lKsWv1msZ7yvwuHvMduEhCBucqHCbuHQ,4753
|
|
10
|
+
lightcone/engine/status.py,sha256=gqIE1VcPjImkajq8mVr_QirA7-xf_6jft81h8SBJngc,4782
|
|
11
|
+
lightcone/engine/targets.py,sha256=p_SntAF2A4a-wi1J6c-y-MYdyOTEY11ipp9n1ZL5pjY,2178
|
|
12
|
+
lightcone/engine/tree.py,sha256=8c4uYveIvdKbZzFcNbg0GP5J1OPF9mRfH11k5QF4inw,8663
|
|
13
|
+
lightcone/eval/__init__.py,sha256=wVFjagUBIHJcFImK-S2JCh4vFkHygieBpqDukNSo278,460
|
|
14
|
+
lightcone/eval/build.py,sha256=GK-yp-CYtkq7_97qM4_208yCBWUz-87ozzwxGLBKGnI,5227
|
|
15
|
+
lightcone/eval/cli.py,sha256=tyGrGGF7iqDI14hnlOSMW6-RS4lNcGgkU2YGl578h68,5464
|
|
16
|
+
lightcone/eval/graders.py,sha256=yn53XOgm77OeK8Xh1eRtdggBr21kTfX73s3pTHdiYXs,5844
|
|
17
|
+
lightcone/eval/harness.py,sha256=3bQPVdAolGpoQUWXc-KlU1ijQ1jZfRoHibs5u6BFcRI,8625
|
|
18
|
+
lightcone/eval/models.py,sha256=53Y4By8qurk5owsld-SQdek_TpICydh0ay1IlwxLbEE,2956
|
|
19
|
+
lightcone/eval/report.py,sha256=sszPDq6KGIf7YlSD0fpHJ_cx-TWuEbPUyMRWvcMuE4M,6625
|
|
20
|
+
lightcone/eval/sandbox.py,sha256=_RFa5DmvSosK6QtCiK8fRX9dZ1riwo1XI51899FkFus,14839
|
|
21
|
+
lightcone/cli/claude/lightcone/agents/lc-extractor.md,sha256=vzJDLsM7dRlQZi0tHye9dgXveXdIm51ga813RHCdfkg,4797
|
|
22
|
+
lightcone/cli/claude/lightcone/guides/astra-reference.md,sha256=hrR13zFT7ldeQ76dOZNN4FFVFkxgGozHAFAXqCsgExE,13507
|
|
23
|
+
lightcone/cli/claude/lightcone/guides/lightcone-cli-reference.md,sha256=uUUkiqMrUUXxGhNVAAKRL05ew96QIxea2yDkI_tW7y8,4309
|
|
24
|
+
lightcone/cli/claude/lightcone/guides/ui-brand.md,sha256=Ht_0wZ4XHOG-L-OOyKlGaXJUeBc9VCXE6tbUXJc-k48,1474
|
|
25
|
+
lightcone/cli/claude/lightcone/hooks/langfuse_git_commit_hook.py,sha256=b0hCgX0zn_TXHRWMIqcgaax_wl4BguYgpB34iknTl8g,9156
|
|
26
|
+
lightcone/cli/claude/lightcone/hooks/langfuse_hook.py,sha256=KWYLAN1lz71pUaSmhGBFil1dRZ81SjNVsU-L0kpo4ds,31149
|
|
27
|
+
lightcone/cli/claude/lightcone/hooks/langfuse_prepare_commit_msg.py,sha256=YrvsVAsPJOLSmVDBNZvNJcB_Pfqs35oTr7L2sL2Qbj8,4431
|
|
28
|
+
lightcone/cli/claude/lightcone/hooks/langfuse_session_init_hook.py,sha256=X0B81heDmhTYp14Sb5NVSqgxeJZOso4s-fIt1Oq4r9c,2323
|
|
29
|
+
lightcone/cli/claude/lightcone/hooks/langfuse_utils.py,sha256=Lbu1wSFPeSbkzoU2Njop_029WtJUK3YxWJCZp8vVvP0,14786
|
|
30
|
+
lightcone/cli/claude/lightcone/scripts/activate-venv.sh,sha256=KopcYtfAep3SOdedCK65LFicz9LbnBdp6xCM6Sjgw4E,1080
|
|
31
|
+
lightcone/cli/claude/lightcone/scripts/check-lc-run.sh,sha256=ha66_S4k9dXXvkBpBfRTgBhg1-VNLAiBuHYiDbT3r4g,5071
|
|
32
|
+
lightcone/cli/claude/lightcone/scripts/session-start.sh,sha256=hedoR8WPVnUC-z8Bg_KgTolfWbOacKHgwxxnEGaUREE,5907
|
|
33
|
+
lightcone/cli/claude/lightcone/scripts/validate-on-save.sh,sha256=cWMHeHCmYIlm8aedlnSH8wT0L_ITIeZZBEq0eeNJvL0,2103
|
|
34
|
+
lightcone/cli/claude/lightcone/skills/lc-build/SKILL.md,sha256=qW7CavCGyJDtDBJmv6MdvMz5geWnoXAj5s1GSpR7LCk,4435
|
|
35
|
+
lightcone/cli/claude/lightcone/skills/lc-build/assets/loop-prompt.md,sha256=7dN92zUaH0LtiiArV65wZTCzC0kKlJRzvNOLMmq1hj8,6947
|
|
36
|
+
lightcone/cli/claude/lightcone/skills/lc-build/scripts/setup-lc-build.sh,sha256=4fU82zidgUFEHtNcU4Q3gB66I_MVpl7xaH5dv59aNK4,8612
|
|
37
|
+
lightcone/cli/claude/lightcone/skills/lc-feedback/SKILL.md,sha256=BKfA7ZKWqZwNARspFO71pT5dyQJIQL4CINvlhb55n64,2453
|
|
38
|
+
lightcone/cli/claude/lightcone/skills/lc-migrate/SKILL.md,sha256=N1m9oN_UtyU0IhlmiH5Wb8BtIqO4v3HyiCoP9hkrapc,5582
|
|
39
|
+
lightcone/cli/claude/lightcone/skills/lc-new/SKILL.md,sha256=Y6iOkp7Ovb8TPZWKdJ5cfJGEVuU1WDwwq1IFPCCNYBI,9101
|
|
40
|
+
lightcone/cli/claude/lightcone/skills/lc-verify/SKILL.md,sha256=K5nIbon6ZrSwlE9_qkAnhZNNT_AVcgelPQc7r64lSog,1623
|
|
41
|
+
lightcone/cli/claude/lightcone/templates/CLAUDE.md,sha256=tChaQyu6oDUoaq2qhJlxZko9RynxddJzNWs987ormdQ,1534
|
|
42
|
+
lightcone_cli-0.2.0.dist-info/METADATA,sha256=EmGzWOi-YX86WBnI0oIS_kLw1naMdwUjTJcPYm7Cqh8,438
|
|
43
|
+
lightcone_cli-0.2.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
44
|
+
lightcone_cli-0.2.0.dist-info/entry_points.txt,sha256=DR2m0ecW9eYV_W75cEUfxVHH8unod1R-_E4EsbuQugI,42
|
|
45
|
+
lightcone_cli-0.2.0.dist-info/licenses/LICENSE,sha256=FgoMEqKiIZLQGlRsS8-iVcUpR2X0oYYVXQuWDLUXqmI,1586
|
|
46
|
+
lightcone_cli-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Centre National de la Recherche Scientifique (CNRS) and
|
|
4
|
+
The Regents of the University of California
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|