mira-cli-ts 0.26.5 → 0.26.7
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/dist/cli.js +314 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -136,6 +136,11 @@ Usage:
|
|
|
136
136
|
mira config get [key] Get config (or filtered by key)
|
|
137
137
|
mira config set <key> <value> Set config (dot notation, JSON value)
|
|
138
138
|
mira finding list [--status open] [--limit 20] List findings
|
|
139
|
+
mira workspace list List workspaces (recent)
|
|
140
|
+
mira workspace add <path> Add workspace (validates path)
|
|
141
|
+
mira workspace remove <id|path> Remove workspace
|
|
142
|
+
mira workspace switch <id|path> Switch workspace (sets cwd for new sessions)
|
|
143
|
+
mira project init [--template ts] [--path <dir>] Init mira.json in project
|
|
139
144
|
mira manager Active jobs + recent sessions
|
|
140
145
|
mira health Liveness (/healthz)
|
|
141
146
|
mira complete --prefix "..." [--suffix "..."] [--file path] Ghost-text completion
|
|
@@ -145,11 +150,15 @@ Usage:
|
|
|
145
150
|
Env:
|
|
146
151
|
MIRA_API_URL server URL (default http://127.0.0.1:4096)
|
|
147
152
|
MIRA_TOKEN bearer token
|
|
153
|
+
MIRA_WORKSPACE single workspace path (env)
|
|
154
|
+
MIRA_WORKSPACE_ROOTS comma-separated workspace roots (env)
|
|
148
155
|
|
|
149
156
|
Examples:
|
|
150
157
|
mira serve
|
|
151
158
|
mira session create --agent ask --title "Q&A"
|
|
152
159
|
mira session prompt --id abc --prompt "explain ./src/index.ts"
|
|
160
|
+
mira workspace add /path/to/repo
|
|
161
|
+
mira project init --template ts
|
|
153
162
|
mira skill list
|
|
154
163
|
mira command list
|
|
155
164
|
mira tool list
|
|
@@ -501,6 +510,282 @@ async function cmdHealth() {
|
|
|
501
510
|
}
|
|
502
511
|
console.log(JSON.stringify(await res.json(), null, 2));
|
|
503
512
|
}
|
|
513
|
+
async function cmdWorkspaceList() {
|
|
514
|
+
try {
|
|
515
|
+
const res = await apiFetch("/workspaces");
|
|
516
|
+
if (!res.ok) {
|
|
517
|
+
console.error(`workspace list failed: ${res.status} ${await res.text()}`);
|
|
518
|
+
process.exit(1);
|
|
519
|
+
}
|
|
520
|
+
const data = await res.json();
|
|
521
|
+
const list = data.workspaces ?? [];
|
|
522
|
+
if (list.length === 0) {
|
|
523
|
+
console.log("No workspaces \u2014 add one with: mira workspace add /path/to/repo");
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
for (const w of list) {
|
|
527
|
+
console.log(`${w.id.slice(0, 8)} ${w.path} (${w.name})`);
|
|
528
|
+
}
|
|
529
|
+
} catch (e) {
|
|
530
|
+
try {
|
|
531
|
+
const { readFileSync, existsSync } = __require("fs");
|
|
532
|
+
const home = process.env.HOME ?? "";
|
|
533
|
+
const fp = home ? `${home}/.mira/workspaces.json` : `${process.cwd()}/.mira/workspaces.json`;
|
|
534
|
+
if (existsSync(fp)) {
|
|
535
|
+
const raw = readFileSync(fp, "utf-8");
|
|
536
|
+
const parsed = JSON.parse(raw);
|
|
537
|
+
const list = Array.isArray(parsed) ? parsed : parsed.workspaces ?? [];
|
|
538
|
+
if (list.length === 0)
|
|
539
|
+
console.log("No workspaces");
|
|
540
|
+
else
|
|
541
|
+
for (const w of list)
|
|
542
|
+
console.log(`${(w.id ?? "").slice(0, 8)} ${w.path} (${w.name ?? w.path.split("/").pop()})`);
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
} catch {}
|
|
546
|
+
console.error(`workspace list failed: ${String(e.message ?? e)}`);
|
|
547
|
+
process.exit(1);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
async function cmdWorkspaceAdd(opts) {
|
|
551
|
+
const positional = Bun.argv.slice(3).filter((a) => !a.startsWith("-"));
|
|
552
|
+
let rawPath = String(opts.path ?? opts.p ?? opts["1"] ?? positional[1] ?? "").trim();
|
|
553
|
+
if (!rawPath || rawPath === "add")
|
|
554
|
+
rawPath = String(positional[1] ?? opts["1"] ?? "").trim();
|
|
555
|
+
if (!rawPath)
|
|
556
|
+
rawPath = String(positional[0] ?? "").trim();
|
|
557
|
+
if (!rawPath || rawPath === "add") {
|
|
558
|
+
console.error("workspace add requires <path> \u2014 e.g. mira workspace add /path/to/repo");
|
|
559
|
+
process.exit(1);
|
|
560
|
+
}
|
|
561
|
+
try {
|
|
562
|
+
const res = await apiFetch("/workspaces", { method: "POST", body: JSON.stringify({ path: rawPath }) });
|
|
563
|
+
if (!res.ok) {
|
|
564
|
+
console.error(`workspace add failed: ${res.status} ${await res.text()}`);
|
|
565
|
+
process.exit(1);
|
|
566
|
+
}
|
|
567
|
+
const data = await res.json();
|
|
568
|
+
console.log(`Added workspace: ${data.workspace.path} (${data.workspace.id.slice(0, 8)})`);
|
|
569
|
+
console.log(JSON.stringify(data.workspace, null, 2));
|
|
570
|
+
} catch (e) {
|
|
571
|
+
const msg = String(e.message ?? e);
|
|
572
|
+
const isConn = msg.includes("ECONNREFUSED") || msg.includes("fetch failed") || msg.includes("Connection refused") || msg.includes("Unable to connect") || msg.includes("ECONNRESET");
|
|
573
|
+
if (!isConn) {
|
|
574
|
+
console.error(`workspace add failed: ${msg}`);
|
|
575
|
+
process.exit(1);
|
|
576
|
+
}
|
|
577
|
+
try {
|
|
578
|
+
const { readFileSync, existsSync, mkdirSync, writeFileSync } = __require("fs");
|
|
579
|
+
const { resolve } = __require("path");
|
|
580
|
+
const absPath = rawPath.startsWith("/") ? rawPath : resolve(process.cwd(), rawPath);
|
|
581
|
+
if (!existsSync(absPath)) {
|
|
582
|
+
console.error(`path not found: ${absPath}`);
|
|
583
|
+
process.exit(1);
|
|
584
|
+
}
|
|
585
|
+
const home = process.env.HOME ?? "";
|
|
586
|
+
const fp = home ? `${home}/.mira/workspaces.json` : `${process.cwd()}/.mira/workspaces.json`;
|
|
587
|
+
const dir = fp.slice(0, fp.lastIndexOf("/"));
|
|
588
|
+
if (dir)
|
|
589
|
+
mkdirSync(dir, { recursive: true });
|
|
590
|
+
let existing = [];
|
|
591
|
+
if (existsSync(fp)) {
|
|
592
|
+
try {
|
|
593
|
+
const raw = readFileSync(fp, "utf-8");
|
|
594
|
+
const parsed = JSON.parse(raw);
|
|
595
|
+
existing = Array.isArray(parsed) ? parsed : parsed.workspaces ?? [];
|
|
596
|
+
} catch {}
|
|
597
|
+
}
|
|
598
|
+
if (existing.some((w) => w.path === absPath)) {
|
|
599
|
+
console.log(`Workspace already exists: ${absPath}`);
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
const id = Buffer.from(absPath).toString("base64url");
|
|
603
|
+
const entry = { id, path: absPath, name: absPath.split("/").pop() || absPath, addedAt: Date.now() };
|
|
604
|
+
existing.push(entry);
|
|
605
|
+
writeFileSync(fp, JSON.stringify({ workspaces: existing }, null, 2) + `
|
|
606
|
+
`);
|
|
607
|
+
console.log(`Added workspace (offline): ${absPath} (${id.slice(0, 8)})`);
|
|
608
|
+
} catch (err) {
|
|
609
|
+
console.error(`workspace add failed: ${String(err.message ?? err)}`);
|
|
610
|
+
process.exit(1);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
async function cmdWorkspaceRemove(opts) {
|
|
615
|
+
const positional = Bun.argv.slice(3).filter((a) => !a.startsWith("-"));
|
|
616
|
+
let id = String(opts.id ?? opts["1"] ?? positional[1] ?? "").trim();
|
|
617
|
+
if (!id || id === "remove" || id === "rm" || id === "delete")
|
|
618
|
+
id = String(positional[1] ?? opts["1"] ?? "").trim();
|
|
619
|
+
if (!id)
|
|
620
|
+
id = String(positional[0] ?? "").trim();
|
|
621
|
+
if (!id || ["remove", "rm", "delete"].includes(id)) {
|
|
622
|
+
console.error("workspace remove requires <id|path> \u2014 e.g. mira workspace remove <id>");
|
|
623
|
+
process.exit(1);
|
|
624
|
+
}
|
|
625
|
+
try {
|
|
626
|
+
const res = await apiFetch(`/workspaces/${encodeURIComponent(id)}`, { method: "DELETE" });
|
|
627
|
+
if (!res.ok) {
|
|
628
|
+
console.error(`workspace remove failed: ${res.status} ${await res.text()}`);
|
|
629
|
+
process.exit(1);
|
|
630
|
+
}
|
|
631
|
+
console.log(JSON.stringify(await res.json(), null, 2));
|
|
632
|
+
return;
|
|
633
|
+
} catch (e) {
|
|
634
|
+
const msg = String(e.message ?? e);
|
|
635
|
+
const isConn = msg.includes("ECONNREFUSED") || msg.includes("fetch failed") || msg.includes("Connection refused") || msg.includes("Unable to connect") || msg.includes("ECONNRESET");
|
|
636
|
+
if (!isConn) {
|
|
637
|
+
console.error(`workspace remove failed: ${msg}`);
|
|
638
|
+
process.exit(1);
|
|
639
|
+
}
|
|
640
|
+
try {
|
|
641
|
+
const { readFileSync, existsSync, writeFileSync } = __require("fs");
|
|
642
|
+
const home = process.env.HOME ?? "";
|
|
643
|
+
const fp = home ? `${home}/.mira/workspaces.json` : `${process.cwd()}/.mira/workspaces.json`;
|
|
644
|
+
if (!existsSync(fp)) {
|
|
645
|
+
console.error(`workspace not found: ${id}`);
|
|
646
|
+
process.exit(1);
|
|
647
|
+
}
|
|
648
|
+
const raw = readFileSync(fp, "utf-8");
|
|
649
|
+
const parsed = JSON.parse(raw);
|
|
650
|
+
const list = Array.isArray(parsed) ? parsed : parsed.workspaces ?? [];
|
|
651
|
+
const idx = list.findIndex((w) => w.id === id || w.path === id || w.id.startsWith(id) || w.path.endsWith(id));
|
|
652
|
+
if (idx === -1) {
|
|
653
|
+
console.error(`workspace not found: ${id}`);
|
|
654
|
+
process.exit(1);
|
|
655
|
+
}
|
|
656
|
+
const removed = list[idx];
|
|
657
|
+
const next = list.filter((_, i) => i !== idx);
|
|
658
|
+
writeFileSync(fp, JSON.stringify({ workspaces: next }, null, 2) + `
|
|
659
|
+
`);
|
|
660
|
+
console.log(`Removed workspace (offline): ${removed.path}`);
|
|
661
|
+
console.log(JSON.stringify({ ok: true, removed }, null, 2));
|
|
662
|
+
} catch (err) {
|
|
663
|
+
console.error(`workspace remove failed: ${String(err.message ?? err)}`);
|
|
664
|
+
process.exit(1);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
async function cmdWorkspaceSwitch(opts) {
|
|
669
|
+
const positional = Bun.argv.slice(3).filter((a) => !a.startsWith("-"));
|
|
670
|
+
let target = String(opts.path ?? opts["1"] ?? positional[1] ?? "").trim();
|
|
671
|
+
if (!target || target === "switch")
|
|
672
|
+
target = String(positional[1] ?? opts["1"] ?? "").trim();
|
|
673
|
+
if (!target)
|
|
674
|
+
target = String(positional[0] ?? "").trim();
|
|
675
|
+
try {
|
|
676
|
+
const res = await apiFetch("/workspaces");
|
|
677
|
+
if (res.ok) {
|
|
678
|
+
const data = await res.json();
|
|
679
|
+
const found = data.workspaces.find((w) => w.id === target || w.path === target || w.id.startsWith(target) || w.path.endsWith(target));
|
|
680
|
+
if (found) {
|
|
681
|
+
console.log(`Switched to workspace: ${found.path} (${found.name})`);
|
|
682
|
+
console.log(`Set MIRA_WORKSPACE=${found.path} for new sessions (export MIRA_WORKSPACE="${found.path}")`);
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
console.error(`workspace not found: ${target}`);
|
|
686
|
+
process.exit(1);
|
|
687
|
+
}
|
|
688
|
+
} catch {}
|
|
689
|
+
try {
|
|
690
|
+
const { readFileSync, existsSync } = __require("fs");
|
|
691
|
+
const home = process.env.HOME ?? "";
|
|
692
|
+
const fp = home ? `${home}/.mira/workspaces.json` : `${process.cwd()}/.mira/workspaces.json`;
|
|
693
|
+
if (existsSync(fp)) {
|
|
694
|
+
const raw = readFileSync(fp, "utf-8");
|
|
695
|
+
const parsed = JSON.parse(raw);
|
|
696
|
+
const list = Array.isArray(parsed) ? parsed : parsed.workspaces ?? [];
|
|
697
|
+
const found = list.find((w) => w.id === target || w.path === target || w.id.startsWith(target));
|
|
698
|
+
if (found) {
|
|
699
|
+
console.log(`Switched to workspace: ${found.path} (${found.name ?? found.path.split("/").pop()})`);
|
|
700
|
+
console.log(`Set MIRA_WORKSPACE=${found.path} for new sessions (export MIRA_WORKSPACE="${found.path}")`);
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
} catch {}
|
|
705
|
+
console.error(`workspace not found: ${target}`);
|
|
706
|
+
process.exit(1);
|
|
707
|
+
}
|
|
708
|
+
async function cmdProjectInit(opts) {
|
|
709
|
+
const template = String(opts.template ?? opts.t ?? "default").trim() || "default";
|
|
710
|
+
const targetPath = String(opts.path ?? opts.p ?? positionalPath() ?? process.cwd()).trim() || process.cwd();
|
|
711
|
+
function positionalPath() {
|
|
712
|
+
const positional = Bun.argv.slice(3).filter((a) => !a.startsWith("-"));
|
|
713
|
+
const tIdx = Bun.argv.indexOf("--template");
|
|
714
|
+
const tIdx2 = Bun.argv.indexOf("-t");
|
|
715
|
+
let afterTemplate = -1;
|
|
716
|
+
if (tIdx !== -1)
|
|
717
|
+
afterTemplate = tIdx + 2;
|
|
718
|
+
else if (tIdx2 !== -1)
|
|
719
|
+
afterTemplate = tIdx2 + 2;
|
|
720
|
+
if (afterTemplate !== -1 && Bun.argv[afterTemplate] && !Bun.argv[afterTemplate].startsWith("-"))
|
|
721
|
+
return Bun.argv[afterTemplate];
|
|
722
|
+
if (template !== "default" && positional.length > 0) {
|
|
723
|
+
if (positional[0] === template)
|
|
724
|
+
return positional[1];
|
|
725
|
+
}
|
|
726
|
+
return positional[0];
|
|
727
|
+
}
|
|
728
|
+
const { existsSync, mkdirSync, writeFileSync, readFileSync } = __require("fs");
|
|
729
|
+
const { resolve } = __require("path");
|
|
730
|
+
const absPath = targetPath.startsWith("/") ? targetPath : resolve(process.cwd(), targetPath);
|
|
731
|
+
try {
|
|
732
|
+
mkdirSync(absPath, { recursive: true });
|
|
733
|
+
} catch {}
|
|
734
|
+
const miraJsonPath = `${absPath}/mira.json`;
|
|
735
|
+
if (existsSync(miraJsonPath)) {
|
|
736
|
+
console.error(`mira.json already exists at ${miraJsonPath} \u2014 refusing to overwrite`);
|
|
737
|
+
process.exit(1);
|
|
738
|
+
}
|
|
739
|
+
let config;
|
|
740
|
+
if (template === "ts" || template === "typescript") {
|
|
741
|
+
config = {
|
|
742
|
+
model: "openrouter/anthropic/claude-sonnet-4",
|
|
743
|
+
permission: { bash: "ask", read: "allow", write: "ask", edit: "ask" },
|
|
744
|
+
guardrails: { allowedRoots: ["."], enforce: false },
|
|
745
|
+
mcp: {},
|
|
746
|
+
provider: {},
|
|
747
|
+
agents: {}
|
|
748
|
+
};
|
|
749
|
+
} else {
|
|
750
|
+
config = {
|
|
751
|
+
model: "openrouter/anthropic/claude-sonnet-4",
|
|
752
|
+
permission: {},
|
|
753
|
+
mcp: {},
|
|
754
|
+
provider: {}
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
writeFileSync(miraJsonPath, JSON.stringify(config, null, 2) + `
|
|
758
|
+
`);
|
|
759
|
+
console.log(`Created ${miraJsonPath} (template: ${template})`);
|
|
760
|
+
try {
|
|
761
|
+
const res = await apiFetch("/workspaces", { method: "POST", body: JSON.stringify({ path: absPath }) });
|
|
762
|
+
if (res.ok) {
|
|
763
|
+
const data = await res.json();
|
|
764
|
+
console.log(`Registered workspace: ${data.workspace.path}`);
|
|
765
|
+
}
|
|
766
|
+
} catch {}
|
|
767
|
+
try {
|
|
768
|
+
const home = process.env.HOME ?? "";
|
|
769
|
+
const fp = home ? `${home}/.mira/workspaces.json` : `${process.cwd()}/.mira/workspaces.json`;
|
|
770
|
+
const dir = fp.slice(0, fp.lastIndexOf("/"));
|
|
771
|
+
if (dir)
|
|
772
|
+
mkdirSync(dir, { recursive: true });
|
|
773
|
+
let existing = [];
|
|
774
|
+
if (existsSync(fp)) {
|
|
775
|
+
try {
|
|
776
|
+
const raw = readFileSync(fp, "utf-8");
|
|
777
|
+
const parsed = JSON.parse(raw);
|
|
778
|
+
existing = Array.isArray(parsed) ? parsed : parsed.workspaces ?? [];
|
|
779
|
+
} catch {}
|
|
780
|
+
}
|
|
781
|
+
if (!existing.some((w) => w.path === absPath)) {
|
|
782
|
+
const id = Buffer.from(absPath).toString("base64url");
|
|
783
|
+
existing.push({ id, path: absPath, name: absPath.split("/").pop() || absPath, addedAt: Date.now() });
|
|
784
|
+
writeFileSync(fp, JSON.stringify({ workspaces: existing }, null, 2) + `
|
|
785
|
+
`);
|
|
786
|
+
}
|
|
787
|
+
} catch {}
|
|
788
|
+
}
|
|
504
789
|
async function main() {
|
|
505
790
|
const rawCmd = Bun.argv[2] ?? "help";
|
|
506
791
|
if (rawCmd.startsWith("/")) {
|
|
@@ -610,6 +895,35 @@ async function main() {
|
|
|
610
895
|
process.exit(1);
|
|
611
896
|
}
|
|
612
897
|
return;
|
|
898
|
+
case "workspace":
|
|
899
|
+
case "workspaces":
|
|
900
|
+
if (sub === "list" || sub === null)
|
|
901
|
+
await cmdWorkspaceList();
|
|
902
|
+
else if (sub === "add")
|
|
903
|
+
await cmdWorkspaceAdd(opts);
|
|
904
|
+
else if (sub === "remove" || sub === "rm" || sub === "delete")
|
|
905
|
+
await cmdWorkspaceRemove(opts);
|
|
906
|
+
else if (sub === "switch")
|
|
907
|
+
await cmdWorkspaceSwitch(opts);
|
|
908
|
+
else {
|
|
909
|
+
if (sub && !["list", "add", "remove", "rm", "delete", "switch"].includes(sub)) {
|
|
910
|
+
opts["path"] = sub;
|
|
911
|
+
await cmdWorkspaceAdd(opts);
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
console.error(`unknown workspace subcommand: ${sub ?? ""} \u2014 try: list, add, remove, switch`);
|
|
915
|
+
process.exit(1);
|
|
916
|
+
}
|
|
917
|
+
return;
|
|
918
|
+
case "project":
|
|
919
|
+
case "projects":
|
|
920
|
+
if (sub === "init" || sub === null)
|
|
921
|
+
await cmdProjectInit(opts);
|
|
922
|
+
else {
|
|
923
|
+
console.error(`unknown project subcommand: ${sub ?? ""} \u2014 try: init`);
|
|
924
|
+
process.exit(1);
|
|
925
|
+
}
|
|
926
|
+
return;
|
|
613
927
|
case "complete":
|
|
614
928
|
case "autocomplete":
|
|
615
929
|
await cmdComplete(opts);
|