@lizard-build/cli 4.0.43 → 4.0.45
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/AGENTS.md +5 -2
- package/dist/commands/skill.d.ts +0 -2
- package/dist/commands/skill.js +90 -33
- package/dist/commands/skill.js.map +1 -1
- package/dist/index.js +13 -22
- package/dist/index.js.map +1 -1
- package/dist/lib/skills-data.generated.d.ts +2 -0
- package/dist/lib/skills-data.generated.js +6 -5
- package/dist/lib/skills-data.generated.js.map +1 -1
- package/dist/lib/updater.d.ts +1 -1
- package/dist/lib/updater.js +1 -1
- package/package.json +1 -1
- package/scripts/gen-skills.mjs +25 -20
- package/skill-data/core/SKILL.md +152 -147
- package/skill-data/core/references/deploys.md +190 -0
- package/skill-data/{sandbox/SKILL.md → core/references/sandboxes.md} +14 -70
- package/skill-data/core/references/sdk.md +215 -0
- package/src/commands/skill.ts +96 -36
- package/src/index.ts +17 -23
- package/src/lib/updater.ts +1 -1
- package/test/unit/skills.test.ts +35 -58
- package/dist/lib/skill-route.d.ts +0 -2
- package/dist/lib/skill-route.js +0 -7
- package/dist/lib/skill-route.js.map +0 -1
- package/skill-data/_shared/basics.md +0 -92
- package/src/lib/skill-route.ts +0 -7
package/src/commands/skill.ts
CHANGED
|
@@ -11,24 +11,16 @@ import { EMBEDDED_SKILLS } from "../lib/skills-data.generated.js";
|
|
|
11
11
|
//
|
|
12
12
|
// LIZARD_SKILLS_DIR overrides the embedded source with a real directory — useful
|
|
13
13
|
// for authoring new skills locally without a rebuild.
|
|
14
|
+
//
|
|
15
|
+
// A skill is SKILL.md plus optional references/<ref>.md files that go deeper on
|
|
16
|
+
// one part of the platform; `lizard skills get <name> --ref <ref>` prints one.
|
|
14
17
|
|
|
15
18
|
interface Source {
|
|
16
19
|
list(): string[];
|
|
17
20
|
read(name: string): string | null;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
/** Inline `<!-- include: _shared/x.md -->` lines, as scripts/gen-skills.mjs does for the embedded copy. */
|
|
22
|
-
export function expandIncludes(md: string, root: string): string {
|
|
23
|
-
return md.replace(/^<!-- include: ([\w./-]+) -->$/gm, (line, rel: string) => {
|
|
24
|
-
const file = path.resolve(root, rel);
|
|
25
|
-
if (!file.startsWith(path.resolve(root) + path.sep)) return line;
|
|
26
|
-
try {
|
|
27
|
-
return readFileSync(file, "utf8").trimEnd();
|
|
28
|
-
} catch {
|
|
29
|
-
return line;
|
|
30
|
-
}
|
|
31
|
-
});
|
|
21
|
+
refs(name: string): string[];
|
|
22
|
+
readRef(name: string, ref: string): string | null;
|
|
23
|
+
pathOf(name?: string, ref?: string): string;
|
|
32
24
|
}
|
|
33
25
|
|
|
34
26
|
function fsSource(dir: string): Source {
|
|
@@ -37,7 +29,6 @@ function fsSource(dir: string): Source {
|
|
|
37
29
|
try {
|
|
38
30
|
return readdirSync(dir)
|
|
39
31
|
.filter((name) => {
|
|
40
|
-
if (name.startsWith("_")) return false;
|
|
41
32
|
try {
|
|
42
33
|
return statSync(path.join(dir, name)).isDirectory();
|
|
43
34
|
} catch {
|
|
@@ -51,12 +42,34 @@ function fsSource(dir: string): Source {
|
|
|
51
42
|
},
|
|
52
43
|
read: (name) => {
|
|
53
44
|
try {
|
|
54
|
-
return
|
|
45
|
+
return readFileSync(path.join(dir, name, "SKILL.md"), "utf8");
|
|
46
|
+
} catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
refs: (name) => {
|
|
51
|
+
try {
|
|
52
|
+
return readdirSync(path.join(dir, name, "references"))
|
|
53
|
+
.filter((f) => f.endsWith(".md"))
|
|
54
|
+
.map((f) => f.slice(0, -3))
|
|
55
|
+
.sort();
|
|
56
|
+
} catch {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
readRef: (name, ref) => {
|
|
61
|
+
try {
|
|
62
|
+
return readFileSync(path.join(dir, name, "references", `${ref}.md`), "utf8");
|
|
55
63
|
} catch {
|
|
56
64
|
return null;
|
|
57
65
|
}
|
|
58
66
|
},
|
|
59
|
-
pathOf: (name
|
|
67
|
+
pathOf: (name, ref) => {
|
|
68
|
+
if (!name) return dir;
|
|
69
|
+
return ref
|
|
70
|
+
? path.join(dir, name, "references", `${ref}.md`)
|
|
71
|
+
: path.join(dir, name, "SKILL.md");
|
|
72
|
+
},
|
|
60
73
|
};
|
|
61
74
|
}
|
|
62
75
|
|
|
@@ -64,8 +77,14 @@ function embeddedSource(): Source {
|
|
|
64
77
|
return {
|
|
65
78
|
list: () => Object.keys(EMBEDDED_SKILLS).sort(),
|
|
66
79
|
read: (name) => EMBEDDED_SKILLS[name]?.content ?? null,
|
|
67
|
-
|
|
68
|
-
|
|
80
|
+
refs: (name) => Object.keys(EMBEDDED_SKILLS[name]?.references ?? {}).sort(),
|
|
81
|
+
readRef: (name, ref) => EMBEDDED_SKILLS[name]?.references[ref] ?? null,
|
|
82
|
+
pathOf: (name, ref) => {
|
|
83
|
+
if (!name) return "embedded://skills";
|
|
84
|
+
return ref
|
|
85
|
+
? `embedded://skills/${name}/references/${ref}.md`
|
|
86
|
+
: `embedded://skills/${name}/SKILL.md`;
|
|
87
|
+
},
|
|
69
88
|
};
|
|
70
89
|
}
|
|
71
90
|
|
|
@@ -111,6 +130,29 @@ function readSkill(
|
|
|
111
130
|
return { md, meta: parseFrontmatter(md) };
|
|
112
131
|
}
|
|
113
132
|
|
|
133
|
+
function readRef(src: Source, name: string, ref: string): string {
|
|
134
|
+
readSkill(src, name);
|
|
135
|
+
const md = src.readRef(name, ref);
|
|
136
|
+
if (md == null) {
|
|
137
|
+
const refs = src.refs(name);
|
|
138
|
+
throw new Error(
|
|
139
|
+
`Reference '${ref}' not found in skill '${name}'. ` +
|
|
140
|
+
(refs.length
|
|
141
|
+
? `Available: ${refs.join(", ")}.`
|
|
142
|
+
: `This skill has no references.`),
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
return md;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function refEntries(src: Source, name: string) {
|
|
149
|
+
return src.refs(name).map((ref) => ({
|
|
150
|
+
name: ref,
|
|
151
|
+
path: src.pathOf(name, ref),
|
|
152
|
+
command: `lizard skills get ${name} --ref ${ref}`,
|
|
153
|
+
}));
|
|
154
|
+
}
|
|
155
|
+
|
|
114
156
|
function shortDesc(desc: string, max = 100): string {
|
|
115
157
|
const oneLine = desc.replace(/\s+/g, " ").trim();
|
|
116
158
|
return oneLine.length > max ? oneLine.slice(0, max - 1) + "…" : oneLine;
|
|
@@ -132,12 +174,13 @@ export function registerSkill(program: Command) {
|
|
|
132
174
|
printJSON({
|
|
133
175
|
dir: src.pathOf(),
|
|
134
176
|
skills: names.map((n) => {
|
|
177
|
+
let description = "";
|
|
135
178
|
try {
|
|
136
|
-
|
|
137
|
-
return { name: n, description: meta.description || "" };
|
|
179
|
+
description = readSkill(src, n).meta.description || "";
|
|
138
180
|
} catch {
|
|
139
|
-
|
|
181
|
+
// unreadable SKILL.md: list it without a description
|
|
140
182
|
}
|
|
183
|
+
return { name: n, description, references: src.refs(n) };
|
|
141
184
|
}),
|
|
142
185
|
});
|
|
143
186
|
return;
|
|
@@ -147,38 +190,54 @@ export function registerSkill(program: Command) {
|
|
|
147
190
|
return;
|
|
148
191
|
}
|
|
149
192
|
const rows = names.map((n) => {
|
|
193
|
+
let description = "";
|
|
150
194
|
try {
|
|
151
|
-
|
|
152
|
-
return [n, shortDesc(meta.description || "")];
|
|
195
|
+
description = shortDesc(readSkill(src, n).meta.description || "");
|
|
153
196
|
} catch {
|
|
154
|
-
|
|
197
|
+
// unreadable SKILL.md: list it without a description
|
|
155
198
|
}
|
|
199
|
+
return [n, description, src.refs(n).join(", ")];
|
|
156
200
|
});
|
|
157
|
-
table(["name", "description"], rows);
|
|
201
|
+
table(["name", "description", "references"], rows);
|
|
158
202
|
});
|
|
159
203
|
|
|
160
204
|
skill
|
|
161
205
|
.command("get")
|
|
162
|
-
.description("Print a skill's
|
|
206
|
+
.description("Print a skill's content (markdown), or one of its references")
|
|
163
207
|
.argument("<name>", "skill name (e.g. core)")
|
|
164
|
-
.option(
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
)
|
|
168
|
-
.action((name: string, _opts: { full?: boolean }) => {
|
|
208
|
+
.option("--ref <ref>", "print one reference instead (e.g. sandboxes, deploys, sdk)")
|
|
209
|
+
.option("--full", "append every reference after the skill")
|
|
210
|
+
.action((name: string, opts: { ref?: string; full?: boolean }) => {
|
|
169
211
|
const src = getSource();
|
|
170
212
|
try {
|
|
213
|
+
if (opts.ref) {
|
|
214
|
+
const md = readRef(src, name, opts.ref);
|
|
215
|
+
if (isJSONMode()) {
|
|
216
|
+
printJSON({ name, ref: opts.ref, path: src.pathOf(name, opts.ref), content: md });
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
process.stdout.write(md.endsWith("\n") ? md : md + "\n");
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
171
222
|
const { md, meta } = readSkill(src, name);
|
|
223
|
+
let content = md;
|
|
224
|
+
if (opts.full) {
|
|
225
|
+
for (const ref of src.refs(name)) {
|
|
226
|
+
const body = src.readRef(name, ref) ?? "";
|
|
227
|
+
content = `${content.trimEnd()}\n\n---\n<!-- references/${ref}.md -->\n\n${body}`;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
172
230
|
if (isJSONMode()) {
|
|
173
231
|
printJSON({
|
|
174
232
|
name,
|
|
175
233
|
path: src.pathOf(name),
|
|
176
234
|
frontmatter: meta,
|
|
177
|
-
content
|
|
235
|
+
content,
|
|
236
|
+
references: refEntries(src, name),
|
|
178
237
|
});
|
|
179
238
|
return;
|
|
180
239
|
}
|
|
181
|
-
process.stdout.write(
|
|
240
|
+
process.stdout.write(content.endsWith("\n") ? content : content + "\n");
|
|
182
241
|
} catch (e: any) {
|
|
183
242
|
fail(e.message, 3);
|
|
184
243
|
}
|
|
@@ -188,9 +247,10 @@ export function registerSkill(program: Command) {
|
|
|
188
247
|
.command("path")
|
|
189
248
|
.description("Print the filesystem path of a skill (or the skills root)")
|
|
190
249
|
.argument("[name]", "skill name; omit to print the skills directory")
|
|
191
|
-
.
|
|
250
|
+
.option("--ref <ref>", "print the path of one reference instead")
|
|
251
|
+
.action((name: string | undefined, opts: { ref?: string }) => {
|
|
192
252
|
const src = getSource();
|
|
193
|
-
const target = src.pathOf(name);
|
|
253
|
+
const target = src.pathOf(name, name ? opts.ref : undefined);
|
|
194
254
|
if (isJSONMode()) {
|
|
195
255
|
printJSON({ path: target });
|
|
196
256
|
return;
|
package/src/index.ts
CHANGED
|
@@ -6,7 +6,6 @@ import { setJSONMode, isJSONMode, error } from "./lib/format.js";
|
|
|
6
6
|
import { requireAuth, isLoggedIn } from "./lib/auth.js";
|
|
7
7
|
import { setBaseURL, setAccessToken, APIError, isProjectDeletedError, isAccountScopeError } from "./lib/api.js";
|
|
8
8
|
import { checkForUpdateInBackground, runBackgroundUpdate, CURRENT_VERSION } from "./lib/updater.js";
|
|
9
|
-
import { skillFor } from "./lib/skill-route.js";
|
|
10
9
|
|
|
11
10
|
const BANNER = chalk.rgb(16, 185, 129)(
|
|
12
11
|
[
|
|
@@ -28,34 +27,37 @@ const BANNER = chalk.rgb(16, 185, 129)(
|
|
|
28
27
|
// of guessing commands from flag docs alone.
|
|
29
28
|
const AGENTS_HELP = [
|
|
30
29
|
chalk.bold("Start here (for AI agents):"),
|
|
31
|
-
" " + chalk.cyan("lizard skills get
|
|
32
|
-
" " + chalk.cyan("lizard skills get core") + chalk.dim(" deploys: services, databases, builds, secrets, domains"),
|
|
30
|
+
" " + chalk.cyan("lizard skills get core"),
|
|
33
31
|
"",
|
|
34
32
|
chalk.dim(
|
|
35
|
-
" Skills ship with the CLI (always version-matched) and
|
|
33
|
+
" Skills ship with the CLI (always version-matched) and cover sandboxes and the full app",
|
|
36
34
|
),
|
|
37
35
|
chalk.dim(
|
|
38
|
-
"
|
|
36
|
+
" lifecycle — deploy, link, addons, logs, scaling, secrets, domains — with",
|
|
37
|
+
),
|
|
38
|
+
chalk.dim(
|
|
39
|
+
" copy-paste examples. Prefer this over guessing commands from flag docs alone.",
|
|
40
|
+
),
|
|
41
|
+
chalk.dim(
|
|
42
|
+
" For more depth: lizard skills get core --ref sandboxes | deploys | sdk",
|
|
39
43
|
),
|
|
40
44
|
].join("\n");
|
|
41
45
|
|
|
42
|
-
|
|
43
46
|
// Compact form of the same pointer, shown on every subcommand's `--help` so the
|
|
44
47
|
// per-command flag docs never read as the whole story. Mirrors the `agents`
|
|
45
48
|
// block that `--help --json` already emits for every command; one tight line so
|
|
46
49
|
// it sits above a subcommand's Usage without crowding it.
|
|
47
|
-
const
|
|
50
|
+
const AGENTS_HELP_COMPACT =
|
|
48
51
|
chalk.bold("For AI agents:") +
|
|
49
52
|
" run " +
|
|
50
|
-
chalk.cyan(
|
|
53
|
+
chalk.cyan("lizard skills get core") +
|
|
51
54
|
chalk.dim(" for version-matched usage and examples.");
|
|
52
55
|
|
|
53
|
-
/** Attach the compact agents pointer to every (nested) subcommand's help
|
|
54
|
-
function addAgentsPointerToSubcommands(cmd: Command
|
|
56
|
+
/** Attach the compact agents pointer to every (nested) subcommand's help. */
|
|
57
|
+
function addAgentsPointerToSubcommands(cmd: Command) {
|
|
55
58
|
for (const sub of cmd.commands) {
|
|
56
|
-
|
|
57
|
-
sub
|
|
58
|
-
addAgentsPointerToSubcommands(sub, own);
|
|
59
|
+
sub.addHelpText("before", AGENTS_HELP_COMPACT + "\n");
|
|
60
|
+
addAgentsPointerToSubcommands(sub);
|
|
59
61
|
}
|
|
60
62
|
}
|
|
61
63
|
|
|
@@ -275,13 +277,6 @@ function findTargetCommand(
|
|
|
275
277
|
return { target: cur, unknown: null };
|
|
276
278
|
}
|
|
277
279
|
|
|
278
|
-
/** The command directly under the root that `cmd` belongs to. */
|
|
279
|
-
function topLevelOf(cmd: Command): Command {
|
|
280
|
-
let top = cmd;
|
|
281
|
-
while (top.parent && top.parent !== program) top = top.parent;
|
|
282
|
-
return top;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
280
|
function dumpOption(o: any) {
|
|
286
281
|
return {
|
|
287
282
|
flags: o.flags,
|
|
@@ -394,9 +389,8 @@ async function main() {
|
|
|
394
389
|
cli: "lizard",
|
|
395
390
|
version: CURRENT_VERSION,
|
|
396
391
|
agents: {
|
|
397
|
-
start:
|
|
398
|
-
|
|
399
|
-
note: "Skills ship with this CLI version, with copy-paste examples: `sandbox` for sandboxes (run code, files, ports, snapshots, desktop, coding agents), `core` for deploys (services, databases, builds, secrets, domains). Prefer them over guessing commands from flag docs alone.",
|
|
392
|
+
start: "lizard skills get core",
|
|
393
|
+
note: "Skills ship with this CLI version and cover sandboxes and the full app lifecycle (deploy, link, addons, logs, scaling, secrets, domains) with copy-paste examples. Prefer this over guessing commands from flag docs alone. For more depth: `lizard skills get core --ref sandboxes|deploys|sdk`.",
|
|
400
394
|
},
|
|
401
395
|
command: dumpCommand(target),
|
|
402
396
|
globalOptions: isRoot
|
package/src/lib/updater.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { join, dirname } from "node:path";
|
|
|
5
5
|
import os from "node:os";
|
|
6
6
|
import { spawn } from "node:child_process";
|
|
7
7
|
|
|
8
|
-
export const CURRENT_VERSION = "4.0.
|
|
8
|
+
export const CURRENT_VERSION = "4.0.45";
|
|
9
9
|
const RELEASES_API = "https://api.github.com/repos/lizard-build/lizard-cli/releases/latest";
|
|
10
10
|
const RELEASE_BASE = "https://github.com/lizard-build/lizard-cli/releases/latest/download";
|
|
11
11
|
|
package/test/unit/skills.test.ts
CHANGED
|
@@ -1,78 +1,55 @@
|
|
|
1
|
-
import { describe, expect, test
|
|
2
|
-
import * as path from "node:path";
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
3
2
|
import { EMBEDDED_SKILLS } from "../../src/lib/skills-data.generated.js";
|
|
4
|
-
import { expandIncludes } from "../../src/commands/skill.js";
|
|
5
|
-
import { skillFor } from "../../src/lib/skill-route.js";
|
|
6
|
-
import * as fs from "node:fs";
|
|
7
3
|
|
|
8
|
-
const
|
|
4
|
+
const core = EMBEDDED_SKILLS.core;
|
|
5
|
+
const files: Record<string, string> = {
|
|
6
|
+
"SKILL.md": core.content,
|
|
7
|
+
...Object.fromEntries(Object.entries(core.references).map(([ref, md]) => [`references/${ref}.md`, md])),
|
|
8
|
+
};
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
const block = md.slice(4, md.indexOf("\n---", 3));
|
|
12
|
-
return Object.fromEntries(
|
|
13
|
-
block.split("\n").map((l) => l.match(/^([\w-]+):\s*"?(.*?)"?$/)).filter(Boolean).map((m) => [m![1], m![2]]),
|
|
14
|
-
);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/** GitHub-style heading slug, which the skills' own links use. */
|
|
10
|
+
/** GitHub-style heading slug, which the skill's own links use. */
|
|
18
11
|
const slug = (h: string) => h.toLowerCase().replace(/[^\w\s-]/g, "").trim().replace(/\s+/g, "-");
|
|
19
12
|
|
|
20
|
-
describe("
|
|
21
|
-
test("
|
|
22
|
-
expect(Object.keys(EMBEDDED_SKILLS)
|
|
13
|
+
describe("the core skill", () => {
|
|
14
|
+
test("is the only embedded skill", () => {
|
|
15
|
+
expect(Object.keys(EMBEDDED_SKILLS)).toEqual(["core"]);
|
|
23
16
|
});
|
|
24
17
|
|
|
25
|
-
test
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
expect(md).toContain(`\n${h}\n`);
|
|
18
|
+
test("ships the sandboxes, deploys and sdk references, and names each one", () => {
|
|
19
|
+
expect(Object.keys(core.references).sort()).toEqual(["deploys", "sandboxes", "sdk"]);
|
|
20
|
+
for (const ref of Object.keys(core.references)) {
|
|
21
|
+
expect(core.content).toContain(`--ref ${ref}`);
|
|
30
22
|
}
|
|
31
23
|
});
|
|
32
24
|
|
|
33
|
-
test
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
expect(
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
test("descriptions fit the 1024-character limit and say who they are for", () => {
|
|
41
|
-
const core = frontmatter(EMBEDDED_SKILLS.core.content);
|
|
42
|
-
const sandbox = frontmatter(EMBEDDED_SKILLS.sandbox.content);
|
|
43
|
-
expect(sandbox.name).toBe("lizard-sandbox");
|
|
44
|
-
expect(core.description.length).toBeLessThanOrEqual(1024);
|
|
45
|
-
expect(sandbox.description.length).toBeLessThanOrEqual(1024);
|
|
46
|
-
expect(sandbox.description).toMatch(/^Creates and manages Lizard sandboxes/);
|
|
25
|
+
test("covers the whole platform in SKILL.md", () => {
|
|
26
|
+
for (const h of ["Sign-in", "Exit codes", "Default project", "Sandboxes", "Deploys", "Databases and storage", "Secrets and config", "Domains", "Billing"]) {
|
|
27
|
+
expect(core.content).toContain(`\n## ${h}\n`);
|
|
28
|
+
}
|
|
29
|
+
expect(core.content).not.toContain("lizard skills get sandbox ");
|
|
47
30
|
});
|
|
48
31
|
|
|
49
|
-
test("
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
expect(core).not.toMatch(/^## Sandboxes/m);
|
|
53
|
-
expect(core).toContain("lizard skills get sandbox --json");
|
|
54
|
-
for (const h of ["## Quickstart", "## Templates", "## Lifetime", "## Snapshots and pause/resume", "## From code: Lizard SDK"]) {
|
|
55
|
-
expect(sandbox).toContain(`\n${h}\n`);
|
|
56
|
-
}
|
|
57
|
-
expect(sandbox).toContain("lizard skills get core --json");
|
|
32
|
+
test("keeps SKILL.md under 500 lines", () => {
|
|
33
|
+
const body = core.content.replace(/^---\n[\s\S]*?\n---\n/, "");
|
|
34
|
+
expect(body.split("\n").length).toBeLessThan(500);
|
|
58
35
|
});
|
|
59
|
-
});
|
|
60
36
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
37
|
+
test.each(Object.keys(files))("every #link in %s points at a heading in the same file", (file) => {
|
|
38
|
+
const md = files[file];
|
|
39
|
+
const headings = new Set([...md.matchAll(/^#{1,4} (.+)$/gm)].map((m) => slug(m[1])));
|
|
40
|
+
const links = [...md.matchAll(/\]\(#([\w-]+)\)/g)].map((m) => m[1]);
|
|
41
|
+
expect(links.filter((l) => !headings.has(l))).toEqual([]);
|
|
65
42
|
});
|
|
66
|
-
});
|
|
67
43
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
44
|
+
test("references never link to other files", () => {
|
|
45
|
+
for (const md of Object.values(core.references)) {
|
|
46
|
+
expect(md).not.toMatch(/\]\((?!https?:|#)[^)]+\)/);
|
|
47
|
+
}
|
|
72
48
|
});
|
|
73
49
|
|
|
74
|
-
test("
|
|
75
|
-
const
|
|
76
|
-
expect(
|
|
50
|
+
test("its description fits the 1024-character limit", () => {
|
|
51
|
+
const description = core.content.match(/^description: "(.*)"$/m)![1];
|
|
52
|
+
expect(description.length).toBeLessThanOrEqual(1024);
|
|
53
|
+
expect(description).toMatch(/^Creates cloud sandboxes and deploys apps/);
|
|
77
54
|
});
|
|
78
55
|
});
|
package/dist/lib/skill-route.js
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/** Top-level commands whose work the `sandbox` skill covers; everything else is in `core` (deploys). */
|
|
2
|
-
const SANDBOX_COMMANDS = new Set(["sandbox", "volume", "agents"]);
|
|
3
|
-
/** Which embedded skill an agent should read for a top-level command. */
|
|
4
|
-
export function skillFor(topLevel) {
|
|
5
|
-
return SANDBOX_COMMANDS.has(topLevel) ? "sandbox" : "core";
|
|
6
|
-
}
|
|
7
|
-
//# sourceMappingURL=skill-route.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"skill-route.js","sourceRoot":"","sources":["../../src/lib/skill-route.ts"],"names":[],"mappings":"AAAA,wGAAwG;AACxG,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;AAElE,yEAAyE;AACzE,MAAM,UAAU,QAAQ,CAAC,QAAgB;IACvC,OAAO,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC;AAC7D,CAAC"}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
## Execution rules
|
|
2
|
-
|
|
3
|
-
1. Prefer the `lizard` CLI. For anything it does not expose, ask the user: don't call the API directly.
|
|
4
|
-
2. Always pass `--json` on non-interactive calls. The CLI also switches to JSON by itself when stdout isn't a TTY.
|
|
5
|
-
3. For an unfamiliar command, run `lizard <cmd> --help --json` first: never guess flag shapes. See [Discovery](#discovery).
|
|
6
|
-
4. Resolve context before any change: `lizard status` shows the folder's link and its project. Confirm you are targeting the right project, service or sandbox.
|
|
7
|
-
5. For destructive actions (delete a service or sandbox, drop an addon, overwrite a project-wide secret, restart production), confirm with the user first. The CLI's own prompts only appear on a TTY.
|
|
8
|
-
|
|
9
|
-
## Discovery
|
|
10
|
-
|
|
11
|
-
The CLI has ~30 subcommands. Discover at runtime:
|
|
12
|
-
|
|
13
|
-
```
|
|
14
|
-
lizard --help --json # root + all commands + global flags + exit codes
|
|
15
|
-
lizard <cmd> --help --json # specific command schema
|
|
16
|
-
lizard <cmd> <sub> --help --json # nested (e.g. `lizard service set --help --json`)
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
Returns `{ cli, version, command: { arguments, options, subcommands }, globalOptions, exitCodes }`.
|
|
20
|
-
|
|
21
|
-
## Sign-in
|
|
22
|
-
|
|
23
|
-
Every command that needs an account checks the sign-in first. Without one, in JSON mode it fails with exit code `2` and `error.code: "NOT_AUTHENTICATED"`, and `error.body.login` holds what you need to get the user signed in. `lizard login --json` prints the same object at the top level. It never waits and never opens a browser:
|
|
24
|
-
|
|
25
|
-
- `status: "pending"`: `authUrls` holds one link per sign-in method (`github`, `google`), and `authUrl` the one to use when only one applies. Ask the user how they sign in to Lizard and give them that link as a clickable URL. Someone with an account must use the same method as before: another method opens a second, empty account unless both share a verified email. Once they say the browser shows "CLI authorized", run the same command again (`nextCommand` when present); it picks the sign-in up. A link lasts 5 minutes; the next run prints a new one. Follow `instruction`.
|
|
26
|
-
- `status: "complete"` (from `lizard login` only): `username`, `email`, `source` (`browser`, `saved`, or `env` for `LIZARD_TOKEN` / `LIZARD_API_KEY`), `account` with the plan's `state` (`trial_available`, `subscription_required`, `trialing`, `active`, `past_due`, `credits`, `enterprise`, `not_owner`, `checkout_unavailable`, `unknown`), `billingUrl`, `payUrl` when a payment failed, and an `instruction` that says what to offer and what not to touch, plus `next` commands.
|
|
27
|
-
|
|
28
|
-
Never start a plan (`lizard billing start`) unless the user agrees. `lizard login` is safe to re-run: a signed-in machine skips straight to the plan. In CI nothing can be opened, so set `LIZARD_TOKEN` or `LIZARD_API_KEY`, or save a token on the machine with `printf '%s' "$TOKEN" | lizard login --token-stdin`. `lizard login --token <token>` works too, but the token lands in the process list and the shell history.
|
|
29
|
-
|
|
30
|
-
## Exit codes
|
|
31
|
-
|
|
32
|
-
- `0` success — continue
|
|
33
|
-
- `1` generic error — inspect message, surface to user
|
|
34
|
-
- `2` auth (401/403) — not signed in: give the user the link from `error.body.login` and run the command again once they have signed in (see [Sign-in](#sign-in)). No links in the body (CI, or the platform could not be reached)? Run `lizard login --json`, or ask for `LIZARD_TOKEN`.
|
|
35
|
-
- `3` not found (404) — wrong name / resource gone; verify with `lizard project list` / `lizard ps`
|
|
36
|
-
- `4` timeout — retry or report
|
|
37
|
-
- `5` cancelled by user — stop
|
|
38
|
-
|
|
39
|
-
## Default project
|
|
40
|
-
|
|
41
|
-
A command that works on a project takes it from `--project`, then from the folder's link, then falls back to the account's **default project**: the oldest live project in the personal workspace, which every account gets at sign-up (`<username>'s Project`). So `lizard sandbox create`, `lizard up`, `lizard logs` and the rest work in a folder that was never linked. They say so on stderr: `Project: <name> (your default)`.
|
|
42
|
-
|
|
43
|
-
- `lizard sandbox create` with no project lets the platform pick the default; its JSON carries `projectId`, `projectName` and `projectDefaulted: true`.
|
|
44
|
-
- `lizard up` links the unlinked folder to the default project first, then creates a service named after the folder, so the next `up` from that folder updates the same service. `lizard init --force` relinks it elsewhere.
|
|
45
|
-
- No default project (every project deleted): the old `No project linked. Run lizard init or pass --project <id>.` error.
|
|
46
|
-
|
|
47
|
-
## Billing
|
|
48
|
-
|
|
49
|
-
Billing belongs to the account; these commands take no project or service selector.
|
|
50
|
-
|
|
51
|
-
```sh
|
|
52
|
-
lizard billing --json # plan, trial, this month's usage, next charge, billingUrl
|
|
53
|
-
lizard billing start --json # Stripe Checkout link: the Pro trial, or Pro at once
|
|
54
|
-
lizard billing promo <code> --json # a longer trial with more trial usage
|
|
55
|
-
lizard billing open # the Billing page
|
|
56
|
-
lizard metrics --cost --json # spend per project; proCredits for Pro accounts
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
Pro costs $19/month, taxes included, and includes $19 of usage each month for
|
|
60
|
-
everything on the account; unused usage does not carry over. Usage above that is
|
|
61
|
-
pay as you go at the published rates, charged to the card as it builds up. A new
|
|
62
|
-
account starts with no plan. The 7-day trial includes $5 of usage; Checkout asks
|
|
63
|
-
for a card and charges nothing until the trial ends, then Pro is $19/month. When
|
|
64
|
-
the trial usage runs out, running resources pause and nothing new can be created
|
|
65
|
-
until Pro starts. Data is kept. Promo codes work only before the first payment.
|
|
66
|
-
Trial accounts run 1 replica per app and 5 sandboxes at once; after the first
|
|
67
|
-
paid invoice, 5 replicas per app and 100 sandboxes (paused sandboxes don't
|
|
68
|
-
count). Pro cards are changed or removed on the Billing page;
|
|
69
|
-
`lizard billing payment-methods remove` works only for Enterprise accounts.
|
|
70
|
-
Enterprise accounts pay as you go, invoiced monthly. There are no top-ups, no
|
|
71
|
-
auto top-up and no crypto or x402 payments.
|
|
72
|
-
|
|
73
|
-
When the account needs a plan or a payment, every create command (`up`, `add`,
|
|
74
|
-
`sandbox create`, `volume create`, `domain`, redeploys) fails with HTTP 402.
|
|
75
|
-
The JSON error has `code: "PAYMENT_REQUIRED"` (`"INSUFFICIENT_CREDITS"` from
|
|
76
|
-
older servers), a `message` to show the user as is (its last line is the link),
|
|
77
|
-
and `body.status` with the links:
|
|
78
|
-
|
|
79
|
-
| `body.status` | What to offer |
|
|
80
|
-
|---|---|
|
|
81
|
-
| `trial_available` | Start the trial: `body.subscribeUrl`, or `lizard billing start` |
|
|
82
|
-
| `subscription_required` | The trial is used: Start Pro ($19 today) the same way |
|
|
83
|
-
| `trial_credits_used` | Trial usage is used up: Start Pro now with `lizard billing start-now` or in Billing |
|
|
84
|
-
| `past_due` | A payment failed: pay the open invoice in Billing (`body.billingUrl`) |
|
|
85
|
-
| `paused` | Running resources are paused, data kept. `message` says why: trial usage used up (Start Pro now, as above) or a failed payment (pay the open invoice in Billing) |
|
|
86
|
-
| anything else | Show `message` and `body.billingUrl` |
|
|
87
|
-
|
|
88
|
-
Checkout and invoice pages open in the user's browser; you cannot finish them.
|
|
89
|
-
Give the user the link, never ask for card details in chat, and retry the
|
|
90
|
-
failed command after they say they are done. `start-now` (charges $19 now),
|
|
91
|
-
`cancel` and `resume` change what the user pays: run them only when the user
|
|
92
|
-
asks. Without a terminal, `start-now` and `cancel` need `-y`.
|
package/src/lib/skill-route.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/** Top-level commands whose work the `sandbox` skill covers; everything else is in `core` (deploys). */
|
|
2
|
-
const SANDBOX_COMMANDS = new Set(["sandbox", "volume", "agents"]);
|
|
3
|
-
|
|
4
|
-
/** Which embedded skill an agent should read for a top-level command. */
|
|
5
|
-
export function skillFor(topLevel: string): "sandbox" | "core" {
|
|
6
|
-
return SANDBOX_COMMANDS.has(topLevel) ? "sandbox" : "core";
|
|
7
|
-
}
|