@layerarc/agent-sdk-cli 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,58 @@
1
+ # @layerarc/agent-sdk-cli
2
+
3
+ Agent-friendly command line interface for the LayerArc Agent API.
4
+
5
+ The CLI is designed for coding agents and power users that need stable JSON output, non-interactive execution, and environment-only authentication.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npx -y @layerarc/agent-sdk-cli projects list
11
+ ```
12
+
13
+ The package exposes the `layerarc` binary.
14
+
15
+ ## Environment
16
+
17
+ ```bash
18
+ export LAYERARC_AGENT_API_BASE_URL=https://agent.layerarc.com/v1
19
+ export LAYERARC_AGENT_API_TOKEN=<token>
20
+ export LAYERARC_PROJECT_ID=<optional default project id>
21
+ ```
22
+
23
+ `LAYERARC_AGENT_API_TOKEN` is required for API commands. `LAYERARC_PROJECT_ID` is optional and is used as the default project id. A command-level `--project-id` value takes precedence.
24
+
25
+ ## Examples
26
+
27
+ ```bash
28
+ npx -y @layerarc/agent-sdk-cli projects list
29
+ npx -y @layerarc/agent-sdk-cli pages list
30
+ npx -y @layerarc/agent-sdk-cli pages create --body page.json
31
+ npx -y @layerarc/agent-sdk-cli pages content put --page-id 123 --file page.html
32
+ npx -y @layerarc/agent-sdk-cli gsc url-inspection inspect --inspection-url https://example.com/page
33
+ ```
34
+
35
+ ## Docs And Skills
36
+
37
+ ```bash
38
+ npx -y @layerarc/agent-sdk-cli docs list
39
+ npx -y @layerarc/agent-sdk-cli docs show pages
40
+ npx -y @layerarc/agent-sdk-cli skills list
41
+ npx -y @layerarc/agent-sdk-cli skills show create-page
42
+ ```
43
+
44
+ ## Output Contract
45
+
46
+ Successful commands write machine-readable JSON to stdout:
47
+
48
+ ```json
49
+ { "ok": true, "data": {} }
50
+ ```
51
+
52
+ Failures use the same stable wrapper:
53
+
54
+ ```json
55
+ { "ok": false, "error": { "code": "AUTH_REQUIRED", "message": "Set LAYERARC_AGENT_API_TOKEN." } }
56
+ ```
57
+
58
+ Diagnostics are written to stderr only. The CLI does not prompt interactively and does not print tokens.
package/dist/cli.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ export type CliEnv = Record<string, string | undefined>;
2
+ export type CliIo = {
3
+ env: CliEnv;
4
+ fetch: typeof fetch;
5
+ readFile: (path: string) => Promise<string>;
6
+ readStdin: () => Promise<string>;
7
+ };
8
+ export type CliResult = {
9
+ exitCode: number;
10
+ stdout: string;
11
+ stderr: string;
12
+ };
13
+ export declare function execute(argv: string[], io: CliIo): Promise<CliResult>;
package/dist/cli.js ADDED
@@ -0,0 +1,464 @@
1
+ const DEFAULT_API_BASE_URL = "https://agent.layerarc.com/v1";
2
+ const DOC_PATHS = [
3
+ "/docs/cli.md",
4
+ "/docs/cli/projects.md",
5
+ "/docs/cli/pipelines.md",
6
+ "/docs/cli/pages.md",
7
+ "/docs/cli/page-content.md",
8
+ "/docs/cli/gsc.md",
9
+ "/docs/cli/gsc-sites.md",
10
+ "/docs/cli/gsc-search-analytics.md",
11
+ "/docs/cli/gsc-sitemaps.md",
12
+ "/docs/cli/gsc-url-inspection.md",
13
+ ];
14
+ const SKILL_PATHS = [
15
+ "/skills/cli.md",
16
+ "/skills/cli/create-page.md",
17
+ "/skills/cli/update-page.md",
18
+ "/skills/cli/attach-pipeline.md",
19
+ "/skills/cli/full-page-lifecycle.md",
20
+ "/skills/cli/gsc-select-property.md",
21
+ "/skills/cli/gsc-get-summary.md",
22
+ "/skills/cli/gsc-list-page-rows.md",
23
+ "/skills/cli/gsc-top-queries.md",
24
+ "/skills/cli/gsc-high-potential-pages.md",
25
+ "/skills/cli/gsc-high-potential-ranking-pages.md",
26
+ "/skills/cli/gsc-list-sitemaps.md",
27
+ "/skills/cli/gsc-submit-sitemap.md",
28
+ "/skills/cli/gsc-inspect-url.md",
29
+ ];
30
+ class CliError extends Error {
31
+ code;
32
+ exitCode;
33
+ constructor(code, message, exitCode = 1) {
34
+ super(message);
35
+ this.code = code;
36
+ this.exitCode = exitCode;
37
+ }
38
+ }
39
+ export async function execute(argv, io) {
40
+ try {
41
+ const parsed = parseArgs(argv);
42
+ const config = getConfig(io.env, parsed.options);
43
+ const data = await runCommand(parsed, config, io);
44
+ return {
45
+ exitCode: 0,
46
+ stdout: `${JSON.stringify({ ok: true, data })}\n`,
47
+ stderr: "",
48
+ };
49
+ }
50
+ catch (error) {
51
+ const normalized = normalizeError(error);
52
+ return {
53
+ exitCode: error instanceof CliError ? error.exitCode : 1,
54
+ stdout: `${JSON.stringify({ ok: false, error: normalized })}\n`,
55
+ stderr: "",
56
+ };
57
+ }
58
+ }
59
+ function parseArgs(argv) {
60
+ const command = [];
61
+ const options = {};
62
+ for (let index = 0; index < argv.length; index += 1) {
63
+ const arg = argv[index];
64
+ if (!arg.startsWith("--")) {
65
+ command.push(arg);
66
+ continue;
67
+ }
68
+ const withoutPrefix = arg.slice(2);
69
+ const equalsIndex = withoutPrefix.indexOf("=");
70
+ if (equalsIndex >= 0) {
71
+ options[withoutPrefix.slice(0, equalsIndex)] = withoutPrefix.slice(equalsIndex + 1);
72
+ continue;
73
+ }
74
+ const next = argv[index + 1];
75
+ if (next && !next.startsWith("--")) {
76
+ options[withoutPrefix] = next;
77
+ index += 1;
78
+ }
79
+ else {
80
+ options[withoutPrefix] = true;
81
+ }
82
+ }
83
+ return {
84
+ command,
85
+ options,
86
+ };
87
+ }
88
+ function getConfig(env, options) {
89
+ return {
90
+ apiBaseUrl: env.LAYERARC_AGENT_API_BASE_URL || DEFAULT_API_BASE_URL,
91
+ token: env.LAYERARC_AGENT_API_TOKEN,
92
+ projectId: stringOption(options, "project-id") || env.LAYERARC_PROJECT_ID,
93
+ };
94
+ }
95
+ async function runCommand(parsed, config, io) {
96
+ const [group, name, subcommand, action] = parsed.command;
97
+ if (!group) {
98
+ throw new CliError("COMMAND_REQUIRED", "Provide a LayerArc command.");
99
+ }
100
+ if (group === "docs") {
101
+ return runDocsCommand(parsed, config, io);
102
+ }
103
+ if (group === "skills") {
104
+ return runSkillsCommand(parsed, config, io);
105
+ }
106
+ const spec = await buildApiRequest(parsed, config, io);
107
+ return sendApiRequest(spec, config, io.fetch);
108
+ }
109
+ async function buildApiRequest(parsed, config, io) {
110
+ const [group, name, subcommand, action] = parsed.command;
111
+ const options = parsed.options;
112
+ if (group === "projects" && name === "list") {
113
+ return { method: "GET", path: "/projects" };
114
+ }
115
+ if (group === "projects" && name === "get") {
116
+ return { method: "GET", path: `/projects/${encodeURIComponent(requireProjectId(config))}` };
117
+ }
118
+ if (group === "pipelines" && name === "get") {
119
+ return {
120
+ method: "GET",
121
+ path: `/projects/${encodeURIComponent(requireProjectId(config))}/pipelines/${encodeURIComponent(requireStringOption(options, "pipeline-id", "PIPELINE_ID_REQUIRED"))}`,
122
+ };
123
+ }
124
+ if (group === "pages") {
125
+ return buildPagesRequest(name, subcommand, options, config, io);
126
+ }
127
+ if (group === "gsc") {
128
+ return buildGscRequest(name, subcommand, action, options, config, io);
129
+ }
130
+ throw new CliError("UNKNOWN_COMMAND", `Unknown command: ${parsed.command.join(" ")}`);
131
+ }
132
+ async function buildPagesRequest(name, subcommand, options, config, io) {
133
+ const projectPath = `/projects/${encodeURIComponent(requireProjectId(config))}`;
134
+ if (name === "list") {
135
+ return {
136
+ method: "GET",
137
+ path: `${projectPath}/pages`,
138
+ query: pickStringOptions(options, ["limit", "offset", "language", "slug"]),
139
+ };
140
+ }
141
+ if (name === "create") {
142
+ return {
143
+ method: "POST",
144
+ path: `${projectPath}/pages`,
145
+ body: await readJsonBody(options, io),
146
+ };
147
+ }
148
+ if (name === "get") {
149
+ return {
150
+ method: "GET",
151
+ path: `${projectPath}/pages/${encodeURIComponent(requireStringOption(options, "page-id", "PAGE_ID_REQUIRED"))}`,
152
+ };
153
+ }
154
+ if (name === "update") {
155
+ return {
156
+ method: "PATCH",
157
+ path: `${projectPath}/pages/${encodeURIComponent(requireStringOption(options, "page-id", "PAGE_ID_REQUIRED"))}`,
158
+ body: await readJsonBody(options, io),
159
+ };
160
+ }
161
+ if (name === "delete") {
162
+ return {
163
+ method: "DELETE",
164
+ path: `${projectPath}/pages/${encodeURIComponent(requireStringOption(options, "page-id", "PAGE_ID_REQUIRED"))}`,
165
+ };
166
+ }
167
+ if (name === "content") {
168
+ const pagePath = `${projectPath}/pages/${encodeURIComponent(requireStringOption(options, "page-id", "PAGE_ID_REQUIRED"))}/content`;
169
+ if (subcommand === "get") {
170
+ return {
171
+ method: "GET",
172
+ path: pagePath,
173
+ contentType: "html",
174
+ };
175
+ }
176
+ if (subcommand === "put") {
177
+ return {
178
+ method: "PUT",
179
+ path: pagePath,
180
+ body: await readHtmlBody(options, io),
181
+ contentType: "html",
182
+ };
183
+ }
184
+ }
185
+ throw new CliError("UNKNOWN_COMMAND", "Unknown pages command.");
186
+ }
187
+ async function buildGscRequest(name, subcommand, action, options, config, io) {
188
+ const projectPath = `/projects/${encodeURIComponent(requireProjectId(config))}/gsc`;
189
+ if (name === "sites") {
190
+ if (subcommand === "list" || subcommand === "get" || subcommand === "delete") {
191
+ return { method: "POST", path: `${projectPath}/sites/${subcommand}`, body: {} };
192
+ }
193
+ if (subcommand === "add") {
194
+ return {
195
+ method: "POST",
196
+ path: `${projectPath}/sites/add`,
197
+ body: { siteUrl: requireStringOption(options, "site-url", "SITE_URL_REQUIRED") },
198
+ };
199
+ }
200
+ }
201
+ if (name === "search-analytics" && subcommand === "query") {
202
+ return {
203
+ method: "POST",
204
+ path: `${projectPath}/search-analytics/query`,
205
+ body: await readJsonBody(options, io),
206
+ };
207
+ }
208
+ if (name === "sitemaps") {
209
+ if (subcommand === "list") {
210
+ return {
211
+ method: "POST",
212
+ path: `${projectPath}/sitemaps/list`,
213
+ body: stringOption(options, "body") ? await readJsonBody(options, io) : {},
214
+ };
215
+ }
216
+ if (subcommand === "get" || subcommand === "submit" || subcommand === "delete") {
217
+ return {
218
+ method: "POST",
219
+ path: `${projectPath}/sitemaps/${subcommand}`,
220
+ body: { feedpath: requireStringOption(options, "feedpath", "FEEDPATH_REQUIRED") },
221
+ };
222
+ }
223
+ }
224
+ if (name === "url-inspection" && subcommand === "inspect") {
225
+ const languageCode = stringOption(options, "language-code");
226
+ return {
227
+ method: "POST",
228
+ path: `${projectPath}/url-inspection/inspect`,
229
+ body: {
230
+ inspectionUrl: requireStringOption(options, "inspection-url", "INSPECTION_URL_REQUIRED"),
231
+ ...(languageCode ? { languageCode } : {}),
232
+ },
233
+ };
234
+ }
235
+ throw new CliError("UNKNOWN_COMMAND", "Unknown GSC command.");
236
+ }
237
+ async function sendApiRequest(spec, config, fetchImpl) {
238
+ if (!config.token) {
239
+ throw new CliError("AUTH_REQUIRED", "Set LAYERARC_AGENT_API_TOKEN.");
240
+ }
241
+ const url = new URL(`${config.apiBaseUrl}${spec.path}`);
242
+ for (const [key, value] of Object.entries(spec.query || {})) {
243
+ if (value !== undefined) {
244
+ url.searchParams.set(key, value);
245
+ }
246
+ }
247
+ const headers = new Headers({
248
+ Accept: spec.contentType === "html" ? "text/html, application/json" : "application/json",
249
+ Authorization: `Bearer ${config.token}`,
250
+ });
251
+ let body;
252
+ if (spec.body !== undefined) {
253
+ if (spec.contentType === "html") {
254
+ headers.set("Content-Type", "text/html; charset=utf-8");
255
+ body = String(spec.body);
256
+ }
257
+ else {
258
+ headers.set("Content-Type", "application/json");
259
+ body = JSON.stringify(spec.body);
260
+ }
261
+ }
262
+ const response = await fetchImpl(url.toString(), {
263
+ method: spec.method,
264
+ headers,
265
+ body,
266
+ });
267
+ const text = await response.text();
268
+ const payload = parseTextPayload(text, response.headers.get("content-type") || "");
269
+ if (!response.ok) {
270
+ throw responseError(response.status, payload);
271
+ }
272
+ if (spec.contentType === "html" && typeof payload === "string") {
273
+ return { content: payload };
274
+ }
275
+ if (payload && typeof payload === "object" && "data" in payload) {
276
+ return payload.data;
277
+ }
278
+ return payload;
279
+ }
280
+ async function runDocsCommand(parsed, config, io) {
281
+ const [, action, slug] = parsed.command;
282
+ if (action === "list") {
283
+ return {
284
+ docs: DOC_PATHS.map((path) => pathToSlug(path, "/docs/cli/")),
285
+ };
286
+ }
287
+ if (action === "show") {
288
+ const path = docsSlugToPath(requirePositional(slug, "DOC_SLUG_REQUIRED"));
289
+ return {
290
+ path,
291
+ content: await fetchTemplate(path, config, io.fetch),
292
+ };
293
+ }
294
+ throw new CliError("UNKNOWN_COMMAND", "Unknown docs command.");
295
+ }
296
+ async function runSkillsCommand(parsed, config, io) {
297
+ const [, action, slug] = parsed.command;
298
+ if (action === "list") {
299
+ return {
300
+ skills: SKILL_PATHS.map((path) => pathToSlug(path, "/skills/cli/")),
301
+ };
302
+ }
303
+ if (action === "show") {
304
+ const path = skillSlugToPath(requirePositional(slug, "SKILL_SLUG_REQUIRED"));
305
+ return {
306
+ path,
307
+ content: await fetchTemplate(path, config, io.fetch),
308
+ };
309
+ }
310
+ throw new CliError("UNKNOWN_COMMAND", "Unknown skills command.");
311
+ }
312
+ async function fetchTemplate(path, config, fetchImpl) {
313
+ const origin = new URL(config.apiBaseUrl).origin;
314
+ const response = await fetchImpl(`${origin}/internal/docs-template${path}`, {
315
+ headers: {
316
+ Accept: "text/markdown",
317
+ },
318
+ });
319
+ const markdown = await response.text();
320
+ if (!response.ok) {
321
+ throw new CliError("DOCS_UNAVAILABLE", markdown || `Docs request failed with status ${response.status}.`);
322
+ }
323
+ return markdown
324
+ .replaceAll("{{AGENT_API_BASE_URL}}", config.apiBaseUrl)
325
+ .replaceAll("{{DOCS_BASE_URL}}", origin)
326
+ .replaceAll("{{AUTH_TOKEN}}", "<YOUR_AUTH_TOKEN>");
327
+ }
328
+ function docsSlugToPath(slug) {
329
+ const path = slugToPath(slug, "/docs/cli/");
330
+ if (!DOC_PATHS.includes(path)) {
331
+ throw new CliError("DOC_NOT_FOUND", `Unknown docs page: ${slug}`);
332
+ }
333
+ return path;
334
+ }
335
+ function skillSlugToPath(slug) {
336
+ const path = slugToPath(slug, "/skills/cli/");
337
+ if (!SKILL_PATHS.includes(path)) {
338
+ throw new CliError("SKILL_NOT_FOUND", `Unknown skill page: ${slug}`);
339
+ }
340
+ return path;
341
+ }
342
+ function slugToPath(slug, prefix) {
343
+ if (slug === "index") {
344
+ return prefix === "/docs/cli/" ? "/docs/cli.md" : "/skills/cli.md";
345
+ }
346
+ const normalized = slug.endsWith(".md") ? slug : `${slug}.md`;
347
+ if (prefix === "/docs/cli/" && (normalized === "cli.md" || normalized.startsWith("cli/"))) {
348
+ return `/docs/${normalized}`;
349
+ }
350
+ if (prefix === "/skills/cli/" && (normalized === "cli.md" || normalized.startsWith("cli/"))) {
351
+ return `/skills/${normalized}`;
352
+ }
353
+ return `${prefix}${normalized}`;
354
+ }
355
+ function pathToSlug(path, prefix) {
356
+ if (path === "/docs/cli.md" || path === "/skills/cli.md") {
357
+ return "index";
358
+ }
359
+ return path.slice(prefix.length).replace(/\.md$/, "");
360
+ }
361
+ async function readJsonBody(options, io) {
362
+ const path = requireStringOption(options, "body", "BODY_REQUIRED");
363
+ const text = await io.readFile(path);
364
+ try {
365
+ return JSON.parse(text);
366
+ }
367
+ catch {
368
+ throw new CliError("INVALID_JSON_BODY", `Body file must contain valid JSON: ${path}`);
369
+ }
370
+ }
371
+ async function readHtmlBody(options, io) {
372
+ const file = stringOption(options, "file");
373
+ const useStdin = options.stdin === true;
374
+ if (file && useStdin) {
375
+ throw new CliError("CONTENT_SOURCE_CONFLICT", "Use either --file or --stdin, not both.");
376
+ }
377
+ if (file) {
378
+ return io.readFile(file);
379
+ }
380
+ if (useStdin) {
381
+ return io.readStdin();
382
+ }
383
+ throw new CliError("CONTENT_SOURCE_REQUIRED", "Provide --file or --stdin.");
384
+ }
385
+ function pickStringOptions(options, names) {
386
+ const picked = {};
387
+ for (const name of names) {
388
+ picked[name] = stringOption(options, name);
389
+ }
390
+ return picked;
391
+ }
392
+ function requireProjectId(config) {
393
+ if (!config.projectId) {
394
+ throw new CliError("PROJECT_ID_REQUIRED", "Provide --project-id or set LAYERARC_PROJECT_ID.");
395
+ }
396
+ return config.projectId;
397
+ }
398
+ function requireStringOption(options, name, code) {
399
+ const value = stringOption(options, name);
400
+ if (!value) {
401
+ throw new CliError(code, `Provide --${name}.`);
402
+ }
403
+ return value;
404
+ }
405
+ function requirePositional(value, code) {
406
+ if (!value) {
407
+ throw new CliError(code, "Provide the required command argument.");
408
+ }
409
+ return value;
410
+ }
411
+ function stringOption(options, name) {
412
+ const value = options[name];
413
+ return typeof value === "string" && value.length > 0 ? value : undefined;
414
+ }
415
+ function parseTextPayload(text, contentType) {
416
+ if (!text) {
417
+ return null;
418
+ }
419
+ if (contentType.includes("application/json")) {
420
+ try {
421
+ return JSON.parse(text);
422
+ }
423
+ catch {
424
+ return text;
425
+ }
426
+ }
427
+ try {
428
+ return JSON.parse(text);
429
+ }
430
+ catch {
431
+ return text;
432
+ }
433
+ }
434
+ function responseError(status, payload) {
435
+ if (payload && typeof payload === "object") {
436
+ const record = payload;
437
+ const code = typeof record.error?.code === "string" ? record.error.code : `HTTP_${status}`;
438
+ const message = typeof record.error?.message === "string"
439
+ ? record.error.message
440
+ : typeof record.message === "string"
441
+ ? record.message
442
+ : `Request failed with status ${status}.`;
443
+ return new CliError(code, message);
444
+ }
445
+ return new CliError(`HTTP_${status}`, typeof payload === "string" && payload ? payload : `Request failed with status ${status}.`);
446
+ }
447
+ function normalizeError(error) {
448
+ if (error instanceof CliError) {
449
+ return {
450
+ code: error.code,
451
+ message: error.message,
452
+ };
453
+ }
454
+ if (error instanceof Error) {
455
+ return {
456
+ code: "CLI_ERROR",
457
+ message: error.message,
458
+ };
459
+ }
460
+ return {
461
+ code: "CLI_ERROR",
462
+ message: "Unexpected CLI error.",
463
+ };
464
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ import { readFile } from "node:fs/promises";
3
+ import process from "node:process";
4
+ import { execute } from "./cli.js";
5
+ async function readStdin() {
6
+ const chunks = [];
7
+ for await (const chunk of process.stdin) {
8
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
9
+ }
10
+ return Buffer.concat(chunks).toString("utf8");
11
+ }
12
+ const result = await execute(process.argv.slice(2), {
13
+ env: process.env,
14
+ fetch: globalThis.fetch,
15
+ readFile: (path) => readFile(path, "utf8"),
16
+ readStdin,
17
+ });
18
+ if (result.stderr) {
19
+ process.stderr.write(result.stderr);
20
+ }
21
+ process.stdout.write(result.stdout);
22
+ process.exitCode = result.exitCode;
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@layerarc/agent-sdk-cli",
3
+ "version": "0.1.0",
4
+ "description": "Agent-friendly command line interface for the LayerArc Agent API.",
5
+ "type": "module",
6
+ "bin": {
7
+ "layerarc": "dist/index.js"
8
+ },
9
+ "homepage": "https://layerarc.com/cli",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+ssh://git@github.com/capgoai/capgo-v1.git",
13
+ "directory": "apps/layerarc-sdk-cli"
14
+ },
15
+ "bugs": {
16
+ "url": "https://github.com/capgoai/capgo-v1/issues"
17
+ },
18
+ "license": "UNLICENSED",
19
+ "keywords": [
20
+ "layerarc",
21
+ "agent",
22
+ "cli",
23
+ "seo",
24
+ "gsc"
25
+ ],
26
+ "engines": {
27
+ "node": ">=18.17"
28
+ },
29
+ "files": [
30
+ "dist",
31
+ "README.md"
32
+ ],
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "scripts": {
37
+ "build": "tsc -p tsconfig.json",
38
+ "prepack": "bun run build",
39
+ "typecheck": "tsc -p tsconfig.json --pretty false --noEmit",
40
+ "test": "bun test src"
41
+ },
42
+ "devDependencies": {
43
+ "@types/bun": "^1.3.10",
44
+ "@types/node": "^22.10.2",
45
+ "typescript": "^5.5.2"
46
+ }
47
+ }