macaw-ai 0.9.10 → 0.9.12

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 (3) hide show
  1. package/README.md +4 -2
  2. package/bin/macaw.js +74 -19
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -42,8 +42,10 @@ because it has no path to the place approvals are given.
42
42
  Anthropic, LangChain, MCP, LiteLLM and Pydantic AI, with the examples ready to
43
43
  run.
44
44
 
45
- Both are free, and both create a workspace on first run: a link appears in
46
- your terminal, you approve it in a browser, and that is the account.
45
+ Both are free. The first install creates a workspace: a link appears in your
46
+ terminal, you approve it in a browser, and that is the account. Later installs
47
+ find that key and join the same workspace, so choosing both surfaces above
48
+ asks you once, not twice.
47
49
 
48
50
  ## Licence
49
51
 
package/bin/macaw.js CHANGED
@@ -1,26 +1,32 @@
1
1
  #!/usr/bin/env node
2
- // `npx macaw-ai install` - the one name to remember. It installs nothing
3
- // itself; it asks which surface you want governed and hands off to the
4
- // package that does that one thing.
2
+ // `macaw-ai install` - the one name to remember. It installs nothing itself;
3
+ // it asks which surface you want governed and hands off to the package that
4
+ // does that one thing.
5
5
  //
6
6
  // Kept separate from those packages so this name never has to narrow. It
7
7
  // means "MACAW" today and will still mean it when there are six surfaces.
8
8
 
9
9
  import { spawnSync } from "node:child_process";
10
10
  import { createInterface } from "node:readline/promises";
11
+ import fs from "node:fs";
12
+ import path from "node:path";
11
13
 
12
14
  const SURFACES = [
13
15
  {
14
16
  key: "1",
15
17
  pkg: "@macaw-ai/coding-agents",
16
- label: "Claude Code and Codex on this machine",
18
+ label: "Claude Code and Codex",
17
19
  detail: "every tool call stops at a hook and waits for a decision",
20
+ command: "macaw-sidecar",
21
+ subcommand: true,
18
22
  },
19
23
  {
20
24
  key: "2",
21
25
  pkg: "@macaw-ai/client",
22
- label: "a Python project",
23
- detail: "the SDK and adapters, for agents you write yourself",
26
+ label: "the MACAW SDK Client Library and secureAI adapters",
27
+ detail: "drop-in adapters for popular agentic frameworks",
28
+ command: "macaw-client",
29
+ subcommand: true,
24
30
  },
25
31
  ];
26
32
 
@@ -28,12 +34,58 @@ const C = process.stdout.isTTY
28
34
  ? { b: "\x1b[1m", dim: "\x1b[2m", c: "\x1b[36m", x: "\x1b[0m" }
29
35
  : { b: "", dim: "", c: "", x: "" };
30
36
 
31
- function run(pkg, args) {
32
- console.log(`\n${C.dim} → npx ${pkg} ${args.join(" ")}${C.x}\n`);
33
- const r = spawnSync("npx", ["-y", pkg, ...args], { stdio: "inherit" });
37
+ function sh(cmd, args, env) {
38
+ console.log(`\n${C.dim} → ${cmd} ${args.join(" ")}${C.x}\n`);
39
+ const r = spawnSync(cmd, args, { stdio: "inherit", env: { ...process.env, ...env } });
34
40
  return r.status ?? 1;
35
41
  }
36
42
 
43
+ // Installed, then run by name. npx links no commands, so the launchers it
44
+ // leaves behind can only be reached by their full path inside an npm cache
45
+ // directory - which is how an install ended up telling people to type ninety
46
+ // characters.
47
+ function run(surface, args, env) {
48
+ const code = sh("npm", ["install", "-g", surface.pkg], env);
49
+ if (code !== 0) return code;
50
+ const rest = surface.subcommand ? args : args.filter((a) => a !== "install");
51
+ return sh(surface.command, rest, env);
52
+ }
53
+
54
+ // The coding-agents installer writes its key here. Reading it back lets the
55
+ // second surface join that workspace without asking for a key the user
56
+ // approved thirty seconds earlier.
57
+ function keyFromThisDirectory() {
58
+ try {
59
+ const cfg = JSON.parse(fs.readFileSync(path.join(process.cwd(), ".macaw", "config.json"), "utf8"));
60
+ if (typeof cfg?.api_key === "string" && cfg.api_key.startsWith("mw_")) {
61
+ return { MACAW_API_KEY: cfg.api_key, MACAW_WORKSPACE: cfg.workspace || "" };
62
+ }
63
+ } catch {
64
+ // No install here, or not readable. The next installer asks, as it would.
65
+ }
66
+ return undefined;
67
+ }
68
+
69
+ // Both surfaces, in this order. Coding agents first because that install is
70
+ // the one that creates the workspace, and the key it writes is then handed to
71
+ // the client rather than asked for again.
72
+ //
73
+ // The order is load-bearing the other way too: coding-agents writes
74
+ // $PWD/.macaw/config.json, which the client's lookup reads. The client writes
75
+ // into a subdirectory it chooses, which nothing else can be expected to find.
76
+ function both(args) {
77
+ const steps = args.length ? args : ["install"];
78
+ for (const [i, s] of SURFACES.entries()) {
79
+ // Two installers each printing their own banner reads as the thing having
80
+ // restarted. Say which step this is.
81
+ console.log("");
82
+ console.log(`${C.b} ── ${i + 1} of ${SURFACES.length} · ${s.label} ──${C.x}`);
83
+ const code = run(s, steps, i === 0 ? undefined : keyFromThisDirectory());
84
+ if (code !== 0) return code;
85
+ }
86
+ return 0;
87
+ }
88
+
37
89
  async function main() {
38
90
  const args = process.argv.slice(2);
39
91
 
@@ -41,7 +93,14 @@ async function main() {
41
93
  // macaw-ai install --coding-agents
42
94
  const named = SURFACES.find((s) => args.includes(`--${s.pkg.split("/")[1]}`));
43
95
  if (named) {
44
- process.exit(run(named.pkg, args.filter((a) => !a.startsWith("--macaw") && a !== `--${named.pkg.split("/")[1]}`)));
96
+ process.exit(run(named, args.filter((a) => !a.startsWith("--macaw") && a !== `--${named.pkg.split("/")[1]}`)));
97
+ }
98
+
99
+ // macaw-ai install --both
100
+ // The menu's third choice, as a flag. The other two already have one, and
101
+ // without this there is no way to ask for both without a terminal.
102
+ if (args.includes("--both")) {
103
+ process.exit(both(args.filter((a) => a !== "--both")));
45
104
  }
46
105
 
47
106
  console.log("");
@@ -60,7 +119,9 @@ async function main() {
60
119
  // something nobody asked for. Say what to type instead.
61
120
  if (!process.stdin.isTTY) {
62
121
  console.log(" Nothing is attached to answer, so pick one directly:");
63
- for (const s of SURFACES) console.log(` npx ${s.pkg} install`);
122
+ for (const s of SURFACES) {
123
+ console.log(` npm install -g ${s.pkg} && ${s.command}${s.subcommand ? " install" : ""}`);
124
+ }
64
125
  console.log("");
65
126
  process.exit(1);
66
127
  }
@@ -70,20 +131,14 @@ async function main() {
70
131
  rl.close();
71
132
 
72
133
  const rest = args.length ? args : ["install"];
73
- if (choice === "3") {
74
- for (const s of SURFACES) {
75
- const code = run(s.pkg, rest);
76
- if (code !== 0) process.exit(code);
77
- }
78
- process.exit(0);
79
- }
134
+ if (choice === "3") process.exit(both(rest));
80
135
 
81
136
  const picked = SURFACES.find((s) => s.key === choice);
82
137
  if (!picked) {
83
138
  console.log(" Not a choice.");
84
139
  process.exit(1);
85
140
  }
86
- process.exit(run(picked.pkg, rest));
141
+ process.exit(run(picked, rest));
87
142
  }
88
143
 
89
144
  main().catch((e) => { console.error(e.message); process.exit(1); });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "macaw-ai",
3
- "version": "0.9.10",
3
+ "version": "0.9.12",
4
4
  "description": "Deterministic controls for agents. Ask MACAW to govern your coding agent or your Python project, and it installs the right thing.",
5
5
  "type": "module",
6
6
  "bin": {