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.
Files changed (4) hide show
  1. package/README.md +27 -27
  2. package/SKILL.md +22 -22
  3. package/dist/cli.js +22 -22
  4. package/package.json +2 -2
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # ai-native-lab · `aln`
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 # 命令名是 aln,需要 Node.js 20+
13
- aln login # 打开浏览器确认授权;自建服务:aln login https://your-host
14
- aln whoami
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
- ALN_TOKEN=aln_xxx ALN_API_URL=https://ai-native-lab.mixdao.world aln ls --json
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
- aln tags # 标签组和标签
27
- aln ls --tag agentic-coding # 组内「或」、组间「且」
28
- aln search 公司大脑
29
- aln read <slug> # Markdown;HTML 内容输出抽出的文字,--html 输出源码
30
- aln bookmark <slug>
31
- aln redeem ALN-XXXX-XXXX-XXXX # 兑换会员
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
- aln push post.md --dry-run
55
- aln push post.md --publish
56
- aln pull my-post -o post.md # 改完再 push,--if-match <version> 防止覆盖别人的修改
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` + 相对路径的资源,元数据放 `aln.json`):
59
+ HTML 页面(单个 `.html`、`.zip`,或一个目录:根目录 `index.html` + 相对路径的资源,元数据放 `ailab.json`):
60
60
 
61
61
  ```bash
62
- aln push ./site/ --slug my-report --title 报告 --access member --publish
63
- aln pull my-report -o ./site/ # 解压并写 aln.json,改完:aln push ./site/ --if-match <version>
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
- aln image upload a.png b.jpg # 输出可直接粘贴的 Markdown
70
- aln tag create company-brain --name 公司大脑 --group topic
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
- aln users --expiring 7
77
- aln member grant someone@example.com --days 365
78
- aln role someone@example.com editor
79
- aln code create --days 365 --count 10
80
- aln image purge --dry-run
81
- aln audit --action membership.
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
- aln skill --install # 写入 ~/.claude/skills/ai-native-lab/SKILL.md
87
+ ailab skill --install # 写入 ~/.claude/skills/ai-native-lab/SKILL.md
88
88
  ```
89
89
 
90
- 完整命令:`aln --help`、`aln <命令> --help`。
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 `aln` 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.
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 (`aln`)
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 `aln login` or set `ALN_TOKEN`) · 4 forbidden / locked · 5 conflict (re-fetch, then retry).
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
- - `aln 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 `aln login <url>` (it opens a browser), or use `ALN_TOKEN=aln_… ALN_API_URL=https://…`.
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. `aln tags --json` to learn the tag slugs.
20
- 2. `aln ls --tag <a,b> --json` (OR within a tag group, AND across groups), or `aln 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. `aln 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.
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
- - `aln follow <tag>` subscribes the user to a weekly email digest (Mondays) of new content in that tag; `aln follow <tag> --remove` unsubscribes; `aln follow --json` lists current follows. Only do this when the user asks.
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: `aln pull <slug> -o post.md` → edit the file → `aln push post.md --if-match <version from frontmatter>`.
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
- `aln push post.md --dry-run --json` → `aln push post.md --json` (saves a draft) → `aln publish <slug>` when the user approves.
32
- - Unknown tags fail with `unknown_tag`; create them with `aln tag create <slug> --name <name> --group <key>`, or pass `--auto-tags <group>`.
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` / `aln 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
- - `aln users --expiring 7 --json`, `aln member grant <email> --days 365`, `aln member revoke <email>`, `aln role <email> editor`.
39
- - `aln code create --days 365 --count 10` prints codes once; store them safely.
40
- - `aln audit --action membership. --json`.
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 (`![说明](./img/chart.png)`, also `cover: ./img/cover.jpg`); `aln push` uploads them, compresses them to WebP (long edge ≤ 2000px) and rewrites the links. `--dry-run` does not upload.
45
- - Or upload first: `aln image upload a.png b.jpg --json` → paste the returned `url`.
46
- - `aln image ls --unused`; admins: `aln image purge --dry-run` then `aln image purge`, `aln image rm <id> [--force]`.
44
+ - Reference local images in Markdown with relative paths (`![说明](./img/chart.png)`, 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: `aln push page.html`, `aln push site.zip`, or `aln 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 `aln.json` in the folder (`{"slug","title","summary","access","status","tags","cover"}`). Title falls back to `<title>`.
52
- - Edit: `aln pull <slug> -o ./site/` (writes the files and `aln.json` with `version`) → edit → `aln push ./site/ --if-match <version>`.
53
- - `aln read <slug>` prints the page's extracted text; `aln read <slug> --html` prints the entry HTML.
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.0",
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
- aln: "dist/cli.js"
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": `aln-cli/${package_default.version}`, ...headers };
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": `aln-cli/${package_default.version}` };
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": `aln-cli/${package_default.version}`, ...extraHeaders };
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.ALN_API_URL;
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.ALN_TOKEN ?? (same ? c.token : void 0) };
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 = "aln.json";
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("aln").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 ALN_JSON=1)").default(process.env.ALN_JSON === "1")).option("--api-url <url>", "server URL, overrides the saved one (env ALN_API_URL)").option("--token <token>", "API token, overrides the saved one (env ALN_TOKEN)").showHelpAfterError().addHelpText(
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 ![\u8BF4\u660E](./img/a.png) 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 aln.json)
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
- $ aln login # default server; or: aln login https://your-host
347
- $ aln ls --tag agentic-coding --json
348
- $ aln read company-brain > company-brain.md
349
- $ cat note.md | aln push - --slug quick-note --publish
350
- $ ALN_TOKEN=aln_xxx ALN_API_URL=https://lab.example.com aln ls --json`
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 `aln login <url>` or set ALN_TOKEN", EXIT.auth);
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.ALN_API_URL ?? saved.url ?? DEFAULT_API_URL);
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(`aln on ${hostname()}`.slice(0, 80));
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 `aln 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) => {
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 `aln tags`, then `aln follow <tag>`", res2);
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 aln.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) => {
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: aln push ${o.out} --if-match ${item.version}`, { ok: true, dir: o.out, files: n, version: item.version });
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.0",
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
- "aln": "dist/cli.js"
25
+ "ailab": "dist/cli.js"
26
26
  },
27
27
  "files": [
28
28
  "dist",