Help Desk › Working with your AI
Show current information from public web services, spreadsheets and business tools.
Tell your AI where the information lives and how often you need it. Your AI builds the page and sets up a refresh job when the source needs one. The job saves the result; lite.computer displays it. Your AI asks before installing anything on your Mac.
A page can read a compatible public service while it is open: weather, market prices, exchange rates, news headlines or public statistics. Ask your AI to check how often the provider updates its data and whether it allows the page to read it directly.
Some services need a sign-in or access key, or allow only their own website to read them. For those, an authorized script or AI job reads the service and saves the result for the page. Keep access keys out of Showspace pages.
For a non-sensitive sheet, your AI can read a viewer link. Anyone with that link can read the sheet. A private sheet can stay private when your AI has an authorized connection to your Google account. Tell it which tab and figures belong on the page. Your changes appear after the next successful refresh and, when needed, folder sync. The timing depends on the schedule you choose.
Tell your AI which worksheet and figures to use, then save the workbook as usual. Changes reach the page after the next successful refresh. For a workbook in OneDrive or Google Drive, keep it available to the updater and test the scheduled job while it runs unattended. The job also needs permission to read the file.
A menu, price list or roster you edit by hand. Save the file; the page receives the change after its refresh job reads the new copy.
Accounting, sales, bookings, analytics.
Your AI writes a small script that reads it and updates the page on a schedule. See Turning records into pages.
For an explanation alongside the figures, ask a scheduled AI to read the latest validated data and write a short, dated briefing. A simple script can calculate repeatable totals while the AI writes only the interpretation. Codex, Claude, Gemini or another AI can do the job if it can read the source and save the result to your Showspace.
Keep one active updater for each output file. Show when the source was updated and when the job last succeeded. If a refresh fails, keep the last good figures and label the briefing as old. Keep forecasts separate from completed results.
Here is a starter prompt. How well it works depends on how your AI is set up, so adjust it to fit yours.
Build me [a dashboard / a menu board / a page of records] as one HTML page in my Showspace, with changing figures in a small data file beside it. The source is [WEBSITE, GOOGLE SHEET, EXCEL WORKBOOK, CSV OR BUSINESS SERVICE]. Ask which figures, dates, source fields and time zone matter. Set a suitable refresh schedule and tell me where the job runs and what access it needs. Show the source time, the last successful refresh and a stale label after a failed update. Keep the last good figures and leave private rows and keys out of the page. [OPTIONAL: Add a short daily AI briefing.] Ask before installing anything on my Mac, then verify the first unattended run.
This starter script copies a plain table from a public Google Sheets CSV link, the first worksheet of an Excel workbook, or a CSV file into the data file the page loads. A private sheet needs an authorized connection. A business workbook with formulas, several worksheets, booking statuses or sensitive rows needs a tailored updater and checked metric rules.
#!/bin/sh # lite-refresh.sh — copy a spreadsheet's numbers into a data file beside a lite.computer page. # # lite-refresh.sh SOURCE DEST # # DEST ends in .js — for pages shown inside lite.computer (the page loads it as a script). # DEST ends in .csv — a plain CSV, for pages that will only ever run on a website. # # SOURCE is one of: # - a Google Sheets CSV link (the sheet must be shared "anyone with the link can view", # or published to the web): # https://docs.google.com/spreadsheets/d/<SHEET ID>/export?format=csv # - the path to an Excel file (.xlsx). Its first sheet is used. Needs python3. It reads a plain # table: text, numbers and TRUE/FALSE, with formulas as Excel last saved them. Dates arrive as # Excel's day numbers. For a workbook that needs more, read it with a workbook library instead. # - the path to a CSV file a person edits by hand (.csv). # # DEST is the data file beside the page, inside the Showspace. # # Two safety rules: # 1. The new file is written beside the old one and swapped in, so the page never reads half a file. # 2. If anything goes wrong, the last good copy stays where it is and this script exits non-zero. set -u if [ "$#" -ne 2 ]; then echo "usage: lite-refresh.sh SOURCE DEST" >&2 exit 2 fi SOURCE=$1 DEST=$2 DIR=$(dirname "$DEST") TMP= WRAP= fail() { echo "lite-refresh: $1 — kept the last good copy of $(basename "$DEST")" >&2 exit 1 } [ -d "$DIR" ] || fail "the folder $DIR does not exist" # Each run gets its own hidden temp files, so two runs for the same page can never swap each # other's half-written work. They are removed however the script ends. TMP=$(mktemp "$DIR/.$(basename "$DEST").XXXXXX") || fail "could not write in $DIR" cleanup() { rm -f "${TMP:-}" "${WRAP:-}"; } trap cleanup EXIT trap 'cleanup; exit 129' HUP trap 'cleanup; exit 130' INT trap 'cleanup; exit 143' TERM case "$SOURCE" in http://*|https://*) # A sheet that is not shared answers with a sign-in web page, not an error. # So check what came back, not only whether the download worked. TYPE=$(curl -s -L --max-time 30 -o "$TMP" -w "%{http_code} %{content_type}" "$SOURCE") \ || fail "could not reach the sheet" case "$TYPE" in "200 text/csv"*|"200 text/plain"*|"200 application/octet-stream"*) ;; *) fail "the link did not return a CSV (got: $TYPE). Is the sheet shared by link?" ;; esac # Some servers label a web page as plain text. Look at the first bytes as well. if head -c 512 "$TMP" | LC_ALL=C grep -Eqi '^[[:space:]]*(<!doctype[[:space:]]+html|<html([[:space:]>]|$))'; then fail "the link returned a web page, not a CSV. Is the sheet shared by link?" fi ;; *.xlsx) [ -f "$SOURCE" ] || fail "cannot find $SOURCE" command -v python3 >/dev/null 2>&1 || fail "python3 is needed to read Excel files" python3 - "$SOURCE" "$TMP" <<'PY' || fail "could not read the Excel file" import csv, re, sys, zipfile import xml.etree.ElementTree as ET src, out = sys.argv[1], sys.argv[2] M = "{http://schemas.openxmlformats.org/spreadsheetml/2006/main}" R = "{http://schemas.openxmlformats.org/officeDocument/2006/relationships}" P = "{http://schemas.openxmlformats.org/package/2006/relationships}" with zipfile.ZipFile(src) as z: names = set(z.namelist()) shared = [] if "xl/sharedStrings.xml" in names: for si in ET.fromstring(z.read("xl/sharedStrings.xml")).iter(M + "si"): shared.append("".join(t.text or "" for t in si.iter(M + "t"))) # The first sheet in the workbook's own order, whatever its file is called. wb = ET.fromstring(z.read("xl/workbook.xml")) first = wb.find(M + "sheets").find(M + "sheet") rels = ET.fromstring(z.read("xl/_rels/workbook.xml.rels")) target = next(r.get("Target") for r in rels.iter(P + "Relationship") if r.get("Id") == first.get(R + "id")) path = target.lstrip("/") if target.startswith("/") else "xl/" + target sheet = ET.fromstring(z.read(path)) def col_index(ref): n = 0 for ch in re.match(r"[A-Z]+", ref).group(0): n = n * 26 + ord(ch) - 64 return n - 1 rows = [] for row in sheet.iter(M + "row"): cells = {} for c in row.iter(M + "c"): kind, v = c.get("t"), c.find(M + "v") if kind == "s" and v is not None: text = shared[int(v.text)] elif kind == "inlineStr": text = "".join(t.text or "" for t in c.iter(M + "t")) elif kind == "b": text = "TRUE" if v is not None and v.text == "1" else "FALSE" else: text = v.text if v is not None and v.text is not None else "" # Excel stores 9.8 as 9.800000000000001. Write the number a person typed. if text and kind in (None, "n"): try: text = format(float(text), ".12g") except ValueError: pass cells[col_index(c.get("r"))] = text if cells: rows.append([cells.get(i, "") for i in range(max(cells) + 1)]) rows = [r for r in rows if any(x.strip() for x in r)] if not rows: sys.exit("the first sheet is empty") with open(out, "w", newline="", encoding="utf-8") as f: csv.writer(f).writerows(rows) PY ;; *.csv) # A CSV a person edits by hand (a menu, a price list). Nothing to convert: only to hand over. [ -f "$SOURCE" ] || fail "cannot find $SOURCE" cp "$SOURCE" "$TMP" || fail "could not read $SOURCE" ;; *) fail "SOURCE must be a web link, an .xlsx file or a .csv file" ;; esac [ -s "$TMP" ] || fail "the result was empty" # A page inside lite.computer is walled off and may not READ a file beside it, but it may LOAD a # script from one. So when DEST ends in .js, hand the page its numbers as a one-line script: # liteData(`...the CSV text...`) # Backslashes, backticks and ${ are escaped, so nothing typed into a sheet can run as page code. case "$DEST" in *.js) WRAP=$(mktemp "$DIR/.$(basename "$DEST").wrap.XXXXXX") || fail "could not prepare the script file" { printf 'window.liteData&&window.liteData(`' sed -e 's/\\/\\\\/g' -e 's/`/\\`/g' -e 's/\${/\\${/g' "$TMP" printf '`);\n' } > "$WRAP" || fail "could not prepare the script file" mv -f "$WRAP" "$TMP" || fail "could not prepare the script file" ;; esac # Only swap the file in when something changed, so the page and any sync service stay quiet. if [ -f "$DEST" ] && cmp -s "$TMP" "$DEST"; then exit 0 fi if [ -f "$DEST" ]; then chmod "$(stat -f %Lp "$DEST")" "$TMP" || fail "could not set permissions on $DEST" else chmod 644 "$TMP" || fail "could not set permissions on $DEST" fi mv -f "$TMP" "$DEST" || fail "could not write $DEST" echo "lite-refresh: updated $(basename "$DEST")"
Still stuck? Write to support@lite.computer.