Atlas / Skills / jkorsvik / tun

tunSAFE

skills/jkorsvik/tun

Tún: portable single-file dashboards for SharePoint and OneDrive

Verdict
SAFE
Grade
B
Trust score
89 /100
Version
2.0.0
Hosts
5 documented
License
AGPL-3.0
Stars
0
01

Overview

From the repository's own README, as read at the audited commit. Badges and raw HTML are left out.

Interactive dashboards that are files, not services. Built as single self-contained HTML artefacts, distributed through SharePoint and OneDrive, openable by anyone who can open the folder, with no Power BI licence, no workspace, no capacity and no admin approval.

Tún is a passion project, credited to the product owner and the maintainer. <3 The idea, the name and the direction are the product owner's; deliberately open-ended. The maintainer builds the plumbing: the awkward platform constraints measured so nobody has to rediscover them, a working template, the gates that keep it honest, and a set of RAIWork skills so building and sharing an artefact is a sentence rather than a project. Scaffolding, not a specification. Nobody asked for this and nothing depends on it. That is the point: it exists because sharing a number with a colleague should not cost a licence, a workspace and three weeks, and because finding out exactly why it does was interesting enough to keep going.

The idea in one paragraph

Building a dashboard is easy. Sharing one is where the organisation loses weeks. It takes licences per viewer, a workspace, a capacity, a publish step, and still no way to show it to someone without a Pro seat. So people fall back to a screenshot in PowerPoint, which is dead on arrival. Instead: make the dashboard a document. One .html file with the data, charts and interaction logic inside it. Drop it in a SharePoint library. Everyone already knows how to share a document, and SharePoint already handles permissions, versioning, audit, sync and offline. Reuse that machinery instead of fighting it.

Status

Read from source at commit 141b38605b8bOBSERVED · 2026-10-10
02

Install

Commands as the repository documents them. They are shown, not run.

uv sync --extra verify && uv run playwright install chromium
uv run --no-project --no-config --index-url https://pypi.org/simple --with playwright python tools/verify_dashboard.py <fil
uv run --no-project --no-config --index-url https://pypi.org/simple --with playwright python tests/test_feedback_link_brows
uv sync --extra verify && uv run playwright install chromium   # once
uv run python tools/verify_dashboard.py /tmp/demo.html
git clone <your-forge>/<owner>/tun.git
03

Host compatibility

What the documentation claims. We have not run a compatibility test.

HostStatusNotes
claude-codementioned
codexmentioned
copilotmentioned
cursormentioned
gemini-climentioned
04

What it tells the agent

The instruction file, verbatim from the audited commit — this is the text the model reads, and the surface the audit's instruction layer examines. Quoted here so you can judge it without cloning anything.

---
name: tun-data
description: Refresh the data in an existing standalone dashboard, or add a new data source to it. Converts Excel to a usable format, re-extracts from the source, and rebuilds the file in place so existing share links keep working. Use when dashboard numbers are stale, someone asks to update or refresh a dashboard, or wants to point it at different data.
license: AGPL-3.0-or-later
---

# Refresh or repoint a dashboard's data

**Check first whether the user even needs you.** A paired dashboard can be updated with no
tooling at all, and if that fits, say so instead of doing it for them — it makes them
self-sufficient, which is worth more than one refresh.

## The no-tooling routes (offer these first)

**A — drag in, click, drag out.** No text editing, no terminal.

1. Open the dashboard and **drag a CSV onto it** — it re-renders immediately with the new
   numbers, and the header says *"ikke lagret ennå"* so nobody mistakes it for published.
2. Click **"Lagre datafil (data.js)"** — the dashboard writes the data file for you.
3. Drop that `data.js` into the SharePoint folder next to the dashboard.

Everyone with the folder synced sees the new numbers next time they open it. Norwegian
number formats (`1 500 000,50`) are parsed correctly, and a column only becomes numeric if
every value in it parses — so an ID column survives as text.

**B — edit `data.js` in Notepad.** For a small hand-maintained table. The file can carry
raw CSV between backticks:

```js
window.DASHBOARD_CSV = `Maaned,Region,Belop
2026-01,Oslo,1500000
2026-02,Bergen,2500000`;
window.DASHBOARD_META = {generated:"2026-03-01", source:"manuelt vedlikeholdt", rows:2};
```

Paste the new CSV between the backticks, save. That's the whole update.

## When you should do it (route C)

Take over when the refresh involves real work: re-running a query, joining sources,
aggregating to a coarser grain, or converting from Excel. Then continue below.

## Step 1 — Find out what it was built from

Read the provenance block at the top of the dashboard HTML:

```bash
head -30 <dashboard.html>
```

It records the source description, extract timestamp, build timestamp, requester, and
the row count. That tells you what to re-extract and what the numbers should roughly
look like afterwards.

If the provenance block is missing, the dashboard predates the convention or was
hand-edited. Reconstruct what you can, ask the user, and **make sure the rebuild has a
proper one.**

## Step 2 — Get fresh data in the same shape

The rebuild only works if the new extract has the **same column names**. If columns
were renamed or dropped upstream, the build will fail its own checks — that is the
intended behaviour, not a bug to work around. Tell the user what changed.

**Excel (`.xlsx`)** — convert to CSV. Prefer stdlib-free options in this order:

```bash
# 1. if the file is simple, ask the user to "Save As CSV" — zero tooling, zero risk
# 2. otherwise, a converter (only if already available):
python3 -c "
import openpyxl, csv, sys
wb = openpyxl.load_workbook(sys.argv[1], data_only=True)   # data_only: values, not formulas
ws = wb[sys.argv[3]] if len(sys.argv) > 3 else wb.active
with open(sys.argv[2], 'w', newline='', encoding='utf-8') as f:
    csv.writer(f).writerows(ws.values)
" input.xlsx output.csv [SheetName]
```

⚠️ `data_only=True` matters — without it you get formula strings instead of values and
every numeric column silently becomes text. If openpyxl is not installed, do not
install a toolchain on the user's behalf for a one-off; ask them to Save As CSV.

**A query (Snowflake etc.)** — re-run it and write the result to CSV. Record the query
text in `--source` so the next refresh is reproducible.

**Sanity-check the extract before rebuilding.** Row count in the same ballpark as last
time? If it dropped by 90%, something upstream broke — say so instead of shipping a
dashboard that quietly shows a tenth of the business.

## Step 3 — Rebuild in place

Same output path, same column arguments as the original build:

```bash
python3 tools/build_dashboard.py \
  --data <fresh.csv> \
  --title "<same title>" \
  --dimension <same> --measure <same> --time <same> \
  --filters <same> \
  --source "<source description>" \
  --requested-by "<original requester>" \
  --out <same/path/dashboard.html>
```

Overwriting the same path is deliberate: the share link keeps working and SharePoint
keeps the old version in its version history.

## Step 4 — Verify, then compare

```bash
python3 tools/verify_dashboard.py <dashboard.html>
```

Then do the thing that catches real errors: **compare the new headline number to the
old one** and say it out loud.

> "Total Belop went from 648 924 441 to 701 233 908 — up 8%, and there are 40 more rows.
> That looks like a normal month."

If a number moved implausibly, investigate before publishing. This comparison is the
main defence against a broken extract sailing through every automated check.

## Step 5 — Republish

Copy over the published copy (`tun-publish`), same path. Then tell the user what
the new "as of" date is, because that is the only thing that visibly changed.

## Adding a new data source

To combine sources, join them into one flat table **before** building — the template
takes a single array of rows. Do the join in Python or SQL, keep the joined extract, and
describe both sources in `--source`.

If the two sources have different grains, aggregate to the coarser one first. Silently
fanning out rows through a join is the fastest way to produce a dashboard that
double-counts.

## Automating a recurring refresh

If a dashboard needs refreshing on a schedule, that is a small pipeline: extract →
build → verify → copy to the synced folder. Only automate it once the manual path has
run cleanly a few times, and make sure a failure is **loud** — a silently stale
dashboard is worse than an obviously broken one, because people keep trusting it.
05

Trust audit

SAFEgrade B · trust 89/100 Nothing in the source contradicts what it says it does. Grade A is reserved for packages that have also passed the behavioural sandbox.

LayerWhat it checksResult
L0Provenance & inventoryPASS
L1Static analysis of the codePASS
L2Instruction surface (what it tells the agent)PASS
L3Class-specific surfacePASS
L4Behavioural (sandbox)SKIPPED

What the source does

Filesystem
declared (1 observation(s))
Network
declared (4 observation(s))
Shell
none-observed
Dependencies
pinned
Secrets in source
none-found

Findings (12)

LOWCode injection · code.eval_exec · CWE-78, CWE-94, CWE-95
tests/test_cube_reproducible.py:98
exec(SHIM.split("gzip.compress = older_build")[0], ns)  # defines older_build only
Why it matters. evaluates text as code
Fix. remove; use a parser or a dispatch table
LOWCode injection · code.eval_exec · CWE-78, CWE-94, CWE-95
tests/test_row_24_26_checks.py:284
exec(SOURCE[:start] + mutated + SOURCE[end:], ns)  # noqa: S102 — test harness
Why it matters. evaluates text as code
Fix. remove; use a parser or a dispatch table
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
examples/06-chart-palette-demo/build_chart_palette_demo.py:795
print("next: python3 ../../tools/verify_standalone_core.py " + str(OUT))
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
examples/07-topbar-pattern-demo/build_topbar_demo.py:343
print("next: python3 ../../tools/verify_standalone_core.py " + str(OUT))
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
examples/14-chart-emulation-nojs-poc/build_chart_emulation_poc.py:659
print("next: python3 ../../tools/verify_standalone_core.py " + str(OUT))
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
examples/15-chart-library-comparison-poc/build_comparison_poc.py:586
print("next: python3 ../../tools/verify_standalone_core.py " + str(OUT))
LOWObfuscation / stealth · obf.base64_blob · CWE-506, CWE-94
examples/03-cube-dashboard/transaksjoner.html:137
<script id="cube" type="application/gzip;base64">H4sIAAAAAAAC/xyaZVRUbduGDwwUYbqHoTsM7G6xu5vHQsTAJAwEBBSRVkCk7EBRTFBRbBQDu7u7O777/f45MOx9xVnXWqb0UpK6SM6hDwpOPFfSxVxJ4Twlv/LkXCrWMMNSwruPMtxjlawJ1PKtWMa
LOWObfuscation / stealth · obf.decode_call · CWE-506, CWE-94
tests/test_cube_reproducible.py:35
WANT_HEADER = bytes.fromhex("1f8b08000000000002ff")
LOWObfuscation / stealth · obf.decode_call · CWE-506, CWE-94
tests/test_cube_reproducible.py:77
return base64.b64decode(m.group(1))
LOWObfuscation / stealth · obf.decode_then_exec · CWE-506, CWE-94
tests/test_cube_reproducible.py:77
b64decode( ... subprocess.
Why it matters. decodes a payload and executes it
INFOPrompt injection · prompt.conditional_escalation · CWE-94, CWE-1427
skills/dev/magnus-review/SKILL.md:3
description: "Run an adversarial review of a PR, branch, or large diff through two senior-engineer lenses — Magnus (correctness: falsify the author's story, trace every finding through the actual code
INFOPrompt injection · prompt.conditional_escalation · CWE-94, CWE-1427
skills/dev/magnus-review/SKILL.md:13
When the user says "Magnus style review", "Magnus-style review", "run a Magnus review", "adversarial review", or asks for a senior/skeptical review of a PR, branch, or large diff that goes beyond veri

Gates applied: no_behavioural_pass.

Audited 2026-10-10 · audit v0.4.1 · source sha 141b38605b8bfull audit observations/trust-audit/skill/jkorsvik__tun.json · Report an issue / request a re-scan
06

Audit history

Every audit this skill has had.

DateSourceVerdictGradeScoreChange
2026-10-10141b38605b8bSAFEB89first audit
07

Questions

What does the tun skill do?

Tún: portable single-file dashboards for SharePoint and OneDrive

Is tun safe to install?

The audit found nothing in the source that contradicts what it says it does, and graded it B (89/100). Grade A is held back for packages that have also passed a sandboxed behavioural run, which is why a clean skill reads B.

What can tun access on my machine?

The audit observed that it reaches the network and reads or writes files. Each of those is consistent with what it says it does. Secrets in the source: none found.

Which assistants does tun work with?

Its documentation mentions claude-code, codex, copilot, cursor and gemini-cli. That is what the text claims, not a compatibility test we ran.

How current is this page?

The grade is for one exact copy of the source (141b38605b8b), read on 2026-10-10. The repository is watched, and a new audit runs when it changes — this is the first audit.

Advertisement