refactor: Convert to UV Python project with proper package structure
- Restructured project to use src/stocktool package layout - Migrated to UV for dependency management - Added pyproject.toml with all dependencies (sv-ttk, pillow, requests, pyyaml) - Organized test files into tests/ directory - Updated .gitignore for UV projects - Comprehensive README with installation and usage instructions - Removed old unused files (main.py, stock_tool_gui.py, duplicate copy) - Added CLI entry point: stock-tool command 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
13
.claude/settings.local.json
Normal file
13
.claude/settings.local.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(python -m py_compile:*)",
|
||||
"Bash(python:*)",
|
||||
"Bash(git add:*)",
|
||||
"Bash(uv:*)",
|
||||
"Bash(tree:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
}
|
||||
}
|
||||
202
.claude/skills/artifacts-builder/LICENSE.txt
Normal file
202
.claude/skills/artifacts-builder/LICENSE.txt
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
74
.claude/skills/artifacts-builder/SKILL.md
Normal file
74
.claude/skills/artifacts-builder/SKILL.md
Normal file
@@ -0,0 +1,74 @@
|
||||
---
|
||||
name: artifacts-builder
|
||||
description: Suite of tools for creating elaborate, multi-component claude.ai HTML artifacts using modern frontend web technologies (React, Tailwind CSS, shadcn/ui). Use for complex artifacts requiring state management, routing, or shadcn/ui components - not for simple single-file HTML/JSX artifacts.
|
||||
license: Complete terms in LICENSE.txt
|
||||
---
|
||||
|
||||
# Artifacts Builder
|
||||
|
||||
To build powerful frontend claude.ai artifacts, follow these steps:
|
||||
1. Initialize the frontend repo using `scripts/init-artifact.sh`
|
||||
2. Develop your artifact by editing the generated code
|
||||
3. Bundle all code into a single HTML file using `scripts/bundle-artifact.sh`
|
||||
4. Display artifact to user
|
||||
5. (Optional) Test the artifact
|
||||
|
||||
**Stack**: React 18 + TypeScript + Vite + Parcel (bundling) + Tailwind CSS + shadcn/ui
|
||||
|
||||
## Design & Style Guidelines
|
||||
|
||||
VERY IMPORTANT: To avoid what is often referred to as "AI slop", avoid using excessive centered layouts, purple gradients, uniform rounded corners, and Inter font.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Step 1: Initialize Project
|
||||
|
||||
Run the initialization script to create a new React project:
|
||||
```bash
|
||||
bash scripts/init-artifact.sh <project-name>
|
||||
cd <project-name>
|
||||
```
|
||||
|
||||
This creates a fully configured project with:
|
||||
- ✅ React + TypeScript (via Vite)
|
||||
- ✅ Tailwind CSS 3.4.1 with shadcn/ui theming system
|
||||
- ✅ Path aliases (`@/`) configured
|
||||
- ✅ 40+ shadcn/ui components pre-installed
|
||||
- ✅ All Radix UI dependencies included
|
||||
- ✅ Parcel configured for bundling (via .parcelrc)
|
||||
- ✅ Node 18+ compatibility (auto-detects and pins Vite version)
|
||||
|
||||
### Step 2: Develop Your Artifact
|
||||
|
||||
To build the artifact, edit the generated files. See **Common Development Tasks** below for guidance.
|
||||
|
||||
### Step 3: Bundle to Single HTML File
|
||||
|
||||
To bundle the React app into a single HTML artifact:
|
||||
```bash
|
||||
bash scripts/bundle-artifact.sh
|
||||
```
|
||||
|
||||
This creates `bundle.html` - a self-contained artifact with all JavaScript, CSS, and dependencies inlined. This file can be directly shared in Claude conversations as an artifact.
|
||||
|
||||
**Requirements**: Your project must have an `index.html` in the root directory.
|
||||
|
||||
**What the script does**:
|
||||
- Installs bundling dependencies (parcel, @parcel/config-default, parcel-resolver-tspaths, html-inline)
|
||||
- Creates `.parcelrc` config with path alias support
|
||||
- Builds with Parcel (no source maps)
|
||||
- Inlines all assets into single HTML using html-inline
|
||||
|
||||
### Step 4: Share Artifact with User
|
||||
|
||||
Finally, share the bundled HTML file in conversation with the user so they can view it as an artifact.
|
||||
|
||||
### Step 5: Testing/Visualizing the Artifact (Optional)
|
||||
|
||||
Note: This is a completely optional step. Only perform if necessary or requested.
|
||||
|
||||
To test/visualize the artifact, use available tools (including other Skills or built-in tools like Playwright or Puppeteer). In general, avoid testing the artifact upfront as it adds latency between the request and when the finished artifact can be seen. Test later, after presenting the artifact, if requested or if issues arise.
|
||||
|
||||
## Reference
|
||||
|
||||
- **shadcn/ui components**: https://ui.shadcn.com/docs/components
|
||||
54
.claude/skills/artifacts-builder/scripts/bundle-artifact.sh
Normal file
54
.claude/skills/artifacts-builder/scripts/bundle-artifact.sh
Normal file
@@ -0,0 +1,54 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "📦 Bundling React app to single HTML artifact..."
|
||||
|
||||
# Check if we're in a project directory
|
||||
if [ ! -f "package.json" ]; then
|
||||
echo "❌ Error: No package.json found. Run this script from your project root."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if index.html exists
|
||||
if [ ! -f "index.html" ]; then
|
||||
echo "❌ Error: No index.html found in project root."
|
||||
echo " This script requires an index.html entry point."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Install bundling dependencies
|
||||
echo "📦 Installing bundling dependencies..."
|
||||
pnpm add -D parcel @parcel/config-default parcel-resolver-tspaths html-inline
|
||||
|
||||
# Create Parcel config with tspaths resolver
|
||||
if [ ! -f ".parcelrc" ]; then
|
||||
echo "🔧 Creating Parcel configuration with path alias support..."
|
||||
cat > .parcelrc << 'EOF'
|
||||
{
|
||||
"extends": "@parcel/config-default",
|
||||
"resolvers": ["parcel-resolver-tspaths", "..."]
|
||||
}
|
||||
EOF
|
||||
fi
|
||||
|
||||
# Clean previous build
|
||||
echo "🧹 Cleaning previous build..."
|
||||
rm -rf dist bundle.html
|
||||
|
||||
# Build with Parcel
|
||||
echo "🔨 Building with Parcel..."
|
||||
pnpm exec parcel build index.html --dist-dir dist --no-source-maps
|
||||
|
||||
# Inline everything into single HTML
|
||||
echo "🎯 Inlining all assets into single HTML file..."
|
||||
pnpm exec html-inline dist/index.html > bundle.html
|
||||
|
||||
# Get file size
|
||||
FILE_SIZE=$(du -h bundle.html | cut -f1)
|
||||
|
||||
echo ""
|
||||
echo "✅ Bundle complete!"
|
||||
echo "📄 Output: bundle.html ($FILE_SIZE)"
|
||||
echo ""
|
||||
echo "You can now use this single HTML file as an artifact in Claude conversations."
|
||||
echo "To test locally: open bundle.html in your browser"
|
||||
322
.claude/skills/artifacts-builder/scripts/init-artifact.sh
Normal file
322
.claude/skills/artifacts-builder/scripts/init-artifact.sh
Normal file
@@ -0,0 +1,322 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Exit on error
|
||||
set -e
|
||||
|
||||
# Detect Node version
|
||||
NODE_VERSION=$(node -v | cut -d'v' -f2 | cut -d'.' -f1)
|
||||
|
||||
echo "🔍 Detected Node.js version: $NODE_VERSION"
|
||||
|
||||
if [ "$NODE_VERSION" -lt 18 ]; then
|
||||
echo "❌ Error: Node.js 18 or higher is required"
|
||||
echo " Current version: $(node -v)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Set Vite version based on Node version
|
||||
if [ "$NODE_VERSION" -ge 20 ]; then
|
||||
VITE_VERSION="latest"
|
||||
echo "✅ Using Vite latest (Node 20+)"
|
||||
else
|
||||
VITE_VERSION="5.4.11"
|
||||
echo "✅ Using Vite $VITE_VERSION (Node 18 compatible)"
|
||||
fi
|
||||
|
||||
# Detect OS and set sed syntax
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
SED_INPLACE="sed -i ''"
|
||||
else
|
||||
SED_INPLACE="sed -i"
|
||||
fi
|
||||
|
||||
# Check if pnpm is installed
|
||||
if ! command -v pnpm &> /dev/null; then
|
||||
echo "📦 pnpm not found. Installing pnpm..."
|
||||
npm install -g pnpm
|
||||
fi
|
||||
|
||||
# Check if project name is provided
|
||||
if [ -z "$1" ]; then
|
||||
echo "❌ Usage: ./create-react-shadcn-complete.sh <project-name>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PROJECT_NAME="$1"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
COMPONENTS_TARBALL="$SCRIPT_DIR/shadcn-components.tar.gz"
|
||||
|
||||
# Check if components tarball exists
|
||||
if [ ! -f "$COMPONENTS_TARBALL" ]; then
|
||||
echo "❌ Error: shadcn-components.tar.gz not found in script directory"
|
||||
echo " Expected location: $COMPONENTS_TARBALL"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "🚀 Creating new React + Vite project: $PROJECT_NAME"
|
||||
|
||||
# Create new Vite project (always use latest create-vite, pin vite version later)
|
||||
pnpm create vite "$PROJECT_NAME" --template react-ts
|
||||
|
||||
# Navigate into project directory
|
||||
cd "$PROJECT_NAME"
|
||||
|
||||
echo "🧹 Cleaning up Vite template..."
|
||||
$SED_INPLACE '/<link rel="icon".*vite\.svg/d' index.html
|
||||
$SED_INPLACE 's/<title>.*<\/title>/<title>'"$PROJECT_NAME"'<\/title>/' index.html
|
||||
|
||||
echo "📦 Installing base dependencies..."
|
||||
pnpm install
|
||||
|
||||
# Pin Vite version for Node 18
|
||||
if [ "$NODE_VERSION" -lt 20 ]; then
|
||||
echo "📌 Pinning Vite to $VITE_VERSION for Node 18 compatibility..."
|
||||
pnpm add -D vite@$VITE_VERSION
|
||||
fi
|
||||
|
||||
echo "📦 Installing Tailwind CSS and dependencies..."
|
||||
pnpm install -D tailwindcss@3.4.1 postcss autoprefixer @types/node tailwindcss-animate
|
||||
pnpm install class-variance-authority clsx tailwind-merge lucide-react next-themes
|
||||
|
||||
echo "⚙️ Creating Tailwind and PostCSS configuration..."
|
||||
cat > postcss.config.js << 'EOF'
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "📝 Configuring Tailwind with shadcn theme..."
|
||||
cat > tailwind.config.js << 'EOF'
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
darkMode: ["class"],
|
||||
content: [
|
||||
"./index.html",
|
||||
"./src/**/*.{js,ts,jsx,tsx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
border: "hsl(var(--border))",
|
||||
input: "hsl(var(--input))",
|
||||
ring: "hsl(var(--ring))",
|
||||
background: "hsl(var(--background))",
|
||||
foreground: "hsl(var(--foreground))",
|
||||
primary: {
|
||||
DEFAULT: "hsl(var(--primary))",
|
||||
foreground: "hsl(var(--primary-foreground))",
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: "hsl(var(--secondary))",
|
||||
foreground: "hsl(var(--secondary-foreground))",
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: "hsl(var(--destructive))",
|
||||
foreground: "hsl(var(--destructive-foreground))",
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: "hsl(var(--muted))",
|
||||
foreground: "hsl(var(--muted-foreground))",
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: "hsl(var(--accent))",
|
||||
foreground: "hsl(var(--accent-foreground))",
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: "hsl(var(--popover))",
|
||||
foreground: "hsl(var(--popover-foreground))",
|
||||
},
|
||||
card: {
|
||||
DEFAULT: "hsl(var(--card))",
|
||||
foreground: "hsl(var(--card-foreground))",
|
||||
},
|
||||
},
|
||||
borderRadius: {
|
||||
lg: "var(--radius)",
|
||||
md: "calc(var(--radius) - 2px)",
|
||||
sm: "calc(var(--radius) - 4px)",
|
||||
},
|
||||
keyframes: {
|
||||
"accordion-down": {
|
||||
from: { height: "0" },
|
||||
to: { height: "var(--radix-accordion-content-height)" },
|
||||
},
|
||||
"accordion-up": {
|
||||
from: { height: "var(--radix-accordion-content-height)" },
|
||||
to: { height: "0" },
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
"accordion-down": "accordion-down 0.2s ease-out",
|
||||
"accordion-up": "accordion-up 0.2s ease-out",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require("tailwindcss-animate")],
|
||||
}
|
||||
EOF
|
||||
|
||||
# Add Tailwind directives and CSS variables to index.css
|
||||
echo "🎨 Adding Tailwind directives and CSS variables..."
|
||||
cat > src/index.css << 'EOF'
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 0 0% 3.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 0 0% 3.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 0 0% 3.9%;
|
||||
--primary: 0 0% 9%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
--secondary: 0 0% 96.1%;
|
||||
--secondary-foreground: 0 0% 9%;
|
||||
--muted: 0 0% 96.1%;
|
||||
--muted-foreground: 0 0% 45.1%;
|
||||
--accent: 0 0% 96.1%;
|
||||
--accent-foreground: 0 0% 9%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 0 0% 89.8%;
|
||||
--input: 0 0% 89.8%;
|
||||
--ring: 0 0% 3.9%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 0 0% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
--card: 0 0% 3.9%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
--popover: 0 0% 3.9%;
|
||||
--popover-foreground: 0 0% 98%;
|
||||
--primary: 0 0% 98%;
|
||||
--primary-foreground: 0 0% 9%;
|
||||
--secondary: 0 0% 14.9%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
--muted: 0 0% 14.9%;
|
||||
--muted-foreground: 0 0% 63.9%;
|
||||
--accent: 0 0% 14.9%;
|
||||
--accent-foreground: 0 0% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 0 0% 14.9%;
|
||||
--input: 0 0% 14.9%;
|
||||
--ring: 0 0% 83.1%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# Add path aliases to tsconfig.json
|
||||
echo "🔧 Adding path aliases to tsconfig.json..."
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const config = JSON.parse(fs.readFileSync('tsconfig.json', 'utf8'));
|
||||
config.compilerOptions = config.compilerOptions || {};
|
||||
config.compilerOptions.baseUrl = '.';
|
||||
config.compilerOptions.paths = { '@/*': ['./src/*'] };
|
||||
fs.writeFileSync('tsconfig.json', JSON.stringify(config, null, 2));
|
||||
"
|
||||
|
||||
# Add path aliases to tsconfig.app.json
|
||||
echo "🔧 Adding path aliases to tsconfig.app.json..."
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const path = 'tsconfig.app.json';
|
||||
const content = fs.readFileSync(path, 'utf8');
|
||||
// Remove comments manually
|
||||
const lines = content.split('\n').filter(line => !line.trim().startsWith('//'));
|
||||
const jsonContent = lines.join('\n');
|
||||
const config = JSON.parse(jsonContent.replace(/\/\*[\s\S]*?\*\//g, '').replace(/,(\s*[}\]])/g, '\$1'));
|
||||
config.compilerOptions = config.compilerOptions || {};
|
||||
config.compilerOptions.baseUrl = '.';
|
||||
config.compilerOptions.paths = { '@/*': ['./src/*'] };
|
||||
fs.writeFileSync(path, JSON.stringify(config, null, 2));
|
||||
"
|
||||
|
||||
# Update vite.config.ts
|
||||
echo "⚙️ Updating Vite configuration..."
|
||||
cat > vite.config.ts << 'EOF'
|
||||
import path from "path";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import { defineConfig } from "vite";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
},
|
||||
},
|
||||
});
|
||||
EOF
|
||||
|
||||
# Install all shadcn/ui dependencies
|
||||
echo "📦 Installing shadcn/ui dependencies..."
|
||||
pnpm install @radix-ui/react-accordion @radix-ui/react-aspect-ratio @radix-ui/react-avatar @radix-ui/react-checkbox @radix-ui/react-collapsible @radix-ui/react-context-menu @radix-ui/react-dialog @radix-ui/react-dropdown-menu @radix-ui/react-hover-card @radix-ui/react-label @radix-ui/react-menubar @radix-ui/react-navigation-menu @radix-ui/react-popover @radix-ui/react-progress @radix-ui/react-radio-group @radix-ui/react-scroll-area @radix-ui/react-select @radix-ui/react-separator @radix-ui/react-slider @radix-ui/react-slot @radix-ui/react-switch @radix-ui/react-tabs @radix-ui/react-toast @radix-ui/react-toggle @radix-ui/react-toggle-group @radix-ui/react-tooltip
|
||||
pnpm install sonner cmdk vaul embla-carousel-react react-day-picker react-resizable-panels date-fns react-hook-form @hookform/resolvers zod
|
||||
|
||||
# Extract shadcn components from tarball
|
||||
echo "📦 Extracting shadcn/ui components..."
|
||||
tar -xzf "$COMPONENTS_TARBALL" -C src/
|
||||
|
||||
# Create components.json for reference
|
||||
echo "📝 Creating components.json config..."
|
||||
cat > components.json << 'EOF'
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "default",
|
||||
"rsc": false,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.js",
|
||||
"css": "src/index.css",
|
||||
"baseColor": "slate",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "✅ Setup complete! You can now use Tailwind CSS and shadcn/ui in your project."
|
||||
echo ""
|
||||
echo "📦 Included components (40+ total):"
|
||||
echo " - accordion, alert, aspect-ratio, avatar, badge, breadcrumb"
|
||||
echo " - button, calendar, card, carousel, checkbox, collapsible"
|
||||
echo " - command, context-menu, dialog, drawer, dropdown-menu"
|
||||
echo " - form, hover-card, input, label, menubar, navigation-menu"
|
||||
echo " - popover, progress, radio-group, resizable, scroll-area"
|
||||
echo " - select, separator, sheet, skeleton, slider, sonner"
|
||||
echo " - switch, table, tabs, textarea, toast, toggle, toggle-group, tooltip"
|
||||
echo ""
|
||||
echo "To start developing:"
|
||||
echo " cd $PROJECT_NAME"
|
||||
echo " pnpm dev"
|
||||
echo ""
|
||||
echo "📚 Import components like:"
|
||||
echo " import { Button } from '@/components/ui/button'"
|
||||
echo " import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'"
|
||||
echo " import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/dialog'"
|
||||
Binary file not shown.
203
.claude/skills/git-commit-helper/SKILL.md
Normal file
203
.claude/skills/git-commit-helper/SKILL.md
Normal file
@@ -0,0 +1,203 @@
|
||||
---
|
||||
name: Git Commit Helper
|
||||
description: Generate descriptive commit messages by analyzing git diffs. Use when the user asks for help writing commit messages or reviewing staged changes.
|
||||
---
|
||||
|
||||
# Git Commit Helper
|
||||
|
||||
## Quick start
|
||||
|
||||
Analyze staged changes and generate commit message:
|
||||
|
||||
```bash
|
||||
# View staged changes
|
||||
git diff --staged
|
||||
|
||||
# Generate commit message based on changes
|
||||
# (Claude will analyze the diff and suggest a message)
|
||||
```
|
||||
|
||||
## Commit message format
|
||||
|
||||
Follow conventional commits format:
|
||||
|
||||
```
|
||||
<type>(<scope>): <description>
|
||||
|
||||
[optional body]
|
||||
|
||||
[optional footer]
|
||||
```
|
||||
|
||||
### Types
|
||||
|
||||
- **feat**: New feature
|
||||
- **fix**: Bug fix
|
||||
- **docs**: Documentation changes
|
||||
- **style**: Code style changes (formatting, missing semicolons)
|
||||
- **refactor**: Code refactoring
|
||||
- **test**: Adding or updating tests
|
||||
- **chore**: Maintenance tasks
|
||||
|
||||
### Examples
|
||||
|
||||
**Feature commit:**
|
||||
```
|
||||
feat(auth): add JWT authentication
|
||||
|
||||
Implement JWT-based authentication system with:
|
||||
- Login endpoint with token generation
|
||||
- Token validation middleware
|
||||
- Refresh token support
|
||||
```
|
||||
|
||||
**Bug fix:**
|
||||
```
|
||||
fix(api): handle null values in user profile
|
||||
|
||||
Prevent crashes when user profile fields are null.
|
||||
Add null checks before accessing nested properties.
|
||||
```
|
||||
|
||||
**Refactor:**
|
||||
```
|
||||
refactor(database): simplify query builder
|
||||
|
||||
Extract common query patterns into reusable functions.
|
||||
Reduce code duplication in database layer.
|
||||
```
|
||||
|
||||
## Analyzing changes
|
||||
|
||||
Review what's being committed:
|
||||
|
||||
```bash
|
||||
# Show files changed
|
||||
git status
|
||||
|
||||
# Show detailed changes
|
||||
git diff --staged
|
||||
|
||||
# Show statistics
|
||||
git diff --staged --stat
|
||||
|
||||
# Show changes for specific file
|
||||
git diff --staged path/to/file
|
||||
```
|
||||
|
||||
## Commit message guidelines
|
||||
|
||||
**DO:**
|
||||
- Use imperative mood ("add feature" not "added feature")
|
||||
- Keep first line under 50 characters
|
||||
- Capitalize first letter
|
||||
- No period at end of summary
|
||||
- Explain WHY not just WHAT in body
|
||||
|
||||
**DON'T:**
|
||||
- Use vague messages like "update" or "fix stuff"
|
||||
- Include technical implementation details in summary
|
||||
- Write paragraphs in summary line
|
||||
- Use past tense
|
||||
|
||||
## Multi-file commits
|
||||
|
||||
When committing multiple related changes:
|
||||
|
||||
```
|
||||
refactor(core): restructure authentication module
|
||||
|
||||
- Move auth logic from controllers to service layer
|
||||
- Extract validation into separate validators
|
||||
- Update tests to use new structure
|
||||
- Add integration tests for auth flow
|
||||
|
||||
Breaking change: Auth service now requires config object
|
||||
```
|
||||
|
||||
## Scope examples
|
||||
|
||||
**Frontend:**
|
||||
- `feat(ui): add loading spinner to dashboard`
|
||||
- `fix(form): validate email format`
|
||||
|
||||
**Backend:**
|
||||
- `feat(api): add user profile endpoint`
|
||||
- `fix(db): resolve connection pool leak`
|
||||
|
||||
**Infrastructure:**
|
||||
- `chore(ci): update Node version to 20`
|
||||
- `feat(docker): add multi-stage build`
|
||||
|
||||
## Breaking changes
|
||||
|
||||
Indicate breaking changes clearly:
|
||||
|
||||
```
|
||||
feat(api)!: restructure API response format
|
||||
|
||||
BREAKING CHANGE: All API responses now follow JSON:API spec
|
||||
|
||||
Previous format:
|
||||
{ "data": {...}, "status": "ok" }
|
||||
|
||||
New format:
|
||||
{ "data": {...}, "meta": {...} }
|
||||
|
||||
Migration guide: Update client code to handle new response structure
|
||||
```
|
||||
|
||||
## Template workflow
|
||||
|
||||
1. **Review changes**: `git diff --staged`
|
||||
2. **Identify type**: Is it feat, fix, refactor, etc.?
|
||||
3. **Determine scope**: What part of the codebase?
|
||||
4. **Write summary**: Brief, imperative description
|
||||
5. **Add body**: Explain why and what impact
|
||||
6. **Note breaking changes**: If applicable
|
||||
|
||||
## Interactive commit helper
|
||||
|
||||
Use `git add -p` for selective staging:
|
||||
|
||||
```bash
|
||||
# Stage changes interactively
|
||||
git add -p
|
||||
|
||||
# Review what's staged
|
||||
git diff --staged
|
||||
|
||||
# Commit with message
|
||||
git commit -m "type(scope): description"
|
||||
```
|
||||
|
||||
## Amending commits
|
||||
|
||||
Fix the last commit message:
|
||||
|
||||
```bash
|
||||
# Amend commit message only
|
||||
git commit --amend
|
||||
|
||||
# Amend and add more changes
|
||||
git add forgotten-file.js
|
||||
git commit --amend --no-edit
|
||||
```
|
||||
|
||||
## Best practices
|
||||
|
||||
1. **Atomic commits** - One logical change per commit
|
||||
2. **Test before commit** - Ensure code works
|
||||
3. **Reference issues** - Include issue numbers if applicable
|
||||
4. **Keep it focused** - Don't mix unrelated changes
|
||||
5. **Write for humans** - Future you will read this
|
||||
|
||||
## Commit message checklist
|
||||
|
||||
- [ ] Type is appropriate (feat/fix/docs/etc.)
|
||||
- [ ] Scope is specific and clear
|
||||
- [ ] Summary is under 50 characters
|
||||
- [ ] Summary uses imperative mood
|
||||
- [ ] Body explains WHY not just WHAT
|
||||
- [ ] Breaking changes are clearly marked
|
||||
- [ ] Related issue numbers are included
|
||||
202
.claude/skills/webapp-testing/LICENSE.txt
Normal file
202
.claude/skills/webapp-testing/LICENSE.txt
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
96
.claude/skills/webapp-testing/SKILL.md
Normal file
96
.claude/skills/webapp-testing/SKILL.md
Normal file
@@ -0,0 +1,96 @@
|
||||
---
|
||||
name: webapp-testing
|
||||
description: Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.
|
||||
license: Complete terms in LICENSE.txt
|
||||
---
|
||||
|
||||
# Web Application Testing
|
||||
|
||||
To test local web applications, write native Python Playwright scripts.
|
||||
|
||||
**Helper Scripts Available**:
|
||||
- `scripts/with_server.py` - Manages server lifecycle (supports multiple servers)
|
||||
|
||||
**Always run scripts with `--help` first** to see usage. DO NOT read the source until you try running the script first and find that a customized solution is abslutely necessary. These scripts can be very large and thus pollute your context window. They exist to be called directly as black-box scripts rather than ingested into your context window.
|
||||
|
||||
## Decision Tree: Choosing Your Approach
|
||||
|
||||
```
|
||||
User task → Is it static HTML?
|
||||
├─ Yes → Read HTML file directly to identify selectors
|
||||
│ ├─ Success → Write Playwright script using selectors
|
||||
│ └─ Fails/Incomplete → Treat as dynamic (below)
|
||||
│
|
||||
└─ No (dynamic webapp) → Is the server already running?
|
||||
├─ No → Run: python scripts/with_server.py --help
|
||||
│ Then use the helper + write simplified Playwright script
|
||||
│
|
||||
└─ Yes → Reconnaissance-then-action:
|
||||
1. Navigate and wait for networkidle
|
||||
2. Take screenshot or inspect DOM
|
||||
3. Identify selectors from rendered state
|
||||
4. Execute actions with discovered selectors
|
||||
```
|
||||
|
||||
## Example: Using with_server.py
|
||||
|
||||
To start a server, run `--help` first, then use the helper:
|
||||
|
||||
**Single server:**
|
||||
```bash
|
||||
python scripts/with_server.py --server "npm run dev" --port 5173 -- python your_automation.py
|
||||
```
|
||||
|
||||
**Multiple servers (e.g., backend + frontend):**
|
||||
```bash
|
||||
python scripts/with_server.py \
|
||||
--server "cd backend && python server.py" --port 3000 \
|
||||
--server "cd frontend && npm run dev" --port 5173 \
|
||||
-- python your_automation.py
|
||||
```
|
||||
|
||||
To create an automation script, include only Playwright logic (servers are managed automatically):
|
||||
```python
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.launch(headless=True) # Always launch chromium in headless mode
|
||||
page = browser.new_page()
|
||||
page.goto('http://localhost:5173') # Server already running and ready
|
||||
page.wait_for_load_state('networkidle') # CRITICAL: Wait for JS to execute
|
||||
# ... your automation logic
|
||||
browser.close()
|
||||
```
|
||||
|
||||
## Reconnaissance-Then-Action Pattern
|
||||
|
||||
1. **Inspect rendered DOM**:
|
||||
```python
|
||||
page.screenshot(path='/tmp/inspect.png', full_page=True)
|
||||
content = page.content()
|
||||
page.locator('button').all()
|
||||
```
|
||||
|
||||
2. **Identify selectors** from inspection results
|
||||
|
||||
3. **Execute actions** using discovered selectors
|
||||
|
||||
## Common Pitfall
|
||||
|
||||
❌ **Don't** inspect the DOM before waiting for `networkidle` on dynamic apps
|
||||
✅ **Do** wait for `page.wait_for_load_state('networkidle')` before inspection
|
||||
|
||||
## Best Practices
|
||||
|
||||
- **Use bundled scripts as black boxes** - To accomplish a task, consider whether one of the scripts available in `scripts/` can help. These scripts handle common, complex workflows reliably without cluttering the context window. Use `--help` to see usage, then invoke directly.
|
||||
- Use `sync_playwright()` for synchronous scripts
|
||||
- Always close the browser when done
|
||||
- Use descriptive selectors: `text=`, `role=`, CSS selectors, or IDs
|
||||
- Add appropriate waits: `page.wait_for_selector()` or `page.wait_for_timeout()`
|
||||
|
||||
## Reference Files
|
||||
|
||||
- **examples/** - Examples showing common patterns:
|
||||
- `element_discovery.py` - Discovering buttons, links, and inputs on a page
|
||||
- `static_html_automation.py` - Using file:// URLs for local HTML
|
||||
- `console_logging.py` - Capturing console logs during automation
|
||||
35
.claude/skills/webapp-testing/examples/console_logging.py
Normal file
35
.claude/skills/webapp-testing/examples/console_logging.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
# Example: Capturing console logs during browser automation
|
||||
|
||||
url = 'http://localhost:5173' # Replace with your URL
|
||||
|
||||
console_logs = []
|
||||
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.launch(headless=True)
|
||||
page = browser.new_page(viewport={'width': 1920, 'height': 1080})
|
||||
|
||||
# Set up console log capture
|
||||
def handle_console_message(msg):
|
||||
console_logs.append(f"[{msg.type}] {msg.text}")
|
||||
print(f"Console: [{msg.type}] {msg.text}")
|
||||
|
||||
page.on("console", handle_console_message)
|
||||
|
||||
# Navigate to page
|
||||
page.goto(url)
|
||||
page.wait_for_load_state('networkidle')
|
||||
|
||||
# Interact with the page (triggers console logs)
|
||||
page.click('text=Dashboard')
|
||||
page.wait_for_timeout(1000)
|
||||
|
||||
browser.close()
|
||||
|
||||
# Save console logs to file
|
||||
with open('/mnt/user-data/outputs/console.log', 'w') as f:
|
||||
f.write('\n'.join(console_logs))
|
||||
|
||||
print(f"\nCaptured {len(console_logs)} console messages")
|
||||
print(f"Logs saved to: /mnt/user-data/outputs/console.log")
|
||||
40
.claude/skills/webapp-testing/examples/element_discovery.py
Normal file
40
.claude/skills/webapp-testing/examples/element_discovery.py
Normal file
@@ -0,0 +1,40 @@
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
# Example: Discovering buttons and other elements on a page
|
||||
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.launch(headless=True)
|
||||
page = browser.new_page()
|
||||
|
||||
# Navigate to page and wait for it to fully load
|
||||
page.goto('http://localhost:5173')
|
||||
page.wait_for_load_state('networkidle')
|
||||
|
||||
# Discover all buttons on the page
|
||||
buttons = page.locator('button').all()
|
||||
print(f"Found {len(buttons)} buttons:")
|
||||
for i, button in enumerate(buttons):
|
||||
text = button.inner_text() if button.is_visible() else "[hidden]"
|
||||
print(f" [{i}] {text}")
|
||||
|
||||
# Discover links
|
||||
links = page.locator('a[href]').all()
|
||||
print(f"\nFound {len(links)} links:")
|
||||
for link in links[:5]: # Show first 5
|
||||
text = link.inner_text().strip()
|
||||
href = link.get_attribute('href')
|
||||
print(f" - {text} -> {href}")
|
||||
|
||||
# Discover input fields
|
||||
inputs = page.locator('input, textarea, select').all()
|
||||
print(f"\nFound {len(inputs)} input fields:")
|
||||
for input_elem in inputs:
|
||||
name = input_elem.get_attribute('name') or input_elem.get_attribute('id') or "[unnamed]"
|
||||
input_type = input_elem.get_attribute('type') or 'text'
|
||||
print(f" - {name} ({input_type})")
|
||||
|
||||
# Take screenshot for visual reference
|
||||
page.screenshot(path='/tmp/page_discovery.png', full_page=True)
|
||||
print("\nScreenshot saved to /tmp/page_discovery.png")
|
||||
|
||||
browser.close()
|
||||
@@ -0,0 +1,33 @@
|
||||
from playwright.sync_api import sync_playwright
|
||||
import os
|
||||
|
||||
# Example: Automating interaction with static HTML files using file:// URLs
|
||||
|
||||
html_file_path = os.path.abspath('path/to/your/file.html')
|
||||
file_url = f'file://{html_file_path}'
|
||||
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.launch(headless=True)
|
||||
page = browser.new_page(viewport={'width': 1920, 'height': 1080})
|
||||
|
||||
# Navigate to local HTML file
|
||||
page.goto(file_url)
|
||||
|
||||
# Take screenshot
|
||||
page.screenshot(path='/mnt/user-data/outputs/static_page.png', full_page=True)
|
||||
|
||||
# Interact with elements
|
||||
page.click('text=Click Me')
|
||||
page.fill('#name', 'John Doe')
|
||||
page.fill('#email', 'john@example.com')
|
||||
|
||||
# Submit form
|
||||
page.click('button[type="submit"]')
|
||||
page.wait_for_timeout(500)
|
||||
|
||||
# Take final screenshot
|
||||
page.screenshot(path='/mnt/user-data/outputs/after_submit.png', full_page=True)
|
||||
|
||||
browser.close()
|
||||
|
||||
print("Static HTML automation completed!")
|
||||
106
.claude/skills/webapp-testing/scripts/with_server.py
Normal file
106
.claude/skills/webapp-testing/scripts/with_server.py
Normal file
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Start one or more servers, wait for them to be ready, run a command, then clean up.
|
||||
|
||||
Usage:
|
||||
# Single server
|
||||
python scripts/with_server.py --server "npm run dev" --port 5173 -- python automation.py
|
||||
python scripts/with_server.py --server "npm start" --port 3000 -- python test.py
|
||||
|
||||
# Multiple servers
|
||||
python scripts/with_server.py \
|
||||
--server "cd backend && python server.py" --port 3000 \
|
||||
--server "cd frontend && npm run dev" --port 5173 \
|
||||
-- python test.py
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import socket
|
||||
import time
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
def is_server_ready(port, timeout=30):
|
||||
"""Wait for server to be ready by polling the port."""
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < timeout:
|
||||
try:
|
||||
with socket.create_connection(('localhost', port), timeout=1):
|
||||
return True
|
||||
except (socket.error, ConnectionRefusedError):
|
||||
time.sleep(0.5)
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Run command with one or more servers')
|
||||
parser.add_argument('--server', action='append', dest='servers', required=True, help='Server command (can be repeated)')
|
||||
parser.add_argument('--port', action='append', dest='ports', type=int, required=True, help='Port for each server (must match --server count)')
|
||||
parser.add_argument('--timeout', type=int, default=30, help='Timeout in seconds per server (default: 30)')
|
||||
parser.add_argument('command', nargs=argparse.REMAINDER, help='Command to run after server(s) ready')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Remove the '--' separator if present
|
||||
if args.command and args.command[0] == '--':
|
||||
args.command = args.command[1:]
|
||||
|
||||
if not args.command:
|
||||
print("Error: No command specified to run")
|
||||
sys.exit(1)
|
||||
|
||||
# Parse server configurations
|
||||
if len(args.servers) != len(args.ports):
|
||||
print("Error: Number of --server and --port arguments must match")
|
||||
sys.exit(1)
|
||||
|
||||
servers = []
|
||||
for cmd, port in zip(args.servers, args.ports):
|
||||
servers.append({'cmd': cmd, 'port': port})
|
||||
|
||||
server_processes = []
|
||||
|
||||
try:
|
||||
# Start all servers
|
||||
for i, server in enumerate(servers):
|
||||
print(f"Starting server {i+1}/{len(servers)}: {server['cmd']}")
|
||||
|
||||
# Use shell=True to support commands with cd and &&
|
||||
process = subprocess.Popen(
|
||||
server['cmd'],
|
||||
shell=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE
|
||||
)
|
||||
server_processes.append(process)
|
||||
|
||||
# Wait for this server to be ready
|
||||
print(f"Waiting for server on port {server['port']}...")
|
||||
if not is_server_ready(server['port'], timeout=args.timeout):
|
||||
raise RuntimeError(f"Server failed to start on port {server['port']} within {args.timeout}s")
|
||||
|
||||
print(f"Server ready on port {server['port']}")
|
||||
|
||||
print(f"\nAll {len(servers)} server(s) ready")
|
||||
|
||||
# Run the command
|
||||
print(f"Running: {' '.join(args.command)}\n")
|
||||
result = subprocess.run(args.command)
|
||||
sys.exit(result.returncode)
|
||||
|
||||
finally:
|
||||
# Clean up all servers
|
||||
print(f"\nStopping {len(server_processes)} server(s)...")
|
||||
for i, process in enumerate(server_processes):
|
||||
try:
|
||||
process.terminate()
|
||||
process.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
process.kill()
|
||||
process.wait()
|
||||
print(f"Server {i+1} stopped")
|
||||
print("All servers stopped")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
41
.gitignore
vendored
41
.gitignore
vendored
@@ -4,29 +4,20 @@ __pycache__/
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
dist/
|
||||
build/
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# Virtual Environment
|
||||
.venv/
|
||||
venv/
|
||||
env/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
env/
|
||||
|
||||
# UV
|
||||
.uv/
|
||||
uv.lock
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
@@ -34,20 +25,14 @@ venv.bak/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.DS_Store
|
||||
|
||||
# Configuration files (may contain sensitive data)
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Config
|
||||
*.yaml
|
||||
*.yml
|
||||
!example_config.yaml
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# Test files (optional - uncomment if you don't want to track tests)
|
||||
# test_*.py
|
||||
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.bak
|
||||
*.cache
|
||||
|
||||
58
PUSH_TO_GITEA.txt
Normal file
58
PUSH_TO_GITEA.txt
Normal file
@@ -0,0 +1,58 @@
|
||||
================================================================================
|
||||
QUICK START: Push to Gitea
|
||||
================================================================================
|
||||
|
||||
Your repository is initialized and ready! Here's what to do:
|
||||
|
||||
1. CREATE REPOSITORY ON GITEA
|
||||
- Go to your Gitea instance
|
||||
- Click "New Repository"
|
||||
- Name: inventree-stock-tool (or your choice)
|
||||
- Do NOT initialize with README (we already have one)
|
||||
- Click "Create Repository"
|
||||
|
||||
2. ADD GITEA REMOTE
|
||||
Gitea will show you a URL like:
|
||||
https://gitea.yourcompany.com/username/inventree-stock-tool.git
|
||||
|
||||
Run this command (replace with your actual URL):
|
||||
|
||||
cd "C:\Users\berwn\Desktop\barcodes\stocktool"
|
||||
git remote add origin https://gitea.yourcompany.com/username/inventree-stock-tool.git
|
||||
|
||||
3. PUSH TO GITEA
|
||||
|
||||
git push -u origin master
|
||||
|
||||
Enter your Gitea username and password when prompted.
|
||||
|
||||
4. VERIFY
|
||||
Open your Gitea repository in a browser - you should see all files!
|
||||
|
||||
================================================================================
|
||||
Current Repository Status
|
||||
================================================================================
|
||||
|
||||
Location: C:\Users\berwn\Desktop\barcodes\stocktool
|
||||
Branch: master
|
||||
Commits: 2
|
||||
|
||||
Files tracked:
|
||||
- stock_tool_gui_v2.py (main application)
|
||||
- README.md (documentation)
|
||||
- FIXES_APPLIED.md (bug fix details)
|
||||
- GITEA_SETUP.md (detailed instructions)
|
||||
- example_config.yaml (config template)
|
||||
- .gitignore (git configuration)
|
||||
- test_*.py (4 test files)
|
||||
|
||||
Files excluded (in .gitignore):
|
||||
- *.yaml (except example_config.yaml) - protects credentials
|
||||
- __pycache__/
|
||||
- *.pyc
|
||||
- Virtual environments
|
||||
- IDE files
|
||||
|
||||
================================================================================
|
||||
For detailed instructions, see: GITEA_SETUP.md
|
||||
================================================================================
|
||||
247
README.md
247
README.md
@@ -1,219 +1,138 @@
|
||||
# InvenTree Stock Tool
|
||||
|
||||
A comprehensive barcode scanning application for InvenTree inventory management with a modern GUI.
|
||||
|
||||

|
||||

|
||||
A comprehensive barcode scanning application for InvenTree inventory management.
|
||||
|
||||
## Features
|
||||
|
||||
- **Barcode Scanning**: Process barcodes in multiple formats (JSON-like, separator-based)
|
||||
- **Multiple Operation Modes**:
|
||||
- Add Stock: Create new stock items or add to existing ones
|
||||
- Update Stock: Manually update stock quantities
|
||||
- Check Stock: View current stock levels
|
||||
- Locate Part: Find all locations where a part is stored
|
||||
- **Smart Duplicate Prevention**: Automatically adds to existing stock items instead of creating duplicates
|
||||
- **Real-time Server Monitoring**: Visual connection status indicator
|
||||
- **Part Information Display**: Shows part details, parameters, and images
|
||||
- **Debug Mode**: Optional detailed logging for troubleshooting
|
||||
|
||||
## Screenshots
|
||||
|
||||
The application features a dark-themed interface with:
|
||||
- Location scanner
|
||||
- Mode selection (Add/Update/Check/Locate)
|
||||
- Part information display with images
|
||||
- Real-time activity logging
|
||||
- Server connection status
|
||||
- **Stock Addition**: Add stock to inventory by scanning barcodes
|
||||
- **Stock Updates**: Update existing stock levels
|
||||
- **Stock Checking**: Check current stock levels
|
||||
- **Part Location**: Find where parts are stored
|
||||
- **Server Connection Monitoring**: Real-time connection status
|
||||
- **Barcode Command Support**: Control the app via barcode commands
|
||||
|
||||
## Requirements
|
||||
|
||||
- Python 3.8 or higher
|
||||
- InvenTree server instance
|
||||
- Required Python packages:
|
||||
- `sv-ttk` - Modern themed tkinter widgets
|
||||
- `pillow` - Image handling
|
||||
- `requests` - HTTP API communication
|
||||
- `pyyaml` - Configuration file parsing
|
||||
- Python 3.9 or higher
|
||||
- InvenTree server with API access
|
||||
|
||||
## Installation
|
||||
|
||||
1. Clone this repository:
|
||||
### Using UV (Recommended)
|
||||
|
||||
```bash
|
||||
git clone <your-gitea-url>/stocktool.git
|
||||
# Clone the repository
|
||||
git clone <repository-url>
|
||||
cd stocktool
|
||||
|
||||
# Install with uv
|
||||
uv sync
|
||||
|
||||
# Run the application
|
||||
uv run stock-tool
|
||||
```
|
||||
|
||||
2. Install dependencies:
|
||||
### Manual Installation
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
pip install sv-ttk pillow requests pyyaml
|
||||
|
||||
# Run the application
|
||||
python src/stocktool/stock_tool_gui_v2.py
|
||||
```
|
||||
|
||||
3. Create configuration file at `~/.config/scan_and_import.yaml`:
|
||||
## Configuration
|
||||
|
||||
Create a configuration file at `~/.config/scan_and_import.yaml`:
|
||||
|
||||
```yaml
|
||||
host: https://your-inventree-server.com
|
||||
token: your-api-token-here
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Config File Location
|
||||
- **Linux/Mac**: `~/.config/scan_and_import.yaml`
|
||||
- **Windows**: `C:\Users\<username>\.config\scan_and_import.yaml`
|
||||
|
||||
### Config File Format
|
||||
```yaml
|
||||
host: https://inventree.example.com # Your InvenTree server URL (no trailing slash)
|
||||
token: abcdef1234567890 # Your InvenTree API token
|
||||
```
|
||||
|
||||
### Getting Your API Token
|
||||
1. Log into your InvenTree instance
|
||||
2. Navigate to Settings > User Settings > API Tokens
|
||||
3. Create a new token or copy an existing one
|
||||
### Required Fields
|
||||
- `host`: Your InvenTree server URL (without trailing slash)
|
||||
- `token`: Your InvenTree API token
|
||||
|
||||
## Usage
|
||||
|
||||
### Starting the Application
|
||||
```bash
|
||||
python stock_tool_gui_v2.py
|
||||
```
|
||||
|
||||
### Basic Workflow
|
||||
|
||||
1. **Set Location**: Scan a location barcode (format: `INV-SL<location_id>`)
|
||||
2. **Select Mode**: Choose operation mode (Add/Update/Check/Locate)
|
||||
1. **Select Location**: Scan a location barcode or enter location ID
|
||||
2. **Select Mode**: Choose operation mode (Add Stock, Update Stock, Check Stock, or Locate Part)
|
||||
3. **Scan Parts**: Scan part barcodes to perform operations
|
||||
|
||||
### Supported Barcode Formats
|
||||
|
||||
#### JSON-like Format
|
||||
```
|
||||
{pbn:PICK251017100019,on:WM2510170196,pc:C18548292,pm:STHW4-DU-HS24041,qty:150,mc:,cc:1,pdi:180368458,hp:null,wc:JS}
|
||||
```
|
||||
- `pm`: Part code
|
||||
- `qty`: Quantity
|
||||
|
||||
#### Separator-based Format
|
||||
Uses ASCII separators (GS/RS) to delimit fields:
|
||||
- `30P<part_code>` - Part code with 30P prefix
|
||||
- `1P<part_code>` - Part code with 1P prefix
|
||||
- `Q<quantity>` - Quantity
|
||||
|
||||
### Barcode Commands
|
||||
|
||||
You can use special barcodes to control the application:
|
||||
The application supports special barcode commands for quick mode switching:
|
||||
|
||||
**Mode Switching:**
|
||||
- `MODE:ADD` / `IMPORT` - Switch to Add Stock mode
|
||||
- `MODE:UPDATE` / `UPDATE` - Switch to Update Stock mode
|
||||
- `MODE:CHECK` / `CHECK` - Switch to Check Stock mode
|
||||
- `MODE:LOCATE` / `LOCATE` - Switch to Locate Part mode
|
||||
- **Mode Switching**:
|
||||
- `MODE:ADD`, `MODE:IMPORT`, `ADD_STOCK`, `IMPORT` - Switch to Add Stock mode
|
||||
- `MODE:UPDATE`, `UPDATE_STOCK`, `UPDATE` - Switch to Update Stock mode
|
||||
- `MODE:CHECK`, `MODE:GET`, `CHECK_STOCK`, `CHECK` - Switch to Check Stock mode
|
||||
- `MODE:LOCATE`, `LOCATE_PART`, `LOCATE`, `FIND_PART` - Switch to Locate Part mode
|
||||
|
||||
**Debug Control:**
|
||||
- `DEBUG:ON` - Enable debug mode
|
||||
- `DEBUG:OFF` - Disable debug mode
|
||||
- **Debug Control**:
|
||||
- `DEBUG:ON`, `DEBUG_ON` - Enable debug mode
|
||||
- `DEBUG:OFF`, `DEBUG_OFF` - Disable debug mode
|
||||
|
||||
**Location Management:**
|
||||
- `CHANGE_LOCATION` - Prompt for new location
|
||||
- **Location Management**:
|
||||
- `CHANGE_LOCATION`, `NEW_LOCATION`, `SET_LOCATION`, `LOCATION` - Change current location
|
||||
|
||||
## Operation Modes
|
||||
### Supported Barcode Formats
|
||||
|
||||
### Add Stock Mode
|
||||
Adds stock to InvenTree. If the part already exists at the location, it adds to the existing stock item instead of creating a duplicate.
|
||||
The application supports multiple barcode formats:
|
||||
|
||||
**Example:**
|
||||
- First scan: Creates StockItem #123 with quantity 150
|
||||
- Second scan: Updates StockItem #123 to quantity 250 (adds 100)
|
||||
|
||||
### Update Stock Mode
|
||||
Manually set the stock quantity for a part. Opens a dialog to enter the exact quantity.
|
||||
|
||||
### Check Stock Mode
|
||||
Displays the current stock level for a part at the selected location.
|
||||
|
||||
### Locate Part Mode
|
||||
Shows all locations where a part is stored, with quantities at each location.
|
||||
|
||||
## Character Encoding
|
||||
|
||||
The tool automatically cleans non-ASCII characters from part codes, handling common barcode encoding issues:
|
||||
- Input: `STHW4-DU-HS24041¡`
|
||||
- Cleaned: `STHW4-DU-HS24041`
|
||||
|
||||
## API Compatibility
|
||||
|
||||
This tool works with InvenTree's REST API and handles various response formats:
|
||||
- Paginated responses with `results` key
|
||||
- Direct list responses
|
||||
- Single object responses
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Connection Issues
|
||||
- Check that your InvenTree server URL is correct
|
||||
- Verify your API token is valid
|
||||
- Ensure the server is accessible from your network
|
||||
- Check the connection status indicator (green = connected)
|
||||
|
||||
### Import Failures
|
||||
If a part cannot be found, the tool attempts to import it using `inventree-part-import`. Ensure this tool is installed and configured.
|
||||
|
||||
### Debug Mode
|
||||
Enable debug mode via checkbox or `DEBUG:ON` barcode to see detailed operation logs.
|
||||
|
||||
## Recent Fixes
|
||||
|
||||
### Version 2.x (Latest)
|
||||
- Fixed encoding issues with non-ASCII characters in part codes
|
||||
- Fixed API response handling for both list and dict formats
|
||||
- Implemented smart duplicate prevention when adding stock
|
||||
- Moved misplaced function definitions to correct locations
|
||||
- Added comprehensive error logging with tracebacks
|
||||
|
||||
See `FIXES_APPLIED.md` for detailed information about recent bug fixes.
|
||||
1. **JSON-like format**: `{PM:PART-CODE,QTY:10}`
|
||||
2. **Separator-based format**: Fields separated by `\x1D` or `\x1E`
|
||||
- Part codes starting with `30P` or `1P`
|
||||
- Quantities starting with `Q`
|
||||
3. **InvenTree location barcodes**: `INV-SL<location_id>`
|
||||
|
||||
## Development
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
stocktool/
|
||||
├── stock_tool_gui_v2.py # Main application
|
||||
├── FIXES_APPLIED.md # Documentation of bug fixes
|
||||
├── test_parse_fix.py # Test for encoding fixes
|
||||
├── test_stock_level.py # Test for stock level retrieval
|
||||
├── test_add_stock.py # Test for API response handling
|
||||
├── test_duplicate_handling.py # Test for duplicate prevention
|
||||
├── .gitignore # Git ignore rules
|
||||
└── README.md # This file
|
||||
├── src/
|
||||
│ └── stocktool/
|
||||
│ ├── __init__.py
|
||||
│ └── stock_tool_gui_v2.py
|
||||
├── tests/
|
||||
│ ├── __init__.py
|
||||
│ ├── test_add_stock.py
|
||||
│ ├── test_duplicate_handling.py
|
||||
│ ├── test_parse_fix.py
|
||||
│ └── test_stock_level.py
|
||||
├── pyproject.toml
|
||||
├── .gitignore
|
||||
└── README.md
|
||||
```
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
python test_parse_fix.py
|
||||
python test_stock_level.py
|
||||
python test_add_stock.py
|
||||
python test_duplicate_handling.py
|
||||
# Using UV
|
||||
uv run pytest
|
||||
|
||||
# Manual
|
||||
pytest tests/
|
||||
```
|
||||
|
||||
## Contributing
|
||||
### Building
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
||||
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
||||
4. Push to the branch (`git push origin feature/amazing-feature`)
|
||||
5. Open a Pull Request
|
||||
```bash
|
||||
# Build the package
|
||||
uv build
|
||||
|
||||
# Install locally
|
||||
uv pip install -e .
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License - see the LICENSE file for details.
|
||||
MIT License
|
||||
|
||||
## Support
|
||||
## Contributing
|
||||
|
||||
For issues, questions, or contributions, please use the repository's issue tracker.
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
- Built for use with [InvenTree](https://inventree.org/)
|
||||
- Uses [sv-ttk](https://github.com/rdbende/Sun-Valley-ttk-theme) for modern theming
|
||||
Contributions are welcome! Please feel free to submit a Pull Request.
|
||||
|
||||
29
pyproject.toml
Normal file
29
pyproject.toml
Normal file
@@ -0,0 +1,29 @@
|
||||
[project]
|
||||
name = "stocktool"
|
||||
version = "2.0.0"
|
||||
description = "InvenTree Stock Management Tool - A comprehensive barcode scanning application for InvenTree inventory management"
|
||||
authors = [
|
||||
{name = "InvenTree Stock Tool Contributors"}
|
||||
]
|
||||
requires-python = ">=3.9"
|
||||
dependencies = [
|
||||
"sv-ttk>=2.0.0",
|
||||
"pillow>=10.0.0",
|
||||
"requests>=2.31.0",
|
||||
"pyyaml>=6.0.0",
|
||||
]
|
||||
readme = "README.md"
|
||||
license = {text = "MIT"}
|
||||
|
||||
[project.scripts]
|
||||
stock-tool = "stocktool.stock_tool_gui_v2:main"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["src/stocktool"]
|
||||
|
||||
[tool.uv]
|
||||
dev-dependencies = []
|
||||
0
src/stocktool/__init__.py
Normal file
0
src/stocktool/__init__.py
Normal file
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
Reference in New Issue
Block a user