@nkmc/cli 0.0.2 → 0.1.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 ADDED
@@ -0,0 +1,156 @@
1
+ # @nkmc/cli
2
+
3
+ Command-line tool for the nkmc gateway. Scan your API, generate a `skill.md`, register it with the gateway, and interact with registered services as an agent.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g @nkmc/cli
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```bash
14
+ # Authenticate with the gateway (saves token to ~/.nkmc/credentials.json)
15
+ nkmc auth
16
+
17
+ # List services on the gateway
18
+ nkmc ls /
19
+
20
+ # Read a service's skill.md
21
+ nkmc cat /api.weather.gov/skill.md
22
+ ```
23
+
24
+ ## Commands
25
+
26
+ ### `nkmc auth`
27
+
28
+ Authenticate with the gateway. Fetches a JWT token (valid 24h) and saves it locally.
29
+
30
+ ```bash
31
+ nkmc auth
32
+ nkmc auth --gateway-url https://your-gateway.example.com
33
+ ```
34
+
35
+ After authenticating, all `ls`/`cat`/`grep`/`write`/`rm` commands work without setting environment variables.
36
+
37
+ ### `nkmc init [dir]`
38
+
39
+ Detect your project's framework and generate a `nkmc.config.ts` configuration file.
40
+
41
+ ```bash
42
+ nkmc init
43
+ ```
44
+
45
+ Supports Hono, Express, Fastify, and Next.js. Detects Prisma and Drizzle ORMs.
46
+
47
+ ### `nkmc generate [dir]`
48
+
49
+ Scan your project and generate `.well-known/skill.md`.
50
+
51
+ ```bash
52
+ nkmc generate
53
+ nkmc generate --register --gateway-url https://api.nkmc.ai --domain myapi.com
54
+ ```
55
+
56
+ Options:
57
+ - `--register` — Register with the gateway after generating
58
+ - `--gateway-url <url>` — Gateway URL for registration
59
+ - `--token <token>` — Auth token for registration
60
+ - `--domain <domain>` — Domain name for the service
61
+
62
+ ### `nkmc claim <domain>`
63
+
64
+ Claim domain ownership via DNS TXT record verification.
65
+
66
+ ```bash
67
+ # Step 1: Request a DNS challenge
68
+ nkmc claim myapi.com --gateway-url https://api.nkmc.ai
69
+
70
+ # Step 2: Add the TXT record to your DNS, then verify
71
+ nkmc claim myapi.com --gateway-url https://api.nkmc.ai --verify
72
+ ```
73
+
74
+ On success, a publish token is saved to `~/.nkmc/credentials.json` for that domain.
75
+
76
+ ### `nkmc register`
77
+
78
+ Register your `skill.md` with the gateway.
79
+
80
+ ```bash
81
+ nkmc register --gateway-url https://api.nkmc.ai --domain myapi.com
82
+ ```
83
+
84
+ The token is resolved in this order:
85
+ 1. `--token` flag
86
+ 2. `NKMC_PUBLISH_TOKEN` env var
87
+ 3. Saved publish token from `nkmc claim`
88
+
89
+ ### `nkmc ls <path>`
90
+
91
+ List files/services on the gateway.
92
+
93
+ ```bash
94
+ nkmc ls / # List all services
95
+ nkmc ls /api.weather.gov/ # List contents of a service
96
+ ```
97
+
98
+ ### `nkmc cat <path>`
99
+
100
+ Read a file from the gateway.
101
+
102
+ ```bash
103
+ nkmc cat /api.weather.gov/skill.md
104
+ nkmc cat /api.weather.gov/alerts/active
105
+ ```
106
+
107
+ ### `nkmc grep <pattern> <path>`
108
+
109
+ Search across services on the gateway.
110
+
111
+ ```bash
112
+ nkmc grep "weather" /
113
+ nkmc grep "forecast" /api.weather.gov/
114
+ ```
115
+
116
+ ### `nkmc write <path> <data>`
117
+
118
+ Write data to a path on the gateway.
119
+
120
+ ### `nkmc rm <path>`
121
+
122
+ Remove a file on the gateway.
123
+
124
+ ## Authentication
125
+
126
+ The CLI resolves credentials in this order:
127
+
128
+ 1. `NKMC_TOKEN` / `NKMC_GATEWAY_URL` environment variables
129
+ 2. Saved agent token from `nkmc auth` (`~/.nkmc/credentials.json`)
130
+ 3. Default gateway URL: `https://api.nkmc.ai`
131
+
132
+ ## Configuration Directory
133
+
134
+ Credentials are stored in `~/.nkmc/credentials.json` (permissions `0600`). Override the directory with the `NKMC_HOME` environment variable.
135
+
136
+ ## Workflow: Registering Your API
137
+
138
+ ```bash
139
+ # 1. Initialize config
140
+ nkmc init
141
+
142
+ # 2. Generate skill.md
143
+ nkmc generate
144
+
145
+ # 3. Claim your domain
146
+ nkmc claim myapi.com --gateway-url https://api.nkmc.ai
147
+ # ... add DNS TXT record ...
148
+ nkmc claim myapi.com --gateway-url https://api.nkmc.ai --verify
149
+
150
+ # 4. Register with the gateway
151
+ nkmc register --gateway-url https://api.nkmc.ai --domain myapi.com
152
+ ```
153
+
154
+ ## License
155
+
156
+ MIT
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/credentials.ts
4
+ import { readFile, writeFile, mkdir } from "fs/promises";
5
+ import { join } from "path";
6
+ import { homedir } from "os";
7
+ import { chmod } from "fs/promises";
8
+ function nkmcDir() {
9
+ return process.env.NKMC_HOME || join(homedir(), ".nkmc");
10
+ }
11
+ function credentialsPath() {
12
+ return join(nkmcDir(), "credentials.json");
13
+ }
14
+ async function loadCredentials() {
15
+ try {
16
+ const raw = await readFile(credentialsPath(), "utf-8");
17
+ return JSON.parse(raw);
18
+ } catch {
19
+ return { tokens: {} };
20
+ }
21
+ }
22
+ async function saveToken(domain, publishToken) {
23
+ const creds = await loadCredentials();
24
+ const payloadB64 = publishToken.split(".")[1];
25
+ const payload = JSON.parse(
26
+ Buffer.from(payloadB64, "base64url").toString("utf-8")
27
+ );
28
+ creds.tokens[domain] = {
29
+ publishToken,
30
+ issuedAt: new Date(payload.iat * 1e3).toISOString(),
31
+ expiresAt: new Date(payload.exp * 1e3).toISOString()
32
+ };
33
+ const dir = nkmcDir();
34
+ await mkdir(dir, { recursive: true });
35
+ const filePath = credentialsPath();
36
+ await writeFile(filePath, JSON.stringify(creds, null, 2) + "\n");
37
+ await chmod(filePath, 384);
38
+ }
39
+ async function saveAgentToken(gatewayUrl, token) {
40
+ const creds = await loadCredentials();
41
+ const payloadB64 = token.split(".")[1];
42
+ const payload = JSON.parse(
43
+ Buffer.from(payloadB64, "base64url").toString("utf-8")
44
+ );
45
+ creds.agentToken = {
46
+ token,
47
+ gatewayUrl,
48
+ issuedAt: new Date(payload.iat * 1e3).toISOString(),
49
+ expiresAt: new Date(payload.exp * 1e3).toISOString()
50
+ };
51
+ const dir = nkmcDir();
52
+ await mkdir(dir, { recursive: true });
53
+ const filePath = credentialsPath();
54
+ await writeFile(filePath, JSON.stringify(creds, null, 2) + "\n");
55
+ await chmod(filePath, 384);
56
+ }
57
+ async function getAgentToken() {
58
+ const creds = await loadCredentials();
59
+ const entry = creds.agentToken;
60
+ if (!entry) return null;
61
+ if (new Date(entry.expiresAt).getTime() < Date.now()) {
62
+ return null;
63
+ }
64
+ return entry;
65
+ }
66
+ async function getToken(domain) {
67
+ const creds = await loadCredentials();
68
+ const entry = creds.tokens[domain];
69
+ if (!entry) return null;
70
+ if (new Date(entry.expiresAt).getTime() < Date.now()) {
71
+ return null;
72
+ }
73
+ return entry.publishToken;
74
+ }
75
+
76
+ export {
77
+ loadCredentials,
78
+ saveToken,
79
+ saveAgentToken,
80
+ getAgentToken,
81
+ getToken
82
+ };
@@ -1,60 +1,15 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ getToken
4
+ } from "./chunk-MPXYSTOK.js";
2
5
 
3
6
  // src/commands/register.ts
4
- import { readFile as readFile2 } from "fs/promises";
5
- import { join as join2 } from "path";
6
-
7
- // src/credentials.ts
8
- import { readFile, writeFile, mkdir } from "fs/promises";
7
+ import { readFile } from "fs/promises";
9
8
  import { join } from "path";
10
- import { homedir } from "os";
11
- import { chmod } from "fs/promises";
12
- function nkmcDir() {
13
- return process.env.NKMC_HOME || join(homedir(), ".nkmc");
14
- }
15
- function credentialsPath() {
16
- return join(nkmcDir(), "credentials.json");
17
- }
18
- async function loadCredentials() {
19
- try {
20
- const raw = await readFile(credentialsPath(), "utf-8");
21
- return JSON.parse(raw);
22
- } catch {
23
- return { tokens: {} };
24
- }
25
- }
26
- async function saveToken(domain, publishToken) {
27
- const creds = await loadCredentials();
28
- const payloadB64 = publishToken.split(".")[1];
29
- const payload = JSON.parse(
30
- Buffer.from(payloadB64, "base64url").toString("utf-8")
31
- );
32
- creds.tokens[domain] = {
33
- publishToken,
34
- issuedAt: new Date(payload.iat * 1e3).toISOString(),
35
- expiresAt: new Date(payload.exp * 1e3).toISOString()
36
- };
37
- const dir = nkmcDir();
38
- await mkdir(dir, { recursive: true });
39
- const filePath = credentialsPath();
40
- await writeFile(filePath, JSON.stringify(creds, null, 2) + "\n");
41
- await chmod(filePath, 384);
42
- }
43
- async function getToken(domain) {
44
- const creds = await loadCredentials();
45
- const entry = creds.tokens[domain];
46
- if (!entry) return null;
47
- if (new Date(entry.expiresAt).getTime() < Date.now()) {
48
- return null;
49
- }
50
- return entry.publishToken;
51
- }
52
-
53
- // src/commands/register.ts
54
9
  async function registerService(options) {
55
10
  const { gatewayUrl, token, domain, skillMdPath } = options;
56
- const mdPath = skillMdPath ?? join2(process.cwd(), ".well-known", "skill.md");
57
- const skillMd = await readFile2(mdPath, "utf-8");
11
+ const mdPath = skillMdPath ?? join(process.cwd(), ".well-known", "skill.md");
12
+ const skillMd = await readFile(mdPath, "utf-8");
58
13
  if (!skillMd.trim()) {
59
14
  throw new Error(`skill.md is empty at ${mdPath}`);
60
15
  }
@@ -115,12 +70,11 @@ async function runRegister(options) {
115
70
  gatewayUrl,
116
71
  token,
117
72
  domain,
118
- skillMdPath: join2(projectDir, ".well-known", "skill.md")
73
+ skillMdPath: join(projectDir, ".well-known", "skill.md")
119
74
  });
120
75
  }
121
76
 
122
77
  export {
123
- saveToken,
124
78
  registerService,
125
79
  resolveToken,
126
80
  runRegister
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ getAgentToken,
4
+ getToken,
5
+ loadCredentials,
6
+ saveAgentToken,
7
+ saveToken
8
+ } from "./chunk-MPXYSTOK.js";
9
+ export {
10
+ getAgentToken,
11
+ getToken,
12
+ loadCredentials,
13
+ saveAgentToken,
14
+ saveToken
15
+ };
package/dist/index.js CHANGED
@@ -1,8 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- runRegister,
3
+ runRegister
4
+ } from "./chunk-OV4JPTYH.js";
5
+ import {
6
+ saveAgentToken,
4
7
  saveToken
5
- } from "./chunk-53LSAGFF.js";
8
+ } from "./chunk-MPXYSTOK.js";
6
9
 
7
10
  // src/index.ts
8
11
  import { Command } from "commander";
@@ -140,7 +143,7 @@ function extractRouteFromCall(call, projectDir) {
140
143
  const firstArg = args[0];
141
144
  if (firstArg.getKind() !== SyntaxKind.StringLiteral) return null;
142
145
  const path = firstArg.asKind(SyntaxKind.StringLiteral)?.getLiteralValue();
143
- if (!path) return null;
146
+ if (!path || !path.startsWith("/")) return null;
144
147
  const description = extractLeadingComment(call);
145
148
  const filePath = relative(projectDir, call.getSourceFile().getFilePath());
146
149
  return {
@@ -192,7 +195,7 @@ async function findTsFiles(dir) {
192
195
  }
193
196
  async function findFiles(dir, pattern) {
194
197
  const results = [];
195
- const SKIP = /* @__PURE__ */ new Set(["node_modules", ".next", "dist", "build", ".git"]);
198
+ const SKIP = /* @__PURE__ */ new Set(["node_modules", ".next", "dist", "build", ".git", ".wrangler", ".output", ".nuxt", ".svelte-kit", ".vercel"]);
196
199
  async function walk(current) {
197
200
  const entries = await readdir(current, { withFileTypes: true });
198
201
  for (const entry of entries) {
@@ -318,7 +321,7 @@ async function runGenerate(projectDir, options) {
318
321
  await writeFile2(outputPath, md);
319
322
  console.log(`Generated ${outputPath}`);
320
323
  if (options?.register) {
321
- const { registerService, resolveToken } = await import("./register-GWQ2Z2YL.js");
324
+ const { registerService, resolveToken } = await import("./register-PZERV3OY.js");
322
325
  const gatewayUrl = options.gatewayUrl ?? process.env.NKMC_GATEWAY_URL;
323
326
  const domain = options.domain ?? process.env.NKMC_DOMAIN;
324
327
  if (!gatewayUrl || !domain) {
@@ -409,6 +412,31 @@ Domain ${domain} verified successfully!`);
409
412
  console.log(` nkmc register --domain ${domain}`);
410
413
  }
411
414
 
415
+ // src/commands/auth.ts
416
+ async function runAuth(opts) {
417
+ const gatewayUrl = opts.gatewayUrl ?? process.env.NKMC_GATEWAY_URL ?? "https://api.nkmc.ai";
418
+ const sub = `agent-${Date.now()}`;
419
+ const res = await fetch(`${gatewayUrl}/auth/token`, {
420
+ method: "POST",
421
+ headers: { "Content-Type": "application/json" },
422
+ body: JSON.stringify({
423
+ sub,
424
+ svc: "gateway",
425
+ roles: ["agent"],
426
+ expiresIn: "24h"
427
+ })
428
+ });
429
+ if (!res.ok) {
430
+ const body = await res.text();
431
+ throw new Error(`Auth failed (${res.status}): ${body}`);
432
+ }
433
+ const { token } = await res.json();
434
+ await saveAgentToken(gatewayUrl, token);
435
+ console.log("Authenticated with gateway");
436
+ console.log(` Token saved to ~/.nkmc/credentials.json`);
437
+ console.log(` Gateway: ${gatewayUrl}`);
438
+ }
439
+
412
440
  // src/gateway/client.ts
413
441
  var GatewayClient = class {
414
442
  constructor(gatewayUrl, token) {
@@ -432,11 +460,16 @@ var GatewayClient = class {
432
460
  return res.json();
433
461
  }
434
462
  };
435
- function createClient() {
436
- const gatewayUrl = process.env.NKMC_GATEWAY_URL;
437
- const token = process.env.NKMC_TOKEN;
438
- if (!gatewayUrl) throw new Error("NKMC_GATEWAY_URL is required");
439
- if (!token) throw new Error("NKMC_TOKEN is required");
463
+ async function createClient() {
464
+ const { getAgentToken } = await import("./credentials-42DL3WPT.js");
465
+ const stored = await getAgentToken();
466
+ const gatewayUrl = process.env.NKMC_GATEWAY_URL ?? stored?.gatewayUrl ?? "https://api.nkmc.ai";
467
+ const token = process.env.NKMC_TOKEN ?? stored?.token ?? null;
468
+ if (!token) {
469
+ throw new Error(
470
+ "No token found. Run 'nkmc auth' first, or set NKMC_TOKEN."
471
+ );
472
+ }
440
473
  return new GatewayClient(gatewayUrl, token);
441
474
  }
442
475
 
@@ -452,7 +485,7 @@ function handleError(err) {
452
485
  function registerFsCommands(program2) {
453
486
  program2.command("ls").description("List files in a directory").argument("<path>", "Directory path").action(async (path) => {
454
487
  try {
455
- const client = createClient();
488
+ const client = await createClient();
456
489
  const result = await client.execute(`ls ${path}`);
457
490
  output(result);
458
491
  } catch (err) {
@@ -461,7 +494,7 @@ function registerFsCommands(program2) {
461
494
  });
462
495
  program2.command("cat").description("Read file contents").argument("<path>", "File path").action(async (path) => {
463
496
  try {
464
- const client = createClient();
497
+ const client = await createClient();
465
498
  const result = await client.execute(`cat ${path}`);
466
499
  output(result);
467
500
  } catch (err) {
@@ -470,7 +503,7 @@ function registerFsCommands(program2) {
470
503
  });
471
504
  program2.command("write").description("Write data to a file").argument("<path>", "File path").argument("<data>", "Data to write").action(async (path, data) => {
472
505
  try {
473
- const client = createClient();
506
+ const client = await createClient();
474
507
  const result = await client.execute(`write ${path} ${data}`);
475
508
  output(result);
476
509
  } catch (err) {
@@ -479,7 +512,7 @@ function registerFsCommands(program2) {
479
512
  });
480
513
  program2.command("rm").description("Remove a file").argument("<path>", "File path").action(async (path) => {
481
514
  try {
482
- const client = createClient();
515
+ const client = await createClient();
483
516
  const result = await client.execute(`rm ${path}`);
484
517
  output(result);
485
518
  } catch (err) {
@@ -488,7 +521,7 @@ function registerFsCommands(program2) {
488
521
  });
489
522
  program2.command("grep").description("Search file contents").argument("<pattern>", "Search pattern").argument("<path>", "File or directory path").action(async (pattern, path) => {
490
523
  try {
491
- const client = createClient();
524
+ const client = await createClient();
492
525
  const result = await client.execute(`grep ${pattern} ${path}`);
493
526
  output(result);
494
527
  } catch (err) {
@@ -536,5 +569,8 @@ program.command("register").description("Register skill.md with the gateway").op
536
569
  dir: opts.dir === "." ? process.cwd() : opts.dir
537
570
  });
538
571
  });
572
+ program.command("auth").description("Authenticate with the nkmc gateway").option("--gateway-url <url>", "Gateway URL (default: https://api.nkmc.ai)").action(async (opts) => {
573
+ await runAuth({ gatewayUrl: opts.gatewayUrl });
574
+ });
539
575
  registerFsCommands(program);
540
576
  program.parse();
@@ -3,7 +3,8 @@ import {
3
3
  registerService,
4
4
  resolveToken,
5
5
  runRegister
6
- } from "./chunk-53LSAGFF.js";
6
+ } from "./chunk-OV4JPTYH.js";
7
+ import "./chunk-MPXYSTOK.js";
7
8
  export {
8
9
  registerService,
9
10
  resolveToken,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nkmc/cli",
3
- "version": "0.0.2",
3
+ "version": "0.1.0",
4
4
  "description": "CLI for scanning and registering APIs with the nkmc gateway",
5
5
  "license": "MIT",
6
6
  "type": "module",