forgefy-cli 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.
Files changed (28) hide show
  1. {forgefy_cli-0.1.0 → forgefy_cli-0.2.0}/PKG-INFO +35 -11
  2. forgefy_cli-0.1.0/src/forgefy_cli.egg-info/PKG-INFO → forgefy_cli-0.2.0/README.md +193 -190
  3. {forgefy_cli-0.1.0 → forgefy_cli-0.2.0}/pyproject.toml +1 -1
  4. forgefy_cli-0.2.0/src/forgefy_cli/auth.py +65 -0
  5. {forgefy_cli-0.1.0 → forgefy_cli-0.2.0}/src/forgefy_cli/cli.py +12 -0
  6. {forgefy_cli-0.1.0 → forgefy_cli-0.2.0}/src/forgefy_cli/config.py +14 -6
  7. forgefy_cli-0.2.0/src/forgefy_cli/login.py +101 -0
  8. forgefy_cli-0.1.0/README.md → forgefy_cli-0.2.0/src/forgefy_cli.egg-info/PKG-INFO +214 -169
  9. {forgefy_cli-0.1.0 → forgefy_cli-0.2.0}/src/forgefy_cli.egg-info/SOURCES.txt +4 -1
  10. forgefy_cli-0.2.0/tests/test_login.py +135 -0
  11. {forgefy_cli-0.1.0 → forgefy_cli-0.2.0}/LICENSE +0 -0
  12. {forgefy_cli-0.1.0 → forgefy_cli-0.2.0}/setup.cfg +0 -0
  13. {forgefy_cli-0.1.0 → forgefy_cli-0.2.0}/src/forgefy_cli/__init__.py +0 -0
  14. {forgefy_cli-0.1.0 → forgefy_cli-0.2.0}/src/forgefy_cli/chat.py +0 -0
  15. {forgefy_cli-0.1.0 → forgefy_cli-0.2.0}/src/forgefy_cli/context.py +0 -0
  16. {forgefy_cli-0.1.0 → forgefy_cli-0.2.0}/src/forgefy_cli/editing.py +0 -0
  17. {forgefy_cli-0.1.0 → forgefy_cli-0.2.0}/src/forgefy_cli/file_tools.py +0 -0
  18. {forgefy_cli-0.1.0 → forgefy_cli-0.2.0}/src/forgefy_cli/providers.py +0 -0
  19. {forgefy_cli-0.1.0 → forgefy_cli-0.2.0}/src/forgefy_cli/skills.py +0 -0
  20. {forgefy_cli-0.1.0 → forgefy_cli-0.2.0}/src/forgefy_cli.egg-info/dependency_links.txt +0 -0
  21. {forgefy_cli-0.1.0 → forgefy_cli-0.2.0}/src/forgefy_cli.egg-info/entry_points.txt +0 -0
  22. {forgefy_cli-0.1.0 → forgefy_cli-0.2.0}/src/forgefy_cli.egg-info/requires.txt +0 -0
  23. {forgefy_cli-0.1.0 → forgefy_cli-0.2.0}/src/forgefy_cli.egg-info/top_level.txt +0 -0
  24. {forgefy_cli-0.1.0 → forgefy_cli-0.2.0}/tests/test_chat.py +0 -0
  25. {forgefy_cli-0.1.0 → forgefy_cli-0.2.0}/tests/test_chat_process.py +0 -0
  26. {forgefy_cli-0.1.0 → forgefy_cli-0.2.0}/tests/test_cli.py +0 -0
  27. {forgefy_cli-0.1.0 → forgefy_cli-0.2.0}/tests/test_create.py +0 -0
  28. {forgefy_cli-0.1.0 → forgefy_cli-0.2.0}/tests/test_edit.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: forgefy-cli
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Forgefy CLI — coding assistance with local and hosted model-provider profiles.
5
5
  License-Expression: LicenseRef-Proprietary
6
6
  Project-URL: Homepage, https://forgefy.app
@@ -27,18 +27,28 @@ coverage depend on the selected model; no model is guaranteed to be best at ever
27
27
 
28
28
  ## Install
29
29
 
30
+ No Python required — installs a standalone `forgefy` binary and puts it on
31
+ your PATH, the same way Ollama's or Claude Code's installer does:
32
+
33
+ macOS / Linux:
34
+ ```sh
35
+ curl -fsSL https://raw.githubusercontent.com/Polybamz/forgefy-cli/main/install.sh | sh
36
+ ```
37
+
38
+ Windows (PowerShell):
30
39
  ```powershell
31
- pip install forgefy-cli
40
+ irm https://raw.githubusercontent.com/Polybamz/forgefy-cli/main/install.ps1 | iex
32
41
  ```
33
42
 
34
- Not yet published to PyPI? Install straight from GitHub instead:
43
+ Already have Python? `pip`/`pipx` work too prefer `pipx` over plain `pip`,
44
+ since `pip install` can silently install to a user directory that isn't on
45
+ your PATH:
35
46
 
36
47
  ```powershell
37
- pip install git+https://github.com/Polybamz/forgefy-cli.git
48
+ pipx install forgefy-cli
38
49
  ```
39
50
 
40
- Either way this creates the `forgefy` executable on your PATH (inside whichever
41
- Python environment you ran `pip install` in):
51
+ Either way, open a new terminal and you should have the `forgefy` command:
42
52
 
43
53
  ```powershell
44
54
  forgefy --help
@@ -179,12 +189,26 @@ pytest
179
189
 
180
190
  ## Releasing (maintainers)
181
191
 
182
- CI runs on every push/PR (`.github/workflows/ci.yml`). To publish a new version to PyPI:
192
+ CI runs on every push/PR (`.github/workflows/ci.yml`). One tag produces both
193
+ distribution channels:
183
194
 
184
195
  1. Bump `version` in `pyproject.toml` and commit.
185
196
  2. `git tag vX.Y.Z && git push origin vX.Y.Z`.
186
- 3. `.github/workflows/release.yml` builds, tests, and publishes via PyPI Trusted
187
- Publishing no token stored in the repo. One-time setup: on the PyPI project's
188
- *Publishing* settings, add a Trusted Publisher for `Polybamz/forgefy-cli`,
189
- workflow `release.yml`, environment `pypi`.
197
+ 3. `.github/workflows/release.yml` then, in parallel:
198
+ - builds and publishes the PyPI package via Trusted Publishing no token
199
+ stored in the repo. One-time setup: on the PyPI project's *Publishing*
200
+ settings, add a Trusted Publisher for `Polybamz/forgefy-cli`, workflow
201
+ `release.yml`, environment `pypi`.
202
+ - builds a standalone `forgefy` binary for Windows/macOS/Linux with
203
+ PyInstaller and attaches them to a GitHub Release for the tag — what
204
+ `install.sh`/`install.ps1` fetch. No setup needed; uses the repo's
205
+ built-in `GITHUB_TOKEN`.
206
+
207
+ To build the standalone binary locally (e.g. to test before tagging):
208
+
209
+ ```powershell
210
+ pip install -e . pyinstaller
211
+ pyinstaller --onefile --name forgefy --paths src --hidden-import anyio._backends._asyncio --distpath dist_native --workpath build_native --specpath build_native build_installer/entrypoint.py
212
+ .\dist_native\forgefy.exe --help
213
+ ```
190
214
 
@@ -1,190 +1,193 @@
1
- Metadata-Version: 2.4
2
- Name: forgefy-cli
3
- Version: 0.1.0
4
- Summary: Forgefy CLI coding assistance with local and hosted model-provider profiles.
5
- License-Expression: LicenseRef-Proprietary
6
- Project-URL: Homepage, https://forgefy.app
7
- Project-URL: Repository, https://github.com/Polybamz/forgefy-cli
8
- Classifier: Environment :: Console
9
- Classifier: Intended Audience :: Developers
10
- Classifier: Programming Language :: Python :: 3
11
- Classifier: Programming Language :: Python :: 3.11
12
- Classifier: Programming Language :: Python :: 3.12
13
- Classifier: Programming Language :: Python :: 3.13
14
- Requires-Python: >=3.11
15
- Description-Content-Type: text/markdown
16
- License-File: LICENSE
17
- Requires-Dist: httpx>=0.27
18
- Provides-Extra: dev
19
- Requires-Dist: pytest>=8; extra == "dev"
20
- Dynamic: license-file
21
-
22
- # Forgefy CLI (initial release)
23
-
24
- A Python 3.11+ coding-assistance CLI. Language-independent prompts support writing,
25
- debugging, reviewing, testing, refactoring and planning code. Quality and language
26
- coverage depend on the selected model; no model is guaranteed to be best at everything.
27
-
28
- ## Install
29
-
30
- ```powershell
31
- pip install forgefy-cli
32
- ```
33
-
34
- Not yet published to PyPI? Install straight from GitHub instead:
35
-
36
- ```powershell
37
- pip install git+https://github.com/Polybamz/forgefy-cli.git
38
- ```
39
-
40
- Either way this creates the `forgefy` executable on your PATH (inside whichever
41
- Python environment you ran `pip install` in):
42
-
43
- ```powershell
44
- forgefy --help
45
- forgefy config --init
46
- forgefy providers
47
- forgefy skills
48
- forgefy doctor
49
- ```
50
-
51
- ### Developing locally
52
-
53
- ```powershell
54
- git clone https://github.com/Polybamz/forgefy-cli.git
55
- cd forgefy-cli
56
- pip install -e ".[dev]"
57
- ```
58
-
59
- ## Local and hosted models
60
-
61
- With Ollama running and a model installed, list exact IDs and choose one:
62
-
63
- ```powershell
64
- forgefy models --provider ollama
65
- forgefy run "Write a Rust function with unit tests that validates an email address" --provider ollama --model YOUR_INSTALLED_MODEL
66
- ```
67
-
68
- For OpenRouter, set `OPENROUTER_API_KEY` in your environment, list models, then select
69
- an exact available ID. Free-tier models and availability are provider-controlled;
70
- verify pricing before sending requests. Local inference has hardware/energy costs.
71
-
72
- ```powershell
73
- forgefy models --provider openrouter
74
- forgefy run "Explain this Python module and suggest tests" --provider openrouter --model YOUR_MODEL_ID --workspace 'C:\Users\USER\Desktop\polycarp\forgefy-cli' --file src/forgefy_cli/context.py --skill review
75
- ```
76
-
77
- Built-in profiles: Ollama, OpenAI, OpenRouter, DeepSeek and Groq. Anthropic and Gemini
78
- models can be used through OpenRouter when offered there; native Anthropic/Gemini
79
- protocols are not implemented. No automatic provider or paid-model fallback occurs.
80
- Compatibility requires the `/models` and `/chat/completions` endpoints; a listed model
81
- is not necessarily a compatible text-generation model.
82
-
83
- ## Provider plugins
84
-
85
- `forgefy config` prints the config path (normally your home directory's
86
- `.forgefy/config.toml`). `FORGEFY_CONFIG` can select an alternate file.
87
- Add declarative OpenAI-compatible provider profiles:
88
-
89
- ```toml
90
- default_provider = "local_server"
91
- default_model = "your-model-id"
92
-
93
- [providers.local_server]
94
- base_url = "http://localhost:1234/v1"
95
- api_key_env = ""
96
-
97
- [providers.company]
98
- base_url = "https://models.example.com/v1"
99
- api_key_env = "COMPANY_MODEL_KEY"
100
- ```
101
-
102
- Never store keys directly in configuration. Custom profiles cannot override built-in
103
- names. HTTPS is required for non-loopback endpoints. Only configure servers you trust:
104
- the chosen server receives your prompt, explicit file context, and its configured key.
105
- These plugins are configuration, not executable Python code or an MCP integration.
106
-
107
- ## Skills and context
108
-
109
- Choose `--skill code|debug|review|test|refactor|plan`. Repeat `--skill-file` to include
110
- trusted UTF-8 Markdown instructions. Repeat `--file` to send workspace-relative source
111
- files. Use a prompt of `-` for standard input. No repository files are sent implicitly.
112
- Files must resolve within the workspace. Common credential paths are excluded, but this
113
- is not a secret scanner: review every file and prompt before sending. Requests have a
114
- 120,000-character input cap; individual models may require much smaller inputs.
115
-
116
- ## Interactive chat
117
-
118
- ```powershell
119
- & 'C:\Users\USER\Desktop\polycarp\.venv\Scripts\forgefy.exe' chat --provider ollama --model llama3:latest
120
- ```
121
-
122
- Chat retains conversation history in memory for follow-up questions. `/new` clears it,
123
- `/help` lists commands, and `/exit` or `/quit` ends the session. EOF exits normally;
124
- Ctrl+C cancels. History is not saved to disk. Each request resends retained history,
125
- so hosted-provider usage can grow each turn. No automatic paid fallback occurs.
126
- Oldest complete user/assistant pairs are omitted when conversational content exceeds
127
- 120,000 characters; system/skill instructions are additional. This is a character cap,
128
- not a token budget. Failed requests preserve prior history. Chat accepts single-line
129
- turns and skill plugins; explicit `--file` context is currently supported by `run` only.
130
-
131
- ## Approved file editing
132
-
133
- `forgefy edit` can modify explicitly selected, **existing** UTF-8 files with a model
134
- supporting OpenAI-compatible tool calling. `run` and `chat` remain suggestion-only.
135
- In an interactive terminal, for example:
136
-
137
- ```powershell
138
- & 'C:\Users\USER\Desktop\polycarp\.venv\Scripts\forgefy.exe' edit "Improve error handling in this module" --provider ollama --model YOUR_TOOL_CALLING_MODEL --workspace 'C:\Users\USER\Desktop\polycarp\forgefy-cli' --file src/forgefy_cli/context.py
139
- ```
140
-
141
- Review the selected files for secrets before starting: the model can read their contents
142
- and send them to the selected provider without further read approval. Each replacement
143
- shows a complete diff and requires typing `yes`. There is no automatic approval flag.
144
- The executor requires a prior read, an exact single match, and unchanged contents before
145
- and after approval. Updates use a sibling temporary file and atomic replacement.
146
-
147
- Only files named by repeatable `--file` options are accessible. Hidden paths, common
148
- credential files, symlinks, junctions, hardlinks and nonregular files are rejected.
149
- Files and replacements are limited to 32,000 bytes; oversized diffs are rejected, not
150
- truncated for approval. These checks are not a secret scanner or an OS security sandbox.
151
- Use a trusted workspace without concurrent writers: a small filesystem race window
152
- remains between validation and replacement. Atomic replacement preserves mode bits,
153
- not necessarily all filesystem metadata or custom ACLs.
154
-
155
- The default limit is 12 model requests (`--max-turns` accepts 1–30), with a
156
- 120,000-character serialized conversation cap. Exit code 2 means a limit stopped an
157
- incomplete session; 1 indicates an error and 130 indicates cancellation. Exit code 0
158
- means the model finished, not that its changes are correct or tested. Applied edits
159
- remain on disk if the session stops or fails—there is no session-wide rollback.
160
- Use version control or backups and review the printed list of files actually changed.
161
-
162
- Editing currently supports replacements only: no file creation, deletion, shell commands,
163
- or custom skill files. Its integration tests use mocked model responses and temporary
164
- files; live model-driven editing has not been verified.
165
-
166
- ## Current boundaries
167
-
168
- This release can apply approved replacements, but it does **not** execute
169
- commands, run tests on generated code, stream tokens, persist chat history, or connect
170
- to the Forgefy admin catalogue. Provider profiles and skill files are the initial plugin
171
- interfaces, not a full autonomous coding-agent system. Output is untrusted: inspect it
172
- before running anything. Tests use mocked HTTP, not live model quality benchmarks.
173
-
174
- ## Tests
175
-
176
- ```powershell
177
- pytest
178
- ```
179
-
180
- ## Releasing (maintainers)
181
-
182
- CI runs on every push/PR (`.github/workflows/ci.yml`). To publish a new version to PyPI:
183
-
184
- 1. Bump `version` in `pyproject.toml` and commit.
185
- 2. `git tag vX.Y.Z && git push origin vX.Y.Z`.
186
- 3. `.github/workflows/release.yml` builds, tests, and publishes via PyPI Trusted
187
- Publishing — no token stored in the repo. One-time setup: on the PyPI project's
188
- *Publishing* settings, add a Trusted Publisher for `Polybamz/forgefy-cli`,
189
- workflow `release.yml`, environment `pypi`.
190
-
1
+ # Forgefy CLI (initial release)
2
+
3
+ A Python 3.11+ coding-assistance CLI. Language-independent prompts support writing,
4
+ debugging, reviewing, testing, refactoring and planning code. Quality and language
5
+ coverage depend on the selected model; no model is guaranteed to be best at everything.
6
+
7
+ ## Install
8
+
9
+ No Python required installs a standalone `forgefy` binary and puts it on
10
+ your PATH, the same way Ollama's or Claude Code's installer does:
11
+
12
+ macOS / Linux:
13
+ ```sh
14
+ curl -fsSL https://raw.githubusercontent.com/Polybamz/forgefy-cli/main/install.sh | sh
15
+ ```
16
+
17
+ Windows (PowerShell):
18
+ ```powershell
19
+ irm https://raw.githubusercontent.com/Polybamz/forgefy-cli/main/install.ps1 | iex
20
+ ```
21
+
22
+ Already have Python? `pip`/`pipx` work too — prefer `pipx` over plain `pip`,
23
+ since `pip install` can silently install to a user directory that isn't on
24
+ your PATH:
25
+
26
+ ```powershell
27
+ pipx install forgefy-cli
28
+ ```
29
+
30
+ Either way, open a new terminal and you should have the `forgefy` command:
31
+
32
+ ```powershell
33
+ forgefy --help
34
+ forgefy config --init
35
+ forgefy providers
36
+ forgefy skills
37
+ forgefy doctor
38
+ ```
39
+
40
+ ### Developing locally
41
+
42
+ ```powershell
43
+ git clone https://github.com/Polybamz/forgefy-cli.git
44
+ cd forgefy-cli
45
+ pip install -e ".[dev]"
46
+ ```
47
+
48
+ ## Local and hosted models
49
+
50
+ With Ollama running and a model installed, list exact IDs and choose one:
51
+
52
+ ```powershell
53
+ forgefy models --provider ollama
54
+ forgefy run "Write a Rust function with unit tests that validates an email address" --provider ollama --model YOUR_INSTALLED_MODEL
55
+ ```
56
+
57
+ For OpenRouter, set `OPENROUTER_API_KEY` in your environment, list models, then select
58
+ an exact available ID. Free-tier models and availability are provider-controlled;
59
+ verify pricing before sending requests. Local inference has hardware/energy costs.
60
+
61
+ ```powershell
62
+ forgefy models --provider openrouter
63
+ forgefy run "Explain this Python module and suggest tests" --provider openrouter --model YOUR_MODEL_ID --workspace 'C:\Users\USER\Desktop\polycarp\forgefy-cli' --file src/forgefy_cli/context.py --skill review
64
+ ```
65
+
66
+ Built-in profiles: Ollama, OpenAI, OpenRouter, DeepSeek and Groq. Anthropic and Gemini
67
+ models can be used through OpenRouter when offered there; native Anthropic/Gemini
68
+ protocols are not implemented. No automatic provider or paid-model fallback occurs.
69
+ Compatibility requires the `/models` and `/chat/completions` endpoints; a listed model
70
+ is not necessarily a compatible text-generation model.
71
+
72
+ ## Provider plugins
73
+
74
+ `forgefy config` prints the config path (normally your home directory's
75
+ `.forgefy/config.toml`). `FORGEFY_CONFIG` can select an alternate file.
76
+ Add declarative OpenAI-compatible provider profiles:
77
+
78
+ ```toml
79
+ default_provider = "local_server"
80
+ default_model = "your-model-id"
81
+
82
+ [providers.local_server]
83
+ base_url = "http://localhost:1234/v1"
84
+ api_key_env = ""
85
+
86
+ [providers.company]
87
+ base_url = "https://models.example.com/v1"
88
+ api_key_env = "COMPANY_MODEL_KEY"
89
+ ```
90
+
91
+ Never store keys directly in configuration. Custom profiles cannot override built-in
92
+ names. HTTPS is required for non-loopback endpoints. Only configure servers you trust:
93
+ the chosen server receives your prompt, explicit file context, and its configured key.
94
+ These plugins are configuration, not executable Python code or an MCP integration.
95
+
96
+ ## Skills and context
97
+
98
+ Choose `--skill code|debug|review|test|refactor|plan`. Repeat `--skill-file` to include
99
+ trusted UTF-8 Markdown instructions. Repeat `--file` to send workspace-relative source
100
+ files. Use a prompt of `-` for standard input. No repository files are sent implicitly.
101
+ Files must resolve within the workspace. Common credential paths are excluded, but this
102
+ is not a secret scanner: review every file and prompt before sending. Requests have a
103
+ 120,000-character input cap; individual models may require much smaller inputs.
104
+
105
+ ## Interactive chat
106
+
107
+ ```powershell
108
+ & 'C:\Users\USER\Desktop\polycarp\.venv\Scripts\forgefy.exe' chat --provider ollama --model llama3:latest
109
+ ```
110
+
111
+ Chat retains conversation history in memory for follow-up questions. `/new` clears it,
112
+ `/help` lists commands, and `/exit` or `/quit` ends the session. EOF exits normally;
113
+ Ctrl+C cancels. History is not saved to disk. Each request resends retained history,
114
+ so hosted-provider usage can grow each turn. No automatic paid fallback occurs.
115
+ Oldest complete user/assistant pairs are omitted when conversational content exceeds
116
+ 120,000 characters; system/skill instructions are additional. This is a character cap,
117
+ not a token budget. Failed requests preserve prior history. Chat accepts single-line
118
+ turns and skill plugins; explicit `--file` context is currently supported by `run` only.
119
+
120
+ ## Approved file editing
121
+
122
+ `forgefy edit` can modify explicitly selected, **existing** UTF-8 files with a model
123
+ supporting OpenAI-compatible tool calling. `run` and `chat` remain suggestion-only.
124
+ In an interactive terminal, for example:
125
+
126
+ ```powershell
127
+ & 'C:\Users\USER\Desktop\polycarp\.venv\Scripts\forgefy.exe' edit "Improve error handling in this module" --provider ollama --model YOUR_TOOL_CALLING_MODEL --workspace 'C:\Users\USER\Desktop\polycarp\forgefy-cli' --file src/forgefy_cli/context.py
128
+ ```
129
+
130
+ Review the selected files for secrets before starting: the model can read their contents
131
+ and send them to the selected provider without further read approval. Each replacement
132
+ shows a complete diff and requires typing `yes`. There is no automatic approval flag.
133
+ The executor requires a prior read, an exact single match, and unchanged contents before
134
+ and after approval. Updates use a sibling temporary file and atomic replacement.
135
+
136
+ Only files named by repeatable `--file` options are accessible. Hidden paths, common
137
+ credential files, symlinks, junctions, hardlinks and nonregular files are rejected.
138
+ Files and replacements are limited to 32,000 bytes; oversized diffs are rejected, not
139
+ truncated for approval. These checks are not a secret scanner or an OS security sandbox.
140
+ Use a trusted workspace without concurrent writers: a small filesystem race window
141
+ remains between validation and replacement. Atomic replacement preserves mode bits,
142
+ not necessarily all filesystem metadata or custom ACLs.
143
+
144
+ The default limit is 12 model requests (`--max-turns` accepts 1–30), with a
145
+ 120,000-character serialized conversation cap. Exit code 2 means a limit stopped an
146
+ incomplete session; 1 indicates an error and 130 indicates cancellation. Exit code 0
147
+ means the model finished, not that its changes are correct or tested. Applied edits
148
+ remain on disk if the session stops or fails—there is no session-wide rollback.
149
+ Use version control or backups and review the printed list of files actually changed.
150
+
151
+ Editing currently supports replacements only: no file creation, deletion, shell commands,
152
+ or custom skill files. Its integration tests use mocked model responses and temporary
153
+ files; live model-driven editing has not been verified.
154
+
155
+ ## Current boundaries
156
+
157
+ This release can apply approved replacements, but it does **not** execute
158
+ commands, run tests on generated code, stream tokens, persist chat history, or connect
159
+ to the Forgefy admin catalogue. Provider profiles and skill files are the initial plugin
160
+ interfaces, not a full autonomous coding-agent system. Output is untrusted: inspect it
161
+ before running anything. Tests use mocked HTTP, not live model quality benchmarks.
162
+
163
+ ## Tests
164
+
165
+ ```powershell
166
+ pytest
167
+ ```
168
+
169
+ ## Releasing (maintainers)
170
+
171
+ CI runs on every push/PR (`.github/workflows/ci.yml`). One tag produces both
172
+ distribution channels:
173
+
174
+ 1. Bump `version` in `pyproject.toml` and commit.
175
+ 2. `git tag vX.Y.Z && git push origin vX.Y.Z`.
176
+ 3. `.github/workflows/release.yml` then, in parallel:
177
+ - builds and publishes the PyPI package via Trusted Publishing — no token
178
+ stored in the repo. One-time setup: on the PyPI project's *Publishing*
179
+ settings, add a Trusted Publisher for `Polybamz/forgefy-cli`, workflow
180
+ `release.yml`, environment `pypi`.
181
+ - builds a standalone `forgefy` binary for Windows/macOS/Linux with
182
+ PyInstaller and attaches them to a GitHub Release for the tag what
183
+ `install.sh`/`install.ps1` fetch. No setup needed; uses the repo's
184
+ built-in `GITHUB_TOKEN`.
185
+
186
+ To build the standalone binary locally (e.g. to test before tagging):
187
+
188
+ ```powershell
189
+ pip install -e . pyinstaller
190
+ pyinstaller --onefile --name forgefy --paths src --hidden-import anyio._backends._asyncio --distpath dist_native --workpath build_native --specpath build_native build_installer/entrypoint.py
191
+ .\dist_native\forgefy.exe --help
192
+ ```
193
+
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "forgefy-cli"
3
- version = "0.1.0"
3
+ version = "0.2.0"
4
4
  description = "Forgefy CLI — coding assistance with local and hosted model-provider profiles."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -0,0 +1,65 @@
1
+ """Local storage for the credential `forgefy login` obtains.
2
+
3
+ config.py is explicit that provider credentials are read only from the
4
+ environment and never stored in config.toml. This module doesn't relax that:
5
+ Provider.headers() still reads only os.environ. `forgefy login` just gives
6
+ one more, opt-in way to populate $FORGEFY_API_KEY for the process — from a
7
+ single-purpose file the user created by explicitly running `login`, kept out
8
+ of config.toml and permissioned as tightly as the OS allows, the same
9
+ convention `gh`/`docker`/`npm` use for their own login credential.
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ import os
15
+ import stat
16
+ from pathlib import Path
17
+
18
+
19
+ def credentials_path() -> Path:
20
+ return Path(
21
+ os.environ.get("FORGEFY_CREDENTIALS", str(Path.home() / ".forgefy" / "credentials.json"))
22
+ ).expanduser()
23
+
24
+
25
+ def save_credentials(api_key: str) -> Path:
26
+ path = credentials_path()
27
+ path.parent.mkdir(parents=True, exist_ok=True)
28
+ path.write_text(json.dumps({"api_key": api_key}), encoding="utf-8")
29
+ try:
30
+ os.chmod(path, stat.S_IRUSR | stat.S_IWUSR) # 0600 — no-op on Windows, effective on POSIX
31
+ except OSError:
32
+ pass # best-effort; some filesystems (e.g. certain network mounts) reject chmod
33
+ return path
34
+
35
+
36
+ def load_credentials() -> str | None:
37
+ path = credentials_path()
38
+ if not path.exists():
39
+ return None
40
+ try:
41
+ data = json.loads(path.read_text(encoding="utf-8"))
42
+ except (json.JSONDecodeError, OSError, UnicodeDecodeError):
43
+ return None
44
+ key = data.get("api_key") if isinstance(data, dict) else None
45
+ return key if isinstance(key, str) and key else None
46
+
47
+
48
+ def clear_credentials() -> bool:
49
+ path = credentials_path()
50
+ if path.exists():
51
+ path.unlink()
52
+ return True
53
+ return False
54
+
55
+
56
+ def bootstrap_env() -> None:
57
+ """Populate $FORGEFY_API_KEY from the stored credential if not already set.
58
+
59
+ Called once at CLI startup, before providers are resolved. An explicitly
60
+ exported FORGEFY_API_KEY always wins over a stored one.
61
+ """
62
+ if not os.environ.get("FORGEFY_API_KEY"):
63
+ key = load_credentials()
64
+ if key:
65
+ os.environ["FORGEFY_API_KEY"] = key
@@ -8,6 +8,7 @@ import sys
8
8
 
9
9
  import httpx
10
10
 
11
+ from .auth import bootstrap_env
11
12
  from .chat import chat_loop
12
13
  from .config import TEMPLATE, config_path, load_config
13
14
  from .context import build_prompt
@@ -43,6 +44,9 @@ def parser() -> argparse.ArgumentParser:
43
44
  edit.add_argument("--max-turns", type=int, default=12, help="Maximum model requests (1–30; default 12)")
44
45
  models = sub.add_parser("models", help="List live provider model IDs (availability and pricing vary)")
45
46
  models.add_argument("--provider")
47
+ login = sub.add_parser("login", help="Sign in with your Forgefy account via the browser (device code)")
48
+ login.add_argument("--api-url", help="Override FORGEFY_API_URL for this login only")
49
+ sub.add_parser("logout", help="Remove the locally stored Forgefy account credential")
46
50
  sub.add_parser("providers", help="List built-in and configured provider plugins")
47
51
  sub.add_parser("skills", help="List built-in coding skills")
48
52
  sub.add_parser("doctor", help="Check configuration and key presence without sending requests")
@@ -53,7 +57,15 @@ def parser() -> argparse.ArgumentParser:
53
57
 
54
58
  def main(argv: list[str] | None = None) -> int:
55
59
  args = parser().parse_args(argv)
60
+ bootstrap_env()
56
61
  try:
62
+ if args.command == "login":
63
+ from .login import device_login
64
+ with httpx.Client(timeout=15) as http:
65
+ return device_login(http, api_url=args.api_url)
66
+ if args.command == "logout":
67
+ from .login import logout
68
+ return logout()
57
69
  if args.command == "config":
58
70
  path = config_path()
59
71
  if args.init:
@@ -24,10 +24,16 @@ class Provider:
24
24
  return {"Authorization": f"Bearer {key}"}
25
25
 
26
26
 
27
- # Same $FORGEFY_API_URL convention as the official SDKs (sdks/python,
28
- # sdks/typescript) and the self-hosted docs the API has no single fixed
29
- # public host, so the origin is always read from the environment.
30
- _FORGEFY_API_URL = os.environ.get("FORGEFY_API_URL", "http://localhost:5000").rstrip("/")
27
+ def forgefy_api_url() -> str:
28
+ """Return the Forgefy API originsame $FORGEFY_API_URL convention as the
29
+ official SDKs (sdks/python, sdks/typescript) and the self-hosted docs, since
30
+ the API has no single fixed public host.
31
+
32
+ Read fresh (not cached at import time) so `forgefy login` picks up a
33
+ `--api-url` override passed after the process has already started.
34
+ """
35
+ return os.environ.get("FORGEFY_API_URL", "http://localhost:5000").rstrip("/")
36
+
31
37
 
32
38
  BUILTINS = {
33
39
  "ollama": Provider("ollama", "http://localhost:11434/v1"),
@@ -39,8 +45,10 @@ BUILTINS = {
39
45
  # Developers page — the same fgy_live_… keys and monthly token budget the
40
46
  # extract API and web builds use — and `export FORGEFY_API_KEY=fgy_live_…`.
41
47
  # Set FORGEFY_API_URL too if you're not on the default host (self-hosted,
42
- # local dev). See app/api/v1/cli.py in forgefy-backend.
43
- "forgefy": Provider("forgefy", f"{_FORGEFY_API_URL}/api/v1/cli", "FORGEFY_API_KEY"),
48
+ # local dev). Run `forgefy login` to fill FORGEFY_API_KEY automatically
49
+ # instead of copying a key from the Developers page by hand. See
50
+ # app/api/v1/cli.py in forgefy-backend.
51
+ "forgefy": Provider("forgefy", f"{forgefy_api_url()}/api/v1/cli", "FORGEFY_API_KEY"),
44
52
  }
45
53
 
46
54
  TEMPLATE = '''# Keep API keys in environment variables, never here.