@index365/cli 0.1.0 → 0.1.2
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/package.json +1 -1
- package/src/cli.mjs +95 -7
- package/src/client.mjs +7 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@index365/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "index365 CLI - run AI-readiness and marketing-signal audits and read findings from your terminal, CI, or agents. Wraps the public /api/v1.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/cli.mjs
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
writeConfigFile,
|
|
13
13
|
} from "./config.mjs";
|
|
14
14
|
|
|
15
|
-
export const CLI_VERSION = "0.1.
|
|
15
|
+
export const CLI_VERSION = "0.1.2";
|
|
16
16
|
|
|
17
17
|
const HELP = `index365 - website audits from your terminal, CI, or agents
|
|
18
18
|
|
|
@@ -24,13 +24,15 @@ COMMANDS
|
|
|
24
24
|
logout Remove the saved API key
|
|
25
25
|
doctor Verify auth, scopes, API reachability, contract version
|
|
26
26
|
projects list List projects in your organization
|
|
27
|
-
|
|
27
|
+
projects create Create a project (--domain <domain> [--name <name>])
|
|
28
|
+
projects delete Delete a project (<projectId> --confirm <domain>)
|
|
29
|
+
runs start Start a paid AI-readiness audit (--project <id>, repeatable --page <path>, optional --wait)
|
|
28
30
|
runs get Show one run (<runId>)
|
|
29
31
|
findings list List findings for a run (--run <id>)
|
|
30
32
|
findings get Show one finding (--run <id> <findingId>)
|
|
31
33
|
reports context Compact agent report context for a run (<runId>)
|
|
32
34
|
reports download Download the PDF report to .index365/ (<runId> [--output <file>])
|
|
33
|
-
marketing run Start a Marketing Signal audit (--project <id>, optional --wait)
|
|
35
|
+
marketing run Start a Marketing Signal audit (--project <id>, repeatable --page <path>, optional --wait)
|
|
34
36
|
marketing report Latest Marketing Signal report for a project (--project <id>)
|
|
35
37
|
marketing findings Marketing findings (--run <id> | --project <id>, optional --stage)
|
|
36
38
|
integrations list Connected-signal providers + status (--project <id>)
|
|
@@ -45,7 +47,7 @@ EXIT CODES
|
|
|
45
47
|
0 ok · 1 error · 2 usage · 3 auth · 4 not found · 5 quota/conflict/rate
|
|
46
48
|
|
|
47
49
|
AUTH
|
|
48
|
-
Keys are created from
|
|
50
|
+
Keys are created from any active paid plan's workspace in the dashboard
|
|
49
51
|
(Org settings -> API keys) and stored at ~/.config/index365/config.json
|
|
50
52
|
(0600). INDEX365_API_KEY overrides the file.
|
|
51
53
|
`;
|
|
@@ -185,7 +187,13 @@ async function cmdDoctor(argv, io, env) {
|
|
|
185
187
|
|
|
186
188
|
async function cmdProjects(argv, io, env) {
|
|
187
189
|
const [sub, ...rest] = argv;
|
|
188
|
-
if (sub
|
|
190
|
+
if (sub === "list") return cmdProjectsList(rest, io, env);
|
|
191
|
+
if (sub === "create") return cmdProjectsCreate(rest, io, env);
|
|
192
|
+
if (sub === "delete") return cmdProjectsDelete(rest, io, env);
|
|
193
|
+
throw new CliError("Usage: index365 projects <list|create|delete>", EXIT.USAGE);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function cmdProjectsList(rest, io, env) {
|
|
189
197
|
const { values } = parse(rest, {
|
|
190
198
|
limit: { type: "string" },
|
|
191
199
|
cursor: { type: "string" },
|
|
@@ -207,6 +215,77 @@ async function cmdProjects(argv, io, env) {
|
|
|
207
215
|
return EXIT.OK;
|
|
208
216
|
}
|
|
209
217
|
|
|
218
|
+
async function cmdProjectsCreate(rest, io, env) {
|
|
219
|
+
const { values } = parse(rest, {
|
|
220
|
+
domain: { type: "string" },
|
|
221
|
+
name: { type: "string" },
|
|
222
|
+
});
|
|
223
|
+
if (!values.domain) {
|
|
224
|
+
throw new CliError(
|
|
225
|
+
"Usage: index365 projects create --domain <domain> [--name <name>]",
|
|
226
|
+
EXIT.USAGE,
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
const settings = settingsFor(values, env);
|
|
230
|
+
const project = await apiRequest(settings, "POST", "/api/v1/projects", {
|
|
231
|
+
body: { domain: values.domain, name: values.name },
|
|
232
|
+
fetchImpl: io.fetch,
|
|
233
|
+
});
|
|
234
|
+
if (values.json) {
|
|
235
|
+
printJson(io, project);
|
|
236
|
+
} else {
|
|
237
|
+
const suffix = project.idempotent ? " [already existed]" : "";
|
|
238
|
+
out(
|
|
239
|
+
io,
|
|
240
|
+
`${project.projectId} ${project.domain} (${project.name}, ${project.status})${suffix}`,
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
return EXIT.OK;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function cmdProjectsDelete(rest, io, env) {
|
|
247
|
+
const { values, positionals } = parse(rest, { confirm: { type: "string" } });
|
|
248
|
+
const projectId = positionals[0];
|
|
249
|
+
if (!projectId) {
|
|
250
|
+
throw new CliError(
|
|
251
|
+
"Usage: index365 projects delete <projectId> --confirm <domain>",
|
|
252
|
+
EXIT.USAGE,
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
const settings = settingsFor(values, env);
|
|
256
|
+
// Read-before-destroy: fetch the project so we confirm the exact domain first.
|
|
257
|
+
const project = await apiRequest(settings, "GET", `/api/v1/projects/${projectId}`, {
|
|
258
|
+
fetchImpl: io.fetch,
|
|
259
|
+
});
|
|
260
|
+
let confirm = values.confirm;
|
|
261
|
+
if (!confirm) {
|
|
262
|
+
if (input.isTTY) {
|
|
263
|
+
out(
|
|
264
|
+
io,
|
|
265
|
+
`About to permanently delete ${project.domain} (${project.projectId}). This cannot be undone.`,
|
|
266
|
+
);
|
|
267
|
+
const rl = createInterface({ input, output });
|
|
268
|
+
try {
|
|
269
|
+
confirm = (await rl.question(`Type the domain (${project.domain}) to confirm: `)).trim();
|
|
270
|
+
} finally {
|
|
271
|
+
rl.close();
|
|
272
|
+
}
|
|
273
|
+
} else {
|
|
274
|
+
throw new CliError(
|
|
275
|
+
`Refusing to delete ${project.domain} without confirmation. Re-run with --confirm ${project.domain}.`,
|
|
276
|
+
EXIT.USAGE,
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const result = await apiRequest(settings, "DELETE", `/api/v1/projects/${projectId}`, {
|
|
281
|
+
query: { confirm },
|
|
282
|
+
fetchImpl: io.fetch,
|
|
283
|
+
});
|
|
284
|
+
if (values.json) printJson(io, result);
|
|
285
|
+
else out(io, `Deleted ${project.domain} (${projectId}).`);
|
|
286
|
+
return EXIT.OK;
|
|
287
|
+
}
|
|
288
|
+
|
|
210
289
|
const WAIT_POLL_MS = 5_000;
|
|
211
290
|
const WAIT_TIMEOUT_MS = 15 * 60_000;
|
|
212
291
|
const TERMINAL_STATUSES = new Set(["completed", "failed", "failed_auto_credit", "refunded"]);
|
|
@@ -216,11 +295,15 @@ async function cmdRuns(argv, io, env) {
|
|
|
216
295
|
if (sub === "start") {
|
|
217
296
|
const { values } = parse(rest, {
|
|
218
297
|
project: { type: "string" },
|
|
298
|
+
page: { type: "string", multiple: true },
|
|
219
299
|
"idempotency-key": { type: "string" },
|
|
220
300
|
wait: { type: "boolean", default: false },
|
|
221
301
|
});
|
|
222
302
|
if (!values.project) {
|
|
223
|
-
throw new CliError(
|
|
303
|
+
throw new CliError(
|
|
304
|
+
"Usage: index365 runs start --project <projectId> [--page <path> ...] [--wait]",
|
|
305
|
+
EXIT.USAGE,
|
|
306
|
+
);
|
|
224
307
|
}
|
|
225
308
|
return startRunAndMaybeWait(values, io, env, "paid_ai_readiness");
|
|
226
309
|
}
|
|
@@ -347,8 +430,12 @@ async function cmdReports(argv, io, env) {
|
|
|
347
430
|
/** Shared run-start + optional poll loop (AI-readiness and Marketing Signal). */
|
|
348
431
|
async function startRunAndMaybeWait(values, io, env, scanMode) {
|
|
349
432
|
const settings = settingsFor(values, env);
|
|
433
|
+
// `--page` is repeatable (parseArgs multiple:true), so values.page is a
|
|
434
|
+
// string[] of extra same-origin pages to scan beyond the homepage. Omitted =
|
|
435
|
+
// homepage only. The engine resolves/validates same-origin + caps the set.
|
|
436
|
+
const pages = Array.isArray(values.page) ? values.page : [];
|
|
350
437
|
let run = await apiRequest(settings, "POST", "/api/v1/runs", {
|
|
351
|
-
body: { projectId: values.project, scanMode },
|
|
438
|
+
body: { projectId: values.project, scanMode, ...(pages.length ? { pages } : {}) },
|
|
352
439
|
idempotencyKey: values["idempotency-key"],
|
|
353
440
|
fetchImpl: io.fetch,
|
|
354
441
|
});
|
|
@@ -382,6 +469,7 @@ async function cmdMarketing(argv, io, env) {
|
|
|
382
469
|
if (sub === "run") {
|
|
383
470
|
const { values } = parse(rest, {
|
|
384
471
|
project: { type: "string" },
|
|
472
|
+
page: { type: "string", multiple: true },
|
|
385
473
|
"idempotency-key": { type: "string" },
|
|
386
474
|
wait: { type: "boolean", default: false },
|
|
387
475
|
});
|
package/src/client.mjs
CHANGED
|
@@ -50,8 +50,14 @@ export async function apiRequest(settings, method, apiPath, options = {}) {
|
|
|
50
50
|
|
|
51
51
|
const headers = {
|
|
52
52
|
authorization: `Bearer ${settings.apiKey}`,
|
|
53
|
-
"user-agent": "index365-cli/0.1.
|
|
53
|
+
"user-agent": "index365-cli/0.1.2",
|
|
54
54
|
};
|
|
55
|
+
// Optional source tag so a wrapping skill can attribute its traffic
|
|
56
|
+
// (e.g. INDEX365_CLIENT=skill/index365-audit-and-fix). The server validates it.
|
|
57
|
+
const clientTag = process.env.INDEX365_CLIENT;
|
|
58
|
+
if (typeof clientTag === "string" && clientTag.trim()) {
|
|
59
|
+
headers["x-index365-client"] = clientTag.trim();
|
|
60
|
+
}
|
|
55
61
|
if (body !== undefined) headers["content-type"] = "application/json";
|
|
56
62
|
if (idempotencyKey) headers["idempotency-key"] = idempotencyKey;
|
|
57
63
|
|