@nkmc/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.
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- getToken
3
+ getToken,
4
+ saveToken
4
5
  } from "./chunk-MPXYSTOK.js";
5
6
 
6
7
  // src/commands/register.ts
@@ -29,12 +30,35 @@ async function registerService(options) {
29
30
  const result = await res.json();
30
31
  console.log(`Registered ${result.name} as ${result.domain}`);
31
32
  }
33
+ async function renewToken(gatewayUrl, domain) {
34
+ const baseUrl = gatewayUrl.replace(/\/$/, "");
35
+ try {
36
+ const res = await fetch(`${baseUrl}/domains/verify`, {
37
+ method: "POST",
38
+ headers: { "Content-Type": "application/json" },
39
+ body: JSON.stringify({ domain })
40
+ });
41
+ if (!res.ok) return null;
42
+ const data = await res.json();
43
+ if (!data.publishToken) return null;
44
+ await saveToken(domain, data.publishToken);
45
+ console.log(`Token renewed for ${domain}`);
46
+ return data.publishToken;
47
+ } catch {
48
+ return null;
49
+ }
50
+ }
32
51
  async function resolveToken(options) {
33
52
  if (options.token) return options.token;
34
53
  if (process.env.NKMC_PUBLISH_TOKEN) return process.env.NKMC_PUBLISH_TOKEN;
35
54
  if (options.domain) {
36
55
  const stored = await getToken(options.domain);
37
56
  if (stored) return stored;
57
+ const gw = options.gatewayUrl ?? process.env.NKMC_GATEWAY_URL;
58
+ if (gw) {
59
+ const renewed = await renewToken(gw, options.domain);
60
+ if (renewed) return renewed;
61
+ }
38
62
  }
39
63
  if (options.adminToken) {
40
64
  console.warn("Warning: --admin-token is deprecated. Use `nkmc claim` to obtain a publish token.");
@@ -49,13 +73,8 @@ async function resolveToken(options) {
49
73
  }
50
74
  async function runRegister(options) {
51
75
  const projectDir = options.dir ?? process.cwd();
52
- const gatewayUrl = options.gatewayUrl ?? process.env.NKMC_GATEWAY_URL;
76
+ const gatewayUrl = options.gatewayUrl ?? process.env.NKMC_GATEWAY_URL ?? "https://api.nkmc.ai";
53
77
  const domain = options.domain ?? process.env.NKMC_DOMAIN;
54
- if (!gatewayUrl) {
55
- throw new Error(
56
- "Gateway URL is required. Use --gateway-url or NKMC_GATEWAY_URL env var."
57
- );
58
- }
59
78
  if (!domain) {
60
79
  throw new Error(
61
80
  "Domain is required. Use --domain or NKMC_DOMAIN env var."
@@ -64,7 +83,8 @@ async function runRegister(options) {
64
83
  const token = await resolveToken({
65
84
  token: options.token,
66
85
  adminToken: options.adminToken,
67
- domain
86
+ domain,
87
+ gatewayUrl
68
88
  });
69
89
  await registerService({
70
90
  gatewayUrl,
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runRegister
4
- } from "./chunk-OV4JPTYH.js";
4
+ } from "./chunk-5ZYHHSZI.js";
5
5
  import {
6
6
  saveAgentToken,
7
7
  saveToken
@@ -115,18 +115,137 @@ async function scanRoutes(projectDir, framework) {
115
115
  }
116
116
  return scanCodeRoutes(projectDir);
117
117
  }
118
+ function extractMountCalls(project) {
119
+ const mounts = [];
120
+ for (const sourceFile of project.getSourceFiles()) {
121
+ const filePath = sourceFile.getFilePath();
122
+ const fileDir = dirname(filePath);
123
+ const importMap = /* @__PURE__ */ new Map();
124
+ for (const imp of sourceFile.getImportDeclarations()) {
125
+ const specifier = imp.getModuleSpecifierValue();
126
+ const resolved = resolveModulePath(project, fileDir, specifier);
127
+ if (!resolved) continue;
128
+ for (const named of imp.getNamedImports()) {
129
+ const localName = named.getAliasNode()?.getText() || named.getName();
130
+ importMap.set(localName, resolved);
131
+ }
132
+ const defaultImport = imp.getDefaultImport();
133
+ if (defaultImport) {
134
+ importMap.set(defaultImport.getText(), resolved);
135
+ }
136
+ }
137
+ const calls = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression);
138
+ for (const call of calls) {
139
+ const expr = call.getExpression();
140
+ if (expr.getKind() !== SyntaxKind.PropertyAccessExpression) continue;
141
+ const propAccess = expr.asKind(SyntaxKind.PropertyAccessExpression);
142
+ if (!propAccess || propAccess.getName() !== "route") continue;
143
+ const args = call.getArguments();
144
+ if (args.length < 2) continue;
145
+ const pathArg = args[0];
146
+ if (pathArg.getKind() !== SyntaxKind.StringLiteral) continue;
147
+ const mountPath = pathArg.asKind(SyntaxKind.StringLiteral)?.getLiteralValue();
148
+ if (!mountPath) continue;
149
+ const childVar = args[1].getText();
150
+ const parentVar = propAccess.getExpression().getText();
151
+ mounts.push({
152
+ parentVar,
153
+ mountPath,
154
+ childVar,
155
+ childSourceFile: importMap.get(childVar) || null,
156
+ inFile: filePath
157
+ });
158
+ }
159
+ }
160
+ return mounts;
161
+ }
162
+ function resolveModulePath(project, fromDir, specifier) {
163
+ if (!specifier.startsWith(".")) return null;
164
+ const base = join3(fromDir, specifier);
165
+ const extensions = [".ts", ".tsx", ".js", ".jsx"];
166
+ for (const ext of extensions) {
167
+ if (project.getSourceFile(base + ext)) return base + ext;
168
+ }
169
+ for (const ext of extensions) {
170
+ const indexPath = join3(base, "index" + ext);
171
+ if (project.getSourceFile(indexPath)) return indexPath;
172
+ }
173
+ return null;
174
+ }
175
+ function computePrefixes(mounts) {
176
+ const filePrefixMap = /* @__PURE__ */ new Map();
177
+ const varPrefixMap = /* @__PURE__ */ new Map();
178
+ const localChildKeys = /* @__PURE__ */ new Set();
179
+ for (const m of mounts) {
180
+ if (!m.childSourceFile) {
181
+ localChildKeys.add(`${m.inFile}\0${m.childVar}`);
182
+ }
183
+ }
184
+ for (const m of mounts) {
185
+ const parentKey = `${m.inFile}\0${m.parentVar}`;
186
+ if (!localChildKeys.has(parentKey)) {
187
+ varPrefixMap.set(parentKey, "");
188
+ }
189
+ }
190
+ let changed = true;
191
+ let iterations = 0;
192
+ while (changed && iterations < 10) {
193
+ changed = false;
194
+ iterations++;
195
+ for (const m of mounts) {
196
+ const parentKey = `${m.inFile}\0${m.parentVar}`;
197
+ let parentPrefix = varPrefixMap.get(parentKey);
198
+ if (parentPrefix === void 0 && filePrefixMap.has(m.inFile)) {
199
+ parentPrefix = filePrefixMap.get(m.inFile);
200
+ varPrefixMap.set(parentKey, parentPrefix);
201
+ changed = true;
202
+ }
203
+ if (parentPrefix === void 0) continue;
204
+ const fullPrefix = joinPaths(parentPrefix, m.mountPath);
205
+ if (m.childSourceFile) {
206
+ if (!filePrefixMap.has(m.childSourceFile)) {
207
+ filePrefixMap.set(m.childSourceFile, fullPrefix);
208
+ changed = true;
209
+ }
210
+ } else {
211
+ const childKey = `${m.inFile}\0${m.childVar}`;
212
+ if (!varPrefixMap.has(childKey)) {
213
+ varPrefixMap.set(childKey, fullPrefix);
214
+ changed = true;
215
+ }
216
+ }
217
+ }
218
+ }
219
+ return { filePrefixMap, varPrefixMap };
220
+ }
221
+ function joinPaths(prefix, path) {
222
+ if (!prefix) return path;
223
+ if (path === "/") return prefix;
224
+ const cleanPrefix = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
225
+ const cleanPath = path.startsWith("/") ? path : "/" + path;
226
+ return cleanPrefix + cleanPath;
227
+ }
118
228
  async function scanCodeRoutes(projectDir) {
119
229
  const project = new Project({ skipAddingFilesFromTsConfig: true });
120
230
  const srcFiles = await findTsFiles(projectDir);
121
231
  for (const filePath of srcFiles) {
122
232
  project.addSourceFileAtPath(filePath);
123
233
  }
234
+ const mounts = extractMountCalls(project);
235
+ const { filePrefixMap, varPrefixMap } = computePrefixes(mounts);
124
236
  const routes = [];
125
237
  for (const sourceFile of project.getSourceFiles()) {
238
+ const absPath = sourceFile.getFilePath();
239
+ const filePrefix = filePrefixMap.get(absPath);
126
240
  const calls = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression);
127
241
  for (const call of calls) {
128
- const route = extractRouteFromCall(call, projectDir);
129
- if (route) routes.push(route);
242
+ const result = extractRouteFromCall(call, projectDir);
243
+ if (!result) continue;
244
+ const { route, callerVar } = result;
245
+ const varKey = `${absPath}\0${callerVar}`;
246
+ const prefix = varPrefixMap.get(varKey) ?? filePrefix ?? "";
247
+ route.path = joinPaths(prefix, route.path);
248
+ routes.push(route);
130
249
  }
131
250
  }
132
251
  return routes;
@@ -146,11 +265,15 @@ function extractRouteFromCall(call, projectDir) {
146
265
  if (!path || !path.startsWith("/")) return null;
147
266
  const description = extractLeadingComment(call);
148
267
  const filePath = relative(projectDir, call.getSourceFile().getFilePath());
268
+ const callerVar = propAccess.getExpression().getText();
149
269
  return {
150
- method: methodName.toUpperCase(),
151
- path,
152
- filePath,
153
- description
270
+ route: {
271
+ method: methodName.toUpperCase(),
272
+ path,
273
+ filePath,
274
+ description
275
+ },
276
+ callerVar
154
277
  };
155
278
  }
156
279
  function extractLeadingComment(call) {
@@ -321,7 +444,7 @@ async function runGenerate(projectDir, options) {
321
444
  await writeFile2(outputPath, md);
322
445
  console.log(`Generated ${outputPath}`);
323
446
  if (options?.register) {
324
- const { registerService, resolveToken } = await import("./register-PZERV3OY.js");
447
+ const { registerService, resolveToken } = await import("./register-7SUETZ7U.js");
325
448
  const gatewayUrl = options.gatewayUrl ?? process.env.NKMC_GATEWAY_URL;
326
449
  const domain = options.domain ?? process.env.NKMC_DOMAIN;
327
450
  if (!gatewayUrl || !domain) {
@@ -477,6 +600,34 @@ async function createClient() {
477
600
  function output(result) {
478
601
  console.log(JSON.stringify(result));
479
602
  }
603
+ function isSearchResults(data) {
604
+ if (!Array.isArray(data) || data.length === 0) return false;
605
+ const first = data[0];
606
+ return typeof first === "object" && first !== null && "domain" in first && "name" in first;
607
+ }
608
+ function isEndpointResults(data) {
609
+ if (!Array.isArray(data) || data.length === 0) return false;
610
+ const first = data[0];
611
+ return typeof first === "object" && first !== null && "method" in first && "path" in first;
612
+ }
613
+ function formatGrepResults(data) {
614
+ if (isSearchResults(data)) {
615
+ return data.map((s) => {
616
+ const header = `${s.domain} \u2014 ${s.name}`;
617
+ if (!s.matchedEndpoints || s.matchedEndpoints.length === 0) {
618
+ return header;
619
+ }
620
+ const endpoints = s.matchedEndpoints.map((e) => ` ${e.method.padEnd(6)} ${e.path} \u2014 ${e.description}`).join("\n");
621
+ return `${header} \xB7 ${s.matchedEndpoints.length} matched
622
+ ${endpoints}`;
623
+ }).join("\n\n");
624
+ }
625
+ if (isEndpointResults(data)) {
626
+ if (data.length === 0) return "No matching endpoints.";
627
+ return data.map((e) => `${e.method.padEnd(6)} ${e.path} \u2014 ${e.description}`).join("\n");
628
+ }
629
+ return JSON.stringify(data);
630
+ }
480
631
  function handleError(err) {
481
632
  const message = err instanceof Error ? err.message : String(err);
482
633
  console.error(JSON.stringify({ error: message }));
@@ -523,6 +674,41 @@ function registerFsCommands(program2) {
523
674
  try {
524
675
  const client = await createClient();
525
676
  const result = await client.execute(`grep ${pattern} ${path}`);
677
+ console.log(formatGrepResults(result));
678
+ } catch (err) {
679
+ handleError(err);
680
+ }
681
+ });
682
+ program2.command("pipe").description("Pipe commands: cat <path> | write <path>").argument("<expression...>", "Pipe expression").action(async (expression) => {
683
+ try {
684
+ const full = expression.join(" ");
685
+ const parts = full.split("|").map((s) => s.trim());
686
+ if (parts.length !== 2) {
687
+ throw new Error("Pipe expression must have exactly two stages separated by '|'");
688
+ }
689
+ const [source, target] = parts;
690
+ if (!source.startsWith("cat ")) {
691
+ throw new Error("Pipe step 1 must be a 'cat' command");
692
+ }
693
+ if (!target.startsWith("write ")) {
694
+ throw new Error("Pipe step 2 must be a 'write' command");
695
+ }
696
+ const client = await createClient();
697
+ let data;
698
+ try {
699
+ data = await client.execute(source);
700
+ } catch (err) {
701
+ const msg = err instanceof Error ? err.message : String(err);
702
+ throw new Error(`Pipe step 1 failed: ${msg}`);
703
+ }
704
+ const writePath = target.slice("write ".length).trim();
705
+ let result;
706
+ try {
707
+ result = await client.execute(`write ${writePath} ${JSON.stringify(data)}`);
708
+ } catch (err) {
709
+ const msg = err instanceof Error ? err.message : String(err);
710
+ throw new Error(`Pipe step 2 failed: ${msg}`);
711
+ }
526
712
  output(result);
527
713
  } catch (err) {
528
714
  handleError(err);
@@ -548,12 +734,7 @@ program.command("generate").description("Scan project and generate skill.md").ar
548
734
  });
549
735
  });
550
736
  program.command("claim <domain>").description("Claim domain ownership via DNS verification").option("--verify", "Verify DNS record and obtain publish token").option("--gateway-url <url>", "Gateway URL").action(async (domain, opts) => {
551
- const gatewayUrl = opts.gatewayUrl ?? process.env.NKMC_GATEWAY_URL;
552
- if (!gatewayUrl) {
553
- throw new Error(
554
- "Gateway URL is required. Use --gateway-url or NKMC_GATEWAY_URL env var."
555
- );
556
- }
737
+ const gatewayUrl = opts.gatewayUrl ?? process.env.NKMC_GATEWAY_URL ?? "https://api.nkmc.ai";
557
738
  await runClaim({
558
739
  gatewayUrl,
559
740
  domain,
@@ -3,7 +3,7 @@ import {
3
3
  registerService,
4
4
  resolveToken,
5
5
  runRegister
6
- } from "./chunk-OV4JPTYH.js";
6
+ } from "./chunk-5ZYHHSZI.js";
7
7
  import "./chunk-MPXYSTOK.js";
8
8
  export {
9
9
  registerService,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nkmc/cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "CLI for scanning and registering APIs with the nkmc gateway",
5
5
  "license": "MIT",
6
6
  "type": "module",