@thehumanpatternlab/hpl 0.0.1-alpha.6 → 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,173 +1,182 @@
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
- - `hpl notes list`
119
- - `hpl notes get <slug>`
120
- - `hpl notes sync --content-repo <owner/name|url>`
121
- - `hpl notes sync --dir <path>` (advanced / local development)
122
-
123
- ### health
124
-
125
- ```bash
126
- hpl health
127
- ```
128
-
129
- ### version
130
-
131
- ```bash
132
- hpl version
133
- ```
134
-
135
- ---
136
-
137
- ## JSON Output Contract
138
-
139
- Structured output is treated as a **contract**, not a courtesy.
140
-
141
- When `--json` is provided:
142
-
143
- - stdout contains **only valid JSON**
144
- - stderr is used for logs and diagnostics
145
- - exit codes are deterministic
146
-
147
- A verification step is included:
148
-
149
- ```bash
150
- npm run json:check
151
- ```
152
-
153
- This command fails if any non-JSON output appears on stdout.
154
-
155
- ---
156
-
157
- ## What HPL Is Not
158
-
159
- HPL is not:
160
- - a chatbot interface
161
- - an agent framework
162
- - a memory system
163
- - an inference layer
164
-
165
- It is a command-line tool for interacting with Human Pattern Lab systems in a predictable, human-owned way.
166
-
167
- ---
168
-
169
- **The Human Pattern Lab**
170
- https://thehumanpatternlab.com
171
-
172
- *The lantern is lit.
173
- The foxes are watching.*
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.*
@@ -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
+ }
@@ -22,11 +22,15 @@ import { Command } from "commander";
22
22
  import { notesListSubcommand } from "./list.js";
23
23
  import { notesGetSubcommand } from "./get.js";
24
24
  import { notesSyncSubcommand } from "./notesSync.js";
25
+ import { notesCreateSubcommand } from "./create.js";
26
+ import { notesUpdateSubcommand } from "./update.js";
25
27
  export function notesCommand() {
26
28
  const notes = new Command("notes").description("Lab Notes commands");
27
29
  // Subcommands
28
30
  notes.addCommand(notesListSubcommand());
29
31
  notes.addCommand(notesGetSubcommand());
32
+ notes.addCommand(notesCreateSubcommand());
33
+ notes.addCommand(notesUpdateSubcommand());
30
34
  notes.addCommand(notesSyncSubcommand());
31
35
  return notes;
32
36
  }
@@ -0,0 +1,217 @@
1
+ /* ===========================================================
2
+ 🦊 THE HUMAN PATTERN LAB — HPL CLI
3
+ -----------------------------------------------------------
4
+ File: update.ts
5
+ Role: Notes subcommand: `hpl notes update`
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
+ Update an existing 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
+ - Uses upsert endpoint (same as create)
20
+ =========================================================== */
21
+ import fs from "node:fs";
22
+ import { Command } from "commander";
23
+ import { getOutputMode, printJson } from "../../cli/output.js";
24
+ import { renderText } from "../../render/text.js";
25
+ import { LabNoteUpsertSchema } from "../../types/labNotes.js";
26
+ import { HPL_TOKEN } from "../../lib/config.js";
27
+ import { getAlphaIntent } from "../../contract/intents.js";
28
+ import { ok, err } from "../../contract/envelope.js";
29
+ import { EXIT } from "../../contract/exitCodes.js";
30
+ import { postJson, HttpError } from "../../http/client.js";
31
+ /**
32
+ * Core: update an existing Lab Note.
33
+ * Returns structured envelope + exitCode (no printing here).
34
+ */
35
+ export async function runNotesUpdate(options, commandName = "notes.update") {
36
+ const intent = getAlphaIntent("update_lab_note");
37
+ // Authentication check
38
+ const token = HPL_TOKEN();
39
+ if (!token) {
40
+ return {
41
+ envelope: err(commandName, intent, {
42
+ code: "E_AUTH",
43
+ message: "Authentication required. Set HPL_TOKEN environment variable or configure token in ~/.humanpatternlab/hpl.json",
44
+ }),
45
+ exitCode: EXIT.AUTH,
46
+ };
47
+ }
48
+ // Get markdown content if provided
49
+ let markdown;
50
+ if (options.file) {
51
+ if (!fs.existsSync(options.file)) {
52
+ return {
53
+ envelope: err(commandName, intent, {
54
+ code: "E_NOT_FOUND",
55
+ message: `File not found: ${options.file}`,
56
+ }),
57
+ exitCode: EXIT.NOT_FOUND,
58
+ };
59
+ }
60
+ try {
61
+ markdown = fs.readFileSync(options.file, "utf-8");
62
+ }
63
+ catch (e) {
64
+ const msg = e instanceof Error ? e.message : String(e);
65
+ return {
66
+ envelope: err(commandName, intent, {
67
+ code: "E_IO",
68
+ message: `Failed to read file: ${msg}`,
69
+ }),
70
+ exitCode: EXIT.IO,
71
+ };
72
+ }
73
+ }
74
+ else if (options.markdown) {
75
+ markdown = options.markdown;
76
+ }
77
+ // For updates, we need at least title OR markdown
78
+ if (!options.title && !markdown) {
79
+ return {
80
+ envelope: err(commandName, intent, {
81
+ code: "E_VALIDATION",
82
+ message: "Must provide at least --title or --markdown/--file for update",
83
+ }),
84
+ exitCode: EXIT.VALIDATION,
85
+ };
86
+ }
87
+ // Build payload - use required fields from what's provided
88
+ // The API will handle partial updates if it supports them,
89
+ // or we provide what we have
90
+ const payload = {
91
+ slug: options.slug,
92
+ };
93
+ if (options.title)
94
+ payload.title = options.title;
95
+ if (markdown)
96
+ payload.markdown = markdown;
97
+ if (options.locale)
98
+ payload.locale = options.locale;
99
+ if (options.subtitle)
100
+ payload.subtitle = options.subtitle;
101
+ if (options.summary)
102
+ payload.summary = options.summary;
103
+ if (options.tags)
104
+ payload.tags = options.tags;
105
+ if (options.published)
106
+ payload.published = options.published;
107
+ if (options.status)
108
+ payload.status = options.status;
109
+ if (options.type)
110
+ payload.type = options.type;
111
+ if (options.dept)
112
+ payload.dept = options.dept;
113
+ // The upsert endpoint requires title and markdown
114
+ // For an update operation, if these aren't provided, we should fetch the existing note first
115
+ if (!payload.title || !payload.markdown) {
116
+ return {
117
+ envelope: err(commandName, intent, {
118
+ code: "E_VALIDATION",
119
+ message: "Update requires both --title and (--markdown or --file). For partial updates, use the API directly or fetch the existing note first.",
120
+ }),
121
+ exitCode: EXIT.VALIDATION,
122
+ };
123
+ }
124
+ // Validate payload
125
+ const parsed = LabNoteUpsertSchema.safeParse(payload);
126
+ if (!parsed.success) {
127
+ return {
128
+ envelope: err(commandName, intent, {
129
+ code: "E_VALIDATION",
130
+ message: "Invalid note data",
131
+ details: parsed.error.flatten(),
132
+ }),
133
+ exitCode: EXIT.VALIDATION,
134
+ };
135
+ }
136
+ // Make API request
137
+ try {
138
+ const response = await postJson("/lab-notes/upsert", parsed.data, token);
139
+ return {
140
+ envelope: ok(commandName, intent, {
141
+ slug: response.slug,
142
+ action: response.action ?? "updated",
143
+ message: `Lab Note ${response.action ?? "updated"}: ${response.slug}`,
144
+ }),
145
+ exitCode: EXIT.OK,
146
+ };
147
+ }
148
+ catch (e) {
149
+ if (e instanceof HttpError) {
150
+ if (e.status === 401 || e.status === 403) {
151
+ return {
152
+ envelope: err(commandName, intent, {
153
+ code: "E_AUTH",
154
+ message: "Authentication failed. Check your HPL_TOKEN.",
155
+ }),
156
+ exitCode: EXIT.AUTH,
157
+ };
158
+ }
159
+ if (e.status === 404) {
160
+ return {
161
+ envelope: err(commandName, intent, {
162
+ code: "E_NOT_FOUND",
163
+ message: `No lab note found for slug: ${options.slug}`,
164
+ }),
165
+ exitCode: EXIT.NOT_FOUND,
166
+ };
167
+ }
168
+ const code = e.status && e.status >= 500 ? "E_SERVER" : "E_HTTP";
169
+ return {
170
+ envelope: err(commandName, intent, {
171
+ code,
172
+ message: `API request failed (${e.status ?? "unknown"})`,
173
+ details: e.body ? e.body.slice(0, 500) : undefined,
174
+ }),
175
+ exitCode: e.status && e.status >= 500 ? EXIT.SERVER : EXIT.NETWORK,
176
+ };
177
+ }
178
+ const msg = e instanceof Error ? e.message : String(e);
179
+ return {
180
+ envelope: err(commandName, intent, {
181
+ code: "E_UNKNOWN",
182
+ message: msg,
183
+ }),
184
+ exitCode: EXIT.UNKNOWN,
185
+ };
186
+ }
187
+ }
188
+ /**
189
+ * Commander: `hpl notes update`
190
+ */
191
+ export function notesUpdateSubcommand() {
192
+ return new Command("update")
193
+ .description("Update an existing Lab Note (contract: update_lab_note)")
194
+ .argument("<slug>", "Note slug to update")
195
+ .option("--title <title>", "Note title")
196
+ .option("--markdown <text>", "Markdown content (inline)")
197
+ .option("--file <path>", "Path to markdown file")
198
+ .option("--locale <code>", "Locale code")
199
+ .option("--subtitle <text>", "Note subtitle")
200
+ .option("--summary <text>", "Note summary")
201
+ .option("--tags <tags>", "Comma-separated tags", (val) => val.split(",").map((t) => t.trim()))
202
+ .option("--published <date>", "Publication date (ISO format)")
203
+ .option("--status <status>", "Note status (draft|published|archived)")
204
+ .option("--type <type>", "Note type (labnote|paper|memo|lore|weather)")
205
+ .option("--dept <dept>", "Department code")
206
+ .action(async (slug, opts, cmd) => {
207
+ const mode = getOutputMode(cmd);
208
+ const { envelope, exitCode } = await runNotesUpdate({ ...opts, slug }, "notes.update");
209
+ if (mode === "json") {
210
+ printJson(envelope);
211
+ }
212
+ else {
213
+ renderText(envelope);
214
+ }
215
+ process.exitCode = exitCode;
216
+ });
217
+ }
@@ -14,6 +14,8 @@ export const EXIT = {
14
14
  NOT_FOUND: 3, // 404 semantics
15
15
  AUTH: 4, // auth required / invalid token
16
16
  FORBIDDEN: 5, // insufficient scope/permission
17
+ VALIDATION: 6, // data validation failed
18
+ IO: 7, // file I/O errors
17
19
  NETWORK: 10, // DNS/timeout/unreachable
18
20
  SERVER: 11, // 5xx or unexpected response
19
21
  CONTRACT: 12, // schema mismatch / invalid JSON contract
@@ -40,6 +40,20 @@ export const INTENTS_ALPHA = {
40
40
  sideEffects: [],
41
41
  reversible: true,
42
42
  },
43
+ create_lab_note: {
44
+ intent: "create_lab_note",
45
+ intentVersion: "1",
46
+ scope: ["lab_notes", "remote_api"],
47
+ sideEffects: ["write_remote"],
48
+ reversible: false,
49
+ },
50
+ update_lab_note: {
51
+ intent: "update_lab_note",
52
+ intentVersion: "1",
53
+ scope: ["lab_notes", "remote_api"],
54
+ sideEffects: ["write_remote"],
55
+ reversible: false,
56
+ },
43
57
  };
44
58
  export function getAlphaIntent(id) {
45
59
  return INTENTS_ALPHA[id];
@@ -37,3 +37,29 @@ export async function getJson(path, signal) {
37
37
  const payload = (await res.json());
38
38
  return unwrap(payload);
39
39
  }
40
+ export async function postJson(path, body, token, signal) {
41
+ const { apiBaseUrl } = getConfig();
42
+ const url = apiBaseUrl + path;
43
+ const headers = {
44
+ "Content-Type": "application/json",
45
+ };
46
+ if (token) {
47
+ headers["Authorization"] = `Bearer ${token}`;
48
+ }
49
+ const res = await fetch(url, {
50
+ method: "POST",
51
+ headers,
52
+ body: JSON.stringify(body),
53
+ signal,
54
+ });
55
+ if (!res.ok) {
56
+ let responseBody = "";
57
+ try {
58
+ responseBody = await res.text();
59
+ }
60
+ catch { /* ignore */ }
61
+ throw new HttpError(`POST ${path} failed`, res.status, responseBody);
62
+ }
63
+ const payload = (await res.json());
64
+ return unwrap(payload);
65
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thehumanpatternlab/hpl",
3
- "version": "0.0.1-alpha.6",
3
+ "version": "1.0.0",
4
4
  "description": "AI-forward, automation-safe SDK and CLI for the Human Pattern Lab",
5
5
  "type": "module",
6
6
  "license": "MIT",