diff --git a/esbuild.mjs b/esbuild.mjs index fe87920..05eaeea 100644 --- a/esbuild.mjs +++ b/esbuild.mjs @@ -40,7 +40,7 @@ const ctx = await esbuild.context({ outExtension: { '.js': '.cjs', }, - loader: { '.ts': 'ts' }, + loader: { '.ts': 'ts', '.tsx': 'tsx' }, external: ['vscode'], platform: 'node', sourcemap: !minify, diff --git a/package.json b/package.json index d4d7a19..aeb4774 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,6 @@ "@redocly/cli": "^2.15.0", "agentlang": "^0.10.2", "better-sqlite3": "^12.6.2", - "chalk": "^5.6.2", "chokidar": "^5.0.0", "commander": "^14.0.2", "cors": "^2.8.6", @@ -66,6 +65,7 @@ "esbuild": "^0.27.2", "express": "^5.2.1", "fs-extra": "^11.3.3", + "ink": "^6.8.0", "langium": "^4.1.3", "mammoth": "^1.11.0", "multer": "^2.0.2", @@ -75,6 +75,7 @@ "openapi-to-postmanv2": "^5.8.0", "ora": "^9.1.0", "pdf-parse": "^2.4.5", + "react": "^19.2.4", "simple-git": "^3.30.0", "sqlite-vec": "0.1.7-alpha.2", "tmp": "^0.2.5", @@ -91,6 +92,7 @@ "@types/multer": "^2.0.0", "@types/node": "^25.0.10", "@types/openapi-to-postmanv2": "^5.0.0", + "@types/react": "^19.2.14", "@types/tmp": "^0.2.6", "eslint": "^9.39.2", "prettier": "^3.8.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f51357a..13e80f4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -28,13 +28,10 @@ importers: version: 2.16.0(@opentelemetry/api@1.9.0)(core-js@3.48.0) agentlang: specifier: ^0.10.2 - version: 0.10.2(@cfworker/json-schema@4.1.1)(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(axios@1.13.4)(js-yaml@4.1.1)(openai@6.18.0(zod@4.3.6))(ts-node@10.9.2(@types/node@25.2.1)(typescript@5.9.3)) + version: 0.10.2(@cfworker/json-schema@4.1.1)(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(axios@1.13.4)(js-yaml@4.1.1)(openai@6.18.0(ws@8.19.0)(zod@4.3.6))(ts-node@10.9.2(@types/node@25.2.1)(typescript@5.9.3))(ws@8.19.0) better-sqlite3: specifier: ^12.6.2 version: 12.6.2 - chalk: - specifier: ^5.6.2 - version: 5.6.2 chokidar: specifier: ^5.0.0 version: 5.0.0 @@ -56,6 +53,9 @@ importers: fs-extra: specifier: ^11.3.3 version: 11.3.3 + ink: + specifier: ^6.8.0 + version: 6.8.0(@types/react@19.2.14)(react@19.2.4) langium: specifier: ^4.1.3 version: 4.2.0 @@ -70,7 +70,7 @@ importers: version: 11.0.0 openai: specifier: ^6.16.0 - version: 6.18.0(zod@4.3.6) + version: 6.18.0(ws@8.19.0)(zod@4.3.6) openapi-client-axios: specifier: ^7.8.0 version: 7.8.0(axios@1.13.4)(js-yaml@4.1.1) @@ -83,6 +83,9 @@ importers: pdf-parse: specifier: ^2.4.5 version: 2.4.5 + react: + specifier: ^19.2.4 + version: 19.2.4 simple-git: specifier: ^3.30.0 version: 3.30.0 @@ -126,6 +129,9 @@ importers: '@types/openapi-to-postmanv2': specifier: ^5.0.0 version: 5.0.0 + '@types/react': + specifier: ^19.2.14 + version: 19.2.14 '@types/tmp': specifier: ^0.2.6 version: 0.2.6 @@ -156,6 +162,10 @@ packages: '@agentlang/lstudio@0.6.0': resolution: {integrity: sha512-lmvWPtGpkQ1ORBdjTTD27Nst8RNeh3h2wNOHcW04HWEQRcm5P9hIxxm0pLQANfE2BMmzKynP81n2aOIgJ5gNQw==} + '@alcalzone/ansi-tokenize@0.2.5': + resolution: {integrity: sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw==} + engines: {node: '>=18'} + '@anthropic-ai/claude-agent-sdk@0.2.34': resolution: {integrity: sha512-QLHd3Nt7bGU7/YH71fXFaztM9fNxGGruzTMrTYJkbm5gYJl5ZyU2zGyoE5VpWC0e1QU0yYdNdBVgqSYDcJGufg==} engines: {node: '>=18.0.0'} @@ -1328,6 +1338,9 @@ packages: '@types/range-parser@1.2.7': resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + '@types/react@19.2.14': + resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + '@types/send@1.2.1': resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} @@ -1480,6 +1493,10 @@ packages: amazon-cognito-identity-js@6.3.16: resolution: {integrity: sha512-HPGSBGD6Q36t99puWh0LnptxO/4icnk2kqIQ9cTJ2tFQo5NMUnWQIgtrTAk8nm+caqUbjDzXzG56GBjI2tS6jQ==} + ansi-escapes@7.3.0: + resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} + engines: {node: '>=18'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -1526,6 +1543,10 @@ packages: asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + auto-bind@5.0.1: + resolution: {integrity: sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + available-typed-arrays@1.0.7: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} @@ -1666,6 +1687,14 @@ packages: classnames@2.5.1: resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} + cli-boxes@3.0.0: + resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} + engines: {node: '>=10'} + + cli-cursor@4.0.0: + resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + cli-cursor@5.0.0: resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} engines: {node: '>=18'} @@ -1674,6 +1703,10 @@ packages: resolution: {integrity: sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==} engines: {node: '>=18.20'} + cli-truncate@5.1.1: + resolution: {integrity: sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==} + engines: {node: '>=20'} + cliui@7.0.4: resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} @@ -1685,6 +1718,10 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} + code-excerpt@4.0.0: + resolution: {integrity: sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + codepage@1.15.0: resolution: {integrity: sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==} engines: {node: '>=0.8'} @@ -1753,6 +1790,10 @@ packages: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} + convert-to-spaces@2.0.1: + resolution: {integrity: sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + cookie-signature@1.2.2: resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} engines: {node: '>=6.6.0'} @@ -1908,6 +1949,9 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1924,6 +1968,10 @@ packages: end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} + es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -1940,6 +1988,9 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} + es-toolkit@1.44.0: + resolution: {integrity: sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg==} + es6-promise@3.3.1: resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==} @@ -1955,6 +2006,10 @@ packages: escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -2284,12 +2339,29 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + indent-string@5.0.0: + resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} + engines: {node: '>=12'} + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + ink@6.8.0: + resolution: {integrity: sha512-sbl1RdLOgkO9isK42WCZlJCFN9hb++sX9dsklOvfd1YQ3bQ2AiFu12Q6tFlr0HvEUvzraJntQCCpfEoUe9DSzA==} + engines: {node: '>=20'} + peerDependencies: + '@types/react': '>=19.0.0' + react: '>=19.0.0' + react-devtools-core: '>=6.1.2' + peerDependenciesMeta: + '@types/react': + optional: true + react-devtools-core: + optional: true + ip-address@10.0.1: resolution: {integrity: sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==} engines: {node: '>= 12'} @@ -2315,10 +2387,19 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + is-fullwidth-code-point@5.1.0: + resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} + engines: {node: '>=18'} + is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-in-ci@2.0.0: + resolution: {integrity: sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w==} + engines: {node: '>=20'} + hasBin: true + is-in-ssh@1.0.0: resolution: {integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==} engines: {node: '>=20'} @@ -2597,6 +2678,10 @@ packages: engines: {node: '>=10.0.0'} hasBin: true + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + mimic-function@5.0.1: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} @@ -2761,6 +2846,10 @@ packages: one-time@1.0.0: resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==} + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + onetime@7.0.0: resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} engines: {node: '>=18'} @@ -2849,6 +2938,10 @@ packages: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} + patch-console@2.0.0: + resolution: {integrity: sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} @@ -3055,6 +3148,12 @@ packages: react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-reconciler@0.33.0: + resolution: {integrity: sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA==} + engines: {node: '>=0.10.0'} + peerDependencies: + react: ^19.2.0 + react-tabs@6.1.0: resolution: {integrity: sha512-6QtbTRDKM+jA/MZTTefvigNxo0zz+gnBTVFw2CFVvq+f2BuH0nF0vDLNClL045nuTAdOoK/IL1vTP0ZLX0DAyQ==} peerDependencies: @@ -3106,6 +3205,10 @@ packages: resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + restore-cursor@4.0.0: + resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + restore-cursor@5.1.0: resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} engines: {node: '>=18'} @@ -3215,6 +3318,9 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} @@ -3234,6 +3340,14 @@ packages: simple-websocket@9.1.0: resolution: {integrity: sha512-8MJPnjRN6A8UCp1I+H/dSFyjwJhp6wta4hsVRhjf8w9qBHRzxYt14RaOcjvQnhD1N4yKOddEjflwMnQM4VtXjQ==} + slice-ansi@7.1.2: + resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} + engines: {node: '>=18'} + + slice-ansi@8.0.0: + resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==} + engines: {node: '>=20'} + slugify@1.4.7: resolution: {integrity: sha512-tf+h5W1IrjNm/9rKKj0JU2MDMruiopx0jjVA5zCdBtcGjfp0+c5rHw/zADLC3IeKlGHtVbHtpfzvYA0OYT+HKg==} engines: {node: '>=8.0.0'} @@ -3295,6 +3409,10 @@ packages: stack-trace@0.0.10: resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + statuses@2.0.2: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} @@ -3318,6 +3436,10 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + string-width@8.1.1: resolution: {integrity: sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==} engines: {node: '>=20'} @@ -3371,6 +3493,10 @@ packages: resolution: {integrity: sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g==} hasBin: true + tagged-tag@1.0.0: + resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} + engines: {node: '>=20'} + tar-fs@2.1.4: resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} @@ -3378,6 +3504,10 @@ packages: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} + terminal-size@4.0.1: + resolution: {integrity: sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ==} + engines: {node: '>=18'} + text-hex@1.0.0: resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} @@ -3448,6 +3578,10 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} + type-fest@5.4.4: + resolution: {integrity: sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw==} + engines: {node: '>=20'} + type-is@1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} @@ -3647,6 +3781,10 @@ packages: engines: {node: '>= 8'} hasBin: true + widest-line@6.0.0: + resolution: {integrity: sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA==} + engines: {node: '>=20'} + winston-daily-rotate-file@5.0.0: resolution: {integrity: sha512-JDjiXXkM5qvwY06733vf09I2wnMXpZEhxEVOSPenZMii+g7pcDcTBt2MRugnoi8BwVSuCT2jfRXBUy+n1Zz/Yw==} engines: {node: '>=8'} @@ -3684,6 +3822,10 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -3699,6 +3841,18 @@ packages: utf-8-validate: optional: true + ws@8.19.0: + resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + wsl-utils@0.3.1: resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==} engines: {node: '>=20'} @@ -3760,6 +3914,9 @@ packages: resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} engines: {node: '>=18'} + yoga-layout@3.2.1: + resolution: {integrity: sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==} + zod-to-json-schema@3.25.1: resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==} peerDependencies: @@ -3774,6 +3931,11 @@ snapshots: '@agentlang/lstudio@0.6.0': {} + '@alcalzone/ansi-tokenize@0.2.5': + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 + '@anthropic-ai/claude-agent-sdk@0.2.34(zod@4.3.6)': dependencies: zod: 4.3.6 @@ -4837,20 +4999,20 @@ snapshots: '@kwsites/promise-deferred@1.1.1': {} - '@langchain/anthropic@1.3.15(@langchain/core@1.1.19(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(openai@6.18.0(zod@4.3.6)))': + '@langchain/anthropic@1.3.15(@langchain/core@1.1.19(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(openai@6.18.0(ws@8.19.0)(zod@4.3.6)))': dependencies: '@anthropic-ai/sdk': 0.73.0(zod@4.3.6) - '@langchain/core': 1.1.19(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(openai@6.18.0(zod@4.3.6)) + '@langchain/core': 1.1.19(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(openai@6.18.0(ws@8.19.0)(zod@4.3.6)) zod: 4.3.6 - '@langchain/core@1.1.19(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(openai@6.18.0(zod@4.3.6))': + '@langchain/core@1.1.19(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(openai@6.18.0(ws@8.19.0)(zod@4.3.6))': dependencies: '@cfworker/json-schema': 4.1.1 ansi-styles: 5.2.0 camelcase: 6.3.0 decamelize: 1.2.0 js-tiktoken: 1.0.21 - langsmith: 0.4.12(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(openai@6.18.0(zod@4.3.6)) + langsmith: 0.4.12(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(openai@6.18.0(ws@8.19.0)(zod@4.3.6)) mustache: 4.2.0 p-queue: 6.6.2 uuid: 10.0.0 @@ -4861,11 +5023,11 @@ snapshots: - '@opentelemetry/sdk-trace-base' - openai - '@langchain/openai@1.2.5(@langchain/core@1.1.19(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(openai@6.18.0(zod@4.3.6)))': + '@langchain/openai@1.2.5(@langchain/core@1.1.19(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(openai@6.18.0(ws@8.19.0)(zod@4.3.6)))(ws@8.19.0)': dependencies: - '@langchain/core': 1.1.19(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(openai@6.18.0(zod@4.3.6)) + '@langchain/core': 1.1.19(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(openai@6.18.0(ws@8.19.0)(zod@4.3.6)) js-tiktoken: 1.0.21 - openai: 6.18.0(zod@4.3.6) + openai: 6.18.0(ws@8.19.0)(zod@4.3.6) zod: 4.3.6 transitivePeerDependencies: - ws @@ -5567,6 +5729,10 @@ snapshots: '@types/range-parser@1.2.7': {} + '@types/react@19.2.14': + dependencies: + csstype: 3.2.3 + '@types/send@1.2.1': dependencies: '@types/node': 25.2.1 @@ -5703,16 +5869,16 @@ snapshots: agent-base@7.1.4: {} - agentlang@0.10.2(@cfworker/json-schema@4.1.1)(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(axios@1.13.4)(js-yaml@4.1.1)(openai@6.18.0(zod@4.3.6))(ts-node@10.9.2(@types/node@25.2.1)(typescript@5.9.3)): + agentlang@0.10.2(@cfworker/json-schema@4.1.1)(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(axios@1.13.4)(js-yaml@4.1.1)(openai@6.18.0(ws@8.19.0)(zod@4.3.6))(ts-node@10.9.2(@types/node@25.2.1)(typescript@5.9.3))(ws@8.19.0): dependencies: '@aws-sdk/client-cognito-identity': 3.984.0 '@aws-sdk/client-cognito-identity-provider': 3.984.0 '@aws-sdk/client-s3': 3.986.0 '@aws-sdk/credential-providers': 3.984.0 '@isomorphic-git/lightning-fs': 4.6.2 - '@langchain/anthropic': 1.3.15(@langchain/core@1.1.19(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(openai@6.18.0(zod@4.3.6))) - '@langchain/core': 1.1.19(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(openai@6.18.0(zod@4.3.6)) - '@langchain/openai': 1.2.5(@langchain/core@1.1.19(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(openai@6.18.0(zod@4.3.6))) + '@langchain/anthropic': 1.3.15(@langchain/core@1.1.19(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(openai@6.18.0(ws@8.19.0)(zod@4.3.6))) + '@langchain/core': 1.1.19(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(openai@6.18.0(ws@8.19.0)(zod@4.3.6)) + '@langchain/openai': 1.2.5(@langchain/core@1.1.19(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(openai@6.18.0(ws@8.19.0)(zod@4.3.6)))(ws@8.19.0) '@modelcontextprotocol/sdk': 1.26.0(@cfworker/json-schema@4.1.1)(zod@4.3.6) '@types/multer': 2.0.0 amazon-cognito-identity-js: 6.3.16 @@ -5814,6 +5980,10 @@ snapshots: transitivePeerDependencies: - encoding + ansi-escapes@7.3.0: + dependencies: + environment: 1.1.0 + ansi-regex@5.0.1: {} ansi-regex@6.2.2: {} @@ -5844,6 +6014,8 @@ snapshots: asynckit@0.4.0: {} + auto-bind@5.0.1: {} + available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.1.0 @@ -6006,12 +6178,23 @@ snapshots: classnames@2.5.1: {} + cli-boxes@3.0.0: {} + + cli-cursor@4.0.0: + dependencies: + restore-cursor: 4.0.0 + cli-cursor@5.0.0: dependencies: restore-cursor: 5.1.0 cli-spinners@3.4.0: {} + cli-truncate@5.1.1: + dependencies: + slice-ansi: 7.1.2 + string-width: 8.1.1 + cliui@7.0.4: dependencies: string-width: 4.2.3 @@ -6026,6 +6209,10 @@ snapshots: clsx@2.1.1: {} + code-excerpt@4.0.0: + dependencies: + convert-to-spaces: 2.0.1 + codepage@1.15.0: {} color-convert@2.0.1: @@ -6091,6 +6278,8 @@ snapshots: content-type@1.0.5: {} + convert-to-spaces@2.0.1: {} + cookie-signature@1.2.2: {} cookie@0.7.2: {} @@ -6203,6 +6392,8 @@ snapshots: ee-first@1.1.1: {} + emoji-regex@10.6.0: {} + emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} @@ -6215,6 +6406,8 @@ snapshots: dependencies: once: 1.4.0 + environment@1.1.0: {} + es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -6230,6 +6423,8 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 + es-toolkit@1.44.0: {} + es6-promise@3.3.1: {} esbuild@0.27.3: @@ -6265,6 +6460,8 @@ snapshots: escape-html@1.0.3: {} + escape-string-regexp@2.0.0: {} + escape-string-regexp@4.0.0: {} eslint-scope@8.4.0: @@ -6621,10 +6818,46 @@ snapshots: imurmurhash@0.1.4: {} + indent-string@5.0.0: {} + inherits@2.0.4: {} ini@1.3.8: {} + ink@6.8.0(@types/react@19.2.14)(react@19.2.4): + dependencies: + '@alcalzone/ansi-tokenize': 0.2.5 + ansi-escapes: 7.3.0 + ansi-styles: 6.2.3 + auto-bind: 5.0.1 + chalk: 5.6.2 + cli-boxes: 3.0.0 + cli-cursor: 4.0.0 + cli-truncate: 5.1.1 + code-excerpt: 4.0.0 + es-toolkit: 1.44.0 + indent-string: 5.0.0 + is-in-ci: 2.0.0 + patch-console: 2.0.0 + react: 19.2.4 + react-reconciler: 0.33.0(react@19.2.4) + scheduler: 0.27.0 + signal-exit: 3.0.7 + slice-ansi: 8.0.0 + stack-utils: 2.0.6 + string-width: 8.1.1 + terminal-size: 4.0.1 + type-fest: 5.4.4 + widest-line: 6.0.0 + wrap-ansi: 9.0.2 + ws: 8.19.0 + yoga-layout: 3.2.1 + optionalDependencies: + '@types/react': 19.2.14 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + ip-address@10.0.1: {} ipaddr.js@1.9.1: {} @@ -6637,10 +6870,16 @@ snapshots: is-fullwidth-code-point@3.0.0: {} + is-fullwidth-code-point@5.1.0: + dependencies: + get-east-asian-width: 1.4.0 + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 + is-in-ci@2.0.0: {} + is-in-ssh@1.0.0: {} is-inside-container@1.0.0: @@ -6780,7 +7019,7 @@ snapshots: vscode-languageserver-textdocument: 1.0.12 vscode-uri: 3.1.0 - langsmith@0.4.12(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(openai@6.18.0(zod@4.3.6)): + langsmith@0.4.12(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(openai@6.18.0(ws@8.19.0)(zod@4.3.6)): dependencies: '@types/uuid': 10.0.0 chalk: 4.1.2 @@ -6791,7 +7030,7 @@ snapshots: optionalDependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/sdk-trace-base': 2.0.1(@opentelemetry/api@1.9.0) - openai: 6.18.0(zod@4.3.6) + openai: 6.18.0(ws@8.19.0)(zod@4.3.6) leven@3.1.0: {} @@ -6897,6 +7136,8 @@ snapshots: mime@3.0.0: {} + mimic-fn@2.1.0: {} + mimic-function@5.0.1: {} mimic-response@3.1.0: {} @@ -7047,6 +7288,10 @@ snapshots: dependencies: fn.name: 1.1.0 + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + onetime@7.0.0: dependencies: mimic-function: 5.0.1 @@ -7060,8 +7305,9 @@ snapshots: powershell-utils: 0.1.0 wsl-utils: 0.3.1 - openai@6.18.0(zod@4.3.6): + openai@6.18.0(ws@8.19.0)(zod@4.3.6): optionalDependencies: + ws: 8.19.0 zod: 4.3.6 openapi-client-axios@7.8.0(axios@1.13.4)(js-yaml@4.1.1): @@ -7159,6 +7405,8 @@ snapshots: parseurl@1.3.3: {} + patch-console@2.0.0: {} + path-browserify@1.0.1: {} path-exists@4.0.0: {} @@ -7370,6 +7618,11 @@ snapshots: react-is@16.13.1: {} + react-reconciler@0.33.0(react@19.2.4): + dependencies: + react: 19.2.4 + scheduler: 0.27.0 + react-tabs@6.1.0(react@19.2.4): dependencies: clsx: 2.1.1 @@ -7441,6 +7694,11 @@ snapshots: resolve-pkg-maps@1.0.0: {} + restore-cursor@4.0.0: + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + restore-cursor@5.1.0: dependencies: onetime: 7.0.0 @@ -7580,6 +7838,8 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + signal-exit@3.0.7: {} + signal-exit@4.1.0: {} simple-concat@1.0.1: {} @@ -7612,6 +7872,16 @@ snapshots: - supports-color - utf-8-validate + slice-ansi@7.1.2: + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 + + slice-ansi@8.0.0: + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 + slugify@1.4.7: {} source-map-js@1.2.1: {} @@ -7655,6 +7925,10 @@ snapshots: stack-trace@0.0.10: {} + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + statuses@2.0.2: {} stdin-discarder@0.3.1: {} @@ -7675,6 +7949,12 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.2 + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.4.0 + strip-ansi: 7.1.2 + string-width@8.1.1: dependencies: get-east-asian-width: 1.4.0 @@ -7741,6 +8021,8 @@ snapshots: transitivePeerDependencies: - encoding + tagged-tag@1.0.0: {} + tar-fs@2.1.4: dependencies: chownr: 1.1.4 @@ -7756,6 +8038,8 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 + terminal-size@4.0.1: {} + text-hex@1.0.0: {} tinyglobby@0.2.15: @@ -7822,6 +8106,10 @@ snapshots: dependencies: prelude-ls: 1.2.1 + type-fest@5.4.4: + dependencies: + tagged-tag: 1.0.0 + type-is@1.6.18: dependencies: media-typer: 0.3.0 @@ -7974,6 +8262,10 @@ snapshots: dependencies: isexe: 2.0.0 + widest-line@6.0.0: + dependencies: + string-width: 8.1.1 + winston-daily-rotate-file@5.0.0(winston@3.19.0): dependencies: file-stream-rotator: 0.6.1 @@ -8022,10 +8314,18 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.1.2 + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.1.2 + wrappy@1.0.2: {} ws@7.5.10: {} + ws@8.19.0: {} + wsl-utils@0.3.1: dependencies: is-wsl: 3.1.0 @@ -8083,6 +8383,8 @@ snapshots: yoctocolors@2.1.2: {} + yoga-layout@3.2.1: {} + zod-to-json-schema@3.25.1(zod@4.3.6): dependencies: zod: 4.3.6 diff --git a/src/main.ts b/src/main.ts index fb860c5..1bab78a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,4 +1,3 @@ -import chalk from 'chalk'; import { Command } from 'commander'; import { NodeFileSystem } from 'langium/node'; import * as path from 'node:path'; @@ -9,6 +8,10 @@ import { initializeProject } from './utils/projectInitializer.js'; import { existsSync, readFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname, join } from 'node:path'; +import { renderToString } from 'ink'; +import React from 'react'; +import Help from './ui/components/Help.js'; +import { ui, ansi } from './ui/index.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); @@ -80,8 +83,7 @@ function getDefaultRepoUrl(appName: string): string { async function promptAndPushRepository(git: SimpleGit, appName: string): Promise { if (!process.stdin.isTTY || !process.stdout.isTTY) { - // eslint-disable-next-line no-console - console.log(chalk.dim('Skipping git push prompt (non-interactive terminal).')); + ui.dim('Skipping git push prompt (non-interactive terminal).'); return; } @@ -91,7 +93,7 @@ async function promptAndPushRepository(git: SimpleGit, appName: string): Promise }); try { - const pushAnswer = (await rl.question(chalk.cyan('Would you like to push this repo now? (y/N) '))) + const pushAnswer = (await rl.question(ansi.cyan('Would you like to push this repo now? (y/N) '))) .trim() .toLowerCase(); @@ -100,7 +102,7 @@ async function promptAndPushRepository(git: SimpleGit, appName: string): Promise } const defaultRepoUrl = getDefaultRepoUrl(appName); - const repoUrlInputPromise = rl.question(chalk.cyan('Repository URL: ')); + const repoUrlInputPromise = rl.question(ansi.cyan('Repository URL: ')); rl.write(defaultRepoUrl); const repoUrlInput = await repoUrlInputPromise; @@ -116,13 +118,9 @@ async function promptAndPushRepository(git: SimpleGit, appName: string): Promise const currentBranch = (await git.branch()).current || 'main'; await git.push(['-u', 'origin', currentBranch]); - // eslint-disable-next-line no-console - console.log(`${chalk.green('āœ“')} Pushed to ${chalk.cyan(repoUrl)}`); + ui.step('āœ“', 'Pushed to', repoUrl); } catch (error) { - // eslint-disable-next-line no-console - console.log( - chalk.yellow(`āš ļø Skipped pushing repository: ${error instanceof Error ? error.message : String(error)}`), - ); + ui.warn(`Skipped pushing repository: ${error instanceof Error ? error.message : String(error)}`); } finally { rl.close(); } @@ -133,35 +131,44 @@ export const initCommand = async (appName: string, options?: { prompt?: string } const currentDir = process.cwd(); const targetDir = join(currentDir, appName); + ui.blank(); + ui.banner('Initialize App'); + ui.blank(); + ui.label('App', appName, 'cyan'); + ui.label('Location', targetDir); + ui.blank(); + try { await initializeProject(targetDir, appName, { prompt: options?.prompt, - silent: false, // Maintain logs for CLI + silent: false, }); - // Change to the app directory (for CLI context) try { process.chdir(targetDir); - // eslint-disable-next-line no-console - console.log(chalk.cyan(`\nšŸ“‚ Changed directory to ${chalk.bold(appName)}`)); } catch { // Ignore if can't change directory } - // eslint-disable-next-line no-console - console.log(chalk.green('\n✨ Successfully initialized Agentlang application!')); - // eslint-disable-next-line no-console - console.log(chalk.dim('\nNext steps:')); - // eslint-disable-next-line no-console - console.log(chalk.dim(' 1. Add your application logic to src/core.al')); - // eslint-disable-next-line no-console - console.log(chalk.dim(' 2. Run your app with: ') + chalk.cyan('agent run')); - // eslint-disable-next-line no-console - console.log(chalk.dim(' 3. Or start Studio UI with: ') + chalk.cyan('agent studio')); + ui.blank(); + ui.divider(50); + ui.success(`${appName} initialized successfully!`); + ui.blank(); + ui.dim('Next steps:'); + ui.dim(' 1. Add your application logic to src/core.al'); + ui.row([ + { text: ' 2. Run your app with: ', dimColor: true }, + { text: 'agent run', color: 'cyan' }, + ]); + ui.row([ + { text: ' 3. Or start Studio UI with: ', dimColor: true }, + { text: 'agent studio', color: 'cyan' }, + ]); + ui.divider(50); + ui.blank(); // Handle interactive git push const git = simpleGit(targetDir); - // Check if git is initialized (initializeProject does it, but let's be safe) if (await git.checkIsRepo()) { await promptAndPushRepository(git, appName); } @@ -170,128 +177,40 @@ export const initCommand = async (appName: string, options?: { prompt?: string } process.exit(0); } } catch (error) { - // eslint-disable-next-line no-console - console.error(chalk.red('āŒ Error initializing application:'), error instanceof Error ? error.message : error); + ui.error(`Error initializing application: ${error instanceof Error ? error.message : String(error)}`); process.exit(1); } }; -// Custom help formatter -function customHelp(): string { - const gradient = [chalk.hex('#00D9FF'), chalk.hex('#00C4E6'), chalk.hex('#00AFCC'), chalk.hex('#009AB3')]; - - const header = ` - ${gradient[0]('ā–ˆā–ˆā–ˆā–ˆā–ˆā•—')} ${gradient[1](' ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•—')} ${gradient[2]('ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•—')}${gradient[3]('ā–ˆā–ˆā–ˆā•— ā–ˆā–ˆā•—')}${gradient[0]('ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•—')} - ${gradient[0]('ā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•—')}${gradient[1]('ā–ˆā–ˆā•”ā•ā•ā•ā•ā•')} ${gradient[2]('ā–ˆā–ˆā•”ā•ā•ā•ā•ā•')}${gradient[3]('ā–ˆā–ˆā–ˆā–ˆā•— ā–ˆā–ˆā•‘')}${gradient[0]('ā•šā•ā•ā–ˆā–ˆā•”ā•ā•ā•')} - ${gradient[0]('ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•‘')}${gradient[1]('ā–ˆā–ˆā•‘ ā–ˆā–ˆā–ˆā•—')}${gradient[2]('ā–ˆā–ˆā–ˆā–ˆā–ˆā•—')} ${gradient[3]('ā–ˆā–ˆā•”ā–ˆā–ˆā•— ā–ˆā–ˆā•‘')}${gradient[0](' ā–ˆā–ˆā•‘')} - ${gradient[0]('ā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•‘')}${gradient[1]('ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘')}${gradient[2]('ā–ˆā–ˆā•”ā•ā•ā•')} ${gradient[3]('ā–ˆā–ˆā•‘ā•šā–ˆā–ˆā•—ā–ˆā–ˆā•‘')}${gradient[0](' ā–ˆā–ˆā•‘')} - ${gradient[0]('ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘')}${gradient[1]('ā•šā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•”ā•')}${gradient[2]('ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•—')}${gradient[3]('ā–ˆā–ˆā•‘ ā•šā–ˆā–ˆā–ˆā–ˆā•‘')}${gradient[0](' ā–ˆā–ˆā•‘')} - ${gradient[0]('ā•šā•ā• ā•šā•ā•')} ${gradient[1]('ā•šā•ā•ā•ā•ā•ā•')} ${gradient[2]('ā•šā•ā•ā•ā•ā•ā•ā•')}${gradient[3]('ā•šā•ā• ā•šā•ā•ā•ā•')}${gradient[0](' ā•šā•ā•')} - - ${chalk.bold.white('Agentlang CLI')} ${chalk.dim(`v${packageVersion}`)} - ${chalk.dim('CLI for all things Agentlang')} -`; - - const usage = ` - ${chalk.bold.white('USAGE')} - ${chalk.dim('$')} ${chalk.cyan('agent')} ${chalk.yellow('')} ${chalk.dim('[options]')} - - ${chalk.bold.white('COMMANDS')} - - ${chalk.cyan.bold('init')} ${chalk.dim('')} - ${chalk.white('ā–ø')} Initialize a new Agentlang application - ${chalk.dim('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')} - ${chalk.yellow('OPTIONS')} - ${chalk.cyan('-p, --prompt')} ${chalk.dim('')} Description or prompt for the application - - ${chalk.cyan.bold('run')} ${chalk.dim('[file]')} - ${chalk.white('ā–ø')} Load and execute an Agentlang module - ${chalk.dim('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')} - ${chalk.yellow('OPTIONS')} - ${chalk.cyan('-c, --config')} ${chalk.dim('')} Configuration file path - - ${chalk.cyan.bold('repl')} ${chalk.dim('[directory]')} - ${chalk.white('ā–ø')} Start interactive REPL environment - ${chalk.dim('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')} - ${chalk.yellow('OPTIONS')} - ${chalk.cyan('-w, --watch')} Watch files and reload automatically - ${chalk.cyan('-q, --quiet')} Suppress startup messages - - ${chalk.cyan.bold('doc')} ${chalk.dim('[file]')} - ${chalk.white('ā–ø')} Generate API documentation (Swagger/OpenAPI) - ${chalk.dim('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')} - ${chalk.yellow('OPTIONS')} - ${chalk.cyan('-h, --outputHtml')} ${chalk.dim('')} Generate HTML documentation - ${chalk.cyan('-p, --outputPostman')} ${chalk.dim('')} Generate Postman collection - - ${chalk.cyan.bold('parseAndValidate')} ${chalk.dim('')} - ${chalk.white('ā–ø')} Parse and validate Agentlang source code - ${chalk.dim('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')} - ${chalk.yellow('OPTIONS')} - ${chalk.cyan('-d, --destination')} ${chalk.dim('')} Output directory - - ${chalk.cyan.bold('ui-gen')} ${chalk.dim('[spec-file]')} - ${chalk.white('ā–ø')} Generate UI from specification ${chalk.dim('(requires Anthropic API key)')} - ${chalk.dim('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')} - ${chalk.yellow('OPTIONS')} - ${chalk.cyan('-d, --directory')} ${chalk.dim('')} Target directory - ${chalk.cyan('-k, --api-key')} ${chalk.dim('')} Anthropic API key - ${chalk.cyan('-p, --push')} Commit and push to git - ${chalk.cyan('-m, --message')} ${chalk.dim('')} Update instructions - - ${chalk.cyan.bold('fork')} ${chalk.dim(' [name]')} - ${chalk.white('ā–ø')} Fork an app from a local directory or git repository - ${chalk.dim('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')} - ${chalk.yellow('OPTIONS')} - ${chalk.cyan('-b, --branch')} ${chalk.dim('')} Git branch to clone (for git URLs) - ${chalk.cyan('-u, --username')} ${chalk.dim('')} GitHub username for authenticated access - ${chalk.cyan('-t, --token')} ${chalk.dim('')} GitHub token for authenticated access - - ${chalk.cyan.bold('import')} ${chalk.dim(' [name]')} - ${chalk.white('ā–ø')} Import an app (alias for fork) - ${chalk.dim('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')} - ${chalk.yellow('OPTIONS')} - ${chalk.cyan('-b, --branch')} ${chalk.dim('')} Git branch to clone (for git URLs) - ${chalk.cyan('-u, --username')} ${chalk.dim('')} GitHub username for authenticated access - ${chalk.cyan('-t, --token')} ${chalk.dim('')} GitHub token for authenticated access - - ${chalk.cyan.bold('studio')} ${chalk.dim('[path]')} - ${chalk.white('ā–ø')} Start Agentlang Studio with local server - ${chalk.dim('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')} - ${chalk.yellow('OPTIONS')} - ${chalk.cyan('-p, --port')} ${chalk.dim('')} Port to run Studio server on (default: 4000) - - ${chalk.bold.white('GLOBAL OPTIONS')} - ${chalk.cyan('-h, --help')} Display help information - ${chalk.cyan('-V, --version')} Display version number - - ${chalk.bold.white('LEARN MORE')} - ${chalk.white('Docs')} ${chalk.cyan('https://github.com/agentlang/agentlang-cli')} - ${chalk.white('Issues')} ${chalk.cyan('https://github.com/agentlang/agentlang-cli/issues')} - - ${chalk.dim('Run')} ${chalk.cyan('agent --help')} ${chalk.dim('for detailed command information')} -`; - - return header + usage; -} - export default function (): void { const program = new Command(); // Configure program program .name('agent') - .description(chalk.gray('CLI for all things Agentlang')) + .description('CLI for all things Agentlang') .version(packageVersion, '-V, --version', 'Display version number') - .helpOption('-h, --help', 'Show help information') + .helpOption(false) .helpCommand(false) .configureHelp({ sortSubcommands: true, sortOptions: true, }); - // Override help display - program.helpInformation = customHelp; + // Use ink-rendered help via renderToString + program.helpInformation = () => { + return renderToString(React.createElement(Help, { version: packageVersion }), { + columns: process.stdout.columns || 80, + }); + }; + + // Add explicit help flag since we disabled the built-in one + program.option('-h, --help', 'Show help information'); + program.on('option:help', () => { + // eslint-disable-next-line no-console + console.log(program.helpInformation()); + process.exit(0); + }); const fileExtensions = AgentlangLanguageMetaData.fileExtensions.join(', '); @@ -303,7 +222,7 @@ export default function (): void { .addHelpText( 'after', ` -${chalk.bold.white('DESCRIPTION')} +${ui.format.boldWhite('DESCRIPTION')} Creates a new Agentlang application with the necessary project structure. This command will create: • package.json with your app name and version @@ -313,18 +232,18 @@ ${chalk.bold.white('DESCRIPTION')} The command checks if the directory is already initialized by looking for existing package.json or .al files (excluding config.al). -${chalk.bold.white('EXAMPLES')} - ${chalk.dim('Initialize a new app called CarDealership')} - ${chalk.dim('$')} ${chalk.cyan('agent init CarDealership')} +${ui.format.boldWhite('EXAMPLES')} + ${ui.format.dim('Initialize a new app called CarDealership')} + ${ui.format.dim('$')} ${ui.format.cyan('agent init CarDealership')} - ${chalk.dim('Initialize a new e-commerce app')} - ${chalk.dim('$')} ${chalk.cyan('agent init MyShop')} + ${ui.format.dim('Initialize a new e-commerce app')} + ${ui.format.dim('$')} ${ui.format.cyan('agent init MyShop')} - ${chalk.dim('Initialize with multiple words (use PascalCase)')} - ${chalk.dim('$')} ${chalk.cyan('agent init InventoryManagement')} + ${ui.format.dim('Initialize with multiple words (use PascalCase)')} + ${ui.format.dim('$')} ${ui.format.cyan('agent init InventoryManagement')} - ${chalk.dim('Initialize with a description/prompt')} - ${chalk.dim('$')} ${chalk.cyan('agent init ShowroomApp --prompt "a showroom app"')} + ${ui.format.dim('Initialize with a description/prompt')} + ${ui.format.dim('$')} ${ui.format.cyan('agent init ShowroomApp --prompt "a showroom app"')} `, ) .action(initCommand); @@ -337,22 +256,22 @@ ${chalk.bold.white('EXAMPLES')} .addHelpText( 'after', ` -${chalk.bold.white('DESCRIPTION')} +${ui.format.boldWhite('DESCRIPTION')} Loads and executes an Agentlang module, starting the runtime environment and initializing all configured services, databases, and integrations. -${chalk.bold.white('EXAMPLES')} - ${chalk.dim('Run module in current directory')} - ${chalk.dim('$')} ${chalk.cyan('agent run')} +${ui.format.boldWhite('EXAMPLES')} + ${ui.format.dim('Run module in current directory')} + ${ui.format.dim('$')} ${ui.format.cyan('agent run')} - ${chalk.dim('Run specific module file')} - ${chalk.dim('$')} ${chalk.cyan('agent run ./my-app/main.al')} + ${ui.format.dim('Run specific module file')} + ${ui.format.dim('$')} ${ui.format.cyan('agent run ./my-app/main.al')} - ${chalk.dim('Run with custom configuration')} - ${chalk.dim('$')} ${chalk.cyan('agent run ./my-app -c config.json')} + ${ui.format.dim('Run with custom configuration')} + ${ui.format.dim('$')} ${ui.format.cyan('agent run ./my-app -c config.json')} - ${chalk.dim('Run module from specific directory')} - ${chalk.dim('$')} ${chalk.cyan('agent run ~/projects/erp-system')} + ${ui.format.dim('Run module from specific directory')} + ${ui.format.dim('$')} ${ui.format.cyan('agent run ~/projects/erp-system')} `, ) .action(runModule); @@ -366,26 +285,26 @@ ${chalk.bold.white('EXAMPLES')} .addHelpText( 'after', ` -${chalk.bold.white('DESCRIPTION')} +${ui.format.boldWhite('DESCRIPTION')} Starts an interactive Read-Eval-Print Loop (REPL) environment for Agentlang, allowing you to execute code interactively, test functions, and explore your application in real-time. -${chalk.bold.white('EXAMPLES')} - ${chalk.dim('Start REPL in current directory')} - ${chalk.dim('$')} ${chalk.cyan('agent repl')} +${ui.format.boldWhite('EXAMPLES')} + ${ui.format.dim('Start REPL in current directory')} + ${ui.format.dim('$')} ${ui.format.cyan('agent repl')} - ${chalk.dim('Start REPL in specific directory')} - ${chalk.dim('$')} ${chalk.cyan('agent repl ./my-app')} + ${ui.format.dim('Start REPL in specific directory')} + ${ui.format.dim('$')} ${ui.format.cyan('agent repl ./my-app')} - ${chalk.dim('Start with file watching enabled')} - ${chalk.dim('$')} ${chalk.cyan('agent repl --watch')} + ${ui.format.dim('Start with file watching enabled')} + ${ui.format.dim('$')} ${ui.format.cyan('agent repl --watch')} - ${chalk.dim('Start in quiet mode (no startup messages)')} - ${chalk.dim('$')} ${chalk.cyan('agent repl --quiet')} + ${ui.format.dim('Start in quiet mode (no startup messages)')} + ${ui.format.dim('$')} ${ui.format.cyan('agent repl --quiet')} - ${chalk.dim('Combine options for development workflow')} - ${chalk.dim('$')} ${chalk.cyan('agent repl . --watch')} + ${ui.format.dim('Combine options for development workflow')} + ${ui.format.dim('$')} ${ui.format.cyan('agent repl . --watch')} `, ) .action(replCommand); @@ -399,26 +318,26 @@ ${chalk.bold.white('EXAMPLES')} .addHelpText( 'after', ` -${chalk.bold.white('DESCRIPTION')} +${ui.format.boldWhite('DESCRIPTION')} Generates comprehensive API documentation from your Agentlang module in Swagger/OpenAPI format. Supports both HTML and Postman collection output formats for easy API exploration and testing. -${chalk.bold.white('EXAMPLES')} - ${chalk.dim('Generate OpenAPI spec (outputs to console)')} - ${chalk.dim('$')} ${chalk.cyan('agent doc')} +${ui.format.boldWhite('EXAMPLES')} + ${ui.format.dim('Generate OpenAPI spec (outputs to console)')} + ${ui.format.dim('$')} ${ui.format.cyan('agent doc')} - ${chalk.dim('Generate HTML documentation')} - ${chalk.dim('$')} ${chalk.cyan('agent doc --outputHtml api-docs.html')} + ${ui.format.dim('Generate HTML documentation')} + ${ui.format.dim('$')} ${ui.format.cyan('agent doc --outputHtml api-docs.html')} - ${chalk.dim('Generate Postman collection')} - ${chalk.dim('$')} ${chalk.cyan('agent doc --outputPostman collection.json')} + ${ui.format.dim('Generate Postman collection')} + ${ui.format.dim('$')} ${ui.format.cyan('agent doc --outputPostman collection.json')} - ${chalk.dim('Generate both HTML and Postman')} - ${chalk.dim('$')} ${chalk.cyan('agent doc -h docs.html -p collection.json')} + ${ui.format.dim('Generate both HTML and Postman')} + ${ui.format.dim('$')} ${ui.format.cyan('agent doc -h docs.html -p collection.json')} - ${chalk.dim('Generate docs for specific module')} - ${chalk.dim('$')} ${chalk.cyan('agent doc ./my-api -h api.html')} + ${ui.format.dim('Generate docs for specific module')} + ${ui.format.dim('$')} ${ui.format.cyan('agent doc ./my-api -h api.html')} `, ) .action(generateDoc); @@ -431,20 +350,20 @@ ${chalk.bold.white('EXAMPLES')} .addHelpText( 'after', ` -${chalk.bold.white('DESCRIPTION')} +${ui.format.boldWhite('DESCRIPTION')} Parses and validates an Agentlang source file, checking for syntax errors, lexer issues, and semantic validation problems. Useful for CI/CD pipelines and pre-deployment validation. -${chalk.bold.white('EXAMPLES')} - ${chalk.dim('Validate a source file')} - ${chalk.dim('$')} ${chalk.cyan('agent parseAndValidate ./src/main.al')} +${ui.format.boldWhite('EXAMPLES')} + ${ui.format.dim('Validate a source file')} + ${ui.format.dim('$')} ${ui.format.cyan('agent parseAndValidate ./src/main.al')} - ${chalk.dim('Parse and validate with output directory')} - ${chalk.dim('$')} ${chalk.cyan('agent parseAndValidate main.al -d ./out')} + ${ui.format.dim('Parse and validate with output directory')} + ${ui.format.dim('$')} ${ui.format.cyan('agent parseAndValidate main.al -d ./out')} - ${chalk.dim('Validate in CI/CD pipeline')} - ${chalk.dim('$')} ${chalk.cyan('agent parseAndValidate app.al && npm run deploy')} + ${ui.format.dim('Validate in CI/CD pipeline')} + ${ui.format.dim('$')} ${ui.format.cyan('agent parseAndValidate app.al && npm run deploy')} `, ) .action(parseAndValidate); @@ -460,36 +379,36 @@ ${chalk.bold.white('EXAMPLES')} .addHelpText( 'after', ` -${chalk.bold.white('DESCRIPTION')} +${ui.format.boldWhite('DESCRIPTION')} Generates a complete UI application from a ui-spec.json specification using AI. Supports incremental updates, allowing you to evolve your UI over time with natural language instructions. -${chalk.yellow.bold('API KEY REQUIRED')} - Set ${chalk.cyan('ANTHROPIC_API_KEY')} environment variable or use ${chalk.cyan('--api-key')} flag - ${chalk.dim('Get your key at: https://console.anthropic.com')} +${ui.format.row([{ text: 'API KEY REQUIRED', color: 'yellow', bold: true }])} + Set ${ui.format.cyan('ANTHROPIC_API_KEY')} environment variable or use ${ui.format.cyan('--api-key')} flag + ${ui.format.dim('Get your key at: https://console.anthropic.com')} -${chalk.bold.white('EXAMPLES')} - ${chalk.dim('Generate UI with auto-detected spec')} - ${chalk.dim('$')} ${chalk.cyan('agent ui-gen')} +${ui.format.boldWhite('EXAMPLES')} + ${ui.format.dim('Generate UI with auto-detected spec')} + ${ui.format.dim('$')} ${ui.format.cyan('agent ui-gen')} - ${chalk.dim('Generate from specific spec file')} - ${chalk.dim('$')} ${chalk.cyan('agent ui-gen ui-spec.json')} + ${ui.format.dim('Generate from specific spec file')} + ${ui.format.dim('$')} ${ui.format.cyan('agent ui-gen ui-spec.json')} - ${chalk.dim('Generate and commit to git')} - ${chalk.dim('$')} ${chalk.cyan('agent ui-gen --push')} + ${ui.format.dim('Generate and commit to git')} + ${ui.format.dim('$')} ${ui.format.cyan('agent ui-gen --push')} - ${chalk.dim('Generate in specific directory')} - ${chalk.dim('$')} ${chalk.cyan('agent ui-gen -d ./frontend')} + ${ui.format.dim('Generate in specific directory')} + ${ui.format.dim('$')} ${ui.format.cyan('agent ui-gen -d ./frontend')} - ${chalk.dim('Update existing UI with changes')} - ${chalk.dim('$')} ${chalk.cyan('agent ui-gen -m "Add dark mode toggle"')} + ${ui.format.dim('Update existing UI with changes')} + ${ui.format.dim('$')} ${ui.format.cyan('agent ui-gen -m "Add dark mode toggle"')} - ${chalk.dim('Incremental update with git push')} - ${chalk.dim('$')} ${chalk.cyan('agent ui-gen -m "Fix login validation" -p')} + ${ui.format.dim('Incremental update with git push')} + ${ui.format.dim('$')} ${ui.format.cyan('agent ui-gen -m "Fix login validation" -p')} - ${chalk.dim('Use custom API key')} - ${chalk.dim('$')} ${chalk.cyan('agent ui-gen --api-key sk-ant-...')} + ${ui.format.dim('Use custom API key')} + ${ui.format.dim('$')} ${ui.format.cyan('agent ui-gen --api-key sk-ant-...')} `, ) .action(generateUICommand); @@ -505,26 +424,26 @@ ${chalk.bold.white('EXAMPLES')} .addHelpText( 'after', ` -${chalk.bold.white('DESCRIPTION')} +${ui.format.boldWhite('DESCRIPTION')} Forks an Agentlang application from a source path (local directory or git URL) into the current workspace. The forked app will be initialized with dependencies installed and a fresh git repository. -${chalk.bold.white('EXAMPLES')} - ${chalk.dim('Fork from local directory')} - ${chalk.dim('$')} ${chalk.cyan('agent fork ./my-app MyForkedApp')} +${ui.format.boldWhite('EXAMPLES')} + ${ui.format.dim('Fork from local directory')} + ${ui.format.dim('$')} ${ui.format.cyan('agent fork ./my-app MyForkedApp')} - ${chalk.dim('Fork from GitHub repository')} - ${chalk.dim('$')} ${chalk.cyan('agent fork https://github.com/user/repo.git MyApp')} + ${ui.format.dim('Fork from GitHub repository')} + ${ui.format.dim('$')} ${ui.format.cyan('agent fork https://github.com/user/repo.git MyApp')} - ${chalk.dim('Fork from GitHub with specific branch')} - ${chalk.dim('$')} ${chalk.cyan('agent fork https://github.com/user/repo.git MyApp --branch develop')} + ${ui.format.dim('Fork from GitHub with specific branch')} + ${ui.format.dim('$')} ${ui.format.cyan('agent fork https://github.com/user/repo.git MyApp --branch develop')} - ${chalk.dim('Fork private repository with authentication')} - ${chalk.dim('$')} ${chalk.cyan('agent fork https://github.com/user/repo.git MyApp -u username -t token')} + ${ui.format.dim('Fork private repository with authentication')} + ${ui.format.dim('$')} ${ui.format.cyan('agent fork https://github.com/user/repo.git MyApp -u username -t token')} - ${chalk.dim('Fork using git@ URL')} - ${chalk.dim('$')} ${chalk.cyan('agent fork git@github.com:user/repo.git MyApp')} + ${ui.format.dim('Fork using git@ URL')} + ${ui.format.dim('$')} ${ui.format.cyan('agent fork git@github.com:user/repo.git MyApp')} `, ) .action(forkCommand); @@ -540,16 +459,16 @@ ${chalk.bold.white('EXAMPLES')} .addHelpText( 'after', ` -${chalk.bold.white('DESCRIPTION')} +${ui.format.boldWhite('DESCRIPTION')} Imports an Agentlang application from a source path. This is an alias for the 'fork' command and uses the same functionality. -${chalk.bold.white('EXAMPLES')} - ${chalk.dim('Import from local directory')} - ${chalk.dim('$')} ${chalk.cyan('agent import ./my-app MyImportedApp')} +${ui.format.boldWhite('EXAMPLES')} + ${ui.format.dim('Import from local directory')} + ${ui.format.dim('$')} ${ui.format.cyan('agent import ./my-app MyImportedApp')} - ${chalk.dim('Import from GitHub repository')} - ${chalk.dim('$')} ${chalk.cyan('agent import https://github.com/user/repo.git MyApp')} + ${ui.format.dim('Import from GitHub repository')} + ${ui.format.dim('$')} ${ui.format.cyan('agent import https://github.com/user/repo.git MyApp')} `, ) .action(forkCommand); @@ -563,7 +482,7 @@ ${chalk.bold.white('EXAMPLES')} .addHelpText( 'after', ` -${chalk.bold.white('DESCRIPTION')} +${ui.format.boldWhite('DESCRIPTION')} Starts the Agentlang Design Studio locally for your project. This command: • Starts the Agentlang server (via 'agent run') • Serves the Studio UI on a local web server @@ -572,21 +491,21 @@ ${chalk.bold.white('DESCRIPTION')} The Studio UI allows you to visually edit Agents, Data Models, and Workflows, with changes saved directly to your project files (.al files, package.json, etc.). -${chalk.bold.white('EXAMPLES')} - ${chalk.dim('Start Studio in current directory')} - ${chalk.dim('$')} ${chalk.cyan('agent studio')} +${ui.format.boldWhite('EXAMPLES')} + ${ui.format.dim('Start Studio in current directory')} + ${ui.format.dim('$')} ${ui.format.cyan('agent studio')} - ${chalk.dim('Start Studio for specific project')} - ${chalk.dim('$')} ${chalk.cyan('agent studio ./my-project')} + ${ui.format.dim('Start Studio for specific project')} + ${ui.format.dim('$')} ${ui.format.cyan('agent studio ./my-project')} - ${chalk.dim('Start Studio on custom port')} - ${chalk.dim('$')} ${chalk.cyan('agent studio --port 5000')} + ${ui.format.dim('Start Studio on custom port')} + ${ui.format.dim('$')} ${ui.format.cyan('agent studio --port 5000')} - ${chalk.dim('Start Studio with path and custom port')} - ${chalk.dim('$')} ${chalk.cyan('agent studio ./monitoring -p 5000')} + ${ui.format.dim('Start Studio with path and custom port')} + ${ui.format.dim('$')} ${ui.format.cyan('agent studio ./monitoring -p 5000')} - ${chalk.dim('Start only the backend server (for development)')} - ${chalk.dim('$')} ${chalk.cyan('agent studio --server-only')} + ${ui.format.dim('Start only the backend server (for development)')} + ${ui.format.dim('$')} ${ui.format.cyan('agent studio --server-only')} `, ) .action(studioCommand); @@ -610,11 +529,9 @@ export const parseAndValidate = async (fileName: string): Promise => { const parseResult = document.parseResult; // verify no lexer, parser, or general diagnostic errors show up if (parseResult.lexerErrors.length === 0 && parseResult.parserErrors.length === 0) { - // eslint-disable-next-line no-console - console.log(chalk.green(`Parsed and validated ${fileName} successfully!`)); + ui.success(`Parsed and validated ${fileName} successfully!`); } else { - // eslint-disable-next-line no-console - console.log(chalk.red(`Failed to parse and validate ${fileName}!`)); + ui.error(`Failed to parse and validate ${fileName}!`); } }; @@ -641,9 +558,8 @@ export const runModule = async (fileName: string): Promise => { await runPostInitTasks(appSpec, config); }); } catch (err: unknown) { - if (isNodeEnv && chalk) { - // eslint-disable-next-line no-console - console.error(chalk.red(String(err))); + if (isNodeEnv) { + ui.error(String(err)); } else { // eslint-disable-next-line no-console console.error(String(err)); @@ -675,8 +591,7 @@ export const replCommand = async ( verbose: !options?.quiet, }); } catch (error) { - // eslint-disable-next-line no-console - console.log(chalk.red(`Failed to start REPL: ${error instanceof Error ? error.message : String(error)}`)); + ui.error(`Failed to start REPL: ${error instanceof Error ? error.message : String(error)}`); process.exit(1); } }; @@ -691,24 +606,27 @@ export async function internAndRunModule(module: ModuleDefinition, appSpec?: App return rm; } -/* eslint-disable no-console */ export const generateUICommand = async ( specFile?: string, options?: { directory?: string; apiKey?: string; push?: boolean; message?: string }, ): Promise => { try { - console.log(chalk.blue('šŸš€ Agentlang UI Generator\n')); + ui.blank(); + ui.banner('UI Generator'); + ui.blank(); // Get API key from options or environment const apiKey = options?.apiKey || process.env.ANTHROPIC_API_KEY; if (!apiKey) { - console.error(chalk.red('āŒ Error: Anthropic API key is required.')); - console.log(chalk.yellow(' Set ANTHROPIC_API_KEY environment variable or use --api-key flag.')); - console.log(chalk.gray('\n Example:')); - console.log(chalk.gray(' $ export ANTHROPIC_API_KEY=sk-ant-...')); - console.log(chalk.gray(' $ agent ui-gen')); - console.log(chalk.gray('\n Or:')); - console.log(chalk.gray(' $ agent ui-gen --api-key sk-ant-...')); + ui.error('Anthropic API key is required.'); + ui.warn('Set ANTHROPIC_API_KEY environment variable or use --api-key flag.'); + ui.blank(); + ui.gray(' Example:'); + ui.gray(' $ export ANTHROPIC_API_KEY=sk-ant-...'); + ui.gray(' $ agent ui-gen'); + ui.blank(); + ui.gray(' Or:'); + ui.gray(' $ agent ui-gen --api-key sk-ant-...'); process.exit(1); } @@ -719,31 +637,34 @@ export const generateUICommand = async ( // Auto-detect spec file if not provided let specFilePath: string; if (!specFile) { - console.log(chalk.cyan('šŸ“„ Searching for UI spec file...')); + ui.dim('Searching for UI spec file...'); specFilePath = await findSpecFile(absoluteTargetDir); } else { specFilePath = path.resolve(process.cwd(), specFile); } // Load the UI spec - console.log(chalk.cyan(`šŸ“„ Loading UI spec from: ${specFilePath}`)); const uiSpec = await loadUISpec(specFilePath); - console.log(chalk.cyan(`šŸ“‚ Target directory: ${absoluteTargetDir}`)); - console.log(chalk.cyan(`šŸ“¦ Output will be created in: ${path.join(absoluteTargetDir, 'ui')}`)); + ui.label('Spec', specFilePath, 'cyan'); + ui.label('Target', absoluteTargetDir); + ui.label('Output', path.join(absoluteTargetDir, 'ui')); + ui.blank(); // Generate or update the UI await generateUI(uiSpec, absoluteTargetDir, apiKey, options?.push || false, options?.message); - console.log(chalk.green('\nāœ… UI generation completed successfully!')); + ui.blank(); + ui.divider(50); + ui.success('UI generation completed!'); + ui.divider(50); + ui.blank(); } catch (error) { - console.error(chalk.red('\nāŒ Error:'), error instanceof Error ? error.message : error); + ui.error(error instanceof Error ? error.message : String(error)); process.exit(1); } }; -/* eslint-enable no-console */ -/* eslint-disable no-console */ export const studioCommand = async ( projectPath?: string, options?: { port?: string; serverOnly?: boolean }, @@ -751,31 +672,26 @@ export const studioCommand = async ( try { const port = parseInt(options?.port || '4000', 10); if (isNaN(port) || port < 1 || port > 65535) { - console.error(chalk.red('Invalid port number. Port must be between 1 and 65535.')); + ui.error('Invalid port number. Port must be between 1 and 65535.'); process.exit(1); } await startStudio(projectPath || '.', port, options?.serverOnly); } catch (error) { - console.error(chalk.red(`Failed to start Studio: ${error instanceof Error ? error.message : String(error)}`)); + ui.error(`Failed to start Studio: ${error instanceof Error ? error.message : String(error)}`); process.exit(1); } }; -/* eslint-enable no-console */ -/* eslint-disable no-console */ export const forkCommand = async ( source: string, name?: string, options?: { branch?: string; username?: string; token?: string }, ): Promise => { try { - console.log(chalk.blue('šŸš€ Forking Agentlang application...\n')); - // Determine destination name let appName = name; if (!appName) { if (source.startsWith('http') || source.startsWith('git@')) { - // Try to infer from URL const parts = source.split('/'); const lastPart = parts[parts.length - 1].replace('.git', ''); appName = lastPart; @@ -799,29 +715,45 @@ export const forkCommand = async ( }; } - console.log(chalk.cyan(`šŸ“¦ Source: ${source}`)); - console.log(chalk.cyan(`šŸ“‚ Destination: ${destPath}`)); + ui.blank(); + ui.banner('Fork App'); + ui.blank(); + ui.label('Source', source, 'cyan'); + ui.label('Destination', destPath); if (options?.branch) { - console.log(chalk.cyan(`🌿 Branch: ${options.branch}`)); + ui.label('Branch', options.branch, 'cyan'); } if (forkOptions.credentials) { - console.log(chalk.cyan(`šŸ” Authenticated as: ${forkOptions.credentials.username}`)); + ui.label('Auth', forkOptions.credentials.username, 'cyan'); } + ui.blank(); // Perform the fork const result = await forkApp(source, destPath, forkOptions); - console.log(chalk.green(`\nāœ… Successfully forked app "${result.name}"!`)); - console.log(chalk.dim('\nNext steps:')); - console.log(chalk.dim(' 1. Change directory: ') + chalk.cyan(`cd ${result.name}`)); - console.log(chalk.dim(' 2. Run your app: ') + chalk.cyan('agent run')); - console.log(chalk.dim(' 3. Or start Studio: ') + chalk.cyan('agent studio')); + ui.divider(50); + ui.success(`Forked "${result.name}" successfully!`); + ui.blank(); + ui.dim('Next steps:'); + ui.row([ + { text: ' 1. Change directory: ', dimColor: true }, + { text: `cd ${result.name}`, color: 'cyan' }, + ]); + ui.row([ + { text: ' 2. Run your app: ', dimColor: true }, + { text: 'agent run', color: 'cyan' }, + ]); + ui.row([ + { text: ' 3. Or start Studio: ', dimColor: true }, + { text: 'agent studio', color: 'cyan' }, + ]); + ui.divider(50); + ui.blank(); } catch (error) { - console.error(chalk.red('\nāŒ Error:'), error instanceof Error ? error.message : error); + ui.error(error instanceof Error ? error.message : String(error)); process.exit(1); } }; -/* eslint-enable no-console */ interface OpenApiConfigItem { name: string; diff --git a/src/repl.ts b/src/repl.ts index bc8b077..f86e4c9 100644 --- a/src/repl.ts +++ b/src/repl.ts @@ -1,8 +1,8 @@ import * as readline from 'node:readline'; import * as path from 'node:path'; import * as chokidar from 'chokidar'; -import chalk from 'chalk'; import { existsSync } from 'node:fs'; +import { ui, ansi } from './ui/index.js'; import type { ApplicationSpec } from 'agentlang/out/runtime/loader.js'; import type { Config } from 'agentlang/out/runtime/state.js'; @@ -297,8 +297,7 @@ function createReplHelpers() { const inspect = { modules: () => { const modules = getUserModuleNames(); - // eslint-disable-next-line no-console - console.log(chalk.blue('šŸ“¦ Available Modules:')); + ui.info('Available Modules:'); // eslint-disable-next-line no-console modules.forEach(mod => console.log(` • ${mod}`)); return modules; @@ -306,8 +305,7 @@ function createReplHelpers() { entities: (moduleName?: string) => { const mod = moduleName ? fetchModule(moduleName) : fetchModule(getActiveModuleName()); const entities = mod.getEntityNames(); - // eslint-disable-next-line no-console - console.log(chalk.green(`šŸ—ļø Entities in ${mod.name}:`)); + ui.success(`Entities in ${mod.name}:`); // eslint-disable-next-line no-console entities.forEach(ent => console.log(` • ${ent}`)); return entities; @@ -315,8 +313,7 @@ function createReplHelpers() { events: (moduleName?: string) => { const mod = moduleName ? fetchModule(moduleName) : fetchModule(getActiveModuleName()); const events = mod.getEventNames(); - // eslint-disable-next-line no-console - console.log(chalk.yellow(`⚔ Events in ${mod.name}:`)); + ui.info(`Events in ${mod.name}:`); // eslint-disable-next-line no-console events.forEach(evt => console.log(` • ${evt}`)); return events; @@ -324,8 +321,7 @@ function createReplHelpers() { relationships: (moduleName?: string) => { const mod = moduleName ? fetchModule(moduleName) : fetchModule(getActiveModuleName()); const rels = mod.getRelationshipNames(); - // eslint-disable-next-line no-console - console.log(chalk.magenta(`šŸ”— Relationships in ${mod.name}:`)); + ui.info(`Relationships in ${mod.name}:`); // eslint-disable-next-line no-console rels.forEach(rel => console.log(` • ${rel}`)); return rels; @@ -335,8 +331,7 @@ function createReplHelpers() { throw new Error('entityName is required'); } const instances = await lookupAllInstances(entityName); - // eslint-disable-next-line no-console - console.log(chalk.cyan(`šŸ­ Instances for ${entityName}:`)); + ui.info(`Instances for ${entityName}:`); return instances; }, @@ -346,53 +341,63 @@ function createReplHelpers() { const utils = { help: () => { /* eslint-disable no-console */ - console.log(chalk.blue.bold('\nšŸš€ AgentLang REPL - Comprehensive Guide\n')); + ui.blank(); + ui.row([{ text: 'AgentLang REPL — Comprehensive Guide', color: 'blue', bold: true }]); + ui.blank(); - console.log(chalk.green.bold('šŸ“‹ Basic Commands:')); + ui.row([{ text: 'Basic Commands:', color: 'green', bold: true }]); console.log(' help, ? // Show this help'); console.log(' exit, quit // Exit REPL'); console.log(' clear // Clear screen'); console.log(' restart // Restart REPL'); - console.log(chalk.cyan.bold('\nšŸ—ļø Entity Creation:')); + ui.blank(); + ui.row([{ text: 'Entity Creation:', color: 'cyan', bold: true }]); console.log(' e("User") // Empty entity'); console.log(' e("User", {name: "String"}) // Object syntax'); console.log(' e("User", "name String, age Int") // String syntax'); console.log(' entity("User", {id: "String @id"}) // Alias for e()'); - console.log(chalk.magenta.bold('\nšŸ“„ Record Creation:')); + ui.blank(); + ui.row([{ text: 'Record Creation:', color: 'magenta', bold: true }]); console.log(' r("Config") // Empty record'); console.log(' r("Config", {key: "String"}) // Object syntax'); console.log(' r("Config", "key String, val Any") // String syntax'); console.log(' record("Config", {settings: "Map"}) // Alias for r()'); - console.log(chalk.yellow.bold('\n⚔ Event Creation:')); + ui.blank(); + ui.row([{ text: 'Event Creation:', color: 'yellow', bold: true }]); console.log(' ev("UserCreated") // Empty event'); console.log(' ev("UserCreated", {id: "String"}) // Object syntax'); console.log(' ev("UserCreated", "id String") // String syntax'); console.log(' event("UserCreated", {data: "Map"}) // Alias for ev()'); - console.log(chalk.red.bold('\nšŸ”— Relationship Creation:')); + ui.blank(); + ui.row([{ text: 'Relationship Creation:', color: 'red', bold: true }]); console.log(' rel("UserPosts", "contains", ["User", "Post"])'); console.log(' rel("Friendship", "between", ["User", "User"])'); console.log(' relationship("Owns", "contains", ["User", "Asset"])'); - console.log(chalk.blue.bold('\nšŸ”„ Workflow Creation:')); + ui.blank(); + ui.row([{ text: 'Workflow Creation:', color: 'blue', bold: true }]); console.log(' w("ProcessUser") // Empty workflow'); console.log(' w("ProcessUser", ["step1", "step2"]) // With steps'); console.log(' workflow("HandleOrder", ["validate", "process"])'); - console.log(chalk.green.bold('\nšŸ­ Instance Creation:')); + ui.blank(); + ui.row([{ text: 'Instance Creation:', color: 'green', bold: true }]); console.log(' inst("User", {name: "John", age: 30})'); console.log(' instance("Post", {title: "Hello", content: "World"})'); - console.log(chalk.cyan.bold('\nšŸ“ Template Literal Usage:')); + ui.blank(); + ui.row([{ text: 'Template Literal Usage:', color: 'cyan', bold: true }]); console.log(' al`entity User { name String }` // AgentLang code'); console.log(' al`record Config { key String }` // Multi-line supported'); console.log(' al("entity User { name String }") // Function syntax'); console.log(' ag`event Created { id String }` // Alias for al'); - console.log(chalk.magenta.bold('\nšŸ“¦ Module Management (m.*):')); + ui.blank(); + ui.row([{ text: 'Module Management (m.*):', color: 'magenta', bold: true }]); console.log(' m.active() // Get active module name'); console.log(' m.list() // List all user modules'); console.log(' m.get("MyApp") // Get specific module'); @@ -400,7 +405,8 @@ function createReplHelpers() { console.log(' m.remove("Mod") // Remove module'); console.log(' modules.active() // Alias for m.active()'); - console.log(chalk.yellow.bold('\nšŸ” Inspection Commands (inspect.*):')); + ui.blank(); + ui.row([{ text: 'Inspection Commands (inspect.*):', color: 'yellow', bold: true }]); console.log(' inspect.modules() // List all modules'); console.log(' inspect.entities() // List entities in active module'); console.log(' inspect.entities("MyApp") // List entities in specific module'); @@ -410,78 +416,80 @@ function createReplHelpers() { console.log(' inspect.relationships("MyApp") // List relationships in specific module'); console.log(' inspect.instances("MyApp/EntityName") // List instances created for an entity'); - console.log( - chalk.red.bold( - '\nšŸ› ļø Direct Runtime Functions (requires full qualified names in string: "/"):', - ), - ); - console.log(chalk.white(' Entity Management:')); + ui.blank(); + ui.row([ + { + text: 'Direct Runtime Functions (full qualified names: "/"):', + color: 'red', + bold: true, + }, + ]); + ui.plain(' Entity Management:'); console.log(' addEntity(name, definition) // Add entity to runtime'); console.log(' removeEntity(name) // Remove entity from runtime'); console.log(' getEntity(name) // Get entity definition'); - console.log(chalk.white(' Record Management:')); + ui.plain(' Record Management:'); console.log(' addRecord(name, definition) // Add record to runtime'); console.log(' removeRecord(name) // Remove record from runtime'); console.log(' getRecord(name) // Get record definition'); - console.log(chalk.white(' Event Management:')); + ui.plain(' Event Management:'); console.log(' addEvent(name, definition) // Add event to runtime'); console.log(' removeEvent(name) // Remove event from runtime'); console.log(' getEvent(name) // Get event definition'); - console.log(chalk.white(' Relationship Management:')); + ui.plain(' Relationship Management:'); console.log(' addRelationship(name, def) // Add relationship to runtime'); console.log(' removeRelationship(name) // Remove relationship from runtime'); console.log(' getRelationship(name) // Get relationship definition'); - console.log(chalk.white(' Workflow Management:')); + ui.plain(' Workflow Management:'); console.log(' addWorkflow(name, definition) // Add workflow to runtime'); console.log(' removeWorkflow(name) // Remove workflow from runtime'); console.log(' getWorkflow(name) // Get workflow definition'); - console.log(chalk.white(' Core Processing:')); + ui.plain(' Core Processing:'); console.log(' processAgentlang(code) // Process raw AgentLang code'); console.log(' parseAndEvaluateStatement(stmt) // Parse and evaluate AgentLang statement'); console.log(' // Example: parseAndEvaluateStatement("{MyApp/User {id 1, name \\"Alice\\"}}");'); - console.log(chalk.gray.bold('\nšŸ› ļø Utility Commands (utils.*):')); + ui.blank(); + ui.row([{ text: 'Utility Commands (utils.*):', color: 'gray', bold: true }]); console.log(' utils.help() // Show this help'); console.log(' utils.clear() // Clear screen'); console.log(' utils.restart() // Restart REPL'); console.log(' utils.exit() // Exit REPL'); - console.log(chalk.gray.bold('\nšŸ’” Tips:')); + ui.blank(); + ui.row([{ text: 'Tips:', color: 'gray', bold: true }]); console.log(' • Use tab completion for commands'); console.log(' • Template literals support multi-line code'); console.log(' • All functions return promises - use await if needed'); console.log(' • File watching auto-restarts on changes (if enabled)'); console.log(' • Use inspect.* commands to explore your application'); - console.log(chalk.blue('\nšŸ“š Examples:')); + ui.blank(); + ui.info('Examples:'); console.log(' al`entity User { id String @id, name String }`'); console.log(' inst("User", {id: "123", name: "Alice"})'); console.log(' inspect.entities()'); console.log(' inspect.instances(MyApp/EntityName)'); console.log(' m.active()'); - /* eslint-enable no-console */ return ''; }, clear: () => { - // eslint-disable-next-line no-console console.log('\x1b[2J\x1b[0f'); return ''; }, restart: async () => { - // eslint-disable-next-line no-console - console.log(chalk.yellow('šŸ”„ Restarting REPL...')); + ui.warn('Restarting REPL...'); await restartRepl(); return ''; }, exit: () => { - // eslint-disable-next-line no-console - console.log(chalk.yellow('\nšŸ‘‹ Goodbye!')); + ui.warn('Goodbye!'); cleanup(); process.exit(0); }, @@ -561,28 +569,24 @@ function setupFileWatcher(appDir: string, options: ReplOptions): chokidar.FSWatc }) .on('change', filePath => { if (!options.quiet && isWatcherReady) { - // eslint-disable-next-line no-console - console.log(chalk.blue(`\nšŸ“ File changed: ${path.relative(appDir, filePath)}`)); + ui.info(`File changed: ${path.relative(appDir, filePath)}`); } debouncedRestart(); }) .on('add', filePath => { if (!options.quiet && isWatcherReady) { - // eslint-disable-next-line no-console - console.log(chalk.green(`\nšŸ“ File added: ${path.relative(appDir, filePath)}`)); + ui.success(`File added: ${path.relative(appDir, filePath)}`); } debouncedRestart(); }) .on('unlink', filePath => { if (!options.quiet && isWatcherReady) { - // eslint-disable-next-line no-console - console.log(chalk.red(`\nšŸ“ File removed: ${path.relative(appDir, filePath)}`)); + ui.error(`File removed: ${path.relative(appDir, filePath)}`); } debouncedRestart(); }) .on('error', (error: unknown) => { - // eslint-disable-next-line no-console - console.error(chalk.red(`Watcher error: ${String(error)}`)); + ui.error(`Watcher error: ${String(error)}`); }); return watcher; @@ -595,21 +599,17 @@ async function restartRepl(): Promise { replState.isRestarting = true; try { - // eslint-disable-next-line no-console - console.log(chalk.yellow('\nšŸ”„ Restarting AgentLang REPL...')); + ui.warn('Restarting AgentLang REPL...'); // Reload the application if (replState.appDir) { await loadApplication(replState.appDir); } - // eslint-disable-next-line no-console - console.log(chalk.green('āœ… REPL restarted successfully')); - // eslint-disable-next-line no-console - console.log(chalk.blue('šŸ’¬ Ready for input\n')); + ui.success('REPL restarted successfully'); + ui.info('Ready for input'); } catch (error) { - // eslint-disable-next-line no-console - console.error(chalk.red(`āŒ Failed to restart: ${String(error)}`)); + ui.error(`Failed to restart: ${String(error)}`); } finally { replState.isRestarting = false; } @@ -624,25 +624,21 @@ async function loadApplication(appDir: string): Promise { const configPath = path.join(appDir, 'app.config.json'); const rawConfig = (await loadRawConfig(configPath)) as Record; replState.config = setAppConfig(rawConfig as Parameters[0]); - // eslint-disable-next-line no-console - console.log(chalk.blue(`šŸ“‹ Loaded config from ${configPath}`)); + ui.info(`Loaded config from ${configPath}`); } catch { // Config is optional if (!replState.options.quiet) { - // eslint-disable-next-line no-console - console.log(chalk.yellow('āš ļø No app.config.json found, using defaults')); + ui.warn('No app.config.json found, using defaults'); } } // Load the application - // eslint-disable-next-line no-console - console.log(chalk.blue(`šŸ“‚ Loading application from: ${appDir}`)); + ui.info(`Loading application from: ${appDir}`); await load(appDir, undefined, async (appSpec?: ApplicationSpec) => { if (replState) { replState.appSpec = appSpec; if (appSpec && 'name' in appSpec) { - // eslint-disable-next-line no-console - console.log(chalk.green(`āœ… Loaded application: ${(appSpec as { name: string }).name}`)); + ui.success(`Loaded application: ${(appSpec as { name: string }).name}`); } } await runPostInitTasks(appSpec, replState?.config); @@ -655,8 +651,7 @@ function setupSignalHandlers(): void { signals.forEach(signal => { process.on(signal, () => { - // eslint-disable-next-line no-console - console.log(chalk.yellow(`\n\nšŸ›‘ Received ${signal}, shutting down gracefully...`)); + ui.warn(`Received ${signal}, shutting down gracefully...`); cleanup(); process.exit(0); }); @@ -678,15 +673,18 @@ function cleanup(): void { // Main REPL function export async function startRepl(appDir = '.', options: ReplOptions = {}): Promise { - // eslint-disable-next-line no-console - console.log(chalk.blue.bold('šŸš€ Starting AgentLang REPL...\n')); + // Resolve app directory + const resolvedAppDir = path.resolve(process.cwd(), appDir); + + ui.blank(); + ui.banner('Agentlang REPL'); + ui.blank(); + ui.label('Directory', resolvedAppDir); + ui.blank(); // Setup signal handlers setupSignalHandlers(); - // Resolve app directory - const resolvedAppDir = path.resolve(process.cwd(), appDir); - // Initialize REPL state replState = { appDir: resolvedAppDir, @@ -694,7 +692,7 @@ export async function startRepl(appDir = '.', options: ReplOptions = {}): Promis rl: readline.createInterface({ input: process.stdin, output: process.stdout, - prompt: chalk.cyan('agentlang> '), + prompt: ansi.cyan('agentlang> '), completer: (line: string) => { const completions = [ 'help', @@ -771,22 +769,19 @@ export async function startRepl(appDir = '.', options: ReplOptions = {}): Promis try { await loadApplication(process.cwd()); } catch { - // eslint-disable-next-line no-console - console.log(chalk.blue('šŸ“‚ Starting REPL without loading an application')); + ui.dim('Starting in standalone mode (no application loaded)'); await runPostInitTasks(); } } - // eslint-disable-next-line no-console - console.log(chalk.green('āœ… AgentLang runtime initialized')); + ui.success('AgentLang runtime initialized'); // Setup file watcher AFTER initial load to prevent immediate restart if (options.watch && appDir !== '') { // Give the initial load time to complete before starting watcher await new Promise(resolve => setTimeout(resolve, 100)); replState.watcher = setupFileWatcher(resolvedAppDir, options); - // eslint-disable-next-line no-console - console.log(chalk.green('šŸ‘€ File watching enabled')); + ui.success('File watching enabled'); } // Mark initialization as complete @@ -795,9 +790,7 @@ export async function startRepl(appDir = '.', options: ReplOptions = {}): Promis // Give any async startup messages time to complete await new Promise(resolve => setTimeout(resolve, 50)); - // eslint-disable-next-line no-console - console.log(chalk.blue('šŸ’¬ REPL ready - type "help" for help')); - // eslint-disable-next-line no-console + ui.info('Type "help" for commands'); console.log(); // Extra newline for clean prompt appearance // Create and expose helper functions globally @@ -849,20 +842,16 @@ export async function startRepl(appDir = '.', options: ReplOptions = {}): Promis try { const resolved = await (result as Promise); if (resolved !== undefined && resolved !== '') { - // eslint-disable-next-line no-console - console.log(chalk.green('→'), resolved); + console.log(ui.format.success('→'), resolved); } } catch (error) { - // eslint-disable-next-line no-console - console.error(chalk.red('Promise rejected:'), error); + console.error(ui.format.error('Promise rejected:'), error); } } else if (result !== undefined && result !== '') { - // eslint-disable-next-line no-console - console.log(chalk.green('→'), result); + console.log(ui.format.success('→'), result); } } catch (error) { - // eslint-disable-next-line no-console - console.error(chalk.red('Error:'), error); + console.error(ui.format.error('Error:'), error); } replState?.rl.prompt(); @@ -874,30 +863,21 @@ export async function startRepl(appDir = '.', options: ReplOptions = {}): Promis process.exit(0); }); } catch (error) { - // eslint-disable-next-line no-console - console.error(chalk.red('āŒ Failed to start REPL:')); + ui.error('Failed to start REPL:'); if (error instanceof Error) { const nodeError = error as Error & { code?: string; path?: string }; if (nodeError.code === 'ENOENT') { - // eslint-disable-next-line no-console - console.error(chalk.red('File or directory not found:'), nodeError.path || 'unknown path'); - // eslint-disable-next-line no-console - console.error( - chalk.yellow('šŸ’” Tip: Make sure the directory exists and contains a valid AgentLang application'), - ); + ui.error(`File or directory not found: ${nodeError.path || 'unknown path'}`); + ui.warn('Tip: Make sure the directory exists and contains a valid AgentLang application'); } else if (error.message.includes('app.config.json') || error.message.includes('package.json')) { - // eslint-disable-next-line no-console - console.error(chalk.red('Could not find required configuration files in the specified directory')); - // eslint-disable-next-line no-console - console.error(chalk.yellow('šŸ’” Tip: Make sure you are pointing to a valid AgentLang application directory')); + ui.error('Could not find required configuration files in the specified directory'); + ui.warn('Tip: Make sure you are pointing to a valid AgentLang application directory'); } else { - // eslint-disable-next-line no-console - console.error(chalk.red('Error:'), error.message); + ui.error(`Error: ${error.message}`); } } else { - // eslint-disable-next-line no-console - console.error(chalk.red('Unknown error:'), error); + ui.error(`Unknown error: ${String(error)}`); } cleanup(); diff --git a/src/studio.ts b/src/studio.ts index 5a3dc82..69f938d 100644 --- a/src/studio.ts +++ b/src/studio.ts @@ -1,9 +1,8 @@ -/* eslint-disable no-console */ import express from 'express'; import cors from 'cors'; import path from 'path'; -import chalk from 'chalk'; import ora from 'ora'; +import { ui } from './ui/index.js'; import open from 'open'; import { getWorkspaceRoot, findLStudioPath } from './studio/utils.js'; import { FileService } from './studio/services/FileService.js'; @@ -11,36 +10,43 @@ import { StudioServer } from './studio/services/StudioServer.js'; import { createRoutes } from './studio/routes.js'; export async function startStudio(projectPath = '.', studioPort = 4000, serverOnly = false): Promise { - const spinner = ora(serverOnly ? 'Starting Studio backend server...' : 'Starting Agent Studio...').start(); const inputDir = path.resolve(process.cwd(), projectPath); // Smart Parent Detection: Determine workspace root and initial app const { workspaceRoot, initialAppPath } = getWorkspaceRoot(inputDir); + // ── Startup banner ────────────────────────────────────────────────────────── + ui.blank(); + ui.banner(serverOnly ? 'Agent Studio' : 'Agent Studio', serverOnly ? 'Backend Server' : undefined); + ui.blank(); + + if (initialAppPath) { + ui.label('Project', path.basename(initialAppPath), 'cyan'); + } + ui.label('Workspace', workspaceRoot); + ui.label('Port', String(studioPort), 'cyan'); + ui.blank(); + + // ── Initialize ────────────────────────────────────────────────────────────── + const spinner = ora({ + text: ui.format.dim('Initializing...'), + spinner: 'dots', + }).start(); + // Initialize Services with workspace root const fileService = new FileService(workspaceRoot); const studioServer = new StudioServer(workspaceRoot, initialAppPath, fileService); - // Always use Dashboard Mode if (initialAppPath) { - // Launched from inside a project - show workspace with initial app highlighted - console.log(chalk.blue(`ℹ Detected project: ${path.basename(initialAppPath)}`)); - console.log(chalk.blue(`ℹ Workspace root: ${workspaceRoot}`)); - console.log(chalk.dim(' Auto-launching app...')); - - // Auto-launch the detected app - // This sets the current app in StudioServer, which the frontend can detect via /workspace or /apps + spinner.text = ui.format.dim(`Launching ${path.basename(initialAppPath)}...`); await studioServer.launchApp(initialAppPath); } else { - // Launched from a workspace directory - console.log(chalk.blue(`ℹ Workspace root: ${workspaceRoot}`)); - console.log(chalk.dim(' Starting in Dashboard Mode. Select an app from the UI to launch it.')); + spinner.text = ui.format.dim('Starting Dashboard Mode...'); } - spinner.succeed(chalk.green('Studio Dashboard Ready')); + + spinner.succeed(ui.format.success('Studio initialized')); // Find @agentlang/lstudio (skip in server-only mode) - // Try to find it in the input directory first (for projects with local lstudio) - // then fall back to workspace root let lstudioPath: string | null = null; if (!serverOnly) { lstudioPath = findLStudioPath(inputDir); @@ -48,9 +54,7 @@ export async function startStudio(projectPath = '.', studioPort = 4000, serverOn lstudioPath = findLStudioPath(workspaceRoot); } if (!lstudioPath) { - // Only error if we really can't find it, but in dev mode it might differ. - // Warn instead of exit in case we are just using API - console.warn(chalk.yellow('Warning: Could not find @agentlang/lstudio UI files.')); + ui.warn('Could not find @agentlang/lstudio UI files.'); } } @@ -64,12 +68,9 @@ export async function startStudio(projectPath = '.', studioPort = 4000, serverOn // Serve static files from @agentlang/lstudio/dist (skip in server-only mode) if (!serverOnly && lstudioPath) { - // Serve static files with fallthrough disabled - if file not found, continue to next middleware app.use(express.static(lstudioPath, { fallthrough: true })); - // Handle client-side routing - serve index.html for all non-API, non-static-file routes app.use((req, res, next) => { - // Skip if this is an API route (already handled by createRoutes) if ( req.path.startsWith('/files') || req.path.startsWith('/file') || @@ -78,16 +79,14 @@ export async function startStudio(projectPath = '.', studioPort = 4000, serverOn req.path.startsWith('/branch') || req.path.startsWith('/install') || req.path.startsWith('/env-config.js') || - req.path.startsWith('/workspace') || // workspace info + req.path.startsWith('/workspace') || req.path.startsWith('/apps') || req.path.startsWith('/app/') || - req.path.startsWith('/documents') // document upload routes + req.path.startsWith('/documents') ) { return next(); } - // Check if this is a request for a static file with a known extension - // express.static would have already served it if it existed const staticFileExtensions = [ '.js', '.css', @@ -107,13 +106,10 @@ export async function startStudio(projectPath = '.', studioPort = 4000, serverOn ]; const hasStaticExtension = staticFileExtensions.some(ext => req.path.toLowerCase().endsWith(ext)); - // If it's a static file request that express.static didn't handle, return 404 if (hasStaticExtension) { return res.status(404).send('File not found'); } - // For all other GET requests, serve index.html - // (client-side routing will handle the rest, including routes with dots in them) if (req.method === 'GET' && lstudioPath) { res.sendFile(path.join(lstudioPath, 'index.html')); } else { @@ -122,29 +118,34 @@ export async function startStudio(projectPath = '.', studioPort = 4000, serverOn }); } - // Start server + // ── Start server ──────────────────────────────────────────────────────────── await new Promise(resolve => { app.listen(studioPort, () => { - spinner.succeed(chalk.green(`Studio server is running on http://localhost:${studioPort}`)); const studioUrl = `http://localhost:${studioPort}`; + ui.blank(); + ui.divider(50); + ui.success('Studio is ready'); + ui.blank(); + if (serverOnly) { - console.log(chalk.blue(`Backend API available at: ${studioUrl}`)); - console.log(chalk.dim('Endpoints: /files, /file, /info, /branch, /test, /workspace, /apps, /app/launch')); + ui.label('API', studioUrl, 'cyan'); + ui.dim(' Endpoints: /files, /file, /info, /branch, /test, /workspace, /apps, /app/launch'); } else { - console.log(chalk.blue(`Studio UI is available at: ${studioUrl}`)); + ui.label('Local', studioUrl, 'cyan'); } - // Open browser automatically (skip in server-only mode) + ui.blank(); + ui.divider(50); + ui.blank(); + if (!serverOnly) { - void open(studioUrl).catch(() => { - // Ignore errors when opening browser - }); + void open(studioUrl); } - // Handle cleanup on exit const cleanup = () => { - console.log(chalk.yellow('\nShutting down...')); + ui.blank(); + ui.warn('Shutting down...'); studioServer.stopApp(false); process.exit(0); }; diff --git a/src/studio/services/AppManagementService.ts b/src/studio/services/AppManagementService.ts index cfaf838..65db54e 100644 --- a/src/studio/services/AppManagementService.ts +++ b/src/studio/services/AppManagementService.ts @@ -2,7 +2,7 @@ import path from 'path'; import { existsSync } from 'fs'; import { rm } from 'fs/promises'; -import chalk from 'chalk'; +import { ui } from '../../ui/index.js'; import { initializeProject } from '../../utils/projectInitializer.js'; import { forkApp as forkAppUtil, type ForkOptions } from '../../utils/forkApp.js'; import { AppInfo } from '../types.js'; @@ -12,30 +12,15 @@ export class AppManagementService { async createApp(name: string): Promise { const appPath = path.join(this.workspaceRoot, name); - console.log(`[AppManagement] createApp: Creating app "${name}" at path: ${appPath}`); try { - // Use the shared initialization logic - console.log(`[AppManagement] createApp: Starting project initialization for "${name}"`); - const initStartTime = Date.now(); - await initializeProject(appPath, name, { - silent: false, // Enable logging for better visibility - skipInstall: false, // Install dependencies so the app is ready to run - skipGit: false, // Initialize git repo + silent: false, + skipInstall: false, + skipGit: false, }); - const initDuration = Date.now() - initStartTime; - console.log(`[AppManagement] createApp: Project initialization completed for "${name}" in ${initDuration}ms`); - - const appInfo = { - name, - path: appPath, - isInitialApp: false, - }; - - console.log(`[AppManagement] createApp: Successfully created app "${name}"`); - return appInfo; + return { name, path: appPath, isInitialApp: false }; } catch (error) { console.error(`[AppManagement] createApp: Failed to create app "${name}":`, error); throw error; @@ -73,8 +58,8 @@ export class AppManagementService { } // Delete the app directory - console.log(chalk.yellow(`Deleting app: ${appPath}`)); + ui.warn(`Deleting app: ${appPath}`); await rm(appPath, { recursive: true, force: true }); - console.log(chalk.green(`Successfully deleted app: ${appPath}`)); + ui.success(`Successfully deleted app: ${appPath}`); } } diff --git a/src/studio/services/AppRuntimeService.ts b/src/studio/services/AppRuntimeService.ts index 1614f0a..f98600f 100644 --- a/src/studio/services/AppRuntimeService.ts +++ b/src/studio/services/AppRuntimeService.ts @@ -1,8 +1,7 @@ -/* eslint-disable no-console */ import path from 'path'; import { existsSync } from 'fs'; import { spawn, ChildProcess, execSync } from 'child_process'; -import chalk from 'chalk'; +import { ui, ansi } from '../../ui/index.js'; import { FileService } from './FileService.js'; import { runPreInitTasks } from '../runtime.js'; import { fileURLToPath } from 'url'; @@ -17,7 +16,7 @@ export class AppRuntimeService { constructor(private fileService: FileService) {} async launchApp(appPath: string): Promise { - console.log(chalk.blue(`\nLaunching app: ${appPath}`)); + ui.dim(` Starting ${path.basename(appPath)}...`); // 1. Stop existing app if any this.stopApp(false); // false = don't reset to root yet, we are switching @@ -30,10 +29,10 @@ export class AppRuntimeService { try { const preInitSuccess = await runPreInitTasks(); if (!preInitSuccess) { - console.warn(chalk.yellow('Warning: Failed to initialize Agentlang runtime')); + ui.warn('Failed to initialize Agentlang runtime'); } } catch (error) { - console.warn(chalk.yellow('Warning: Runtime initialization error:'), error); + ui.warn(`Runtime initialization error: ${String(error)}`); } // 4. Check and install dependencies if needed @@ -44,7 +43,7 @@ export class AppRuntimeService { const needsInstall = !existsSync(nodeModulesPath) || !existsSync(path.join(nodeModulesPath, 'sqlite3')); if (needsInstall) { - console.log(chalk.yellow('šŸ“¦ Dependencies not found. Installing...')); + ui.warn('Dependencies not found. Installing...'); try { execSync('npm install', { cwd: appPath, @@ -55,9 +54,9 @@ export class AppRuntimeService { GIT_TERMINAL_PROMPT: '0', }, }); - console.log(chalk.green('āœ“ Dependencies installed')); + ui.success('Dependencies installed'); } catch (error) { - console.error(chalk.red('Failed to install dependencies:'), error); + ui.error(`Failed to install dependencies: ${String(error)}`); throw new Error('Failed to install dependencies. Please run "npm install" manually in the app directory.'); } } @@ -67,7 +66,7 @@ export class AppRuntimeService { try { await this.fileService.loadProject(); } catch (error) { - console.error(chalk.red('Failed to load project runtime:'), error); + ui.error(`Failed to load project runtime: ${String(error)}`); // Continue anyway to allow editing } @@ -107,22 +106,22 @@ export class AppRuntimeService { if (this.agentProcess) { this.agentProcess.stdout?.on('data', (data: Buffer) => { - process.stdout.write(chalk.dim(`[Agent ${path.basename(appPath)}] ${data.toString()}`)); + process.stdout.write(ansi.dim(`[Agent ${path.basename(appPath)}] ${data.toString()}`)); }); this.agentProcess.stderr?.on('data', (data: Buffer) => { - process.stderr.write(chalk.dim(`[Agent ${path.basename(appPath)}] ${data.toString()}`)); + process.stderr.write(ansi.dim(`[Agent ${path.basename(appPath)}] ${data.toString()}`)); }); - console.log(chalk.green(`āœ“ Agent process started (PID: ${this.agentProcess.pid})`)); + ui.success(`Agent process started (PID: ${this.agentProcess.pid})`); } } catch (error) { - console.error(chalk.red('Failed to spawn agent process:'), error); + ui.error(`Failed to spawn agent process: ${String(error)}`); } } stopApp(resetToRoot = true, workspaceRoot?: string): void { if (this.agentProcess) { - console.log(chalk.yellow('\nStopping active app...')); + ui.warn('Stopping active app...'); this.agentProcess.kill(); this.agentProcess = null; } @@ -130,7 +129,7 @@ export class AppRuntimeService { if (resetToRoot && workspaceRoot) { this.currentAppPath = null; this.fileService.setTargetDir(workspaceRoot); - console.log(chalk.green('āœ“ Returned to Dashboard Mode')); + ui.success('Returned to Dashboard Mode'); } else if (resetToRoot) { this.currentAppPath = null; } diff --git a/src/studio/services/GitHubService.ts b/src/studio/services/GitHubService.ts index b1d753f..1e53519 100644 --- a/src/studio/services/GitHubService.ts +++ b/src/studio/services/GitHubService.ts @@ -2,7 +2,7 @@ import path from 'path'; import { existsSync } from 'fs'; import { unlink } from 'fs/promises'; -import chalk from 'chalk'; +import { ui } from '../../ui/index.js'; import { simpleGit } from 'simple-git'; export class GitHubService { @@ -23,7 +23,7 @@ export class GitHubService { try { let progressMessage = 'Checking GitHub repository...'; - console.log(chalk.blue(`[GitHubService] ${progressMessage}`)); + console.log(ui.format.info(`[GitHubService] ${progressMessage}`)); try { const response = await fetch(`https://api.github.com/repos/${githubUsername}/${repoName}`, { @@ -35,10 +35,10 @@ export class GitHubService { if (response.ok) { progressMessage = 'GitHub repository found.'; - console.log(chalk.green(`[GitHubService] ${progressMessage}`)); + console.log(ui.format.success(`[GitHubService] ${progressMessage}`)); } else if (response.status === 404) { progressMessage = 'Creating GitHub repository...'; - console.log(chalk.yellow(`[GitHubService] ${progressMessage}`)); + console.log(ui.format.warn(`[GitHubService] ${progressMessage}`)); const createResponse = await fetch('https://api.github.com/user/repos', { method: 'POST', headers: { @@ -61,7 +61,7 @@ export class GitHubService { } progressMessage = 'GitHub repository created.'; - console.log(chalk.green(`[GitHubService] ${progressMessage}`)); + console.log(ui.format.success(`[GitHubService] ${progressMessage}`)); } else { const errorData = (await response.json().catch(() => ({}))) as { message?: string }; throw new Error(`Failed to check repository: ${response.statusText} - ${errorData.message || ''}`); @@ -75,13 +75,13 @@ export class GitHubService { } progressMessage = 'Initializing git repository...'; - console.log(chalk.blue(`[GitHubService] ${progressMessage}`)); + console.log(ui.format.info(`[GitHubService] ${progressMessage}`)); const git = simpleGit(appPath); const isRepo = await git.checkIsRepo(); if (!isRepo) { progressMessage = 'Initializing git repository...'; - console.log(chalk.yellow(`[GitHubService] ${progressMessage}`)); + console.log(ui.format.warn(`[GitHubService] ${progressMessage}`)); await git.init(); await git.checkoutLocalBranch('main'); } @@ -94,7 +94,7 @@ export class GitHubService { } progressMessage = 'Configuring git remote...'; - console.log(chalk.blue(`[GitHubService] ${progressMessage}`)); + console.log(ui.format.info(`[GitHubService] ${progressMessage}`)); const remotes = await git.getRemotes(true); const originRemote = remotes.find(r => r.name === 'origin'); const expectedRemoteUrl = `https://${githubUsername}:${githubToken}@github.com/${githubUsername}/${repoName}.git`; @@ -102,17 +102,17 @@ export class GitHubService { if (originRemote) { if (originRemote.refs.fetch !== expectedRemoteUrl) { progressMessage = 'Updating remote URL...'; - console.log(chalk.yellow(`[GitHubService] ${progressMessage}`)); + console.log(ui.format.warn(`[GitHubService] ${progressMessage}`)); await git.remote(['set-url', 'origin', expectedRemoteUrl]); } } else { progressMessage = 'Adding remote origin...'; - console.log(chalk.yellow(`[GitHubService] ${progressMessage}`)); + console.log(ui.format.warn(`[GitHubService] ${progressMessage}`)); await git.addRemote('origin', expectedRemoteUrl); } progressMessage = 'Checking for changes to push...'; - console.log(chalk.blue(`[GitHubService] ${progressMessage}`)); + console.log(ui.format.info(`[GitHubService] ${progressMessage}`)); const status = await git.status(); const currentBranch = status.current || 'main'; @@ -175,12 +175,12 @@ export class GitHubService { // This handles edge cases where git status might fail const errorMessage = error instanceof Error ? error.message : String(error); console.warn( - chalk.yellow(`[GitHubService] Could not determine push status, attempting push anyway: ${errorMessage}`), + ui.format.warn(`[GitHubService] Could not determine push status, attempting push anyway: ${errorMessage}`), ); } progressMessage = 'Pushing to GitHub...'; - console.log(chalk.blue(`[GitHubService] ${progressMessage}`)); + console.log(ui.format.info(`[GitHubService] ${progressMessage}`)); await git.push(['-u', 'origin', currentBranch]); // Clean up any potential git index locks that might interfere with file operations @@ -188,7 +188,7 @@ export class GitHubService { const indexLockPath = path.join(appPath, '.git', 'index.lock'); try { if (existsSync(indexLockPath)) { - console.log(chalk.yellow('[GitHubService] Removing stale git index lock...')); + console.log(ui.format.warn('[GitHubService] Removing stale git index lock...')); await unlink(indexLockPath); } } catch { @@ -196,14 +196,14 @@ export class GitHubService { } const successMessage = `Successfully pushed to GitHub: ${githubUsername}/${repoName}`; - console.log(chalk.green(`[GitHubService] ${successMessage}`)); + console.log(ui.format.success(`[GitHubService] ${successMessage}`)); return { success: true, message: successMessage, }; } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); - console.error(chalk.red(`[GitHubService] Failed to push to GitHub: ${errorMessage}`)); + console.error(ui.format.error(`[GitHubService] Failed to push to GitHub: ${errorMessage}`)); throw error; } } diff --git a/src/ui-generator/specFinder.ts b/src/ui-generator/specFinder.ts index ee05816..6cedfce 100644 --- a/src/ui-generator/specFinder.ts +++ b/src/ui-generator/specFinder.ts @@ -1,6 +1,6 @@ import fs from 'fs-extra'; import path from 'path'; -import chalk from 'chalk'; +import { ui } from '../ui/index.js'; /** * Searches for a UI spec file in the given directory @@ -21,8 +21,7 @@ export async function findSpecFile(searchDir: string = process.cwd()): Promise 0) { const filePath = path.join(searchDir, uiSpecFiles[0]); - // eslint-disable-next-line no-console - console.log(chalk.gray(` Found spec file: ${uiSpecFiles[0]}`)); + ui.gray(` Found spec file: ${uiSpecFiles[0]}`); if (uiSpecFiles.length > 1) { - // eslint-disable-next-line no-console - console.log( - chalk.yellow( - ` Note: Multiple spec files found, using ${uiSpecFiles[0]}. Other files: ${uiSpecFiles.slice(1).join(', ')}`, - ), + ui.warn( + ` Note: Multiple spec files found, using ${uiSpecFiles[0]}. Other files: ${uiSpecFiles.slice(1).join(', ')}`, ); } return filePath; diff --git a/src/ui-generator/specLoader.ts b/src/ui-generator/specLoader.ts index a0fdeb0..c48ffcd 100644 --- a/src/ui-generator/specLoader.ts +++ b/src/ui-generator/specLoader.ts @@ -1,6 +1,6 @@ import fs from 'fs-extra'; import path from 'path'; -import chalk from 'chalk'; +import { ui } from '../ui/index.js'; export interface UISpec { appInfo: { @@ -30,8 +30,7 @@ export async function loadUISpec(specPath: string): Promise { throw new Error('Invalid UI spec: missing appInfo.name'); } - // eslint-disable-next-line no-console - console.log(chalk.green(`āœ“ Loaded spec for: ${spec.appInfo.title || spec.appInfo.name}`)); + ui.step('āœ“', ` Loaded spec for: ${spec.appInfo.title || spec.appInfo.name}`); return spec; } catch (error) { diff --git a/src/ui-generator/uiGenerator.ts b/src/ui-generator/uiGenerator.ts index 5ed6fc4..4c89e28 100644 --- a/src/ui-generator/uiGenerator.ts +++ b/src/ui-generator/uiGenerator.ts @@ -2,8 +2,8 @@ import { query, tool, createSdkMcpServer } from '@anthropic-ai/claude-agent-sdk' import { z } from 'zod'; import fs from 'fs-extra'; import path from 'path'; -import chalk from 'chalk'; import ora from 'ora'; +import { ui } from '../ui/index.js'; import { UISpec } from './specLoader.js'; interface ProjectAnalysis { @@ -81,15 +81,11 @@ async function analyzeExistingProject(projectDir: string): Promise 0) { - console.log(chalk.yellow(` āš ļø Warning: Directory exists with ${projectAnalysis.fileCount} files`)); - console.log(chalk.yellow(' āš ļø Switching to incremental mode to preserve existing files')); + ui.warn(`Directory exists with ${projectAnalysis.fileCount} files`); + ui.warn('Switching to incremental mode to preserve existing files'); mode = 'incremental'; } else { - console.log(chalk.cyan(' šŸ“¦ Mode: Fresh generation')); + ui.cyan(' šŸ“¦ Mode: Fresh generation'); } } if (mode === 'incremental') { spinner.succeed('Project analyzed'); console.log(''); // Empty line for spacing - console.log(chalk.cyan(' šŸ”„ Mode: Incremental update')); - console.log(chalk.gray(` šŸ“‚ Found existing project with ${projectAnalysis.fileCount} files`)); - console.log(chalk.gray(' šŸ“ Will add missing files based on spec')); + ui.cyan(' šŸ”„ Mode: Incremental update'); + ui.gray(` šŸ“‚ Found existing project with ${projectAnalysis.fileCount} files`); + ui.gray(' šŸ“ Will add missing files based on spec'); spinner.start('Preparing incremental update...'); } else if (mode === 'update') { spinner.succeed('Project analyzed'); console.log(''); // Empty line for spacing - console.log(chalk.cyan(' āœļø Mode: User-directed update')); - console.log(chalk.gray(` šŸ“‚ Found existing project with ${projectAnalysis.fileCount} files`)); - console.log(chalk.gray(` šŸ’¬ User message: "${userMessage}"`)); + ui.cyan(' āœļø Mode: User-directed update'); + ui.gray(` šŸ“‚ Found existing project with ${projectAnalysis.fileCount} files`); + ui.gray(` šŸ’¬ User message: "${userMessage}"`); // Check if request is vague const vagueKeywords = ['fix', 'make sure', 'properly', 'work', 'working', 'issue', 'problem', 'error']; const isVague = vagueKeywords.some(keyword => userMessage?.toLowerCase().includes(keyword)); if (isVague) { - console.log(chalk.yellow(' āš ļø Note: Request is general - agent will first diagnose issues')); + ui.warn('Note: Request is general - agent will first diagnose issues'); } spinner.start('Preparing update...'); @@ -260,7 +256,7 @@ export async function generateUI( // Start clean generation console.log(''); - spinner.start(chalk.cyan('Starting agent...')); + spinner.start(ui.format.cyan('Starting agent...')); // Query Claude with our MCP server const session = query({ @@ -346,18 +342,18 @@ export async function generateUI( } // Update spinner with clean progress info - let spinnerText = chalk.cyan(`Generating... ${cachedFileCount} files • ${elapsed}s`); + let spinnerText = ui.format.cyan(`Generating... ${cachedFileCount} files • ${elapsed}s`); // Show current tool being used if (currentTool) { - spinnerText += chalk.blue(` • Tool: ${currentTool}`); + spinnerText += ui.format.info(` • Tool: ${currentTool}`); } // Show current thinking or last file created if (currentThinking) { - spinnerText += chalk.gray(` • ${currentThinking}${currentThinking.length >= 60 ? '...' : ''}`); + spinnerText += ui.format.gray(` • ${currentThinking}${currentThinking.length >= 60 ? '...' : ''}`); } else if (lastFileCreated) { - spinnerText += chalk.gray(` • ${lastFileCreated}`); + spinnerText += ui.format.gray(` • ${lastFileCreated}`); } spinner.text = spinnerText; @@ -365,11 +361,7 @@ export async function generateUI( // Show periodic progress updates (every 10 seconds) if (now - lastProgressUpdate > PROGRESS_UPDATE_INTERVAL && cachedFileCount > 0) { spinner.stop(); - console.log( - chalk.gray( - ` šŸ“Š Progress: ${cachedFileCount} files created, ${toolCallCount} operations, ${elapsed}s elapsed`, - ), - ); + ui.gray(` šŸ“Š Progress: ${cachedFileCount} files created, ${toolCallCount} operations, ${elapsed}s elapsed`); spinner.start(spinnerText); lastProgressUpdate = now; } @@ -380,24 +372,24 @@ export async function generateUI( if (message.subtype === 'success') { sessionSucceeded = true; - console.log(chalk.green('\nāœ… Agent completed successfully')); - console.log(chalk.gray(` ā± Time: ${finalElapsed}s`)); - console.log(chalk.gray(` šŸ”„ Turns: ${message.num_turns}`)); - console.log(chalk.gray(` šŸ”§ Operations: ${toolCallCount}`)); - console.log(chalk.gray(` šŸ’° Cost: $${message.total_cost_usd.toFixed(4)}`)); + ui.success('Agent completed successfully'); + ui.gray(` ā± Time: ${finalElapsed}s`); + ui.gray(` šŸ”„ Turns: ${message.num_turns}`); + ui.gray(` šŸ”§ Operations: ${toolCallCount}`); + ui.gray(` šŸ’° Cost: $${message.total_cost_usd.toFixed(4)}`); } else { sessionSucceeded = false; sessionError = message.subtype; - console.log(chalk.yellow(`\nāš ļø Agent finished with status: ${message.subtype}`)); - console.log(chalk.gray(` ā± Time: ${finalElapsed}s`)); + ui.warn(`Agent finished with status: ${message.subtype}`); + ui.gray(` ā± Time: ${finalElapsed}s`); // Check if agent did no work if (toolCallCount === 0) { - console.log(chalk.yellow('\nāš ļø Warning: Agent completed but performed no operations.')); - console.log(chalk.gray(' This might indicate:')); - console.log(chalk.gray(' • The task description was unclear or too vague')); - console.log(chalk.gray(' • The agent thought no changes were needed')); - console.log(chalk.gray(' • An error occurred before tools could be used')); + ui.warn('Agent completed but performed no operations.'); + ui.gray(' This might indicate:'); + ui.gray(' • The task description was unclear or too vague'); + ui.gray(' • The agent thought no changes were needed'); + ui.gray(' • An error occurred before tools could be used'); } } } @@ -417,35 +409,38 @@ export async function generateUI( // Count actual files generated in the ui/ directory const actualFileCount = await countGeneratedFiles(projectDir); - console.log(chalk.green('\nāœ… Generation complete!')); - console.log(chalk.green('\nšŸ“Š Summary:')); - console.log(chalk.gray(' • Files created: ') + chalk.white(actualFileCount)); - console.log(chalk.gray(' • Time elapsed: ') + chalk.white(`${((Date.now() - startTime) / 1000).toFixed(1)}s`)); - console.log(chalk.gray(' • Output location: ') + chalk.white(projectDir)); + ui.success('Generation complete!'); + ui.info('Summary:'); + ui.row([{ text: ' • Files created: ', color: 'gray' }, { text: String(actualFileCount) }]); + ui.row([ + { text: ' • Time elapsed: ', color: 'gray' }, + { text: `${((Date.now() - startTime) / 1000).toFixed(1)}s` }, + ]); + ui.row([{ text: ' • Output location: ', color: 'gray' }, { text: projectDir }]); // Show sample files created (first 8) if (filesCreated.length > 0) { - console.log(chalk.cyan('\nšŸ“„ Sample files created:')); + ui.cyan('\nšŸ“„ Sample files created:'); const sampleFiles = filesCreated.slice(0, 8); sampleFiles.forEach(file => { - console.log(chalk.gray(` • ${file}`)); + ui.gray(` • ${file}`); }); if (filesCreated.length > 8) { - console.log(chalk.gray(` ... and ${filesCreated.length - 8} more files`)); + ui.gray(` ... and ${filesCreated.length - 8} more files`); } } // Git operations if requested if (shouldPush) { - console.log(''); // Add newline + ui.blank(); await performGitOperations(projectDir, outputBaseDir, uiSpec.appInfo.title); } - console.log(chalk.cyan('\nšŸ“ Next steps:')); - console.log(chalk.white(` cd ${projectDir}`)); - console.log(chalk.white(' npm install')); - console.log(chalk.white(' npm run build # Verify build succeeds')); - console.log(chalk.white(' npm run dev # Start development server')); + ui.cyan('\nšŸ“ Next steps:'); + ui.plain(` cd ${projectDir}`); + ui.plain(' npm install'); + ui.plain(' npm run build # Verify build succeeds'); + ui.plain(' npm run dev # Start development server'); } catch (error) { spinner.fail('UI generation failed'); throw error; @@ -482,7 +477,6 @@ async function countGeneratedFiles(projectDir: string): Promise { return count; } -/* eslint-disable no-console */ async function performGitOperations(projectDir: string, repoRoot: string, appTitle: string): Promise { const { exec } = await import('child_process'); const { promisify } = await import('util'); @@ -503,7 +497,7 @@ async function performGitOperations(projectDir: string, repoRoot: string, appTit await execAsync('git rev-parse --git-dir'); } catch { gitSpinner.fail('Not a git repository'); - console.log(chalk.yellow(' āš ļø Skipping git operations - not a git repository')); + ui.warn('Skipping git operations - not a git repository'); process.chdir(originalCwd); return; } @@ -535,7 +529,7 @@ async function performGitOperations(projectDir: string, repoRoot: string, appTit await execAsync('git remote get-url origin'); } catch { gitSpinner.warn('No remote repository configured'); - console.log(chalk.yellow(' āš ļø Skipping push - no remote configured')); + ui.warn('Skipping push - no remote configured'); process.chdir(originalCwd); return; } @@ -549,25 +543,24 @@ async function performGitOperations(projectDir: string, repoRoot: string, appTit await execAsync(`git push origin ${currentBranch}`); gitSpinner.succeed(`Pushed to remote (${currentBranch})`); - console.log(chalk.green('\nāœ… Successfully committed and pushed UI changes')); + ui.success('Successfully committed and pushed UI changes'); // Restore original directory process.chdir(originalCwd); } catch (error) { gitSpinner.fail('Git operations failed'); - console.log(chalk.yellow('\nāš ļø Warning: Git operations encountered an error')); + ui.warn('Git operations encountered an error'); if (error instanceof Error) { // Extract the meaningful part of the error message const errorMessage = error.message.split('\n')[0]; - console.log(chalk.gray(` ${errorMessage}`)); + ui.gray(` ${errorMessage}`); } - console.log(chalk.yellow(' šŸ’” You may need to commit and push manually:')); - console.log(chalk.gray(' git add ui/')); - console.log(chalk.gray(` git commit -m "Add generated UI for ${appTitle}"`)); - console.log(chalk.gray(' git push')); + ui.warn('You may need to commit and push manually:'); + ui.gray(' git add ui/'); + ui.gray(` git commit -m "Add generated UI for ${appTitle}"`); + ui.gray(' git push'); } } -/* eslint-enable no-console */ function createGenerationPrompt( uiSpec: UISpec, diff --git a/src/ui/components/Help.tsx b/src/ui/components/Help.tsx new file mode 100644 index 0000000..9c6f0d0 --- /dev/null +++ b/src/ui/components/Help.tsx @@ -0,0 +1,249 @@ +import { Box, Text } from 'ink'; + +interface HelpProps { + version: string; +} + +function Separator() { + return {'─'.repeat(56)}; +} + +function SectionTitle({ title }: { title: string }) { + return ( + + + {title} + + + ); +} + +function Command({ name, args, description }: { name: string; args?: string; description: string }) { + return ( + + + {' '} + + {name} + + {args && {args}} + + + {' '} + {description} + + + ); +} + +function Option({ flag, arg, desc }: { flag: string; arg?: string; desc: string }) { + return ( + + {' '} + {flag} + {arg && {arg}} + {' '} + {desc} + + ); +} + +function SubOptions({ children }: { children: React.ReactNode }) { + return ( + + {' OPTIONS'} + {children} + + ); +} + +export default function Help({ version }: HelpProps) { + const g0 = '#00D9FF'; + const g1 = '#00C4E6'; + const g2 = '#00AFCC'; + const g3 = '#009AB3'; + + return ( + + {/* ASCII Art Header — wrapped in a rounded cyan border */} + + + {'ā–ˆā–ˆā–ˆā–ˆā–ˆā•— '} + {' ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•— '} + {'ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•—'} + {'ā–ˆā–ˆā–ˆā•— ā–ˆā–ˆā•—'} + {'ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•—'} + + + {'ā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•—'} + {'ā–ˆā–ˆā•”ā•ā•ā•ā•ā• '} + {'ā–ˆā–ˆā•”ā•ā•ā•ā•ā•'} + {'ā–ˆā–ˆā–ˆā–ˆā•— ā–ˆā–ˆā•‘'} + {'ā•šā•ā•ā–ˆā–ˆā•”ā•ā•ā•'} + + + {'ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•‘'} + {'ā–ˆā–ˆā•‘ ā–ˆā–ˆā–ˆā•—'} + {'ā–ˆā–ˆā–ˆā–ˆā–ˆā•— '} + {'ā–ˆā–ˆā•”ā–ˆā–ˆā•— ā–ˆā–ˆā•‘'} + {' ā–ˆā–ˆā•‘'} + + + {'ā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•‘'} + {'ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘'} + {'ā–ˆā–ˆā•”ā•ā•ā• '} + {'ā–ˆā–ˆā•‘ā•šā–ˆā–ˆā•—ā–ˆā–ˆā•‘'} + {' ā–ˆā–ˆā•‘'} + + + {'ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘'} + {'ā•šā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•”ā•'} + {'ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•—'} + {'ā–ˆā–ˆā•‘ ā•šā–ˆā–ˆā–ˆā–ˆā•‘'} + {' ā–ˆā–ˆā•‘'} + + + {'ā•šā•ā• ā•šā•ā• '} + {' ā•šā•ā•ā•ā•ā•ā• '} + {'ā•šā•ā•ā•ā•ā•ā•ā•'} + {'ā•šā•ā• ā•šā•ā•ā•ā•'} + {' ā•šā•ā•'} + + + + + Agentlang CLI + + {' '} + v{version} + + CLI for all things Agentlang + + + {/* Usage */} + + + + + + $ + agent {''} [options] + + + + + {/* Commands */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* Global Options */} + + + + + + -h, --help + {' '} + Display help information + + + -V, --version + {' '} + Display version number + + + + + {/* Learn More */} + + + + + + Docs + {' '} + https://github.com/agentlang/agentlang-cli + + + Issues + {' '} + https://github.com/agentlang/agentlang-cli/issues + + + + + + + {'Run '} + agent {''} --help + {' for detailed command information'} + + + + ); +} diff --git a/src/ui/index.ts b/src/ui/index.ts new file mode 100644 index 0000000..8948697 --- /dev/null +++ b/src/ui/index.ts @@ -0,0 +1 @@ +export { ui, ansi } from './print.js'; diff --git a/src/ui/print.tsx b/src/ui/print.tsx new file mode 100644 index 0000000..1717592 --- /dev/null +++ b/src/ui/print.tsx @@ -0,0 +1,178 @@ +import React from 'react'; +import { renderToString, Text, Box } from 'ink'; + +interface Part { + text: string; + color?: string; + bold?: boolean; + dimColor?: boolean; +} + +function p(el: React.ReactElement): void { + // eslint-disable-next-line no-console + console.log(renderToString(el)); +} + +function pe(el: React.ReactElement): void { + process.stderr.write(`${renderToString(el)}\n`); +} + +export const ui = { + // Bold green with āœ” prefix + success: (msg: string) => + p( + + {'āœ” '} + {msg} + , + ), + + // Red rounded border box with āœ– prefix — draws maximum attention + error: (msg: string) => + pe( + + + {'āœ– '} + {msg} + + , + ), + + // Bold yellow with ⚠ prefix + warn: (msg: string) => + p( + + {'⚠ '} + {msg} + , + ), + + // Cyan with › prefix — uses › instead of ℹ which breaks on many terminals + info: (msg: string) => + p( + + {'› '} + {msg} + , + ), + + cyan: (msg: string) => p({msg}), + + dim: (msg: string) => p({msg}), + + plain: (msg: string) => p({msg}), + + bold: (msg: string) => p({msg}), + + magenta: (msg: string) => p({msg}), + + gray: (msg: string) => p({msg}), + + // Startup banner — bold title with cyan underline + banner: (title: string, subtitle?: string) => + p( + + + + {title} + + {subtitle !== undefined && subtitle !== '' && ( + + {' '} + {subtitle} + + )} + + {'─'.repeat(title.length + (subtitle ? subtitle.length + 2 : 0))} + , + ), + + // Aligned key-value label pair — for project/workspace/port display + label: (key: string, value: string, valueColor?: string) => + p( + + + {key} + + {value} + , + ), + + // Section title with a cyan underline separator + header: (title: string) => + p( + + + {title} + + {'─'.repeat(title.length)} + , + ), + + // Dim horizontal rule for visual separation + divider: (width = 48) => p({'─'.repeat(width)}), + + // Empty line + // eslint-disable-next-line no-console + blank: () => console.log(''), + + // Composite: "āœ“ label value" with bold green check + optional cyan value + step: (check: string, label: string, value?: string) => + p( + + + {check} + + {label} + {value !== undefined && {value}} + , + ), + + // Multi-part row with mixed colors in a single line + row: (parts: Part[]) => + p( + + {parts.map((part, i) => ( + + {part.text} + + ))} + , + ), + + // Returns formatted ANSI strings (for spinner text, inline template use) + format: { + success: (msg: string) => renderToString({msg}), + error: (msg: string) => renderToString({msg}), + warn: (msg: string) => renderToString({msg}), + info: (msg: string) => renderToString({msg}), + cyan: (msg: string) => renderToString({msg}), + dim: (msg: string) => renderToString({msg}), + gray: (msg: string) => renderToString({msg}), + bold: (msg: string) => renderToString({msg}), + boldWhite: (msg: string) => + renderToString( + + {msg} + , + ), + hex: (color: string) => (msg: string) => renderToString({msg}), + row: (parts: Part[]) => + renderToString( + + {parts.map((part, i) => ( + + {part.text} + + ))} + , + ), + }, +}; + +// Raw ANSI for string contexts that require a plain string (readline prompts) +export const ansi = { + cyan: (s: string) => `\x1b[36m${s}\x1b[0m`, + green: (s: string) => `\x1b[32m${s}\x1b[0m`, + dim: (s: string) => `\x1b[2m${s}\x1b[0m`, +}; diff --git a/src/utils/projectInitializer.ts b/src/utils/projectInitializer.ts index 010d8fa..814e783 100644 --- a/src/utils/projectInitializer.ts +++ b/src/utils/projectInitializer.ts @@ -1,9 +1,8 @@ -/* eslint-disable no-console */ import { join } from 'path'; import { existsSync, mkdirSync, writeFileSync, readdirSync, statSync } from 'fs'; import { execSync } from 'child_process'; -import chalk from 'chalk'; import { simpleGit, type SimpleGit } from 'simple-git'; +import { ui } from '../ui/index.js'; import { generateApp } from '../app-generator/index.js'; export interface InitializeProjectOptions { @@ -73,7 +72,7 @@ function writeGitignore(targetDir: string, silent = false): void { } writeFileSync(gitignorePath, defaultGitignoreContent, 'utf-8'); - if (!silent) console.log(`${chalk.green('āœ“')} Created ${chalk.cyan('.gitignore')}`); + if (!silent) ui.step('āœ“', ' Created ', '.gitignore'); } async function initializeGitRepository(targetDir: string, silent = false): Promise { @@ -84,17 +83,15 @@ async function initializeGitRepository(targetDir: string, silent = false): Promi if (!isRepo) { await git.init(); await git.checkoutLocalBranch('main'); - if (!silent) console.log(`${chalk.green('āœ“')} Initialized ${chalk.cyan('git')} repository`); + if (!silent) ui.step('āœ“', ' Initialized ', 'git repository'); } else { - if (!silent) console.log(chalk.dim('ā„¹ļø Git repository already initialized.')); + if (!silent) ui.dim('Git repository already initialized.'); } return git; } catch (error) { if (!silent) { - console.log( - chalk.yellow(`āš ļø Skipping git initialization: ${error instanceof Error ? error.message : String(error)}`), - ); + ui.warn(`Skipping git initialization: ${error instanceof Error ? error.message : String(error)}`); } return null; } @@ -105,31 +102,21 @@ function installDependencies(targetDir: string, silent = false) { } export async function setupGitRepository(targetDir: string, silent = false): Promise { - console.log(`[ProjectInitializer] Setting up git repository at ${targetDir}`); writeGitignore(targetDir, silent); const git = await initializeGitRepository(targetDir, silent); - if (!git) { - console.log('[ProjectInitializer] Git repository initialization skipped or failed'); - return null; - } + if (!git) return null; try { - console.log('[ProjectInitializer] Adding files to git repository'); await git.add('.'); const status = await git.status(); if (status.files.length > 0) { - console.log(`[ProjectInitializer] Creating initial git commit with ${status.files.length} files`); await git.commit('chore: initial Agentlang app scaffold'); - if (!silent) console.log(`${chalk.green('āœ“')} Created initial git commit`); - console.log('[ProjectInitializer] Initial git commit created successfully'); - } else { - console.log('[ProjectInitializer] No files to commit'); + if (!silent) ui.step('āœ“', ' Created initial git commit'); } } catch (error) { - console.error('[ProjectInitializer] Error setting up git repository:', error); if (!silent) { - console.log(chalk.yellow(`āš ļø Skipping commit: ${error instanceof Error ? error.message : String(error)}`)); + ui.warn(`Skipping commit: ${error instanceof Error ? error.message : String(error)}`); } } @@ -146,9 +133,9 @@ export const initializeProject = async ( let coreContent: string; if (prompt) { - if (!silent) console.log(chalk.dim('Generating app template via AI...')); + if (!silent) ui.dim('Generating app template via AI...'); coreContent = await generateApp(prompt, appName); - if (!silent) console.log(`${chalk.green('āœ“')} Finished generating app template via AI`); + if (!silent) ui.step('āœ“', ' Finished generating app template via AI'); } else { coreContent = `module ${appName}.core`; } @@ -156,22 +143,16 @@ export const initializeProject = async ( // Check if already initialized if (isAppInitialized(targetDir)) { if (!silent) { - console.log(chalk.yellow('āš ļø This directory already contains an Agentlang application.')); - console.log(chalk.dim(' Found existing package.json or .al files.')); - console.log(chalk.dim(' No initialization needed.')); + ui.warn('This directory already contains an Agentlang application.'); + ui.dim('Found existing package.json or .al files.'); + ui.dim('No initialization needed.'); } throw new Error('Directory already initialized'); } try { - console.log(`[ProjectInitializer] Starting initialization for "${appName}" at ${targetDir}`); - if (!silent) console.log(chalk.cyan(`šŸš€ Initializing Agentlang application: ${chalk.bold(appName)}\n`)); - if (!existsSync(targetDir)) { - console.log(`[ProjectInitializer] Creating directory: ${targetDir}`); mkdirSync(targetDir, { recursive: true }); - } else { - console.log(`[ProjectInitializer] Directory already exists: ${targetDir}`); } // Create package.json @@ -185,9 +166,8 @@ export const initializeProject = async ( '@agentlang/lstudio': '*', }, }; - console.log(`[ProjectInitializer] Creating package.json for "${appName}"`); writeFileSync(join(targetDir, 'package.json'), JSON.stringify(packageJson, null, 2), 'utf-8'); - if (!silent) console.log(`${chalk.green('āœ“')} Created ${chalk.cyan('package.json')}`); + if (!silent) ui.step('āœ“', ' Created ', 'package.json'); // Create config.al with Agentlang syntax for LLM and JSON for the rest const configAlContent = `{ @@ -225,54 +205,32 @@ export const initializeProject = async ( ] }`; - console.log(`[ProjectInitializer] Creating config.al for "${appName}"`); writeFileSync(join(targetDir, 'config.al'), configAlContent, 'utf-8'); - if (!silent) console.log(`${chalk.green('āœ“')} Created ${chalk.cyan('config.al')}`); + if (!silent) ui.step('āœ“', ' Created ', 'config.al'); // Create src directory const srcDir = join(targetDir, 'src'); mkdirSync(srcDir, { recursive: true }); - console.log(`[ProjectInitializer] Creating src/core.al for "${appName}"`); writeFileSync(join(srcDir, 'core.al'), coreContent, 'utf-8'); - if (!silent) console.log(`${chalk.green('āœ“')} Created ${chalk.cyan('src/core.al')}`); + if (!silent) ui.step('āœ“', ' Created ', 'src/core.al'); // Install dependencies if (!skipInstall) { - console.log(`[ProjectInitializer] Installing dependencies for "${appName}" (this may take a while)...`); - const installStartTime = Date.now(); - if (!silent) console.log(chalk.cyan('\nšŸ“¦ Installing dependencies...')); + if (!silent) ui.info('Installing dependencies...'); try { installDependencies(targetDir, silent); - } catch (error) { - const installDuration = Date.now() - installStartTime; - console.error( - `[ProjectInitializer] Failed to install dependencies for "${appName}" after ${installDuration}ms:`, - error, - ); - if (!silent) - console.log(chalk.yellow('āš ļø Failed to install dependencies. You may need to run npm install manually.')); + } catch { + if (!silent) ui.warn('Failed to install dependencies. You may need to run npm install manually.'); } - const installDuration = Date.now() - installStartTime; - console.log(`[ProjectInitializer] Dependencies installed for "${appName}" in ${installDuration}ms`); - if (!silent) console.log(`${chalk.green('āœ“')} Dependencies installed`); - } else { - console.log(`[ProjectInitializer] Skipping dependency installation for "${appName}"`); + if (!silent) ui.step('āœ“', ' Dependencies installed'); } if (!skipGit) { - console.log(`[ProjectInitializer] Initializing git repository for "${appName}"`); await setupGitRepository(targetDir, silent); - console.log(`[ProjectInitializer] Git repository initialized for "${appName}"`); - } else { - console.log(`[ProjectInitializer] Skipping git initialization for "${appName}"`); } - - console.log(`[ProjectInitializer] Successfully completed initialization for "${appName}"`); } catch (error) { - console.error(`[ProjectInitializer] Error initializing application "${appName}":`, error); - if (!silent) - console.error(chalk.red('āŒ Error initializing application:'), error instanceof Error ? error.message : error); + if (!silent) ui.error(`Error initializing application: ${error instanceof Error ? error.message : String(error)}`); throw error; } }; diff --git a/tsconfig.json b/tsconfig.json index 50a96a9..2c36776 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,8 +16,10 @@ "rootDir": ".", "noEmit": true, "experimentalDecorators": true, - "emitDecoratorMetadata": true + "emitDecoratorMetadata": true, + "jsx": "react-jsx", + "jsxImportSource": "react" }, - "include": ["src/**/*.ts"], + "include": ["src/**/*.ts", "src/**/*.tsx"], "exclude": ["node_modules"] } diff --git a/tsconfig.src.json b/tsconfig.src.json index f5efd40..7521c88 100644 --- a/tsconfig.src.json +++ b/tsconfig.src.json @@ -4,5 +4,5 @@ "noEmit": false, "rootDir": "src" }, - "include": ["src/**/*.ts"] + "include": ["src/**/*.ts", "src/**/*.tsx"] }