ai-native-lab 0.1.0 → 0.1.1
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 +27 -27
- package/SKILL.md +22 -22
- package/dist/cli.js +22 -22
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# ai-native-lab · `
|
|
1
|
+
# ai-native-lab · `ailab`
|
|
2
2
|
|
|
3
3
|
[AI Native Lab](https://ai-native-lab.mixdao.world) 的命令行工具,给人和 Agent 用:读、搜、发布内容,管理标签、图片、会员。
|
|
4
4
|
|
|
@@ -9,26 +9,26 @@
|
|
|
9
9
|
## 安装
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npm i -g ai-native-lab # 命令名是
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
npm i -g ai-native-lab # 命令名是 ailab,需要 Node.js 20+
|
|
13
|
+
ailab login # 打开浏览器确认授权;自建服务:ailab login https://your-host
|
|
14
|
+
ailab whoami
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
Agent / CI 里直接用 Token(在网站「我的账户 → API Token」生成):
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
|
|
20
|
+
AILAB_TOKEN=aln_xxx AILAB_API_URL=https://ai-native-lab.mixdao.world ailab ls --json
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
## 阅读
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
ailab tags # 标签组和标签
|
|
27
|
+
ailab ls --tag agentic-coding # 组内「或」、组间「且」
|
|
28
|
+
ailab search 公司大脑
|
|
29
|
+
ailab read <slug> # Markdown;HTML 内容输出抽出的文字,--html 输出源码
|
|
30
|
+
ailab bookmark <slug>
|
|
31
|
+
ailab redeem ALN-XXXX-XXXX-XXXX # 兑换会员
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
会员内容权限不够时只输出摘要,退出码 `4`。
|
|
@@ -51,40 +51,40 @@ cover: ./img/cover.jpg # 本地图片会自动上传
|
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
ailab push post.md --dry-run
|
|
55
|
+
ailab push post.md --publish
|
|
56
|
+
ailab pull my-post -o post.md # 改完再 push,--if-match <version> 防止覆盖别人的修改
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
HTML 页面(单个 `.html`、`.zip`,或一个目录:根目录 `index.html` + 相对路径的资源,元数据放 `
|
|
59
|
+
HTML 页面(单个 `.html`、`.zip`,或一个目录:根目录 `index.html` + 相对路径的资源,元数据放 `ailab.json`):
|
|
60
60
|
|
|
61
61
|
```bash
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
ailab push ./site/ --slug my-report --title 报告 --access member --publish
|
|
63
|
+
ailab pull my-report -o ./site/ # 解压并写 ailab.json,改完:ailab push ./site/ --if-match <version>
|
|
64
64
|
```
|
|
65
65
|
|
|
66
66
|
图片和标签:
|
|
67
67
|
|
|
68
68
|
```bash
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
ailab image upload a.png b.jpg # 输出可直接粘贴的 Markdown
|
|
70
|
+
ailab tag create company-brain --name 公司大脑 --group topic
|
|
71
71
|
```
|
|
72
72
|
|
|
73
73
|
## 管理(管理员)
|
|
74
74
|
|
|
75
75
|
```bash
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
76
|
+
ailab users --expiring 7
|
|
77
|
+
ailab member grant someone@example.com --days 365
|
|
78
|
+
ailab role someone@example.com editor
|
|
79
|
+
ailab code create --days 365 --count 10
|
|
80
|
+
ailab image purge --dry-run
|
|
81
|
+
ailab audit --action membership.
|
|
82
82
|
```
|
|
83
83
|
|
|
84
84
|
## 给 Claude Code 用
|
|
85
85
|
|
|
86
86
|
```bash
|
|
87
|
-
|
|
87
|
+
ailab skill --install # 写入 ~/.claude/skills/ai-native-lab/SKILL.md
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
-
完整命令:`
|
|
90
|
+
完整命令:`ailab --help`、`ailab <命令> --help`。
|
package/SKILL.md
CHANGED
|
@@ -1,54 +1,54 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ai-native-lab
|
|
3
|
-
description: Read, search and publish content on the AI Native Lab platform with the `
|
|
3
|
+
description: Read, search and publish content on the AI Native Lab platform with the `ailab` CLI. Use when the user asks to look up, quote, summarize, write, update or publish AI Native Lab content, manage its tags, or manage members / redeem codes.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# AI Native Lab CLI (`
|
|
6
|
+
# AI Native Lab CLI (`ailab`)
|
|
7
7
|
|
|
8
8
|
Always pass `--json` and parse stdout (one JSON object). Errors are `{"error":{"code","message"}}` on stdout with a non-zero exit code.
|
|
9
9
|
|
|
10
|
-
Exit codes: 0 ok · 1 error · 2 usage · 3 auth (run `
|
|
10
|
+
Exit codes: 0 ok · 1 error · 2 usage · 3 auth (run `ailab login` or set `AILAB_TOKEN`) · 4 forbidden / locked · 5 conflict (re-fetch, then retry).
|
|
11
11
|
|
|
12
12
|
## Auth
|
|
13
13
|
|
|
14
|
-
- `
|
|
15
|
-
- Not logged in: ask the user to run `
|
|
14
|
+
- `ailab whoami --json` → `role` is one of guest / user / member / editor / admin, plus `permissions` and `membership.expiresAt`.
|
|
15
|
+
- Not logged in: ask the user to run `ailab login <url>` (it opens a browser), or use `AILAB_TOKEN=aln_… AILAB_API_URL=https://…`.
|
|
16
16
|
|
|
17
17
|
## Read
|
|
18
18
|
|
|
19
|
-
1. `
|
|
20
|
-
2. `
|
|
21
|
-
3. `
|
|
19
|
+
1. `ailab tags --json` to learn the tag slugs.
|
|
20
|
+
2. `ailab ls --tag <a,b> --json` (OR within a tag group, AND across groups), or `ailab search <text> --json` (full text, best matches first; the body only counts for content the reader can open, so locked items match on title / summary only).
|
|
21
|
+
3. `ailab read <slug>` prints Markdown. `locked: true` (exit 4) means the reader's role can't open the full text; only the summary is available. Don't try to work around it.
|
|
22
22
|
|
|
23
23
|
## Follow tags
|
|
24
24
|
|
|
25
|
-
- `
|
|
25
|
+
- `ailab follow <tag>` subscribes the user to a weekly email digest (Mondays) of new content in that tag; `ailab follow <tag> --remove` unsubscribes; `ailab follow --json` lists current follows. Only do this when the user asks.
|
|
26
26
|
|
|
27
27
|
## Write (editor or admin)
|
|
28
28
|
|
|
29
|
-
- Edit existing: `
|
|
29
|
+
- Edit existing: `ailab pull <slug> -o post.md` → edit the file → `ailab push post.md --if-match <version from frontmatter>`.
|
|
30
30
|
- New content: write Markdown with frontmatter (`slug`, `title`, `summary`, `access`, `tags`), then
|
|
31
|
-
`
|
|
32
|
-
- Unknown tags fail with `unknown_tag`; create them with `
|
|
31
|
+
`ailab push post.md --dry-run --json` → `ailab push post.md --json` (saves a draft) → `ailab publish <slug>` when the user approves.
|
|
32
|
+
- Unknown tags fail with `unknown_tag`; create them with `ailab tag create <slug> --name <name> --group <key>`, or pass `--auto-tags <group>`.
|
|
33
33
|
- `access`: `public` (everyone), `registered` (signed-in users), `member` (members only). Always write a useful `summary`: it's all that locked readers see.
|
|
34
|
-
- Publishing is visible to everyone and logged; confirm with the user before `--publish` / `
|
|
34
|
+
- Publishing is visible to everyone and logged; confirm with the user before `--publish` / `ailab publish`.
|
|
35
35
|
|
|
36
36
|
## Admin
|
|
37
37
|
|
|
38
|
-
- `
|
|
39
|
-
- `
|
|
40
|
-
- `
|
|
38
|
+
- `ailab users --expiring 7 --json`, `ailab member grant <email> --days 365`, `ailab member revoke <email>`, `ailab role <email> editor`.
|
|
39
|
+
- `ailab code create --days 365 --count 10` prints codes once; store them safely.
|
|
40
|
+
- `ailab audit --action membership. --json`.
|
|
41
41
|
|
|
42
42
|
## Images
|
|
43
43
|
|
|
44
|
-
- Reference local images in Markdown with relative paths (``, also `cover: ./img/cover.jpg`); `
|
|
45
|
-
- Or upload first: `
|
|
46
|
-
- `
|
|
44
|
+
- Reference local images in Markdown with relative paths (``, also `cover: ./img/cover.jpg`); `ailab push` uploads them, compresses them to WebP (long edge ≤ 2000px) and rewrites the links. `--dry-run` does not upload.
|
|
45
|
+
- Or upload first: `ailab image upload a.png b.jpg --json` → paste the returned `url`.
|
|
46
|
+
- `ailab image ls --unused`; admins: `ailab image purge --dry-run` then `ailab image purge`, `ailab image rm <id> [--force]`.
|
|
47
47
|
|
|
48
48
|
## HTML pages
|
|
49
49
|
|
|
50
50
|
- Content is either Markdown or an HTML page (`format` in `--json` output). HTML pages open full-page at `pageUrl` (`/p/<slug>/`) in a sandbox: scripts run, but `localStorage`, cookies and the platform API are unavailable to the page.
|
|
51
|
-
- Publish a page: `
|
|
52
|
-
- Edit: `
|
|
53
|
-
- `
|
|
51
|
+
- Publish a page: `ailab push page.html`, `ailab push site.zip`, or `ailab push ./site/` (a folder with `index.html` at the top plus relative css / js / images). Metadata comes from flags (`--slug --title --summary --access --tags`) or `ailab.json` in the folder (`{"slug","title","summary","access","status","tags","cover"}`). Title falls back to `<title>`.
|
|
52
|
+
- Edit: `ailab pull <slug> -o ./site/` (writes the files and `ailab.json` with `version`) → edit → `ailab push ./site/ --if-match <version>`.
|
|
53
|
+
- `ailab read <slug>` prints the page's extracted text; `ailab read <slug> --html` prints the entry HTML.
|
|
54
54
|
- Pushing Markdown to an HTML slug fails with `html_content` (exit 5) — that's intentional; don't convert formats unless the user asks.
|
package/dist/cli.js
CHANGED
|
@@ -30,7 +30,7 @@ var PAGE_LIMITS = { maxUploadBytes: 50 * 1024 * 1024, maxTotalBytes: 200 * 1024
|
|
|
30
30
|
// package.json
|
|
31
31
|
var package_default = {
|
|
32
32
|
name: "ai-native-lab",
|
|
33
|
-
version: "0.1.
|
|
33
|
+
version: "0.1.1",
|
|
34
34
|
description: "AI Native Lab CLI for people and agents: read, search and publish content. Non-interactive, --json output, stable exit codes.",
|
|
35
35
|
keywords: [
|
|
36
36
|
"ai-native-lab",
|
|
@@ -52,7 +52,7 @@ var package_default = {
|
|
|
52
52
|
},
|
|
53
53
|
type: "module",
|
|
54
54
|
bin: {
|
|
55
|
-
|
|
55
|
+
ailab: "dist/cli.js"
|
|
56
56
|
},
|
|
57
57
|
files: [
|
|
58
58
|
"dist",
|
|
@@ -96,7 +96,7 @@ var CliError = class extends Error {
|
|
|
96
96
|
var exitFor = (status, code2) => status === 401 || code2 === "pending_approval" || code2 === "banned" || code2 === "expired_token" || code2 === "access_denied" ? EXIT.auth : status === 403 ? EXIT.forbidden : status === 409 || status === 412 ? EXIT.conflict : status === 400 ? EXIT.usage : EXIT.error;
|
|
97
97
|
function createClient(apiUrl, token) {
|
|
98
98
|
async function call(method, p, body, headers = {}) {
|
|
99
|
-
const h = { "user-agent": `
|
|
99
|
+
const h = { "user-agent": `ailab-cli/${package_default.version}`, ...headers };
|
|
100
100
|
if (token) h.authorization = `Bearer ${token}`;
|
|
101
101
|
if (body !== void 0) h["content-type"] = "application/json";
|
|
102
102
|
let res;
|
|
@@ -119,7 +119,7 @@ function createClient(apiUrl, token) {
|
|
|
119
119
|
return json;
|
|
120
120
|
}
|
|
121
121
|
async function download(p) {
|
|
122
|
-
const h = { "user-agent": `
|
|
122
|
+
const h = { "user-agent": `ailab-cli/${package_default.version}` };
|
|
123
123
|
if (token) h.authorization = `Bearer ${token}`;
|
|
124
124
|
let res;
|
|
125
125
|
try {
|
|
@@ -135,7 +135,7 @@ function createClient(apiUrl, token) {
|
|
|
135
135
|
return new Uint8Array(await res.arrayBuffer());
|
|
136
136
|
}
|
|
137
137
|
async function upload(p, form, method = "POST", extraHeaders = {}) {
|
|
138
|
-
const h = { "user-agent": `
|
|
138
|
+
const h = { "user-agent": `ailab-cli/${package_default.version}`, ...extraHeaders };
|
|
139
139
|
if (token) h.authorization = `Bearer ${token}`;
|
|
140
140
|
let res;
|
|
141
141
|
try {
|
|
@@ -196,10 +196,10 @@ async function saveConfig(c) {
|
|
|
196
196
|
var clearConfig = () => rm(FILE, { force: true });
|
|
197
197
|
async function resolveAuth(flags) {
|
|
198
198
|
const c = await loadConfig();
|
|
199
|
-
const explicit = flags.apiUrl ?? process.env.
|
|
199
|
+
const explicit = flags.apiUrl ?? process.env.AILAB_API_URL;
|
|
200
200
|
const apiUrl = normalizeUrl(explicit ?? c.url ?? DEFAULT_API_URL);
|
|
201
201
|
const same = !explicit || c.url && normalizeUrl(c.url) === apiUrl;
|
|
202
|
-
return { apiUrl, token: flags.token ?? process.env.
|
|
202
|
+
return { apiUrl, token: flags.token ?? process.env.AILAB_TOKEN ?? (same ? c.token : void 0) };
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
// src/images.ts
|
|
@@ -268,7 +268,7 @@ ${d.body.replace(/^\n+/, "")}`;
|
|
|
268
268
|
import { mkdir as mkdir2, readdir, readFile as readFile3, stat, writeFile as writeFile2 } from "fs/promises";
|
|
269
269
|
import path3 from "path";
|
|
270
270
|
import { unzipSync, zipSync } from "fflate";
|
|
271
|
-
var META_FILE = "
|
|
271
|
+
var META_FILE = "ailab.json";
|
|
272
272
|
var IGNORE = /* @__PURE__ */ new Set([".git", "node_modules", ".DS_Store", "Thumbs.db", META_FILE]);
|
|
273
273
|
async function zipDir(dir) {
|
|
274
274
|
const files = {};
|
|
@@ -315,7 +315,7 @@ async function extractTo(dir, zip, meta) {
|
|
|
315
315
|
}
|
|
316
316
|
|
|
317
317
|
// src/cli.ts
|
|
318
|
-
var program = new Command().name("
|
|
318
|
+
var program = new Command().name("ailab").description("AI Native Lab: read, search and publish content from the terminal or an agent").version(package_default.version).addOption(new Option("--json", "print one JSON object to stdout (also AILAB_JSON=1)").default(process.env.AILAB_JSON === "1")).option("--api-url <url>", "server URL, overrides the saved one (env AILAB_API_URL)").option("--token <token>", "API token, overrides the saved one (env AILAB_TOKEN)").showHelpAfterError().addHelpText(
|
|
319
319
|
"after",
|
|
320
320
|
`
|
|
321
321
|
Recommended workflow:
|
|
@@ -326,7 +326,7 @@ Recommended workflow:
|
|
|
326
326
|
Write (editor) pull <slug> -o post.md \u2192 edit \u2192 push post.md --publish
|
|
327
327
|
New content push post.md --slug <slug> --tags a,b [--auto-tags topic] --dry-run, then without --dry-run
|
|
328
328
|
Images  in Markdown is uploaded by push automatically; or: image upload a.png
|
|
329
|
-
HTML pages push page.html | push site.zip | push ./site/ (folder: index.html + assets, metadata in
|
|
329
|
+
HTML pages push page.html | push site.zip | push ./site/ (folder: index.html + assets, metadata in ailab.json)
|
|
330
330
|
pull <slug> -o ./site/ \u2192 edit \u2192 push ./site/ --if-match <version>
|
|
331
331
|
Members (admin) users --expiring 7 \u2192 member grant <email> --days 365
|
|
332
332
|
|
|
@@ -343,11 +343,11 @@ Markdown files carry metadata in YAML frontmatter:
|
|
|
343
343
|
Exit codes: 0 ok \xB7 1 error \xB7 2 usage \xB7 3 auth \xB7 4 forbidden \xB7 5 conflict (version mismatch / exists)
|
|
344
344
|
|
|
345
345
|
Examples:
|
|
346
|
-
$
|
|
347
|
-
$
|
|
348
|
-
$
|
|
349
|
-
$ cat note.md |
|
|
350
|
-
$
|
|
346
|
+
$ ailab login # default server; or: ailab login https://your-host
|
|
347
|
+
$ ailab ls --tag agentic-coding --json
|
|
348
|
+
$ ailab read company-brain > company-brain.md
|
|
349
|
+
$ cat note.md | ailab push - --slug quick-note --publish
|
|
350
|
+
$ AILAB_TOKEN=aln_xxx AILAB_API_URL=https://lab.example.com ailab ls --json`
|
|
351
351
|
);
|
|
352
352
|
var g = () => program.opts();
|
|
353
353
|
function out(human, data) {
|
|
@@ -364,7 +364,7 @@ async function client() {
|
|
|
364
364
|
}
|
|
365
365
|
async function needToken() {
|
|
366
366
|
const { apiUrl, token } = await resolveAuth(g());
|
|
367
|
-
if (!token) throw new CliError("unauthorized", "Not logged in. Run `
|
|
367
|
+
if (!token) throw new CliError("unauthorized", "Not logged in. Run `ailab login <url>` or set AILAB_TOKEN", EXIT.auth);
|
|
368
368
|
return createClient(apiUrl, token);
|
|
369
369
|
}
|
|
370
370
|
var readInput = async (file) => {
|
|
@@ -387,9 +387,9 @@ function openBrowser(url) {
|
|
|
387
387
|
}
|
|
388
388
|
program.command("login [url]").description("log in through the browser (device code); the token is saved to ~/.config/ai-native-lab").option("--no-browser", "don't open the browser, just print the link").action(async (url, opts) => {
|
|
389
389
|
const saved = await loadConfig();
|
|
390
|
-
const apiUrl = normalizeUrl(url ?? g().apiUrl ?? process.env.
|
|
390
|
+
const apiUrl = normalizeUrl(url ?? g().apiUrl ?? process.env.AILAB_API_URL ?? saved.url ?? DEFAULT_API_URL);
|
|
391
391
|
const c = createClient(apiUrl);
|
|
392
|
-
const start = await c.deviceStart(`
|
|
392
|
+
const start = await c.deviceStart(`ailab on ${hostname()}`.slice(0, 80));
|
|
393
393
|
note(`Open ${start.verificationUrlComplete}
|
|
394
394
|
and confirm the code ${start.userCode}. Waiting\u2026`);
|
|
395
395
|
if (opts.browser) openBrowser(start.verificationUrlComplete);
|
|
@@ -417,7 +417,7 @@ program.command("whoami").description("who you are, your role and membership exp
|
|
|
417
417
|
out(me.user ? `${me.user.email} \xB7 ${me.role}${m}
|
|
418
418
|
permissions: ${me.permissions.join(", ")}` : "guest (not logged in)", me);
|
|
419
419
|
});
|
|
420
|
-
program.command("ls").description("list content (newest first)").option("--tag <slugs>", "comma separated tag slugs; OR within a tag group, AND across groups").option("--access <level>", "public | registered | member").option("--status <status>", "published (default) | draft | archived | all (editors only for non-published)").option("-q, --query <text>", "full-text search (same as `
|
|
420
|
+
program.command("ls").description("list content (newest first)").option("--tag <slugs>", "comma separated tag slugs; OR within a tag group, AND across groups").option("--access <level>", "public | registered | member").option("--status <status>", "published (default) | draft | archived | all (editors only for non-published)").option("-q, --query <text>", "full-text search (same as `ailab search`)").option("--bookmarked", "only content you bookmarked").option("--limit <n>", "page size, max 100", "20").option("--offset <n>", "skip n items", "0").action(async (o) => {
|
|
421
421
|
const res = await (await client()).list({ tag: o.tag, access: o.access, status: o.status, q: o.query, bookmarked: o.bookmarked, limit: o.limit, offset: o.offset });
|
|
422
422
|
const more = res.nextOffset != null ? `
|
|
423
423
|
\u2026 more: --offset ${res.nextOffset}` : "";
|
|
@@ -461,7 +461,7 @@ program.command("follow [tag]").description("follow a tag (weekly email digest o
|
|
|
461
461
|
if (!slug) {
|
|
462
462
|
if (o.remove) throw new CliError("bad_request", "Pass the tag to unfollow", EXIT.usage);
|
|
463
463
|
const res2 = await c.call("GET", "/me/follows");
|
|
464
|
-
return out(res2.follows.map((t) => `${pad(t.slug, 28)} ${t.name}${t.archived ? " (archived)" : ""}`).join("\n") || "Not following any tags. Try `
|
|
464
|
+
return out(res2.follows.map((t) => `${pad(t.slug, 28)} ${t.name}${t.archived ? " (archived)" : ""}`).join("\n") || "Not following any tags. Try `ailab tags`, then `ailab follow <tag>`", res2);
|
|
465
465
|
}
|
|
466
466
|
const res = await c.call(o.remove ? "DELETE" : "PUT", `/tags/${encodeURIComponent(slug)}/follow`);
|
|
467
467
|
out(o.remove ? `Unfollowed ${slug}` : `Following ${slug} \xB7 weekly digest every Monday`, res);
|
|
@@ -543,7 +543,7 @@ program.command("push <file>").description("create or update content from a Mark
|
|
|
543
543
|
if (res.dryRun) return out(`dry run: would ${res.action} ${slug}`, res);
|
|
544
544
|
out(`${res.created ? "Created" : "Updated"} ${slug} \xB7 ${res.content.status} \xB7 v${res.content.version}`, { ...res, images });
|
|
545
545
|
});
|
|
546
|
-
program.command("pull <slug>").description("download content to edit and push back: Markdown with frontmatter, or for HTML pages a folder (with
|
|
546
|
+
program.command("pull <slug>").description("download content to edit and push back: Markdown with frontmatter, or for HTML pages a folder (with ailab.json) / .zip").option("-o, --out <file>", "write to a file instead of stdout; HTML pages: a folder, or a path ending in .zip").action(async (slug, o) => {
|
|
547
547
|
const c = await needToken();
|
|
548
548
|
const item = await c.get(slug);
|
|
549
549
|
if (item.locked || item.body == null) throw new CliError("locked", "You can't read the full text of this content", EXIT.forbidden);
|
|
@@ -564,7 +564,7 @@ program.command("pull <slug>").description("download content to edit and push ba
|
|
|
564
564
|
cover: item.cover,
|
|
565
565
|
version: item.version
|
|
566
566
|
});
|
|
567
|
-
return out(`Wrote ${n} files to ${o.out}/ (v${item.version}); edit, then:
|
|
567
|
+
return out(`Wrote ${n} files to ${o.out}/ (v${item.version}); edit, then: ailab push ${o.out} --if-match ${item.version}`, { ok: true, dir: o.out, files: n, version: item.version });
|
|
568
568
|
}
|
|
569
569
|
const md = formatDoc({ ...item, tags: item.tags.map((t) => t.slug), body: item.body });
|
|
570
570
|
if (!o.out) return void process.stdout.write(md.endsWith("\n") ? md : `${md}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-native-lab",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "AI Native Lab CLI for people and agents: read, search and publish content. Non-interactive, --json output, stable exit codes.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-native-lab",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"type": "module",
|
|
24
24
|
"bin": {
|
|
25
|
-
"
|
|
25
|
+
"ailab": "dist/cli.js"
|
|
26
26
|
},
|
|
27
27
|
"files": [
|
|
28
28
|
"dist",
|