@leeandrew94/ccm 0.2.0 → 0.3.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 +4 -3
- package/README.zh.md +4 -3
- package/bin/ccm.js +245 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,14 +35,14 @@ rm -rf ~/.ccm
|
|
|
35
35
|
## Quick Start
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
|
-
#
|
|
39
|
-
ccm
|
|
38
|
+
# Interactive setup wizard (recommended)
|
|
39
|
+
ccm init
|
|
40
40
|
|
|
41
41
|
# Launch
|
|
42
42
|
ccm mimo
|
|
43
43
|
|
|
44
44
|
# Open another terminal, switch to a different model
|
|
45
|
-
ccm
|
|
45
|
+
ccm init
|
|
46
46
|
ccm deepseek
|
|
47
47
|
|
|
48
48
|
# See what's running
|
|
@@ -53,6 +53,7 @@ ccm ps
|
|
|
53
53
|
|
|
54
54
|
| Command | Description |
|
|
55
55
|
|---|---|
|
|
56
|
+
| `ccm init` | Interactive setup wizard |
|
|
56
57
|
| `ccm <name>` | Load profile and launch claude |
|
|
57
58
|
| `ccm add <name>` | Add a new profile |
|
|
58
59
|
| `ccm edit <name>` | Edit an existing profile |
|
package/README.zh.md
CHANGED
|
@@ -35,14 +35,14 @@ rm -rf ~/.ccm
|
|
|
35
35
|
## 快速开始
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
|
-
#
|
|
39
|
-
ccm
|
|
38
|
+
# 交互式初始化(推荐)
|
|
39
|
+
ccm init
|
|
40
40
|
|
|
41
41
|
# 启动
|
|
42
42
|
ccm mimo
|
|
43
43
|
|
|
44
44
|
# 新开终端,换个模型
|
|
45
|
-
ccm
|
|
45
|
+
ccm init
|
|
46
46
|
ccm deepseek
|
|
47
47
|
|
|
48
48
|
# 查看谁在跑
|
|
@@ -53,6 +53,7 @@ ccm ps
|
|
|
53
53
|
|
|
54
54
|
| 命令 | 说明 |
|
|
55
55
|
|---|---|
|
|
56
|
+
| `ccm init` | 交互式初始化向导 |
|
|
56
57
|
| `ccm <name>` | 加载 profile 并启动 claude |
|
|
57
58
|
| `ccm add <name>` | 新增 profile |
|
|
58
59
|
| `ccm edit <name>` | 修改 profile |
|
package/bin/ccm.js
CHANGED
|
@@ -443,6 +443,213 @@ function cmdList() {
|
|
|
443
443
|
`);
|
|
444
444
|
}
|
|
445
445
|
|
|
446
|
+
// src/commands/init.ts
|
|
447
|
+
import https from "https";
|
|
448
|
+
import http from "http";
|
|
449
|
+
function httpRequest(url, token, timeout = 8e3) {
|
|
450
|
+
return new Promise((resolve) => {
|
|
451
|
+
const parsed = new URL(url);
|
|
452
|
+
const mod = parsed.protocol === "https:" ? https : http;
|
|
453
|
+
const req = mod.request(url, {
|
|
454
|
+
method: "GET",
|
|
455
|
+
headers: { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" },
|
|
456
|
+
timeout
|
|
457
|
+
}, (res) => {
|
|
458
|
+
let body = "";
|
|
459
|
+
res.on("data", (chunk) => {
|
|
460
|
+
body += chunk;
|
|
461
|
+
});
|
|
462
|
+
res.on("end", () => {
|
|
463
|
+
try {
|
|
464
|
+
resolve({ status: res.statusCode || 0, body: JSON.parse(body) });
|
|
465
|
+
} catch {
|
|
466
|
+
resolve({ status: res.statusCode || 0, body });
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
req.on("error", () => resolve({ status: 0, body: {} }));
|
|
471
|
+
req.on("timeout", () => {
|
|
472
|
+
req.destroy();
|
|
473
|
+
resolve({ status: 0, body: {} });
|
|
474
|
+
});
|
|
475
|
+
req.end();
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
async function fetchModels(baseUrl, token) {
|
|
479
|
+
const url = baseUrl.replace(/\/+$/, "");
|
|
480
|
+
for (const suffix of ["/anthropic", "/v1/chat/completions"]) {
|
|
481
|
+
if (url.endsWith(suffix)) return [];
|
|
482
|
+
}
|
|
483
|
+
const endpoints = [`${url}/models`, `${url}/v1/models`];
|
|
484
|
+
for (const ep of endpoints) {
|
|
485
|
+
const result = await httpRequest(ep, token);
|
|
486
|
+
if (result.status === 200 && result.body) {
|
|
487
|
+
const data = result.body.data || result.body.models;
|
|
488
|
+
if (Array.isArray(data)) {
|
|
489
|
+
return data.map((m) => m.id || m.name).filter(Boolean).sort();
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return [];
|
|
494
|
+
}
|
|
495
|
+
async function selectModel(models, label) {
|
|
496
|
+
if (models.length === 0) {
|
|
497
|
+
return await ask("Model name");
|
|
498
|
+
}
|
|
499
|
+
console.log(`
|
|
500
|
+
\x1B[1m${label}\x1B[0m (\u2191\u2193 navigate, Enter to select):
|
|
501
|
+
`);
|
|
502
|
+
const PAGE = 12;
|
|
503
|
+
let offset = 0;
|
|
504
|
+
let selected = 0;
|
|
505
|
+
function render() {
|
|
506
|
+
process.stdout.write("\x1B[?25l");
|
|
507
|
+
const start = offset;
|
|
508
|
+
const end = Math.min(offset + PAGE, models.length);
|
|
509
|
+
for (let i = start; i < end; i++) {
|
|
510
|
+
process.stdout.write("\x1B[2K");
|
|
511
|
+
if (i === selected) {
|
|
512
|
+
console.log(` \x1B[36m\u276F\x1B[0m \x1B[1m${models[i]}\x1B[0m`);
|
|
513
|
+
} else {
|
|
514
|
+
console.log(` \x1B[90m${models[i]}\x1B[0m`);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
process.stdout.write("\x1B[2K");
|
|
518
|
+
if (models.length > PAGE) {
|
|
519
|
+
console.log(` \x1B[90m${selected + 1}/${models.length} \u2014 \u2191\u2193 scroll, Enter select\x1B[0m`);
|
|
520
|
+
}
|
|
521
|
+
const lines = end - start + (models.length > PAGE ? 1 : 0);
|
|
522
|
+
process.stdout.write(`\x1B[${lines}A`);
|
|
523
|
+
}
|
|
524
|
+
return new Promise((resolve) => {
|
|
525
|
+
process.stdin.setRawMode(true);
|
|
526
|
+
process.stdin.resume();
|
|
527
|
+
process.stdin.setEncoding("utf-8");
|
|
528
|
+
render();
|
|
529
|
+
let buf = "";
|
|
530
|
+
process.stdin.on("data", function onData(key) {
|
|
531
|
+
if (key === "\x1B[A") {
|
|
532
|
+
selected = Math.max(0, selected - 1);
|
|
533
|
+
if (selected < offset) offset = selected;
|
|
534
|
+
render();
|
|
535
|
+
} else if (key === "\x1B[B") {
|
|
536
|
+
selected = Math.min(models.length - 1, selected + 1);
|
|
537
|
+
if (selected >= offset + PAGE) offset = selected - PAGE + 1;
|
|
538
|
+
render();
|
|
539
|
+
} else if (key === "\r" || key === "\n") {
|
|
540
|
+
const lines = Math.min(PAGE, models.length - offset) + (models.length > PAGE ? 1 : 0);
|
|
541
|
+
for (let i = 0; i < lines; i++) {
|
|
542
|
+
process.stdout.write("\x1B[2K\n");
|
|
543
|
+
}
|
|
544
|
+
process.stdout.write(`\x1B[${lines}A`);
|
|
545
|
+
for (let i = 0; i < lines; i++) {
|
|
546
|
+
process.stdout.write("\x1B[2K\n");
|
|
547
|
+
}
|
|
548
|
+
process.stdout.write(`\x1B[${lines}A`);
|
|
549
|
+
process.stdout.write(` \x1B[36m\u276F\x1B[0m \x1B[1m${models[selected]}\x1B[0m
|
|
550
|
+
`);
|
|
551
|
+
process.stdout.write("\x1B[?25h");
|
|
552
|
+
process.stdin.setRawMode(false);
|
|
553
|
+
process.stdin.removeListener("data", onData);
|
|
554
|
+
process.stdin.pause();
|
|
555
|
+
resolve(models[selected]);
|
|
556
|
+
} else if (key === "") {
|
|
557
|
+
process.stdout.write("\x1B[?25h");
|
|
558
|
+
process.exit(0);
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
async function cmdInit() {
|
|
564
|
+
console.log();
|
|
565
|
+
console.log(" \x1B[1;36m\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\x1B[0m");
|
|
566
|
+
console.log(" \x1B[1;36m\u2502\x1B[0m ccm - Claude Code Model Manager \x1B[1;36m\u2502\x1B[0m");
|
|
567
|
+
console.log(" \x1B[1;36m\u2502\x1B[0m \x1B[90mSet up your model profile\x1B[0m \x1B[1;36m\u2502\x1B[0m");
|
|
568
|
+
console.log(" \x1B[1;36m\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\x1B[0m");
|
|
569
|
+
console.log();
|
|
570
|
+
if (!whichClaude()) {
|
|
571
|
+
warn("Claude Code is not installed.");
|
|
572
|
+
info("Install it first: npm install -g @anthropic-ai/claude-code\n");
|
|
573
|
+
const proceed = await ask("Continue anyway? [y/N]");
|
|
574
|
+
if (proceed.toLowerCase() !== "y") return;
|
|
575
|
+
}
|
|
576
|
+
const baseUrl = await ask("API Base URL (*)");
|
|
577
|
+
if (!baseUrl) {
|
|
578
|
+
err("Base URL is required.");
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
const token = await ask("API Key (*)");
|
|
582
|
+
if (!token) {
|
|
583
|
+
err("API Key is required.");
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
console.log();
|
|
587
|
+
info("Validating connection...");
|
|
588
|
+
const isAnthropic = baseUrl.toLowerCase().includes("anthropic");
|
|
589
|
+
let models = [];
|
|
590
|
+
if (!isAnthropic) {
|
|
591
|
+
models = await fetchModels(baseUrl, token);
|
|
592
|
+
if (models.length > 0) {
|
|
593
|
+
ok(`Found \x1B[1m${models.length}\x1B[0m models`);
|
|
594
|
+
} else {
|
|
595
|
+
info("Could not fetch model list (enter manually)");
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
let model;
|
|
599
|
+
if (isAnthropic) {
|
|
600
|
+
const anthropicModels = [
|
|
601
|
+
"claude-sonnet-4-20250514",
|
|
602
|
+
"claude-opus-4-20250514",
|
|
603
|
+
"claude-haiku-4-20250501"
|
|
604
|
+
];
|
|
605
|
+
model = await selectModel(anthropicModels, "Select a model");
|
|
606
|
+
} else if (models.length > 0) {
|
|
607
|
+
model = await selectModel(models, "Select a model");
|
|
608
|
+
} else {
|
|
609
|
+
model = await ask("Model name (*)");
|
|
610
|
+
if (!model) {
|
|
611
|
+
err("Model name is required.");
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
console.log();
|
|
616
|
+
info("Optional: model mappings for Haiku/Sonnet/Opus (Enter to skip)\n");
|
|
617
|
+
const haiku = await ask("Haiku model");
|
|
618
|
+
const sonnet = await ask("Sonnet model");
|
|
619
|
+
const opus = await ask("Opus model");
|
|
620
|
+
console.log();
|
|
621
|
+
const name = await ask("Profile name (*)");
|
|
622
|
+
if (!name) {
|
|
623
|
+
err("Profile name is required.");
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
if (profileExists(name)) {
|
|
627
|
+
warn(`Profile '${name}' already exists.`);
|
|
628
|
+
const overwrite = await ask("Overwrite? [y/N]");
|
|
629
|
+
if (overwrite.toLowerCase() !== "y") {
|
|
630
|
+
info("Cancelled.");
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
const profile = {
|
|
635
|
+
ANTHROPIC_BASE_URL: baseUrl,
|
|
636
|
+
ANTHROPIC_AUTH_TOKEN: token,
|
|
637
|
+
ANTHROPIC_MODEL: model
|
|
638
|
+
};
|
|
639
|
+
if (haiku) profile.ANTHROPIC_DEFAULT_HAIKU_MODEL = haiku;
|
|
640
|
+
if (sonnet) profile.ANTHROPIC_DEFAULT_SONNET_MODEL = sonnet;
|
|
641
|
+
if (opus) profile.ANTHROPIC_DEFAULT_OPUS_MODEL = opus;
|
|
642
|
+
addProfile(name, profile);
|
|
643
|
+
console.log();
|
|
644
|
+
ok(`Profile '\x1B[1m${name}\x1B[0m' saved!
|
|
645
|
+
`);
|
|
646
|
+
console.log(` \x1B[36mccm ${name}\x1B[0m launch with this profile`);
|
|
647
|
+
console.log(` \x1B[36mccm edit ${name}\x1B[0m edit configuration`);
|
|
648
|
+
console.log(` \x1B[36mccm test ${name}\x1B[0m test connection`);
|
|
649
|
+
console.log(` \x1B[36mccm balance ${name}\x1B[0m check credits`);
|
|
650
|
+
console.log();
|
|
651
|
+
}
|
|
652
|
+
|
|
446
653
|
// src/commands/runtime.ts
|
|
447
654
|
function cmdPs() {
|
|
448
655
|
const runs = getAllRuns();
|
|
@@ -494,12 +701,12 @@ function cmdCheck() {
|
|
|
494
701
|
}
|
|
495
702
|
|
|
496
703
|
// src/commands/diagnose.ts
|
|
497
|
-
import
|
|
498
|
-
import
|
|
499
|
-
function
|
|
704
|
+
import https2 from "https";
|
|
705
|
+
import http2 from "http";
|
|
706
|
+
function httpRequest2(url, token, method = "GET", timeout = 1e4, headers = {}) {
|
|
500
707
|
return new Promise((resolve) => {
|
|
501
708
|
const parsed = new URL(url);
|
|
502
|
-
const mod = parsed.protocol === "https:" ?
|
|
709
|
+
const mod = parsed.protocol === "https:" ? https2 : http2;
|
|
503
710
|
const reqHeaders = {
|
|
504
711
|
"Authorization": `Bearer ${token}`,
|
|
505
712
|
"x-api-key": token,
|
|
@@ -536,7 +743,7 @@ function isAnthropicCompat(baseUrl) {
|
|
|
536
743
|
async function testConnection(baseUrl, token) {
|
|
537
744
|
const url = baseUrl.replace(/\/+$/, "");
|
|
538
745
|
if (isAnthropicCompat(baseUrl)) {
|
|
539
|
-
const result = await
|
|
746
|
+
const result = await httpRequest2(`${url}/v1/messages`, token, "POST", 1e4, {
|
|
540
747
|
"anthropic-version": "2023-06-01"
|
|
541
748
|
});
|
|
542
749
|
if (result.status === 200 || result.status === 400) return { ok: true, endpoint: "/v1/messages" };
|
|
@@ -544,14 +751,14 @@ async function testConnection(baseUrl, token) {
|
|
|
544
751
|
return { ok: false, error: result.error || `HTTP ${result.status}` };
|
|
545
752
|
}
|
|
546
753
|
for (const endpoint of ["/v1/models", "/models"]) {
|
|
547
|
-
const result = await
|
|
754
|
+
const result = await httpRequest2(`${url}${endpoint}`, token);
|
|
548
755
|
if (result.status === 200) return { ok: true, endpoint };
|
|
549
756
|
if (result.status === 401 || result.status === 403) return { ok: false, error: "Authentication failed (invalid token)" };
|
|
550
757
|
}
|
|
551
758
|
try {
|
|
552
759
|
const baseParts = url.split("//");
|
|
553
760
|
const hostUrl = `${baseParts[0]}//${baseParts[1].split("/")[0]}/`;
|
|
554
|
-
const result = await
|
|
761
|
+
const result = await httpRequest2(hostUrl, token);
|
|
555
762
|
if (result.status > 0) return { ok: true, endpoint: "(host reachable)" };
|
|
556
763
|
} catch {
|
|
557
764
|
}
|
|
@@ -568,10 +775,11 @@ async function queryBalance(baseUrl, token) {
|
|
|
568
775
|
`${url}/dashboard/billing/usage`,
|
|
569
776
|
`${url}/api/user/balance`,
|
|
570
777
|
`${url}/billing/usage`,
|
|
571
|
-
`${url}/v1/dashboard/billing/usage
|
|
778
|
+
`${url}/v1/dashboard/billing/usage`,
|
|
779
|
+
`${url}/auth/key`
|
|
572
780
|
];
|
|
573
781
|
for (const ep of endpoints) {
|
|
574
|
-
const result = await
|
|
782
|
+
const result = await httpRequest2(ep, token);
|
|
575
783
|
if (result.status === 200) return result.body;
|
|
576
784
|
}
|
|
577
785
|
return null;
|
|
@@ -636,6 +844,23 @@ function formatBalanceCell(data, statusWidth) {
|
|
|
636
844
|
const bal = fmtAmount(data.total_available, currency);
|
|
637
845
|
return [`\x1B[1;32m${bal}\x1B[0m`, "\x1B[32m\u25CF active\x1B[0m"];
|
|
638
846
|
}
|
|
847
|
+
if ("data" in data && typeof data.data === "object" && data.data !== null && "usage" in data.data && "limit" in data.data) {
|
|
848
|
+
const d = data.data;
|
|
849
|
+
const used = parseFloat(d.usage);
|
|
850
|
+
const limit = parseFloat(d.limit);
|
|
851
|
+
const remaining = limit - used;
|
|
852
|
+
const balanceStr = `\x1B[1;32m${fmtAmount(remaining.toFixed(4))}\x1B[0m`;
|
|
853
|
+
let statusStr;
|
|
854
|
+
if (limit > 0) {
|
|
855
|
+
const ratio = used / limit;
|
|
856
|
+
const pct = ratio * 100;
|
|
857
|
+
const barStr = bar(ratio, 12);
|
|
858
|
+
statusStr = `${barStr} ${pct.toFixed(1)}% used`;
|
|
859
|
+
} else {
|
|
860
|
+
statusStr = d.is_free_tier ? "\x1B[32m\u25CF free tier\x1B[0m" : "\x1B[32m\u25CF active\x1B[0m";
|
|
861
|
+
}
|
|
862
|
+
return [balanceStr, statusStr];
|
|
863
|
+
}
|
|
639
864
|
if ("data" in data && typeof data.data === "object" && data.data !== null) {
|
|
640
865
|
const d = data.data;
|
|
641
866
|
const granted = d.total_granted;
|
|
@@ -756,7 +981,7 @@ function padAnsi2(text, width) {
|
|
|
756
981
|
}
|
|
757
982
|
|
|
758
983
|
// src/completions.ts
|
|
759
|
-
var COMMANDS = ["add", "edit", "rm", "list", "ls", "ps", "kill", "check", "test", "balance", "bal", "config", "completions"];
|
|
984
|
+
var COMMANDS = ["init", "add", "edit", "rm", "list", "ls", "ps", "kill", "check", "test", "balance", "bal", "config", "completions", "sessions"];
|
|
760
985
|
function cmdCompletions(args) {
|
|
761
986
|
const shell = args.shell || detectShell();
|
|
762
987
|
if (shell === "zsh") {
|
|
@@ -1126,7 +1351,7 @@ function cleanupOldTrash() {
|
|
|
1126
1351
|
|
|
1127
1352
|
// src/session-server.ts
|
|
1128
1353
|
import { exec } from "child_process";
|
|
1129
|
-
import
|
|
1354
|
+
import http3 from "http";
|
|
1130
1355
|
function openBrowser(url) {
|
|
1131
1356
|
const cmd = process.platform === "darwin" ? `open "${url}"` : `xdg-open "${url}"`;
|
|
1132
1357
|
exec(cmd);
|
|
@@ -1137,7 +1362,7 @@ function jsonResponse(res, data, status = 200) {
|
|
|
1137
1362
|
}
|
|
1138
1363
|
function startSessionServer(port = 13501) {
|
|
1139
1364
|
return new Promise((resolve) => {
|
|
1140
|
-
const server =
|
|
1365
|
+
const server = http3.createServer((req, res) => {
|
|
1141
1366
|
const url = new URL(req.url || "/", `http://localhost:${port}`);
|
|
1142
1367
|
if (req.method === "GET" && (url.pathname === "/" || /^\/session\/[0-9a-f-]+$/.test(url.pathname))) {
|
|
1143
1368
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
@@ -1503,8 +1728,10 @@ body {
|
|
|
1503
1728
|
flex: 1; overflow-y: auto; padding: 24px 16px;
|
|
1504
1729
|
background: var(--td-bg-card);box-shadow: var(--td-shadow-1);
|
|
1505
1730
|
}
|
|
1506
|
-
.message { margin-bottom: 20px; display: flex; gap: 10px; }
|
|
1731
|
+
.message { margin-bottom: 20px; display: flex; gap: 10px; width: 100%; }
|
|
1507
1732
|
.message.user { flex-direction: row-reverse; justify-content: flex-start; }
|
|
1733
|
+
.message.user > div:not(.avatar) { flex: 1; min-width: 0; display: flex; flex-direction: column; align-items: flex-end; }
|
|
1734
|
+
.message.user .bubble { max-width: 85%; }
|
|
1508
1735
|
.message .avatar {
|
|
1509
1736
|
width: 32px; height: 32px; border-radius: 50%;
|
|
1510
1737
|
display: flex; align-items: center; justify-content: center;
|
|
@@ -1901,6 +2128,7 @@ var VERSION = pkg.version;
|
|
|
1901
2128
|
var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
1902
2129
|
"_launch",
|
|
1903
2130
|
"_register",
|
|
2131
|
+
"init",
|
|
1904
2132
|
"add",
|
|
1905
2133
|
"edit",
|
|
1906
2134
|
"rm",
|
|
@@ -1921,6 +2149,7 @@ function printHelp() {
|
|
|
1921
2149
|
Usage: ccm <command> [options]
|
|
1922
2150
|
|
|
1923
2151
|
Commands:
|
|
2152
|
+
init Interactive setup wizard
|
|
1924
2153
|
add <name> Add a new model profile
|
|
1925
2154
|
edit <name> Edit an existing profile
|
|
1926
2155
|
rm <name> Delete a profile
|
|
@@ -2002,6 +2231,9 @@ async function main() {
|
|
|
2002
2231
|
case "_register":
|
|
2003
2232
|
cmdRegister({ name: args.name, pid: args.pid, tty: args.tty });
|
|
2004
2233
|
break;
|
|
2234
|
+
case "init":
|
|
2235
|
+
await cmdInit();
|
|
2236
|
+
break;
|
|
2005
2237
|
case "add":
|
|
2006
2238
|
await cmdAdd({ name: args.name });
|
|
2007
2239
|
break;
|