Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

### Fixed
- Setup failures now print the full error in red, and the agent hand-off prompt includes the error text so the agent can diagnose it directly.
- Error output with bracket tokens (e.g. `[notice]`) is no longer swallowed by rich markup.
- Setup failures print the full error in red and include it in the agent hand-off prompt, so the agent can diagnose it directly.
- Connecting the playground workspace no longer fails with a confusing "already exists" error; setup connects to it directly.

### Changed
- The launch-plan preview shows an `<error shown above>` placeholder instead of repeating the error; wrapped lines keep their indentation; non-interactive runs no longer hard-wrap the hand-off prompt.
- The hand-off prompt prints as plain flush-left text instead of inside a panel, so it can be selected and copied manually without grabbing box borders.
- The scaffold's `.scripts/show_notebook.py` opens the notebook with `?hide_header=true` for a cleaner view.
- Refreshed the bundled minimal workspace `uv.lock` (`dlthub-client` 0.28.1, `marimo` 0.23.13, `pandas` 3.0.3 — 3.0.4 was yanked for datetime segfaults) and updated the notebook session snapshot's pinned marimo version.
- The hand-off prompt prints as plain text (no panel), so it can be copied manually.
- The onboarding notebook is a single page; its "Next step" button navigates to the organization's setup page on dltHub.
- The dataset viewer now hides the notebook header, and opens on whichever stack (local or hosted) you're connected to instead of always pointing to production.
- Refreshed the bundled workspace `uv.lock` (`dlthub-client` 0.28.1, `marimo` 0.23.13; `pandas` 3.0.3 — 3.0.4 was yanked).

## [0.10.1] - 2026-07-01

Expand Down
8 changes: 4 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -127,14 +127,14 @@ workspace-env: ## Like workspace, but pins api_base_url (+ auth_base_url / dlthu
fi; \
echo "workspace-env: pinned + validated api_base_url = $(API_BASE_URL)$(if $(AUTH_BASE_URL), (auth_base_url = $(AUTH_BASE_URL))) in $$cfg"

workspace-local: ## Scaffold a workspace pointed at the local stack (api + auth on *.dlthub.test) with an editable dlthub-client; skips TLS verify (mkcert CA is not in Python's bundle)
$(MAKE) workspace-env API_BASE_URL=https://api.dlthub.test AUTH_BASE_URL=https://auth.dlthub.test DLT_RUNTIME_INSECURE=true DLTHUB_CLIENT_SOURCE="$(or $(DLTHUB_CLIENT_SOURCE),$(CURDIR)/../runtime/clients/cli)"
workspace-local: ## Scaffold a workspace pointed at the local stack (api + auth on *.dlthub.test) with the released dlthub-client; skips TLS verify (mkcert CA is not in Python's bundle)
$(MAKE) workspace-env API_BASE_URL=https://api.dlthub.test AUTH_BASE_URL=https://auth.dlthub.test DLT_RUNTIME_INSECURE=true

workspace-stage: ## Scaffold a workspace pointed at the staging stack (api.dlthub.net)
$(MAKE) workspace-env API_BASE_URL=https://api.dlthub.net

workspace-dev: ## Scaffold a workspace pointed at the dev stack (api.dlthub.dev) with an editable dlthub-client matching the dev API
$(MAKE) workspace-env API_BASE_URL=https://api.dlthub.dev DLTHUB_CLIENT_SOURCE="$(or $(DLTHUB_CLIENT_SOURCE),$(CURDIR)/../runtime/clients/cli)"
workspace-dev: ## Scaffold a workspace pointed at the dev stack (api.dlthub.dev) with the released dlthub-client
$(MAKE) workspace-env API_BASE_URL=https://api.dlthub.dev

workspace-here: dev ## Init in place: make empty ./$(WORKSPACE_HERE_DIR), cd in, run the local CLI with no positional (pass ARGS="--yes --skip-uv-sync")
@case "$(WORKSPACE_HERE_DIR)" in *..*|"") echo "invalid WORKSPACE_HERE_DIR: $(WORKSPACE_HERE_DIR)"; exit 1;; esac
Expand Down
53 changes: 5 additions & 48 deletions src/create_dlthub_workspace/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from __future__ import annotations

import argparse
import re
import shutil
import subprocess
import sys
Expand Down Expand Up @@ -43,7 +42,7 @@
validate_agent,
validate_scaffold_name,
)
from .uv import capture_uv_command, execute_uv_install, find_uv, run_uv_command, run_uv_sync
from .uv import execute_uv_install, find_uv, run_uv_command, run_uv_sync


def _ensure_utf8_io_on_windows() -> None:
Expand Down Expand Up @@ -325,55 +324,13 @@ def _login_and_connect_playground(uv_executable: str, project_dir: Path, *, verb
"""Log in and bind the playground workspace, the setup the entry skill assumes is done."""
with substep(strings.MSG_CONNECTING_DLTHUB, strings.MSG_CONNECTED_DLTHUB, verbose=verbose):
run_uv_command(uv_executable, project_dir, ["run", "dlthub", "login"], verbose=verbose)
# connect --create errors on an existing workspace, so pass it only when absent.
connect_args = ["run", "dlthub", "workspace", "connect", PLAYGROUND_WORKSPACE]
if not _playground_exists(uv_executable, project_dir):
connect_args.append("--create")
run_uv_command(uv_executable, project_dir, connect_args, verbose=verbose)


def _workspace_in_list(list_output: str, name: str) -> bool:
"""True if ``name`` appears in the Name column of `dlthub workspace list`.

The output is a space-padded table; workspace names can contain single
spaces (e.g. "My Workspace"), so columns are split on runs of 2+ spaces and
the first field is the name. The header row (before the dashed separator)
and the separator itself are skipped, so a workspace literally named like a
column header can't false-match.
"""
seen_separator = False
for line in list_output.splitlines():
stripped = line.strip()
if not stripped:
continue
if set(stripped) <= {"-", " "}:
seen_separator = True
continue
if not seen_separator:
continue # header row(s) above the separator
first_column = re.split(r"\s{2,}", stripped)[0]
if first_column == name:
return True
return False


def _playground_exists(uv_executable: str, project_dir: Path) -> bool:
"""Report whether the playground workspace already exists for the user.

Lists remote workspaces with --non-interactive so an unauthenticated user
fails fast (no hanging prompt) instead of blocking. On any failure we report
False, so the caller falls back to `connect --create` — and that connect
step then triggers the interactive login.
"""
try:
output = capture_uv_command(
# The account always has a playground workspace, so connect without --create.
run_uv_command(
uv_executable,
project_dir,
["run", "dlthub", "--non-interactive", "workspace", "list"],
["run", "dlthub", "workspace", "connect", PLAYGROUND_WORKSPACE],
verbose=verbose,
)
except UvError:
return False
return _workspace_in_list(output, PLAYGROUND_WORKSPACE)


if __name__ == "__main__":
Expand Down
Original file line number Diff line number Diff line change
@@ -1,44 +1,34 @@
"""Open a deployed notebook's read-only "show" page in the dltHub web app.

Builds and opens:
{APP_BASE}/w/{workspace_id}/notebooks/{job_ref}/show?hide_header=true
{web_ui_base}/w/{workspace_id}/notebooks/{job_ref}/show?hide_header=true

The workspace id is read from this workspace's `.dlt/config.toml` (so it tracks
whatever this workspace is connected to). Pass the job ref as the only argument.
Override the web-app base with DLTHUB_APP_URL (defaults to prod).
The workspace id and web-app base come from the active workspace context —
`dlt_runtime.urls` mirrors the web app's routes — so the URL tracks whatever
stack this workspace is connected to. Pass the job ref as the only argument.

Usage (run from the workspace root):
Usage (run from the workspace root so the workspace context resolves):
uv run .scripts/show_notebook.py jobs.onboarding_success
DLTHUB_APP_URL=https://app.dlthub.test uv run .scripts/show_notebook.py jobs.onboarding_success
"""

import os
import sys
import tomllib
import webbrowser
from pathlib import Path

APP_BASE = os.environ.get("DLTHUB_APP_URL", "https://app.dlthub.com").rstrip("/")

if len(sys.argv) < 2:
sys.exit("usage: uv run .scripts/show_notebook.py <job-ref> e.g. jobs.onboarding_success")
ref = sys.argv[1]

from dlt._workspace._workspace_context import active
from dlt._workspace.exceptions import WorkspaceRunContextNotAvailable
from dlt_runtime.urls import workspace_url

def _find_config() -> Path:
"""Locate .dlt/config.toml by walking up from this script (location-independent)."""
for parent in Path(__file__).resolve().parents:
candidate = parent / ".dlt" / "config.toml"
if candidate.is_file():
return candidate
sys.exit("Could not find .dlt/config.toml above this script — run inside a workspace.")


cfg = tomllib.loads(_find_config().read_text())
ws = cfg.get("runtime", {}).get("workspace_id")
try:
ws = active().runtime_config.workspace_id
except WorkspaceRunContextNotAvailable:
sys.exit("No workspace found here — run from the workspace root.")
if not ws:
sys.exit("No workspace_id in .dlt/config.toml — connect the workspace first.")

url = f"{APP_BASE}/w/{ws}/notebooks/{ref}/show?hide_header=true"
url = f"{workspace_url(ws)}/notebooks/{ref}/show?hide_header=true"
print(f"Opening {url}")
webbrowser.open(url)
Loading
Loading