@fastino-ai/pioneer-cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +116 -0
- package/bun.lock +127 -0
- package/install.sh +68 -0
- package/package.json +31 -0
- package/src/api.ts +348 -0
- package/src/config.ts +64 -0
- package/src/index.tsx +417 -0
- package/tsconfig.json +21 -0
package/README.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Pioneer CLI
|
|
2
|
+
|
|
3
|
+
Command-line interface for the Pioneer AI training platform.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
██████╗ ██╗ ██████╗ ███╗ ██╗███████╗███████╗██████╗
|
|
7
|
+
██╔══██╗██║██╔═══██╗████╗ ██║██╔════╝██╔════╝██╔══██╗
|
|
8
|
+
██████╔╝██║██║ ██║██╔██╗ ██║█████╗ █████╗ ██████╔╝
|
|
9
|
+
██╔═══╝ ██║██║ ██║██║╚██╗██║██╔══╝ ██╔══╝ ██╔══██╗
|
|
10
|
+
██║ ██║╚██████╔╝██║ ╚████║███████╗███████╗██║ ██║
|
|
11
|
+
╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝╚══════╝╚═╝ ╚═╝
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
### Quick Install (requires Bun)
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
curl -fsSL https://pioneer.ai/install.sh | sh
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Manual Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# Install Bun if needed
|
|
26
|
+
curl -fsSL https://bun.sh/install | bash
|
|
27
|
+
|
|
28
|
+
# Clone and install
|
|
29
|
+
git clone https://github.com/fastino-ai/pioneer-cli.git
|
|
30
|
+
cd pioneer-cli/
|
|
31
|
+
bun install
|
|
32
|
+
|
|
33
|
+
# Run directly
|
|
34
|
+
bun run src/index.tsx --help
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### npm (coming soon)
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install -g @fastino-ai/pioneer-cli
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Show help
|
|
47
|
+
pioneer --help
|
|
48
|
+
|
|
49
|
+
# Authentication
|
|
50
|
+
pioneer auth login # Enter API key interactively
|
|
51
|
+
pioneer auth logout # Clear stored API key
|
|
52
|
+
pioneer auth status # Check if logged in
|
|
53
|
+
|
|
54
|
+
# Datasets
|
|
55
|
+
pioneer dataset list
|
|
56
|
+
pioneer dataset get <id>
|
|
57
|
+
pioneer dataset delete <id>
|
|
58
|
+
pioneer dataset download <id>
|
|
59
|
+
pioneer dataset analyze <id>
|
|
60
|
+
|
|
61
|
+
# Training Jobs
|
|
62
|
+
pioneer job list
|
|
63
|
+
pioneer job get <id>
|
|
64
|
+
pioneer job logs <id>
|
|
65
|
+
pioneer job create --model-name "My Model" --dataset-ids ds_123,ds_456
|
|
66
|
+
|
|
67
|
+
# Models
|
|
68
|
+
pioneer model list # List deployed models
|
|
69
|
+
pioneer model trained # List trained models
|
|
70
|
+
pioneer model delete <id>
|
|
71
|
+
pioneer model download <id>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Configuration
|
|
75
|
+
|
|
76
|
+
The CLI stores configuration in `~/.pioneer/config.json`.
|
|
77
|
+
|
|
78
|
+
### Environment Variables
|
|
79
|
+
|
|
80
|
+
| Variable | Description |
|
|
81
|
+
|----------|-------------|
|
|
82
|
+
| `PIONEER_API_URL` | API base URL (default: `https://agent.pioneer.ai`) |
|
|
83
|
+
| `PIONEER_API_KEY` | API key (overrides saved key) |
|
|
84
|
+
|
|
85
|
+
## Authentication
|
|
86
|
+
|
|
87
|
+
The CLI validates your API key by making a test request to `/felix/datasets`. If successful, the key is saved locally for future use.
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# Login with API key
|
|
91
|
+
pioneer auth login
|
|
92
|
+
# Enter your API key when prompted
|
|
93
|
+
|
|
94
|
+
# Or set via environment
|
|
95
|
+
export PIONEER_API_KEY="your-api-key"
|
|
96
|
+
pioneer dataset list
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Development
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
cd pioneer
|
|
103
|
+
bun install
|
|
104
|
+
bun run dev # Hot reload
|
|
105
|
+
bun run typecheck # Type checking
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Tech Stack
|
|
109
|
+
|
|
110
|
+
- **Runtime**: [Bun](https://bun.sh)
|
|
111
|
+
- **UI**: [Ink](https://github.com/vadimdemedes/ink) (React for CLI)
|
|
112
|
+
- **Language**: TypeScript
|
|
113
|
+
|
|
114
|
+
## License
|
|
115
|
+
|
|
116
|
+
MIT
|
package/bun.lock
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lockfileVersion": 1,
|
|
3
|
+
"configVersion": 1,
|
|
4
|
+
"workspaces": {
|
|
5
|
+
"": {
|
|
6
|
+
"name": "pioneer",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"ink": "^4.4.1",
|
|
9
|
+
"ink-spinner": "^5.0.0",
|
|
10
|
+
"ink-text-input": "^5.0.1",
|
|
11
|
+
"react": "^18.3.1",
|
|
12
|
+
},
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"@types/node": "^22.0.0",
|
|
15
|
+
"@types/react": "^18.3.0",
|
|
16
|
+
"typescript": "^5.6.3",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
"packages": {
|
|
21
|
+
"@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.1.3", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^4.0.0" } }, "sha512-3yWxPTq3UQ/FY9p1ErPxIyfT64elWaMvM9lIHnaqpyft63tkxodF5aUElYHrdisWve5cETkh1+KBw1yJuW0aRw=="],
|
|
22
|
+
|
|
23
|
+
"@types/node": ["@types/node@22.19.3", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA=="],
|
|
24
|
+
|
|
25
|
+
"@types/prop-types": ["@types/prop-types@15.7.15", "", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="],
|
|
26
|
+
|
|
27
|
+
"@types/react": ["@types/react@18.3.27", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w=="],
|
|
28
|
+
|
|
29
|
+
"ansi-escapes": ["ansi-escapes@6.2.1", "", {}, "sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig=="],
|
|
30
|
+
|
|
31
|
+
"ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
|
|
32
|
+
|
|
33
|
+
"ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
|
34
|
+
|
|
35
|
+
"auto-bind": ["auto-bind@5.0.1", "", {}, "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg=="],
|
|
36
|
+
|
|
37
|
+
"chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
|
|
38
|
+
|
|
39
|
+
"ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="],
|
|
40
|
+
|
|
41
|
+
"cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="],
|
|
42
|
+
|
|
43
|
+
"cli-cursor": ["cli-cursor@4.0.0", "", { "dependencies": { "restore-cursor": "^4.0.0" } }, "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg=="],
|
|
44
|
+
|
|
45
|
+
"cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="],
|
|
46
|
+
|
|
47
|
+
"cli-truncate": ["cli-truncate@3.1.0", "", { "dependencies": { "slice-ansi": "^5.0.0", "string-width": "^5.0.0" } }, "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA=="],
|
|
48
|
+
|
|
49
|
+
"code-excerpt": ["code-excerpt@4.0.0", "", { "dependencies": { "convert-to-spaces": "^2.0.1" } }, "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA=="],
|
|
50
|
+
|
|
51
|
+
"convert-to-spaces": ["convert-to-spaces@2.0.1", "", {}, "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ=="],
|
|
52
|
+
|
|
53
|
+
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
|
54
|
+
|
|
55
|
+
"eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
|
|
56
|
+
|
|
57
|
+
"emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
|
58
|
+
|
|
59
|
+
"escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="],
|
|
60
|
+
|
|
61
|
+
"indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="],
|
|
62
|
+
|
|
63
|
+
"ink": ["ink@4.4.1", "", { "dependencies": { "@alcalzone/ansi-tokenize": "^0.1.3", "ansi-escapes": "^6.0.0", "auto-bind": "^5.0.1", "chalk": "^5.2.0", "cli-boxes": "^3.0.0", "cli-cursor": "^4.0.0", "cli-truncate": "^3.1.0", "code-excerpt": "^4.0.0", "indent-string": "^5.0.0", "is-ci": "^3.0.1", "is-lower-case": "^2.0.2", "is-upper-case": "^2.0.2", "lodash": "^4.17.21", "patch-console": "^2.0.0", "react-reconciler": "^0.29.0", "scheduler": "^0.23.0", "signal-exit": "^3.0.7", "slice-ansi": "^6.0.0", "stack-utils": "^2.0.6", "string-width": "^5.1.2", "type-fest": "^0.12.0", "widest-line": "^4.0.1", "wrap-ansi": "^8.1.0", "ws": "^8.12.0", "yoga-wasm-web": "~0.3.3" }, "peerDependencies": { "@types/react": ">=18.0.0", "react": ">=18.0.0", "react-devtools-core": "^4.19.1" }, "optionalPeers": ["@types/react", "react-devtools-core"] }, "sha512-rXckvqPBB0Krifk5rn/5LvQGmyXwCUpBfmTwbkQNBY9JY8RSl3b8OftBNEYxg4+SWUhEKcPifgope28uL9inlA=="],
|
|
64
|
+
|
|
65
|
+
"ink-spinner": ["ink-spinner@5.0.0", "", { "dependencies": { "cli-spinners": "^2.7.0" }, "peerDependencies": { "ink": ">=4.0.0", "react": ">=18.0.0" } }, "sha512-EYEasbEjkqLGyPOUc8hBJZNuC5GvXGMLu0w5gdTNskPc7Izc5vO3tdQEYnzvshucyGCBXc86ig0ujXPMWaQCdA=="],
|
|
66
|
+
|
|
67
|
+
"ink-text-input": ["ink-text-input@5.0.1", "", { "dependencies": { "chalk": "^5.2.0", "type-fest": "^3.6.1" }, "peerDependencies": { "ink": "^4.0.0", "react": "^18.0.0" } }, "sha512-crnsYJalG4EhneOFnr/q+Kzw1RgmXI2KsBaLFE6mpiIKxAtJLUnvygOF2IUKO8z4nwkSkveGRBMd81RoYdRSag=="],
|
|
68
|
+
|
|
69
|
+
"is-ci": ["is-ci@3.0.1", "", { "dependencies": { "ci-info": "^3.2.0" }, "bin": { "is-ci": "bin.js" } }, "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ=="],
|
|
70
|
+
|
|
71
|
+
"is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="],
|
|
72
|
+
|
|
73
|
+
"is-lower-case": ["is-lower-case@2.0.2", "", { "dependencies": { "tslib": "^2.0.3" } }, "sha512-bVcMJy4X5Og6VZfdOZstSexlEy20Sr0k/p/b2IlQJlfdKAQuMpiv5w2Ccxb8sKdRUNAG1PnHVHjFSdRDVS6NlQ=="],
|
|
74
|
+
|
|
75
|
+
"is-upper-case": ["is-upper-case@2.0.2", "", { "dependencies": { "tslib": "^2.0.3" } }, "sha512-44pxmxAvnnAOwBg4tHPnkfvgjPwbc5QIsSstNU+YcJ1ovxVzCWpSGosPJOZh/a1tdl81fbgnLc9LLv+x2ywbPQ=="],
|
|
76
|
+
|
|
77
|
+
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
|
78
|
+
|
|
79
|
+
"lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
|
|
80
|
+
|
|
81
|
+
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
|
|
82
|
+
|
|
83
|
+
"mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
|
|
84
|
+
|
|
85
|
+
"onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
|
|
86
|
+
|
|
87
|
+
"patch-console": ["patch-console@2.0.0", "", {}, "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA=="],
|
|
88
|
+
|
|
89
|
+
"react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="],
|
|
90
|
+
|
|
91
|
+
"react-reconciler": ["react-reconciler@0.29.2", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-zZQqIiYgDCTP/f1N/mAR10nJGrPD2ZR+jDSEsKWJHYC7Cm2wodlwbR3upZRdC3cjIjSlTLNVyO7Iu0Yy7t2AYg=="],
|
|
92
|
+
|
|
93
|
+
"restore-cursor": ["restore-cursor@4.0.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg=="],
|
|
94
|
+
|
|
95
|
+
"scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
|
|
96
|
+
|
|
97
|
+
"signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
|
|
98
|
+
|
|
99
|
+
"slice-ansi": ["slice-ansi@6.0.0", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^4.0.0" } }, "sha512-6bn4hRfkTvDfUoEQYkERg0BVF1D0vrX9HEkMl08uDiNWvVvjylLHvZFZWkDo6wjT8tUctbYl1nCOuE66ZTaUtA=="],
|
|
100
|
+
|
|
101
|
+
"stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="],
|
|
102
|
+
|
|
103
|
+
"string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
|
|
104
|
+
|
|
105
|
+
"strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
|
|
106
|
+
|
|
107
|
+
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
|
108
|
+
|
|
109
|
+
"type-fest": ["type-fest@0.12.0", "", {}, "sha512-53RyidyjvkGpnWPMF9bQgFtWp+Sl8O2Rp13VavmJgfAP9WWG6q6TkrKU8iyJdnwnfgHI6k2hTlgqH4aSdjoTbg=="],
|
|
110
|
+
|
|
111
|
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
112
|
+
|
|
113
|
+
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
|
114
|
+
|
|
115
|
+
"widest-line": ["widest-line@4.0.1", "", { "dependencies": { "string-width": "^5.0.1" } }, "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig=="],
|
|
116
|
+
|
|
117
|
+
"wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
|
|
118
|
+
|
|
119
|
+
"ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
|
|
120
|
+
|
|
121
|
+
"yoga-wasm-web": ["yoga-wasm-web@0.3.3", "", {}, "sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA=="],
|
|
122
|
+
|
|
123
|
+
"cli-truncate/slice-ansi": ["slice-ansi@5.0.0", "", { "dependencies": { "ansi-styles": "^6.0.0", "is-fullwidth-code-point": "^4.0.0" } }, "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ=="],
|
|
124
|
+
|
|
125
|
+
"ink-text-input/type-fest": ["type-fest@3.13.1", "", {}, "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g=="],
|
|
126
|
+
}
|
|
127
|
+
}
|
package/install.sh
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
# Pioneer CLI installer
|
|
3
|
+
# Usage: curl -fsSL https://pioneer.ai/install.sh | sh
|
|
4
|
+
set -e
|
|
5
|
+
|
|
6
|
+
cat <<'EOF'
|
|
7
|
+
██████╗ ██╗ ██████╗ ███╗ ██╗███████╗███████╗██████╗
|
|
8
|
+
██╔══██╗██║██╔═══██╗████╗ ██║██╔════╝██╔════╝██╔══██╗
|
|
9
|
+
██████╔╝██║██║ ██║██╔██╗ ██║█████╗ █████╗ ██████╔╝
|
|
10
|
+
██╔═══╝ ██║██║ ██║██║╚██╗██║██╔══╝ ██╔══╝ ██╔══██╗
|
|
11
|
+
██║ ██║╚██████╔╝██║ ╚████║███████╗███████╗██║ ██║
|
|
12
|
+
╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝╚══════╝╚═╝ ╚═╝
|
|
13
|
+
EOF
|
|
14
|
+
|
|
15
|
+
echo ""
|
|
16
|
+
echo "Installing Pioneer CLI..."
|
|
17
|
+
echo ""
|
|
18
|
+
|
|
19
|
+
# Check for Bun
|
|
20
|
+
if ! command -v bun >/dev/null 2>&1; then
|
|
21
|
+
echo "Bun is required but not installed."
|
|
22
|
+
echo "Install Bun first: curl -fsSL https://bun.sh/install | bash"
|
|
23
|
+
exit 1
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
# Clone or update repo
|
|
27
|
+
INSTALL_DIR="${PIONEER_INSTALL_DIR:-$HOME/.pioneer-cli}"
|
|
28
|
+
|
|
29
|
+
if [ -d "$INSTALL_DIR" ]; then
|
|
30
|
+
echo "Updating existing installation..."
|
|
31
|
+
cd "$INSTALL_DIR"
|
|
32
|
+
git pull --quiet origin main 2>/dev/null || true
|
|
33
|
+
else
|
|
34
|
+
echo "Cloning Pioneer CLI..."
|
|
35
|
+
git clone --quiet --depth 1 https://github.com/fastino-ai/pioneer-cli.git "$INSTALL_DIR" 2>/dev/null || {
|
|
36
|
+
echo "Git clone failed. Creating local installation..."
|
|
37
|
+
mkdir -p "$INSTALL_DIR"
|
|
38
|
+
}
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
# Install dependencies
|
|
42
|
+
cd "$INSTALL_DIR/pioneer" 2>/dev/null || cd "$INSTALL_DIR"
|
|
43
|
+
if [ -f "package.json" ]; then
|
|
44
|
+
echo "Installing dependencies..."
|
|
45
|
+
bun install --silent
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# Create symlink
|
|
49
|
+
BIN_DIR="${BIN_DIR:-$HOME/.local/bin}"
|
|
50
|
+
mkdir -p "$BIN_DIR"
|
|
51
|
+
|
|
52
|
+
cat > "$BIN_DIR/pioneer" << 'SCRIPT'
|
|
53
|
+
#!/usr/bin/env sh
|
|
54
|
+
PIONEER_DIR="${PIONEER_INSTALL_DIR:-$HOME/.pioneer-cli}"
|
|
55
|
+
exec bun run "$PIONEER_DIR/pioneer/src/index.tsx" "$@"
|
|
56
|
+
SCRIPT
|
|
57
|
+
chmod +x "$BIN_DIR/pioneer"
|
|
58
|
+
|
|
59
|
+
echo ""
|
|
60
|
+
echo "Pioneer CLI installed successfully!"
|
|
61
|
+
echo ""
|
|
62
|
+
echo "Add to PATH if needed:"
|
|
63
|
+
echo " export PATH=\"\$HOME/.local/bin:\$PATH\""
|
|
64
|
+
echo ""
|
|
65
|
+
echo "Get started:"
|
|
66
|
+
echo " pioneer --help"
|
|
67
|
+
echo " pioneer auth login"
|
|
68
|
+
echo ""
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fastino-ai/pioneer-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Pioneer CLI - AI training platform",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"bin": {
|
|
10
|
+
"pioneer": "./src/index.tsx"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"start": "bun run src/index.tsx",
|
|
14
|
+
"dev": "bun run --hot src/index.tsx",
|
|
15
|
+
"typecheck": "tsc --noEmit"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"ink": "^4.4.1",
|
|
19
|
+
"ink-spinner": "^5.0.0",
|
|
20
|
+
"ink-text-input": "^5.0.1",
|
|
21
|
+
"react": "^18.3.1"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/node": "^22.0.0",
|
|
25
|
+
"@types/react": "^18.3.0",
|
|
26
|
+
"typescript": "^5.6.3"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"bun": ">=1.1.0"
|
|
30
|
+
}
|
|
31
|
+
}
|
package/src/api.ts
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Felix API client for Pioneer CLI.
|
|
3
|
+
* Calls the real /felix/* endpoints.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { getApiKey, getBaseUrl } from "./config.js";
|
|
7
|
+
|
|
8
|
+
export interface ApiResult<T = unknown> {
|
|
9
|
+
ok: boolean;
|
|
10
|
+
status: number;
|
|
11
|
+
data?: T;
|
|
12
|
+
error?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function request<T = unknown>(
|
|
16
|
+
method: string,
|
|
17
|
+
path: string,
|
|
18
|
+
body?: unknown
|
|
19
|
+
): Promise<ApiResult<T>> {
|
|
20
|
+
const baseUrl = getBaseUrl().replace(/\/$/, "");
|
|
21
|
+
const apiKey = getApiKey();
|
|
22
|
+
const url = `${baseUrl}${path}`;
|
|
23
|
+
|
|
24
|
+
const headers: Record<string, string> = {
|
|
25
|
+
"Content-Type": "application/json",
|
|
26
|
+
"User-Agent": "pioneer-cli/0.1.0",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
if (apiKey) {
|
|
30
|
+
headers["X-API-Key"] = apiKey;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const res = await fetch(url, {
|
|
35
|
+
method,
|
|
36
|
+
headers,
|
|
37
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const text = await res.text();
|
|
41
|
+
let data: T | undefined;
|
|
42
|
+
try {
|
|
43
|
+
data = JSON.parse(text) as T;
|
|
44
|
+
} catch {
|
|
45
|
+
// Not JSON
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!res.ok) {
|
|
49
|
+
return {
|
|
50
|
+
ok: false,
|
|
51
|
+
status: res.status,
|
|
52
|
+
error: data ? JSON.stringify(data) : text || `HTTP ${res.status}`,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return { ok: true, status: res.status, data };
|
|
57
|
+
} catch (err) {
|
|
58
|
+
return {
|
|
59
|
+
ok: false,
|
|
60
|
+
status: 0,
|
|
61
|
+
error: err instanceof Error ? err.message : String(err),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
67
|
+
// Auth - validate API key by calling /felix/datasets
|
|
68
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
export async function validateApiKey(apiKey: string): Promise<ApiResult> {
|
|
71
|
+
const baseUrl = getBaseUrl().replace(/\/$/, "");
|
|
72
|
+
const url = `${baseUrl}/felix/datasets`;
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const res = await fetch(url, {
|
|
76
|
+
method: "GET",
|
|
77
|
+
headers: {
|
|
78
|
+
"Content-Type": "application/json",
|
|
79
|
+
"User-Agent": "pioneer-cli/0.1.0",
|
|
80
|
+
"X-API-Key": apiKey,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
if (res.ok) {
|
|
85
|
+
return { ok: true, status: res.status };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (res.status === 401 || res.status === 403) {
|
|
89
|
+
return { ok: false, status: res.status, error: "Invalid API key" };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const text = await res.text();
|
|
93
|
+
return { ok: false, status: res.status, error: text || `HTTP ${res.status}` };
|
|
94
|
+
} catch (err) {
|
|
95
|
+
return {
|
|
96
|
+
ok: false,
|
|
97
|
+
status: 0,
|
|
98
|
+
error: err instanceof Error ? err.message : String(err),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
104
|
+
// Datasets
|
|
105
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
export interface Dataset {
|
|
108
|
+
id: string;
|
|
109
|
+
user_id: string;
|
|
110
|
+
dataset_name: string;
|
|
111
|
+
dataset_path: string;
|
|
112
|
+
dataset_type: string;
|
|
113
|
+
size?: number;
|
|
114
|
+
sample_size?: number;
|
|
115
|
+
created_at: string;
|
|
116
|
+
updated_at: string;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface DatasetListResponse {
|
|
120
|
+
success: boolean;
|
|
121
|
+
datasets: Dataset[];
|
|
122
|
+
count: number;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export async function listDatasets(): Promise<ApiResult<DatasetListResponse>> {
|
|
126
|
+
return request<DatasetListResponse>("GET", "/felix/datasets");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export async function getDataset(datasetId: string): Promise<ApiResult<Dataset>> {
|
|
130
|
+
return request<Dataset>("GET", `/felix/datasets/${datasetId}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface DatasetCreateRequest {
|
|
134
|
+
dataset_name: string;
|
|
135
|
+
dataset_path: string;
|
|
136
|
+
dataset_type?: "classification" | "ner" | "custom";
|
|
137
|
+
size?: number;
|
|
138
|
+
sample_size?: number;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export async function createDataset(
|
|
142
|
+
req: DatasetCreateRequest
|
|
143
|
+
): Promise<ApiResult<Dataset>> {
|
|
144
|
+
return request<Dataset>("POST", "/felix/datasets", req);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export async function deleteDataset(datasetId: string): Promise<ApiResult> {
|
|
148
|
+
return request("DELETE", `/felix/datasets/${datasetId}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface DatasetDownloadResponse {
|
|
152
|
+
download_url: string;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export async function downloadDataset(
|
|
156
|
+
datasetId: string
|
|
157
|
+
): Promise<ApiResult<DatasetDownloadResponse>> {
|
|
158
|
+
return request<DatasetDownloadResponse>(
|
|
159
|
+
"GET",
|
|
160
|
+
`/felix/datasets/${datasetId}/download`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export interface DatasetAnalysisRequest {
|
|
165
|
+
dataset_id: string;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export interface DatasetAnalysisResponse {
|
|
169
|
+
success: boolean;
|
|
170
|
+
analysis: unknown;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export async function analyzeDataset(
|
|
174
|
+
datasetId: string
|
|
175
|
+
): Promise<ApiResult<DatasetAnalysisResponse>> {
|
|
176
|
+
return request<DatasetAnalysisResponse>("POST", "/felix/dataset/analyze", {
|
|
177
|
+
dataset_id: datasetId,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
182
|
+
// Training Jobs
|
|
183
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
184
|
+
|
|
185
|
+
export interface TrainingJob {
|
|
186
|
+
id: string;
|
|
187
|
+
user_id: string;
|
|
188
|
+
model_name?: string;
|
|
189
|
+
dataset_ids: string[];
|
|
190
|
+
train_dataset_paths: string[];
|
|
191
|
+
base_model: string;
|
|
192
|
+
validation_data_percentage: number;
|
|
193
|
+
nr_epochs: number;
|
|
194
|
+
learning_rate: number;
|
|
195
|
+
batch_size: number;
|
|
196
|
+
trained_model_path?: string;
|
|
197
|
+
sagemaker_job_name?: string;
|
|
198
|
+
status: string;
|
|
199
|
+
error_message?: string;
|
|
200
|
+
created_at: string;
|
|
201
|
+
updated_at: string;
|
|
202
|
+
started_at?: string;
|
|
203
|
+
completed_at?: string;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export interface TrainingJobListResponse {
|
|
207
|
+
success: boolean;
|
|
208
|
+
training_jobs: TrainingJob[];
|
|
209
|
+
count: number;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export async function listJobs(): Promise<ApiResult<TrainingJobListResponse>> {
|
|
213
|
+
return request<TrainingJobListResponse>("GET", "/felix/training-jobs");
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export async function getJob(jobId: string): Promise<ApiResult<TrainingJob>> {
|
|
217
|
+
return request<TrainingJob>("GET", `/felix/training-jobs/${jobId}`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export interface TrainingJobCreateRequest {
|
|
221
|
+
model_name: string;
|
|
222
|
+
dataset_ids: string[];
|
|
223
|
+
base_model?: string;
|
|
224
|
+
validation_data_percentage?: number;
|
|
225
|
+
nr_epochs?: number;
|
|
226
|
+
learning_rate?: number;
|
|
227
|
+
batch_size?: number;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export async function createJob(
|
|
231
|
+
req: TrainingJobCreateRequest
|
|
232
|
+
): Promise<ApiResult<TrainingJob>> {
|
|
233
|
+
return request<TrainingJob>("POST", "/felix/training-jobs", req);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export interface TrainingLog {
|
|
237
|
+
id: string;
|
|
238
|
+
job_id: string;
|
|
239
|
+
epoch: number;
|
|
240
|
+
step?: number;
|
|
241
|
+
training_loss?: number;
|
|
242
|
+
validation_loss?: number;
|
|
243
|
+
accuracy?: number;
|
|
244
|
+
learning_rate?: number;
|
|
245
|
+
gpu_memory_used?: number;
|
|
246
|
+
gpu_memory_total?: number;
|
|
247
|
+
created_at: string;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export interface TrainingLogsResponse {
|
|
251
|
+
success: boolean;
|
|
252
|
+
logs: TrainingLog[];
|
|
253
|
+
count: number;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export async function getJobLogs(
|
|
257
|
+
jobId: string
|
|
258
|
+
): Promise<ApiResult<TrainingLogsResponse>> {
|
|
259
|
+
return request<TrainingLogsResponse>("GET", `/felix/training-jobs/${jobId}/logs`);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
263
|
+
// Models
|
|
264
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
265
|
+
|
|
266
|
+
export interface DeployedModel {
|
|
267
|
+
job_id: string;
|
|
268
|
+
model_name?: string;
|
|
269
|
+
endpoint_name?: string;
|
|
270
|
+
status: string;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export interface DeployedModelsListResponse {
|
|
274
|
+
success: boolean;
|
|
275
|
+
models: DeployedModel[];
|
|
276
|
+
count: number;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export async function listModels(): Promise<ApiResult<DeployedModelsListResponse>> {
|
|
280
|
+
return request<DeployedModelsListResponse>("GET", "/felix/models");
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export async function deleteModel(jobId: string): Promise<ApiResult> {
|
|
284
|
+
return request("DELETE", `/felix/models/${jobId}`);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export interface TrainedModel {
|
|
288
|
+
job_id: string;
|
|
289
|
+
model_name?: string;
|
|
290
|
+
trained_model_path?: string;
|
|
291
|
+
status: string;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export interface TrainedModelsListResponse {
|
|
295
|
+
success: boolean;
|
|
296
|
+
models: TrainedModel[];
|
|
297
|
+
count: number;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export async function listTrainedModels(): Promise<
|
|
301
|
+
ApiResult<TrainedModelsListResponse>
|
|
302
|
+
> {
|
|
303
|
+
return request<TrainedModelsListResponse>("GET", "/felix/trained-models");
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export interface ModelDownloadResponse {
|
|
307
|
+
download_url: string;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export async function downloadModel(
|
|
311
|
+
jobId: string
|
|
312
|
+
): Promise<ApiResult<ModelDownloadResponse>> {
|
|
313
|
+
return request<ModelDownloadResponse>(
|
|
314
|
+
"GET",
|
|
315
|
+
`/felix/training/jobs/${jobId}/download`
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
320
|
+
// Evaluations
|
|
321
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
322
|
+
|
|
323
|
+
export interface Evaluation {
|
|
324
|
+
id: string;
|
|
325
|
+
user_id: string;
|
|
326
|
+
dataset_id: string;
|
|
327
|
+
model_id?: string;
|
|
328
|
+
status: string;
|
|
329
|
+
results?: unknown;
|
|
330
|
+
created_at: string;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export async function getEvaluation(
|
|
334
|
+
evalId: string
|
|
335
|
+
): Promise<ApiResult<Evaluation>> {
|
|
336
|
+
return request<Evaluation>("GET", `/felix/evaluations/${evalId}`);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export interface EvaluationCreateRequest {
|
|
340
|
+
dataset_id: string;
|
|
341
|
+
model_id?: string;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export async function createEvaluation(
|
|
345
|
+
req: EvaluationCreateRequest
|
|
346
|
+
): Promise<ApiResult<Evaluation>> {
|
|
347
|
+
return request<Evaluation>("POST", "/felix/evaluations", req);
|
|
348
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration management for Pioneer CLI.
|
|
3
|
+
* Stores API key and base URL in ~/.pioneer/config.json
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from "fs";
|
|
7
|
+
import os from "os";
|
|
8
|
+
import path from "path";
|
|
9
|
+
|
|
10
|
+
export interface Config {
|
|
11
|
+
apiKey?: string;
|
|
12
|
+
baseUrl?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const CONFIG_DIR = path.join(os.homedir(), ".pioneer");
|
|
16
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
17
|
+
|
|
18
|
+
export const DEFAULT_BASE_URL =
|
|
19
|
+
process.env.PIONEER_API_URL ?? "http://localhost:5001";
|
|
20
|
+
|
|
21
|
+
function ensureConfigDir(): void {
|
|
22
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
23
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function loadConfig(): Config {
|
|
28
|
+
try {
|
|
29
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
30
|
+
const raw = fs.readFileSync(CONFIG_FILE, "utf-8");
|
|
31
|
+
return JSON.parse(raw) as Config;
|
|
32
|
+
}
|
|
33
|
+
} catch {
|
|
34
|
+
// Ignore parse errors
|
|
35
|
+
}
|
|
36
|
+
return {};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function saveConfig(config: Config): void {
|
|
40
|
+
ensureConfigDir();
|
|
41
|
+
const existing = loadConfig();
|
|
42
|
+
const merged = { ...existing, ...config };
|
|
43
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function clearApiKey(): void {
|
|
47
|
+
const config = loadConfig();
|
|
48
|
+
delete config.apiKey;
|
|
49
|
+
ensureConfigDir();
|
|
50
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function getApiKey(): string | undefined {
|
|
54
|
+
// Environment variable takes precedence
|
|
55
|
+
if (process.env.PIONEER_API_KEY) {
|
|
56
|
+
return process.env.PIONEER_API_KEY;
|
|
57
|
+
}
|
|
58
|
+
return loadConfig().apiKey;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function getBaseUrl(): string {
|
|
62
|
+
const config = loadConfig();
|
|
63
|
+
return config.baseUrl ?? DEFAULT_BASE_URL;
|
|
64
|
+
}
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Pioneer CLI - main entry point.
|
|
4
|
+
* Uses Ink (React) for terminal UI.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, { useState, useEffect } from "react";
|
|
8
|
+
import { render, Box, Text, useApp, useInput } from "ink";
|
|
9
|
+
import Spinner from "ink-spinner";
|
|
10
|
+
import TextInput from "ink-text-input";
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
getApiKey,
|
|
14
|
+
getBaseUrl,
|
|
15
|
+
saveConfig,
|
|
16
|
+
clearApiKey,
|
|
17
|
+
} from "./config.js";
|
|
18
|
+
import * as api from "./api.js";
|
|
19
|
+
|
|
20
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
21
|
+
// ASCII Banner
|
|
22
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
const BANNER = `
|
|
25
|
+
██████╗ ██╗ ██████╗ ███╗ ██╗███████╗███████╗██████╗
|
|
26
|
+
██╔══██╗██║██╔═══██╗████╗ ██║██╔════╝██╔════╝██╔══██╗
|
|
27
|
+
██████╔╝██║██║ ██║██╔██╗ ██║█████╗ █████╗ ██████╔╝
|
|
28
|
+
██╔═══╝ ██║██║ ██║██║╚██╗██║██╔══╝ ██╔══╝ ██╔══██╗
|
|
29
|
+
██║ ██║╚██████╔╝██║ ╚████║███████╗███████╗██║ ██║
|
|
30
|
+
╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝╚══════╝╚═╝ ╚═╝
|
|
31
|
+
`.trim();
|
|
32
|
+
|
|
33
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
34
|
+
// Helpers
|
|
35
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
function formatJson(data: unknown): string {
|
|
38
|
+
return JSON.stringify(data, null, 2);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function parseArgs(argv: string[]): { command: string[]; flags: Record<string, string> } {
|
|
42
|
+
const command: string[] = [];
|
|
43
|
+
const flags: Record<string, string> = {};
|
|
44
|
+
|
|
45
|
+
for (let i = 0; i < argv.length; i++) {
|
|
46
|
+
const arg = argv[i];
|
|
47
|
+
if (arg.startsWith("--")) {
|
|
48
|
+
const key = arg.slice(2);
|
|
49
|
+
const next = argv[i + 1];
|
|
50
|
+
if (next && !next.startsWith("--")) {
|
|
51
|
+
flags[key] = next;
|
|
52
|
+
i++;
|
|
53
|
+
} else {
|
|
54
|
+
flags[key] = "true";
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
command.push(arg);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return { command, flags };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
65
|
+
// Components
|
|
66
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
const Loading: React.FC<{ message?: string }> = ({ message }) => (
|
|
69
|
+
<Box>
|
|
70
|
+
<Text color="yellow">
|
|
71
|
+
<Spinner type="dots" />
|
|
72
|
+
</Text>
|
|
73
|
+
<Text> {message ?? "Loading..."}</Text>
|
|
74
|
+
</Box>
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const ErrorMessage: React.FC<{ error: string }> = ({ error }) => (
|
|
78
|
+
<Box>
|
|
79
|
+
<Text color="red">Error: {error}</Text>
|
|
80
|
+
</Box>
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const Success: React.FC<{ message: string }> = ({ message }) => (
|
|
84
|
+
<Box>
|
|
85
|
+
<Text color="green">✓ {message}</Text>
|
|
86
|
+
</Box>
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const JsonOutput: React.FC<{ data: unknown }> = ({ data }) => (
|
|
90
|
+
<Box>
|
|
91
|
+
<Text>{formatJson(data)}</Text>
|
|
92
|
+
</Box>
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
96
|
+
// Auth Login Component
|
|
97
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
const AuthLogin: React.FC = () => {
|
|
100
|
+
const { exit } = useApp();
|
|
101
|
+
const [apiKey, setApiKey] = useState("");
|
|
102
|
+
const [state, setState] = useState<"input" | "validating" | "done" | "error">("input");
|
|
103
|
+
const [error, setError] = useState("");
|
|
104
|
+
|
|
105
|
+
const handleSubmit = async (value: string) => {
|
|
106
|
+
if (!value.trim()) {
|
|
107
|
+
setError("API key cannot be empty");
|
|
108
|
+
setState("error");
|
|
109
|
+
setTimeout(() => exit(), 1500);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
setState("validating");
|
|
114
|
+
const result = await api.validateApiKey(value.trim());
|
|
115
|
+
|
|
116
|
+
if (result.ok) {
|
|
117
|
+
saveConfig({ apiKey: value.trim() });
|
|
118
|
+
setState("done");
|
|
119
|
+
} else if (result.status === 401 || result.status === 403) {
|
|
120
|
+
setError(`Invalid API key (HTTP ${result.status}). Please check and try again.`);
|
|
121
|
+
setState("error");
|
|
122
|
+
} else {
|
|
123
|
+
setError(`Connection failed (HTTP ${result.status}): ${result.error ?? "Unknown error"}`);
|
|
124
|
+
setState("error");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
setTimeout(() => exit(), 1500);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
if (state === "validating") {
|
|
131
|
+
return <Loading message="Validating API key..." />;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (state === "done") {
|
|
135
|
+
return <Success message="Logged in successfully. API key saved." />;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (state === "error") {
|
|
139
|
+
return <ErrorMessage error={error} />;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<Box flexDirection="column">
|
|
144
|
+
<Text>Enter your Pioneer API key:</Text>
|
|
145
|
+
<Box>
|
|
146
|
+
<Text color="cyan">> </Text>
|
|
147
|
+
<TextInput value={apiKey} onChange={setApiKey} onSubmit={handleSubmit} mask="*" />
|
|
148
|
+
</Box>
|
|
149
|
+
</Box>
|
|
150
|
+
);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
154
|
+
// Auth Logout Component
|
|
155
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
156
|
+
|
|
157
|
+
const AuthLogout: React.FC = () => {
|
|
158
|
+
const { exit } = useApp();
|
|
159
|
+
|
|
160
|
+
useEffect(() => {
|
|
161
|
+
clearApiKey();
|
|
162
|
+
setTimeout(() => exit(), 500);
|
|
163
|
+
}, [exit]);
|
|
164
|
+
|
|
165
|
+
return <Success message="Logged out. API key cleared." />;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
169
|
+
// Auth Status Component
|
|
170
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
171
|
+
|
|
172
|
+
const AuthStatus: React.FC = () => {
|
|
173
|
+
const { exit } = useApp();
|
|
174
|
+
const apiKey = getApiKey();
|
|
175
|
+
const baseUrl = getBaseUrl();
|
|
176
|
+
|
|
177
|
+
useEffect(() => {
|
|
178
|
+
setTimeout(() => exit(), 500);
|
|
179
|
+
}, [exit]);
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<Box flexDirection="column">
|
|
183
|
+
<Text>Base URL: {baseUrl}</Text>
|
|
184
|
+
<Text>
|
|
185
|
+
Logged in:{" "}
|
|
186
|
+
<Text color={apiKey ? "green" : "red"}>{apiKey ? "Yes" : "No"}</Text>
|
|
187
|
+
</Text>
|
|
188
|
+
</Box>
|
|
189
|
+
);
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
193
|
+
// Generic API Command Component
|
|
194
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
195
|
+
|
|
196
|
+
interface ApiCommandProps<T> {
|
|
197
|
+
action: () => Promise<api.ApiResult<T>>;
|
|
198
|
+
successMessage?: string;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function ApiCommand<T>({ action, successMessage }: ApiCommandProps<T>) {
|
|
202
|
+
const { exit } = useApp();
|
|
203
|
+
const [state, setState] = useState<"loading" | "done" | "error">("loading");
|
|
204
|
+
const [data, setData] = useState<T | null>(null);
|
|
205
|
+
const [error, setError] = useState("");
|
|
206
|
+
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
(async () => {
|
|
209
|
+
const result = await action();
|
|
210
|
+
if (result.ok) {
|
|
211
|
+
setData(result.data ?? null);
|
|
212
|
+
setState("done");
|
|
213
|
+
} else {
|
|
214
|
+
setError(result.error ?? "Unknown error");
|
|
215
|
+
setState("error");
|
|
216
|
+
}
|
|
217
|
+
setTimeout(() => exit(), 500);
|
|
218
|
+
})();
|
|
219
|
+
}, [action, exit]);
|
|
220
|
+
|
|
221
|
+
if (state === "loading") {
|
|
222
|
+
return <Loading />;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (state === "error") {
|
|
226
|
+
return <ErrorMessage error={error} />;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return (
|
|
230
|
+
<Box flexDirection="column">
|
|
231
|
+
{successMessage && <Success message={successMessage} />}
|
|
232
|
+
{data && <JsonOutput data={data} />}
|
|
233
|
+
</Box>
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
238
|
+
// Help Component
|
|
239
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
240
|
+
|
|
241
|
+
const Help: React.FC = () => {
|
|
242
|
+
const { exit } = useApp();
|
|
243
|
+
|
|
244
|
+
useEffect(() => {
|
|
245
|
+
setTimeout(() => exit(), 100);
|
|
246
|
+
}, [exit]);
|
|
247
|
+
|
|
248
|
+
return (
|
|
249
|
+
<Box flexDirection="column">
|
|
250
|
+
<Text color="cyan">{BANNER}</Text>
|
|
251
|
+
<Text> </Text>
|
|
252
|
+
<Text bold>Usage:</Text>
|
|
253
|
+
<Text> pioneer {"<command>"} {"[options]"}</Text>
|
|
254
|
+
<Text> </Text>
|
|
255
|
+
<Text bold>Auth Commands:</Text>
|
|
256
|
+
<Text> auth login Login with API key</Text>
|
|
257
|
+
<Text> auth logout Clear stored API key</Text>
|
|
258
|
+
<Text> auth status Show auth status</Text>
|
|
259
|
+
<Text> </Text>
|
|
260
|
+
<Text bold>Dataset Commands:</Text>
|
|
261
|
+
<Text> dataset list List all datasets</Text>
|
|
262
|
+
<Text> dataset get {"<id>"} Get dataset details</Text>
|
|
263
|
+
<Text> dataset delete {"<id>"} Delete a dataset</Text>
|
|
264
|
+
<Text> dataset download {"<id>"} Get download URL</Text>
|
|
265
|
+
<Text> dataset analyze {"<id>"} Analyze a dataset</Text>
|
|
266
|
+
<Text> </Text>
|
|
267
|
+
<Text bold>Job Commands:</Text>
|
|
268
|
+
<Text> job list List training jobs</Text>
|
|
269
|
+
<Text> job get {"<id>"} Get job details</Text>
|
|
270
|
+
<Text> job logs {"<id>"} Get job logs</Text>
|
|
271
|
+
<Text> job create Create training job</Text>
|
|
272
|
+
<Text> --model-name {"<name>"} Model name (required)</Text>
|
|
273
|
+
<Text> --dataset-ids {"<ids>"} Comma-separated dataset IDs</Text>
|
|
274
|
+
<Text> --base-model {"<model>"} Base model (default: fastino/gliner2-base-v1)</Text>
|
|
275
|
+
<Text> --epochs {"<n>"} Number of epochs (default: 5)</Text>
|
|
276
|
+
<Text> </Text>
|
|
277
|
+
<Text bold>Model Commands:</Text>
|
|
278
|
+
<Text> model list List deployed models</Text>
|
|
279
|
+
<Text> model trained List trained models</Text>
|
|
280
|
+
<Text> model delete {"<id>"} Undeploy a model</Text>
|
|
281
|
+
<Text> model download {"<id>"} Get model download URL</Text>
|
|
282
|
+
<Text> </Text>
|
|
283
|
+
<Text bold>Options:</Text>
|
|
284
|
+
<Text> --help Show this help</Text>
|
|
285
|
+
<Text> </Text>
|
|
286
|
+
<Text dimColor>Environment:</Text>
|
|
287
|
+
<Text dimColor> PIONEER_API_URL API base URL (default: http://localhost:5001)</Text>
|
|
288
|
+
<Text dimColor> PIONEER_API_KEY API key (overrides saved key)</Text>
|
|
289
|
+
</Box>
|
|
290
|
+
);
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
294
|
+
// Main Router
|
|
295
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
296
|
+
|
|
297
|
+
interface AppProps {
|
|
298
|
+
command: string[];
|
|
299
|
+
flags: Record<string, string>;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const App: React.FC<AppProps> = ({ command, flags }) => {
|
|
303
|
+
const [group, action, ...rest] = command;
|
|
304
|
+
|
|
305
|
+
// Help
|
|
306
|
+
if (!group || flags.help === "true") {
|
|
307
|
+
return <Help />;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Auth commands
|
|
311
|
+
if (group === "auth") {
|
|
312
|
+
if (action === "login") return <AuthLogin />;
|
|
313
|
+
if (action === "logout") return <AuthLogout />;
|
|
314
|
+
if (action === "status") return <AuthStatus />;
|
|
315
|
+
return <Help />;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Dataset commands
|
|
319
|
+
if (group === "dataset") {
|
|
320
|
+
if (action === "list") {
|
|
321
|
+
return <ApiCommand action={api.listDatasets} />;
|
|
322
|
+
}
|
|
323
|
+
if (action === "get" && rest[0]) {
|
|
324
|
+
return <ApiCommand action={() => api.getDataset(rest[0])} />;
|
|
325
|
+
}
|
|
326
|
+
if (action === "delete" && rest[0]) {
|
|
327
|
+
return (
|
|
328
|
+
<ApiCommand
|
|
329
|
+
action={() => api.deleteDataset(rest[0])}
|
|
330
|
+
successMessage={`Dataset ${rest[0]} deleted`}
|
|
331
|
+
/>
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
if (action === "download" && rest[0]) {
|
|
335
|
+
return <ApiCommand action={() => api.downloadDataset(rest[0])} />;
|
|
336
|
+
}
|
|
337
|
+
if (action === "analyze" && rest[0]) {
|
|
338
|
+
return <ApiCommand action={() => api.analyzeDataset(rest[0])} />;
|
|
339
|
+
}
|
|
340
|
+
return <Help />;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Job commands
|
|
344
|
+
if (group === "job") {
|
|
345
|
+
if (action === "list") {
|
|
346
|
+
return <ApiCommand action={api.listJobs} />;
|
|
347
|
+
}
|
|
348
|
+
if (action === "get" && rest[0]) {
|
|
349
|
+
return <ApiCommand action={() => api.getJob(rest[0])} />;
|
|
350
|
+
}
|
|
351
|
+
if (action === "logs" && rest[0]) {
|
|
352
|
+
return <ApiCommand action={() => api.getJobLogs(rest[0])} />;
|
|
353
|
+
}
|
|
354
|
+
if (action === "create") {
|
|
355
|
+
const modelName = flags["model-name"];
|
|
356
|
+
const datasetIds = flags["dataset-ids"]?.split(",") ?? [];
|
|
357
|
+
const baseModel = flags["base-model"];
|
|
358
|
+
const epochs = flags["epochs"] ? parseInt(flags["epochs"], 10) : undefined;
|
|
359
|
+
|
|
360
|
+
if (!modelName || datasetIds.length === 0) {
|
|
361
|
+
return <ErrorMessage error="--model-name and --dataset-ids are required" />;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return (
|
|
365
|
+
<ApiCommand
|
|
366
|
+
action={() =>
|
|
367
|
+
api.createJob({
|
|
368
|
+
model_name: modelName,
|
|
369
|
+
dataset_ids: datasetIds,
|
|
370
|
+
base_model: baseModel,
|
|
371
|
+
nr_epochs: epochs,
|
|
372
|
+
})
|
|
373
|
+
}
|
|
374
|
+
successMessage="Training job created"
|
|
375
|
+
/>
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
return <Help />;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Model commands
|
|
382
|
+
if (group === "model") {
|
|
383
|
+
if (action === "list") {
|
|
384
|
+
return <ApiCommand action={api.listModels} />;
|
|
385
|
+
}
|
|
386
|
+
if (action === "trained") {
|
|
387
|
+
return <ApiCommand action={api.listTrainedModels} />;
|
|
388
|
+
}
|
|
389
|
+
if (action === "delete" && rest[0]) {
|
|
390
|
+
return (
|
|
391
|
+
<ApiCommand
|
|
392
|
+
action={() => api.deleteModel(rest[0])}
|
|
393
|
+
successMessage={`Model ${rest[0]} deleted`}
|
|
394
|
+
/>
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
if (action === "download" && rest[0]) {
|
|
398
|
+
return <ApiCommand action={() => api.downloadModel(rest[0])} />;
|
|
399
|
+
}
|
|
400
|
+
return <Help />;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return <Help />;
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
407
|
+
// Entry point
|
|
408
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
409
|
+
|
|
410
|
+
function main() {
|
|
411
|
+
const argv = process.argv.slice(2);
|
|
412
|
+
const { command, flags } = parseArgs(argv);
|
|
413
|
+
|
|
414
|
+
render(<App command={command} flags={flags} />);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
main();
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"jsx": "react-jsx",
|
|
7
|
+
"jsxImportSource": "react",
|
|
8
|
+
"allowJs": false,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"skipLibCheck": true,
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"types": [
|
|
15
|
+
"node"
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
"include": [
|
|
19
|
+
"src"
|
|
20
|
+
]
|
|
21
|
+
}
|