@sleepwalkerai/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 +134 -0
- package/bin/sleepwalker.js +15 -0
- package/package.json +24 -0
- package/scripts/smoke.js +57 -0
- package/src/args.js +79 -0
- package/src/cli.js +1173 -0
- package/src/config.js +83 -0
- package/src/format.js +121 -0
- package/src/http.js +85 -0
- package/src/theme.js +178 -0
package/README.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Sleepwalker CLI
|
|
2
|
+
|
|
3
|
+
Command-line client for the Sleepwalker API.
|
|
4
|
+
|
|
5
|
+
This package is intentionally a thin API client. It does not run Sleepwalker
|
|
6
|
+
analysis locally.
|
|
7
|
+
|
|
8
|
+
Public package:
|
|
9
|
+
|
|
10
|
+
```text
|
|
11
|
+
@sleepwalkerai/cli
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Install it from npm or run it directly with `npx`.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install -g @sleepwalkerai/cli
|
|
20
|
+
sleepwalker init
|
|
21
|
+
sleepwalker auth key set sw_api_live_...
|
|
22
|
+
sleepwalker menu
|
|
23
|
+
sleepwalker doctor
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
One-off usage:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx @sleepwalkerai/cli doctor
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Local Usage
|
|
33
|
+
|
|
34
|
+
From this repository:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
cd packages/cli
|
|
38
|
+
node ./bin/sleepwalker.js
|
|
39
|
+
node ./bin/sleepwalker.js menu
|
|
40
|
+
node ./bin/sleepwalker.js init
|
|
41
|
+
node ./bin/sleepwalker.js commands
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
With an API key:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
export SLEEPWALKER_API_KEY=sw_api_live_...
|
|
48
|
+
node ./bin/sleepwalker.js credits
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Or store the key locally:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
node ./bin/sleepwalker.js auth key set sw_api_live_...
|
|
55
|
+
node ./bin/sleepwalker.js doctor
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
To point the CLI at a non-production API base:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
node ./bin/sleepwalker.js config set api-base-url https://api.sleepwalker.ai
|
|
62
|
+
node ./bin/sleepwalker.js config show
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Examples
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
sleepwalker init
|
|
69
|
+
sleepwalker menu
|
|
70
|
+
sleepwalker doctor
|
|
71
|
+
sleepwalker page serialize https://www.sleepwalker.ai
|
|
72
|
+
sleepwalker reports by-url https://www.sleepwalker.ai
|
|
73
|
+
sleepwalker visibility run https://www.sleepwalker.ai --brand Sleepwalker --prompt "What are the best AI visibility tools?" --platform perplexity --watch
|
|
74
|
+
sleepwalker ci score https://www.sleepwalker.ai
|
|
75
|
+
sleepwalker ci run https://www.sleepwalker.ai --depth full --watch
|
|
76
|
+
sleepwalker activity list
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Add `--json` to print raw API responses.
|
|
80
|
+
|
|
81
|
+
For retryable scripts, pass your own idempotency key on persisted run creation:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
sleepwalker visibility run https://www.sleepwalker.ai \
|
|
85
|
+
--brand Sleepwalker \
|
|
86
|
+
--prompt "What are the best AI visibility tools?" \
|
|
87
|
+
--platform perplexity \
|
|
88
|
+
--idempotency-key visibility-sleepwalker-homepage-001
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`--watch` polls for completion and stops after 15 minutes by default. Override
|
|
92
|
+
that with `--max-wait-seconds <seconds>` when a workflow needs a shorter or
|
|
93
|
+
longer wait.
|
|
94
|
+
|
|
95
|
+
## Smoke Tests
|
|
96
|
+
|
|
97
|
+
Local command smoke:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
npm run smoke
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Read-only live smoke with a real API key:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
export SLEEPWALKER_API_KEY=sw_api_live_...
|
|
107
|
+
npm run smoke:live
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The live smoke does not run billable actions by default. To include one
|
|
111
|
+
intentional serialization action:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
SLEEPWALKER_CLI_SMOKE_WRITE=1 npm run smoke:live
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Terminal Color
|
|
118
|
+
|
|
119
|
+
Human output uses Sleepwalker-inspired terminal color when the terminal supports
|
|
120
|
+
it:
|
|
121
|
+
|
|
122
|
+
- green for successful AI Visibility / completed states;
|
|
123
|
+
- purple for Content Intelligence;
|
|
124
|
+
- cyan/blue for IDs, URLs, and API paths;
|
|
125
|
+
- yellow/red for queued, blocked, or failed work.
|
|
126
|
+
|
|
127
|
+
Colors are never added to `--json` output. Set `NO_COLOR=1` to disable styling
|
|
128
|
+
or `FORCE_COLOR=1` to force it in supported terminals.
|
|
129
|
+
|
|
130
|
+
If your current shell exports `NO_COLOR`, remove it while previewing:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
env -u NO_COLOR FORCE_COLOR=1 node ./bin/sleepwalker.js --help
|
|
134
|
+
```
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { main } from "../src/cli.js";
|
|
4
|
+
|
|
5
|
+
main(process.argv.slice(2), {
|
|
6
|
+
stdin: process.stdin,
|
|
7
|
+
stdout: process.stdout,
|
|
8
|
+
stderr: process.stderr,
|
|
9
|
+
env: process.env,
|
|
10
|
+
fetch: globalThis.fetch,
|
|
11
|
+
}).catch((error) => {
|
|
12
|
+
const message = error && error.message ? error.message : String(error);
|
|
13
|
+
process.stderr.write(`Sleepwalker CLI error: ${message}\n`);
|
|
14
|
+
process.exitCode = typeof error.exitCode === "number" ? error.exitCode : 1;
|
|
15
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sleepwalkerai/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Command-line client for the Sleepwalker API.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"sleepwalker": "bin/sleepwalker.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "node --test",
|
|
11
|
+
"smoke": "node ./bin/sleepwalker.js --help",
|
|
12
|
+
"smoke:live": "node ./scripts/smoke.js"
|
|
13
|
+
},
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18.17"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"bin",
|
|
19
|
+
"src",
|
|
20
|
+
"scripts",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
23
|
+
"license": "MIT"
|
|
24
|
+
}
|
package/scripts/smoke.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
|
|
7
|
+
const execFileAsync = promisify(execFile);
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const cliPath = path.resolve(__dirname, "../bin/sleepwalker.js");
|
|
10
|
+
|
|
11
|
+
async function run(args, { env = process.env } = {}) {
|
|
12
|
+
const label = `sleepwalker ${args.join(" ")}`;
|
|
13
|
+
process.stdout.write(`\n> ${label}\n`);
|
|
14
|
+
const { stdout, stderr } = await execFileAsync(process.execPath, [cliPath, ...args], {
|
|
15
|
+
env: { ...env, NO_COLOR: "1" },
|
|
16
|
+
maxBuffer: 1024 * 1024 * 8,
|
|
17
|
+
});
|
|
18
|
+
if (stdout.trim()) {
|
|
19
|
+
process.stdout.write(`${stdout.trim()}\n`);
|
|
20
|
+
}
|
|
21
|
+
if (stderr.trim()) {
|
|
22
|
+
process.stderr.write(`${stderr.trim()}\n`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function main() {
|
|
27
|
+
await run(["--help"]);
|
|
28
|
+
|
|
29
|
+
if (!process.env.SLEEPWALKER_API_KEY) {
|
|
30
|
+
process.stdout.write("\nNo SLEEPWALKER_API_KEY set. Live read smoke skipped.\n");
|
|
31
|
+
process.stdout.write("Set SLEEPWALKER_API_KEY to smoke-test the hosted API without spending credits.\n");
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
await run(["doctor", "--json"]);
|
|
36
|
+
await run(["credits", "--json"]);
|
|
37
|
+
await run(["tests", "list", "--limit", "1", "--json"]);
|
|
38
|
+
await run(["activity", "list", "--limit", "1", "--json"]);
|
|
39
|
+
|
|
40
|
+
if (process.env.SLEEPWALKER_CLI_SMOKE_WRITE === "1") {
|
|
41
|
+
const url = process.env.SLEEPWALKER_CLI_SMOKE_WRITE_URL || "https://www.sleepwalker.ai";
|
|
42
|
+
await run(["page", "serialize", url, "--max-chars", "1000", "--json"]);
|
|
43
|
+
} else {
|
|
44
|
+
process.stdout.write("\nWrite smoke skipped. Set SLEEPWALKER_CLI_SMOKE_WRITE=1 to include a billable action.\n");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
main().catch((error) => {
|
|
49
|
+
process.stderr.write(`\nSmoke failed: ${error.message}\n`);
|
|
50
|
+
if (error.stdout) {
|
|
51
|
+
process.stderr.write(`${error.stdout}\n`);
|
|
52
|
+
}
|
|
53
|
+
if (error.stderr) {
|
|
54
|
+
process.stderr.write(`${error.stderr}\n`);
|
|
55
|
+
}
|
|
56
|
+
process.exitCode = 1;
|
|
57
|
+
});
|
package/src/args.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
|
|
3
|
+
export function parseFlags(args) {
|
|
4
|
+
const positional = [];
|
|
5
|
+
const flags = {};
|
|
6
|
+
|
|
7
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
8
|
+
const arg = args[index];
|
|
9
|
+
if (!arg.startsWith("--") || arg === "--") {
|
|
10
|
+
positional.push(arg);
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const withoutPrefix = arg.slice(2);
|
|
15
|
+
const [rawKey, inlineValue] = withoutPrefix.split(/=(.*)/s);
|
|
16
|
+
const key = rawKey.trim();
|
|
17
|
+
if (!key) {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let value = inlineValue;
|
|
22
|
+
if (value === undefined) {
|
|
23
|
+
const next = args[index + 1];
|
|
24
|
+
if (next !== undefined && !next.startsWith("--")) {
|
|
25
|
+
value = next;
|
|
26
|
+
index += 1;
|
|
27
|
+
} else {
|
|
28
|
+
value = true;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (flags[key] === undefined) {
|
|
33
|
+
flags[key] = value;
|
|
34
|
+
} else if (Array.isArray(flags[key])) {
|
|
35
|
+
flags[key].push(value);
|
|
36
|
+
} else {
|
|
37
|
+
flags[key] = [flags[key], value];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return { positional, flags };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function flagList(flags, name) {
|
|
45
|
+
const value = flags[name];
|
|
46
|
+
if (value === undefined || value === false) {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
return Array.isArray(value) ? value.map(String) : [String(value)];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function flagString(flags, name, fallback = "") {
|
|
53
|
+
const value = flags[name];
|
|
54
|
+
if (value === undefined || value === true || value === false) {
|
|
55
|
+
return fallback;
|
|
56
|
+
}
|
|
57
|
+
return Array.isArray(value) ? String(value[value.length - 1]) : String(value);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function flagNumber(flags, name, fallback) {
|
|
61
|
+
const value = flagString(flags, name, "");
|
|
62
|
+
if (!value) {
|
|
63
|
+
return fallback;
|
|
64
|
+
}
|
|
65
|
+
const parsed = Number(value);
|
|
66
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function flagBool(flags, name) {
|
|
70
|
+
return Boolean(flags[name]);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function readLinesFromFile(filePath) {
|
|
74
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
75
|
+
return raw
|
|
76
|
+
.split(/\r?\n/)
|
|
77
|
+
.map((line) => line.trim())
|
|
78
|
+
.filter(Boolean);
|
|
79
|
+
}
|