@thehumanpatternlab/hpl 0.0.1-alpha.5 → 1.0.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 CHANGED
@@ -1,42 +1,182 @@
1
- # HPL CLI (Alpha) 🧭🦊
2
-
3
- <span>
4
- <img src="https://img.shields.io/badge/AI--Forward%20CLI-black?style=flat-square" />
5
- <img src="https://img.shields.io/badge/automation--safe%20by%20design-8b5cf6?style=flat-square" />
6
- </span>
7
-
8
- [![Carmel Judgment Protocol 😼](https://github.com/AdaInTheLab/the-human-pattern-lab-cli/actions/workflows/carmel-judgment.yml/badge.svg)](https://github.com/AdaInTheLab/the-human-pattern-lab-cli/actions/workflows/carmel-judgment.yml)
9
-
10
- Contract-first CLI for The Human Pattern Lab.
11
-
12
- ## Install (local dev)
13
-
14
- ```bash
15
- npm install
16
- npm run dev -- --help
17
- ```
18
-
19
- ## Config
20
-
21
- - `HPL_API_BASE_URL` (default: `https://api.thehumanpatternlab.com`)
22
-
23
- ## Commands (MVP)
24
-
25
- - `hpl version`
26
- - `hpl capabilities`
27
- - `hpl health`
28
- - `hpl notes list [--limit N]`
29
- - `hpl notes get <slug> [--raw]`
30
-
31
- ## JSON contract
32
-
33
- Add `--json` to emit machine-readable JSON only on stdout.
34
-
35
- ### Examples
36
-
37
- ```bash
38
- hpl capabilities --json
39
- hpl health --json
40
- hpl notes list --json
41
- hpl notes get the-invitation --json
42
- ```
1
+ <!--
2
+ HPL CLI
3
+ The Human Pattern Lab
4
+
5
+ This README is written for humans.
6
+ Design rationale lives in DESIGN.md.
7
+ -->
8
+
9
+ # HPL CLI (Alpha) 🧭🦊
10
+
11
+ ![AI-Forward CLI](https://img.shields.io/badge/AI--Forward%20CLI-black?style=flat-square)
12
+ ![automation-safe by design](https://img.shields.io/badge/automation--safe%20by%20design-8b5cf6?style=flat-square)
13
+ ![npm (alpha)](https://img.shields.io/npm/v/@thehumanpatternlab/hpl/alpha?label=alpha&color=8b5cf6&style=flat-square)
14
+ ![Carmel Judgment](https://github.com/AdaInTheLab/the-human-pattern-lab-cli/actions/workflows/carmel-judgment.yml/badge.svg)
15
+
16
+ > **Status:** Alpha
17
+ > A modern, automation-safe CLI for The Human Pattern Lab.
18
+
19
+ **HPL** is the official command-line interface for **The Human Pattern Lab**.
20
+
21
+ Formerly developed under the codename **Skulk**, HPL is built to work just as well for humans at the keyboard as it does for automation, CI, and agent-driven workflows.
22
+
23
+ This package is in **active alpha development**. Interfaces are stabilizing, but iteration is expected.
24
+
25
+ ---
26
+
27
+ ## What HPL Connects To
28
+
29
+ HPL is a deterministic bridge between:
30
+
31
+ - the **Human Pattern Lab Content Repository** (source of truth)
32
+ - the **Human Pattern Lab API** (runtime index and operations)
33
+
34
+ Written content lives as Markdown in a dedicated content repository.
35
+ The API syncs and indexes that content so it can be rendered by user interfaces.
36
+
37
+ By default, HPL targets a Human Pattern Lab API instance. You can override the API endpoint with `--base-url` to use staging or a self-hosted deployment of the same API.
38
+
39
+ > Note: `--base-url` is intended for alternate deployments of the Human Pattern Lab API, not arbitrary third-party APIs.
40
+
41
+ ---
42
+
43
+ ## Authentication
44
+
45
+ HPL supports token-based authentication via the `HPL_TOKEN` environment variable.
46
+
47
+ ```bash
48
+ export HPL_TOKEN="your-api-token"
49
+ ```
50
+
51
+ (Optional) Override the API endpoint:
52
+
53
+ ```bash
54
+ export HPL_BASE_URL="https://api.thehumanpatternlab.com"
55
+ ```
56
+
57
+ > `HPL_BASE_URL` should point to the **root** of a Human Pattern Lab API deployment.
58
+ > Do not include additional path segments.
59
+
60
+ Some API endpoints may require authentication depending on server configuration.
61
+
62
+ ---
63
+
64
+ ## Quick Start
65
+
66
+ ### Install (alpha)
67
+
68
+ ```bash
69
+ npm install -g @thehumanpatternlab/hpl@alpha
70
+ ```
71
+
72
+ ### Sync Lab Notes from the content repository
73
+
74
+ ```bash
75
+ hpl notes sync --content-repo AdaInTheLab/the-human-pattern-lab-content
76
+ ```
77
+
78
+ This pulls structured Markdown content from the repository and synchronizes it into the Human Pattern Lab system.
79
+
80
+ ### Machine-readable output
81
+
82
+ ```bash
83
+ hpl --json notes sync
84
+ ```
85
+
86
+ ---
87
+
88
+ ## Content Source Configuration (Optional)
89
+
90
+ By default, `notes sync` expects a content repository with the following structure:
91
+
92
+ ```text
93
+ labnotes/
94
+ en/
95
+ *.md
96
+ ko/
97
+ *.md
98
+ ```
99
+
100
+ You may pin a default content repository using an environment variable:
101
+
102
+ ```bash
103
+ export HPL_CONTENT_REPO="AdaInTheLab/the-human-pattern-lab-content"
104
+ ```
105
+
106
+ This allows `hpl notes sync` to run without explicitly passing `--content-repo`.
107
+
108
+ ---
109
+
110
+ ## Commands
111
+
112
+ ```text
113
+ hpl <domain> <action> [options]
114
+ ```
115
+
116
+ ### notes
117
+
118
+ **Read operations:**
119
+ - `hpl notes list` - List all published Lab Notes
120
+ - `hpl notes get <slug>` - Get a specific Lab Note by slug
121
+
122
+ **Write operations:** (requires `HPL_TOKEN`)
123
+ - `hpl notes create --title "..." --slug "..." --file note.md` - Create a new Lab Note
124
+ - `hpl notes update <slug> --title "..." --file note.md` - Update an existing Lab Note
125
+
126
+ **Bulk operations:**
127
+ - `hpl notes sync --content-repo <owner/name|url>` - Sync from content repository
128
+ - `hpl notes sync --dir <path>` - Sync from local directory (advanced / local development)
129
+
130
+ See [WRITE_OPERATIONS_GUIDE.md](./WRITE_OPERATIONS_GUIDE.md) for detailed write operations documentation.
131
+
132
+ ### health
133
+
134
+ ```bash
135
+ hpl health
136
+ ```
137
+
138
+ ### version
139
+
140
+ ```bash
141
+ hpl version
142
+ ```
143
+
144
+ ---
145
+
146
+ ## JSON Output Contract
147
+
148
+ Structured output is treated as a **contract**, not a courtesy.
149
+
150
+ When `--json` is provided:
151
+
152
+ - stdout contains **only valid JSON**
153
+ - stderr is used for logs and diagnostics
154
+ - exit codes are deterministic
155
+
156
+ A verification step is included:
157
+
158
+ ```bash
159
+ npm run json:check
160
+ ```
161
+
162
+ This command fails if any non-JSON output appears on stdout.
163
+
164
+ ---
165
+
166
+ ## What HPL Is Not
167
+
168
+ HPL is not:
169
+ - a chatbot interface
170
+ - an agent framework
171
+ - a memory system
172
+ - an inference layer
173
+
174
+ It is a command-line tool for interacting with Human Pattern Lab systems in a predictable, human-owned way.
175
+
176
+ ---
177
+
178
+ **The Human Pattern Lab**
179
+ https://thehumanpatternlab.com
180
+
181
+ *The lantern is lit.
182
+ The foxes are watching.*
package/dist/bin/hpl.js CHANGED
@@ -2,157 +2,32 @@
2
2
  /* ===========================================================
3
3
  🌌 HUMAN PATTERN LAB — CLI ENTRYPOINT
4
4
  -----------------------------------------------------------
5
- Commands:
6
- - version
7
- - capabilities
8
- - health
9
- - notes list
10
- - notes get <slug>
11
- Contract: --json => JSON only on stdout
5
+ Purpose:
6
+ - Register top-level commands
7
+ - Define global flags (--json)
8
+ - Parse argv
9
+ Contract:
10
+ --json => JSON only on stdout (enforced in command handlers)
12
11
  Notes:
13
- - Avoid process.exit() inside command handlers (can trip libuv on Windows + tsx).
12
+ Avoid process.exit() inside handlers (Windows + tsx stability).
14
13
  =========================================================== */
15
14
  import { Command } from "commander";
16
- import { writeHuman, writeJson } from "../src/io";
17
- import { EXIT } from "../src/contract/exitCodes";
18
- import { runVersion } from "../src/commands/version";
19
- import { runCapabilities } from "../src/commands/capabilities";
20
- import { runHealth } from "../src/commands/health";
21
- import { runNotesList } from "../src/commands/notes/list";
22
- import { runNotesGet } from "../src/commands/notes/get";
23
- import { renderTable } from "../src/render/table";
24
- import { formatTags, safeLine, stripHtml } from "../src/render/text";
15
+ import { versionCommand } from "../src/commands/version.js";
16
+ import { capabilitiesCommand } from "../src/commands/capabilities.js";
17
+ import { healthCommand } from "../src/commands/health.js";
18
+ import { notesCommand } from "../src/commands/notes/notes.js";
19
+ import { EXIT } from "../src/contract/exitCodes.js";
25
20
  const program = new Command();
26
21
  program
27
22
  .name("hpl")
28
23
  .description("Human Pattern Lab CLI (alpha)")
29
24
  .option("--json", "Emit contract JSON only on stdout")
30
- .showHelpAfterError();
31
- function setExit(code) {
32
- // Let Node exit naturally (important for Windows + tsx stability).
33
- process.exitCode = code;
34
- }
35
- program
36
- .command("version")
37
- .description("Show CLI version (contract: show_version)")
38
- .action(() => {
39
- const opts = program.opts();
40
- const envelope = runVersion("version");
41
- if (opts.json)
42
- writeJson(envelope);
43
- else
44
- writeHuman(`${envelope.data.name} ${envelope.data.version}`);
45
- setExit(EXIT.OK);
46
- });
47
- program
48
- .command("capabilities")
49
- .description("Show CLI capabilities for agents (contract: show_capabilities)")
50
- .action(() => {
51
- const opts = program.opts();
52
- const envelope = runCapabilities("capabilities");
53
- if (opts.json)
54
- writeJson(envelope);
55
- else {
56
- writeHuman(`intentTier: ${envelope.data.intentTier}`);
57
- writeHuman(`schemaVersions: ${envelope.data.schemaVersions.join(", ")}`);
58
- writeHuman(`supportedIntents:`);
59
- for (const i of envelope.data.supportedIntents)
60
- writeHuman(` - ${i}`);
61
- }
62
- setExit(EXIT.OK);
63
- });
64
- program
65
- .command("health")
66
- .description("Check API health (contract: check_health)")
67
- .action(async () => {
68
- const opts = program.opts();
69
- const result = await runHealth("health");
70
- if (opts.json) {
71
- writeJson(result.envelope);
72
- }
73
- else {
74
- if (result.envelope.status === "ok") {
75
- const d = result.envelope.data;
76
- const db = d.dbPath ? ` (db: ${d.dbPath})` : "";
77
- writeHuman(`ok${db}`);
78
- }
79
- else {
80
- const e = result.envelope.error;
81
- writeHuman(`error: ${e.code} — ${e.message}`);
82
- }
83
- }
84
- setExit(result.exitCode);
85
- });
86
- const notes = program.command("notes").description("Lab Notes commands");
87
- notes
88
- .command("list")
89
- .description("List lab notes (contract: render_lab_note)")
90
- .option("--limit <n>", "Limit number of rows (client-side)", (v) => parseInt(v, 10))
91
- .action(async (cmdOpts) => {
92
- const opts = program.opts();
93
- const result = await runNotesList("notes list");
94
- if (opts.json) {
95
- writeJson(result.envelope);
96
- setExit(result.exitCode);
97
- return;
98
- }
99
- if (result.envelope.status !== "ok") {
100
- const e = result.envelope.error;
101
- writeHuman(`error: ${e.code} — ${e.message}`);
102
- setExit(result.exitCode);
103
- return;
104
- }
105
- const data = result.envelope.data;
106
- const rows = data.notes ?? [];
107
- const limit = Number.isFinite(cmdOpts.limit) && cmdOpts.limit > 0 ? cmdOpts.limit : rows.length;
108
- const slice = rows.slice(0, limit);
109
- const table = renderTable(slice, [
110
- { header: "slug", width: 28, value: (n) => safeLine(String(n.slug ?? "")) },
111
- { header: "title", width: 34, value: (n) => safeLine(String(n.title ?? "")) },
112
- { header: "status", width: 10, value: (n) => safeLine(String(n.status ?? "-")) },
113
- { header: "dept", width: 8, value: (n) => safeLine(String(n.department_id ?? "-")) },
114
- { header: "tags", width: 22, value: (n) => formatTags(n.tags) },
115
- ]);
116
- writeHuman(table);
117
- writeHuman(`\ncount: ${data.count}`);
118
- setExit(result.exitCode);
119
- });
120
- notes
121
- .command("get")
122
- .description("Get a lab note by slug (contract: render_lab_note)")
123
- .argument("<slug>", "Lab Note slug")
124
- .option("--raw", "Print raw contentHtml (no HTML stripping)")
125
- .action(async (slug, cmdOpts) => {
126
- const opts = program.opts();
127
- const result = await runNotesGet(slug, "notes get");
128
- if (opts.json) {
129
- writeJson(result.envelope);
130
- setExit(result.exitCode);
131
- return;
132
- }
133
- if (result.envelope.status !== "ok") {
134
- const e = result.envelope.error;
135
- writeHuman(`error: ${e.code} — ${e.message}`);
136
- setExit(result.exitCode);
137
- return;
138
- }
139
- const n = result.envelope.data;
140
- writeHuman(`# ${n.title}`);
141
- writeHuman(`slug: ${n.slug}`);
142
- if (n.status)
143
- writeHuman(`status: ${n.status}`);
144
- if (n.type)
145
- writeHuman(`type: ${n.type}`);
146
- if (n.department_id)
147
- writeHuman(`department_id: ${n.department_id}`);
148
- if (n.published)
149
- writeHuman(`published: ${n.published}`);
150
- if (Array.isArray(n.tags))
151
- writeHuman(`tags: ${formatTags(n.tags)}`);
152
- writeHuman("");
153
- const body = cmdOpts.raw ? String(n.contentHtml ?? "") : stripHtml(String(n.contentHtml ?? ""));
154
- writeHuman(body || "(no content)");
155
- setExit(result.exitCode);
25
+ .showHelpAfterError()
26
+ .configureHelp({ helpWidth: 100 });
27
+ program.addCommand(versionCommand());
28
+ program.addCommand(capabilitiesCommand());
29
+ program.addCommand(healthCommand());
30
+ program.addCommand(notesCommand());
31
+ program.parseAsync(process.argv).catch(() => {
32
+ process.exitCode = EXIT.UNKNOWN;
156
33
  });
157
- // Let commander handle errors; set exit code without hard exit.
158
- program.parseAsync(process.argv).catch(() => setExit(EXIT.UNKNOWN));
@@ -1,9 +1,33 @@
1
1
  /* ===========================================================
2
2
  🌌 HUMAN PATTERN LAB — COMMAND: capabilities
3
3
  =========================================================== */
4
+ import { Command } from "commander";
5
+ import { writeHuman, writeJson } from "../io.js";
6
+ import { EXIT } from "../contract/exitCodes.js";
4
7
  import { getAlphaIntent } from "../contract/intents";
5
8
  import { ok } from "../contract/envelope";
6
9
  import { getCapabilitiesAlpha } from "../contract/capabilities";
10
+ export function capabilitiesCommand() {
11
+ return new Command("capabilities")
12
+ .description("Show CLI capabilities for agents (contract: show_capabilities)")
13
+ .action((...args) => {
14
+ const cmd = args[args.length - 1];
15
+ const rootOpts = (cmd.parent?.opts?.() ?? {});
16
+ const envelope = runCapabilities("capabilities");
17
+ if (rootOpts.json) {
18
+ writeJson(envelope);
19
+ }
20
+ else {
21
+ const d = envelope.data ?? {};
22
+ writeHuman(`intentTier: ${d.intentTier ?? "-"}`);
23
+ writeHuman(`schemaVersions: ${(d.schemaVersions ?? []).join(", ")}`);
24
+ writeHuman(`supportedIntents:`);
25
+ for (const i of d.supportedIntents ?? [])
26
+ writeHuman(` - ${i}`);
27
+ }
28
+ process.exitCode = EXIT.OK;
29
+ });
30
+ }
7
31
  export function runCapabilities(commandName = "capabilities") {
8
32
  const intent = getAlphaIntent("show_capabilities");
9
33
  return ok(commandName, intent, getCapabilitiesAlpha());
@@ -1,6 +1,8 @@
1
1
  /* ===========================================================
2
2
  🌌 HUMAN PATTERN LAB — COMMAND: health
3
3
  =========================================================== */
4
+ import { Command } from "commander";
5
+ import { writeHuman, writeJson } from "../io.js";
4
6
  import { z } from "zod";
5
7
  import { getAlphaIntent } from "../contract/intents";
6
8
  import { ok, err } from "../contract/envelope";
@@ -10,6 +12,30 @@ const HealthSchema = z.object({
10
12
  status: z.string(),
11
13
  dbPath: z.string().optional(),
12
14
  });
15
+ export function healthCommand() {
16
+ return new Command("health")
17
+ .description("Check API health (contract: check_health)")
18
+ .action(async (...args) => {
19
+ const cmd = args[args.length - 1];
20
+ const rootOpts = (cmd.parent?.opts?.() ?? {});
21
+ const result = await runHealth("health");
22
+ if (rootOpts.json) {
23
+ writeJson(result.envelope);
24
+ }
25
+ else {
26
+ if (result.envelope.status === "ok") {
27
+ const d = result.envelope.data ?? {};
28
+ const db = d.dbPath ? ` (db: ${d.dbPath})` : "";
29
+ writeHuman(`ok${db}`);
30
+ }
31
+ else {
32
+ const e = result.envelope.error ?? {};
33
+ writeHuman(`error: ${e.code ?? "E_UNKNOWN"} — ${e.message ?? "unknown"}`);
34
+ }
35
+ }
36
+ process.exitCode = result.exitCode ?? EXIT.UNKNOWN;
37
+ });
38
+ }
13
39
  export async function runHealth(commandName = "health") {
14
40
  const intent = getAlphaIntent("check_health");
15
41
  try {
@@ -0,0 +1,183 @@
1
+ /* ===========================================================
2
+ 🦊 THE HUMAN PATTERN LAB — HPL CLI
3
+ -----------------------------------------------------------
4
+ File: create.ts
5
+ Role: Notes subcommand: `hpl notes create`
6
+ Author: Ada (The Human Pattern Lab)
7
+ Assistant: Claude
8
+ Lab Unit: SCMS — Systems & Code Management Suite
9
+ Status: Active
10
+ -----------------------------------------------------------
11
+ Purpose:
12
+ Create a new Lab Note via API using the upsert endpoint.
13
+ Supports both markdown file input and inline content.
14
+ -----------------------------------------------------------
15
+ Design:
16
+ - Core function returns { envelope, exitCode }
17
+ - Commander adapter decides json vs human rendering
18
+ - Requires authentication via HPL_TOKEN
19
+ =========================================================== */
20
+ import fs from "node:fs";
21
+ import { Command } from "commander";
22
+ import { getOutputMode, printJson } from "../../cli/output.js";
23
+ import { renderText } from "../../render/text.js";
24
+ import { LabNoteUpsertSchema } from "../../types/labNotes.js";
25
+ import { HPL_TOKEN } from "../../lib/config.js";
26
+ import { getAlphaIntent } from "../../contract/intents.js";
27
+ import { ok, err } from "../../contract/envelope.js";
28
+ import { EXIT } from "../../contract/exitCodes.js";
29
+ import { postJson, HttpError } from "../../http/client.js";
30
+ /**
31
+ * Core: create a new Lab Note.
32
+ * Returns structured envelope + exitCode (no printing here).
33
+ */
34
+ export async function runNotesCreate(options, commandName = "notes.create") {
35
+ const intent = getAlphaIntent("create_lab_note");
36
+ // Authentication check
37
+ const token = HPL_TOKEN();
38
+ if (!token) {
39
+ return {
40
+ envelope: err(commandName, intent, {
41
+ code: "E_AUTH",
42
+ message: "Authentication required. Set HPL_TOKEN environment variable or configure token in ~/.humanpatternlab/hpl.json",
43
+ }),
44
+ exitCode: EXIT.AUTH,
45
+ };
46
+ }
47
+ // Get markdown content
48
+ let markdown;
49
+ if (options.file) {
50
+ if (!fs.existsSync(options.file)) {
51
+ return {
52
+ envelope: err(commandName, intent, {
53
+ code: "E_NOT_FOUND",
54
+ message: `File not found: ${options.file}`,
55
+ }),
56
+ exitCode: EXIT.NOT_FOUND,
57
+ };
58
+ }
59
+ try {
60
+ markdown = fs.readFileSync(options.file, "utf-8");
61
+ }
62
+ catch (e) {
63
+ const msg = e instanceof Error ? e.message : String(e);
64
+ return {
65
+ envelope: err(commandName, intent, {
66
+ code: "E_IO",
67
+ message: `Failed to read file: ${msg}`,
68
+ }),
69
+ exitCode: EXIT.IO,
70
+ };
71
+ }
72
+ }
73
+ else if (options.markdown) {
74
+ markdown = options.markdown;
75
+ }
76
+ else {
77
+ return {
78
+ envelope: err(commandName, intent, {
79
+ code: "E_VALIDATION",
80
+ message: "Either --markdown or --file is required",
81
+ }),
82
+ exitCode: EXIT.VALIDATION,
83
+ };
84
+ }
85
+ // Build payload
86
+ const payload = {
87
+ slug: options.slug,
88
+ title: options.title,
89
+ markdown,
90
+ locale: options.locale,
91
+ subtitle: options.subtitle,
92
+ summary: options.summary,
93
+ tags: options.tags,
94
+ published: options.published,
95
+ status: options.status,
96
+ type: options.type,
97
+ dept: options.dept,
98
+ };
99
+ // Validate payload
100
+ const parsed = LabNoteUpsertSchema.safeParse(payload);
101
+ if (!parsed.success) {
102
+ return {
103
+ envelope: err(commandName, intent, {
104
+ code: "E_VALIDATION",
105
+ message: "Invalid note data",
106
+ details: parsed.error.flatten(),
107
+ }),
108
+ exitCode: EXIT.VALIDATION,
109
+ };
110
+ }
111
+ // Make API request
112
+ try {
113
+ const response = await postJson("/lab-notes/upsert", parsed.data, token);
114
+ return {
115
+ envelope: ok(commandName, intent, {
116
+ slug: response.slug,
117
+ action: response.action ?? "created",
118
+ message: `Lab Note ${response.action ?? "created"}: ${response.slug}`,
119
+ }),
120
+ exitCode: EXIT.OK,
121
+ };
122
+ }
123
+ catch (e) {
124
+ if (e instanceof HttpError) {
125
+ if (e.status === 401 || e.status === 403) {
126
+ return {
127
+ envelope: err(commandName, intent, {
128
+ code: "E_AUTH",
129
+ message: "Authentication failed. Check your HPL_TOKEN.",
130
+ }),
131
+ exitCode: EXIT.AUTH,
132
+ };
133
+ }
134
+ const code = e.status && e.status >= 500 ? "E_SERVER" : "E_HTTP";
135
+ return {
136
+ envelope: err(commandName, intent, {
137
+ code,
138
+ message: `API request failed (${e.status ?? "unknown"})`,
139
+ details: e.body ? e.body.slice(0, 500) : undefined,
140
+ }),
141
+ exitCode: e.status && e.status >= 500 ? EXIT.SERVER : EXIT.NETWORK,
142
+ };
143
+ }
144
+ const msg = e instanceof Error ? e.message : String(e);
145
+ return {
146
+ envelope: err(commandName, intent, {
147
+ code: "E_UNKNOWN",
148
+ message: msg,
149
+ }),
150
+ exitCode: EXIT.UNKNOWN,
151
+ };
152
+ }
153
+ }
154
+ /**
155
+ * Commander: `hpl notes create`
156
+ */
157
+ export function notesCreateSubcommand() {
158
+ return new Command("create")
159
+ .description("Create a new Lab Note (contract: create_lab_note)")
160
+ .requiredOption("--title <title>", "Note title")
161
+ .requiredOption("--slug <slug>", "Note slug (unique identifier)")
162
+ .option("--markdown <text>", "Markdown content (inline)")
163
+ .option("--file <path>", "Path to markdown file")
164
+ .option("--locale <code>", "Locale code (default: en)", "en")
165
+ .option("--subtitle <text>", "Note subtitle")
166
+ .option("--summary <text>", "Note summary")
167
+ .option("--tags <tags>", "Comma-separated tags", (val) => val.split(",").map((t) => t.trim()))
168
+ .option("--published <date>", "Publication date (ISO format)")
169
+ .option("--status <status>", "Note status (draft|published|archived)", "draft")
170
+ .option("--type <type>", "Note type (labnote|paper|memo|lore|weather)", "labnote")
171
+ .option("--dept <dept>", "Department code")
172
+ .action(async (opts, cmd) => {
173
+ const mode = getOutputMode(cmd);
174
+ const { envelope, exitCode } = await runNotesCreate(opts, "notes.create");
175
+ if (mode === "json") {
176
+ printJson(envelope);
177
+ }
178
+ else {
179
+ renderText(envelope);
180
+ }
181
+ process.exitCode = exitCode;
182
+ });
183
+ }