Command-line client and example scripts for the Tark Platform API.
Authenticates against any Tark deployment using a Personal Access Token (PAT). Single-file Python script, stdlib only — no pip install required.
New to this / non-technical? See ONBOARDING.md — it gets you researching prospects and firing leads with zero developer setup (no git, Homebrew, or Xcode). Easiest path: open https://claude.ai/code and pick this repo.
You only need Python 3 (stdlib only — no pip install). git is optional.
# 1a. Get the CLI WITHOUT git (one file, public repo):
curl -fsSL https://raw.githubusercontent.com/TarkToostus/automation/main/tark_cli.py -o tark_cli.py
chmod +x tark_cli.py
# 1b. ...or with git, if you have it:
# git clone https://github.com/TarkToostus/automation.git && cd automation
# 2. Configure (replace with your deployment + PAT)
./tark_cli.py config set url https://your-deployment.example.com
./tark_cli.py config set pat tark_pat_xxxxxxxxxxxxx
# 3. First call
./tark_cli.py tasksOne-shot installer (Claude Code + CLI + config, no git/Node/Xcode):
curl -fsSL https://raw.githubusercontent.com/TarkToostus/automation/main/setup.sh | bash(Windows:irm https://raw.githubusercontent.com/TarkToostus/automation/main/setup.ps1 | iex)
Get a PAT from your deployment: Profile → Security → API keys → Add token. Treat it like a password. It inherits your user's permissions.
Optional. Symlink it onto your PATH:
mkdir -p ~/bin
ln -sf "$PWD/tark_cli.py" ~/bin/tark_cli
# ~/bin must be on PATH — if `tark_cli` isn't found, add this to ~/.zshrc and reopen the shell:
# export PATH="$HOME/bin:$PATH"
tark_cli tasks| Command | What it does |
|---|---|
tark_cli tasks |
My open tasks across all projects |
tark_cli tasks --project Sigma |
Tasks in a specific project |
tark_cli tasks --board 100 --status WORK --all |
Board-scoped column listing (follows pagination; a project can hold several boards) |
tark_cli task 123 |
Task detail |
tark_cli deps 200 |
Task dependencies, both directions (blocked by / blocks) |
tark_cli deps 200 add --blocker 201 |
Record a blocker (needs pm:write) |
tark_cli deps 200 remove --blocker 201 |
Drop a blocker (needs pm:delete) |
tark_cli create <project> "Subject text" |
Create a task |
tark_cli timer |
Active timer state |
tark_cli start 123 / stop / discard |
Timer control |
tark_cli log 1.5 123 "fixed bug" |
Log 1.5h to task #123 |
tark_cli time week |
Weekly time report grouped by project |
tark_cli projects / boards / columns |
Browse PM structure |
tark_cli boards-create <project_id> "Board name" |
Create a board (needs pm:write) |
tark_cli comment <task_id> "text" |
Add a task comment (needs pm:write) |
tark_cli task-comment <id> / time-entry <id> |
Retrieve one comment / time entry |
tark_cli time-update <id> --hours 2.5 --description "..." |
Patch a time entry (sparse, needs pm:write) |
tark_cli task-delete <id> [--yes] |
Delete a task (needs pm:delete, destructive - confirms unless --yes) |
tark_cli time-delete <id> [--yes] |
Delete a time entry (needs pm:write, destructive) |
tark_cli sites-active --domains a.tt.ee,b.tt.ee [--window 15m] |
Sites active-now (needs c2:read) |
tark_cli contract-blocks |
List contract blocks (system, sales:read) |
tark_cli <lead|offer|offer-line|contract|pipeline|pipeline-stage|email-task> <id> |
Retrieve one sales record by ID |
tark_cli <client|user|contract-type|contract-block|contract-template|column> <id> |
Retrieve one system/PM record by ID |
tark_cli offer-line-delete <id> [--yes] |
Delete an offer line (needs sales:write, destructive) |
tark_cli leads / offers / contracts |
Browse CRM |
tark_cli leads create --title "Acme retrofit" --company "Acme OÜ" --pipeline Imports --source COLD |
Create a lead (needs sales:write PAT) |
tark_cli leads-update <id> --status QUALIFIED --pipeline-stage 4 |
Patch a lead (sparse, only sends given flags) |
tark_cli leads-ingest --pipeline Imports --leads '[{"title":"..."}]' |
Batch-create leads (dedupes by title) |
tark_cli offers-create --title "..." --client 5 --amount 1500 / offers-update <id> ... |
Create / patch an offer |
tark_cli offer-lines-create --offer 3 --description "..." --quantity 2 --unit-price 99 / offer-lines-update <id> ... |
Create / patch an offer line |
tark_cli contracts-create --title "..." --client 2 --template 1 / contracts-update <id> ... |
Create / patch a contract (content-JSON via api) |
tark_cli email-tasks-create --lead 12 --subject "..." --body "..." --status REVIEW |
Draft a sales email (never confirms/sends) |
tark_cli followups-check |
Run the due-follow-up check now — creates DRAFT EmailTasks for due leads |
tark_cli email-tasks -f DRAFT |
List scheduled sales emails (the follow-up engine), filter by status |
tark_cli email-task-set <id> --body "..." --status REVIEW |
Edit a draft email's body/subject/status |
tark_cli wiki 123 |
Fetch task wiki markdown |
tark_cli wiki 123 set --section Brief --body "..." |
Upsert a wiki section (preferred — dup-safe) |
tark_cli wiki 123 append --section Brief --body "..." |
Append; refuses if section already exists |
tark_cli wiki 123 replace --section Brief --body "..." |
Replace existing section's body |
tark_cli wiki 123 delete --section Brief |
Dry run: show what a delete would remove (exits non-zero) |
tark_cli wiki 123 delete --section Brief --yes |
Remove the section — DESTRUCTIVE, needs a pm:delete PAT scope |
tark_cli stage 123 work |
Advance task stage (server gates on wiki sections) |
tark_cli update 123 --priority high --assignee 38 |
PATCH common task fields |
tark_cli ingest <project> <board> --tasks-file tasks.json |
Bulk-create tasks (dedupes by subject) |
tark_cli api <path> |
Generic GET against any /api/v1/pat/<path>/ endpoint |
tark_cli api <path> --post '{...}' / --patch '{...}' |
Generic POST / PATCH escape hatch |
tark_cli api "pm/tasks/?board=48&page=2" |
Inline query string; merges with --filter (flag wins on key clash) |
Run tark_cli --help for the full list, or tark_cli <command> --help for flags.
PAT create/list/revoke need web login (JWT), not a PAT — a token can never mint or revoke tokens (privilege escalation). These mirror the web UI:
tark_cli tokens # list your PATs (prompts for login)
tark_cli tokens scopes # scope -> capability map (works offline)
tark_cli tokens create --name ci-bot --scope pm:write --scope sales:read [--expires 2026-12-31]
tark_cli tokens revoke <id> [--yes] # soft-revoke (is_active=False), destructive- Username —
--user, else configuser(tark_cli config set user <name>), else prompt. - Password —
getpassprompt, or$TARK_PASSWORDfor automation. Never stored, never written to config; the JWT lives in memory for the one request. Put secrets in~/.tark-secrets.env, never inline. tokens createprints the token once — store it immediately.
Run the tests with python3 -m unittest tests.test_capas_and_tokens (or list the
tests.test_* modules explicitly — plain discover tests collides with the root-level
test_website_cli.py).
The platform runs an in-product sales follow-up cadence. A due lead becomes a
DRAFT EmailTask — a first-class scheduled email whose body IS the verbatim
email (no transform between confirm and send), pre-filled with the lead's
details ({company}, {name}). Two independent send gates protect it:
DRAFT → REVIEW → CONFIRMED → SENT / FAILED
(you draft) (human (platform sender:
confirms + status==CONFIRMED
sets time) AND send_at<=now)
Automation drives the DRAFT → REVIEW half only. Confirmation is a human gate — not available over a PAT at all (the serializer blocks a PAT from setting CONFIRMED/SENT/FAILED), and only the platform sender writes SENT/FAILED. Two surfaces:
tark_cli followups-check # create DRAFT EmailTasks for every due lead now
tark_cli email-tasks -f DRAFT # list scheduled emails by status
# The gate-safe assistant helper — writes the body and moves DRAFT -> REVIEW.
# It HARD-REFUSES any move into CONFIRMED / SENT / FAILED (the human + sender own those).
./sales_followup.py list
./sales_followup.py draft 123 --subject "Acme + Tark — next step" --body "Tere, Anna! ..."followups-check needs a PAT with sales:write scope held by a user with the
sales.change_salesconfig permission; the email-tasks / email-task-set
commands need sales:write.
Three ways to provide credentials, checked in order:
- Environment variable —
TARK_PAT=tark_pat_... TARK_URL=https://...(legacyC2_PAT/C2_URLstill work) - Config file —
~/.config/tark/config.json(chmod 600), set viatark_cli config set - No default — set the URL via
tark_cli config set url ...orTARK_URL(legacyC2_URL); the CLI errors with guidance if none is configured
Run tark_cli config to see what's currently in effect.
Pass --json before the subcommand to get raw JSON for piping into jq:
tark_cli --json tasks --project Sigma | jq '.[] | {id, name, hours: .total_hours}'Without --json, output is human-readable tables.
See examples/ for runnable scripts:
01_dashboard.sh— timer + open tasks + today's time, in one view02_log_time.sh— log time entries, with task picking03_timer.sh— start/stop a timer around a unit of work04_batch_ingest.sh— bulk-create tasks from a JSON file (idempotent)05_weekly_report.sh— time totals grouped by project, for invoicing
This CLI is open source. The security boundary lives at the API:
- PATs are scoped to your user. They cannot escalate privileges. Revoke them anytime in the UI.
- Tenant isolation is enforced server-side. A PAT for tenant A cannot read tenant B data.
- Treat your PAT like a password. Don't commit it. Don't paste it in screenshots. Rotate if exposed.
- Audit log — every PAT call is logged with timestamp + IP + endpoint.
If you find a security issue, email security@tarktoostus.ee rather than opening a public issue.
PAT auth covers the workflow API surface:
pm/— projects, boards, columns, tasks, task dependencies, comments, time entries, timersales/— leads, offers, offer-lines, contracts, pipelines, email-tasks (the follow-up engine), enqueue trigger (config/enqueue-followups)system/— clients, contract-types, contract-templatesc2/— deployment status (read-only)
Some endpoints (analytics, token management, ingest webhooks) require JWT auth via the web UI rather than PAT. The CLI surfaces a hint when you hit one.
MIT. See LICENSE.