@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.
- package/README.md +21 -8
- package/dist/index.js +73 -42
- 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
|
-
|
|
5
|
-
|
|
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://
|
|
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
|
|
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://
|
|
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
|
|
56
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
137
|
-
...
|
|
154
|
+
[sk]: {
|
|
155
|
+
...existingServers,
|
|
138
156
|
higrowth: {
|
|
139
157
|
url: `${input.host}/api/mcp`,
|
|
140
|
-
|
|
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,
|
|
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
|
-
|
|
155
|
-
const
|
|
156
|
-
|
|
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
|
|
177
|
+
delete next[sk];
|
|
159
178
|
}
|
|
160
|
-
writeFileSync(path,
|
|
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
|
|
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
|
|
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 ? {
|
|
200
|
-
|
|
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
|
-
|
|
357
|
+
const heading = cfg.label.padEnd(28);
|
|
358
|
+
process.stdout.write(`${heading} ${entry.url}
|
|
332
359
|
`);
|
|
333
|
-
process.stdout.write(`${"".padEnd(
|
|
360
|
+
process.stdout.write(`${"".padEnd(28)} ${maskToken(entry.token)}
|
|
334
361
|
`);
|
|
335
|
-
process.stdout.write(`${"".padEnd(
|
|
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(
|
|
367
|
+
process.stdout.write(`${"".padEnd(28)} \u2713 ${summary}
|
|
341
368
|
|
|
342
369
|
`);
|
|
343
370
|
} else {
|
|
344
371
|
process.stdout.write(
|
|
345
|
-
`${"".padEnd(
|
|
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.
|
|
1133
|
-
var DEFAULT_HOST = "https://
|
|
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.
|
|
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.
|
|
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
|
}
|