Best for
- 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.
ashfulcra/fulcra-tools/packages/csv-importer/skills/fulcra-csv/SKILL.md
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.
Decision brief
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.
Compatibility matrix
| Platform | Status | Evidence | What to check |
|---|---|---|---|
| Codex | Not declared | No explicit evidence | Portability before use |
| Claude Code | Not declared | No explicit evidence | Portability before use |
| Cursor | Not declared | No explicit evidence | Portability before use |
| Gemini CLI | Not declared | No explicit evidence | Portability before use |
Installation
The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.
npx skills add https://github.com/ashfulcra/fulcra-tools --skill "packages/csv-importer/skills/fulcra-csv"Inspect the Agent Skill "fulcra-csv" from https://github.com/ashfulcra/fulcra-tools/blob/1b2d4915b89ce1fc03ce310e461330cc37552128/packages/csv-importer/skills/fulcra-csv/SKILL.md at commit 1b2d4915b89ce1fc03ce310e461330cc37552128. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.
Workflow
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-pat…
Every import targets ONE of three places. Decide first which you're using:
You have (or will create) a custom Fulcra annotation definition. The CLI writes events under it. Pass --definition-id .
fulcra-csv import mood.csv --definition-id \ --annotation-type instant --ts-col timestamp --value-col score \ --value-type int --note-col context bash fulcra-csv import weights.csv --data-type BodyMass \ --annotation-type instant --ts-col date --value-col kg --unit kg \ --tag ma…
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 wit…
Permission review
The documentation asks the agent to run terminal commands or scripts.
| Probe (run in order) | Command | Passes when | If it fails, enter at |The documentation asks the agent to run terminal commands or scripts.
| Anything to import? | `fulcra-csv import <csv-path> --dry-run` **with a target flag** — `--definition-id <uuid>` (user-defined) or `--data-type <Name>` (built-in), plus the same column flags you'll use for the real run. `--dry-run` skips The documentation includes network, browsing, or remote request actions.
2026-05-01T09:00:00Z,Reelin' In The Years,Steely Dan,1I7zHEdDx8Ny5RxzYPqsU2,https://...Evidence record
| Signal | Value | Evidence type | Meaning |
|---|---|---|---|
| Quality score | 93/100 | Computed | Documentation, specificity, maintenance, and trust rules |
| Repository stars | 8 | Source | Repository attention, not individual Skill quality |
| Compatibility | 0 platforms | Source | Declared in the catalog source record |
| Usage guide | automated source guide | Editorial | Generated or reviewed according to the visible evidence level |
Pinned source
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.
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 source_ids, and the importer dedups against a readback of existing source_ids
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 <csv-path> --dry-run with a target flag — --definition-id <uuid> (user-defined) or --data-type <Name> (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 <uuid> --start "30 days ago" --columns start_time,source_id (built-in target: pass --data-type <Name> 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.
Every import targets ONE of three places. Decide first which you're using:
You have (or will create) a custom Fulcra annotation definition. The CLI writes events under it. Pass --definition-id <uuid>.
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 <uuid> \
--annotation-type instant --ts-col timestamp --value-col score \
--value-type int --note-col context
When the user wants the data to land in Fulcra's native time series (the same place HealthKit imports go), pass --data-type <Name> 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.
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 (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.
If the row has a numeric reading (weight, score, count), use --value-col <name> to lift it into data.value. Coerce with --value-type {float,int,str,bool} (default float). Pair with --unit <string> 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 "").
| 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.<key> | Repeatable |
--extra COL=KEY | Lift CSV column into data.external_ids[<key>] | 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 |
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.
fulcra-csv soft-delete <uuid> --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).
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 source_ids, and before posting each chunk run_import reads back the existing source_ids 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?"
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
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
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
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
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
fulcra-csv export is the inverse of import. Reach for it when the user wants to:
data.<key> + external_ids.<key>) 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.
Pass ONE of:
--definition-id <uuid> — 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 <Name> — 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.
--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 (source_id / definition_id). |
data.<key> | The (possibly JSON-encoded) data payload — the place --data-field COL=KEY lifts to on import. |
external_ids.<key> | data.external_ids[<key>] — 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.
--date-format iso|epoch|local (default iso, always UTC with Z suffix). epoch writes integer seconds. local honors --tz.--tz <iana> — used for parsing relative --start/--end and for --date-format local.--out <path> — file output. Omit for stdout.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.
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.
--definition-id requires the underlying data_type to match. The export defaults to DurationAnnotation; if the user bootstrapped an instant annotation, pass --data-type InstantAnnotation alongside --definition-id.' prefix is a real cell value. The data round-trips through coerce_value, but the CSV bytes differ from the original import file.CSV imports are inherently one-shot ("import this file"), not incremental. There's no watermark layer (that lives in fulcra-media for its API-poll importers).
For agent-driven recurring sync of a CSV that grows over time (e.g. a Pipedream workflow appending rows to a Drive sheet):
fulcra-csv import <tempfile> ... — re-imports are idempotent.The --dry-run flag is useful for a cheap "any new rows?" check (it parses and prints the first 5, doesn't ingest).
Same as fulcra-media: shells out to fulcra auth print-access-token from the fulcra-api package, or honors FULCRA_ACCESS_TOKEN=... for non-interactive contexts.
You forgot --definition-id <uuid> AND didn't set --data-type AND --annotation-type is duration. Either bootstrap a def, or pass --data-type DurationAnnotation explicitly.
fulcra get-recordsFulcra's ingest-to-query indexing lag (seconds to minutes for bulk imports). Re-run shouldn't help — wait a few minutes and re-query.
You're probably using --source-id-col for a per-row id (database row uuid) and expecting per-row uniqueness. The CLI ALWAYS mixes timestamp into the hash. For truly stable per-row identity, ensure the (timestamp, id) pair is stable across imports. If the row's timestamp changes between exports, you'll get distinct source_ids — that's intentional but maybe not what you want.
dateparser can't parse the timestamp column. Try --tz America/New_York if rows are local-time-no-tz. Worst case, pre-process the CSV to ISO 8601.
fulcra_csv.events — GenericEvent, ColumnMap, coerce_valuefulcra_csv.parser — parse_csv(path, column_map, tz, annotation_type)fulcra_csv.fulcra — FulcraClient (auth, ingest, dedup readback, per-chunk verification)fulcra_csv.confidence — apply_cluster_policy, find_low_conf_twins, apply_twin_decisions (used by fulcra-media's Trakt importer, but available standalone)fulcra_csv.cli — click-based CLI entry--definition-id into a script as a constant — the user might soft-delete + re-bootstrap, changing the UUID. Always read it from a fresh fulcra-csv bootstrap or from the user's state.run_import already reads back existing source_ids per window and skips duplicates before posting. Just re-run the import; already-landed rows are skipped silently.data.note from a column the user didn't intend to expose — --note-col is opt-in.--value-type bool for "0/1" rows unless that's truly the schema — bool collapses any non-truthy string to false; for diary-style yes/no events use int (0/1).Frequently asked questions
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.
The source record exposes this install command: npx skills add https://github.com/ashfulcra/fulcra-tools --skill "packages/csv-importer/skills/fulcra-csv". Inspect the command and pinned source before running it.
Static rules flagged exec-script, network in the source; the page lists the matching lines and excerpts.
Alternatives
K-Dense-AI/scientific-agent-skills
Distributed computing for larger-than-RAM pandas/NumPy workflows. Use when you need to scale existing pandas/NumPy code beyond memory or across clusters. Best for parallel file processing, distributed ML, integration with existing pandas code. For out-of-core analytics on single machine use vaex; for in-memory speed use polars.
getcargohq/cargo-skills
Make Cargo actually run something, or show what it would run — execute one connector action, run a multi-step workflow, trigger a batch across a whole segment or model, message an AI agent, build or edit a node graph, draw a workflow, tool or play as a diagram, and query the runtime tables (runs, batches, spans, records) with SQL. Triggers: "run this on all my contacts", "execute the action", "kick off a batch", "build a workflow", "schedule a play", "make it run every morning", "ask the agent",
NVIDIA/skills
Use this skill when the user wants to deploy, run, debug, tear down, or call the REST API of the RTVI-CV 2D detection / tracking microservice. Trigger when the user says things like 'deploy rtvi-cv', 'start warehouse 2d', 'add a stream', 'check rtvi-cv health', or 'stop the perception container'. Not for VLM, embedding, or analytics — use the matching vss-* skill.
UiPath/skills
UiPath Coded Apps — scaffold, build, run, and deploy Coded Web Apps and Coded Action Apps: React/TypeScript apps that call UiPath Cloud APIs via the `@uipath/uipath-typescript` SDK and ship to Automation Cloud (push/pull to Studio Web, pack, publish, deploy, OAuth-PKCE). Also generates live analytics & governance dashboards from a plain-language request, wired to tenant data via the Insights real-time API, with edit and deploy flows. For RPA→uipath-rpa, Python agents→uipath-agents, Maestro flows