@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 +182 -42
- package/dist/bin/hpl.js +20 -145
- package/dist/src/commands/capabilities.js +24 -0
- package/dist/src/commands/health.js +26 -0
- package/dist/src/commands/notes/create.js +183 -0
- package/dist/src/commands/notes/get.js +52 -12
- package/dist/src/commands/notes/list.js +68 -12
- package/dist/src/commands/notes/notes.js +36 -0
- package/dist/src/commands/notes/notesSync.js +221 -0
- package/dist/src/commands/notes/update.js +217 -0
- package/dist/src/commands/notesSync.js +1 -1
- package/dist/src/commands/version.js +17 -0
- package/dist/src/contract/exitCodes.js +2 -0
- package/dist/src/contract/intents.js +14 -0
- package/dist/src/http/client.js +26 -0
- package/dist/src/index.js +8 -7
- package/dist/src/lib/config.js +8 -8
- package/dist/src/lib/contentRepo.js +49 -0
- package/dist/src/render/table.js +7 -0
- package/dist/src/render/text.js +68 -10
- package/dist/src/types/labNotes.js +96 -12
- package/package.json +3 -1
|
@@ -1,11 +1,28 @@
|
|
|
1
1
|
/* ===========================================================
|
|
2
2
|
🌌 HUMAN PATTERN LAB — COMMAND: version
|
|
3
3
|
=========================================================== */
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import { writeHuman, writeJson } from "../io.js";
|
|
6
|
+
import { EXIT } from "../contract/exitCodes.js";
|
|
4
7
|
import { createRequire } from "node:module";
|
|
5
8
|
import { getAlphaIntent } from "../contract/intents";
|
|
6
9
|
import { ok } from "../contract/envelope";
|
|
7
10
|
const require = createRequire(import.meta.url);
|
|
8
11
|
const pkg = require("../../package.json");
|
|
12
|
+
export function versionCommand() {
|
|
13
|
+
return new Command("version")
|
|
14
|
+
.description("Show CLI version (contract: show_version)")
|
|
15
|
+
.action((...args) => {
|
|
16
|
+
const cmd = args[args.length - 1];
|
|
17
|
+
const rootOpts = (cmd.parent?.opts?.() ?? {});
|
|
18
|
+
const envelope = runVersion("version");
|
|
19
|
+
if (rootOpts.json)
|
|
20
|
+
writeJson(envelope);
|
|
21
|
+
else
|
|
22
|
+
writeHuman(`${envelope.data?.name} ${envelope.data?.version}`.trim());
|
|
23
|
+
process.exitCode = EXIT.OK;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
9
26
|
export function runVersion(commandName = "version") {
|
|
10
27
|
const intent = getAlphaIntent("show_version");
|
|
11
28
|
return ok(commandName, intent, { name: pkg.name, version: pkg.version });
|
|
@@ -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];
|
package/dist/src/http/client.js
CHANGED
|
@@ -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/dist/src/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/* ===========================================================
|
|
3
|
-
🦊 THE HUMAN PATTERN LAB —
|
|
3
|
+
🦊 THE HUMAN PATTERN LAB — HPL CLI
|
|
4
4
|
-----------------------------------------------------------
|
|
5
5
|
File: notesSync.ts
|
|
6
6
|
Role: Command Implementation
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
Assistant: Lyric
|
|
9
9
|
Status: Active
|
|
10
10
|
Description:
|
|
11
|
-
Implements the `
|
|
11
|
+
Implements the `hpl notes sync` command.
|
|
12
12
|
Handles human-readable and machine-readable output modes
|
|
13
13
|
with enforced JSON purity for automation safety.
|
|
14
14
|
-----------------------------------------------------------
|
|
@@ -19,18 +19,19 @@
|
|
|
19
19
|
- Exit codes are deterministic
|
|
20
20
|
=========================================================== */
|
|
21
21
|
import { Command } from "commander";
|
|
22
|
-
import {
|
|
22
|
+
import { notesSyncSubcommand } from "./commands/notes/notesSync.js";
|
|
23
23
|
const program = new Command();
|
|
24
24
|
program
|
|
25
|
-
.name("
|
|
26
|
-
.description("
|
|
25
|
+
.name("hpl")
|
|
26
|
+
.description("Human Pattern Lab CLI (alpha)")
|
|
27
27
|
.version("0.1.0")
|
|
28
|
-
.option("--json", "
|
|
28
|
+
.option("--json", "Emit contract JSON only on stdout")
|
|
29
29
|
.configureHelp({ helpWidth: 100 });
|
|
30
30
|
const argv = process.argv.slice(2);
|
|
31
31
|
if (argv.length === 0) {
|
|
32
32
|
program.outputHelp();
|
|
33
33
|
process.exit(0);
|
|
34
34
|
}
|
|
35
|
-
|
|
35
|
+
// Mount domains
|
|
36
|
+
program.addCommand(notesSyncSubcommand());
|
|
36
37
|
program.parse(process.argv);
|
package/dist/src/lib/config.js
CHANGED
|
@@ -3,15 +3,15 @@ import path from 'node:path';
|
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
* Stored in ~/.humanpatternlab/
|
|
6
|
+
* HPL CLI configuration schema
|
|
7
|
+
* Stored in ~/.humanpatternlab/hpl.json
|
|
8
8
|
*/
|
|
9
9
|
const ConfigSchema = z.object({
|
|
10
|
-
apiBaseUrl: z.string().url().default('https://thehumanpatternlab.com
|
|
10
|
+
apiBaseUrl: z.string().url().default('https://api.thehumanpatternlab.com'),
|
|
11
11
|
token: z.string().optional(),
|
|
12
12
|
});
|
|
13
13
|
function getConfigPath() {
|
|
14
|
-
return path.join(os.homedir(), '.humanpatternlab', '
|
|
14
|
+
return path.join(os.homedir(), '.humanpatternlab', 'hpl.json');
|
|
15
15
|
}
|
|
16
16
|
export function loadConfig() {
|
|
17
17
|
const p = getConfigPath();
|
|
@@ -28,11 +28,11 @@ export function saveConfig(partial) {
|
|
|
28
28
|
const next = ConfigSchema.parse({ ...current, ...partial });
|
|
29
29
|
fs.writeFileSync(p, JSON.stringify(next, null, 2), 'utf-8');
|
|
30
30
|
}
|
|
31
|
-
export function
|
|
31
|
+
export function HPL_BASE_URL(override) {
|
|
32
32
|
if (override?.trim())
|
|
33
33
|
return override.trim();
|
|
34
34
|
// NEW official env var
|
|
35
|
-
const env = process.env.
|
|
35
|
+
const env = process.env.HPL_BASE_URL?.trim();
|
|
36
36
|
if (env)
|
|
37
37
|
return env;
|
|
38
38
|
// optional legacy support (remove later if you want)
|
|
@@ -41,8 +41,8 @@ export function SKULK_BASE_URL(override) {
|
|
|
41
41
|
return legacy;
|
|
42
42
|
return loadConfig().apiBaseUrl;
|
|
43
43
|
}
|
|
44
|
-
export function
|
|
45
|
-
const env = process.env.
|
|
44
|
+
export function HPL_TOKEN() {
|
|
45
|
+
const env = process.env.HPL_TOKEN?.trim();
|
|
46
46
|
if (env)
|
|
47
47
|
return env;
|
|
48
48
|
const legacy = process.env.HPL_TOKEN?.trim();
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { execa } from "execa";
|
|
5
|
+
function normalizeRepoUrl(repo) {
|
|
6
|
+
const raw = repo.trim();
|
|
7
|
+
if (raw.startsWith("http://") || raw.startsWith("https://") || raw.startsWith("git@"))
|
|
8
|
+
return raw;
|
|
9
|
+
return `https://github.com/${raw}.git`;
|
|
10
|
+
}
|
|
11
|
+
function safeRepoKey(repo) {
|
|
12
|
+
// stable-ish folder name for owner/name or URL
|
|
13
|
+
return repo.trim().replace(/[^a-z0-9._-]+/gi, "_").toLowerCase();
|
|
14
|
+
}
|
|
15
|
+
function ensureDir(p) {
|
|
16
|
+
fs.mkdirSync(p, { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
async function runGit(args, opts = {}) {
|
|
19
|
+
const p = execa("git", args, {
|
|
20
|
+
cwd: opts.cwd,
|
|
21
|
+
// Avoid stdout pollution when in --json mode (or any strict mode).
|
|
22
|
+
stdio: opts.quietStdout ? ["ignore", "pipe", "pipe"] : "inherit",
|
|
23
|
+
});
|
|
24
|
+
if (opts.quietStdout) {
|
|
25
|
+
p.stdout?.on("data", (d) => process.stderr.write(d));
|
|
26
|
+
p.stderr?.on("data", (d) => process.stderr.write(d));
|
|
27
|
+
}
|
|
28
|
+
return await p;
|
|
29
|
+
}
|
|
30
|
+
export async function resolveContentRepo({ repo, ref = "main", cacheDir, quietStdout = false, }) {
|
|
31
|
+
const repoUrl = normalizeRepoUrl(repo);
|
|
32
|
+
const base = cacheDir ?? path.join(os.homedir(), ".hpl", "cache", "content");
|
|
33
|
+
const dir = path.join(base, safeRepoKey(repo));
|
|
34
|
+
ensureDir(base);
|
|
35
|
+
const gitDir = path.join(dir, ".git");
|
|
36
|
+
const exists = fs.existsSync(gitDir);
|
|
37
|
+
if (!exists) {
|
|
38
|
+
ensureDir(dir);
|
|
39
|
+
// Clone shallow if branch-like ref; if ref is a sha, shallow clone won’t help much.
|
|
40
|
+
await runGit(["clone", "--depth", "1", "--branch", ref, repoUrl, dir], { quietStdout });
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
// Keep it predictable: fetch + checkout + ff-only pull.
|
|
44
|
+
await runGit(["-C", dir, "fetch", "--all", "--tags", "--prune"], { quietStdout });
|
|
45
|
+
await runGit(["-C", dir, "checkout", ref], { quietStdout });
|
|
46
|
+
await runGit(["-C", dir, "pull", "--ff-only"], { quietStdout });
|
|
47
|
+
}
|
|
48
|
+
return { dir, repoUrl, ref };
|
|
49
|
+
}
|
package/dist/src/render/table.js
CHANGED
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
-----------------------------------------------------------
|
|
4
4
|
Purpose: Deterministic fixed-width table output.
|
|
5
5
|
=========================================================== */
|
|
6
|
+
export function safeLine(s) {
|
|
7
|
+
return (s ?? "").replace(/\s+/g, " ").trim();
|
|
8
|
+
}
|
|
9
|
+
export function formatTags(tags) {
|
|
10
|
+
const t = (tags ?? []).filter(Boolean);
|
|
11
|
+
return t.length ? t.join(", ") : "-";
|
|
12
|
+
}
|
|
6
13
|
function pad(s, width) {
|
|
7
14
|
const str = (s ?? "").toString();
|
|
8
15
|
if (str.length >= width)
|
package/dist/src/render/text.js
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
🌌 HUMAN PATTERN LAB — TEXT RENDER UTILS
|
|
3
|
-
-----------------------------------------------------------
|
|
4
|
-
Purpose: Deterministic, dependency-free formatting for terminals.
|
|
5
|
-
=========================================================== */
|
|
1
|
+
import { formatTags, safeLine } from "./table";
|
|
6
2
|
export function stripHtml(input) {
|
|
7
3
|
const s = (input || "");
|
|
8
4
|
// Convert common structure to deterministic newlines first
|
|
@@ -31,10 +27,72 @@ export function stripHtml(input) {
|
|
|
31
27
|
.replace(/\n{3,}/g, "\n\n")
|
|
32
28
|
.trim();
|
|
33
29
|
}
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
function renderError(env) {
|
|
31
|
+
console.error(`✖ ${env.command}`);
|
|
32
|
+
if (env.error?.message) {
|
|
33
|
+
console.error(safeLine(env.error.message));
|
|
34
|
+
}
|
|
35
|
+
if (env.error?.details) {
|
|
36
|
+
console.error();
|
|
37
|
+
console.error(stripHtml(String(env.error.details)));
|
|
38
|
+
}
|
|
36
39
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
function renderWarn(env) {
|
|
41
|
+
console.log(`⚠ ${env.command}`);
|
|
42
|
+
for (const w of env.warnings ?? []) {
|
|
43
|
+
console.log(`- ${safeLine(w)}`);
|
|
44
|
+
}
|
|
45
|
+
if (env.data !== undefined) {
|
|
46
|
+
console.log();
|
|
47
|
+
renderData(env.data);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function renderSuccess(env) {
|
|
51
|
+
renderData(env.data);
|
|
52
|
+
}
|
|
53
|
+
function renderData(data) {
|
|
54
|
+
if (Array.isArray(data)) {
|
|
55
|
+
for (const item of data) {
|
|
56
|
+
renderItem(item);
|
|
57
|
+
console.log();
|
|
58
|
+
}
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (typeof data === "object" && data !== null) {
|
|
62
|
+
renderItem(data);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
console.log(String(data));
|
|
66
|
+
}
|
|
67
|
+
function renderItem(note) {
|
|
68
|
+
if (note.title) {
|
|
69
|
+
console.log(safeLine(note.title));
|
|
70
|
+
}
|
|
71
|
+
if (note.subtitle) {
|
|
72
|
+
console.log(` ${safeLine(note.subtitle)}`);
|
|
73
|
+
}
|
|
74
|
+
if (note.summary || note.excerpt) {
|
|
75
|
+
console.log();
|
|
76
|
+
console.log(stripHtml(note.summary ?? note.excerpt));
|
|
77
|
+
}
|
|
78
|
+
if (note.tags) {
|
|
79
|
+
console.log();
|
|
80
|
+
console.log(`Tags: ${formatTags(note.tags)}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export function renderText(envelope) {
|
|
84
|
+
switch (envelope.status) {
|
|
85
|
+
case "error":
|
|
86
|
+
renderError(envelope);
|
|
87
|
+
return;
|
|
88
|
+
case "warn":
|
|
89
|
+
renderWarn(envelope);
|
|
90
|
+
return;
|
|
91
|
+
case "ok":
|
|
92
|
+
renderSuccess(envelope);
|
|
93
|
+
return;
|
|
94
|
+
default:
|
|
95
|
+
// Exhaustiveness guard
|
|
96
|
+
console.error("Unknown envelope status");
|
|
97
|
+
}
|
|
40
98
|
}
|
|
@@ -5,23 +5,107 @@
|
|
|
5
5
|
Notes:
|
|
6
6
|
- Keep permissive: API may add fields (additive).
|
|
7
7
|
=========================================================== */
|
|
8
|
+
// - GET /lab-notes -> LabNotePreview[]
|
|
9
|
+
// - GET /lab-notes/:slug -> LabNoteDetail (LabNoteView + content_markdown)
|
|
8
10
|
import { z } from "zod";
|
|
9
|
-
|
|
11
|
+
/** Mirrors API LabNoteType */
|
|
12
|
+
export const LabNoteTypeSchema = z.enum(["labnote", "paper", "memo", "lore", "weather"]);
|
|
13
|
+
/** Mirrors API LabNoteStatus */
|
|
14
|
+
export const LabNoteStatusSchema = z.enum(["published", "draft", "archived"]);
|
|
15
|
+
export const ALLOWED_NOTE_TYPES = new Set([
|
|
16
|
+
"labnote",
|
|
17
|
+
"paper",
|
|
18
|
+
"memo",
|
|
19
|
+
"lore",
|
|
20
|
+
"weather",
|
|
21
|
+
]);
|
|
22
|
+
/**
|
|
23
|
+
* GET /lab-notes (list)
|
|
24
|
+
* You are selecting from v_lab_notes without content_html/markdown,
|
|
25
|
+
* then mapping via mapToLabNotePreview(...).
|
|
26
|
+
*
|
|
27
|
+
* We infer likely fields from the SELECT + typical preview mapper.
|
|
28
|
+
* Keep passthrough to allow additive changes.
|
|
29
|
+
*/
|
|
30
|
+
export const LabNotePreviewSchema = z
|
|
31
|
+
.object({
|
|
10
32
|
id: z.string(),
|
|
11
33
|
slug: z.string(),
|
|
12
34
|
title: z.string(),
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
status:
|
|
17
|
-
type:
|
|
35
|
+
subtitle: z.string().optional(),
|
|
36
|
+
summary: z.string().optional(),
|
|
37
|
+
excerpt: z.string().optional(),
|
|
38
|
+
status: LabNoteStatusSchema.optional(),
|
|
39
|
+
type: LabNoteTypeSchema.optional(),
|
|
40
|
+
dept: z.string().optional(),
|
|
18
41
|
locale: z.string().optional(),
|
|
19
|
-
department_id: z.string().optional(),
|
|
42
|
+
department_id: z.string().optional(), // DB has it; mapper may include it
|
|
20
43
|
shadow_density: z.number().optional(),
|
|
21
|
-
safer_landing: z.boolean().optional(),
|
|
22
|
-
|
|
23
|
-
|
|
44
|
+
safer_landing: z.boolean().optional(), // DB is number-ish; mapper likely coerces
|
|
45
|
+
readingTime: z.number().optional(), // from read_time_minutes
|
|
46
|
+
published: z.string().optional(), // from published_at (if mapper emits it)
|
|
24
47
|
created_at: z.string().optional(),
|
|
25
48
|
updated_at: z.string().optional(),
|
|
26
|
-
|
|
27
|
-
|
|
49
|
+
tags: z.array(z.string()).optional(), // mapper adds tags
|
|
50
|
+
})
|
|
51
|
+
.passthrough();
|
|
52
|
+
export const LabNotePreviewListSchema = z.array(LabNotePreviewSchema);
|
|
53
|
+
/**
|
|
54
|
+
* API LabNoteView shape (detail rendering fields).
|
|
55
|
+
* This aligns to your LabNoteView interface.
|
|
56
|
+
*/
|
|
57
|
+
export const LabNoteViewSchema = z
|
|
58
|
+
.object({
|
|
59
|
+
id: z.string(),
|
|
60
|
+
slug: z.string(),
|
|
61
|
+
title: z.string(),
|
|
62
|
+
subtitle: z.string().optional(),
|
|
63
|
+
summary: z.string().optional(),
|
|
64
|
+
// NOTE: contentHtml intentionally excluded.
|
|
65
|
+
// Markdown is the canonical source of truth for CLI clients.
|
|
66
|
+
published: z.string(),
|
|
67
|
+
status: LabNoteStatusSchema.optional(),
|
|
68
|
+
type: LabNoteTypeSchema.optional(),
|
|
69
|
+
dept: z.string().optional(),
|
|
70
|
+
locale: z.string().optional(),
|
|
71
|
+
author: z
|
|
72
|
+
.object({
|
|
73
|
+
kind: z.enum(["human", "ai", "hybrid"]),
|
|
74
|
+
name: z.string().optional(),
|
|
75
|
+
id: z.string().optional(),
|
|
76
|
+
})
|
|
77
|
+
.optional(),
|
|
78
|
+
department_id: z.string(),
|
|
79
|
+
shadow_density: z.number(),
|
|
80
|
+
safer_landing: z.boolean(),
|
|
81
|
+
tags: z.array(z.string()),
|
|
82
|
+
readingTime: z.number(),
|
|
83
|
+
created_at: z.string().optional(),
|
|
84
|
+
updated_at: z.string().optional(),
|
|
85
|
+
})
|
|
86
|
+
.passthrough();
|
|
87
|
+
/**
|
|
88
|
+
* GET /lab-notes/:slug returns LabNoteView + content_markdown (canonical truth).
|
|
89
|
+
*/
|
|
90
|
+
export const LabNoteDetailSchema = LabNoteViewSchema.extend({
|
|
91
|
+
content_markdown: z.string().optional(), // API always includes it in your code, but keep optional for safety
|
|
92
|
+
});
|
|
93
|
+
/**
|
|
94
|
+
* CLI → API payload for upsert (notes sync).
|
|
95
|
+
* Strict: our outbound contract.
|
|
96
|
+
*/
|
|
97
|
+
export const LabNoteUpsertSchema = z
|
|
98
|
+
.object({
|
|
99
|
+
slug: z.string().min(1),
|
|
100
|
+
title: z.string().min(1),
|
|
101
|
+
markdown: z.string().min(1),
|
|
102
|
+
locale: z.string().optional(),
|
|
103
|
+
subtitle: z.string().optional(),
|
|
104
|
+
summary: z.string().optional(),
|
|
105
|
+
tags: z.array(z.string()).optional(),
|
|
106
|
+
published: z.string().optional(),
|
|
107
|
+
status: LabNoteStatusSchema.optional(),
|
|
108
|
+
type: LabNoteTypeSchema.optional(),
|
|
109
|
+
dept: z.string().optional(),
|
|
110
|
+
})
|
|
111
|
+
.strict();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thehumanpatternlab/hpl",
|
|
3
|
-
"version": "0.0
|
|
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",
|
|
@@ -37,10 +37,12 @@
|
|
|
37
37
|
"start": "node ./dist/bin/hpl.js",
|
|
38
38
|
"test": "vitest run",
|
|
39
39
|
"test:watch": "vitest",
|
|
40
|
+
"json:check": "tsx ./bin/hpl.ts --json version | node -e \"JSON.parse(require('fs').readFileSync(0,'utf8'))\"",
|
|
40
41
|
"lint": "node -e \"console.log('lint: add eslint when ready')\""
|
|
41
42
|
},
|
|
42
43
|
"dependencies": {
|
|
43
44
|
"commander": "^12.1.0",
|
|
45
|
+
"execa": "^9.6.1",
|
|
44
46
|
"gray-matter": "^4.0.3",
|
|
45
47
|
"zod": "^3.24.1"
|
|
46
48
|
},
|