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.
Files changed (2) hide show
  1. package/dist/cli.js +314 -0
  2. 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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mira-cli-ts",
3
- "version": "0.26.5",
3
+ "version": "0.26.7",
4
4
  "description": "Mira CLI — thin wrapper around @mira/server (serve + session/agent/complete)",
5
5
  "type": "module",
6
6
  "bin": {