Build an AI File Analyzer CLI
Build a Python CLI that streams AI analysis of local text files.
Introduction
30 Second Summary
Useful details often sit inside reports, notes, or code files that are too long to scan whenever a question comes up. Copying those details by hand adds friction when you only need a summary, a clear explanation, or the next actions.
In this project, you will build FileSense, a Python CLI that analyzes local text or code files with OpenAI. You can switch between three analysis modes before watching each result appear progressively in Terminal.
What You'll Build
Your finished FileSense CLI turns the Project Phoenix report into a live Terminal analysis that surfaces its owner, launch date, blocker, and next action.
By the end of this project, you'll have:
- A grounded file analyzer that produces answers from the actual contents of local UTF-8 text or code files.
- Three selectable analysis modes for summary, explain, or actions. Each mode produces a different useful view of the same file.
- Streamed Terminal output that arrives progressively. Clear messages cover invalid files, missing credentials, connection problems, rate limits, or API failures.
- Secret Mission: Add an optional --save destination that writes the completed streamed analysis to a local file.
Are there any prerequisites?
You'll need a Mac with Python 3.10 or newer, VS Code, and an OpenAI API key with API access. Only the later OpenAI analyses use metered API calls that can incur token charges.
Before We Start
Before any hands-on work begins, choose the local text or code file you want FileSense to analyze first. Connecting that file to a useful outcome gives your CLI a clear purpose from the start.
Set Up a Safe OpenAI Workspace
FileSense eventually sends local file contents to OpenAI. Before that can happen safely, your Python packages need an isolated home.
You will also keep your API key outside the source code. The credential stays inside the current macOS Terminal session.
In this step, get ready to:
- Confirm that Python 3.10 or newer is available.
- Prepare an isolated filesense workspace with OpenAI Python SDK 3.39.0.
- Store OPENAI_API_KEY in the current Terminal session.
Prepare the local workspace
The OpenAI Python package requires Python 3.10 or newer. Checking first prevents package installation problems later.
- Press Cmd+Space to open macOS search.
- Type Terminal and press Enter to open it.
- Check your installed Python version by running this command:
python3 --version
What does this command do?
The python3 command selects the Python 3 interpreter available in your Terminal. The version option prints its installed version without starting an interactive session.
✔️ I see the required version
Good. Your Python interpreter meets the minimum requirement of 3.10.
ⓧ I see an older version
Your current interpreter is below the version required by the pinned OpenAI package. Install a current supported Python release before creating the workspace.
- Open the official Python macOS downloads page.
- Download a current supported Python release.
- Run the downloaded installer package.
- Follow the installer prompts using the default options.
- Return to the Terminal window from earlier.
ⓧ Python is unavailable
Python 3 is not currently available from your Terminal. Install a current supported release before continuing.
- Open the official Python macOS downloads page.
- Download a current supported Python release.
- Run the downloaded installer package.
- Follow the installer prompts using the default options.
- Return to the Terminal window from earlier.
- Confirm that Terminal now uses the required Python version by running the check again:
python3 --version
What confirms Python is ready?
The command prints the interpreter version currently available to this Terminal. Any version from 3.10 upward supports OpenAI Python SDK 3.39.0.
You should see Python 3.10 or a newer version in the output.
A virtual environment keeps this project's packages separate from packages used by other Python projects. This prevents one project from changing another project's dependencies.
- Move to your Desktop by running this command:
cd ~/Desktop
What does this command do?
The cd command changes the current folder. The shortcut in this command points to the Desktop inside your macOS home folder.
- Create the filesense folder by running these commands:
mkdir filesense
cd filesense
What do these commands do?
- The first command creates the filesense folder on your Desktop.
- The second command moves the current Terminal session into that folder.
The current Terminal session is now inside the new filesense folder.
Creating the environment can take a moment while Python prepares its isolated package directories. A quiet Terminal during this command is expected.
- Create the virtual environment inside filesense by running this command:
python3 -m venv .venv
What does this command create?
Python creates a .venv directory inside filesense. That directory holds the isolated interpreter and any packages installed for FileSense.
- Activate the new virtual environment by running this command:
source .venv/bin/activate
What does activation change?
Activation makes this Terminal use the Python interpreter inside .venv. Package installations now stay inside the FileSense workspace.
Your Terminal prompt should now show the virtual environment name. That visible change confirms the isolated environment is active.
Virtual environment not active?
Check that your Terminal is still inside the filesense folder. The activation command depends on finding .venv there.
If the environment directory was not created, rerun the creation command with the confirmed Python interpreter.
help me activate the FileSense virtual environment
Protect dependency consistency
The OpenAI Python SDK gives FileSense a Python client for the Responses API. Pinning version 3.39.0 keeps the project behavior consistent.
- Install the pinned SDK inside the active virtual environment by running this command:
python3 -m pip install 'openai==3.39.0'
What does this command install?
Python runs pip through the active interpreter. The exact-version requirement installs OpenAI Python SDK 3.39.0 into .venv.
The installation output should finish with a successful package installation. Your system-wide Python packages remain unchanged.
SDK installation failed?
Confirm that the virtual environment name still appears in your Terminal prompt. A missing environment name can mean the package is being installed with a different interpreter.
A network-related failure can interrupt the package download. Check your internet connection before rerunning the same installation command.
help me diagnose the OpenAI SDK installation
- Start the Python interpreter from the active environment by running this command:
python3
Why start the interpreter?
This opens an interactive Python session through the active virtual environment. You can test the installed package without creating a source file.
- Verify the installed SDK version by entering these lines:
import openai
print(openai.__version__)
import sys; sys.exit()
What does this check prove?
- The import confirms that Python can load the OpenAI package from the active environment.
- The version check prints the package release that Python loaded.
- The exit line returns you to the normal Terminal shell.
You should see 3.39.0 before the interpreter closes. Great work. The exact SDK version FileSense needs is now isolated inside .venv.
Protect the API credential
An environment variable lets the SDK read your API key without placing it in Python code. The value lasts only inside the current Terminal session.
API usage is metered
Nothing in this setup makes an API request. Successful analyses in later steps can incur token charges.
Handling a live key can feel sensitive. This export keeps it out of the project files that you create later.
- Export your real API key for this Terminal session by replacing the placeholder in this command:
export OPENAI_API_KEY="your_api_key_here"
What does this export do?
The command places your key in OPENAI_API_KEY for the current Terminal session. The OpenAI SDK reads that environment variable automatically.
Closing this Terminal clears the session-scoped value. Your key does not enter the FileSense source code.
- Start another Python interpreter session by running this command:
python3
Why check from Python?
The check confirms that a Python process launched from this Terminal can see the exported environment variable. It reports only whether a value exists.
- Check that the key is present without printing the secret by entering these lines:
import os
print(bool(os.getenv("OPENAI_API_KEY")))
import sys; sys.exit()
How does this protect the key?
The environment lookup retrieves the value associated with OPENAI_API_KEY. Converting it to a Boolean prints only whether the value is present.
The secret itself never appears in the verification output.
You should see True before returning to the shell.
Before you run the final check, which package version and credential state do you expect the interpreter to report?
- Start the final Python verification session by running this command:
python3
What is the final check testing?
This session uses the active virtual environment and inherits the current Terminal environment. One check can now confirm both setup requirements.
- Verify the SDK version and credential state by entering these lines:
import openai, os
print(openai.__version__, bool(os.getenv("OPENAI_API_KEY")))
import sys; sys.exit()
What does this result confirm?
- The package version confirms that the active environment contains the pinned OpenAI SDK.
- The Boolean confirms that this Python process can access the session-scoped API key.
- The exit line returns you to the shell without making an API request.
You should see 3.39.0 True. That result proves the isolated SDK and session-scoped credential are ready.
Final check does not match?
If the package version differs, confirm that the virtual environment name appears in your prompt before starting Python.
If the credential check reports a false value, the export may have run in another Terminal window. Return to the same Terminal session that contains the active .venv environment.
help me fix the final FileSense setup check
- Position your Terminal so the final check and its result are visible.
- Keep the earlier export command outside the screenshot so your API key stays private.
That's the safety layer in place. Next, you will build the first runnable FileSense analyzer and see its AI response in Terminal.
Build a One-Shot AI Analyzer
Your Python workspace is ready for its first AI request. The active virtual environment keeps this project separate from your other Python work.
A direct-text analyzer gives you the shortest path to a visible response from the OpenAI Responses API. You will run that working version before testing what its one-argument interface actually understands.
In this step, get ready to:
- Create a sample release report with known facts.
- Build a direct-text analyzer that sends one argument to OpenAI.
- Compare the response to direct text with the response to a filename.
Create the Project Phoenix sample
A controlled sample gives you facts that can be checked against an AI response. This report makes the later filename test measurable.
- Press Cmd+Space to open macOS search.
- Type VS Code and press Enter to open VS Code.
- Click File in the top menu bar.
- Select Open Folder.
- Select the existing filesense folder from Step 1.
- Click Open.
You should see the filesense folder in the Explorer sidebar. This confirms that new files will be created in the same folder as your active Terminal workspace.
- Click the New File icon in the Explorer sidebar.
- Type sample.txt and press Enter.
- Add the fictional release report by pasting this text:
Project Phoenix release report
Owner: Maya Chen
Launch date: 14 November 2026
Blocker: The payment sandbox returns HTTP 503 during checkout tests.
Next action: Luis will rerun checkout tests after sandbox access is restored.
What Is in This Report?
- The report names Maya Chen as the owner.
- The launch date is 14 November 2026.
- The blocker is an HTTP 503 response from the payment sandbox.
- The next action belongs to Luis.
- Save sample.txt by pressing Cmd+S.
Your known-facts test file is ready. You should see sample.txt in the Explorer sidebar with the complete Project Phoenix report in the editor.
Can't See sample.txt?
Check that the Explorer sidebar shows the filesense folder. If the file tab still shows an unsaved indicator, press Cmd+S again.
Ask for help if the file was created in a different folder: help me locate sample.txt in my FileSense workspace.
✔️ Awesome, I've got everything!
Great. Make sure sample.txt is saved before you build the analyzer.
ⓧ I'd like to double check the full code
Here is the complete sample.txt file for comparison:
Project Phoenix release report
Owner: Maya Chen
Launch date: 14 November 2026
Blocker: The payment sandbox returns HTTP 503 during checkout tests.
Next action: Luis will rerun checkout tests after sandbox access is restored.
Build the direct-text analyzer
The analyzer needs a small command-line interface before it can send anything to the API. Its first interface accepts one positional value named text.
- Click the New File icon in the Explorer sidebar.
- Type filesense.py and press Enter.
- Create the model configuration and argument parser by pasting this code:
import argparse
from openai import OpenAI
MODEL = "gpt-6-luna"
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Send text to OpenAI."
)
parser.add_argument("text", help="Text to analyze.")
return parser.parse_args()
What Does This Code Do?
- The argparse import provides Python's command-line parsing tools.
- The OpenAI import provides the API client used later in the script.
- MODEL keeps the selected model name in one place.
- parse_args() creates the command-line parser.
- The positional text argument captures the phrase supplied after the script name.
- Save filesense.py by pressing Cmd+S.
You should see filesense.py in the Explorer sidebar. The file now contains FileSense's model setting and command-line interface.
Is OpenAI Underlined?
Check that VS Code is using the Python interpreter from .venv. The package is installed inside that isolated environment.
Ask for help selecting the correct interpreter: help me connect VS Code to my .venv environment.
The parser captures the learner's input. The next part creates the client and prints the returned text.
- Place your cursor below the return parser.parse_args() line in filesense.py.
- Complete the API request path by pasting this code:
def main() -> None:
args = parse_args()
client = OpenAI()
response = client.responses.create(
model=MODEL,
input=args.text,
)
print(response.output_text)
if __name__ == "__main__":
main()
How Does the Request Flow?
- main() calls parse_args() to collect the positional text.
- OpenAI() creates a client that reads OPENAI_API_KEY from the current environment.
- client.responses.create() sends args.text to the model selected by MODEL.
- response.output_text provides the response text for printing.
- The final condition runs main() when you execute filesense.py directly.
- Save filesense.py by pressing Cmd+S.
The saved file now has a complete path from Terminal input to printed API output. The next check proves that path works.
✔️ Awesome, I've got everything!
Great. Make sure filesense.py is saved before you run it.
ⓧ I'd like to double check the full code
Here is the complete filesense.py file for comparison:
import argparse
from openai import OpenAI
MODEL = "gpt-6-luna"
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Send text to OpenAI."
)
parser.add_argument("text", help="Text to analyze.")
return parser.parse_args()
def main() -> None:
args = parse_args()
client = OpenAI()
response = client.responses.create(
model=MODEL,
input=args.text,
)
print(response.output_text)
if __name__ == "__main__":
main()
Run the analyzer and test its limit
Your First Metered Request
This next check makes the project's first metered API request. It sends only the quoted sentence to the model.
You stay in control because no request runs until you enter the command.
Before you run it, picture whether FileSense returns one focused sentence or a longer explanation.
- Send the direct-text prompt to FileSense by running this command in the Terminal from Step 1:
python3 filesense.py "Explain Python virtual environments in one sentence"
What Does This Command Test?
- The quoted sentence becomes args.text.
- main() sends that text to the selected model.
- print() writes the returned text to Terminal.
You should see a short AI-generated explanation of Python virtual environments. The exact wording can vary between requests.
That is your first working FileSense loop. Terminal text now returns an AI response.
Did the Request Fail?
- Confirm that you are using the same Terminal window from Step 1.
- Confirm that the Terminal prompt still shows the active .venv environment.
- Re-export your session-scoped key if you opened a new Terminal window.
- Ask for help with the failure: help me diagnose my first FileSense API request.
The direct prompt worked because its full meaning was already present in the argument. Now the same positional interface gets a filename as its input.
Before you run it, picture whether the model can recover Maya Chen's name and the Project Phoenix launch date from the filename alone.
- Pass the sample filename to the current analyzer by running this command:
python3 filesense.py sample.txt
What Did This Command Send?
- The positional text argument receives the characters sample.txt.
- The API request contains that filename string.
- The Project Phoenix report remains inside the local file.
You will see a generic response based on the filename alone. The response cannot ground itself in the stored owner, date, blocker, or next action.
That shortfall is intentional. You have proved that a pathname string does not carry a file's contents.
Your one-shot analyzer can send direct text to OpenAI. Next up, you will teach FileSense to read the report itself and route that content through three analysis modes.
Read and Route Files
Your previous test exposed the exact gap. FileSense sent sample.txt as plain text, so OpenAI never saw the Project Phoenix report.
This step gives FileSense a validated local file pipeline. The selected mode then gives the Responses API a focused job for that file.
In this step, get ready to:
- Replace the direct-text argument with a file path plus an analysis mode.
- Load local UTF-8 content through file checks that stop invalid input early.
- Run summary, explanation, and action-item analyses grounded in the Project Phoenix report.
Define the file interface
A file analyzer needs two inputs. The file path identifies the source content. The mode identifies the job FileSense performs.
- In VS Code, switch back to filesense.py.
- Select everything from import argparse through return parser.parse_args().
- Replace the selection with the file interface code below:
import argparse
import os
import sys
from pathlib import Path
from openai import OpenAI
MODEL = "gpt-6-luna"
MAX_CHARACTERS = 20_000
TASKS = {
"summary": "Produce a concise summary with the file's purpose, key facts, and any risks.",
"explain": "Explain the file clearly to a technically literate reader.",
"actions": "Extract concrete action items without inventing missing details.",
}
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Analyze a local UTF-8 text file with OpenAI."
)
parser.add_argument("file", type=Path, help="Path to a local text or code file.")
parser.add_argument("mode", choices=tuple(TASKS))
return parser.parse_args()
What does this code do?
- Path converts the first command-line argument into a local path object.
- MAX_CHARACTERS sets the project's input guard to 20,000 characters.
- TASKS connects each mode to a focused instruction.
- choices=tuple(TASKS) limits the second argument to summary, explain, or actions.
Validate local file contents
Local validation catches file problems before FileSense creates an API client. This keeps missing, empty, invalid, or oversized input away from a metered request.
- Place your cursor below parse_args().
- Add the file-loading function with the code below:
def load_file(path: Path) -> str:
if not path.is_file():
raise FileNotFoundError(f"No readable file exists at: {path}")
text = path.read_text(encoding="utf-8")
if not text.strip():
raise ValueError("The input file is empty.")
if len(text) > MAX_CHARACTERS:
raise ValueError(f"The input has more than {MAX_CHARACTERS:,} characters.")
return text
How does local validation work?
- is_file() confirms that the path points to a file.
- read_text(encoding="utf-8") loads the file as UTF-8 text.
- text.strip() detects a file that contains no meaningful text.
- len(text) stops content above the project's 20,000-character guard.
The loader raises a specific exception for each local failure category. The main function can turn those exceptions into readable Terminal messages.
- Find the current line def main() -> None:.
- Select from that line through client = OpenAI().
- Replace the selection with the guarded main-function prefix below:
def main() -> int:
args = parse_args()
path = args.file.expanduser()
if not os.getenv("OPENAI_API_KEY"):
print("Error: OPENAI_API_KEY is not set.", file=sys.stderr)
return 1
try:
content = load_file(path)
except FileNotFoundError as exc:
print(f"Error: {exc}", file=sys.stderr)
return 1
except UnicodeDecodeError:
print("Error: The input file is not valid UTF-8 text.", file=sys.stderr)
return 1
except ValueError as exc:
print(f"Error: {exc}", file=sys.stderr)
return 1
except OSError as exc:
print(f"Error: Could not read the file: {exc}", file=sys.stderr)
return 1
client = OpenAI()
Why validate before creating the client?
os.getenv("OPENAI_API_KEY") checks that the current process can access the session-scoped credential. Error messages go to sys.stderr.
load_file(path) runs before OpenAI(). A rejected local file therefore stops before FileSense can make a paid API request.
Route the file into each mode
The validated content now needs to replace the old pathname-only input. FileSense also sends the selected task as a separate instruction.
- Find the existing block that starts with response = client.responses.create(.
- Select from that line through the final main() call.
- Replace the selection with the routed request code below:
response = client.responses.create(
model=MODEL,
instructions=TASKS[args.mode],
input=f"File name: {path.name}\n\nFile contents:\n{content}",
)
print(response.output_text)
return 0
if __name__ == "__main__":
sys.exit(main())
What does the routed request do?
- TASKS[args.mode] selects the instruction that matches the learner's command-line mode.
- path.name gives the model the source filename without the full local path.
- content carries the text returned by load_file().
- sys.exit(main()) converts the integer returned by main() into the command's exit status.
- Save filesense.py.
✔️ Awesome, I've got everything!
Your file interface, local validation, and mode routing are now connected. Make sure filesense.py is saved before testing it.
ⓧ I'd like to double check the full code
Compare your saved filesense.py with this complete version:
import argparse
import os
import sys
from pathlib import Path
from openai import OpenAI
MODEL = "gpt-6-luna"
MAX_CHARACTERS = 20_000
TASKS = {
"summary": "Produce a concise summary with the file's purpose, key facts, and any risks.",
"explain": "Explain the file clearly to a technically literate reader.",
"actions": "Extract concrete action items without inventing missing details.",
}
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Analyze a local UTF-8 text file with OpenAI."
)
parser.add_argument("file", type=Path, help="Path to a local text or code file.")
parser.add_argument("mode", choices=tuple(TASKS))
return parser.parse_args()
def load_file(path: Path) -> str:
if not path.is_file():
raise FileNotFoundError(f"No readable file exists at: {path}")
text = path.read_text(encoding="utf-8")
if not text.strip():
raise ValueError("The input file is empty.")
if len(text) > MAX_CHARACTERS:
raise ValueError(f"The input has more than {MAX_CHARACTERS:,} characters.")
return text
def main() -> int:
args = parse_args()
path = args.file.expanduser()
if not os.getenv("OPENAI_API_KEY"):
print("Error: OPENAI_API_KEY is not set.", file=sys.stderr)
return 1
try:
content = load_file(path)
except FileNotFoundError as exc:
print(f"Error: {exc}", file=sys.stderr)
return 1
except UnicodeDecodeError:
print("Error: The input file is not valid UTF-8 text.", file=sys.stderr)
return 1
except ValueError as exc:
print(f"Error: {exc}", file=sys.stderr)
return 1
except OSError as exc:
print(f"Error: Could not read the file: {exc}", file=sys.stderr)
return 1
client = OpenAI()
response = client.responses.create(
model=MODEL,
instructions=TASKS[args.mode],
input=f"File name: {path.name}\n\nFile contents:\n{content}",
)
print(response.output_text)
return 0
if __name__ == "__main__":
sys.exit(main())
This reference matches the complete non-streamed analyzer for this step. Your existing sample.txt remains unchanged.
Each mode sends a request
The next three checks each send a metered OpenAI API request. Local file validation still runs before the API client is created.
Before you run the summary, which Project Phoenix facts do you expect FileSense to mention now that it can read the file?
- Run the grounded summary check in the Terminal from earlier:
python3 filesense.py sample.txt summary
What should you see?
You should receive a complete summary grounded in the report. It should refer to details such as Maya Chen, 14 November 2026, the payment sandbox HTTP 503 blocker, or Luis's next action.
That closes the filename-only gap. FileSense can now analyze facts that exist inside a local file.
- Run the same command again after changing the final summary value to explain.
- Run it once more after changing the final explain value to actions.
The explanation should describe the report for a technically literate reader. The action-item result should identify Luis's checkout test task without inventing a deadline.
Not seeing a grounded response?
- Confirm that the command uses sample.txt as the first argument.
- Check that input includes content instead of args.text.
- Confirm that you are using the same Terminal session where the virtual environment and credential remain active.
help me debug why FileSense is not using the contents of sample.txt.
Your analyzer now reads local content safely and routes it through three useful jobs. Next, you will stream the output progressively and convert common failures into readable Terminal messages.
Stream and Harden the CLI
Your analyzer now reads sample.txt safely. Each mode returns a complete analysis grounded in the Project Phoenix report.
That complete response can leave Terminal silent while the OpenAI request runs. The delay makes a working CLI feel unresponsive.
An uncaught API failure can also dump a traceback. Streaming events create feedback. Targeted exceptions keep failures readable.
In this step, get ready to:
- Stream analysis text into Terminal as each text delta arrives.
- Track whether the stream completes successfully.
- Convert expected local failures plus OpenAI failures into readable messages.
Stream each response incrementally
The Responses API can deliver text through server-sent events. Handling each text delta lets Terminal update while the analysis is generated.
- In filesense.py, place this helper below load_file() by adding the following code:
def stream_analysis(client: OpenAI, path: Path, content: str, mode: str) -> bool:
stream = client.responses.create(
model=MODEL,
instructions=TASKS[mode],
input=f"File name: {path.name}\n\nFile contents:\n{content}",
stream=True,
store=False,
)
completed = False
for event in stream:
if event.type == "response.output_text.delta":
print(event.delta, end="", flush=True)
elif event.type == "response.failed":
print("\nError: OpenAI reported a failed response.", file=sys.stderr)
return False
elif event.type == "response.completed":
completed = True
print()
if not completed:
print("Error: The stream ended before completion.", file=sys.stderr)
return completed
What does this helper do?
- The stream=True argument returns an event stream that your loop can process.
- The store=False argument prevents later retrieval of this generated response through the API.
- Each response.output_text.delta event carries the next piece of generated text.
- A response.failed event prints a readable message before the helper returns False.
- A response.completed event records that the full stream arrived.
- In main(), locate the request block that begins with client = OpenAI().
- Replace that request block with the following streaming call:
client = OpenAI()
print(f"Analyzing {path} with {MODEL} in {args.mode} mode...\n")
succeeded = stream_analysis(client, path, content, args.mode)
return 0 if succeeded else 1
How does the CLI use the stream?
The status line tells you which file is being analyzed. It also shows the selected model plus mode.
The completion Boolean becomes the process exit result. A completed stream exits successfully.
- Save filesense.py.
This next run makes a metered API request. The file remains small to keep the request focused.
Before you run it, consider this question: will the first words appear before the full analysis is complete.
- Test the streamed response by running this command:
python3 filesense.py sample.txt actions
What should I see?
You should see an analysis status line first. The action analysis should then appear progressively in Terminal.
The result should stay grounded in the Project Phoenix report. It should identify Luis's checkout test task.
No streamed text appearing?
- Check that stream_analysis() includes stream=True in the API request.
- Check that the event loop prints event.delta for response.output_text.delta events.
- Confirm that main() calls stream_analysis().
help me debug my FileSense streaming loop
That feedback loop is now working. FileSense prints useful text while the model is still generating the rest of the analysis.
Handle local and API failures
A dependable CLI gives each expected failure a focused message. It also returns a nonzero exit code so the shell can detect failure.
- In the import section of filesense.py, add this package import above from openai import OpenAI:
import openai
Why import the package too?
The existing class import creates the client. The package import gives main() access to the SDK's exception classes.
- Save filesense.py.
- Confirm that import openai appears above from openai import OpenAI.
- Locate the existing TASKS mapping near the top of filesense.py.
- Replace the mapping with these focused instructions:
TASKS = {
"summary": (
"Produce a concise summary with the file's purpose, key facts, and any risks."
),
"explain": (
"Explain the file clearly to a technically literate reader. "
"Define important terms and relationships."
),
"actions": (
"Extract concrete action items. For each item, name the owner, task, and "
"deadline when present. Do not invent missing details."
),
}
Why tighten the task instructions?
Each mode now defines its expected output more precisely. The actions mode asks for ownership plus deadlines when the file contains them.
Clear routing lets one validated file pipeline support three distinct jobs.
- Save filesense.py.
- Verify the updated actions instruction by running this command:
python3 filesense.py sample.txt actions
What should the refined mode produce?
You should see streamed action items grounded in sample.txt. The result should connect Luis to the checkout tests.
The model may describe the timing as dependent on restored sandbox access. It should avoid inventing a deadline.
- Locate the existing parse_args() function in filesense.py.
- Replace that function with the following version:
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Analyze a local UTF-8 text file with OpenAI."
)
parser.add_argument("file", type=Path, help="Path to a local text or code file.")
parser.add_argument(
"mode",
choices=tuple(TASKS),
help="Choose summary, explain, or actions.",
)
return parser.parse_args()
What does this parser define?
The first positional argument becomes a Path object. The second positional argument must match one of the keys in TASKS.
The help text makes the interface discoverable from Terminal.
- Locate the existing load_file() function in filesense.py.
- Replace that function with the following validated loader:
def load_file(path: Path) -> str:
if not path.is_file():
raise FileNotFoundError(f"No readable file exists at: {path}")
text = path.read_text(encoding="utf-8")
if not text.strip():
raise ValueError("The input file is empty.")
if len(text) > MAX_CHARACTERS:
raise ValueError(
f"The input has {len(text):,} characters; the project limit is "
f"{MAX_CHARACTERS:,}."
)
return text
How does local validation protect the request?
The loader rejects a missing path before an OpenAI client is created. It also rejects empty input.
The character guard reports the observed size plus the project limit. This keeps beginner runs predictable.
- Save filesense.py.
- Check the missing-file path by running this command:
python3 filesense.py missing.txt summary
What should the local check show?
You should see Error: No readable file exists at: missing.txt in Terminal. You should see no traceback.
This failure happens before client creation. No paid API request is made.
Seeing a traceback for the missing file?
- Confirm that load_file(path) remains inside the local try block.
- Confirm that FileNotFoundError has its own except block.
- Check that the error branch returns 1 after printing the message.
help me remove the missing-file traceback
- In main(), locate the section beginning with client = OpenAI().
- Replace that section through the function's final return with this hardened request flow:
client = OpenAI()
print(f"Analyzing {path} with {MODEL} in {args.mode} mode...\n")
try:
succeeded = stream_analysis(client, path, content, args.mode)
except openai.AuthenticationError:
print("\nError: OpenAI rejected the API key.", file=sys.stderr)
return 1
except openai.RateLimitError:
print("\nError: OpenAI rate or usage limit reached.", file=sys.stderr)
return 1
except openai.APIConnectionError:
print("\nError: Could not connect to OpenAI.", file=sys.stderr)
return 1
except openai.APIStatusError as exc:
print(
f"\nError: OpenAI returned HTTP status {exc.status_code}.",
file=sys.stderr,
)
return 1
return 0 if succeeded else 1
Which failures are now readable?
- Authentication failures report that OpenAI rejected the API key.
- Rate or usage limits receive a dedicated message.
- Connection failures report that the CLI could not reach OpenAI.
- Other API status failures include the returned status_code.
- The succeeded Boolean maps stream completion to the final exit code.
- Save filesense.py.
✔️ Awesome, I've got everything!
Your streaming helper plus targeted error handling are now in place. Make sure filesense.py is saved.
ⓧ I'd like to double check the full code
The complete filesense.py file should match this reference exactly.
import argparse
import os
import sys
from pathlib import Path
import openai
from openai import OpenAI
MODEL = "gpt-6-luna"
MAX_CHARACTERS = 20_000
TASKS = {
"summary": (
"Produce a concise summary with the file's purpose, key facts, and any risks."
),
"explain": (
"Explain the file clearly to a technically literate reader. "
"Define important terms and relationships."
),
"actions": (
"Extract concrete action items. For each item, name the owner, task, and "
"deadline when present. Do not invent missing details."
),
}
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Analyze a local UTF-8 text file with OpenAI."
)
parser.add_argument("file", type=Path, help="Path to a local text or code file.")
parser.add_argument(
"mode",
choices=tuple(TASKS),
help="Choose summary, explain, or actions.",
)
return parser.parse_args()
def load_file(path: Path) -> str:
if not path.is_file():
raise FileNotFoundError(f"No readable file exists at: {path}")
text = path.read_text(encoding="utf-8")
if not text.strip():
raise ValueError("The input file is empty.")
if len(text) > MAX_CHARACTERS:
raise ValueError(
f"The input has {len(text):,} characters; the project limit is "
f"{MAX_CHARACTERS:,}."
)
return text
def stream_analysis(client: OpenAI, path: Path, content: str, mode: str) -> bool:
stream = client.responses.create(
model=MODEL,
instructions=TASKS[mode],
input=f"File name: {path.name}\n\nFile contents:\n{content}",
stream=True,
store=False,
)
completed = False
for event in stream:
if event.type == "response.output_text.delta":
print(event.delta, end="", flush=True)
elif event.type == "response.failed":
print("\nError: OpenAI reported a failed response.", file=sys.stderr)
return False
elif event.type == "response.completed":
completed = True
print()
if not completed:
print("Error: The stream ended before completion.", file=sys.stderr)
return completed
def main() -> int:
args = parse_args()
path = args.file.expanduser()
if not os.getenv("OPENAI_API_KEY"):
print("Error: OPENAI_API_KEY is not set.", file=sys.stderr)
return 1
try:
content = load_file(path)
except FileNotFoundError as exc:
print(f"Error: {exc}", file=sys.stderr)
return 1
except UnicodeDecodeError:
print("Error: The input file is not valid UTF-8 text.", file=sys.stderr)
return 1
except ValueError as exc:
print(f"Error: {exc}", file=sys.stderr)
return 1
except OSError as exc:
print(f"Error: Could not read the file: {exc}", file=sys.stderr)
return 1
client = OpenAI()
print(f"Analyzing {path} with {MODEL} in {args.mode} mode...\n")
try:
succeeded = stream_analysis(client, path, content, args.mode)
except openai.AuthenticationError:
print("\nError: OpenAI rejected the API key.", file=sys.stderr)
return 1
except openai.RateLimitError:
print("\nError: OpenAI rate or usage limit reached.", file=sys.stderr)
return 1
except openai.APIConnectionError:
print("\nError: Could not connect to OpenAI.", file=sys.stderr)
return 1
except openai.APIStatusError as exc:
print(
f"\nError: OpenAI returned HTTP status {exc.status_code}.",
file=sys.stderr,
)
return 1
return 0 if succeeded else 1
if __name__ == "__main__":
sys.exit(main())
How this file fits together
This reference is the final base version of FileSense. It combines argument parsing with local validation.
The same file also contains the streaming helper plus targeted OpenAI error handling.
Verify success and safe failure
The final check tests both sides of the CLI. One command proves that valid input streams successfully. The other proves that invalid local input stops safely.
The successful check makes another metered API request. You can keep the Terminal output short by using the focused actions mode.
Before you run it, consider this question: which Project Phoenix detail should appear while the analysis is still streaming.
- Run the successful streaming check with this command:
python3 filesense.py sample.txt actions
What proves streaming works?
You should watch text appear incrementally until the response finishes. The analysis should identify the checkout test action from sample.txt.
A successful completion returns control to your shell without an error message.
Seeing an API failure?
- Read the friendly message to identify whether the failure involves authentication.
- Check whether the message points to a rate or usage limit.
- Confirm that the current Terminal session still has OPENAI_API_KEY available.
help me diagnose my FileSense API failure
Before you run the local failure check, consider this question: should FileSense contact OpenAI for a path that does not exist.
- Run the missing-file check with this command:
python3 filesense.py missing.txt summary
What proves local validation works?
You should see Error: No readable file exists at: missing.txt. You should see no traceback.
No analysis status line should appear. That proves FileSense stopped before creating an API request.
Missing file still reaches OpenAI?
- Confirm that content = load_file(path) appears before client = OpenAI().
- Confirm that the FileNotFoundError branch returns 1 immediately.
help me keep missing files away from the API
That is the robust loop complete. FileSense now streams valid analyses while turning expected failures into useful Terminal messages.
Secret mission
Save the Streamed Analysis
Turn FileSense's live analysis into a reusable local file. Add an optional save destination without sacrificing the streamed Terminal experience.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep, pause, or delete your local workspace. Keeping its files has no ongoing cost because future charges occur only when FileSense makes another metered OpenAI API request.
Resources you used:
- Local filesense folder containing filesense.py, sample.txt, and action-items.md.
- Python virtual environment stored in .venv. It contains the OpenAI Python SDK version 3.39.0.
- Session-scoped OPENAI_API_KEY stored in the current Terminal window.
Keep everything running
No action needed. Choose this option if you are still actively developing or testing FileSense.
- Keep the filesense folder with its CLI, sample report, virtual environment, and saved analysis.
- Keep the current Terminal window open only while you are actively building.
- Run FileSense again when you are ready to make another metered API request.
Pause - I'll come back to this later
This option ends the active shell state while preserving every project file for later.
- Exit the active virtual environment by running this command:
deactivate
What does this command do?
The command exits the active virtual environment. Your installed SDK and project files remain inside the filesense folder.
- Confirm that .venv no longer appears at the start of your Terminal prompt.
- Close the current Terminal window to clear its session-scoped OPENAI_API_KEY.
- Keep the filesense folder so you can reactivate the environment later.
Delete - I don't want to use this again
This option permanently removes your CLI, virtual environment, sample report, and saved analysis. Your OpenAI account remains untouched.
- Exit the active virtual environment by running this command:
deactivate
What does this command do?
The command exits the active virtual environment before you remove its files. This leaves the current shell ready to close.
- Confirm that .venv no longer appears at the start of your Terminal prompt.
- Close the current Terminal window to clear its session-scoped OPENAI_API_KEY.
- In Finder, search for filesense.
- Select the filesense folder that contains filesense.py.
- Move the selected filesense folder to Trash.
- Empty Trash to erase the folder permanently.
- Revoke the API key in the OpenAI dashboard if you exposed or stored it anywhere else.
Previously incurred OpenAI API usage cannot be undone.
Nice Work!
Nice Work!
You did it! You built FileSense, a Python command-line tool that turns validated local files into streamed analysis through the OpenAI Responses API.
You've learned how to:
- Design a clear file-and-mode interface that switches between summary, explain, or actions analysis.
- Build a safe input pipeline that checks each file before an API request. It catches missing, empty, unreadable, non-UTF-8, or oversized input with clear messages.
- Deliver streamed analysis by printing text delta events as they arrive. You also handle credential, connection, rate-limit, status, and incomplete-stream failures without a traceback.
- Secret Mission: Extended FileSense with an optional --save destination that preserves the completed analysis after streaming succeeds.
Ready to quiz yourself?