@higrowth/cli 0.1.0 → 0.2.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.
Files changed (3) hide show
  1. package/README.md +21 -8
  2. package/dist/index.js +73 -42
  3. package/package.json +6 -2
package/README.md CHANGED
@@ -1,15 +1,17 @@
1
1
  # @higrowth/cli
2
2
 
3
3
  Log in to a Higrowth workspace from your terminal and install the
4
- Claude Code skills that drive it. The replacement for `cp -r skills/
5
- higrowth ~/.claude/skills/` and copy-paste tokens.
4
+ Higrowth playbook skills into your AI agent of choice. Supports
5
+ **Claude Code**, **Claude Desktop**, **Cursor**, and **Codex**
6
+ (CLI + Desktop + IDE extension share one config). Replaces the old
7
+ `cp -r skills/higrowth ~/.claude/skills/` + copy-paste-token dance.
6
8
 
7
9
  ## Install
8
10
 
9
11
  ```bash
10
12
  npm install -g @higrowth/cli
11
13
  # or, for the curl-piped flow:
12
- curl -fsSL https://hg-engine.app/install.sh | sh
14
+ curl -fsSL https://app.higrowth.ai/install.sh | sh
13
15
  ```
14
16
 
15
17
  ## Quick start
@@ -33,7 +35,7 @@ higrowth skills install
33
35
  | `higrowth login [--host URL] [--device]` | Browser-auth handshake (PKCE by default; `--device` for SSH/containers). Writes the MCP entry into `~/.claude/mcp.json`. |
34
36
  | `higrowth whoami` | Show the currently-configured workspace + token name. |
35
37
  | `higrowth logout` | Remove the higrowth entry from `~/.claude/mcp.json`. Local-only — does NOT revoke the token (do that in Settings). |
36
- | `higrowth skills install [--target TARGET]` | Copy the five skill files into your agent's skills dir. Auto-detects Claude Code, Cursor, Claude Desktop; pass `--target claude\|cursor\|claude-desktop\|all` to override. |
38
+ | `higrowth skills install [--target TARGET]` | Copy the five skill files into your agent's skills dir. Auto-detects Claude Code / Cursor / Claude Desktop / Codex; pass `--target claude\|cursor\|claude-desktop\|codex\|all` to override. |
37
39
  | `higrowth skills list` | Show installed skills + which are out of date relative to the bundle. |
38
40
  | `higrowth skills update` | Re-install (overwrite) the latest skill versions. |
39
41
 
@@ -41,19 +43,30 @@ higrowth skills install
41
43
 
42
44
  | Flag | Default | What it does |
43
45
  |------|---------|--------------|
44
- | `--host URL` | `https://hg-engine.app` | Override the Higrowth host (for self-hosted or staging). |
46
+ | `--host URL` | `https://app.higrowth.ai` | Override the Higrowth host (for self-hosted or staging). |
45
47
  | `--device` | off | Use OAuth 2.0 device-code flow instead of PKCE. For SSH / containers. |
46
- | `--target TARGET` | auto-detect | `claude` / `cursor` / `claude-desktop` / `all`. |
48
+ | `--target TARGET` | auto-detect | `claude` / `cursor` / `claude-desktop` / `codex` / `all`. `codex` covers Codex CLI + Desktop + IDE extension via one shared `~/.codex/config.toml`. |
47
49
  | `--config PATH` | platform default | Override the MCP config file path. |
48
50
 
51
+ ## Where things land
52
+
53
+ | Target | MCP config | Skills dir |
54
+ |--------|------------|------------|
55
+ | `claude` (Claude Code) | `~/.claude/mcp.json` | `~/.claude/skills/<slug>/SKILL.md` |
56
+ | `cursor` | `~/.cursor/mcp.json` | `~/.cursor/skills/<slug>/SKILL.md` |
57
+ | `claude-desktop` | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) | `~/Library/Application Support/Claude/skills/<slug>/SKILL.md` |
58
+ | `codex` | `~/.codex/config.toml` (TOML, not JSON) | `~/.agents/skills/<slug>/SKILL.md` |
59
+
60
+ For Codex specifically: one write covers Codex CLI, Codex Desktop, and the Codex IDE extension because all three read the same `~/.codex/config.toml`. No per-app duplication.
61
+
49
62
  ## What it does NOT do
50
63
 
51
64
  - Doesn't manage tokens server-side. Token rotation / revocation
52
65
  happens in the Higrowth UI under Settings → API tokens.
53
66
  - Doesn't restart Claude Code for you. After `login` or `skills
54
67
  install`, restart manually so the new config takes effect.
55
- - Doesn't store secrets outside `~/.claude/mcp.json` (mode 600 where
56
- possible).
68
+ - Doesn't store secrets outside the per-target config file you're
69
+ installing into (mode 600 where the filesystem supports it).
57
70
 
58
71
  ## License
59
72
 
package/dist/index.js CHANGED
@@ -74,12 +74,14 @@ function base64UrlEncode(buf) {
74
74
  import { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync } from "fs";
75
75
  import { dirname, join } from "path";
76
76
  import { homedir, platform } from "os";
77
+ import TOML from "@iarna/toml";
77
78
  function configPaths() {
78
79
  const home = homedir();
79
80
  return [
80
- { target: "claude", path: join(home, ".claude", "mcp.json") },
81
- { target: "cursor", path: join(home, ".cursor", "mcp.json") },
82
- { target: "claude-desktop", path: claudeDesktopPath(home) }
81
+ { target: "claude", path: join(home, ".claude", "mcp.json"), format: "json", label: "Claude Code" },
82
+ { target: "cursor", path: join(home, ".cursor", "mcp.json"), format: "json", label: "Cursor" },
83
+ { target: "claude-desktop", path: claudeDesktopPath(home), format: "json", label: "Claude Desktop" },
84
+ { target: "codex", path: join(home, ".codex", "config.toml"), format: "toml", label: "Codex (CLI + Desktop + IDE)" }
83
85
  ];
84
86
  }
85
87
  function claudeDesktopPath(home) {
@@ -118,55 +120,73 @@ function resolveConfigPath(target) {
118
120
  if (!found) throw new Error(`Unknown target: ${resolved}`);
119
121
  return found;
120
122
  }
121
- function readConfig(path) {
123
+ function serversKey(format) {
124
+ return format === "toml" ? "mcp_servers" : "mcpServers";
125
+ }
126
+ function headersKey(format) {
127
+ return format === "toml" ? "http_headers" : "headers";
128
+ }
129
+ function readConfig(path, format) {
122
130
  if (!existsSync(path)) return {};
131
+ const raw = readFileSync(path, "utf-8");
123
132
  try {
124
- const raw = readFileSync(path, "utf-8");
125
- return JSON.parse(raw);
133
+ return format === "toml" ? TOML.parse(raw) : JSON.parse(raw);
126
134
  } catch (err) {
127
135
  throw new Error(
128
136
  `Could not parse existing MCP config at ${path}: ${err instanceof Error ? err.message : String(err)}`
129
137
  );
130
138
  }
131
139
  }
140
+ function serialize(cfg, format) {
141
+ if (format === "toml") {
142
+ return TOML.stringify(cfg);
143
+ }
144
+ return `${JSON.stringify(cfg, null, 2)}
145
+ `;
146
+ }
132
147
  function writeHigrowthEntry(input) {
133
- const existing = readConfig(input.path);
148
+ const existing = readConfig(input.path, input.format);
149
+ const sk = serversKey(input.format);
150
+ const hk = headersKey(input.format);
151
+ const existingServers = existing[sk] ?? {};
134
152
  const next = {
135
153
  ...existing,
136
- mcpServers: {
137
- ...existing.mcpServers ?? {},
154
+ [sk]: {
155
+ ...existingServers,
138
156
  higrowth: {
139
157
  url: `${input.host}/api/mcp`,
140
- headers: {
158
+ [hk]: {
141
159
  Authorization: `Bearer ${input.token}`
142
160
  }
143
161
  }
144
162
  }
145
163
  };
146
164
  mkdirSync(dirname(input.path), { recursive: true });
147
- writeFileSync(input.path, `${JSON.stringify(next, null, 2)}
148
- `, "utf-8");
165
+ writeFileSync(input.path, serialize(next, input.format), "utf-8");
149
166
  tryChmod600(input.path);
150
167
  }
151
- function removeHigrowthEntry(path) {
168
+ function removeHigrowthEntry(path, format) {
152
169
  if (!existsSync(path)) return false;
153
- const existing = readConfig(path);
154
- if (!existing.mcpServers || !existing.mcpServers.higrowth) return false;
155
- const { higrowth: _, ...rest } = existing.mcpServers;
156
- const next = { ...existing, mcpServers: rest };
170
+ const existing = readConfig(path, format);
171
+ const sk = serversKey(format);
172
+ const servers = existing[sk];
173
+ if (!servers || !servers.higrowth) return false;
174
+ const { higrowth: _drop, ...rest } = servers;
175
+ const next = { ...existing, [sk]: rest };
157
176
  if (Object.keys(rest).length === 0) {
158
- delete next.mcpServers;
177
+ delete next[sk];
159
178
  }
160
- writeFileSync(path, `${JSON.stringify(next, null, 2)}
161
- `, "utf-8");
179
+ writeFileSync(path, serialize(next, format), "utf-8");
162
180
  return true;
163
181
  }
164
- function readHigrowthEntry(path) {
182
+ function readHigrowthEntry(path, format) {
165
183
  if (!existsSync(path)) return null;
166
- const cfg = readConfig(path);
167
- const entry = cfg.mcpServers?.higrowth;
184
+ const cfg = readConfig(path, format);
185
+ const servers = cfg[serversKey(format)];
186
+ const entry = servers?.higrowth;
168
187
  if (!entry?.url) return null;
169
- const authHeader = entry.headers?.Authorization ?? "";
188
+ const headers = entry[headersKey(format)] ?? {};
189
+ const authHeader = headers.Authorization ?? "";
170
190
  const match = authHeader.match(/^Bearer\s+(\S+)$/);
171
191
  if (!match) return null;
172
192
  return { url: entry.url, token: match[1] };
@@ -196,8 +216,14 @@ async function loginCommand(opts) {
196
216
  } else {
197
217
  ({ token, organizationId } = await runPkceFlow(api, client));
198
218
  }
199
- const cfg = opts.configOverride !== void 0 ? { target: opts.target === "auto" ? "claude" : opts.target, path: opts.configOverride } : resolveConfigPath(opts.target);
200
- writeHigrowthEntry({ path: cfg.path, host, token });
219
+ const cfg = opts.configOverride !== void 0 ? {
220
+ target: opts.target === "auto" ? "claude" : opts.target,
221
+ path: opts.configOverride,
222
+ // Infer format from extension when the user supplied a custom path.
223
+ // `.toml` → codex-style; anything else (.json by convention) → JSON.
224
+ format: opts.configOverride.toLowerCase().endsWith(".toml") ? "toml" : "json"
225
+ } : resolveConfigPath(opts.target);
226
+ writeHigrowthEntry({ path: cfg.path, format: cfg.format, host, token });
201
227
  process.stdout.write(`
202
228
  \u2713 Logged in to ${host}
203
229
  `);
@@ -306,7 +332,7 @@ function tryHostname() {
306
332
  async function logoutCommand() {
307
333
  let removed = 0;
308
334
  for (const cfg of configPaths()) {
309
- if (removeHigrowthEntry(cfg.path)) {
335
+ if (removeHigrowthEntry(cfg.path, cfg.format)) {
310
336
  process.stdout.write(`\u2713 Removed higrowth entry from ${cfg.path}
311
337
  `);
312
338
  removed += 1;
@@ -325,24 +351,25 @@ async function logoutCommand() {
325
351
  async function whoamiCommand() {
326
352
  let foundAny = false;
327
353
  for (const cfg of configPaths()) {
328
- const entry = readHigrowthEntry(cfg.path);
354
+ const entry = readHigrowthEntry(cfg.path, cfg.format);
329
355
  if (!entry) continue;
330
356
  foundAny = true;
331
- process.stdout.write(`${cfg.target.padEnd(15)} ${entry.url}
357
+ const heading = cfg.label.padEnd(28);
358
+ process.stdout.write(`${heading} ${entry.url}
332
359
  `);
333
- process.stdout.write(`${"".padEnd(15)} ${maskToken(entry.token)}
360
+ process.stdout.write(`${"".padEnd(28)} ${maskToken(entry.token)}
334
361
  `);
335
- process.stdout.write(`${"".padEnd(15)} ${cfg.path}
362
+ process.stdout.write(`${"".padEnd(28)} ${cfg.path}
336
363
 
337
364
  `);
338
365
  const summary = await probeToken(entry.url, entry.token);
339
366
  if (summary) {
340
- process.stdout.write(`${"".padEnd(15)} \u2713 ${summary}
367
+ process.stdout.write(`${"".padEnd(28)} \u2713 ${summary}
341
368
 
342
369
  `);
343
370
  } else {
344
371
  process.stdout.write(
345
- `${"".padEnd(15)} \u2717 token did not authenticate (revoked or stale?)
372
+ `${"".padEnd(28)} \u2717 token did not authenticate (revoked or stale?)
346
373
 
347
374
  `
348
375
  );
@@ -1027,7 +1054,7 @@ async function skillsInstallCommand(opts) {
1027
1054
  const targets = resolveTargets(opts.target);
1028
1055
  if (targets.length === 0) {
1029
1056
  process.stdout.write(
1030
- "No supported skills directory detected. Pass --target claude|cursor|claude-desktop|all.\n"
1057
+ "No supported skills directory detected. Pass --target claude|cursor|claude-desktop|codex|all.\n"
1031
1058
  );
1032
1059
  process.exitCode = 1;
1033
1060
  return;
@@ -1089,7 +1116,8 @@ function resolveTargets(t) {
1089
1116
  const all = [
1090
1117
  "claude",
1091
1118
  "cursor",
1092
- "claude-desktop"
1119
+ "claude-desktop",
1120
+ "codex"
1093
1121
  ];
1094
1122
  if (t === "all") return all;
1095
1123
  if (t === "auto") {
@@ -1102,6 +1130,7 @@ function skillsDir(t) {
1102
1130
  const home = homedir2();
1103
1131
  if (t === "claude") return join2(home, ".claude", "skills");
1104
1132
  if (t === "cursor") return join2(home, ".cursor", "skills");
1133
+ if (t === "codex") return join2(home, ".agents", "skills");
1105
1134
  if (platform3() === "darwin") {
1106
1135
  return join2(home, "Library", "Application Support", "Claude", "skills");
1107
1136
  }
@@ -1129,8 +1158,8 @@ function compareSkill(path, skill) {
1129
1158
  }
1130
1159
 
1131
1160
  // src/index.ts
1132
- var VERSION = "0.1.0";
1133
- var DEFAULT_HOST = "https://hg-engine.app";
1161
+ var VERSION = "0.1.1";
1162
+ var DEFAULT_HOST = "https://app.higrowth.ai";
1134
1163
  var USAGE = `higrowth \u2014 Connect your agent to a Higrowth workspace
1135
1164
 
1136
1165
  USAGE
@@ -1167,13 +1196,15 @@ FLAGS
1167
1196
  --host URL Override Higrowth host (default: ${DEFAULT_HOST}).
1168
1197
  --device Use OAuth device-code flow instead of PKCE.
1169
1198
  --target TARGET One of: auto (default) | claude | cursor |
1170
- claude-desktop | all.
1199
+ claude-desktop | codex | all.
1200
+ codex covers Codex CLI, Codex Desktop, and the
1201
+ Codex IDE extension \u2014 they share one config.
1171
1202
  --config PATH Override the MCP config file path.
1172
1203
 
1173
1204
  EXAMPLES
1174
1205
  higrowth login
1175
1206
  higrowth login --device
1176
- higrowth login --host https://staging.hg-engine.app
1207
+ higrowth login --host https://staging.app.higrowth.ai
1177
1208
  higrowth skills install --target all
1178
1209
  higrowth whoami
1179
1210
  `;
@@ -1207,11 +1238,11 @@ function resolveTarget(raw) {
1207
1238
  if (raw === void 0) return "auto";
1208
1239
  if (raw === true) return "auto";
1209
1240
  const s = String(raw).toLowerCase();
1210
- if (s === "auto" || s === "claude" || s === "cursor" || s === "claude-desktop" || s === "all") {
1241
+ if (s === "auto" || s === "claude" || s === "cursor" || s === "claude-desktop" || s === "codex" || s === "all") {
1211
1242
  return s;
1212
1243
  }
1213
1244
  throw new Error(
1214
- `Unknown target "${s}". Use auto | claude | cursor | claude-desktop | all.`
1245
+ `Unknown target "${s}". Use auto | claude | cursor | claude-desktop | codex | all.`
1215
1246
  );
1216
1247
  }
1217
1248
  async function main() {
@@ -1236,7 +1267,7 @@ async function main() {
1236
1267
  case "login":
1237
1268
  if (target === "all") {
1238
1269
  process.stderr.write(
1239
- "login writes to a single config file. Use --target claude | cursor | claude-desktop (or omit for auto-detect). To install skills across all agents, use `higrowth skills install --target all` after login.\n"
1270
+ "login writes to a single config file. Use --target claude | cursor | claude-desktop | codex (or omit for auto-detect). To install skills across all agents, use `higrowth skills install --target all` after login.\n"
1240
1271
  );
1241
1272
  process.exitCode = 2;
1242
1273
  return;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@higrowth/cli",
3
- "version": "0.1.0",
4
- "description": "Higrowth CLI — log in via browser, install Claude Code skills, manage the MCP connection.",
3
+ "version": "0.2.0",
4
+ "description": "Higrowth CLI — log in via browser, install Claude Code / Codex skills, manage the MCP connection.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
7
7
  "bin": {
@@ -23,6 +23,7 @@
23
23
  "access": "public"
24
24
  },
25
25
  "devDependencies": {
26
+ "@types/iarna__toml": "^2.0.5",
26
27
  "@types/node": "^22.10.5",
27
28
  "tsup": "^8.5.0",
28
29
  "typescript": "^5.6.3"
@@ -33,5 +34,8 @@
33
34
  "type": "git",
34
35
  "url": "git+https://github.com/higrowth-ai/hg-engine.git",
35
36
  "directory": "cli"
37
+ },
38
+ "dependencies": {
39
+ "@iarna/toml": "^2.2.5"
36
40
  }
37
41
  }