Major refactor: Consolidate DSS into unified package structure

- Create new dss/ Python package at project root
- Move MCP core from tools/dss_mcp/ to dss/mcp/
- Move storage layer from tools/storage/ to dss/storage/
- Move domain logic from dss-mvp1/dss/ to dss/
- Move services from tools/api/services/ to dss/services/
- Move API server to apps/api/
- Move CLI to apps/cli/
- Move Storybook assets to storybook/
- Create unified dss/__init__.py with comprehensive exports
- Merge configuration into dss/settings.py (Pydantic-based)
- Create pyproject.toml for proper package management
- Update startup scripts for new paths
- Remove old tools/ and dss-mvp1/ directories

Architecture changes:
- DSS is now MCP-first with 40+ tools for Claude Code
- Clean imports: from dss import Projects, Components, FigmaToolSuite
- No more sys.path.insert() hacking
- apps/ contains thin application wrappers (API, CLI)
- Single unified Python package for all DSS logic

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-10 12:43:18 -03:00
parent bbd67f88c4
commit 41fba59bf7
197 changed files with 3185 additions and 15500 deletions

0
apps/cli/__init__.py Normal file
View File

624
apps/cli/package-lock.json generated Normal file
View File

@@ -0,0 +1,624 @@
{
"name": "@overbits/dss",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@overbits/dss",
"version": "0.1.0",
"license": "MIT",
"dependencies": {
"chalk": "^5.3.0",
"commander": "^12.0.0",
"conf": "^12.0.0",
"node-fetch": "^3.3.2",
"ora": "^8.0.1"
},
"bin": {
"dss": "dist/cli.js"
},
"devDependencies": {
"@types/node": "^20.10.0",
"typescript": "^5.3.0"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@types/node": {
"version": "20.19.25",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.25.tgz",
"integrity": "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
}
},
"node_modules/ajv": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/ajv-formats": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
"license": "MIT",
"dependencies": {
"ajv": "^8.0.0"
},
"peerDependencies": {
"ajv": "^8.0.0"
},
"peerDependenciesMeta": {
"ajv": {
"optional": true
}
}
},
"node_modules/ansi-regex": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/atomically": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/atomically/-/atomically-2.1.0.tgz",
"integrity": "sha512-+gDffFXRW6sl/HCwbta7zK4uNqbPjv4YJEAdz7Vu+FLQHe77eZ4bvbJGi4hE0QPeJlMYMA3piXEr1UL3dAwx7Q==",
"license": "MIT",
"dependencies": {
"stubborn-fs": "^2.0.0",
"when-exit": "^2.1.4"
}
},
"node_modules/chalk": {
"version": "5.6.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
"integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
"license": "MIT",
"engines": {
"node": "^12.17.0 || ^14.13 || >=16.0.0"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/cli-cursor": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
"integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==",
"license": "MIT",
"dependencies": {
"restore-cursor": "^5.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cli-spinners": {
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz",
"integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==",
"license": "MIT",
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/commander": {
"version": "12.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
"integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/conf": {
"version": "12.0.0",
"resolved": "https://registry.npmjs.org/conf/-/conf-12.0.0.tgz",
"integrity": "sha512-fIWyWUXrJ45cHCIQX+Ck1hrZDIf/9DR0P0Zewn3uNht28hbt5OfGUq8rRWsxi96pZWPyBEd0eY9ama01JTaknA==",
"license": "MIT",
"dependencies": {
"ajv": "^8.12.0",
"ajv-formats": "^2.1.1",
"atomically": "^2.0.2",
"debounce-fn": "^5.1.2",
"dot-prop": "^8.0.2",
"env-paths": "^3.0.0",
"json-schema-typed": "^8.0.1",
"semver": "^7.5.4",
"uint8array-extras": "^0.3.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/data-uri-to-buffer": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
"license": "MIT",
"engines": {
"node": ">= 12"
}
},
"node_modules/debounce-fn": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-5.1.2.tgz",
"integrity": "sha512-Sr4SdOZ4vw6eQDvPYNxHogvrxmCIld/VenC5JbNrFwMiwd7lY/Z18ZFfo+EWNG4DD9nFlAujWAo/wGuOPHmy5A==",
"license": "MIT",
"dependencies": {
"mimic-fn": "^4.0.0"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/dot-prop": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-8.0.2.tgz",
"integrity": "sha512-xaBe6ZT4DHPkg0k4Ytbvn5xoxgpG0jOS1dYxSOwAHPuNLjP3/OzN0gH55SrLqpx8cBfSaVt91lXYkApjb+nYdQ==",
"license": "MIT",
"dependencies": {
"type-fest": "^3.8.0"
},
"engines": {
"node": ">=16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/emoji-regex": {
"version": "10.6.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
"integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
"license": "MIT"
},
"node_modules/env-paths": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz",
"integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==",
"license": "MIT",
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"license": "MIT"
},
"node_modules/fast-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
"integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fastify"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fastify"
}
],
"license": "BSD-3-Clause"
},
"node_modules/fetch-blob": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "paypal",
"url": "https://paypal.me/jimmywarting"
}
],
"license": "MIT",
"dependencies": {
"node-domexception": "^1.0.0",
"web-streams-polyfill": "^3.0.3"
},
"engines": {
"node": "^12.20 || >= 14.13"
}
},
"node_modules/formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
"license": "MIT",
"dependencies": {
"fetch-blob": "^3.1.2"
},
"engines": {
"node": ">=12.20.0"
}
},
"node_modules/get-east-asian-width": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz",
"integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-interactive": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz",
"integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-unicode-supported": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz",
"integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"license": "MIT"
},
"node_modules/json-schema-typed": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz",
"integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==",
"license": "BSD-2-Clause"
},
"node_modules/log-symbols": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz",
"integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==",
"license": "MIT",
"dependencies": {
"chalk": "^5.3.0",
"is-unicode-supported": "^1.3.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/log-symbols/node_modules/is-unicode-supported": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz",
"integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/mimic-fn": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
"integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/mimic-function": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
"integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
"deprecated": "Use your platform's native DOMException instead",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "github",
"url": "https://paypal.me/jimmywarting"
}
],
"license": "MIT",
"engines": {
"node": ">=10.5.0"
}
},
"node_modules/node-fetch": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
"integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
"license": "MIT",
"dependencies": {
"data-uri-to-buffer": "^4.0.0",
"fetch-blob": "^3.1.4",
"formdata-polyfill": "^4.0.10"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/node-fetch"
}
},
"node_modules/onetime": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
"integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
"license": "MIT",
"dependencies": {
"mimic-function": "^5.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ora": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz",
"integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==",
"license": "MIT",
"dependencies": {
"chalk": "^5.3.0",
"cli-cursor": "^5.0.0",
"cli-spinners": "^2.9.2",
"is-interactive": "^2.0.0",
"is-unicode-supported": "^2.0.0",
"log-symbols": "^6.0.0",
"stdin-discarder": "^0.2.2",
"string-width": "^7.2.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/restore-cursor": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz",
"integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==",
"license": "MIT",
"dependencies": {
"onetime": "^7.0.0",
"signal-exit": "^4.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/semver": {
"version": "7.7.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/signal-exit": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"license": "ISC",
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/stdin-discarder": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz",
"integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/string-width": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
"integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^10.3.0",
"get-east-asian-width": "^1.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/strip-ansi": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
"integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/stubborn-fs": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/stubborn-fs/-/stubborn-fs-2.0.0.tgz",
"integrity": "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA==",
"license": "MIT",
"dependencies": {
"stubborn-utils": "^1.0.1"
}
},
"node_modules/stubborn-utils": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/stubborn-utils/-/stubborn-utils-1.0.2.tgz",
"integrity": "sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==",
"license": "MIT"
},
"node_modules/type-fest": {
"version": "3.13.1",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz",
"integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==",
"license": "(MIT OR CC0-1.0)",
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/uint8array-extras": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-0.3.0.tgz",
"integrity": "sha512-erJsJwQ0tKdwuqI0359U8ijkFmfiTcq25JvvzRVc1VP+2son1NJRXhxcAKJmAW3ajM8JSGAfsAXye8g4s+znxA==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true,
"license": "MIT"
},
"node_modules/web-streams-polyfill": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
"integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
"license": "MIT",
"engines": {
"node": ">= 8"
}
},
"node_modules/when-exit": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/when-exit/-/when-exit-2.1.5.tgz",
"integrity": "sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg==",
"license": "MIT"
}
}
}

51
apps/cli/package.json Normal file
View File

@@ -0,0 +1,51 @@
{
"name": "@overbits/dss",
"version": "0.1.0",
"description": "Design System Server - UI Developer Companion",
"type": "module",
"main": "dist/index.js",
"bin": {
"dss": "dist/cli.js"
},
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"start": "node dist/cli.js",
"postinstall": "node scripts/postinstall.js",
"prepublishOnly": "npm run build"
},
"keywords": [
"design-system",
"figma",
"tokens",
"components",
"ui",
"developer-tools"
],
"author": "Overbits",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/overbits/dss"
},
"engines": {
"node": ">=18.0.0"
},
"dependencies": {
"commander": "^12.0.0",
"chalk": "^5.3.0",
"ora": "^8.0.1",
"conf": "^12.0.0",
"node-fetch": "^3.3.2"
},
"devDependencies": {
"@types/node": "^20.10.0",
"typescript": "^5.3.0"
},
"files": [
"dist",
"python/api",
"scripts",
"README.md"
]
}

View File

@@ -0,0 +1,5 @@
fastapi>=0.100.0
uvicorn[standard]>=0.23.0
httpx>=0.24.0
python-dotenv>=1.0.0
pydantic>=2.0.0

View File

@@ -0,0 +1,724 @@
"""
Design System Server (DSS) - FastAPI Server
Portable API server providing:
- Project management (CRUD)
- Figma integration endpoints
- Discovery & health endpoints
- Activity tracking
- Runtime configuration management
- Service discovery (Storybook, etc.)
Modes:
- Server: Deployed remotely, serves design systems to teams
- Local: Dev companion, UI advisor, local services
Uses SQLite for persistence, integrates with Figma tools.
"""
import asyncio
import subprocess
import json
import os
from pathlib import Path
from typing import Optional, List, Dict, Any
from datetime import datetime
from fastapi import FastAPI, HTTPException, Query, BackgroundTasks
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
import sys
sys.path.insert(0, str(Path(__file__).parent.parent))
from config import config
from storage.json_store import (
Projects, Components, SyncHistory, ActivityLog, Teams, Cache, get_stats
)
from figma.figma_tools import FigmaToolSuite
# === Runtime Configuration ===
class RuntimeConfig:
"""
Runtime configuration that can be modified from the dashboard.
Persists to .dss/runtime-config.json for portability.
"""
def __init__(self):
self.config_path = Path(__file__).parent.parent.parent / ".dss" / "runtime-config.json"
self.config_path.parent.mkdir(parents=True, exist_ok=True)
self._data = self._load()
def _load(self) -> dict:
if self.config_path.exists():
try:
return json.loads(self.config_path.read_text())
except:
pass
return {
"mode": "local", # "local" or "server"
"figma": {"token": "", "configured": False},
"services": {
"storybook": {"enabled": False, "port": 6006, "url": ""},
"chromatic": {"enabled": False, "project_token": ""},
"github": {"enabled": False, "repo": ""},
},
"features": {
"visual_qa": True,
"token_sync": True,
"code_gen": True,
"ai_advisor": False,
}
}
def _save(self):
self.config_path.write_text(json.dumps(self._data, indent=2))
def get(self, key: str = None):
if key is None:
# Return safe copy without secrets
safe = self._data.copy()
if safe.get("figma", {}).get("token"):
safe["figma"]["token"] = "***configured***"
return safe
return self._data.get(key)
def set(self, key: str, value: Any):
self._data[key] = value
self._save()
return self._data[key]
def update(self, updates: dict):
for key, value in updates.items():
if isinstance(value, dict) and isinstance(self._data.get(key), dict):
self._data[key].update(value)
else:
self._data[key] = value
self._save()
return self.get()
def set_figma_token(self, token: str):
self._data["figma"]["token"] = token
self._data["figma"]["configured"] = bool(token)
self._save()
# Also update the global config
os.environ["FIGMA_TOKEN"] = token
return {"configured": bool(token)}
runtime_config = RuntimeConfig()
# === Service Discovery ===
class ServiceDiscovery:
"""Discovers and manages companion services."""
KNOWN_SERVICES = {
"storybook": {"ports": [6006, 6007], "health": "/"},
"chromatic": {"ports": [], "health": None},
"vite": {"ports": [5173, 5174, 3000], "health": "/"},
"webpack": {"ports": [8080, 8081], "health": "/"},
"nextjs": {"ports": [3000, 3001], "health": "/"},
}
@classmethod
async def discover(cls) -> dict:
"""Discover running services by checking known ports."""
import socket
discovered = {}
for service, info in cls.KNOWN_SERVICES.items():
for port in info["ports"]:
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(0.5)
result = sock.connect_ex(('127.0.0.1', port))
sock.close()
if result == 0:
discovered[service] = {
"running": True,
"port": port,
"url": f"http://localhost:{port}"
}
break
except:
pass
if service not in discovered:
discovered[service] = {"running": False, "port": None, "url": None}
return discovered
@classmethod
async def check_storybook(cls) -> dict:
"""Check Storybook status specifically."""
import httpx
configured = runtime_config.get("services").get("storybook", {})
port = configured.get("port", 6006)
url = configured.get("url") or f"http://localhost:{port}"
try:
async with httpx.AsyncClient(timeout=2.0) as client:
resp = await client.get(url)
return {
"running": resp.status_code == 200,
"url": url,
"port": port
}
except:
return {"running": False, "url": url, "port": port}
# === App Setup ===
app = FastAPI(
title="Design System Server (DSS)",
description="API for design system management and Figma integration",
version="1.0.0"
)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Mount Admin UI static files
UI_DIR = Path(__file__).parent.parent.parent / "admin-ui"
if UI_DIR.exists():
app.mount("/admin-ui", StaticFiles(directory=str(UI_DIR), html=True), name="admin-ui")
# Initialize Figma tools
figma_suite = FigmaToolSuite(output_dir=str(Path(__file__).parent.parent.parent / ".dss" / "output"))
# === Request/Response Models ===
class ProjectCreate(BaseModel):
name: str
description: str = ""
figma_file_key: str = ""
class ProjectUpdate(BaseModel):
name: Optional[str] = None
description: Optional[str] = None
figma_file_key: Optional[str] = None
status: Optional[str] = None
class FigmaExtractRequest(BaseModel):
file_key: str
format: str = "css"
class FigmaSyncRequest(BaseModel):
file_key: str
target_path: str
format: str = "css"
class TeamCreate(BaseModel):
name: str
description: str = ""
# === Root & Health ===
@app.get("/")
async def root():
"""Redirect to Admin UI dashboard."""
from fastapi.responses import RedirectResponse
return RedirectResponse(url="/admin-ui/index.html")
@app.get("/health")
async def health():
"""Health check endpoint."""
return {
"status": "ok",
"name": "dss-api",
"version": "1.0.0",
"timestamp": datetime.utcnow().isoformat() + "Z",
"figma_mode": figma_suite.mode,
"config": config.summary()
}
@app.get("/api/stats")
async def get_statistics():
"""Get database and system statistics."""
db_stats = get_stats()
return {
"database": db_stats,
"figma": {
"mode": figma_suite.mode,
"configured": config.figma.is_configured
}
}
# === Projects ===
@app.get("/api/projects")
async def list_projects(status: Optional[str] = None):
"""List all projects."""
projects = Projects.list(status=status)
return projects
@app.get("/api/projects/{project_id}")
async def get_project(project_id: str):
"""Get a specific project."""
project = Projects.get(project_id)
if not project:
raise HTTPException(status_code=404, detail="Project not found")
return project
@app.post("/api/projects")
async def create_project(project: ProjectCreate):
"""Create a new project."""
project_id = f"proj-{int(datetime.utcnow().timestamp() * 1000)}"
created = Projects.create(
id=project_id,
name=project.name,
description=project.description,
figma_file_key=project.figma_file_key
)
ActivityLog.log(
action="project_created",
entity_type="project",
entity_id=project_id,
project_id=project_id,
details={"name": project.name}
)
return created
@app.put("/api/projects/{project_id}")
async def update_project(project_id: str, update: ProjectUpdate):
"""Update a project."""
existing = Projects.get(project_id)
if not existing:
raise HTTPException(status_code=404, detail="Project not found")
update_data = {k: v for k, v in update.dict().items() if v is not None}
if not update_data:
return existing
updated = Projects.update(project_id, **update_data)
ActivityLog.log(
action="project_updated",
entity_type="project",
entity_id=project_id,
project_id=project_id,
details=update_data
)
return updated
@app.delete("/api/projects/{project_id}")
async def delete_project(project_id: str):
"""Delete a project."""
if not Projects.delete(project_id):
raise HTTPException(status_code=404, detail="Project not found")
ActivityLog.log(
action="project_deleted",
entity_type="project",
entity_id=project_id
)
return {"success": True}
# === Components ===
@app.get("/api/projects/{project_id}/components")
async def list_components(project_id: str):
"""List components for a project."""
if not Projects.get(project_id):
raise HTTPException(status_code=404, detail="Project not found")
return Components.list(project_id)
# === Figma Integration ===
@app.post("/api/figma/extract-variables")
async def extract_variables(request: FigmaExtractRequest, background_tasks: BackgroundTasks):
"""Extract design tokens from Figma file."""
try:
result = await figma_suite.extract_variables(request.file_key, request.format)
ActivityLog.log(
action="figma_extract_variables",
entity_type="figma",
details={"file_key": request.file_key, "format": request.format, "count": result.get("tokens_count")}
)
return result
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/figma/extract-components")
async def extract_components(request: FigmaExtractRequest):
"""Extract components from Figma file."""
try:
result = await figma_suite.extract_components(request.file_key)
ActivityLog.log(
action="figma_extract_components",
entity_type="figma",
details={"file_key": request.file_key, "count": result.get("components_count")}
)
return result
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/figma/extract-styles")
async def extract_styles(request: FigmaExtractRequest):
"""Extract styles from Figma file."""
try:
result = await figma_suite.extract_styles(request.file_key)
return result
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/figma/sync-tokens")
async def sync_tokens(request: FigmaSyncRequest):
"""Sync tokens from Figma to target path."""
try:
result = await figma_suite.sync_tokens(request.file_key, request.target_path, request.format)
ActivityLog.log(
action="figma_sync_tokens",
entity_type="figma",
details={"file_key": request.file_key, "target": request.target_path, "synced": result.get("tokens_synced")}
)
return result
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/figma/validate")
async def validate_components(request: FigmaExtractRequest):
"""Validate components against design system rules."""
try:
result = await figma_suite.validate_components(request.file_key)
return result
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/figma/generate-code")
async def generate_code(file_key: str, component_name: str, framework: str = "webcomponent"):
"""Generate component code from Figma."""
try:
result = await figma_suite.generate_code(file_key, component_name, framework)
return result
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
# === Discovery ===
@app.get("/api/discovery")
async def run_discovery(path: str = "."):
"""Run project discovery."""
script_path = Path(__file__).parent.parent / "discovery" / "discover.sh"
try:
result = subprocess.run(
[str(script_path), path],
capture_output=True,
text=True,
timeout=30
)
if result.returncode == 0:
return json.loads(result.stdout)
else:
return {"error": result.stderr}
except subprocess.TimeoutExpired:
raise HTTPException(status_code=504, detail="Discovery timed out")
except json.JSONDecodeError:
return {"raw_output": result.stdout}
@app.get("/api/discovery/ports")
async def discover_ports():
"""Discover listening ports and services."""
script_path = Path(__file__).parent.parent / "discovery" / "discover-ports.sh"
try:
result = subprocess.run(
[str(script_path)],
capture_output=True,
text=True,
timeout=10
)
return json.loads(result.stdout)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/discovery/env")
async def discover_env(path: str = "."):
"""Analyze environment configuration."""
script_path = Path(__file__).parent.parent / "discovery" / "discover-env.sh"
try:
result = subprocess.run(
[str(script_path), path],
capture_output=True,
text=True,
timeout=10
)
return json.loads(result.stdout)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
# === Activity & Sync History ===
@app.get("/api/activity")
async def get_activity(limit: int = Query(default=50, le=100)):
"""Get recent activity log."""
return ActivityLog.recent(limit=limit)
@app.get("/api/sync-history")
async def get_sync_history(project_id: Optional[str] = None, limit: int = Query(default=20, le=100)):
"""Get sync history."""
return SyncHistory.recent(project_id=project_id, limit=limit)
# === Teams ===
@app.get("/api/teams")
async def list_teams():
"""List all teams."""
return Teams.list()
@app.post("/api/teams")
async def create_team(team: TeamCreate):
"""Create a new team."""
team_id = f"team-{int(datetime.utcnow().timestamp() * 1000)}"
created = Teams.create(team_id, team.name, team.description)
return created
@app.get("/api/teams/{team_id}")
async def get_team(team_id: str):
"""Get a specific team."""
team = Teams.get(team_id)
if not team:
raise HTTPException(status_code=404, detail="Team not found")
return team
# === Cache Management ===
@app.post("/api/cache/clear")
async def clear_cache():
"""Clear expired cache entries."""
count = Cache.clear_expired()
return {"cleared": count}
@app.delete("/api/cache")
async def purge_cache():
"""Purge all cache entries."""
Cache.clear_all()
return {"success": True}
# === Configuration Management ===
class ConfigUpdate(BaseModel):
mode: Optional[str] = None
figma_token: Optional[str] = None
services: Optional[Dict[str, Any]] = None
features: Optional[Dict[str, bool]] = None
@app.get("/api/config")
async def get_config():
"""Get current runtime configuration (secrets masked)."""
return {
"config": runtime_config.get(),
"env": config.summary(),
"mode": runtime_config.get("mode")
}
@app.put("/api/config")
async def update_config(update: ConfigUpdate):
"""Update runtime configuration."""
updates = {}
if update.mode:
updates["mode"] = update.mode
if update.figma_token is not None:
runtime_config.set_figma_token(update.figma_token)
# Reinitialize Figma tools with new token
global figma_suite
figma_suite = FigmaToolSuite(output_dir=str(Path(__file__).parent.parent.parent / ".dss" / "output"))
ActivityLog.log(
action="figma_token_updated",
entity_type="config",
details={"configured": bool(update.figma_token)}
)
if update.services:
updates["services"] = update.services
if update.features:
updates["features"] = update.features
if updates:
runtime_config.update(updates)
ActivityLog.log(
action="config_updated",
entity_type="config",
details={"keys": list(updates.keys())}
)
return runtime_config.get()
@app.get("/api/config/figma")
async def get_figma_config():
"""Get Figma configuration status."""
figma_cfg = runtime_config.get("figma")
return {
"configured": figma_cfg.get("configured", False),
"mode": figma_suite.mode,
"features": {
"extract_variables": True,
"extract_components": True,
"extract_styles": True,
"sync_tokens": True,
"validate": True,
"generate_code": True,
}
}
@app.post("/api/config/figma/test")
async def test_figma_connection():
"""Test Figma API connection."""
try:
# Try to make a simple API call
if not runtime_config.get("figma").get("configured"):
return {"success": False, "error": "Figma token not configured"}
# Test with a minimal API call
import httpx
token = runtime_config._data["figma"]["token"]
async with httpx.AsyncClient() as client:
resp = await client.get(
"https://api.figma.com/v1/me",
headers={"X-Figma-Token": token}
)
if resp.status_code == 200:
user = resp.json()
return {
"success": True,
"user": user.get("email", "connected"),
"handle": user.get("handle")
}
else:
return {"success": False, "error": f"API returned {resp.status_code}"}
except Exception as e:
return {"success": False, "error": str(e)}
# === Service Discovery ===
@app.get("/api/services")
async def list_services():
"""List configured and discovered services."""
configured = runtime_config.get("services")
discovered = await ServiceDiscovery.discover()
return {
"configured": configured,
"discovered": discovered,
"storybook": await ServiceDiscovery.check_storybook()
}
@app.put("/api/services/{service_name}")
async def configure_service(service_name: str, config_data: Dict[str, Any]):
"""Configure a service."""
services = runtime_config.get("services") or {}
services[service_name] = {**services.get(service_name, {}), **config_data}
runtime_config.set("services", services)
ActivityLog.log(
action="service_configured",
entity_type="service",
entity_id=service_name,
details={"keys": list(config_data.keys())}
)
return services[service_name]
@app.get("/api/services/storybook")
async def get_storybook_status():
"""Get Storybook service status."""
return await ServiceDiscovery.check_storybook()
# === DSS Mode ===
@app.get("/api/mode")
async def get_mode():
"""Get current DSS mode."""
mode = runtime_config.get("mode")
return {
"mode": mode,
"description": "Local dev companion" if mode == "local" else "Remote design system server",
"features": runtime_config.get("features")
}
@app.put("/api/mode")
async def set_mode(mode: str):
"""Set DSS mode (local or server)."""
if mode not in ["local", "server"]:
raise HTTPException(status_code=400, detail="Mode must be 'local' or 'server'")
runtime_config.set("mode", mode)
ActivityLog.log(
action="mode_changed",
entity_type="config",
details={"mode": mode}
)
return {"mode": mode, "success": True}
# === Run Server ===
# === Static Files (Admin UI) ===
# Mount at the end so API routes take precedence
# This enables portable mode: ./dss start serves everything on one port
UI_DIR = Path(__file__).parent.parent.parent / "admin-ui"
if UI_DIR.exists():
app.mount("/", StaticFiles(directory=str(UI_DIR), html=True), name="ui")
if __name__ == "__main__":
import uvicorn
port = int(os.getenv("PORT", "3456"))
host = os.getenv("HOST", "0.0.0.0")
url = f"http://{host}:{port}"
print(f"""
╔═══════════════════════════════════════════════════════════════╗
║ Design System Server (DSS) - Portable Server ║
╠═══════════════════════════════════════════════════════════════╣
║ Dashboard: {url + '/':^47}
║ API: {url + '/api':^47}
║ Docs: {url + '/docs':^47}
║ Environment: {config.server.env:^47}
║ Figma Mode: {figma_suite.mode:^47}
╚═══════════════════════════════════════════════════════════════╝
""")
uvicorn.run(
"server:app",
host=host,
port=port,
reload=config.server.env == "development"
)

View File

@@ -0,0 +1,74 @@
#!/usr/bin/env node
/**
* DSS Postinstall Script
*
* Sets up Python virtual environment and installs dependencies
* after npm install.
*/
import { spawn, execSync } from 'child_process';
import { existsSync, mkdirSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const pythonDir = join(__dirname, '..', 'python');
const venvDir = join(pythonDir, 'venv');
const apiDir = join(pythonDir, 'api');
console.log('[DSS] Setting up Python environment...');
// Check if Python 3 is available
function getPythonCmd() {
const commands = ['python3', 'python'];
for (const cmd of commands) {
try {
execSync(`${cmd} --version`, { stdio: 'ignore' });
return cmd;
} catch {
continue;
}
}
return null;
}
const python = getPythonCmd();
if (!python) {
console.error('[DSS] Error: Python 3 is required but not found.');
console.error('[DSS] Please install Python 3.8+ and try again.');
process.exit(1);
}
console.log(`[DSS] Using ${python}`);
// Create virtual environment if needed
if (!existsSync(venvDir)) {
console.log('[DSS] Creating virtual environment...');
try {
execSync(`${python} -m venv ${venvDir}`, { stdio: 'inherit' });
} catch (error) {
console.error('[DSS] Failed to create virtual environment');
console.error('[DSS] Try: pip install virtualenv');
process.exit(1);
}
}
// Install requirements
const pip = join(venvDir, 'bin', 'pip');
const requirements = join(apiDir, 'requirements.txt');
if (existsSync(requirements)) {
console.log('[DSS] Installing Python dependencies...');
try {
execSync(`${pip} install -q -r ${requirements}`, { stdio: 'inherit' });
console.log('[DSS] Python environment ready!');
} catch (error) {
console.error('[DSS] Failed to install Python dependencies');
process.exit(1);
}
} else {
console.log('[DSS] No requirements.txt found, skipping dependency install');
}
console.log('[DSS] Setup complete!');

40
apps/cli/scripts/publish.sh Executable file
View File

@@ -0,0 +1,40 @@
#!/bin/bash
# DSS npm publish script
set -e
echo "🚀 Publishing @overbits/dss to npm..."
# Ensure we're in the cli directory
cd "$(dirname "$0")/.."
# Build
echo "📦 Building..."
npm run build
# Check if logged in to npm
if ! npm whoami &> /dev/null; then
echo "❌ Not logged in to npm. Run: npm login"
exit 1
fi
# Verify package
echo "📋 Package contents:"
npm pack --dry-run
# Confirm
read -p "Publish to npm? (y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "Cancelled."
exit 0
fi
# Publish
npm publish --access public
echo "✅ Published @overbits/dss!"
echo ""
echo "Users can now install with:"
echo " npm install -g @overbits/dss"
echo " npx @overbits/dss"

103
apps/cli/src/cli.ts Normal file
View File

@@ -0,0 +1,103 @@
#!/usr/bin/env node
/**
* 🧬 DSS CLI - Design System Server Organism Controller
*
* A portable companion for UI developers - think of it as the organism's
* command-line nervous system. Through these commands, you can:
*
* - 🧬 Awaken a new organism (init)
* - 💚 Check the organism's vital signs (status)
* - 🩸 Direct the sensory organs to perceive Figma (extract)
* - 🔄 Circulate extracted nutrients (sync)
* - ⚙️ Adjust the organism's behavior (config)
* - 🧠 Birth a conscious instance (start)
*
* Framework: DSS Organism Framework
*/
import { Command } from 'commander';
import chalk from 'chalk';
import { initCommand } from './commands/init.js';
import { startCommand } from './commands/start.js';
import { syncCommand } from './commands/sync.js';
import { extractCommand } from './commands/extract.js';
import { configCommand } from './commands/config.js';
import { statusCommand } from './commands/status.js';
import { stopCommand } from './commands/stop.js';
const program = new Command();
program
.name('dss')
.description('🧬 Design System Server - Organism Controller for UI Developers')
.version('0.1.0');
// Init command - setup DSS in a project
program
.command('init')
.description('🧬 ORGANISM GENESIS - Create a new design system organism in your project')
.option('-f, --figma-key <key>', 'Link to Figma genetic blueprint')
.option('-t, --figma-token <token>', 'Figma sensory organ connection token')
.action(initCommand);
// Start command - start the DSS server
program
.command('start')
.description('💚 ORGANISM AWAKENING - Bring the design system organism to life')
.option('-p, --port <port>', 'Neural pathway port', '3456')
.option('-d, --dev', 'Live consciousness mode with hot-reload')
.option('--no-open', 'Do not open sensory interface')
.action(startCommand);
// Sync command - sync tokens from Figma
program
.command('sync')
.description('🩸 NUTRIENT CIRCULATION - Distribute extracted tokens through the codebase')
.option('-f, --format <format>', 'Nutrient format: css, scss, json, ts', 'css')
.option('-o, --output <path>', 'Circulation destination')
.option('--file-key <key>', 'Figma file key (overrides config)')
.action(syncCommand);
// Extract command - extract components or tokens
program
.command('extract <type>')
.description('👁️ SENSORY PERCEPTION - Direct organism eyes to perceive Figma designs')
.option('-f, --format <format>', 'Perception output format', 'json')
.option('-o, --output <path>', 'Memory storage location')
.option('--file-key <key>', 'Figma file key')
.action(extractCommand);
// Config command - manage configuration
program
.command('config')
.description('⚙️ ENDOCRINE ADJUSTMENT - Configure organism behavior and preferences')
.option('--set <key=value>', 'Set organism hormone value')
.option('--get <key>', 'Read organism hormone value')
.option('--list', 'View all hormones')
.action(configCommand);
// Stop command - stop the server
program
.command('stop')
.description('😴 ORGANISM REST - Put the design system organism into sleep mode')
.action(stopCommand);
// Status command - check DSS status
program
.command('status')
.description('🏥 VITAL SIGNS CHECK - Monitor organism health and configuration')
.action(statusCommand);
// Parse arguments
program.parse();
// Show help if no command provided
if (!process.argv.slice(2).length) {
console.log(chalk.blue(`
╔═══════════════════════════════════════════════════════════════╗
${chalk.bold('DSS')} - Design System Server ║
║ UI Developer Companion ║
╚═══════════════════════════════════════════════════════════════╝
`));
program.outputHelp();
}

View File

@@ -0,0 +1,138 @@
/**
* DSS Config Command
*
* Manage DSS configuration.
*/
import chalk from 'chalk';
import {
getConfig,
loadProjectConfig,
saveProjectConfig,
setGlobalConfig,
getGlobalConfig,
listGlobalConfig,
hasProjectConfig,
} from '../lib/config.js';
interface ConfigOptions {
set?: string;
get?: string;
list?: boolean;
}
export async function configCommand(options: ConfigOptions): Promise<void> {
if (options.set) {
await setConfig(options.set);
} else if (options.get) {
await getConfigValue(options.get);
} else if (options.list) {
await listConfig();
} else {
await listConfig();
}
}
async function setConfig(keyValue: string): Promise<void> {
const [key, ...valueParts] = keyValue.split('=');
const value = valueParts.join('=');
if (!key || value === undefined) {
console.log(chalk.red(' Invalid format. Use: --set key=value'));
process.exit(1);
}
// Determine if this is a global or project config
const globalKeys = ['figmaToken', 'defaultPort', 'defaultFormat'];
const projectKeys = ['figmaFileKey', 'port', 'outputDir', 'tokenFormat', 'componentFramework'];
if (globalKeys.includes(key)) {
// Global config
const parsedValue = parseValue(value);
setGlobalConfig(key, parsedValue as string | number);
console.log(chalk.green(` Set global config: ${key}`));
console.log(chalk.dim(` Value: ${key === 'figmaToken' ? '***hidden***' : value}`));
} else if (projectKeys.includes(key)) {
// Project config
if (!hasProjectConfig()) {
console.log(chalk.yellow(' No project config found. Run: dss init'));
process.exit(1);
}
const config = loadProjectConfig();
(config as Record<string, unknown>)[key] = parseValue(value);
saveProjectConfig(config);
console.log(chalk.green(` Set project config: ${key}`));
console.log(chalk.dim(` Value: ${value}`));
} else {
console.log(chalk.yellow(` Unknown config key: ${key}`));
console.log('');
console.log(chalk.dim(' Global keys: figmaToken, defaultPort, defaultFormat'));
console.log(chalk.dim(' Project keys: figmaFileKey, port, outputDir, tokenFormat, componentFramework'));
}
}
async function getConfigValue(key: string): Promise<void> {
const config = getConfig();
const value = (config as Record<string, unknown>)[key];
if (value === undefined) {
// Try global config
const globalValue = getGlobalConfig(key);
if (globalValue !== undefined) {
console.log(chalk.dim(` ${key} (global):`), key === 'figmaToken' ? '***hidden***' : String(globalValue));
} else {
console.log(chalk.yellow(` Config key not found: ${key}`));
}
} else {
console.log(chalk.dim(` ${key}:`), key === 'figmaToken' ? '***hidden***' : String(value));
}
}
async function listConfig(): Promise<void> {
console.log('');
console.log(chalk.blue(' DSS Configuration'));
console.log(chalk.dim(' ─────────────────'));
console.log('');
// Project config
if (hasProjectConfig()) {
console.log(chalk.green(' Project Config:'));
const projectConfig = loadProjectConfig();
Object.entries(projectConfig).forEach(([key, value]) => {
console.log(chalk.dim(` ${key}:`), String(value));
});
} else {
console.log(chalk.yellow(' No project config (run: dss init)'));
}
console.log('');
// Global config
console.log(chalk.green(' Global Config:'));
const globalConf = listGlobalConfig();
Object.entries(globalConf).forEach(([key, value]) => {
const displayValue = key === 'figmaToken' ? '***hidden***' : String(value);
console.log(chalk.dim(` ${key}:`), displayValue);
});
console.log('');
// Merged config
console.log(chalk.green(' Effective Config:'));
const merged = getConfig();
Object.entries(merged).forEach(([key, value]) => {
const displayValue = key === 'figmaToken' ? (value ? '***configured***' : 'not set') : String(value);
console.log(chalk.dim(` ${key}:`), displayValue);
});
console.log('');
}
function parseValue(value: string): string | number | boolean {
if (value === 'true') return true;
if (value === 'false') return false;
if (!isNaN(Number(value))) return Number(value);
return value;
}

View File

@@ -0,0 +1,146 @@
/**
* DSS Extract Command
*
* Extract tokens or components from Figma.
*/
import chalk from 'chalk';
import ora from 'ora';
import { writeFileSync, existsSync, mkdirSync } from 'fs';
import { join } from 'path';
import { getConfig, getProjectRoot } from '../lib/config.js';
import { getApiClient } from '../lib/api.js';
import { isServerRunning } from '../lib/server.js';
interface ExtractOptions {
format: string;
output?: string;
fileKey?: string;
}
export async function extractCommand(
type: string,
options: ExtractOptions
): Promise<void> {
const validTypes = ['tokens', 'components', 'styles', 'all'];
if (!validTypes.includes(type)) {
console.log(chalk.red(` Invalid type: ${type}`));
console.log(chalk.dim(` Valid types: ${validTypes.join(', ')}`));
process.exit(1);
}
const config = getConfig();
const cwd = getProjectRoot();
// Check if server is running
if (!isServerRunning(cwd)) {
console.log(chalk.yellow(' DSS server is not running'));
console.log(chalk.dim(' Start it with: dss start'));
process.exit(1);
}
const fileKey = options.fileKey || config.figmaFileKey;
if (!fileKey) {
console.log(chalk.red(' No Figma file key configured'));
console.log(chalk.dim(' Set it with: dss config --set figmaFileKey=YOUR_KEY'));
process.exit(1);
}
const outputDir = options.output || join(cwd, '.dss', 'output');
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const api = getApiClient({ port: config.port });
if (type === 'tokens' || type === 'all') {
await extractTokens(api, fileKey, options.format, outputDir);
}
if (type === 'components' || type === 'all') {
await extractComponents(api, fileKey, outputDir);
}
if (type === 'styles' || type === 'all') {
await extractStyles(api, fileKey, outputDir);
}
}
async function extractTokens(
api: ReturnType<typeof getApiClient>,
fileKey: string,
format: string,
outputDir: string
): Promise<void> {
const spinner = ora('Extracting tokens...').start();
try {
const result = await api.extractTokens(fileKey, format || 'json');
const filename = format === 'css' ? 'tokens.css' :
format === 'scss' ? '_tokens.scss' :
format === 'ts' ? 'tokens.ts' : 'tokens.json';
const outputPath = join(outputDir, filename);
writeFileSync(outputPath, result.formatted_output);
spinner.succeed(`Extracted ${result.tokens_count} tokens`);
console.log(chalk.dim(` Output: ${outputPath}`));
} catch (error) {
spinner.fail('Failed to extract tokens');
console.error(chalk.red(` ${(error as Error).message}`));
}
}
async function extractComponents(
api: ReturnType<typeof getApiClient>,
fileKey: string,
outputDir: string
): Promise<void> {
const spinner = ora('Extracting components...').start();
try {
const result = await api.extractComponents(fileKey);
const outputPath = join(outputDir, 'components.json');
writeFileSync(outputPath, JSON.stringify(result.components, null, 2));
spinner.succeed(`Extracted ${result.components_count} components`);
console.log(chalk.dim(` Output: ${outputPath}`));
// Show component summary
console.log('');
result.components.forEach(comp => {
console.log(chalk.dim(` - ${comp.name}`));
if (comp.variants?.length) {
console.log(chalk.dim(` Variants: ${comp.variants.join(', ')}`));
}
});
} catch (error) {
spinner.fail('Failed to extract components');
console.error(chalk.red(` ${(error as Error).message}`));
}
}
async function extractStyles(
api: ReturnType<typeof getApiClient>,
fileKey: string,
outputDir: string
): Promise<void> {
const spinner = ora('Extracting styles...').start();
try {
// Note: This would need a corresponding API endpoint
// For now, we'll extract tokens which include style information
const result = await api.extractTokens(fileKey, 'json');
const outputPath = join(outputDir, 'styles.json');
writeFileSync(outputPath, JSON.stringify(result.tokens, null, 2));
spinner.succeed(`Extracted styles`);
console.log(chalk.dim(` Output: ${outputPath}`));
} catch (error) {
spinner.fail('Failed to extract styles');
console.error(chalk.red(` ${(error as Error).message}`));
}
}

View File

@@ -0,0 +1,107 @@
/**
* DSS Init Command
*
* Initialize DSS in the current project.
*/
import chalk from 'chalk';
import ora from 'ora';
import { existsSync, mkdirSync } from 'fs';
import { join } from 'path';
import {
getProjectRoot,
hasProjectConfig,
saveProjectConfig,
setGlobalConfig,
type DSSConfig,
} from '../lib/config.js';
interface InitOptions {
figmaKey?: string;
figmaToken?: string;
}
export async function initCommand(options: InitOptions): Promise<void> {
const spinner = ora('Initializing DSS...').start();
try {
const projectRoot = getProjectRoot();
const dssDir = join(projectRoot, '.dss');
// Check if already initialized
if (hasProjectConfig()) {
spinner.warn('DSS is already initialized in this project');
console.log(chalk.dim(` Config: ${join(dssDir, 'config.json')}`));
return;
}
// Create .dss directory
if (!existsSync(dssDir)) {
mkdirSync(dssDir, { recursive: true });
}
// Create output directory
const outputDir = join(dssDir, 'output');
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
// Build config
const config: DSSConfig = {
port: 3456,
outputDir: '.dss/output',
tokenFormat: 'css',
componentFramework: 'react',
};
if (options.figmaKey) {
config.figmaFileKey = options.figmaKey;
}
// Save Figma token globally (not in project config for security)
if (options.figmaToken) {
setGlobalConfig('figmaToken', options.figmaToken);
spinner.info('Figma token saved to global config');
}
// Save project config
saveProjectConfig(config);
spinner.succeed('DSS initialized successfully!');
console.log('');
console.log(chalk.green(' Created:'));
console.log(chalk.dim(` .dss/config.json`));
console.log(chalk.dim(` .dss/output/`));
console.log('');
// Next steps
console.log(chalk.blue(' Next steps:'));
if (!options.figmaToken) {
console.log(chalk.dim(' 1. Set your Figma token:'));
console.log(chalk.white(' dss config --set figmaToken=figd_xxxxx'));
}
if (!options.figmaKey) {
console.log(chalk.dim(` ${options.figmaToken ? '1' : '2'}. Set your Figma file key:`));
console.log(chalk.white(' dss config --set figmaFileKey=abc123'));
}
console.log(chalk.dim(` ${options.figmaToken && options.figmaKey ? '1' : '3'}. Start the server:`));
console.log(chalk.white(' dss start'));
console.log('');
// Add to .gitignore if exists
const gitignorePath = join(projectRoot, '.gitignore');
if (existsSync(gitignorePath)) {
const { readFileSync, appendFileSync } = await import('fs');
const gitignore = readFileSync(gitignorePath, 'utf-8');
if (!gitignore.includes('.dss/')) {
appendFileSync(gitignorePath, '\n# DSS\n.dss/\n');
console.log(chalk.dim(' Added .dss/ to .gitignore'));
}
}
} catch (error) {
spinner.fail('Failed to initialize DSS');
console.error(chalk.red(` ${(error as Error).message}`));
process.exit(1);
}
}

View File

@@ -0,0 +1,109 @@
/**
* DSS Start Command
*
* Start the DSS server.
*/
import chalk from 'chalk';
import ora from 'ora';
import { exec } from 'child_process';
import { getConfig, getProjectRoot, hasProjectConfig } from '../lib/config.js';
import {
startServer,
isServerRunning,
getServerPid,
waitForServer,
stopServer,
} from '../lib/server.js';
interface StartOptions {
port: string;
dev: boolean;
open: boolean;
}
export async function startCommand(options: StartOptions): Promise<void> {
const port = parseInt(options.port, 10);
const cwd = getProjectRoot();
const config = getConfig();
// Check if already running
if (isServerRunning(cwd)) {
const pid = getServerPid(cwd);
console.log(chalk.yellow(` DSS is already running (PID: ${pid})`));
console.log(chalk.dim(` Dashboard: http://localhost:${port}`));
console.log('');
console.log(chalk.dim(' To restart: dss stop && dss start'));
return;
}
const spinner = ora('Starting DSS server...').start();
try {
// Start server
const serverProcess = await startServer({
port,
dev: options.dev,
cwd,
});
if (options.dev) {
spinner.succeed('DSS running in development mode');
console.log(chalk.dim(' Press Ctrl+C to stop'));
console.log('');
// In dev mode, we're attached to the process
serverProcess.on('exit', (code) => {
console.log(chalk.dim(`\n Server exited with code ${code}`));
process.exit(code || 0);
});
// Handle Ctrl+C
process.on('SIGINT', () => {
console.log(chalk.dim('\n Stopping server...'));
serverProcess.kill('SIGTERM');
});
} else {
// Wait for server to be ready
spinner.text = 'Waiting for server to be ready...';
const ready = await waitForServer(port, 15000);
if (!ready) {
spinner.fail('Server failed to start');
console.error(chalk.red(' Check logs: .dss/dss.log'));
await stopServer(cwd);
process.exit(1);
}
spinner.succeed(`DSS started (PID: ${serverProcess.pid})`);
console.log('');
console.log(chalk.green(' Dashboard:'), chalk.blue(`http://localhost:${port}`));
console.log(chalk.green(' API: '), chalk.blue(`http://localhost:${port}/api`));
console.log(chalk.green(' Docs: '), chalk.blue(`http://localhost:${port}/docs`));
console.log('');
console.log(chalk.dim(' Logs: .dss/dss.log'));
console.log(chalk.dim(' Stop: dss stop'));
console.log('');
// Show Figma status
if (config.figmaFileKey) {
console.log(chalk.dim(` Figma file: ${config.figmaFileKey}`));
} else {
console.log(chalk.yellow(' No Figma file configured'));
console.log(chalk.dim(' Run: dss config --set figmaFileKey=YOUR_KEY'));
}
// Open browser if requested
if (options.open) {
const url = `http://localhost:${port}`;
const openCmd = process.platform === 'darwin' ? 'open' :
process.platform === 'win32' ? 'start' : 'xdg-open';
exec(`${openCmd} ${url}`);
}
}
} catch (error) {
spinner.fail('Failed to start DSS');
console.error(chalk.red(` ${(error as Error).message}`));
process.exit(1);
}
}

View File

@@ -0,0 +1,96 @@
/**
* 🏥 DSS Status Command - Organism Vital Signs
*
* Check DSS design system organism's vital signs, consciousness state,
* and sensory organ configuration.
*/
import chalk from 'chalk';
import { getConfig, getProjectRoot, hasProjectConfig } from '../lib/config.js';
import { isServerRunning, getServerPid } from '../lib/server.js';
import { getApiClient } from '../lib/api.js';
export async function statusCommand(): Promise<void> {
const cwd = getProjectRoot();
const config = getConfig();
console.log('');
console.log(chalk.cyan(' 🏥 ORGANISM VITAL SIGNS'));
console.log(chalk.dim(' ────────────────────────'));
console.log('');
// Organism status
if (hasProjectConfig()) {
console.log(chalk.green(' 🧬 Organism:'), chalk.dim('Born and conscious'));
console.log(chalk.dim(` Home: ${cwd}`));
} else {
console.log(chalk.yellow(' 🧬 Organism:'), chalk.dim('Not yet born'));
console.log(chalk.dim(' Genesis: dss init'));
}
console.log('');
// Consciousness status (server)
const running = isServerRunning(cwd);
const pid = getServerPid(cwd);
const port = config.port || 3456;
if (running) {
console.log(chalk.green(' 💚 Consciousness:'), chalk.dim(`Awake (PID: ${pid})`));
console.log(chalk.dim(` Neural port: http://localhost:${port}`));
// Try to get health info
try {
const api = getApiClient({ port });
const health = await api.health();
console.log(chalk.dim(` Awareness: ${health.figma_mode}`));
} catch {
console.log(chalk.yellow(' ⚠️ Unable to read consciousness'));
}
} else {
console.log(chalk.yellow(' 💚 Consciousness:'), chalk.dim('Sleeping'));
console.log(chalk.dim(' Awaken: dss start'));
}
console.log('');
// Sensory organs (Figma)
if (config.figmaToken) {
console.log(chalk.green(' 👁️ Sensory Eyes:'), chalk.dim('Configured'));
// Test connection if server is running
if (running) {
try {
const api = getApiClient({ port });
const test = await api.testFigmaConnection();
if (test.success) {
console.log(chalk.green(' Perception:'), chalk.dim(`Clear (${test.user})`));
} else {
console.log(chalk.red(' Perception:'), chalk.dim(test.error || 'Blinded'));
}
} catch {
console.log(chalk.yellow(' Perception:'), chalk.dim('Cannot test'));
}
}
} else {
console.log(chalk.yellow(' 👁️ Sensory Eyes:'), chalk.dim('Not configured'));
console.log(chalk.dim(' Configure: dss config --set figmaToken=figd_xxxxx'));
}
if (config.figmaFileKey) {
console.log(chalk.green(' 📋 Genetic Blueprint:'), chalk.dim(config.figmaFileKey));
} else {
console.log(chalk.yellow(' Figma File:'), chalk.dim('Not configured'));
console.log(chalk.dim(' Set: dss config --set figmaFileKey=abc123'));
}
console.log('');
// Output config
console.log(chalk.dim(' Output:'));
console.log(chalk.dim(` Format: ${config.tokenFormat || 'css'}`));
console.log(chalk.dim(` Framework: ${config.componentFramework || 'react'}`));
console.log(chalk.dim(` Directory: ${config.outputDir || '.dss/output'}`));
console.log('');
}

View File

@@ -0,0 +1,25 @@
/**
* DSS Stop Command
*
* Stop the DSS server.
*/
import chalk from 'chalk';
import { getProjectRoot } from '../lib/config.js';
import { isServerRunning, stopServer, getServerPid } from '../lib/server.js';
export async function stopCommand(): Promise<void> {
const cwd = getProjectRoot();
if (!isServerRunning(cwd)) {
console.log(chalk.yellow(' DSS is not running'));
return;
}
const pid = getServerPid(cwd);
console.log(chalk.dim(` Stopping DSS (PID: ${pid})...`));
await stopServer(cwd);
console.log(chalk.green(' DSS stopped'));
}

View File

@@ -0,0 +1,102 @@
/**
* DSS Sync Command
*
* Sync design tokens from Figma to local files.
*/
import chalk from 'chalk';
import ora from 'ora';
import { writeFileSync, existsSync, mkdirSync } from 'fs';
import { dirname, join } from 'path';
import { getConfig, getProjectRoot } from '../lib/config.js';
import { getApiClient } from '../lib/api.js';
import { isServerRunning } from '../lib/server.js';
interface SyncOptions {
format: string;
output?: string;
fileKey?: string;
}
export async function syncCommand(options: SyncOptions): Promise<void> {
const config = getConfig();
const cwd = getProjectRoot();
// Check if server is running
if (!isServerRunning(cwd)) {
console.log(chalk.yellow(' DSS server is not running'));
console.log(chalk.dim(' Start it with: dss start'));
console.log('');
console.log(chalk.dim(' Or sync directly via API if running remotely'));
process.exit(1);
}
const fileKey = options.fileKey || config.figmaFileKey;
if (!fileKey) {
console.log(chalk.red(' No Figma file key configured'));
console.log(chalk.dim(' Set it with: dss config --set figmaFileKey=YOUR_KEY'));
process.exit(1);
}
const format = options.format || config.tokenFormat || 'css';
const outputPath = options.output || getDefaultOutputPath(format, cwd);
const spinner = ora(`Syncing tokens from Figma (${format})...`).start();
try {
const api = getApiClient({ port: config.port });
// Extract tokens
spinner.text = 'Extracting tokens from Figma...';
const result = await api.extractTokens(fileKey, format);
if (!result.success) {
throw new Error('Token extraction failed');
}
// Write output file
spinner.text = `Writing ${result.tokens_count} tokens to ${outputPath}...`;
const dir = dirname(outputPath);
if (!existsSync(dir)) {
mkdirSync(dir, { recursive: true });
}
writeFileSync(outputPath, result.formatted_output);
spinner.succeed(`Synced ${result.tokens_count} tokens`);
console.log('');
console.log(chalk.green(' Output:'), chalk.dim(outputPath));
console.log('');
// Show token summary
const categories = result.tokens.reduce((acc, t) => {
acc[t.category] = (acc[t.category] || 0) + 1;
return acc;
}, {} as Record<string, number>);
console.log(chalk.dim(' Token breakdown:'));
Object.entries(categories).forEach(([cat, count]) => {
console.log(chalk.dim(` ${cat}: ${count}`));
});
console.log('');
} catch (error) {
spinner.fail('Sync failed');
console.error(chalk.red(` ${(error as Error).message}`));
process.exit(1);
}
}
function getDefaultOutputPath(format: string, cwd: string): string {
const extensions: Record<string, string> = {
css: 'tokens.css',
scss: '_tokens.scss',
json: 'tokens.json',
ts: 'tokens.ts',
js: 'tokens.js',
};
const filename = extensions[format] || 'tokens.css';
return join(cwd, '.dss', 'output', filename);
}

15
apps/cli/src/index.ts Normal file
View File

@@ -0,0 +1,15 @@
/**
* DSS - Design System Server
*
* Programmatic API for DSS.
*/
export { DSSApiClient, getApiClient, type ApiOptions } from './lib/api.js';
export { getConfig, getProjectRoot, hasProjectConfig, type DSSConfig } from './lib/config.js';
export {
startServer,
stopServer,
isServerRunning,
getServerPid,
waitForServer,
} from './lib/server.js';

144
apps/cli/src/lib/api.ts Normal file
View File

@@ -0,0 +1,144 @@
/**
* DSS API Client
*
* Communicates with the DSS server.
*/
import { getConfig } from './config.js';
export interface ApiOptions {
port?: number;
baseUrl?: string;
}
export class DSSApiClient {
private baseUrl: string;
constructor(options: ApiOptions = {}) {
const config = getConfig();
const port = options.port || config.port || 3456;
this.baseUrl = options.baseUrl || `http://localhost:${port}/api`;
}
private async request<T>(
endpoint: string,
options: RequestInit = {}
): Promise<T> {
const url = `${this.baseUrl}${endpoint}`;
const response = await fetch(url, {
...options,
headers: {
'Content-Type': 'application/json',
...options.headers,
},
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({ message: response.statusText })) as { message?: string };
throw new Error(errorData.message || `Request failed: ${response.status}`);
}
const text = await response.text();
return text ? JSON.parse(text) as T : {} as T;
}
async health(): Promise<{ status: string; figma_mode: string }> {
// Health endpoint is at root, not under /api
const url = this.baseUrl.replace('/api', '') + '/health';
const response = await fetch(url);
return response.json() as Promise<{ status: string; figma_mode: string }>;
}
async getConfig(): Promise<Record<string, unknown>> {
return this.request('/config');
}
async setFigmaToken(token: string): Promise<void> {
await this.request('/config', {
method: 'PUT',
body: JSON.stringify({ figma_token: token }),
});
}
async testFigmaConnection(): Promise<{ success: boolean; user?: string; error?: string }> {
return this.request('/config/figma/test', { method: 'POST' });
}
async extractTokens(fileKey: string, format: string = 'css'): Promise<{
success: boolean;
tokens_count: number;
tokens: Array<{ name: string; value: string; type: string; category: string }>;
formatted_output: string;
output_path: string;
}> {
return this.request('/figma/extract-variables', {
method: 'POST',
body: JSON.stringify({ file_key: fileKey, format }),
});
}
async extractComponents(fileKey: string): Promise<{
success: boolean;
components_count: number;
components: Array<{ name: string; key: string; description: string; variants: string[] }>;
output_path: string;
}> {
return this.request('/figma/extract-components', {
method: 'POST',
body: JSON.stringify({ file_key: fileKey }),
});
}
async syncTokens(fileKey: string, targetPath: string, format: string = 'css'): Promise<{
success: boolean;
tokens_synced: number;
output_file: string;
}> {
return this.request('/figma/sync-tokens', {
method: 'POST',
body: JSON.stringify({ file_key: fileKey, target_path: targetPath, format }),
});
}
async generateCode(fileKey: string, componentName: string, framework: string = 'react'): Promise<{
success: boolean;
component: string;
framework: string;
code: string;
}> {
return this.request('/figma/generate-code', {
method: 'POST',
body: JSON.stringify({ file_key: fileKey, component_name: componentName, framework }),
});
}
async getProjects(): Promise<Array<{
id: string;
name: string;
figma_file_key: string;
status: string;
}>> {
return this.request('/projects');
}
async createProject(data: {
name: string;
description?: string;
figma_file_key?: string;
}): Promise<{ id: string; name: string }> {
return this.request('/projects', {
method: 'POST',
body: JSON.stringify(data),
});
}
}
// Singleton instance
let apiClient: DSSApiClient | null = null;
export function getApiClient(options?: ApiOptions): DSSApiClient {
if (!apiClient || options) {
apiClient = new DSSApiClient(options);
}
return apiClient;
}

View File

@@ -0,0 +1,95 @@
/**
* DSS Configuration Manager
*
* Manages local and project configuration.
*/
import Conf from 'conf';
import { existsSync, readFileSync, writeFileSync } from 'fs';
import { join } from 'path';
// Global user config (stored in home directory)
const globalConfig = new Conf({
projectName: 'dss',
schema: {
figmaToken: { type: 'string' },
defaultPort: { type: 'number', default: 3456 },
defaultFormat: { type: 'string', default: 'css' },
},
});
// Project-level config file
const PROJECT_CONFIG_FILE = '.dss/config.json';
export interface DSSConfig {
figmaFileKey?: string;
figmaToken?: string;
port?: number;
outputDir?: string;
tokenFormat?: 'css' | 'scss' | 'json' | 'ts';
componentFramework?: 'react' | 'vue' | 'svelte' | 'webcomponent';
}
export function getProjectRoot(): string {
return process.cwd();
}
export function getProjectConfigPath(): string {
return join(getProjectRoot(), PROJECT_CONFIG_FILE);
}
export function hasProjectConfig(): boolean {
return existsSync(getProjectConfigPath());
}
export function loadProjectConfig(): DSSConfig {
const configPath = getProjectConfigPath();
if (!existsSync(configPath)) {
return {};
}
try {
const content = readFileSync(configPath, 'utf-8');
return JSON.parse(content);
} catch (error) {
console.warn('Failed to parse project config:', error);
return {};
}
}
export function saveProjectConfig(config: DSSConfig): void {
const configPath = getProjectConfigPath();
const dir = join(getProjectRoot(), '.dss');
// Ensure .dss directory exists
if (!existsSync(dir)) {
const { mkdirSync } = require('fs');
mkdirSync(dir, { recursive: true });
}
writeFileSync(configPath, JSON.stringify(config, null, 2));
}
export function getConfig(): DSSConfig {
const project = loadProjectConfig();
return {
figmaToken: project.figmaToken || globalConfig.get('figmaToken') as string | undefined,
figmaFileKey: project.figmaFileKey,
port: project.port || globalConfig.get('defaultPort') as number,
outputDir: project.outputDir || '.dss/output',
tokenFormat: project.tokenFormat || (globalConfig.get('defaultFormat') as DSSConfig['tokenFormat']),
componentFramework: project.componentFramework || 'react',
};
}
export function setGlobalConfig(key: string, value: string | number): void {
globalConfig.set(key, value);
}
export function getGlobalConfig(key: string): unknown {
return globalConfig.get(key);
}
export function listGlobalConfig(): Record<string, unknown> {
return globalConfig.store;
}

197
apps/cli/src/lib/server.ts Normal file
View File

@@ -0,0 +1,197 @@
/**
* DSS Server Manager
*
* Manages the Python server subprocess.
*/
import { spawn, ChildProcess } from 'child_process';
import { existsSync, writeFileSync, readFileSync, unlinkSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url));
interface ServerOptions {
port: number;
dev: boolean;
cwd: string;
}
let serverProcess: ChildProcess | null = null;
export function getPythonPath(): string {
// Check for bundled Python venv in npm package (dist/lib -> ../../python/venv)
const bundledPython = join(__dirname, '../../python/venv/bin/python3');
if (existsSync(bundledPython)) {
return bundledPython;
}
// Development path (src/lib -> ../../python/venv)
const devPython = join(__dirname, '../../../python/venv/bin/python3');
if (existsSync(devPython)) {
return devPython;
}
return 'python3';
}
export function getServerPath(): string {
// Check for bundled server or use from package
const bundledServer = join(__dirname, '../../python/api/server.py');
if (existsSync(bundledServer)) {
return dirname(bundledServer);
}
// Fall back to relative path from CLI (cli/dist/lib -> ../../../../tools/api)
const devServer = join(__dirname, '../../../../tools/api');
if (existsSync(join(devServer, 'server.py'))) {
return devServer;
}
// Also check development src path (cli/src/lib -> ../../../../tools/api)
const srcServer = join(__dirname, '../../../tools/api');
if (existsSync(join(srcServer, 'server.py'))) {
return srcServer;
}
throw new Error('Could not find DSS server. Run from the design-system-swarm directory or install the full package.');
}
export function getPidFile(cwd: string): string {
return join(cwd, '.dss', 'dss.pid');
}
export function getLogFile(cwd: string): string {
return join(cwd, '.dss', 'dss.log');
}
export function isServerRunning(cwd: string): boolean {
const pidFile = getPidFile(cwd);
if (!existsSync(pidFile)) {
return false;
}
try {
const pid = parseInt(readFileSync(pidFile, 'utf-8').trim(), 10);
// Check if process exists
process.kill(pid, 0);
return true;
} catch {
// Process doesn't exist, clean up stale PID file
try {
unlinkSync(pidFile);
} catch {
// Ignore
}
return false;
}
}
export function getServerPid(cwd: string): number | null {
const pidFile = getPidFile(cwd);
if (!existsSync(pidFile)) {
return null;
}
try {
return parseInt(readFileSync(pidFile, 'utf-8').trim(), 10);
} catch {
return null;
}
}
export async function startServer(options: ServerOptions): Promise<ChildProcess> {
const { port, dev, cwd } = options;
if (isServerRunning(cwd)) {
throw new Error(`Server is already running (PID: ${getServerPid(cwd)})`);
}
const pythonPath = getPythonPath();
const serverDir = getServerPath();
// Build command args
const args = [
'-m', 'uvicorn',
'server:app',
'--host', '0.0.0.0',
'--port', String(port),
];
if (dev) {
args.push('--reload');
}
// Set environment
const env = {
...process.env,
PYTHONPATH: join(serverDir, '..'),
PORT: String(port),
};
// Spawn server process
serverProcess = spawn(pythonPath, args, {
cwd: serverDir,
env,
stdio: dev ? 'inherit' : ['ignore', 'pipe', 'pipe'],
detached: !dev,
});
if (!dev && serverProcess.pid) {
// Write PID file
const dssDir = join(cwd, '.dss');
if (!existsSync(dssDir)) {
const { mkdirSync } = await import('fs');
mkdirSync(dssDir, { recursive: true });
}
writeFileSync(getPidFile(cwd), String(serverProcess.pid));
// Write logs
const logStream = await import('fs').then(fs =>
fs.createWriteStream(getLogFile(cwd), { flags: 'a' })
);
serverProcess.stdout?.pipe(logStream);
serverProcess.stderr?.pipe(logStream);
// Detach from parent
serverProcess.unref();
}
return serverProcess;
}
export async function stopServer(cwd: string): Promise<boolean> {
const pid = getServerPid(cwd);
if (!pid) {
return false;
}
try {
process.kill(pid, 'SIGTERM');
unlinkSync(getPidFile(cwd));
return true;
} catch {
return false;
}
}
export async function waitForServer(port: number, timeout = 10000): Promise<boolean> {
const start = Date.now();
const url = `http://localhost:${port}/health`;
while (Date.now() - start < timeout) {
try {
const response = await fetch(url);
if (response.ok) {
return true;
}
} catch {
// Server not ready yet
}
await new Promise(resolve => setTimeout(resolve, 200));
}
return false;
}

20
apps/cli/tsconfig.json Normal file
View File

@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"lib": ["ES2022"],
"outDir": "dist",
"rootDir": "src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}