@octonote/cli 0.0.0-stage → 0.1.4

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.
@@ -0,0 +1,119 @@
1
+ /**
2
+ * `octonote login`: open the browser on <web>/cli/connect, where you click
3
+ * Authorize; the page hands the new token to this process over a one-shot
4
+ * server on 127.0.0.1. The server only accepts a POST from the Octonote
5
+ * site carrying the random `state` we generated, then shuts down. If the
6
+ * browser can't reach it, the page shows the token to paste here instead.
7
+ */
8
+ import { spawn } from "node:child_process";
9
+ import { randomBytes } from "node:crypto";
10
+ import { createServer, } from "node:http";
11
+ import { hostname } from "node:os";
12
+ import { createInterface } from "node:readline";
13
+ import { CliError } from "./errors.js";
14
+ export const TOKEN_RE = /^ont_[A-Za-z0-9_-]{20,}$/;
15
+ export function openBrowser(url) {
16
+ const [cmd, args] = process.platform === "darwin"
17
+ ? ["open", [url]]
18
+ : process.platform === "win32"
19
+ ? ["cmd", ["/c", "start", "", url]]
20
+ : ["xdg-open", [url]];
21
+ try {
22
+ const child = spawn(cmd, args, {
23
+ stdio: "ignore",
24
+ detached: true,
25
+ });
26
+ child.on("error", () => undefined);
27
+ child.unref();
28
+ return true;
29
+ }
30
+ catch {
31
+ return false;
32
+ }
33
+ }
34
+ export async function browserLogin(opts) {
35
+ const state = randomBytes(24).toString("base64url");
36
+ const origin = new URL(opts.webUrl).origin;
37
+ let settle;
38
+ let fail;
39
+ const result = new Promise((res, rej) => {
40
+ settle = res;
41
+ fail = rej;
42
+ });
43
+ const server = createServer((req, res) => handle(req, res));
44
+ const cors = (res) => {
45
+ res.setHeader("access-control-allow-origin", origin);
46
+ res.setHeader("access-control-allow-methods", "POST, OPTIONS");
47
+ res.setHeader("access-control-allow-headers", "content-type");
48
+ // Chrome's Private Network Access: a public site may call localhost only if we say so.
49
+ res.setHeader("access-control-allow-private-network", "true");
50
+ res.setHeader("cache-control", "no-store");
51
+ };
52
+ function handle(req, res) {
53
+ cors(res);
54
+ const url = new URL(req.url ?? "/", "http://127.0.0.1");
55
+ if (url.pathname !== "/cb")
56
+ return void res.writeHead(404).end();
57
+ if (req.method === "OPTIONS")
58
+ return void res.writeHead(204).end();
59
+ if (req.method !== "POST" ||
60
+ (req.headers.origin && req.headers.origin !== origin)) {
61
+ return void res.writeHead(403).end();
62
+ }
63
+ let body = "";
64
+ req.on("data", (c) => {
65
+ body += c;
66
+ if (body.length > 10_000)
67
+ req.destroy();
68
+ });
69
+ req.on("end", () => {
70
+ let data = {};
71
+ try {
72
+ data = JSON.parse(body);
73
+ }
74
+ catch {
75
+ // Fall through to the checks below.
76
+ }
77
+ if (data.state !== state || !data.token || !TOKEN_RE.test(data.token)) {
78
+ return void res.writeHead(400).end();
79
+ }
80
+ res
81
+ .writeHead(200, { "content-type": "application/json" })
82
+ .end('{"ok":true}');
83
+ settle({ token: data.token, email: data.email || null });
84
+ });
85
+ }
86
+ await new Promise((res, rej) => {
87
+ server.once("error", rej);
88
+ server.listen(0, "127.0.0.1", () => res());
89
+ });
90
+ const port = server.address().port;
91
+ const url = `${opts.webUrl}/cli/connect?${new URLSearchParams({
92
+ cb: `http://127.0.0.1:${port}/cb`,
93
+ state,
94
+ name: hostname().split(".")[0] ?? "",
95
+ }).toString()}`;
96
+ opts.onUrl(url);
97
+ openBrowser(url);
98
+ // Paste fallback: a person can paste the token the page shows.
99
+ let rl = null;
100
+ if (process.stdin.isTTY) {
101
+ rl = createInterface({ input: process.stdin });
102
+ rl.on("line", (line) => {
103
+ const token = line.trim();
104
+ if (TOKEN_RE.test(token))
105
+ settle({ token, email: null });
106
+ });
107
+ }
108
+ const timer = setTimeout(() => fail(new CliError("Timed out waiting for the browser.", "LOGIN_TIMEOUT", {
109
+ hint: "Run `octonote login` again.",
110
+ })), opts.timeoutMs ?? 5 * 60_000);
111
+ try {
112
+ return await result;
113
+ }
114
+ finally {
115
+ clearTimeout(timer);
116
+ rl?.close();
117
+ server.close();
118
+ }
119
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Output rules (what agents rely on):
3
+ * - stdout carries only the result; progress and hints go to stderr.
4
+ * - JSON when stdout isn't a terminal (or with --json); readable text for
5
+ * people in a terminal (or with --pretty).
6
+ * - Errors: JSON `{ "error": { code, message, hint } }` on stdout when in
7
+ * JSON mode, else a readable line on stderr; non-zero exit code always.
8
+ */
9
+ import { CliError } from "./errors.js";
10
+ let mode = process.stdout.isTTY ? "pretty" : "json";
11
+ let quiet = false;
12
+ export function setOutputMode(next) {
13
+ if (next.json)
14
+ mode = "json";
15
+ else if (next.pretty)
16
+ mode = "pretty";
17
+ quiet = Boolean(next.quiet);
18
+ }
19
+ export const outputMode = () => mode;
20
+ /** Print a result: JSON as-is, or `pretty(data)` for people. */
21
+ export function emit(data, pretty) {
22
+ if (mode === "json") {
23
+ process.stdout.write(`${JSON.stringify(data, null, 2)}\n`);
24
+ return;
25
+ }
26
+ const text = pretty ? pretty(data) : JSON.stringify(data, null, 2);
27
+ if (text)
28
+ process.stdout.write(text.endsWith("\n") ? text : `${text}\n`);
29
+ }
30
+ /** Progress / notes for people, on stderr so stdout stays clean. */
31
+ export function info(message) {
32
+ if (!quiet && mode === "pretty")
33
+ process.stderr.write(`${message}\n`);
34
+ }
35
+ export function reportError(err) {
36
+ const e = err instanceof CliError
37
+ ? err
38
+ : new CliError(err instanceof Error ? err.message : String(err), "INTERNAL");
39
+ if (mode === "json") {
40
+ process.stdout.write(`${JSON.stringify(e.toJSON(), null, 2)}\n`);
41
+ }
42
+ else {
43
+ process.stderr.write(`✗ ${e.message}${e.code !== "INTERNAL" ? ` (${e.code})` : ""}\n`);
44
+ if (e.hint)
45
+ process.stderr.write(` ${e.hint}\n`);
46
+ }
47
+ return e.exitCode;
48
+ }
49
+ /** A plain text table for people. */
50
+ export function table(rows, columns) {
51
+ if (rows.length === 0)
52
+ return "(none)";
53
+ const widths = columns.map((c) => Math.max(c.length, ...rows.map((r) => (r[c] ?? "").length)));
54
+ const line = (cells) => cells
55
+ .map((cell, i) => cell.padEnd(widths[i]))
56
+ .join(" ")
57
+ .trimEnd();
58
+ return [
59
+ line(columns.map((c) => c.toUpperCase())),
60
+ ...rows.map((r) => line(columns.map((c) => r[c] ?? ""))),
61
+ ].join("\n");
62
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Octonote ids carry their kind (pag_ note, cnv_ canvas, prj_ project,
3
+ * ntb_ notebook), so most commands accept any id and know what it is.
4
+ */
5
+ import { usage } from "./errors.js";
6
+ export const KIND_BY_PREFIX = {
7
+ pag: "page",
8
+ cnv: "canvas",
9
+ prj: "project",
10
+ ntb: "notebook",
11
+ };
12
+ /** What people call each kind. */
13
+ export const KIND_NAME = {
14
+ page: "note",
15
+ canvas: "canvas",
16
+ project: "project",
17
+ notebook: "notebook",
18
+ };
19
+ const APP_PATH = {
20
+ page: "n",
21
+ canvas: "c",
22
+ project: "p",
23
+ notebook: "nb",
24
+ };
25
+ const ID_RE = /^(pag|cnv|prj|ntb)_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
26
+ /** Accepts a bare id or an app / share URL that contains one. */
27
+ export function parseRef(input, expect) {
28
+ const match = input.match(/(pag|cnv|prj|ntb)_[0-9a-f-]{36}/);
29
+ const id = match?.[0];
30
+ if (!id || !ID_RE.test(id)) {
31
+ throw usage(`"${input}" isn't an Octonote id.`, "Ids look like pag_… (note), cnv_… (canvas), prj_… (project) or ntb_… (notebook). `octonote list` shows them.");
32
+ }
33
+ const kind = KIND_BY_PREFIX[id.slice(0, 3)];
34
+ if (expect && kind !== expect) {
35
+ throw usage(`${id} is a ${KIND_NAME[kind]}, not a ${KIND_NAME[expect]}.`);
36
+ }
37
+ return { kind, id };
38
+ }
39
+ export function appUrl(webUrl, kind, id) {
40
+ return `${webUrl}/${APP_PATH[kind]}/${id}`;
41
+ }
@@ -0,0 +1,23 @@
1
+ import { appUrl, KIND_NAME } from "./refs.js";
2
+ export function item(ctx, kind, row) {
3
+ return {
4
+ kind: KIND_NAME[kind],
5
+ id: row.id,
6
+ title: row.title ?? row.name ?? "",
7
+ url: appUrl(ctx.config.webUrl, kind, row.id),
8
+ ...(row.notebookId !== undefined ? { notebookId: row.notebookId } : {}),
9
+ ...(row.updatedAt ? { updatedAt: row.updatedAt } : {}),
10
+ };
11
+ }
12
+ export async function setPublished(ctx, kind, id, published) {
13
+ return ctx.api.put(`/${kind}/${id}/publish`, { published });
14
+ }
15
+ export function describeItem(i) {
16
+ return [
17
+ `${i.kind} ${i.title ? `“${i.title}” ` : ""}${i.id}`,
18
+ ` ${i.url}`,
19
+ i.publicUrl ? ` public: ${i.publicUrl}` : "",
20
+ ]
21
+ .filter(Boolean)
22
+ .join("\n");
23
+ }
@@ -0,0 +1,19 @@
1
+ import { createContext } from "./context.js";
2
+ import { reportError, setOutputMode } from "./output.js";
3
+ export function action(handler, opts = {}) {
4
+ return async (...args) => {
5
+ // Commander passes (...positional, options, command).
6
+ const command = args[args.length - 1];
7
+ const globals = command.optsWithGlobals();
8
+ setOutputMode(globals);
9
+ try {
10
+ const ctx = await createContext(globals, {
11
+ requireAuth: opts.auth !== false,
12
+ });
13
+ await handler(ctx, ...args.slice(0, -1));
14
+ }
15
+ catch (err) {
16
+ process.exitCode = reportError(err);
17
+ }
18
+ };
19
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * The guide agents read — one source for `octonote guide`, the section
3
+ * `octonote setup` writes into CLAUDE.md / AGENTS.md, and the Claude Code
4
+ * skill. Keep it short and task-first: agents act on the first screen.
5
+ */
6
+ /** Short pointer for CLAUDE.md / AGENTS.md (the full guide is one command away). */
7
+ export const PROJECT_SECTION = `## Octonote (notes & canvases)
8
+
9
+ The user keeps notes, diagrams (canvases), projects and notebooks in Octonote. Use the \`octonote\` CLI to read and write them — never ask the user to copy-paste.
10
+
11
+ - Run \`octonote guide\` once per session before using it; \`octonote <command> --help\` for details.
12
+ - Write notes as markdown files (mermaid, \`$math$\`, code and tables work); draw diagrams as mermaid flowcharts (\`--from-mermaid\`), which become real, editable canvases.
13
+ - Output is JSON when piped: read \`id\`, \`url\` and \`publicUrl\` from it, and always give the user the link.
14
+ - Only publish, share or delete when the user asked to.
15
+ `;
16
+ export const AGENT_GUIDE = `# Octonote CLI — guide for agents
17
+
18
+ Octonote holds the user's **notes** (markdown documents), **canvases** (diagrams),
19
+ **projects** (one note + one canvas, side by side) and **notebooks** (folders).
20
+ The \`octonote\` CLI is how you read and write them on the user's behalf.
21
+
22
+ ## Rules
23
+ - Output is JSON when piped (force with --json). Errors print
24
+ \`{"error":{"code","message","hint"}}\` and exit non-zero; branch on \`code\`.
25
+ - Never prompts. Content comes from --file <path>, --stdin, or "-" as the path.
26
+ - Every result has \`id\`, \`url\` (app link) and, once published, \`publicUrl\`.
27
+ Give the user the link at the end.
28
+ - Ids carry their kind: pag_ note, cnv_ canvas, prj_ project, ntb_ notebook.
29
+ Notebooks can also be named by exact name (--notebook "Architecture").
30
+ - Publish, share, invite or delete only when the user asked for it.
31
+ \`delete\` requires --yes.
32
+ - Not signed in (code NOT_LOGGED_IN)? Ask the user to run \`octonote login\`
33
+ (or set OCTONOTE_TOKEN). Don't try to sign in yourself.
34
+
35
+ ## Write a note
36
+ octonote note create "Title" --file doc.md [--notebook "Name"] [--publish]
37
+ cat doc.md | octonote note create "Title" --stdin
38
+ Markdown → rich note: headings, lists, checklists, tables, code, quotes,
39
+ images (by URL), \`$inline math$\`, \`$$display math$$\`, and \`\`\`mermaid blocks
40
+ (rendered diagrams inside the note).
41
+
42
+ ## Read and edit a note
43
+ octonote note show <id> # markdown to stdout
44
+ octonote note pull <id> -o note.md # file with front matter (keeps the id)
45
+ # …edit note.md…
46
+ octonote note push note.md # replaces the note's content
47
+ octonote note append <id> --text "- new item" # or --file / --stdin
48
+ octonote note rename <id> "New title"
49
+
50
+ ## Draw a canvas (diagram)
51
+ Write a mermaid **flowchart** and let Octonote lay it out as real shapes:
52
+ octonote canvas create "Login flow" --from-mermaid flow.mmd [--publish]
53
+ octonote canvas redraw <id> --from-mermaid flow.mmd
54
+ Supported: \`graph|flowchart TD|LR|BT|RL\`; nodes A[box] A(round) A([stadium])
55
+ A((circle)) A{decision} A[(database)] A{{hexagon}}; edges --> --- -.-> ==> <-->
56
+ with labels (A -->|yes| B or A -- yes --> B); chains; A & B --> C;
57
+ \`subgraph id [Title] … end\`. Other diagram types (sequence, gantt…) can't be
58
+ canvases — put them in a note as a \`\`\`mermaid block instead.
59
+ Raw tldraw JSON also works: \`canvas pull <id> -o c.json\` / \`canvas push c.json --id <id>\`.
60
+
61
+ ## Project = note + canvas
62
+ octonote project create "Auth flow" --note auth.md --canvas-mermaid auth.mmd \\
63
+ [--view split|notes|canvas] [--notebook "Name"] [--publish]
64
+ octonote project show <id> # ids of its note and canvas (edit them as above)
65
+
66
+ ## Organise
67
+ octonote list [--kind note|canvas|project|notebook] [--notebook "Name"]
68
+ octonote search <words> # by title
69
+ octonote notebook list | create "Name" | show "Name"
70
+ octonote move <id> --notebook "Name" # or --out
71
+ octonote delete <id> --yes
72
+
73
+ ## Publish and share
74
+ octonote publish <id> # → publicUrl (a public, read-only page)
75
+ octonote unpublish <id>
76
+ octonote share <id> --email person@example.com --role viewer|editor
77
+ octonote share <id> --link view|edit|off # anyone with the app link
78
+ Publishing a notebook publishes everything inside it.
79
+
80
+ ## Account
81
+ octonote whoami | workspace list | workspace use <name|id> | logout
82
+ octonote account rename "Full Name" # display name (author on published pages)
83
+ `;
84
+ /** Claude Code skill file (.claude/skills/octonote/SKILL.md). */
85
+ export const CLAUDE_SKILL = `---
86
+ name: octonote
87
+ description: Read and write the user's Octonote notes, canvases (diagrams), projects and notebooks with the octonote CLI — create notes from markdown, draw diagrams from mermaid flowcharts, publish and share them. Use when the user mentions Octonote, their notes or notebooks, or asks to document, diagram, write up or publish something there.
88
+ ---
89
+
90
+ ${AGENT_GUIDE}`;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * What `octonote` prints with no command — right after install, too, since
3
+ * the install line is `npm i -g … && octonote`. (npm hides messages printed
4
+ * during install, so this is where setup help lives.) Everything needed to
5
+ * get going, without opening the docs.
6
+ */
7
+ export const DOCS_URL = "https://octonote.ai/cli";
8
+ export function gettingStarted(opts) {
9
+ const signIn = opts.signedInAs
10
+ ? ` ✓ Signed in as ${opts.signedInAs}`
11
+ : ` 1. Sign in octonote login
12
+ (no browser? set OCTONOTE_TOKEN from Settings → CLI & agents)`;
13
+ return `
14
+ octonote ${opts.version} — Octonote from your terminal, Claude Code and Codex
15
+
16
+ GET STARTED
17
+ ${signIn}
18
+ ${opts.signedInAs ? "1." : "2."} Teach your agent octonote setup claude # this project's CLAUDE.md
19
+ octonote setup claude --global --skill # every project + skill
20
+ octonote setup codex # this project's AGENTS.md
21
+ octonote setup codex --global
22
+ Then just ask: “write this up in Octonote”, “draw this as an Octonote canvas”.
23
+
24
+ Claude Code: allow it without prompts — add to ~/.claude/settings.json:
25
+ { "permissions": { "allow": ["Bash(octonote:*)"] } }
26
+ Codex: its sandbox blocks the network by default — in ~/.codex/config.toml:
27
+ [sandbox_workspace_write]
28
+ network_access = true
29
+
30
+ COMMON COMMANDS
31
+ octonote note create "Title" --file doc.md note from markdown
32
+ octonote note pull <id> -o doc.md edit locally, then: note push doc.md
33
+ octonote canvas create "Flow" --from-mermaid flow.mmd draw a diagram
34
+ octonote project create "Name" --note doc.md --canvas-mermaid flow.mmd
35
+ octonote list · search <words> · notebook list
36
+ octonote publish <id> · share <id> --email … --role viewer|editor
37
+
38
+ HELP
39
+ octonote --help all commands
40
+ octonote <command> --help one command, e.g. octonote note create --help
41
+ octonote guide the full guide written for AI agents
42
+ ${DOCS_URL} docs on the web
43
+ `;
44
+ }
package/dist/index.js ADDED
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * octonote — the Octonote command-line tool, built for people and for
4
+ * agents (Claude Code, Codex). Commands live in ./commands; shared
5
+ * plumbing (config, HTTP, output, errors) in ./core; the agent guide in
6
+ * ./guide. The CLI only talks to the public Octonote API.
7
+ */
8
+ import { realpathSync } from "node:fs";
9
+ import { fileURLToPath } from "node:url";
10
+ import { Command } from "commander";
11
+ import { registerAgents } from "./commands/agents.js";
12
+ import { registerAuth } from "./commands/auth.js";
13
+ import { registerCanvas } from "./commands/canvas.js";
14
+ import { registerItems } from "./commands/items.js";
15
+ import { registerNote } from "./commands/note.js";
16
+ import { registerNotebook } from "./commands/notebook.js";
17
+ import { registerProject } from "./commands/project.js";
18
+ import { resolveConfig } from "./core/config.js";
19
+ import { DOCS_URL, gettingStarted } from "./guide/getting-started.js";
20
+ import { VERSION } from "./version.js";
21
+ export { VERSION } from "./version.js";
22
+ export function buildProgram() {
23
+ const program = new Command()
24
+ .name("octonote")
25
+ .description("Octonote from your terminal — and from Claude Code or Codex.\nAgents: run `octonote guide` first.")
26
+ .version(VERSION)
27
+ .option("--json", "JSON output (default when piped)")
28
+ .option("--pretty", "human output (default in a terminal)")
29
+ .option("-q, --quiet", "no progress messages")
30
+ .option("-w, --workspace <id>", "workspace to use (default: yours)")
31
+ .option("--api-url <url>", "Octonote API (default: OCTONOTE_API_URL or the hosted API)")
32
+ .option("--web-url <url>", "Octonote web app (default: OCTONOTE_WEB_URL or https://octonote.ai)")
33
+ .showHelpAfterError("(run with --help for usage)")
34
+ .configureOutput({
35
+ // Usage errors: say what's wrong without dumping the full help.
36
+ outputError: (str, write) => write(str),
37
+ });
38
+ // `octonote` on its own: the getting-started screen (also shown right
39
+ // after install — the install line ends in `&& octonote`).
40
+ program.action(async () => {
41
+ const { token, tokenSource, email } = await resolveConfig();
42
+ const signedInAs = token
43
+ ? (email ?? (tokenSource === "env" ? "OCTONOTE_TOKEN" : "your account"))
44
+ : null;
45
+ process.stdout.write(gettingStarted({ version: VERSION, signedInAs }));
46
+ });
47
+ registerAuth(program);
48
+ registerNote(program);
49
+ registerCanvas(program);
50
+ registerProject(program);
51
+ registerNotebook(program);
52
+ registerItems(program);
53
+ registerAgents(program);
54
+ program.addHelpText("after", `\nGetting started (sign-in, Claude Code & Codex setup): run \`octonote\` on its own.\nAgents: \`octonote guide\`. Docs: ${DOCS_URL}`);
55
+ return program;
56
+ }
57
+ // Run only when executed (npm's bin link resolves to this file), not when imported by tests.
58
+ function isMain() {
59
+ try {
60
+ return (realpathSync(process.argv[1] ?? "") === fileURLToPath(import.meta.url));
61
+ }
62
+ catch {
63
+ return false;
64
+ }
65
+ }
66
+ if (isMain()) {
67
+ buildProgram()
68
+ .parseAsync(process.argv)
69
+ .catch((err) => {
70
+ process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
71
+ process.exitCode = 1;
72
+ });
73
+ }
@@ -0,0 +1,48 @@
1
+ import { AGENT_GUIDE, PROJECT_SECTION } from "./guide/agent-guide.js";
2
+ function options(cmd) {
3
+ return cmd.options
4
+ .filter((o) => !["--help", "--version"].includes(o.long ?? ""))
5
+ .map((o) => ({
6
+ flags: o.flags,
7
+ description: o.description,
8
+ required: o.mandatory,
9
+ ...(o.defaultValue !== undefined
10
+ ? { default: String(o.defaultValue) }
11
+ : {}),
12
+ }));
13
+ }
14
+ export function buildReference(program, version) {
15
+ const commands = [];
16
+ const walk = (cmd, prefix) => {
17
+ for (const sub of cmd.commands) {
18
+ const path = [...prefix, sub.name()];
19
+ if (sub.commands.length) {
20
+ walk(sub, path);
21
+ continue;
22
+ }
23
+ commands.push({
24
+ name: path.join(" "),
25
+ aliases: sub.aliases(),
26
+ description: sub.description(),
27
+ arguments: sub.registeredArguments.map((a) => ({
28
+ name: a.name(),
29
+ description: a.description,
30
+ required: a.required,
31
+ variadic: a.variadic,
32
+ ...(a.defaultValue !== undefined
33
+ ? { default: String(a.defaultValue) }
34
+ : {}),
35
+ })),
36
+ options: options(sub),
37
+ });
38
+ }
39
+ };
40
+ walk(program, []);
41
+ return {
42
+ version,
43
+ globalOptions: options(program),
44
+ commands,
45
+ guide: AGENT_GUIDE,
46
+ projectSection: PROJECT_SECTION,
47
+ };
48
+ }
@@ -0,0 +1,2 @@
1
+ /** The CLI's version — keep in step with package.json (a test checks). */
2
+ export const VERSION = "0.1.4";
package/package.json CHANGED
@@ -1,6 +1,53 @@
1
1
  {
2
2
  "name": "@octonote/cli",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.4",
4
+ "description": "The octonote command-line tool: lets you — and agents like Claude Code and Codex — create, edit, publish and share Octonote notes, canvases, projects and notebooks.",
5
+ "homepage": "https://octonote.ai/cli",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/CoderCouple/octonote-ai.git",
9
+ "directory": "packages/cli"
10
+ },
11
+ "type": "module",
12
+ "bin": {
13
+ "octonote": "dist/index.js"
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "engines": {
20
+ "node": ">=20"
21
+ },
22
+ "scripts": {
23
+ "build": "tsc -p tsconfig.json && node scripts/gen-reference.mjs && node scripts/gen-docs.mjs",
24
+ "dev": "tsx src/index.ts",
25
+ "lint": "echo \"lint pending\"",
26
+ "typecheck": "tsc -p tsconfig.json --noEmit",
27
+ "test": "vitest run",
28
+ "check:reference": "node scripts/gen-reference.mjs --check && node scripts/gen-docs.mjs --check"
29
+ },
30
+ "dependencies": {
31
+ "commander": "^12.1.0"
32
+ },
33
+ "devDependencies": {
34
+ "@types/node": "^22.10.2",
35
+ "tsx": "^4.19.2",
36
+ "typescript": "^5.7.2",
37
+ "vitest": "^4.1.9"
38
+ },
39
+ "keywords": [
40
+ "octonote",
41
+ "cli",
42
+ "notes",
43
+ "canvas",
44
+ "mermaid",
45
+ "claude",
46
+ "codex",
47
+ "ai-agent"
48
+ ],
49
+ "license": "MIT",
50
+ "publishConfig": {
51
+ "access": "public"
52
+ }
53
+ }