mira-cli-ts 0.26.6 → 0.26.8
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 +344 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -74,8 +74,37 @@ function apiUrl() {
|
|
|
74
74
|
return `http://127.0.0.1:${port}`;
|
|
75
75
|
return DEFAULT_API.replace(/\/$/, "");
|
|
76
76
|
}
|
|
77
|
+
function readMiraEnvFile() {
|
|
78
|
+
try {
|
|
79
|
+
const { readFileSync, existsSync } = __require("fs");
|
|
80
|
+
const { homedir } = __require("os");
|
|
81
|
+
const { join } = __require("path");
|
|
82
|
+
const cands = [
|
|
83
|
+
process.env.MIRA_DIR?.trim() ? join(process.env.MIRA_DIR.trim(), "mira.env") : null,
|
|
84
|
+
process.env.XDG_CONFIG_HOME?.trim() ? join(process.env.XDG_CONFIG_HOME.trim(), "mira", "mira.env") : null,
|
|
85
|
+
join(homedir(), ".mira", "mira.env")
|
|
86
|
+
].filter(Boolean);
|
|
87
|
+
for (const p of cands) {
|
|
88
|
+
try {
|
|
89
|
+
if (!existsSync(p))
|
|
90
|
+
continue;
|
|
91
|
+
for (const line of readFileSync(p, "utf-8").split(`
|
|
92
|
+
`)) {
|
|
93
|
+
const m = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)\s*$/);
|
|
94
|
+
const k = m?.[1];
|
|
95
|
+
if (k === "MIRA_TOKEN") {
|
|
96
|
+
const v = (m?.[2] ?? "").replace(/^(['"])(.*)\1$/, "$2").trim();
|
|
97
|
+
if (v)
|
|
98
|
+
return v;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
} catch {}
|
|
102
|
+
}
|
|
103
|
+
} catch {}
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
77
106
|
function token() {
|
|
78
|
-
return process.env.MIRA_TOKEN ?? "";
|
|
107
|
+
return process.env.MIRA_TOKEN ?? readMiraEnvFile() ?? "";
|
|
79
108
|
}
|
|
80
109
|
function authHeaders() {
|
|
81
110
|
const t = token();
|
|
@@ -136,6 +165,11 @@ Usage:
|
|
|
136
165
|
mira config get [key] Get config (or filtered by key)
|
|
137
166
|
mira config set <key> <value> Set config (dot notation, JSON value)
|
|
138
167
|
mira finding list [--status open] [--limit 20] List findings
|
|
168
|
+
mira workspace list List workspaces (recent)
|
|
169
|
+
mira workspace add <path> Add workspace (validates path)
|
|
170
|
+
mira workspace remove <id|path> Remove workspace
|
|
171
|
+
mira workspace switch <id|path> Switch workspace (sets cwd for new sessions)
|
|
172
|
+
mira project init [--template ts] [--path <dir>] Init mira.json in project
|
|
139
173
|
mira manager Active jobs + recent sessions
|
|
140
174
|
mira health Liveness (/healthz)
|
|
141
175
|
mira complete --prefix "..." [--suffix "..."] [--file path] Ghost-text completion
|
|
@@ -145,11 +179,15 @@ Usage:
|
|
|
145
179
|
Env:
|
|
146
180
|
MIRA_API_URL server URL (default http://127.0.0.1:4096)
|
|
147
181
|
MIRA_TOKEN bearer token
|
|
182
|
+
MIRA_WORKSPACE single workspace path (env)
|
|
183
|
+
MIRA_WORKSPACE_ROOTS comma-separated workspace roots (env)
|
|
148
184
|
|
|
149
185
|
Examples:
|
|
150
186
|
mira serve
|
|
151
187
|
mira session create --agent ask --title "Q&A"
|
|
152
188
|
mira session prompt --id abc --prompt "explain ./src/index.ts"
|
|
189
|
+
mira workspace add /path/to/repo
|
|
190
|
+
mira project init --template ts
|
|
153
191
|
mira skill list
|
|
154
192
|
mira command list
|
|
155
193
|
mira tool list
|
|
@@ -501,6 +539,282 @@ async function cmdHealth() {
|
|
|
501
539
|
}
|
|
502
540
|
console.log(JSON.stringify(await res.json(), null, 2));
|
|
503
541
|
}
|
|
542
|
+
async function cmdWorkspaceList() {
|
|
543
|
+
try {
|
|
544
|
+
const res = await apiFetch("/workspaces");
|
|
545
|
+
if (!res.ok) {
|
|
546
|
+
console.error(`workspace list failed: ${res.status} ${await res.text()}`);
|
|
547
|
+
process.exit(1);
|
|
548
|
+
}
|
|
549
|
+
const data = await res.json();
|
|
550
|
+
const list = data.workspaces ?? [];
|
|
551
|
+
if (list.length === 0) {
|
|
552
|
+
console.log("No workspaces \u2014 add one with: mira workspace add /path/to/repo");
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
for (const w of list) {
|
|
556
|
+
console.log(`${w.id.slice(0, 8)} ${w.path} (${w.name})`);
|
|
557
|
+
}
|
|
558
|
+
} catch (e) {
|
|
559
|
+
try {
|
|
560
|
+
const { readFileSync, existsSync } = __require("fs");
|
|
561
|
+
const home = process.env.HOME ?? "";
|
|
562
|
+
const fp = home ? `${home}/.mira/workspaces.json` : `${process.cwd()}/.mira/workspaces.json`;
|
|
563
|
+
if (existsSync(fp)) {
|
|
564
|
+
const raw = readFileSync(fp, "utf-8");
|
|
565
|
+
const parsed = JSON.parse(raw);
|
|
566
|
+
const list = Array.isArray(parsed) ? parsed : parsed.workspaces ?? [];
|
|
567
|
+
if (list.length === 0)
|
|
568
|
+
console.log("No workspaces");
|
|
569
|
+
else
|
|
570
|
+
for (const w of list)
|
|
571
|
+
console.log(`${(w.id ?? "").slice(0, 8)} ${w.path} (${w.name ?? w.path.split("/").pop()})`);
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
} catch {}
|
|
575
|
+
console.error(`workspace list failed: ${String(e.message ?? e)}`);
|
|
576
|
+
process.exit(1);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
async function cmdWorkspaceAdd(opts) {
|
|
580
|
+
const positional = Bun.argv.slice(3).filter((a) => !a.startsWith("-"));
|
|
581
|
+
let rawPath = String(opts.path ?? opts.p ?? opts["1"] ?? positional[1] ?? "").trim();
|
|
582
|
+
if (!rawPath || rawPath === "add")
|
|
583
|
+
rawPath = String(positional[1] ?? opts["1"] ?? "").trim();
|
|
584
|
+
if (!rawPath)
|
|
585
|
+
rawPath = String(positional[0] ?? "").trim();
|
|
586
|
+
if (!rawPath || rawPath === "add") {
|
|
587
|
+
console.error("workspace add requires <path> \u2014 e.g. mira workspace add /path/to/repo");
|
|
588
|
+
process.exit(1);
|
|
589
|
+
}
|
|
590
|
+
try {
|
|
591
|
+
const res = await apiFetch("/workspaces", { method: "POST", body: JSON.stringify({ path: rawPath }) });
|
|
592
|
+
if (!res.ok) {
|
|
593
|
+
console.error(`workspace add failed: ${res.status} ${await res.text()}`);
|
|
594
|
+
process.exit(1);
|
|
595
|
+
}
|
|
596
|
+
const data = await res.json();
|
|
597
|
+
console.log(`Added workspace: ${data.workspace.path} (${data.workspace.id.slice(0, 8)})`);
|
|
598
|
+
console.log(JSON.stringify(data.workspace, null, 2));
|
|
599
|
+
} catch (e) {
|
|
600
|
+
const msg = String(e.message ?? e);
|
|
601
|
+
const isConn = msg.includes("ECONNREFUSED") || msg.includes("fetch failed") || msg.includes("Connection refused") || msg.includes("Unable to connect") || msg.includes("ECONNRESET");
|
|
602
|
+
if (!isConn) {
|
|
603
|
+
console.error(`workspace add failed: ${msg}`);
|
|
604
|
+
process.exit(1);
|
|
605
|
+
}
|
|
606
|
+
try {
|
|
607
|
+
const { readFileSync, existsSync, mkdirSync, writeFileSync } = __require("fs");
|
|
608
|
+
const { resolve } = __require("path");
|
|
609
|
+
const absPath = rawPath.startsWith("/") ? rawPath : resolve(process.cwd(), rawPath);
|
|
610
|
+
if (!existsSync(absPath)) {
|
|
611
|
+
console.error(`path not found: ${absPath}`);
|
|
612
|
+
process.exit(1);
|
|
613
|
+
}
|
|
614
|
+
const home = process.env.HOME ?? "";
|
|
615
|
+
const fp = home ? `${home}/.mira/workspaces.json` : `${process.cwd()}/.mira/workspaces.json`;
|
|
616
|
+
const dir = fp.slice(0, fp.lastIndexOf("/"));
|
|
617
|
+
if (dir)
|
|
618
|
+
mkdirSync(dir, { recursive: true });
|
|
619
|
+
let existing = [];
|
|
620
|
+
if (existsSync(fp)) {
|
|
621
|
+
try {
|
|
622
|
+
const raw = readFileSync(fp, "utf-8");
|
|
623
|
+
const parsed = JSON.parse(raw);
|
|
624
|
+
existing = Array.isArray(parsed) ? parsed : parsed.workspaces ?? [];
|
|
625
|
+
} catch {}
|
|
626
|
+
}
|
|
627
|
+
if (existing.some((w) => w.path === absPath)) {
|
|
628
|
+
console.log(`Workspace already exists: ${absPath}`);
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
const id = Buffer.from(absPath).toString("base64url");
|
|
632
|
+
const entry = { id, path: absPath, name: absPath.split("/").pop() || absPath, addedAt: Date.now() };
|
|
633
|
+
existing.push(entry);
|
|
634
|
+
writeFileSync(fp, JSON.stringify({ workspaces: existing }, null, 2) + `
|
|
635
|
+
`);
|
|
636
|
+
console.log(`Added workspace (offline): ${absPath} (${id.slice(0, 8)})`);
|
|
637
|
+
} catch (err) {
|
|
638
|
+
console.error(`workspace add failed: ${String(err.message ?? err)}`);
|
|
639
|
+
process.exit(1);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
async function cmdWorkspaceRemove(opts) {
|
|
644
|
+
const positional = Bun.argv.slice(3).filter((a) => !a.startsWith("-"));
|
|
645
|
+
let id = String(opts.id ?? opts["1"] ?? positional[1] ?? "").trim();
|
|
646
|
+
if (!id || id === "remove" || id === "rm" || id === "delete")
|
|
647
|
+
id = String(positional[1] ?? opts["1"] ?? "").trim();
|
|
648
|
+
if (!id)
|
|
649
|
+
id = String(positional[0] ?? "").trim();
|
|
650
|
+
if (!id || ["remove", "rm", "delete"].includes(id)) {
|
|
651
|
+
console.error("workspace remove requires <id|path> \u2014 e.g. mira workspace remove <id>");
|
|
652
|
+
process.exit(1);
|
|
653
|
+
}
|
|
654
|
+
try {
|
|
655
|
+
const res = await apiFetch(`/workspaces/${encodeURIComponent(id)}`, { method: "DELETE" });
|
|
656
|
+
if (!res.ok) {
|
|
657
|
+
console.error(`workspace remove failed: ${res.status} ${await res.text()}`);
|
|
658
|
+
process.exit(1);
|
|
659
|
+
}
|
|
660
|
+
console.log(JSON.stringify(await res.json(), null, 2));
|
|
661
|
+
return;
|
|
662
|
+
} catch (e) {
|
|
663
|
+
const msg = String(e.message ?? e);
|
|
664
|
+
const isConn = msg.includes("ECONNREFUSED") || msg.includes("fetch failed") || msg.includes("Connection refused") || msg.includes("Unable to connect") || msg.includes("ECONNRESET");
|
|
665
|
+
if (!isConn) {
|
|
666
|
+
console.error(`workspace remove failed: ${msg}`);
|
|
667
|
+
process.exit(1);
|
|
668
|
+
}
|
|
669
|
+
try {
|
|
670
|
+
const { readFileSync, existsSync, writeFileSync } = __require("fs");
|
|
671
|
+
const home = process.env.HOME ?? "";
|
|
672
|
+
const fp = home ? `${home}/.mira/workspaces.json` : `${process.cwd()}/.mira/workspaces.json`;
|
|
673
|
+
if (!existsSync(fp)) {
|
|
674
|
+
console.error(`workspace not found: ${id}`);
|
|
675
|
+
process.exit(1);
|
|
676
|
+
}
|
|
677
|
+
const raw = readFileSync(fp, "utf-8");
|
|
678
|
+
const parsed = JSON.parse(raw);
|
|
679
|
+
const list = Array.isArray(parsed) ? parsed : parsed.workspaces ?? [];
|
|
680
|
+
const idx = list.findIndex((w) => w.id === id || w.path === id || w.id.startsWith(id) || w.path.endsWith(id));
|
|
681
|
+
if (idx === -1) {
|
|
682
|
+
console.error(`workspace not found: ${id}`);
|
|
683
|
+
process.exit(1);
|
|
684
|
+
}
|
|
685
|
+
const removed = list[idx];
|
|
686
|
+
const next = list.filter((_, i) => i !== idx);
|
|
687
|
+
writeFileSync(fp, JSON.stringify({ workspaces: next }, null, 2) + `
|
|
688
|
+
`);
|
|
689
|
+
console.log(`Removed workspace (offline): ${removed.path}`);
|
|
690
|
+
console.log(JSON.stringify({ ok: true, removed }, null, 2));
|
|
691
|
+
} catch (err) {
|
|
692
|
+
console.error(`workspace remove failed: ${String(err.message ?? err)}`);
|
|
693
|
+
process.exit(1);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
async function cmdWorkspaceSwitch(opts) {
|
|
698
|
+
const positional = Bun.argv.slice(3).filter((a) => !a.startsWith("-"));
|
|
699
|
+
let target = String(opts.path ?? opts["1"] ?? positional[1] ?? "").trim();
|
|
700
|
+
if (!target || target === "switch")
|
|
701
|
+
target = String(positional[1] ?? opts["1"] ?? "").trim();
|
|
702
|
+
if (!target)
|
|
703
|
+
target = String(positional[0] ?? "").trim();
|
|
704
|
+
try {
|
|
705
|
+
const res = await apiFetch("/workspaces");
|
|
706
|
+
if (res.ok) {
|
|
707
|
+
const data = await res.json();
|
|
708
|
+
const found = data.workspaces.find((w) => w.id === target || w.path === target || w.id.startsWith(target) || w.path.endsWith(target));
|
|
709
|
+
if (found) {
|
|
710
|
+
console.log(`Switched to workspace: ${found.path} (${found.name})`);
|
|
711
|
+
console.log(`Set MIRA_WORKSPACE=${found.path} for new sessions (export MIRA_WORKSPACE="${found.path}")`);
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
console.error(`workspace not found: ${target}`);
|
|
715
|
+
process.exit(1);
|
|
716
|
+
}
|
|
717
|
+
} catch {}
|
|
718
|
+
try {
|
|
719
|
+
const { readFileSync, existsSync } = __require("fs");
|
|
720
|
+
const home = process.env.HOME ?? "";
|
|
721
|
+
const fp = home ? `${home}/.mira/workspaces.json` : `${process.cwd()}/.mira/workspaces.json`;
|
|
722
|
+
if (existsSync(fp)) {
|
|
723
|
+
const raw = readFileSync(fp, "utf-8");
|
|
724
|
+
const parsed = JSON.parse(raw);
|
|
725
|
+
const list = Array.isArray(parsed) ? parsed : parsed.workspaces ?? [];
|
|
726
|
+
const found = list.find((w) => w.id === target || w.path === target || w.id.startsWith(target));
|
|
727
|
+
if (found) {
|
|
728
|
+
console.log(`Switched to workspace: ${found.path} (${found.name ?? found.path.split("/").pop()})`);
|
|
729
|
+
console.log(`Set MIRA_WORKSPACE=${found.path} for new sessions (export MIRA_WORKSPACE="${found.path}")`);
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
} catch {}
|
|
734
|
+
console.error(`workspace not found: ${target}`);
|
|
735
|
+
process.exit(1);
|
|
736
|
+
}
|
|
737
|
+
async function cmdProjectInit(opts) {
|
|
738
|
+
const template = String(opts.template ?? opts.t ?? "default").trim() || "default";
|
|
739
|
+
const targetPath = String(opts.path ?? opts.p ?? positionalPath() ?? process.cwd()).trim() || process.cwd();
|
|
740
|
+
function positionalPath() {
|
|
741
|
+
const positional = Bun.argv.slice(3).filter((a) => !a.startsWith("-"));
|
|
742
|
+
const tIdx = Bun.argv.indexOf("--template");
|
|
743
|
+
const tIdx2 = Bun.argv.indexOf("-t");
|
|
744
|
+
let afterTemplate = -1;
|
|
745
|
+
if (tIdx !== -1)
|
|
746
|
+
afterTemplate = tIdx + 2;
|
|
747
|
+
else if (tIdx2 !== -1)
|
|
748
|
+
afterTemplate = tIdx2 + 2;
|
|
749
|
+
if (afterTemplate !== -1 && Bun.argv[afterTemplate] && !Bun.argv[afterTemplate].startsWith("-"))
|
|
750
|
+
return Bun.argv[afterTemplate];
|
|
751
|
+
if (template !== "default" && positional.length > 0) {
|
|
752
|
+
if (positional[0] === template)
|
|
753
|
+
return positional[1];
|
|
754
|
+
}
|
|
755
|
+
return positional[0];
|
|
756
|
+
}
|
|
757
|
+
const { existsSync, mkdirSync, writeFileSync, readFileSync } = __require("fs");
|
|
758
|
+
const { resolve } = __require("path");
|
|
759
|
+
const absPath = targetPath.startsWith("/") ? targetPath : resolve(process.cwd(), targetPath);
|
|
760
|
+
try {
|
|
761
|
+
mkdirSync(absPath, { recursive: true });
|
|
762
|
+
} catch {}
|
|
763
|
+
const miraJsonPath = `${absPath}/mira.json`;
|
|
764
|
+
if (existsSync(miraJsonPath)) {
|
|
765
|
+
console.error(`mira.json already exists at ${miraJsonPath} \u2014 refusing to overwrite`);
|
|
766
|
+
process.exit(1);
|
|
767
|
+
}
|
|
768
|
+
let config;
|
|
769
|
+
if (template === "ts" || template === "typescript") {
|
|
770
|
+
config = {
|
|
771
|
+
model: "openrouter/anthropic/claude-sonnet-4",
|
|
772
|
+
permission: { bash: "ask", read: "allow", write: "ask", edit: "ask" },
|
|
773
|
+
guardrails: { allowedRoots: ["."], enforce: false },
|
|
774
|
+
mcp: {},
|
|
775
|
+
provider: {},
|
|
776
|
+
agents: {}
|
|
777
|
+
};
|
|
778
|
+
} else {
|
|
779
|
+
config = {
|
|
780
|
+
model: "openrouter/anthropic/claude-sonnet-4",
|
|
781
|
+
permission: {},
|
|
782
|
+
mcp: {},
|
|
783
|
+
provider: {}
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
writeFileSync(miraJsonPath, JSON.stringify(config, null, 2) + `
|
|
787
|
+
`);
|
|
788
|
+
console.log(`Created ${miraJsonPath} (template: ${template})`);
|
|
789
|
+
try {
|
|
790
|
+
const res = await apiFetch("/workspaces", { method: "POST", body: JSON.stringify({ path: absPath }) });
|
|
791
|
+
if (res.ok) {
|
|
792
|
+
const data = await res.json();
|
|
793
|
+
console.log(`Registered workspace: ${data.workspace.path}`);
|
|
794
|
+
}
|
|
795
|
+
} catch {}
|
|
796
|
+
try {
|
|
797
|
+
const home = process.env.HOME ?? "";
|
|
798
|
+
const fp = home ? `${home}/.mira/workspaces.json` : `${process.cwd()}/.mira/workspaces.json`;
|
|
799
|
+
const dir = fp.slice(0, fp.lastIndexOf("/"));
|
|
800
|
+
if (dir)
|
|
801
|
+
mkdirSync(dir, { recursive: true });
|
|
802
|
+
let existing = [];
|
|
803
|
+
if (existsSync(fp)) {
|
|
804
|
+
try {
|
|
805
|
+
const raw = readFileSync(fp, "utf-8");
|
|
806
|
+
const parsed = JSON.parse(raw);
|
|
807
|
+
existing = Array.isArray(parsed) ? parsed : parsed.workspaces ?? [];
|
|
808
|
+
} catch {}
|
|
809
|
+
}
|
|
810
|
+
if (!existing.some((w) => w.path === absPath)) {
|
|
811
|
+
const id = Buffer.from(absPath).toString("base64url");
|
|
812
|
+
existing.push({ id, path: absPath, name: absPath.split("/").pop() || absPath, addedAt: Date.now() });
|
|
813
|
+
writeFileSync(fp, JSON.stringify({ workspaces: existing }, null, 2) + `
|
|
814
|
+
`);
|
|
815
|
+
}
|
|
816
|
+
} catch {}
|
|
817
|
+
}
|
|
504
818
|
async function main() {
|
|
505
819
|
const rawCmd = Bun.argv[2] ?? "help";
|
|
506
820
|
if (rawCmd.startsWith("/")) {
|
|
@@ -610,6 +924,35 @@ async function main() {
|
|
|
610
924
|
process.exit(1);
|
|
611
925
|
}
|
|
612
926
|
return;
|
|
927
|
+
case "workspace":
|
|
928
|
+
case "workspaces":
|
|
929
|
+
if (sub === "list" || sub === null)
|
|
930
|
+
await cmdWorkspaceList();
|
|
931
|
+
else if (sub === "add")
|
|
932
|
+
await cmdWorkspaceAdd(opts);
|
|
933
|
+
else if (sub === "remove" || sub === "rm" || sub === "delete")
|
|
934
|
+
await cmdWorkspaceRemove(opts);
|
|
935
|
+
else if (sub === "switch")
|
|
936
|
+
await cmdWorkspaceSwitch(opts);
|
|
937
|
+
else {
|
|
938
|
+
if (sub && !["list", "add", "remove", "rm", "delete", "switch"].includes(sub)) {
|
|
939
|
+
opts["path"] = sub;
|
|
940
|
+
await cmdWorkspaceAdd(opts);
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
console.error(`unknown workspace subcommand: ${sub ?? ""} \u2014 try: list, add, remove, switch`);
|
|
944
|
+
process.exit(1);
|
|
945
|
+
}
|
|
946
|
+
return;
|
|
947
|
+
case "project":
|
|
948
|
+
case "projects":
|
|
949
|
+
if (sub === "init" || sub === null)
|
|
950
|
+
await cmdProjectInit(opts);
|
|
951
|
+
else {
|
|
952
|
+
console.error(`unknown project subcommand: ${sub ?? ""} \u2014 try: init`);
|
|
953
|
+
process.exit(1);
|
|
954
|
+
}
|
|
955
|
+
return;
|
|
613
956
|
case "complete":
|
|
614
957
|
case "autocomplete":
|
|
615
958
|
await cmdComplete(opts);
|