Metadata-Version: 2.4
Name: karma365
Version: 0.2.0
Summary: Add to your KARMA 365 library from Python. Your data stays on your phone.
License: MIT
Project-URL: Homepage, https://github.com/ayushkotadiya5499/karma365-sdk
Project-URL: Bug Tracker, https://github.com/ayushkotadiya5499/karma365-sdk/issues
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: cryptography>=42
Requires-Dist: httpx>=0.25

# KARMA 365 SDK for Python

Add to your KARMA 365 library from Python: results, plots, videos, notes and tasks.

Your data stays on your phone. This SDK has no database and no cloud. It talks to the
app over your own Wi-Fi, hotspot or USB, and everything on the way is encrypted.

## Before you start

1. Open KARMA 365 on your phone.
2. Go to Settings, AI MCP, and turn on **Allow PC connection**.
3. Copy the token, and put it in an environment variable:

```bash
export KARMA365_TOKEN="your token"          # macOS, Linux
setx KARMA365_TOKEN "your token"            # Windows, then open a new terminal
```

**Keep the token out of your code, and off GitHub.** Whoever holds it, on the same
network as your phone, can read and change everything that is not PIN-locked. If it
leaks, make a new one in the app. The old one stops working at once.

The phone must be on, with the app unlocked, on the same network as this PC.

## Is it connected?

```bash
python -m karma365 status
```

```python
status = await karma.status()
print(status.connected, status.reason, status.message, status.hint)
```

`connected` is true only after your phone has answered with your token's key, so
another device cannot pass as your phone. The answer is true for that moment; if the
Wi-Fi goes later, the next call says so.

In the app, Settings, AI MCP shows the last connection, and a file being received.

## Use it

With `await`:

```python
import asyncio
from karma365 import AsyncKarmaClient

async def main():
    async with AsyncKarmaClient() as karma:
        topic = await karma.find_minor_id("Machine Learning")
        await karma.add_image(topic, "outputs/confusion_matrix.png", caption="Accuracy 96.4%")
        await karma.create_task("Train ResNet-50", priority="high")

asyncio.run(main())
```

Without `await`, for scripts and notebooks. It works inside Jupyter too:

```python
from karma365 import KarmaClient

karma = KarmaClient()
karma.create_task("Train ResNet-50", priority="high")
```

## Large files

```python
await karma.add_video(
    "Machine Learning", "lecture_8gb.mp4", title="Lecture 1",
    on_progress=lambda sent, total: print(f"{sent * 100 // total}%"),
)
```

- Any size. The limit is your phone's free space, which is checked before anything is sent.
- The file goes in encrypted pieces, so it never fills your memory.
- **If it stops, run the same call again.** Only the missing pieces are sent.
- Nothing appears in the app until the whole file has arrived.
- On the phone, a notification keeps it going while the screen is off. Android can
  still close the app, Oppo phones especially. Open it again and repeat the call.

`await` does not itself make a file faster. It keeps your program free, and lets
several pieces travel at once. The speed comes from your Wi-Fi and your phone. Measure
yours, using a test file that is deleted afterwards:

```bash
python -m karma365 speedtest 100
```

This needs a minor topic to exist, which the test file passes through.

## Locked items

The phone checks every PIN, and allows 6 wrong tries before a short wait.

```python
import getpass
await karma.unlock(getpass.getpass("PIN: "))   # kept in memory until lock() or close()
await karma.list_resources("Private", pin=my_pin)   # or for one call
```

Do not write the PIN in your code. Without it, locked rows say only that they are
locked, and `is_locked` is true on them. The PIN can only be set or changed in the app.

## Errors

Every error says what happened and what to do, and has a `code` that never changes.

```python
from karma365 import KarmaError, PhoneNotFoundError

try:
    await karma.add_video("ML", "lecture.mp4")
except PhoneNotFoundError as e:
    print(e.hint)
except KarmaError as e:
    print(e.code, e.message, e.details)
```

| Code | Meaning |
|---|---|
| `TOKEN_MISSING`, `TOKEN_REJECTED` | No token, or the phone did not accept it |
| `CLOCK_OUT_OF_SYNC` | This PC's clock and the phone's are too far apart |
| `PHONE_NOT_FOUND` | No answer. App closed, Allow PC off, or another network |
| `NOT_YOUR_PHONE` | Something answered, but could not prove it is your phone |
| `APP_LOCKED` | Unlock the app on your phone |
| `CONNECTION_LOST`, `PHONE_TIMEOUT` | Lost during a call. A change may have happened, so check before repeating it |
| `APP_UPDATE_NEEDED` | The app on your phone is too old for this |
| `PIN_REQUIRED`, `PIN_WRONG`, `PIN_LOCKED_OUT`, `PARENT_LOCKED` | About locks |
| `NOT_FOUND`, `AMBIGUOUS_NAME`, `INVALID_INPUT` | About what you passed |
| `FILE_NOT_FOUND`, `FILE_NOT_ALLOWED`, `FILE_CHANGED` | About the file on this PC |
| `PHONE_STORAGE_FULL`, `UPLOAD_INTERRUPTED` | About sending a file |
| `PHONE_ERROR` | Unexpected. Set `KARMA365_DEBUG=1` to see more |

Reads, and pieces of a file, are tried again by themselves. A create or a delete is
not, because the first try may have worked. A wrong PIN is never tried again, so a
script cannot use up your tries.

## Deleting locked items

Something locked is never deleted, not even with the PIN. Neither is something under a
lock, or something holding a locked item. Remove the lock first, then delete. Until then
the call fails with `PARENT_LOCKED`.

## Names and IDs

Anything that changes something needs an ID, or a whole name that fits only one item.
Part of a name works only in `find_id()` and `search()`, so a short word can never
delete the wrong thing.

## Methods

**Connection:** `status`, `is_connected`, `unlock`, `lock`, `close`

**Finding:** `find_id`, `find_minor_id`, `find_major_id`, `get_info`, `search`

**Topics:** `create_major`, `create_minor`, `list_majors`, `list_minors`, `get_major`,
`get_minor`, `change_name`, `change_minor_name`, `change_major_name`, `change_color`,
`change_minor_color`, `change_major_color`, `change_dp`, `change_difficulty`,
`transfer_minor_to`, `transfer_to`, `pin`, `unpin`, `star`, `unstar`, `delete`, `clean_empty`

**Resources:** `add_image`, `add_video`, `add_audio`, `add_pdf`, `add_youtube`, `add_link`,
`add_code`, `add_note`, `append_note`, `list_resources`, `view_all`, `get_resource`,
`change_description`, `mark_done`, `unmark_done`

**Tasks:** `create_task`, `list_tasks`, `get_task`, `change_priority`, `mark_done`,
`unmark_done`, `delete`, `clear_completed_tasks`

**Stats:** `get_stats`

## Settings

| Variable | Use |
|---|---|
| `KARMA365_TOKEN` | Your token |
| `KARMA365_PHONE_URL` | The phone's address, if it is not found by itself |
| `KARMA365_DEBUG` | Set to 1 to add the technical cause to errors. Never shows your token or PIN |
