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.
Files changed (46) hide show
  1. lightcone/cli/__init__.py +16 -0
  2. lightcone/cli/claude/lightcone/agents/lc-extractor.md +114 -0
  3. lightcone/cli/claude/lightcone/guides/astra-reference.md +290 -0
  4. lightcone/cli/claude/lightcone/guides/lightcone-cli-reference.md +75 -0
  5. lightcone/cli/claude/lightcone/guides/ui-brand.md +86 -0
  6. lightcone/cli/claude/lightcone/hooks/langfuse_git_commit_hook.py +303 -0
  7. lightcone/cli/claude/lightcone/hooks/langfuse_hook.py +894 -0
  8. lightcone/cli/claude/lightcone/hooks/langfuse_prepare_commit_msg.py +142 -0
  9. lightcone/cli/claude/lightcone/hooks/langfuse_session_init_hook.py +83 -0
  10. lightcone/cli/claude/lightcone/hooks/langfuse_utils.py +457 -0
  11. lightcone/cli/claude/lightcone/scripts/activate-venv.sh +44 -0
  12. lightcone/cli/claude/lightcone/scripts/check-lc-run.sh +140 -0
  13. lightcone/cli/claude/lightcone/scripts/session-start.sh +140 -0
  14. lightcone/cli/claude/lightcone/scripts/validate-on-save.sh +77 -0
  15. lightcone/cli/claude/lightcone/skills/lc-build/SKILL.md +92 -0
  16. lightcone/cli/claude/lightcone/skills/lc-build/assets/loop-prompt.md +92 -0
  17. lightcone/cli/claude/lightcone/skills/lc-build/scripts/setup-lc-build.sh +240 -0
  18. lightcone/cli/claude/lightcone/skills/lc-feedback/SKILL.md +94 -0
  19. lightcone/cli/claude/lightcone/skills/lc-migrate/SKILL.md +98 -0
  20. lightcone/cli/claude/lightcone/skills/lc-new/SKILL.md +183 -0
  21. lightcone/cli/claude/lightcone/skills/lc-verify/SKILL.md +53 -0
  22. lightcone/cli/claude/lightcone/templates/CLAUDE.md +32 -0
  23. lightcone/cli/commands.py +2327 -0
  24. lightcone/cli/plugin.py +34 -0
  25. lightcone/engine/__init__.py +42 -0
  26. lightcone/engine/assets.py +418 -0
  27. lightcone/engine/container.py +370 -0
  28. lightcone/engine/io_manager.py +27 -0
  29. lightcone/engine/runner.py +1017 -0
  30. lightcone/engine/site_registry.py +142 -0
  31. lightcone/engine/status.py +135 -0
  32. lightcone/engine/targets.py +68 -0
  33. lightcone/engine/tree.py +245 -0
  34. lightcone/eval/__init__.py +25 -0
  35. lightcone/eval/build.py +148 -0
  36. lightcone/eval/cli.py +176 -0
  37. lightcone/eval/graders.py +192 -0
  38. lightcone/eval/harness.py +265 -0
  39. lightcone/eval/models.py +117 -0
  40. lightcone/eval/report.py +214 -0
  41. lightcone/eval/sandbox.py +394 -0
  42. lightcone_cli-0.2.0.dist-info/METADATA +16 -0
  43. lightcone_cli-0.2.0.dist-info/RECORD +46 -0
  44. lightcone_cli-0.2.0.dist-info/WHEEL +4 -0
  45. lightcone_cli-0.2.0.dist-info/entry_points.txt +2 -0
  46. 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,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ lc = lightcone.cli:main
@@ -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.