@archeagle087/costcrew 0.4.1 → 0.4.3
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 +1 -0
- package/bin.js +10 -6
- package/package.json +8 -5
- package/.github/workflows/ci.yml +0 -21
- package/.github/workflows/npm-release.yml +0 -123
- package/.github/workflows/prices-monthly.yml +0 -58
- package/.github/workflows/release.yml +0 -45
- package/Cargo.lock +0 -980
- package/Cargo.toml +0 -22
- package/assets/advise.png +0 -0
- package/assets/board.png +0 -0
- package/assets/catalog.png +0 -0
- package/assets/demo.png +0 -0
- package/assets/help.png +0 -0
- package/assets/mascot.png +0 -0
- package/assets/prices.png +0 -0
- package/assets/scan.png +0 -0
- package/assets/spend.png +0 -0
- package/assets/ui.png +0 -0
- package/data/prices.json +0 -1
- package/dist/costcrew-linux-x86_64.tar.gz +0 -0
- package/dist/costcrew-macos-aarch64.tar.gz +0 -0
- package/dist/costcrew-macos-x86_64.tar.gz +0 -0
- package/dist/costcrew-windows-x86_64.zip +0 -0
- package/install.ps1 +0 -38
- package/install.sh +0 -56
- package/packaging/npm/bin.js +0 -46
- package/packaging/npm/package.json +0 -20
- package/packaging/winget/0.4.0/Arch087146.costcrew.installer.yaml +0 -15
- package/packaging/winget/0.4.0/Arch087146.costcrew.locale.en-US.yaml +0 -28
- package/packaging/winget/0.4.0/Arch087146.costcrew.yaml +0 -5
- package/packaging/winget/README.md +0 -31
- package/scripts/render_screen.py +0 -221
- package/src/advise.rs +0 -154
- package/src/args.rs +0 -239
- package/src/board.rs +0 -227
- package/src/catalog.rs +0 -136
- package/src/demo.rs +0 -79
- package/src/fetch.rs +0 -155
- package/src/main.rs +0 -371
- package/src/price.rs +0 -218
- package/src/prices.rs +0 -182
- package/src/report.rs +0 -194
- package/src/scan.rs +0 -593
- package/src/spend.rs +0 -452
- package/src/ui.rs +0 -367
- package/work/costcrew-darwin-arm64/costcrew +0 -0
- package/work/costcrew-darwin-arm64/package.json +0 -9
- package/work/costcrew-darwin-x64/costcrew +0 -0
- package/work/costcrew-darwin-x64/package.json +0 -9
- package/work/costcrew-linux-x64/costcrew +0 -0
- package/work/costcrew-linux-x64/package.json +0 -9
- package/work/costcrew-win32-x64/costcrew.exe +0 -0
- package/work/costcrew-win32-x64/package.json +0 -9
package/src/ui.rs
DELETED
|
@@ -1,367 +0,0 @@
|
|
|
1
|
-
//! `costcrew` interactive mode — the fancy bit.
|
|
2
|
-
//!
|
|
3
|
-
//! Bare `costcrew` opens this instead of dumping a table: the banner builds
|
|
4
|
-
//! itself in, a spinner runs while the bundled database loads, menu items
|
|
5
|
-
//! cascade in — then it's arrow keys. When you pick an action the logo
|
|
6
|
-
//! clears away and the real output takes the screen; any key returns you to
|
|
7
|
-
//! the menu.
|
|
8
|
-
//!
|
|
9
|
-
//! Rendering is plain ANSI (same style constants as the rest of the app);
|
|
10
|
-
//! crossterm is used only to capture key presses. Anti-flash notes:
|
|
11
|
-
//! - raw mode is enabled ONCE for the whole menu loop (toggling it per
|
|
12
|
-
//! keypress is what caused the flicker), and only dropped while a view
|
|
13
|
-
//! runs with normal `println!` output;
|
|
14
|
-
//! - every menu frame is composed into one String and written in a single
|
|
15
|
-
//! `write_all` — partial repaints are what read as "flashing";
|
|
16
|
-
//! - frames use `\r\n` line endings, required while raw mode is on.
|
|
17
|
-
|
|
18
|
-
use crate::args::{Args, Cmd};
|
|
19
|
-
use crate::price::PriceDB;
|
|
20
|
-
use crate::report::{BOLD, CYAN, DIM, RESET, YELLOW};
|
|
21
|
-
use crossterm::event::{read, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
|
|
22
|
-
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
|
23
|
-
use std::io::Write;
|
|
24
|
-
|
|
25
|
-
/// ANSI 256-color cyan→blue gradient rows for the banner
|
|
26
|
-
const GRAD: [&str; 5] = [
|
|
27
|
-
"\x1b[38;5;51m",
|
|
28
|
-
"\x1b[38;5;45m",
|
|
29
|
-
"\x1b[38;5;39m",
|
|
30
|
-
"\x1b[38;5;33m",
|
|
31
|
-
"\x1b[38;5;27m",
|
|
32
|
-
];
|
|
33
|
-
|
|
34
|
-
/// "COST / CREW" in solid blocks (double-line box glyphs render as tofu in
|
|
35
|
-
/// plain conhost; solid █ renders everywhere)
|
|
36
|
-
#[rustfmt::skip]
|
|
37
|
-
const BANNER: &[&str] = &[
|
|
38
|
-
"█████ █████ █████ █████",
|
|
39
|
-
"█ █ █ █ █ ",
|
|
40
|
-
"█ █ █ █████ █ ",
|
|
41
|
-
"█ █ █ █ █ ",
|
|
42
|
-
"█████ █████ █████ █ ",
|
|
43
|
-
"█████ █████ █████ █ █",
|
|
44
|
-
"█ █ █ █ █ █",
|
|
45
|
-
"█ █████ ████ █ █ █",
|
|
46
|
-
"█ █ █ █ █ █ █",
|
|
47
|
-
"█████ █ █ █████ ██ ██",
|
|
48
|
-
];
|
|
49
|
-
|
|
50
|
-
/// the costcrew mascot: a coin with a dollar-sign, mid-spin.
|
|
51
|
-
/// Only block glyphs + ASCII, so it renders in every terminal font.
|
|
52
|
-
#[rustfmt::skip]
|
|
53
|
-
const MASCOT_FACE: &[&str] = &[
|
|
54
|
-
" ▄▄▄▄▄ ",
|
|
55
|
-
"█ $ █",
|
|
56
|
-
" ▀▀▀▀▀ ",
|
|
57
|
-
];
|
|
58
|
-
|
|
59
|
-
#[rustfmt::skip]
|
|
60
|
-
const MASCOT_THREEQUARTER: &[&str] = &[
|
|
61
|
-
" ▄▄▄▄ ",
|
|
62
|
-
"▌ $ ▐",
|
|
63
|
-
" ▀▀▀▀ ",
|
|
64
|
-
];
|
|
65
|
-
|
|
66
|
-
#[rustfmt::skip]
|
|
67
|
-
const MASCOT_EDGE: &[&str] = &[
|
|
68
|
-
" ▄▄ ",
|
|
69
|
-
"▌$▐",
|
|
70
|
-
" ▀▀ ",
|
|
71
|
-
];
|
|
72
|
-
|
|
73
|
-
struct Item {
|
|
74
|
-
name: &'static str,
|
|
75
|
-
hint: &'static str,
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const ITEMS: [Item; 10] = [
|
|
79
|
-
Item {
|
|
80
|
-
name: "price database",
|
|
81
|
-
hint: "every model, bundled, offline — browse & filter",
|
|
82
|
-
},
|
|
83
|
-
Item {
|
|
84
|
-
name: "your spend",
|
|
85
|
-
hint: "what YOU pay for AI: monthly totals, per service",
|
|
86
|
-
},
|
|
87
|
-
Item {
|
|
88
|
-
name: "add a payment",
|
|
89
|
-
hint: "record what you just paid (sub, credits, GPU)",
|
|
90
|
-
},
|
|
91
|
-
Item {
|
|
92
|
-
name: "leaderboard",
|
|
93
|
-
hint: "arena Elo ranked with prices — elo-per-dollar",
|
|
94
|
-
},
|
|
95
|
-
Item {
|
|
96
|
-
name: "agent catalog",
|
|
97
|
-
hint: "which agent ships with which models",
|
|
98
|
-
},
|
|
99
|
-
Item {
|
|
100
|
-
name: "openrouter actuals",
|
|
101
|
-
hint: "provider-reported spend from your API key",
|
|
102
|
-
},
|
|
103
|
-
Item {
|
|
104
|
-
name: "refresh prices",
|
|
105
|
-
hint: "update the database from OpenRouter live",
|
|
106
|
-
},
|
|
107
|
-
Item {
|
|
108
|
-
name: "scan local logs",
|
|
109
|
-
hint: "optional: estimate per-token from agent logs",
|
|
110
|
-
},
|
|
111
|
-
Item {
|
|
112
|
-
name: "cheaper-model advice",
|
|
113
|
-
hint: "could you have used a cheaper model?",
|
|
114
|
-
},
|
|
115
|
-
Item {
|
|
116
|
-
name: "demo",
|
|
117
|
-
hint: "sample report, no data needed",
|
|
118
|
-
},
|
|
119
|
-
];
|
|
120
|
-
|
|
121
|
-
const FOOTER: &str = "↑/↓ move · Enter select · 1-9,0 jump · q quit";
|
|
122
|
-
|
|
123
|
-
const CLEAR: &str = "\x1b[2J\x1b[H\x1b[?25l";
|
|
124
|
-
|
|
125
|
-
pub fn run_app() {
|
|
126
|
-
let mut out = std::io::stdout();
|
|
127
|
-
let _ = out.write_all(CLEAR.as_bytes());
|
|
128
|
-
|
|
129
|
-
// --- the opener: the coin mascot spins while the database loads ---
|
|
130
|
-
let mut db = PriceDB::builtin();
|
|
131
|
-
crate::fetch::cached_into_db(&mut db);
|
|
132
|
-
let n = db.entries().len();
|
|
133
|
-
let spin = [
|
|
134
|
-
MASCOT_FACE,
|
|
135
|
-
MASCOT_THREEQUARTER,
|
|
136
|
-
MASCOT_EDGE,
|
|
137
|
-
MASCOT_THREEQUARTER,
|
|
138
|
-
];
|
|
139
|
-
for (i, frame) in spin.iter().cycle().take(18).enumerate() {
|
|
140
|
-
if i > 0 {
|
|
141
|
-
// rewind above the 3 mascot lines to redraw in place
|
|
142
|
-
let _ = out.write_all(b"\x1b[3A");
|
|
143
|
-
}
|
|
144
|
-
let mut block = String::new();
|
|
145
|
-
for line in *frame {
|
|
146
|
-
block.push_str(&format!("\x1b[2K\r{CYAN}{line}{RESET}\r\n"));
|
|
147
|
-
}
|
|
148
|
-
block.push_str(&format!(
|
|
149
|
-
"\x1b[2K\r {DIM}loading bundled price database · {n} models{RESET}"
|
|
150
|
-
));
|
|
151
|
-
let _ = out.write_all(block.as_bytes());
|
|
152
|
-
out.flush().ok();
|
|
153
|
-
std::thread::sleep(std::time::Duration::from_millis(115));
|
|
154
|
-
}
|
|
155
|
-
std::thread::sleep(std::time::Duration::from_millis(250));
|
|
156
|
-
|
|
157
|
-
// --- menu items cascade in, banner on top ---
|
|
158
|
-
let mut sel = 0usize;
|
|
159
|
-
draw_menu(&mut out, &db, sel, true);
|
|
160
|
-
|
|
161
|
-
// --- input loop: raw mode stays ON so keypresses don't flicker ---
|
|
162
|
-
if enable_raw_mode().is_err() {
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
while let Some(key) = read_key() {
|
|
166
|
-
match key.code {
|
|
167
|
-
KeyCode::Esc => break,
|
|
168
|
-
KeyCode::Char('q') | KeyCode::Char('Q') => break,
|
|
169
|
-
KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => break,
|
|
170
|
-
KeyCode::Up => {
|
|
171
|
-
sel = sel.wrapping_sub(1).min(ITEMS.len() - 1);
|
|
172
|
-
draw_menu(&mut out, &db, sel, false);
|
|
173
|
-
}
|
|
174
|
-
KeyCode::Down | KeyCode::Tab => {
|
|
175
|
-
sel = (sel + 1) % ITEMS.len();
|
|
176
|
-
draw_menu(&mut out, &db, sel, false);
|
|
177
|
-
}
|
|
178
|
-
KeyCode::Enter => {
|
|
179
|
-
let _ = disable_raw_mode(); // views print with plain println!
|
|
180
|
-
act(sel, &mut out);
|
|
181
|
-
if enable_raw_mode().is_err() {
|
|
182
|
-
break;
|
|
183
|
-
}
|
|
184
|
-
draw_menu(&mut out, &db, sel, false);
|
|
185
|
-
}
|
|
186
|
-
KeyCode::Char(c) => {
|
|
187
|
-
let idx = match c {
|
|
188
|
-
'1'..='9' => (c as u8 - b'1') as usize,
|
|
189
|
-
'0' => 9,
|
|
190
|
-
_ => continue,
|
|
191
|
-
};
|
|
192
|
-
if idx < ITEMS.len() {
|
|
193
|
-
sel = idx;
|
|
194
|
-
let _ = disable_raw_mode();
|
|
195
|
-
act(sel, &mut out);
|
|
196
|
-
if enable_raw_mode().is_err() {
|
|
197
|
-
break;
|
|
198
|
-
}
|
|
199
|
-
draw_menu(&mut out, &db, sel, false);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
_ => {}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// restore terminal
|
|
207
|
-
let _ = disable_raw_mode();
|
|
208
|
-
let _ = out.write_all(b"\x1b[?25h\x1b[0m\r\n");
|
|
209
|
-
out.flush().ok();
|
|
210
|
-
println!(
|
|
211
|
-
" {DIM}price database travels with you — {RESET}{CYAN}costcrew spend add 19.99{RESET} {DIM}from any shell{RESET}"
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/// run one menu item: clear the logo away, do the thing, wait for a key
|
|
216
|
-
fn act(sel: usize, out: &mut std::io::Stdout) {
|
|
217
|
-
let _ = out.write_all("\x1b[2J\x1b[H\x1b[?25h".as_bytes());
|
|
218
|
-
out.flush().ok();
|
|
219
|
-
|
|
220
|
-
match sel {
|
|
221
|
-
0 => {
|
|
222
|
-
let q = prompt("filter the database (enter = everything)");
|
|
223
|
-
let mut args = base(Cmd::Prices);
|
|
224
|
-
if !q.is_empty() {
|
|
225
|
-
args.pos.push(q);
|
|
226
|
-
}
|
|
227
|
-
crate::flow(args);
|
|
228
|
-
}
|
|
229
|
-
1 => crate::flow(base(Cmd::Spend)),
|
|
230
|
-
2 => crate::spend::interactive_add(),
|
|
231
|
-
3 => {
|
|
232
|
-
let mut args = base(Cmd::Leaderboard);
|
|
233
|
-
args.top = 25;
|
|
234
|
-
crate::flow(args);
|
|
235
|
-
}
|
|
236
|
-
4 => crate::flow(base(Cmd::Catalog)),
|
|
237
|
-
5 => {
|
|
238
|
-
let key = match std::env::var("OPENROUTER_API_KEY") {
|
|
239
|
-
Ok(k) => k,
|
|
240
|
-
Err(_) => prompt("OpenRouter key (sk-or-v1-...)"),
|
|
241
|
-
};
|
|
242
|
-
let mut args = base(Cmd::OpenRouter);
|
|
243
|
-
if !key.is_empty() {
|
|
244
|
-
args.key = Some(key);
|
|
245
|
-
}
|
|
246
|
-
crate::flow(args);
|
|
247
|
-
}
|
|
248
|
-
6 => crate::flow(base(Cmd::Fetch)),
|
|
249
|
-
7 => crate::flow(base(Cmd::Scan)),
|
|
250
|
-
8 => crate::flow(base(Cmd::Advise)),
|
|
251
|
-
9 => crate::flow(base(Cmd::Demo)),
|
|
252
|
-
_ => {}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// "press any key" pause, then back to the menu
|
|
256
|
-
println!();
|
|
257
|
-
println!(" {DIM}press any key to go back{RESET}");
|
|
258
|
-
std::io::stdout().flush().ok();
|
|
259
|
-
let _ = wait_any_key();
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// ---------- frames ----------
|
|
263
|
-
|
|
264
|
-
/// full menu frame — banner on top of the options, always
|
|
265
|
-
fn frame(db: &PriceDB, sel: usize) -> String {
|
|
266
|
-
let mut s = String::from(CLEAR);
|
|
267
|
-
for (i, line) in BANNER.iter().enumerate() {
|
|
268
|
-
s.push_str(GRAD[i % GRAD.len()]);
|
|
269
|
-
s.push_str(line);
|
|
270
|
-
s.push_str(RESET);
|
|
271
|
-
s.push_str("\r\n");
|
|
272
|
-
}
|
|
273
|
-
s.push_str(&format!(
|
|
274
|
-
"\r\n {BOLD}the AI coding price database{RESET} {DIM}· {n} models bundled · offline · v{}{RESET}\r\n\r\n",
|
|
275
|
-
env!("CARGO_PKG_VERSION"),
|
|
276
|
-
n = db.entries().len()
|
|
277
|
-
));
|
|
278
|
-
for (i, it) in ITEMS.iter().enumerate() {
|
|
279
|
-
let num = if i == 9 {
|
|
280
|
-
"0".to_string()
|
|
281
|
-
} else {
|
|
282
|
-
format!("{}", i + 1)
|
|
283
|
-
};
|
|
284
|
-
if i == sel {
|
|
285
|
-
s.push_str(&format!(
|
|
286
|
-
" {CYAN}▸ {num} {BOLD}{:<20}{RESET} {CYAN}{}{RESET}\r\n",
|
|
287
|
-
it.name, it.hint
|
|
288
|
-
));
|
|
289
|
-
} else {
|
|
290
|
-
s.push_str(&format!(
|
|
291
|
-
" {DIM}{num} {:<20}{RESET} {DIM}{}{RESET}\r\n",
|
|
292
|
-
it.name, it.hint
|
|
293
|
-
));
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
s.push_str(&format!("\r\n {YELLOW}{FOOTER}{RESET}\r\n"));
|
|
297
|
-
s
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
fn draw_menu(out: &mut std::io::Stdout, db: &PriceDB, sel: usize, animate: bool) {
|
|
301
|
-
let f = frame(db, sel);
|
|
302
|
-
if animate {
|
|
303
|
-
for line in f.split("\r\n") {
|
|
304
|
-
write_line(out, line);
|
|
305
|
-
std::thread::sleep(std::time::Duration::from_millis(16));
|
|
306
|
-
}
|
|
307
|
-
} else {
|
|
308
|
-
// single atomic write — no partial repaint, no flash
|
|
309
|
-
let _ = out.write_all(f.as_bytes());
|
|
310
|
-
out.flush().ok();
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
fn write_line(out: &mut std::io::Stdout, s: &str) {
|
|
315
|
-
let _ = out.write_all(s.as_bytes());
|
|
316
|
-
let _ = out.write_all(b"\r\n");
|
|
317
|
-
out.flush().ok();
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
// ---------- terminal helpers ----------
|
|
321
|
-
|
|
322
|
-
fn base(cmd: Cmd) -> Args {
|
|
323
|
-
Args {
|
|
324
|
-
cmd,
|
|
325
|
-
json: false,
|
|
326
|
-
since_days: 30,
|
|
327
|
-
help: false,
|
|
328
|
-
key: None,
|
|
329
|
-
prices: None,
|
|
330
|
-
sort: None,
|
|
331
|
-
top: 25,
|
|
332
|
-
pos: Vec::new(),
|
|
333
|
-
service: None,
|
|
334
|
-
model: None,
|
|
335
|
-
note: None,
|
|
336
|
-
at: None,
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
/// one key press (skips release events); requires raw mode to be ON
|
|
341
|
-
fn read_key() -> Option<KeyEvent> {
|
|
342
|
-
loop {
|
|
343
|
-
match read() {
|
|
344
|
-
Ok(Event::Key(k)) if k.kind == KeyEventKind::Press => return Some(k),
|
|
345
|
-
Ok(_) => continue, // release events, resize, focus, etc.
|
|
346
|
-
Err(_) => return None,
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
/// for the "press any key" pause — raw mode is OFF here, toggles it itself
|
|
352
|
-
fn wait_any_key() -> Option<KeyEvent> {
|
|
353
|
-
if enable_raw_mode().is_err() {
|
|
354
|
-
return None;
|
|
355
|
-
}
|
|
356
|
-
let k = read_key();
|
|
357
|
-
let _ = disable_raw_mode();
|
|
358
|
-
k
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
fn prompt(label: &str) -> String {
|
|
362
|
-
print!(" {CYAN}{label}{RESET} ");
|
|
363
|
-
std::io::stdout().flush().ok();
|
|
364
|
-
let mut s = String::new();
|
|
365
|
-
std::io::stdin().read_line(&mut s).ok();
|
|
366
|
-
s.trim().to_string()
|
|
367
|
-
}
|
|
Binary file
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@archeagle087/costcrew-darwin-arm64",
|
|
3
|
-
"version": "0.4.1",
|
|
4
|
-
"description": "costcrew native binary (costcrew-darwin-arm64) — do not install directly",
|
|
5
|
-
"os": ["darwin"],
|
|
6
|
-
"cpu": ["arm64"],
|
|
7
|
-
"license": "MIT",
|
|
8
|
-
"repository": "github:Arch087146/costcrew"
|
|
9
|
-
}
|
|
Binary file
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@archeagle087/costcrew-darwin-x64",
|
|
3
|
-
"version": "0.4.1",
|
|
4
|
-
"description": "costcrew native binary (costcrew-darwin-x64) — do not install directly",
|
|
5
|
-
"os": ["darwin"],
|
|
6
|
-
"cpu": ["x64"],
|
|
7
|
-
"license": "MIT",
|
|
8
|
-
"repository": "github:Arch087146/costcrew"
|
|
9
|
-
}
|
|
Binary file
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@archeagle087/costcrew-linux-x64",
|
|
3
|
-
"version": "0.4.1",
|
|
4
|
-
"description": "costcrew native binary (costcrew-linux-x64) — do not install directly",
|
|
5
|
-
"os": ["linux"],
|
|
6
|
-
"cpu": ["x64"],
|
|
7
|
-
"license": "MIT",
|
|
8
|
-
"repository": "github:Arch087146/costcrew"
|
|
9
|
-
}
|
|
Binary file
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@archeagle087/costcrew-win32-x64",
|
|
3
|
-
"version": "0.4.1",
|
|
4
|
-
"description": "costcrew native binary (costcrew-win32-x64) — do not install directly",
|
|
5
|
-
"os": ["win32"],
|
|
6
|
-
"cpu": ["x64"],
|
|
7
|
-
"license": "MIT",
|
|
8
|
-
"repository": "github:Arch087146/costcrew"
|
|
9
|
-
}
|