creatorloop 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {creatorloop-0.1.0 → creatorloop-0.2.0}/CHANGELOG.md +15 -1
- {creatorloop-0.1.0 → creatorloop-0.2.0}/PKG-INFO +51 -15
- {creatorloop-0.1.0 → creatorloop-0.2.0}/README.md +50 -14
- {creatorloop-0.1.0 → creatorloop-0.2.0}/README.ru.md +4 -4
- {creatorloop-0.1.0 → creatorloop-0.2.0}/README.uz.md +4 -4
- {creatorloop-0.1.0 → creatorloop-0.2.0}/server.json +2 -2
- {creatorloop-0.1.0 → creatorloop-0.2.0}/src/creatorloop/__init__.py +1 -1
- {creatorloop-0.1.0 → creatorloop-0.2.0}/src/creatorloop/auth_cli.py +40 -11
- {creatorloop-0.1.0 → creatorloop-0.2.0}/src/creatorloop/comments.py +3 -4
- {creatorloop-0.1.0 → creatorloop-0.2.0}/src/creatorloop/config.py +2 -2
- {creatorloop-0.1.0 → creatorloop-0.2.0}/src/creatorloop/env.example +6 -8
- creatorloop-0.2.0/src/creatorloop/env_file.py +35 -0
- {creatorloop-0.1.0 → creatorloop-0.2.0}/src/creatorloop/graph_client.py +2 -1
- {creatorloop-0.1.0 → creatorloop-0.2.0}/src/creatorloop/instagram.py +5 -2
- creatorloop-0.2.0/src/creatorloop/meta_auth.py +181 -0
- {creatorloop-0.1.0 → creatorloop-0.2.0}/src/creatorloop/server.py +61 -3
- {creatorloop-0.1.0 → creatorloop-0.2.0}/src/creatorloop/setup_cli.py +33 -4
- creatorloop-0.2.0/src/creatorloop/setup_i18n.py +187 -0
- creatorloop-0.2.0/src/creatorloop/setup_page.html +184 -0
- creatorloop-0.2.0/src/creatorloop/setup_web.py +377 -0
- creatorloop-0.2.0/tests/meta_fakes.py +44 -0
- {creatorloop-0.1.0 → creatorloop-0.2.0}/tests/test_api.py +12 -1
- creatorloop-0.2.0/tests/test_auth_cli.py +83 -0
- {creatorloop-0.1.0 → creatorloop-0.2.0}/tests/test_comments.py +3 -1
- creatorloop-0.2.0/tests/test_env_file.py +91 -0
- creatorloop-0.2.0/tests/test_meta_auth.py +216 -0
- {creatorloop-0.1.0 → creatorloop-0.2.0}/tests/test_server.py +154 -1
- {creatorloop-0.1.0 → creatorloop-0.2.0}/tests/test_setup_cli.py +93 -0
- creatorloop-0.2.0/tests/test_setup_i18n.py +47 -0
- creatorloop-0.2.0/tests/test_setup_web.py +413 -0
- {creatorloop-0.1.0 → creatorloop-0.2.0}/.gitignore +0 -0
- {creatorloop-0.1.0 → creatorloop-0.2.0}/LICENSE +0 -0
- {creatorloop-0.1.0 → creatorloop-0.2.0}/pyproject.toml +0 -0
- {creatorloop-0.1.0 → creatorloop-0.2.0}/src/creatorloop/__main__.py +0 -0
- {creatorloop-0.1.0 → creatorloop-0.2.0}/src/creatorloop/analytics.py +0 -0
- {creatorloop-0.1.0 → creatorloop-0.2.0}/src/creatorloop/paths.py +0 -0
- {creatorloop-0.1.0 → creatorloop-0.2.0}/src/creatorloop/post_queue.py +0 -0
- {creatorloop-0.1.0 → creatorloop-0.2.0}/src/creatorloop/publish.py +0 -0
- {creatorloop-0.1.0 → creatorloop-0.2.0}/src/creatorloop/refs.py +0 -0
- {creatorloop-0.1.0 → creatorloop-0.2.0}/src/creatorloop/reviews.py +0 -0
- {creatorloop-0.1.0 → creatorloop-0.2.0}/src/creatorloop/storage.py +0 -0
- {creatorloop-0.1.0 → creatorloop-0.2.0}/src/creatorloop/tunnel.py +0 -0
- {creatorloop-0.1.0 → creatorloop-0.2.0}/tests/__init__.py +0 -0
- {creatorloop-0.1.0 → creatorloop-0.2.0}/tests/conftest.py +0 -0
- {creatorloop-0.1.0 → creatorloop-0.2.0}/tests/fixtures/business_discovery_v26.json +0 -0
- {creatorloop-0.1.0 → creatorloop-0.2.0}/tests/fixtures/media_insights_reels_v26.json +0 -0
- {creatorloop-0.1.0 → creatorloop-0.2.0}/tests/fixtures/tiny-reel-faststart.mp4 +0 -0
- {creatorloop-0.1.0 → creatorloop-0.2.0}/tests/test_analytics.py +0 -0
- {creatorloop-0.1.0 → creatorloop-0.2.0}/tests/test_fixtures.py +0 -0
- {creatorloop-0.1.0 → creatorloop-0.2.0}/tests/test_paths.py +0 -0
- {creatorloop-0.1.0 → creatorloop-0.2.0}/tests/test_publish.py +0 -0
- {creatorloop-0.1.0 → creatorloop-0.2.0}/tests/test_queue.py +0 -0
- {creatorloop-0.1.0 → creatorloop-0.2.0}/tests/test_refs.py +0 -0
- {creatorloop-0.1.0 → creatorloop-0.2.0}/tests/test_release.py +0 -0
- {creatorloop-0.1.0 → creatorloop-0.2.0}/tests/test_reviews.py +0 -0
- {creatorloop-0.1.0 → creatorloop-0.2.0}/tests/test_tunnel.py +0 -0
|
@@ -5,6 +5,19 @@ All notable changes to this project are documented here. The format follows
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [0.2.0] - 2026-10-14
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `Dockerfile` to run the server without a local Python (settings and data in a `/data` volume, `cloudflared`
|
|
12
|
+
included); CI builds the image and checks that it answers MCP `initialize` and lists the tools.
|
|
13
|
+
- Setup page: `creatorloop-setup`, `creatorloop-auth login` and the new `open_setup` tool open a local page that
|
|
14
|
+
checks your Meta app, logs in with Facebook, finds your Instagram account and saves the settings — no Graph API
|
|
15
|
+
Explorer, no editing `.env`, no restart when started from Claude. English, Russian and Uzbek.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- `.env` is written atomically and readable by its owner only.
|
|
19
|
+
- Errors about missing settings or an expired token point to `open_setup` / `creatorloop-auth login`.
|
|
20
|
+
|
|
8
21
|
## [0.1.0] - 2026-10-13
|
|
9
22
|
|
|
10
23
|
First public release.
|
|
@@ -22,5 +35,6 @@ First public release.
|
|
|
22
35
|
- Three MCP prompts: weekly report, competitor brief, data-driven content ideas.
|
|
23
36
|
- `examples/`: a sample carousel, a sample Reel storyboard and a Remotion Reel template with automatic checks.
|
|
24
37
|
|
|
25
|
-
[Unreleased]: https://github.com/pandich93/creatorloop/compare/v0.
|
|
38
|
+
[Unreleased]: https://github.com/pandich93/creatorloop/compare/v0.2.0...HEAD
|
|
39
|
+
[0.2.0]: https://github.com/pandich93/creatorloop/compare/v0.1.0...v0.2.0
|
|
26
40
|
[0.1.0]: https://github.com/pandich93/creatorloop/releases/tag/v0.1.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: creatorloop
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Open-source AI content studio for Instagram creators: Claude analyses what works, helps make Reels and carousels, schedules and reviews them via the official Instagram API
|
|
5
5
|
Project-URL: Homepage, https://github.com/pandich93/creatorloop
|
|
6
6
|
Project-URL: Repository, https://github.com/pandich93/creatorloop
|
|
@@ -65,22 +65,17 @@ There are plenty of "Instagram API for Claude" servers. creatorloop is built for
|
|
|
65
65
|
|
|
66
66
|
You need [uv](https://docs.astral.sh/uv/getting-started/installation/), [Claude Desktop](https://claude.ai/download),
|
|
67
67
|
an Instagram **Business or Creator** account linked to a **Facebook Page**, and your own Meta app
|
|
68
|
-
([5-minute guide](https://github.com/pandich93/creatorloop/blob/main/docs/meta-setup.md)).
|
|
68
|
+
([5-minute guide](https://github.com/pandich93/creatorloop/blob/main/docs/meta-setup.md#1-create-the-app)).
|
|
69
69
|
|
|
70
70
|
```bash
|
|
71
|
-
uvx --from creatorloop creatorloop-setup
|
|
71
|
+
uvx --from creatorloop creatorloop-setup
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
It connects Claude Desktop and opens a setup page in your browser: enter your app's **App ID** and **App Secret**,
|
|
75
|
+
press **Continue with Facebook**, done. Then ask Claude: *"How did my last 10 posts do, and what should I post next?"*
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
uvx --from creatorloop creatorloop-auth whoami --write
|
|
80
|
-
uvx --from creatorloop creatorloop-auth check # → OK: @your_account followers=...
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
Restart Claude Desktop and ask: *"How did my last 10 posts do, and what should I post next?"*
|
|
77
|
+
Already in Claude? Ask *"connect my Instagram"*: Claude opens the same page (`open_setup`). Your App Secret and
|
|
78
|
+
token are typed only into that local page, never into the chat.
|
|
84
79
|
|
|
85
80
|
<details>
|
|
86
81
|
<summary>Installing uv</summary>
|
|
@@ -98,6 +93,47 @@ uv downloads Python 3.14 by itself if you don't have it.
|
|
|
98
93
|
keeps your other servers, makes a backup and creates your settings file. Claude Desktop rewrites its config while it
|
|
99
94
|
runs, so on macOS the command quits and reopens it for you; on Windows and Linux it asks you to quit it first.
|
|
100
95
|
|
|
96
|
+
<details>
|
|
97
|
+
<summary>Running in Docker instead of uv</summary>
|
|
98
|
+
|
|
99
|
+
Build the image straight from GitHub and keep your settings in a folder on your computer:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
docker build -t creatorloop https://github.com/pandich93/creatorloop.git
|
|
103
|
+
mkdir "$HOME/creatorloop"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
If you use the post queue, first create `.env` in that folder with `IG_TIMEZONE=Europe/Berlin` (your zone): the container
|
|
107
|
+
runs in UTC.
|
|
108
|
+
|
|
109
|
+
Connect your account (the setup page runs in the container; open the printed link in your browser). Keep all three
|
|
110
|
+
`8765` the same; to use another port change all three:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
docker run --rm -it -p 127.0.0.1:8765:8765 -v "$HOME/creatorloop:/data" --entrypoint creatorloop-auth creatorloop \
|
|
114
|
+
login --no-browser --port 8765 --bind 0.0.0.0
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`creatorloop-setup` doesn't run inside Docker, so add the server to `claude_desktop_config.json` yourself, with the
|
|
118
|
+
full path to the folder:
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
{
|
|
122
|
+
"mcpServers": {
|
|
123
|
+
"creatorloop": {
|
|
124
|
+
"command": "docker",
|
|
125
|
+
"args": ["run", "-i", "--rm", "-v", "/Users/you/creatorloop:/data", "creatorloop"]
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Post folders for `prepare_post` and `queue_post` go into `creatorloop/content/`. The image includes `cloudflared` for
|
|
132
|
+
publishing carousels.
|
|
133
|
+
|
|
134
|
+
The `open_setup` tool does not work inside Docker; use the command above to reconnect.
|
|
135
|
+
</details>
|
|
136
|
+
|
|
101
137
|
## What you can ask
|
|
102
138
|
|
|
103
139
|
- "Make a report on my content for the last 60 days. Reels or carousels — what works better, and when should I post?"
|
|
@@ -121,9 +157,9 @@ Ready-made prompts in Claude Desktop's **+** menu: *Weekly Instagram report*, *C
|
|
|
121
157
|
| Publishing | `prepare_post`, `get_post_job`, `publish_media`, `list_publications` |
|
|
122
158
|
| Queue | `queue_post`, `list_queue`, `cancel_queued` |
|
|
123
159
|
| Comments | `get_new_comments`, `reply_comment` |
|
|
124
|
-
| Service | `get_api_status`, `clear_cache`, `restart_server` |
|
|
160
|
+
| Service | `get_api_status`, `open_setup`, `clear_cache`, `restart_server` |
|
|
125
161
|
|
|
126
|
-
|
|
162
|
+
34 tools in total. Tools that change something on Instagram are annotated as such for Claude, and their descriptions
|
|
127
163
|
tell it to act only after you approve: `publish_media` and `reply_comment` after you OK the preview or the exact
|
|
128
164
|
text, `queue_post` only with `confirm=true` after you saw the preview.
|
|
129
165
|
|
|
@@ -187,7 +223,7 @@ Set `CREATORLOOP_HOME` to use another folder. The home folder holds `.env`, the
|
|
|
187
223
|
| `IG_ACCESS_TOKEN` | yes | Long-lived (60 days) or System User (never expires) token — see [Meta setup](https://github.com/pandich93/creatorloop/blob/main/docs/meta-setup.md) |
|
|
188
224
|
| `IG_USER_ID` | yes | Your Instagram professional account ID (`1784…`), not the username |
|
|
189
225
|
| `GRAPH_API_VERSION` | no | Default `v26.0` |
|
|
190
|
-
| `META_APP_ID`, `META_APP_SECRET` | no |
|
|
226
|
+
| `META_APP_ID`, `META_APP_SECRET` | no | Filled by the setup page; also used to check the token expiry |
|
|
191
227
|
| `IG_TIMEZONE` | no | IANA name, e.g. `Europe/Berlin`. Default: your computer's timezone |
|
|
192
228
|
| `QUEUE_SLOTS` | no | e.g. `Mon,Wed,Fri 20:00` |
|
|
193
229
|
| `IG_DB_PATH`, `CACHE_TTL_*_MIN` | no | Database location, cache lifetime in minutes |
|
|
@@ -36,22 +36,17 @@ There are plenty of "Instagram API for Claude" servers. creatorloop is built for
|
|
|
36
36
|
|
|
37
37
|
You need [uv](https://docs.astral.sh/uv/getting-started/installation/), [Claude Desktop](https://claude.ai/download),
|
|
38
38
|
an Instagram **Business or Creator** account linked to a **Facebook Page**, and your own Meta app
|
|
39
|
-
([5-minute guide](https://github.com/pandich93/creatorloop/blob/main/docs/meta-setup.md)).
|
|
39
|
+
([5-minute guide](https://github.com/pandich93/creatorloop/blob/main/docs/meta-setup.md#1-create-the-app)).
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
|
-
uvx --from creatorloop creatorloop-setup
|
|
42
|
+
uvx --from creatorloop creatorloop-setup
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
It connects Claude Desktop and opens a setup page in your browser: enter your app's **App ID** and **App Secret**,
|
|
46
|
+
press **Continue with Facebook**, done. Then ask Claude: *"How did my last 10 posts do, and what should I post next?"*
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
uvx --from creatorloop creatorloop-auth whoami --write
|
|
51
|
-
uvx --from creatorloop creatorloop-auth check # → OK: @your_account followers=...
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
Restart Claude Desktop and ask: *"How did my last 10 posts do, and what should I post next?"*
|
|
48
|
+
Already in Claude? Ask *"connect my Instagram"*: Claude opens the same page (`open_setup`). Your App Secret and
|
|
49
|
+
token are typed only into that local page, never into the chat.
|
|
55
50
|
|
|
56
51
|
<details>
|
|
57
52
|
<summary>Installing uv</summary>
|
|
@@ -69,6 +64,47 @@ uv downloads Python 3.14 by itself if you don't have it.
|
|
|
69
64
|
keeps your other servers, makes a backup and creates your settings file. Claude Desktop rewrites its config while it
|
|
70
65
|
runs, so on macOS the command quits and reopens it for you; on Windows and Linux it asks you to quit it first.
|
|
71
66
|
|
|
67
|
+
<details>
|
|
68
|
+
<summary>Running in Docker instead of uv</summary>
|
|
69
|
+
|
|
70
|
+
Build the image straight from GitHub and keep your settings in a folder on your computer:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
docker build -t creatorloop https://github.com/pandich93/creatorloop.git
|
|
74
|
+
mkdir "$HOME/creatorloop"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
If you use the post queue, first create `.env` in that folder with `IG_TIMEZONE=Europe/Berlin` (your zone): the container
|
|
78
|
+
runs in UTC.
|
|
79
|
+
|
|
80
|
+
Connect your account (the setup page runs in the container; open the printed link in your browser). Keep all three
|
|
81
|
+
`8765` the same; to use another port change all three:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
docker run --rm -it -p 127.0.0.1:8765:8765 -v "$HOME/creatorloop:/data" --entrypoint creatorloop-auth creatorloop \
|
|
85
|
+
login --no-browser --port 8765 --bind 0.0.0.0
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`creatorloop-setup` doesn't run inside Docker, so add the server to `claude_desktop_config.json` yourself, with the
|
|
89
|
+
full path to the folder:
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"mcpServers": {
|
|
94
|
+
"creatorloop": {
|
|
95
|
+
"command": "docker",
|
|
96
|
+
"args": ["run", "-i", "--rm", "-v", "/Users/you/creatorloop:/data", "creatorloop"]
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Post folders for `prepare_post` and `queue_post` go into `creatorloop/content/`. The image includes `cloudflared` for
|
|
103
|
+
publishing carousels.
|
|
104
|
+
|
|
105
|
+
The `open_setup` tool does not work inside Docker; use the command above to reconnect.
|
|
106
|
+
</details>
|
|
107
|
+
|
|
72
108
|
## What you can ask
|
|
73
109
|
|
|
74
110
|
- "Make a report on my content for the last 60 days. Reels or carousels — what works better, and when should I post?"
|
|
@@ -92,9 +128,9 @@ Ready-made prompts in Claude Desktop's **+** menu: *Weekly Instagram report*, *C
|
|
|
92
128
|
| Publishing | `prepare_post`, `get_post_job`, `publish_media`, `list_publications` |
|
|
93
129
|
| Queue | `queue_post`, `list_queue`, `cancel_queued` |
|
|
94
130
|
| Comments | `get_new_comments`, `reply_comment` |
|
|
95
|
-
| Service | `get_api_status`, `clear_cache`, `restart_server` |
|
|
131
|
+
| Service | `get_api_status`, `open_setup`, `clear_cache`, `restart_server` |
|
|
96
132
|
|
|
97
|
-
|
|
133
|
+
34 tools in total. Tools that change something on Instagram are annotated as such for Claude, and their descriptions
|
|
98
134
|
tell it to act only after you approve: `publish_media` and `reply_comment` after you OK the preview or the exact
|
|
99
135
|
text, `queue_post` only with `confirm=true` after you saw the preview.
|
|
100
136
|
|
|
@@ -158,7 +194,7 @@ Set `CREATORLOOP_HOME` to use another folder. The home folder holds `.env`, the
|
|
|
158
194
|
| `IG_ACCESS_TOKEN` | yes | Long-lived (60 days) or System User (never expires) token — see [Meta setup](https://github.com/pandich93/creatorloop/blob/main/docs/meta-setup.md) |
|
|
159
195
|
| `IG_USER_ID` | yes | Your Instagram professional account ID (`1784…`), not the username |
|
|
160
196
|
| `GRAPH_API_VERSION` | no | Default `v26.0` |
|
|
161
|
-
| `META_APP_ID`, `META_APP_SECRET` | no |
|
|
197
|
+
| `META_APP_ID`, `META_APP_SECRET` | no | Filled by the setup page; also used to check the token expiry |
|
|
162
198
|
| `IG_TIMEZONE` | no | IANA name, e.g. `Europe/Berlin`. Default: your computer's timezone |
|
|
163
199
|
| `QUEUE_SLOTS` | no | e.g. `Mon,Wed,Fri 20:00` |
|
|
164
200
|
| `IG_DB_PATH`, `CACHE_TTL_*_MIN` | no | Database location, cache lifetime in minutes |
|
|
@@ -22,12 +22,12 @@
|
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
24
|
uvx --from creatorloop creatorloop-setup
|
|
25
|
-
uvx --from creatorloop creatorloop-auth exchange --short-token <ТОКЕН> --write
|
|
26
|
-
uvx --from creatorloop creatorloop-auth whoami --write
|
|
27
25
|
```
|
|
28
26
|
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
Команда подключит Claude Desktop и откроет страницу настройки в браузере: введите **App ID** и **App Secret**
|
|
28
|
+
своего приложения, нажмите **«Продолжить через Facebook»** — готово. Затем спросите Claude: *«Как зашли мои
|
|
29
|
+
последние 10 постов и что мне публиковать дальше?»* Уже в Claude? Попросите *«подключи мой Instagram»* — откроется
|
|
30
|
+
та же страница. Секрет и токен вводятся только на ней, не в чат.
|
|
31
31
|
|
|
32
32
|
Полная документация — в [README на английском](README.md).
|
|
33
33
|
|
|
@@ -22,12 +22,12 @@ Kerak boʻladi: [uv](https://docs.astral.sh/uv/getting-started/installation/), C
|
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
24
|
uvx --from creatorloop creatorloop-setup
|
|
25
|
-
uvx --from creatorloop creatorloop-auth exchange --short-token <TOKEN> --write
|
|
26
|
-
uvx --from creatorloop creatorloop-auth whoami --write
|
|
27
25
|
```
|
|
28
26
|
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
Buyruq Claude Desktop'ni ulaydi va brauzerda sozlash sahifasini ochadi: ilovangizning **App ID** va **App Secret**
|
|
28
|
+
qiymatlarini kiriting, **“Facebook orqali davom etish”** tugmasini bosing — tayyor. Soʻng Claude'dan soʻrang:
|
|
29
|
+
*«Oxirgi 10 ta postim qanday ketdi va keyin nima joylashim kerak?»* Claude'dasizmi? *«Instagram'imni ulab ber»* deb
|
|
30
|
+
soʻrang — oʻsha sahifa ochiladi. Secret va token faqat shu sahifada kiritiladi, chatga emas.
|
|
31
31
|
|
|
32
32
|
Toʻliq hujjatlar — [ingliz tilidagi README](README.md)da.
|
|
33
33
|
|
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
"url": "https://github.com/pandich93/creatorloop",
|
|
8
8
|
"source": "github"
|
|
9
9
|
},
|
|
10
|
-
"version": "0.
|
|
10
|
+
"version": "0.2.0",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "pypi",
|
|
14
14
|
"identifier": "creatorloop",
|
|
15
|
-
"version": "0.
|
|
15
|
+
"version": "0.2.0",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
},
|
|
@@ -5,7 +5,7 @@ analyse your account and competitors, make Reels and carousels, schedule them an
|
|
|
5
5
|
Not affiliated with, endorsed or sponsored by Meta or Instagram.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
__version__ = "0.
|
|
8
|
+
__version__ = "0.2.0"
|
|
9
9
|
|
|
10
10
|
DISCLAIMER = (
|
|
11
11
|
"creatorloop is an independent open-source project. Not affiliated with, endorsed or sponsored by Meta or "
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
"""Helper CLI: exchange tokens and find your
|
|
1
|
+
"""Helper CLI: connect your Instagram account, exchange tokens and find your account ID.
|
|
2
2
|
|
|
3
3
|
Usage:
|
|
4
|
+
uv run creatorloop-auth login [--no-browser] [--port N] [--bind ADDR]
|
|
4
5
|
uv run creatorloop-auth exchange --short-token <TOKEN> [--write]
|
|
5
6
|
uv run creatorloop-auth whoami [--write]
|
|
6
7
|
uv run creatorloop-auth check
|
|
@@ -15,7 +16,7 @@ from pathlib import Path
|
|
|
15
16
|
|
|
16
17
|
import httpx
|
|
17
18
|
|
|
18
|
-
from creatorloop import DISCLAIMER, paths
|
|
19
|
+
from creatorloop import DISCLAIMER, env_file, paths, setup_web
|
|
19
20
|
from creatorloop.config import load_settings
|
|
20
21
|
|
|
21
22
|
ENV_PATH = paths.home_dir() / ".env"
|
|
@@ -31,18 +32,40 @@ def _get(url: str, params: dict[str, str]) -> dict:
|
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
def _write_env(key: str, value: str, path: Path = ENV_PATH) -> None:
|
|
34
|
-
|
|
35
|
-
lines = path.read_text(encoding="utf-8").splitlines() if path.exists() else []
|
|
36
|
-
for i, line in enumerate(lines):
|
|
37
|
-
if line.split("=", 1)[0].strip() == key:
|
|
38
|
-
lines[i] = f"{key}={value}"
|
|
39
|
-
break
|
|
40
|
-
else:
|
|
41
|
-
lines.append(f"{key}={value}")
|
|
42
|
-
path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
35
|
+
env_file.update(path, {key: value})
|
|
43
36
|
print(f" -> saved {key} to {path}")
|
|
44
37
|
|
|
45
38
|
|
|
39
|
+
def cmd_login(args: argparse.Namespace) -> None:
|
|
40
|
+
s = load_settings()
|
|
41
|
+
env_path = paths.home_dir() / ".env"
|
|
42
|
+
if args.bind not in {"127.0.0.1", "localhost", "::1"}:
|
|
43
|
+
print(
|
|
44
|
+
f"Listening on {args.bind}: the page is reachable from the network. That is expected inside Docker with "
|
|
45
|
+
"-p 127.0.0.1:PORT:PORT; otherwise keep the default 127.0.0.1.",
|
|
46
|
+
file=sys.stderr,
|
|
47
|
+
)
|
|
48
|
+
try:
|
|
49
|
+
result = setup_web.run(
|
|
50
|
+
env_path,
|
|
51
|
+
open_browser=not args.no_browser,
|
|
52
|
+
port=args.port,
|
|
53
|
+
bind=args.bind,
|
|
54
|
+
api_version=s.api_version,
|
|
55
|
+
app_id=s.app_id or "",
|
|
56
|
+
app_secret=s.app_secret or "",
|
|
57
|
+
)
|
|
58
|
+
except OSError as exc:
|
|
59
|
+
sys.exit(f"Cannot start the setup page on {args.bind}:{args.port}: {exc}. Pass another --port, or check --bind.")
|
|
60
|
+
if result is None:
|
|
61
|
+
sys.exit(
|
|
62
|
+
"The setup page closed before the account was connected (stopped, or the 15-minute limit). "
|
|
63
|
+
"Run `creatorloop-auth login` again."
|
|
64
|
+
)
|
|
65
|
+
print(f"OK: @{result.username} connected, settings saved to {env_path}. "
|
|
66
|
+
f"Restart Claude Desktop if it is running.")
|
|
67
|
+
|
|
68
|
+
|
|
46
69
|
def cmd_exchange(args: argparse.Namespace) -> None:
|
|
47
70
|
s = load_settings()
|
|
48
71
|
if not (s.app_id and s.app_secret):
|
|
@@ -108,6 +131,12 @@ def main() -> None:
|
|
|
108
131
|
parser = argparse.ArgumentParser(prog="creatorloop-auth", description=__doc__.split("\n")[0], epilog=DISCLAIMER)
|
|
109
132
|
sub = parser.add_subparsers(required=True)
|
|
110
133
|
|
|
134
|
+
p = sub.add_parser("login", help="Connect your Instagram account on a local page in the browser (recommended)")
|
|
135
|
+
p.add_argument("--no-browser", action="store_true", help="Only print the link (e.g. in Docker)")
|
|
136
|
+
p.add_argument("--port", type=int, default=0, help="Port of the setup page (default: any free port)")
|
|
137
|
+
p.add_argument("--bind", default="127.0.0.1", help="Listen address; 0.0.0.0 only inside Docker with -p 127.0.0.1:PORT:PORT")
|
|
138
|
+
p.set_defaults(func=cmd_login)
|
|
139
|
+
|
|
111
140
|
p = sub.add_parser("exchange", help="Exchange short-lived user token for a long-lived (60 days) one")
|
|
112
141
|
p.add_argument("--short-token", required=True)
|
|
113
142
|
p.add_argument("--write", action="store_true", help="Save to .env instead of printing")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Comments on own posts: digest of new / unanswered comments and replies (only after user approval).
|
|
2
2
|
|
|
3
|
-
Needs the token permission instagram_manage_comments (
|
|
3
|
+
Needs the token permission instagram_manage_comments (kept ticked on the setup page, see docs/meta-setup.md).
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
@@ -23,9 +23,8 @@ class CommentsError(RuntimeError):
|
|
|
23
23
|
def _permission_hint(exc: GraphAPIError) -> CommentsError:
|
|
24
24
|
if exc.code in PERMISSION_CODES or "permission" in exc.message.lower():
|
|
25
25
|
return CommentsError(
|
|
26
|
-
"The token has no instagram_manage_comments permission.
|
|
27
|
-
"
|
|
28
|
-
f"--write` (README, section 3). Graph API said: {exc}"
|
|
26
|
+
"The token has no instagram_manage_comments permission. Reconnect with open_setup (again=true) "
|
|
27
|
+
f"and keep instagram_manage_comments ticked. Graph API said: {exc}"
|
|
29
28
|
)
|
|
30
29
|
return CommentsError(f"Graph API error: {exc}")
|
|
31
30
|
|
|
@@ -78,8 +78,8 @@ class Settings:
|
|
|
78
78
|
]
|
|
79
79
|
if missing:
|
|
80
80
|
raise RuntimeError(
|
|
81
|
-
f"Missing required settings: {', '.join(missing)}. "
|
|
82
|
-
|
|
81
|
+
f"Missing required settings: {', '.join(missing)}. Connect your Instagram account: call the open_setup "
|
|
82
|
+
"tool, or run `uvx --from creatorloop creatorloop-auth login` in a terminal."
|
|
83
83
|
)
|
|
84
84
|
|
|
85
85
|
|
|
@@ -1,19 +1,17 @@
|
|
|
1
|
-
# creatorloop settings. `creatorloop-setup` creates this file in your creatorloop home folder
|
|
2
|
-
#
|
|
3
|
-
#
|
|
1
|
+
# creatorloop settings. `creatorloop-setup` creates this file in your creatorloop home folder and fills
|
|
2
|
+
# IG_ACCESS_TOKEN, IG_USER_ID, META_APP_ID and META_APP_SECRET through the setup page in your browser.
|
|
3
|
+
# Connect again any time: `uvx --from creatorloop creatorloop-auth login` (or ask Claude to run open_setup).
|
|
4
4
|
|
|
5
|
-
# Long-lived user access token (60 days) or a System User token (never expires).
|
|
6
|
-
# Get it with: uvx --from creatorloop creatorloop-auth exchange --short-token <TOKEN> --write
|
|
5
|
+
# Long-lived user access token (60 days) or a System User token (never expires). Filled by the setup page.
|
|
7
6
|
IG_ACCESS_TOKEN=
|
|
8
7
|
|
|
9
|
-
# Instagram professional account ID (NOT the username). 17841400000000000-like number.
|
|
10
|
-
# Get it with: uvx --from creatorloop creatorloop-auth whoami --write
|
|
8
|
+
# Instagram professional account ID (NOT the username). 17841400000000000-like number. Filled by the setup page.
|
|
11
9
|
IG_USER_ID=
|
|
12
10
|
|
|
13
11
|
# Graph API version. Check https://developers.facebook.com/docs/graph-api/changelog
|
|
14
12
|
GRAPH_API_VERSION=v26.0
|
|
15
13
|
|
|
16
|
-
#
|
|
14
|
+
# Your Meta app. Filled by the setup page; also used for the token expiry check.
|
|
17
15
|
META_APP_ID=
|
|
18
16
|
META_APP_SECRET=
|
|
19
17
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Update KEY=value lines of the creatorloop .env without touching other lines or comments."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import contextlib
|
|
6
|
+
import os
|
|
7
|
+
import tempfile
|
|
8
|
+
from collections.abc import Mapping
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def update(path: Path, values: Mapping[str, str]) -> None:
|
|
13
|
+
"""Set the given keys (every existing line of a key is replaced, missing keys are appended) in one atomic write.
|
|
14
|
+
The file is readable by its owner only on POSIX: it holds the App Secret and the access token."""
|
|
15
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
16
|
+
lines = path.read_text(encoding="utf-8-sig").splitlines() if path.exists() else [] # -sig: Notepad's BOM
|
|
17
|
+
seen: set[str] = set()
|
|
18
|
+
for i, line in enumerate(lines):
|
|
19
|
+
key = line.split("=", 1)[0].strip()
|
|
20
|
+
if "=" in line and not line.lstrip().startswith("#") and key in values:
|
|
21
|
+
lines[i] = f"{key}={values[key]}"
|
|
22
|
+
seen.add(key)
|
|
23
|
+
lines += [f"{key}={value}" for key, value in values.items() if key not in seen]
|
|
24
|
+
fd, tmp = tempfile.mkstemp(dir=path.parent, prefix=".env.", suffix=".tmp") # mkstemp creates it with 0600
|
|
25
|
+
try:
|
|
26
|
+
with os.fdopen(fd, "w", encoding="utf-8", newline="\n") as f:
|
|
27
|
+
f.write("\n".join(lines) + "\n")
|
|
28
|
+
if hasattr(os, "geteuid") and os.geteuid() == 0: # a root container must not turn the user's .env into root:root
|
|
29
|
+
owner = path.stat() if path.exists() else path.parent.stat()
|
|
30
|
+
with contextlib.suppress(OSError):
|
|
31
|
+
os.chown(tmp, owner.st_uid, owner.st_gid)
|
|
32
|
+
os.replace(tmp, path)
|
|
33
|
+
except BaseException:
|
|
34
|
+
Path(tmp).unlink(missing_ok=True)
|
|
35
|
+
raise
|
|
@@ -218,7 +218,8 @@ class GraphClient:
|
|
|
218
218
|
)
|
|
219
219
|
if code == TOKEN_ERROR_CODE:
|
|
220
220
|
raise TokenError(
|
|
221
|
-
f"Access token problem: {message}.
|
|
221
|
+
f"Access token problem: {message}. Reconnect the account: call open_setup with again=true "
|
|
222
|
+
"(or run `creatorloop-auth login`).",
|
|
222
223
|
**kwargs,
|
|
223
224
|
)
|
|
224
225
|
raise GraphAPIError(message, **kwargs)
|
|
@@ -406,11 +406,14 @@ class InstagramService:
|
|
|
406
406
|
}
|
|
407
407
|
lefts = [d for d in (days_left, data_days_left) if d is not None]
|
|
408
408
|
if not data.get("is_valid"):
|
|
409
|
-
status["warning"] =
|
|
409
|
+
status["warning"] = (
|
|
410
|
+
"Token is invalid. Reconnect: call open_setup with again=true (or run `creatorloop-auth login`)."
|
|
411
|
+
)
|
|
410
412
|
elif lefts and min(lefts) <= TOKEN_WARN_DAYS:
|
|
411
413
|
status["warning"] = (
|
|
412
414
|
f"Token or its data access expires in {min(lefts)} days. "
|
|
413
|
-
"Renew
|
|
415
|
+
"Renew: call open_setup with again=true, or use a never-expiring System User token "
|
|
416
|
+
"(docs/meta-setup.md, step 3)."
|
|
414
417
|
)
|
|
415
418
|
return status
|
|
416
419
|
|