api2cli 0.1.1 → 0.1.3

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/index.js CHANGED
@@ -2147,10 +2147,11 @@ import { join as join3 } from "path";
2147
2147
 
2148
2148
  // src/lib/config.ts
2149
2149
  import { homedir } from "os";
2150
- import { join, resolve } from "path";
2150
+ import { join } from "path";
2151
2151
  var CLI_ROOT = join(homedir(), ".cli");
2152
2152
  var TOKENS_DIR = join(homedir(), ".config", "tokens");
2153
- var TEMPLATE_DIR = resolve(import.meta.dir, "..", "..", "..", "template");
2153
+ var TEMPLATE_REPO = "https://github.com/Melvynx/api2cli.git";
2154
+ var TEMPLATE_REPO_PATH = "packages/template";
2154
2155
  function getCliDir(app) {
2155
2156
  return join(CLI_ROOT, `${app}-cli`);
2156
2157
  }
@@ -2162,13 +2163,27 @@ function getDistDir(app) {
2162
2163
  }
2163
2164
 
2164
2165
  // src/lib/template.ts
2165
- import { existsSync, cpSync, readdirSync, statSync, readFileSync, writeFileSync } from "fs";
2166
+ import { existsSync, cpSync, readdirSync, statSync, readFileSync, writeFileSync, rmSync, mkdtempSync } from "fs";
2166
2167
  import { join as join2 } from "path";
2168
+ import { tmpdir } from "os";
2169
+ import { execSync } from "child_process";
2167
2170
  function copyTemplate(targetDir) {
2168
- if (!existsSync(TEMPLATE_DIR)) {
2169
- throw new Error(`Template not found at ${TEMPLATE_DIR}. Run: api2cli doctor`);
2171
+ const tmp = mkdtempSync(join2(tmpdir(), "api2cli-"));
2172
+ try {
2173
+ execSync(`git clone --depth 1 --filter=blob:none --sparse ${TEMPLATE_REPO} ${tmp}/repo`, {
2174
+ stdio: "pipe"
2175
+ });
2176
+ execSync(`git -C ${tmp}/repo sparse-checkout set ${TEMPLATE_REPO_PATH}`, {
2177
+ stdio: "pipe"
2178
+ });
2179
+ const templateSrc = join2(tmp, "repo", TEMPLATE_REPO_PATH);
2180
+ if (!existsSync(templateSrc)) {
2181
+ throw new Error(`Template not found in repo at ${TEMPLATE_REPO_PATH}`);
2182
+ }
2183
+ cpSync(templateSrc, targetDir, { recursive: true });
2184
+ } finally {
2185
+ rmSync(tmp, { recursive: true, force: true });
2170
2186
  }
2171
- cpSync(TEMPLATE_DIR, targetDir, { recursive: true });
2172
2187
  }
2173
2188
  function replacePlaceholders(dir, vars) {
2174
2189
  const replacements = [
@@ -2501,7 +2516,7 @@ ${import_picocolors7.default.bold("Configured tokens:")}
2501
2516
 
2502
2517
  // src/commands/remove.ts
2503
2518
  var import_picocolors8 = __toESM(require_picocolors(), 1);
2504
- import { existsSync as existsSync9, rmSync } from "fs";
2519
+ import { existsSync as existsSync9, rmSync as rmSync2 } from "fs";
2505
2520
  var removeCommand = new Command("remove").description("Remove a CLI entirely").argument("<app>", "CLI to remove").option("--keep-token", "Keep the auth token").addHelpText("after", `
2506
2521
  Examples:
2507
2522
  api2cli remove typefully
@@ -2512,12 +2527,12 @@ Examples:
2512
2527
  process.exit(1);
2513
2528
  }
2514
2529
  removeFromPath(app, getDistDir(app));
2515
- rmSync(cliDir, { recursive: true, force: true });
2530
+ rmSync2(cliDir, { recursive: true, force: true });
2516
2531
  console.log(`${import_picocolors8.default.green("\u2713")} Removed ${import_picocolors8.default.bold(`${app}-cli`)}`);
2517
2532
  if (!opts.keepToken) {
2518
2533
  const tokenFile = getTokenFile(app);
2519
2534
  if (existsSync9(tokenFile)) {
2520
- rmSync(tokenFile);
2535
+ rmSync2(tokenFile);
2521
2536
  console.log(`${import_picocolors8.default.green("\u2713")} Removed token`);
2522
2537
  }
2523
2538
  }
@@ -2541,6 +2556,14 @@ ${import_picocolors9.default.bold("api2cli doctor")}
2541
2556
  console.log(` ${import_picocolors9.default.red("\u2717")} Bun not found. Install: ${import_picocolors9.default.cyan("https://bun.sh")}`);
2542
2557
  issues++;
2543
2558
  }
2559
+ try {
2560
+ const proc = Bun.spawn(["git", "--version"], { stdout: "pipe", stderr: "pipe" });
2561
+ const version = (await new Response(proc.stdout).text()).trim();
2562
+ console.log(` ${import_picocolors9.default.green("\u2713")} ${version}`);
2563
+ } catch {
2564
+ console.log(` ${import_picocolors9.default.red("\u2717")} Git not found (required to fetch template)`);
2565
+ issues++;
2566
+ }
2544
2567
  if (existsSync10(CLI_ROOT)) {
2545
2568
  console.log(` ${import_picocolors9.default.green("\u2713")} CLI root: ${import_picocolors9.default.dim(CLI_ROOT)}`);
2546
2569
  } else {
@@ -2551,14 +2574,9 @@ ${import_picocolors9.default.bold("api2cli doctor")}
2551
2574
  } else {
2552
2575
  console.log(` ${import_picocolors9.default.yellow("~")} Tokens dir not yet created: ${import_picocolors9.default.dim(TOKENS_DIR)}`);
2553
2576
  }
2554
- if (existsSync10(TEMPLATE_DIR)) {
2555
- console.log(` ${import_picocolors9.default.green("\u2713")} Template: ${import_picocolors9.default.dim(TEMPLATE_DIR)}`);
2556
- } else {
2557
- console.log(` ${import_picocolors9.default.red("\u2717")} Template not found: ${import_picocolors9.default.dim(TEMPLATE_DIR)}`);
2558
- issues++;
2559
- }
2577
+ console.log(` ${import_picocolors9.default.green("\u2713")} Template: ${import_picocolors9.default.dim(TEMPLATE_REPO)} (fetched on create)`);
2560
2578
  console.log(issues === 0 ? `
2561
- ${import_picocolors9.default.green("All good!")} \uD83C\uDF89
2579
+ ${import_picocolors9.default.green("All good!")}
2562
2580
  ` : `
2563
2581
  ${import_picocolors9.default.red(`${issues} issue(s) found.`)}
2564
2582
  `);
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "api2cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Turn any REST API into a standardized, agent-ready CLI",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "api2cli": "./dist/index.js"
8
8
  },
9
+ "files": ["dist", "README.md"],
9
10
  "scripts": {
10
11
  "build": "bun build src/index.ts --outfile dist/index.js --target bun",
11
12
  "dev": "bun run src/index.ts"
@@ -19,5 +20,15 @@
19
20
  "typescript": "^5.7.0"
20
21
  },
21
22
  "license": "MIT",
22
- "keywords": ["cli", "api", "rest", "agent", "skill", "mcp", "agentskills"]
23
+ "keywords": ["cli", "api", "rest", "agent", "skill", "mcp", "agentskills"],
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/Melvynx/api2cli.git",
27
+ "directory": "packages/cli"
28
+ },
29
+ "homepage": "https://github.com/Melvynx/api2cli#readme",
30
+ "bugs": {
31
+ "url": "https://github.com/Melvynx/api2cli/issues"
32
+ },
33
+ "author": "Melvynx"
23
34
  }
@@ -1,68 +0,0 @@
1
- import { Command } from "commander";
2
- import { existsSync, mkdirSync } from "fs";
3
- import { join } from "path";
4
- import pc from "picocolors";
5
- import { getCliDir, getDistDir, CLI_ROOT } from "../lib/config.js";
6
- import { readdirSync } from "fs";
7
-
8
- export const bundleCommand = new Command("bundle")
9
- .description("Build/rebuild a CLI from source")
10
- .argument("[app]", "CLI to build (omit with --all)")
11
- .option("--compile", "Create standalone binary (~50MB, no runtime needed)")
12
- .option("--all", "Build all installed CLIs")
13
- .addHelpText(
14
- "after",
15
- "\nExamples:\n api2cli bundle typefully\n api2cli bundle typefully --compile\n api2cli bundle --all",
16
- )
17
- .action(async (app: string | undefined, opts) => {
18
- if (opts.all) {
19
- if (!existsSync(CLI_ROOT)) {
20
- console.log("No CLIs installed.");
21
- return;
22
- }
23
- const dirs = readdirSync(CLI_ROOT).filter((d) => d.endsWith("-cli"));
24
- for (const d of dirs) {
25
- await buildCli(d.replace(/-cli$/, ""), opts.compile);
26
- }
27
- return;
28
- }
29
-
30
- if (!app) {
31
- console.error("Specify an app name or use --all");
32
- process.exit(2);
33
- }
34
-
35
- await buildCli(app, opts.compile);
36
- });
37
-
38
- async function buildCli(app: string, compile?: boolean): Promise<void> {
39
- const cliDir = getCliDir(app);
40
- if (!existsSync(cliDir)) {
41
- console.error(`${pc.red("✗")} ${app}-cli not found. Run: ${pc.cyan(`api2cli create ${app}`)}`);
42
- return;
43
- }
44
-
45
- const distDir = getDistDir(app);
46
- mkdirSync(distDir, { recursive: true });
47
-
48
- console.log(`Building ${pc.bold(`${app}-cli`)}...`);
49
-
50
- const entry = join(cliDir, "src", "index.ts");
51
- const outfile = join(distDir, compile ? `${app}-cli` : `${app}-cli.js`);
52
- const args = ["bun", "build", entry, "--outfile", outfile, "--target", "bun"];
53
- if (compile) args.push("--compile");
54
-
55
- const proc = Bun.spawn(args, { cwd: cliDir, stdout: "pipe", stderr: "pipe" });
56
- const code = await proc.exited;
57
-
58
- if (code === 0) {
59
- const size = Bun.file(outfile).size;
60
- const sizeStr = size > 1024 * 1024
61
- ? `${(size / 1024 / 1024).toFixed(1)}MB`
62
- : `${(size / 1024).toFixed(1)}KB`;
63
- console.log(`${pc.green("✓")} Built ${pc.bold(`${app}-cli`)} (${sizeStr})`);
64
- } else {
65
- const stderr = await new Response(proc.stderr).text();
66
- console.error(`${pc.red("✗")} Build failed: ${stderr}`);
67
- }
68
- }
@@ -1,78 +0,0 @@
1
- import { Command } from "commander";
2
- import { existsSync, mkdirSync } from "fs";
3
- import { join } from "path";
4
- import pc from "picocolors";
5
- import { getCliDir } from "../lib/config.js";
6
- import { copyTemplate, replacePlaceholders } from "../lib/template.js";
7
-
8
- export const createCommand = new Command("create")
9
- .description("Generate a new CLI from API documentation")
10
- .argument("<app>", "API/app name (e.g. typefully, dub, mercury)")
11
- .option("--docs <url>", "URL to API documentation")
12
- .option("--openapi <url>", "URL to OpenAPI/Swagger spec")
13
- .option("--base-url <url>", "API base URL", "https://api.example.com")
14
- .option("--auth-type <type>", "Auth type: bearer, api-key, basic, custom", "bearer")
15
- .option("--auth-header <name>", "Auth header name", "Authorization")
16
- .option("--force", "Overwrite existing CLI", false)
17
- .addHelpText(
18
- "after",
19
- `
20
- Examples:
21
- api2cli create typefully --base-url https://api.typefully.com --auth-type bearer
22
- api2cli create dub --openapi https://api.dub.co/openapi.json
23
- api2cli create my-api --docs https://docs.example.com/api`,
24
- )
25
- .action(async (app: string, opts) => {
26
- const cliDir = getCliDir(app);
27
-
28
- if (existsSync(cliDir) && !opts.force) {
29
- console.error(`${pc.red("✗")} ${app}-cli already exists at ${cliDir}`);
30
- console.error(` Use ${pc.cyan("--force")} to overwrite.`);
31
- process.exit(1);
32
- }
33
-
34
- console.log(`\n${pc.bold("Creating")} ${pc.cyan(`${app}-cli`)}...\n`);
35
-
36
- // 1. Create target directory
37
- mkdirSync(cliDir, { recursive: true });
38
- console.log(` ${pc.green("+")} Created ${pc.dim(cliDir)}`);
39
-
40
- // 2. Copy template
41
- copyTemplate(cliDir);
42
- console.log(` ${pc.green("+")} Copied template scaffold`);
43
-
44
- // 3. Replace placeholders
45
- replacePlaceholders(cliDir, {
46
- appName: app,
47
- appCli: `${app}-cli`,
48
- baseUrl: opts.baseUrl,
49
- authType: opts.authType,
50
- authHeader: opts.authHeader,
51
- });
52
- console.log(` ${pc.green("+")} Configured for ${pc.bold(app)}`);
53
-
54
- // 4. Install dependencies
55
- console.log(` ${pc.dim("Installing dependencies...")}`);
56
- const install = Bun.spawn(["bun", "install"], {
57
- cwd: cliDir,
58
- stdout: "ignore",
59
- stderr: "pipe",
60
- });
61
- await install.exited;
62
- console.log(` ${pc.green("+")} Dependencies installed`);
63
-
64
- // 5. Rename SKILL.md.template
65
- const skillTemplate = join(cliDir, "SKILL.md.template");
66
- const skillTarget = join(cliDir, "SKILL.md");
67
- if (existsSync(skillTemplate)) {
68
- const { renameSync } = require("fs");
69
- renameSync(skillTemplate, skillTarget);
70
- }
71
-
72
- console.log(`\n${pc.green("✓")} Created ${pc.bold(`${app}-cli`)} at ${pc.dim(cliDir)}`);
73
- console.log(`\n${pc.bold("Next steps:")}`);
74
- console.log(` 1. Edit resources in ${pc.dim(`${cliDir}/src/resources/`)}`);
75
- console.log(` 2. Build: ${pc.cyan(`api2cli bundle ${app}`)}`);
76
- console.log(` 3. Link: ${pc.cyan(`api2cli link ${app}`)}`);
77
- console.log(` 4. Auth: ${pc.cyan(`${app}-cli auth set "your-token"`)}`);
78
- });
@@ -1,48 +0,0 @@
1
- import { Command } from "commander";
2
- import { existsSync } from "fs";
3
- import pc from "picocolors";
4
- import { CLI_ROOT, TOKENS_DIR, TEMPLATE_DIR } from "../lib/config.js";
5
-
6
- export const doctorCommand = new Command("doctor")
7
- .description("Check system requirements and configuration")
8
- .addHelpText("after", "\nExample:\n api2cli doctor")
9
- .action(async () => {
10
- console.log(`\n${pc.bold("api2cli doctor")}\n`);
11
- let issues = 0;
12
-
13
- // Bun
14
- try {
15
- const proc = Bun.spawn(["bun", "--version"], { stdout: "pipe", stderr: "pipe" });
16
- const version = (await new Response(proc.stdout).text()).trim();
17
- console.log(` ${pc.green("✓")} Bun ${version}`);
18
- } catch {
19
- console.log(` ${pc.red("✗")} Bun not found. Install: ${pc.cyan("https://bun.sh")}`);
20
- issues++;
21
- }
22
-
23
- // CLI root
24
- if (existsSync(CLI_ROOT)) {
25
- console.log(` ${pc.green("✓")} CLI root: ${pc.dim(CLI_ROOT)}`);
26
- } else {
27
- console.log(` ${pc.yellow("~")} CLI root not yet created: ${pc.dim(CLI_ROOT)}`);
28
- }
29
-
30
- // Tokens dir
31
- if (existsSync(TOKENS_DIR)) {
32
- console.log(` ${pc.green("✓")} Tokens dir: ${pc.dim(TOKENS_DIR)}`);
33
- } else {
34
- console.log(` ${pc.yellow("~")} Tokens dir not yet created: ${pc.dim(TOKENS_DIR)}`);
35
- }
36
-
37
- // Template
38
- if (existsSync(TEMPLATE_DIR)) {
39
- console.log(` ${pc.green("✓")} Template: ${pc.dim(TEMPLATE_DIR)}`);
40
- } else {
41
- console.log(` ${pc.red("✗")} Template not found: ${pc.dim(TEMPLATE_DIR)}`);
42
- issues++;
43
- }
44
-
45
- console.log(
46
- issues === 0 ? `\n${pc.green("All good!")} 🎉\n` : `\n${pc.red(`${issues} issue(s) found.`)}\n`,
47
- );
48
- });
@@ -1,29 +0,0 @@
1
- import { Command } from "commander";
2
- import { existsSync } from "fs";
3
- import pc from "picocolors";
4
- import { getCliDir } from "../lib/config.js";
5
-
6
- export const installCommand = new Command("install")
7
- .description("Install a pre-built CLI from the registry")
8
- .argument("<app>", "CLI to install (e.g. typefully, dub)")
9
- .option("--force", "Overwrite existing CLI", false)
10
- .addHelpText(
11
- "after",
12
- "\nExamples:\n api2cli install typefully\n api2cli install dub --force",
13
- )
14
- .action(async (app: string, opts) => {
15
- const cliDir = getCliDir(app);
16
-
17
- if (existsSync(cliDir) && !opts.force) {
18
- console.error(`${pc.red("✗")} ${app}-cli already installed. Use ${pc.cyan("--force")} to reinstall.`);
19
- process.exit(1);
20
- }
21
-
22
- console.log(`Installing ${pc.bold(`${app}-cli`)} from registry...`);
23
-
24
- // TODO: Fetch from npm @api2cli/<app> or api2cli.dev API
25
- // Download resources + config -> merge with template -> build -> link
26
- console.log(`\n${pc.yellow("🚧")} Registry not yet available.`);
27
- console.log(`\nCreate it manually instead:`);
28
- console.log(` ${pc.cyan(`api2cli create ${app} --docs <api-docs-url>`)}`);
29
- });
@@ -1,32 +0,0 @@
1
- import { Command } from "commander";
2
- import { existsSync, readdirSync } from "fs";
3
- import pc from "picocolors";
4
- import { getCliDir, getDistDir, CLI_ROOT } from "../lib/config.js";
5
- import { addToPath } from "../lib/shell.js";
6
-
7
- export const linkCommand = new Command("link")
8
- .description("Add a CLI to your PATH")
9
- .argument("[app]", "CLI to link (omit with --all)")
10
- .option("--all", "Link all installed CLIs")
11
- .addHelpText("after", "\nExamples:\n api2cli link typefully\n api2cli link --all")
12
- .action((app: string | undefined, opts) => {
13
- if (opts.all || !app) {
14
- if (!existsSync(CLI_ROOT)) {
15
- console.log("No CLIs installed.");
16
- return;
17
- }
18
- const dirs = readdirSync(CLI_ROOT).filter((d) => d.endsWith("-cli"));
19
- for (const d of dirs) {
20
- const name = d.replace(/-cli$/, "");
21
- addToPath(name, getDistDir(name));
22
- }
23
- return;
24
- }
25
-
26
- if (!existsSync(getCliDir(app))) {
27
- console.error(`${pc.red("✗")} ${app}-cli not found. Run: ${pc.cyan(`api2cli create ${app}`)}`);
28
- process.exit(1);
29
- }
30
-
31
- addToPath(app, getDistDir(app));
32
- });
@@ -1,52 +0,0 @@
1
- import { Command } from "commander";
2
- import { existsSync, readdirSync, statSync } from "fs";
3
- import { join } from "path";
4
- import pc from "picocolors";
5
- import { CLI_ROOT, TOKENS_DIR } from "../lib/config.js";
6
-
7
- export const listCommand = new Command("list")
8
- .description("List all installed CLIs")
9
- .option("--json", "Output as JSON")
10
- .addHelpText("after", "\nExamples:\n api2cli list\n api2cli list --json")
11
- .action((opts) => {
12
- if (!existsSync(CLI_ROOT)) {
13
- console.log("No CLIs installed. Run: api2cli create <app>");
14
- return;
15
- }
16
-
17
- const dirs = readdirSync(CLI_ROOT).filter((d) => {
18
- return statSync(join(CLI_ROOT, d)).isDirectory() && d.endsWith("-cli");
19
- });
20
-
21
- if (dirs.length === 0) {
22
- console.log("No CLIs installed.");
23
- return;
24
- }
25
-
26
- if (opts.json) {
27
- const data = dirs.map((d) => {
28
- const name = d.replace(/-cli$/, "");
29
- return {
30
- name,
31
- built: existsSync(join(CLI_ROOT, d, "dist")),
32
- hasToken: existsSync(join(TOKENS_DIR, `${d}.txt`)),
33
- path: join(CLI_ROOT, d),
34
- };
35
- });
36
- console.log(JSON.stringify({ ok: true, data }, null, 2));
37
- return;
38
- }
39
-
40
- console.log(`\n${pc.bold("Installed CLIs:")}\n`);
41
- for (const d of dirs) {
42
- const name = d.replace(/-cli$/, "");
43
- const built = existsSync(join(CLI_ROOT, d, "dist"));
44
- const hasToken = existsSync(join(TOKENS_DIR, `${d}.txt`));
45
- const status = [
46
- built ? pc.green("built") : pc.yellow("not built"),
47
- hasToken ? pc.green("auth") : pc.dim("no auth"),
48
- ].join(pc.dim(" | "));
49
- console.log(` ${pc.bold(name.padEnd(20))} ${status}`);
50
- }
51
- console.log();
52
- });
@@ -1,22 +0,0 @@
1
- import { Command } from "commander";
2
- import { existsSync } from "fs";
3
- import pc from "picocolors";
4
- import { getCliDir } from "../lib/config.js";
5
-
6
- export const publishCommand = new Command("publish")
7
- .description("Publish a CLI to the api2cli registry")
8
- .argument("<app>", "CLI to publish")
9
- .option("--scope <scope>", "npm scope", "@api2cli")
10
- .addHelpText("after", "\nExample:\n api2cli publish typefully")
11
- .action(async (app: string, opts) => {
12
- const cliDir = getCliDir(app);
13
-
14
- if (!existsSync(cliDir)) {
15
- console.error(`${pc.red("✗")} ${app}-cli not found.`);
16
- process.exit(1);
17
- }
18
-
19
- // TODO: Package resources + config, publish to npm + api2cli.dev
20
- console.log(`Publishing ${pc.bold(`${app}-cli`)} as ${pc.cyan(`${opts.scope}/${app}`)}...`);
21
- console.log(`\n${pc.yellow("🚧")} Publishing not yet implemented.`);
22
- });
@@ -1,35 +0,0 @@
1
- import { Command } from "commander";
2
- import { existsSync, rmSync } from "fs";
3
- import pc from "picocolors";
4
- import { getCliDir, getTokenFile, getDistDir } from "../lib/config.js";
5
- import { removeFromPath } from "../lib/shell.js";
6
-
7
- export const removeCommand = new Command("remove")
8
- .description("Remove a CLI entirely")
9
- .argument("<app>", "CLI to remove")
10
- .option("--keep-token", "Keep the auth token")
11
- .addHelpText("after", "\nExamples:\n api2cli remove typefully\n api2cli remove typefully --keep-token")
12
- .action((app: string, opts) => {
13
- const cliDir = getCliDir(app);
14
-
15
- if (!existsSync(cliDir)) {
16
- console.error(`${pc.red("✗")} ${app}-cli not found.`);
17
- process.exit(1);
18
- }
19
-
20
- // Remove from PATH
21
- removeFromPath(app, getDistDir(app));
22
-
23
- // Remove directory
24
- rmSync(cliDir, { recursive: true, force: true });
25
- console.log(`${pc.green("✓")} Removed ${pc.bold(`${app}-cli`)}`);
26
-
27
- // Remove token unless --keep-token
28
- if (!opts.keepToken) {
29
- const tokenFile = getTokenFile(app);
30
- if (existsSync(tokenFile)) {
31
- rmSync(tokenFile);
32
- console.log(`${pc.green("✓")} Removed token`);
33
- }
34
- }
35
- });
@@ -1,35 +0,0 @@
1
- import { Command } from "commander";
2
- import { existsSync, readdirSync, readFileSync } from "fs";
3
- import pc from "picocolors";
4
- import { TOKENS_DIR } from "../lib/config.js";
5
- import { join } from "path";
6
-
7
- export const tokensCommand = new Command("tokens")
8
- .description("List all configured API tokens")
9
- .option("--show", "Show full unmasked tokens")
10
- .addHelpText("after", "\nExamples:\n api2cli tokens\n api2cli tokens --show")
11
- .action((opts) => {
12
- if (!existsSync(TOKENS_DIR)) {
13
- console.log("No tokens configured yet.");
14
- return;
15
- }
16
-
17
- const files = readdirSync(TOKENS_DIR).filter((f) => f.endsWith(".txt"));
18
- if (files.length === 0) {
19
- console.log("No tokens configured yet.");
20
- return;
21
- }
22
-
23
- console.log(`\n${pc.bold("Configured tokens:")}\n`);
24
- for (const f of files) {
25
- const name = f.replace(".txt", "");
26
- const token = readFileSync(join(TOKENS_DIR, f), "utf-8").trim();
27
- const display = opts.show
28
- ? token
29
- : token.length > 8
30
- ? `${token.slice(0, 4)}${pc.dim("...")}${token.slice(-4)}`
31
- : pc.dim("****");
32
- console.log(` ${pc.bold(name.padEnd(25))} ${display}`);
33
- }
34
- console.log();
35
- });
@@ -1,11 +0,0 @@
1
- import { Command } from "commander";
2
- import { getDistDir } from "../lib/config.js";
3
- import { removeFromPath } from "../lib/shell.js";
4
-
5
- export const unlinkCommand = new Command("unlink")
6
- .description("Remove a CLI from your PATH")
7
- .argument("<app>", "CLI to unlink")
8
- .addHelpText("after", "\nExample:\n api2cli unlink typefully")
9
- .action((app: string) => {
10
- removeFromPath(app, getDistDir(app));
11
- });
@@ -1,26 +0,0 @@
1
- import { Command } from "commander";
2
- import { existsSync } from "fs";
3
- import pc from "picocolors";
4
- import { getCliDir } from "../lib/config.js";
5
-
6
- export const updateCommand = new Command("update")
7
- .description("Re-sync a CLI when the upstream API changes")
8
- .argument("<app>", "CLI to update")
9
- .option("--docs <url>", "Updated API documentation URL")
10
- .option("--openapi <url>", "Updated OpenAPI spec URL")
11
- .addHelpText("after", "\nExample:\n api2cli update typefully --docs https://docs.typefully.com")
12
- .action(async (app: string) => {
13
- const cliDir = getCliDir(app);
14
-
15
- if (!existsSync(cliDir)) {
16
- console.error(`${pc.red("✗")} ${app}-cli not found. Run: ${pc.cyan(`api2cli create ${app}`)}`);
17
- process.exit(1);
18
- }
19
-
20
- // TODO: Agent-driven update flow
21
- // Re-read API docs -> diff endpoints -> add/update resources -> rebuild
22
- console.log(`${pc.yellow("🚧")} Update is agent-driven.`);
23
- console.log(`\nUse your AI agent to update resources in:`);
24
- console.log(` ${pc.dim(`${cliDir}/src/resources/`)}`);
25
- console.log(`\nThen rebuild: ${pc.cyan(`api2cli bundle ${app}`)}`);
26
- });
package/src/index.ts DELETED
@@ -1,41 +0,0 @@
1
- #!/usr/bin/env bun
2
- import { Command } from "commander";
3
- import { createCommand } from "./commands/create.js";
4
- import { installCommand } from "./commands/install.js";
5
- import { listCommand } from "./commands/list.js";
6
- import { bundleCommand } from "./commands/bundle.js";
7
- import { linkCommand } from "./commands/link.js";
8
- import { unlinkCommand } from "./commands/unlink.js";
9
- import { tokensCommand } from "./commands/tokens.js";
10
- import { removeCommand } from "./commands/remove.js";
11
- import { doctorCommand } from "./commands/doctor.js";
12
- import { updateCommand } from "./commands/update.js";
13
- import { publishCommand } from "./commands/publish.js";
14
-
15
- const program = new Command();
16
-
17
- program
18
- .name("api2cli")
19
- .description("Turn any REST API into a standardized, agent-ready CLI")
20
- .version("0.1.0");
21
-
22
- // Core
23
- program.addCommand(createCommand);
24
- program.addCommand(bundleCommand);
25
- program.addCommand(linkCommand);
26
- program.addCommand(unlinkCommand);
27
- program.addCommand(listCommand);
28
-
29
- // Auth
30
- program.addCommand(tokensCommand);
31
-
32
- // Lifecycle
33
- program.addCommand(removeCommand);
34
- program.addCommand(doctorCommand);
35
- program.addCommand(updateCommand);
36
-
37
- // Registry
38
- program.addCommand(installCommand);
39
- program.addCommand(publishCommand);
40
-
41
- program.parse();
package/src/lib/bashrc.ts DELETED
@@ -1,55 +0,0 @@
1
- import { existsSync, readFileSync, appendFileSync } from "fs";
2
- import { homedir } from "os";
3
- import { join } from "path";
4
-
5
- const MARKER_START = "# >>> api2cli >>>";
6
- const MARKER_END = "# <<< api2cli <<<";
7
-
8
- function getShellRc(): string {
9
- const zshrc = join(homedir(), ".zshrc");
10
- const bashrc = join(homedir(), ".bashrc");
11
- if (existsSync(zshrc)) return zshrc;
12
- return bashrc;
13
- }
14
-
15
- export function addToPath(appName: string, binDir: string): void {
16
- const rcFile = getShellRc();
17
- const content = existsSync(rcFile) ? readFileSync(rcFile, "utf-8") : "";
18
-
19
- const exportLine = `export PATH="${binDir}:$PATH"`;
20
-
21
- // Check if already linked
22
- if (content.includes(exportLine)) {
23
- console.log(`${appName} already in PATH`);
24
- return;
25
- }
26
-
27
- // Find or create api2cli block
28
- if (content.includes(MARKER_START)) {
29
- // Insert before the end marker
30
- const updated = content.replace(
31
- MARKER_END,
32
- `${exportLine}\n${MARKER_END}`
33
- );
34
- const { writeFileSync } = require("fs");
35
- writeFileSync(rcFile, updated);
36
- } else {
37
- appendFileSync(rcFile, `\n${MARKER_START}\n${exportLine}\n${MARKER_END}\n`);
38
- }
39
-
40
- console.log(`Added ${appName} to PATH in ${rcFile}`);
41
- console.log(`Run: source ${rcFile}`);
42
- }
43
-
44
- export function removeFromPath(appName: string, binDir: string): void {
45
- const rcFile = getShellRc();
46
- if (!existsSync(rcFile)) return;
47
-
48
- const content = readFileSync(rcFile, "utf-8");
49
- const exportLine = `export PATH="${binDir}:$PATH"`;
50
- const updated = content.replace(`${exportLine}\n`, "");
51
-
52
- const { writeFileSync } = require("fs");
53
- writeFileSync(rcFile, updated);
54
- console.log(`Removed ${appName} from PATH`);
55
- }
package/src/lib/config.ts DELETED
@@ -1,35 +0,0 @@
1
- import { homedir } from "os";
2
- import { join, resolve } from "path";
3
-
4
- /** Root directory for all generated CLIs */
5
- export const CLI_ROOT = join(homedir(), ".cli");
6
-
7
- /** Centralized token storage directory */
8
- export const TOKENS_DIR = join(homedir(), ".config", "tokens");
9
-
10
- /** Template directory (relative to this package in the monorepo) */
11
- export const TEMPLATE_DIR = resolve(import.meta.dir, "..", "..", "..", "template");
12
-
13
- /** Placeholders used in the template that get replaced during create */
14
- export const PLACEHOLDERS = [
15
- "{{APP_NAME}}",
16
- "{{APP_CLI}}",
17
- "{{BASE_URL}}",
18
- "{{AUTH_TYPE}}",
19
- "{{AUTH_HEADER}}",
20
- ] as const;
21
-
22
- /** Get the installation directory for a CLI */
23
- export function getCliDir(app: string): string {
24
- return join(CLI_ROOT, `${app}-cli`);
25
- }
26
-
27
- /** Get the token file path for a CLI */
28
- export function getTokenFile(app: string): string {
29
- return join(TOKENS_DIR, `${app}-cli.txt`);
30
- }
31
-
32
- /** Get the dist directory for a CLI */
33
- export function getDistDir(app: string): string {
34
- return join(getCliDir(app), "dist");
35
- }
package/src/lib/shell.ts DELETED
@@ -1,57 +0,0 @@
1
- import { existsSync, readFileSync, writeFileSync, appendFileSync } from "fs";
2
- import { homedir } from "os";
3
- import { join } from "path";
4
- import pc from "picocolors";
5
-
6
- const MARKER_START = "# >>> api2cli >>>";
7
- const MARKER_END = "# <<< api2cli <<<";
8
-
9
- /** Detect the user's shell rc file */
10
- function getShellRc(): string {
11
- const shell = process.env.SHELL ?? "";
12
- if (shell.includes("zsh")) return join(homedir(), ".zshrc");
13
- if (shell.includes("fish")) return join(homedir(), ".config", "fish", "config.fish");
14
- // Check if .zshrc exists even if SHELL isn't set
15
- const zshrc = join(homedir(), ".zshrc");
16
- if (existsSync(zshrc)) return zshrc;
17
- return join(homedir(), ".bashrc");
18
- }
19
-
20
- /** Add a CLI's dist directory to PATH via the shell rc file. Idempotent. */
21
- export function addToPath(app: string, binDir: string): void {
22
- const rcFile = getShellRc();
23
- const content = existsSync(rcFile) ? readFileSync(rcFile, "utf-8") : "";
24
- const exportLine = `export PATH="${binDir}:$PATH"`;
25
-
26
- if (content.includes(exportLine)) {
27
- console.log(`${pc.dim(app)} already in PATH`);
28
- return;
29
- }
30
-
31
- if (content.includes(MARKER_START)) {
32
- const updated = content.replace(MARKER_END, `${exportLine}\n${MARKER_END}`);
33
- writeFileSync(rcFile, updated);
34
- } else {
35
- appendFileSync(rcFile, `\n${MARKER_START}\n${exportLine}\n${MARKER_END}\n`);
36
- }
37
-
38
- console.log(`${pc.green("+")} Added ${pc.bold(app)} to PATH in ${pc.dim(rcFile)}`);
39
- console.log(` Run: ${pc.cyan(`source ${rcFile}`)}`);
40
- }
41
-
42
- /** Remove a CLI from PATH in the shell rc file */
43
- export function removeFromPath(app: string, binDir: string): void {
44
- const rcFile = getShellRc();
45
- if (!existsSync(rcFile)) return;
46
-
47
- const content = readFileSync(rcFile, "utf-8");
48
- const exportLine = `export PATH="${binDir}:$PATH"\n`;
49
-
50
- if (!content.includes(exportLine)) {
51
- console.log(`${pc.dim(app)} not in PATH`);
52
- return;
53
- }
54
-
55
- writeFileSync(rcFile, content.replace(exportLine, ""));
56
- console.log(`${pc.red("-")} Removed ${pc.bold(app)} from PATH`);
57
- }
@@ -1,63 +0,0 @@
1
- import { existsSync, cpSync, readdirSync, statSync, readFileSync, writeFileSync } from "fs";
2
- import { join } from "path";
3
- import { TEMPLATE_DIR } from "./config.js";
4
-
5
- interface TemplateVars {
6
- appName: string;
7
- appCli: string;
8
- baseUrl: string;
9
- authType: string;
10
- authHeader: string;
11
- }
12
-
13
- /** Copy the template scaffold to a target directory */
14
- export function copyTemplate(targetDir: string): void {
15
- if (!existsSync(TEMPLATE_DIR)) {
16
- throw new Error(`Template not found at ${TEMPLATE_DIR}. Run: api2cli doctor`);
17
- }
18
- cpSync(TEMPLATE_DIR, targetDir, { recursive: true });
19
- }
20
-
21
- /** Replace all {{PLACEHOLDER}} tokens in every file in a directory tree */
22
- export function replacePlaceholders(dir: string, vars: TemplateVars): void {
23
- const replacements: [string, string][] = [
24
- ["{{APP_NAME}}", vars.appName],
25
- ["{{APP_CLI}}", vars.appCli],
26
- ["{{BASE_URL}}", vars.baseUrl],
27
- ["{{AUTH_TYPE}}", vars.authType],
28
- ["{{AUTH_HEADER}}", vars.authHeader],
29
- ];
30
-
31
- walkFiles(dir, (filePath) => {
32
- // Skip binary files and node_modules
33
- if (filePath.includes("node_modules")) return;
34
- const ext = filePath.split(".").pop() ?? "";
35
- if (!["ts", "js", "json", "md", "txt", "template"].includes(ext)) return;
36
-
37
- let content = readFileSync(filePath, "utf-8");
38
- let changed = false;
39
-
40
- for (const [placeholder, value] of replacements) {
41
- if (content.includes(placeholder)) {
42
- content = content.replaceAll(placeholder, value);
43
- changed = true;
44
- }
45
- }
46
-
47
- if (changed) {
48
- writeFileSync(filePath, content);
49
- }
50
- });
51
- }
52
-
53
- /** Recursively walk all files in a directory */
54
- function walkFiles(dir: string, callback: (path: string) => void): void {
55
- for (const entry of readdirSync(dir)) {
56
- const full = join(dir, entry);
57
- if (statSync(full).isDirectory()) {
58
- walkFiles(full, callback);
59
- } else {
60
- callback(full);
61
- }
62
- }
63
- }
package/tsconfig.json DELETED
@@ -1,8 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.base.json",
3
- "compilerOptions": {
4
- "outDir": "dist",
5
- "rootDir": "src"
6
- },
7
- "include": ["src"]
8
- }