AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Fulcra Csv

skill-ashfulcra-fulcra-tools-fulcra-csv · by ashfulcra

Import any CSV stream into a Fulcra account as annotations — body weight, mood scores, expenses, sleep, media plays, anything timestamped. Use when the user wants to ingest a CSV they got from somewhere (a wearable, a Pipedream/IFTTT workflow, a hand-rolled spreadsheet) into Fulcra.

No reviews yet
0 installs
0 views
view→install

Install

$ agentstack add skill-ashfulcra-fulcra-tools-fulcra-csv

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access Used
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-ashfulcra-fulcra-tools-fulcra-csv)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
today

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Fulcra Csv? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

fulcra-csv — Import any CSV into Fulcra

fulcra-csv is a small CLI that maps CSV columns onto Fulcra annotation events. It's the foundation fulcra-media-helpers uses for its generic-csv importer, but it works standalone for any kind of data: weights, moods, expenses, water intake, custom logs.

This skill teaches you (the AI agent) how to import a CSV into the right Fulcra annotation type for a given user data shape.


Where to start — the re-entrancy probes

Before running an import, probe how far this user already got. The states are a prefix of the import flow — authed? → target def exists? → anything-to-import? → already landed? — so enter at the first probe that fails (per the repo's skill-quality pattern, docs/skill-quality-pattern.md). Every state is safely re-enterable: re-running an import produces the same deterministic sourceids, and the importer dedups against a readback of existing sourceids before posting (client-side, in run_import; see "Critical invariant" below), so there is never a penalty for re-probing or re-importing.

| Probe (run in order) | Command | Passes when | If it fails, enter at | |---|---|---|---| | Authed? | fulcra auth print-access-token | exits 0 and prints a non-empty token (the CLI mints/refreshes it; FULCRA_ACCESS_TOKEN in the env also satisfies this) | AUTH — tell the user to run fulcra auth login (interactive browser flow); see "Fulcra Life API auth" below | | Target picked + def exists? | for a user-defined/generic annotation, list the account's live annotation definitions and confirm the intended UUID is present: curl --oauth2-bearer "$(fulcra auth print-access-token)" https://api.fulcradynamics.com/user/v1alpha1/annotation (this is the GET annotations_catalog() reads; fulcra catalog lists data types, not user-defined annotation defs, so don't use it here). For a built-in type (--data-type BodyMass, etc.), no def is needed — this probe is N/A | the intended --definition-id UUID appears in the JSON with deleted_at null, OR the user is targeting a built-in type | BOOTSTRAP — mint a def with fulcra-csv bootstrap … (prints the UUID), then import against it. Do NOT bootstrap if the UUID is already present — that mints a duplicate def and splits the data | | Anything to import? | fulcra-csv import --dry-run with a target flag--definition-id (user-defined) or --data-type (built-in), plus the same column flags you'll use for the real run. --dry-run skips auth/ingest but the CLI still rejects the run with a UsageError if neither --definition-id nor --data-type is present (there'd be no target), so pass one even in dry-run | prints parsed N events … with N > 0 and the sampled rows look right (pure parse — no auth or ingest) | EXPORT/COLLECT — the file is empty or the column mapping is wrong; fix the flags or get a better file before importing | | Already landed? | fulcra-csv export --definition-id --start "30 days ago" --columns start_time,source_id (built-in target: pass --data-type instead) | rows come back that match the CSV you're about to import (compare source_id hashes or timestamps) | IMPORT — nothing landed yet; run fulcra-csv import … for real (drop --dry-run) |

All probes pass → the data is already imported; tell the user and point them at Context Web to browse it. A brand-new user fails the first probe. Note the fulcra auth print-access-token command (probe 1, and reused inside the probe-2 curl) belongs to the separate fulcra-api CLI; fulcra-csv has no auth subcommand of its own. Probe 2 hits the Fulcra Life API annotation endpoint (GET /user/v1alpha1/annotation on https://api.fulcradynamics.com, the host the fulcra-api client is what this repo's fulcra-common client resolves from FULCRA_API_BASE; the fulcra-api lib derives the same host from its OIDC audience) directly with curl — the same GET the CLI's annotations_catalog() performs — because neither fulcra nor fulcra-csv exposes a subcommand that lists user-defined annotation definitions.


Three target modes — pick first

Every import targets ONE of three places. Decide first which you're using:

1. User-defined annotation (most common)

You have (or will create) a custom Fulcra annotation definition. The CLI writes events under it. Pass --definition-id .

fulcra-csv bootstrap --name "Mood" --description "Mood self-reports" \
  --annotation-type instant --value-type int --tag mood
# → prints the new UUID; save it
fulcra-csv import mood.csv --definition-id  \
  --annotation-type instant --ts-col timestamp --value-col score \
  --value-type int --note-col context

2. Built-in Fulcra type (BodyMass, HeartRate, etc.) — no definition-id

When the user wants the data to land in Fulcra's native time series (the same place HealthKit imports go), pass --data-type and SKIP --definition-id. The CLI doesn't append an annotation-def source to the record, so dedup is purely source-id-based and CSV imports can coexist with HealthKit imports of the same kind without duplicating.

fulcra-csv import weights.csv --data-type BodyMass \
  --annotation-type instant --ts-col date --value-col kg --unit kg \
  --tag manual-scale

⚠️ Built-in-type writes assume the receiving schema matches. Check fulcra catalog for known data types. As of this skill's writing, the data-type write API is forthcoming — when shipped, BodyMass/HeartRate/StepCount/etc. are first-class targets.

3. Generic DurationAnnotation / InstantAnnotation — custom definition

Create a simple annotation definition with bootstrap, then import against it with --definition-id. The CLI writes plain DurationAnnotation events (or InstantAnnotation with --annotation-type instant). Useful for "throw a CSV in and forget" cases where the data does not belong to a built-in Fulcra type.

fulcra-csv bootstrap --name "Imported CSV"
# -> save UUID as $CSV_UUID
fulcra-csv import random.csv --definition-id $CSV_UUID

Annotation type — duration vs instant

--annotation-type duration (default) — events have start_time and end_time. For watches, listens, workouts (anything with a span). The CLI uses --end-col or --duration-col; falls back to a 1-second sentinel when neither is given (Fulcra silently drops zero-duration events).

--annotation-type instant — point-in-time. For weights, moods, single readings. recorded_at only has start_time. The --end-col / --duration-col flags error out if you pass them with --annotation-type instant.


Value column — for measurements

If the row has a numeric reading (weight, score, count), use --value-col to lift it into data.value. Coerce with --value-type {float,int,str,bool} (default float). Pair with --unit to add a constant data.unit.

# Body weight in kg
fulcra-csv import weights.csv \
  --data-type BodyMass --annotation-type instant \
  --ts-col date --value-col kg --value-type float --unit kg \
  --tag manual-scale

Empty value cells become None (not zero, not the string "").


Column mapping cheatsheet

| Flag | Purpose | Required? | |---|---|---| | --ts-col | Timestamp column header | Yes (default timestamp) | | --title-col | Title column header | No (default title); used for both note and title | | --subtitle-col | Subtitle (e.g. artist for music) | No; if set, note becomes subtitle – title | | --note-col | Override note column | No | | --value-col | Measurement value column | Only for value-bearing rows | | --value-type | float/int/str/bool | Default float; only matters with --value-col | | --unit | Constant unit string | Optional | | --end-col | Explicit end-time column | Optional (duration only) | | --duration-col | Duration-in-seconds column | Optional (duration only) | | --source-id-col | Per-content id column (mixed into source-id hash) | Optional | | --tag-col | Per-row tag column | Optional | | --tag | Default tag for all rows | Optional | | --data-field COL=KEY | Lift CSV column into data. | Repeatable | | --extra COL=KEY | Lift CSV column into data.external_ids[] | Repeatable | | --tz | IANA tz for naive timestamps | Default UTC | | --source-id-prefix | Override deterministic id prefix | Default com.fulcradynamics.csv.v1 | | --dry-run | Parse + print first 5 rows; don't ingest | Optional |


Bootstrap a new annotation definition

If the user wants a custom def, mint it first:

fulcra-csv bootstrap \
  --name "Concerts attended" \
  --description "Live music I went to" \
  --tag music --tag tickets
# → prints UUID

For measurement-bearing annotations, set --annotation-type and --value-type:

fulcra-csv bootstrap \
  --name "Resting Heart Rate (manual)" --description "Daily morning RHR" \
  --annotation-type instant --value-type int --unit bpm

Tags are auto-created if they don't exist.


Soft-delete a definition

fulcra-csv soft-delete  --confirm

⚠️ Fulcra has no per-event delete. Soft-deleting a definition removes the def from the user's account but its events stay visible in queries with their source_id pointing at the deleted def. For a true "reset," soft-delete + create a new def with a different source_id_prefix so future imports namespace cleanly. The fulcra-media sibling has a reset command that wraps this for the four media defs (Watched/Listened/Activity/Read).


Critical invariant: source_ids always include the timestamp

When --source-id-col is set, the column value is mixed into the hash with the timestamp, not used verbatim. Two plays of the same Spotify track at different times produce distinct events. Don't try to "preserve" content IDs in source_ids — they're hashed, idempotency is per-row, not per-content. (Content-level identity belongs in --extra content_fingerprint=fp.)

This is why re-running the same import is always safe — same input rows produce the same sourceids, and before posting each chunk run_import reads back the existing sourceids in that time window and skips the ones already present (client-side dedup, not a server-side guarantee). You don't need to track "have I imported this yet?"


Recipes — common shapes

Body weight (HealthKit-compatible)

date,kg
2026-05-01,82.4
2026-05-02,82.1
fulcra-csv import weights.csv \
  --data-type BodyMass --annotation-type instant \
  --ts-col date --value-col kg --value-type float --unit kg \
  --tag manual-scale

Mood entries

timestamp,score,note
2026-05-01T09:00:00Z,7,morning coffee good
2026-05-01T22:00:00Z,5,long day
fulcra-csv bootstrap --name "Mood" --annotation-type instant \
  --value-type int --tag mood
# → save UUID as $MOOD_UUID

fulcra-csv import mood.csv --definition-id $MOOD_UUID \
  --annotation-type instant --ts-col timestamp \
  --value-col score --value-type int --note-col note

Expenses with category tags

date,amount,merchant,category
2026-05-01,12.50,Blue Bottle,coffee
2026-05-01,38.00,Whole Foods,groceries
fulcra-csv bootstrap --name "Expenses" --annotation-type instant \
  --value-type float --unit usd --tag finance
# → save UUID

fulcra-csv import expenses.csv --definition-id $UUID \
  --annotation-type instant --ts-col date --value-col amount \
  --value-type float --unit usd \
  --title-col merchant --tag-col category

Pipedream / IFTTT play log (music)

ts,track,artist,track_id,url
2026-05-01T09:00:00Z,Reelin' In The Years,Steely Dan,1I7zHEdDx8Ny5RxzYPqsU2,https://...
fulcra-csv import plays.csv --definition-id $LISTENED_UUID \
  --ts-col ts --title-col track --subtitle-col artist \
  --source-id-col track_id --tag spotify --extra url=spotify_url

Sleep durations

start,end,quality
2026-05-01T23:00:00Z,2026-05-02T07:30:00Z,8
fulcra-csv bootstrap --name "Sleep" --tag sleep
fulcra-csv import sleep.csv --definition-id $UUID \
  --ts-col start --end-col end --value-col quality --value-type int

Export — round-tripping annotations back to CSV

fulcra-csv export is the inverse of import. Reach for it when the user wants to:

  • Round-trip a CSV — re-export what an import landed to verify columns/values look right, or to hand the data to another tool that expects CSV.
  • Audit a recent import — pull the last day/week of a definition and eyeball it instead of running ad-hoc API queries.
  • Slice for a downstream tool — pull a configurable subset of fields (well-known + data. + external_ids.) into a tidy CSV, optionally with epoch timestamps.

It is NOT a sync/backfill mechanism — it's a one-shot read. If the user wants ongoing sync, point them at a scheduled job that runs export on a window.

Target — same model as import

Pass ONE of:

  • --definition-id — scope to a user-defined annotation. The CLI fetches the underlying data type (default DurationAnnotation) and filters records whose sources array references the target def. This mirrors the importer's dedup-readback, so what you see in export is what import would dedup against.
  • --data-type — pull a built-in time series (BodyMass, HeartRate, DurationAnnotation, etc.).

--start is required (ISO-8601 or relative — "1 week ago", "yesterday"). --end defaults to now.

Column model — --columns col1,col2,...

Default: start_time,end_time,tag,note,value. Each entry resolves against the record in one of three ways:

| Form | Source | |---|---| | Well-known field (start_time, end_time, note, title, value, unit, tag, tags, category, source_id, definition_id, ...) | Top-level on the record, or normalised from recorded_at (timestamps), tag_names (tags), sources (sourceid / definitionid). | | data. | The (possibly JSON-encoded) data payload — the place --data-field COL=KEY lifts to on import. | | external_ids. | data.external_ids[] — symmetric to import's --extra COL=KEY. |

source_id is special: it returns the first non-definition source (the per-row dedup key), so you can round-trip dedup hashes back through.

Other knobs

  • --date-format iso|epoch|local (default iso, always UTC with Z suffix). epoch writes integer seconds. local honors --tz.
  • --tz — used for parsing relative --start/--end and for --date-format local.
  • --out — file output. Omit for stdout.

CSV-injection guard

By default, cells starting with = + - @ \t \r are prefixed with a single quote (') so Excel/Sheets/Numbers don't interpret them as formulas. This is OWASP-grade defense-in-depth and on by default. The library exposes ExportOptions.guard_formulas=False for callers feeding CSV into a downstream parser that doesn't need it; the CLI does NOT expose a flag — keep it on for spreadsheets. Booleans render as lowercase "true"/"false" so they round-trip through coerce_value.

Worked example — audit yesterday's mood imports

The user just ran fulcra-csv import mood.csv --definition-id $MOOD_UUID ... and wants to see what landed:

fulcra-csv export \
  --definition-id $MOOD_UUID \
  --start yesterday \
  --columns start_time,value,note,tag,source_id \
  --date-format local --tz America/New_York

Stdout is a CSV with five columns, timestamps in the user's wall-clock TZ, plus source_id so they can match rows against the importer's deterministic hashes.

Pitfalls

  • Export does NOT trigger a re-fetch from the source. It reads what Fulcra has. If a recent import is still propagating through ingest, you may see fewer rows than you posted — wait a minute and re-run.
  • --definition-id requires the underlying data_type to match. The export defaults to DurationAnnotation; if the user bootstrapped an instan

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.