@creator-notes/cnotes 0.16.11
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/.claude-plugin/plugin.json +14 -0
- package/.mcp.json +12 -0
- package/LICENSE +21 -0
- package/README.md +303 -0
- package/dist/cn.d.ts +3 -0
- package/dist/cn.d.ts.map +1 -0
- package/dist/cn.js +124 -0
- package/dist/cn.js.map +1 -0
- package/dist/commands/auth.d.ts +10 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +188 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/canvas.d.ts +3 -0
- package/dist/commands/canvas.d.ts.map +1 -0
- package/dist/commands/canvas.js +1383 -0
- package/dist/commands/canvas.js.map +1 -0
- package/dist/commands/claude-hook.d.ts +28 -0
- package/dist/commands/claude-hook.d.ts.map +1 -0
- package/dist/commands/claude-hook.js +59 -0
- package/dist/commands/claude-hook.js.map +1 -0
- package/dist/commands/config.d.ts +3 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +47 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/files.d.ts +3 -0
- package/dist/commands/files.d.ts.map +1 -0
- package/dist/commands/files.js +119 -0
- package/dist/commands/files.js.map +1 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +473 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/mcp.d.ts +15 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/mcp.js +118 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/memory.d.ts +3 -0
- package/dist/commands/memory.d.ts.map +1 -0
- package/dist/commands/memory.js +150 -0
- package/dist/commands/memory.js.map +1 -0
- package/dist/commands/notes.d.ts +3 -0
- package/dist/commands/notes.d.ts.map +1 -0
- package/dist/commands/notes.js +706 -0
- package/dist/commands/notes.js.map +1 -0
- package/dist/commands/operations.d.ts +18 -0
- package/dist/commands/operations.d.ts.map +1 -0
- package/dist/commands/operations.js +231 -0
- package/dist/commands/operations.js.map +1 -0
- package/dist/commands/relationships.d.ts +3 -0
- package/dist/commands/relationships.d.ts.map +1 -0
- package/dist/commands/relationships.js +94 -0
- package/dist/commands/relationships.js.map +1 -0
- package/dist/commands/schema.d.ts +12 -0
- package/dist/commands/schema.d.ts.map +1 -0
- package/dist/commands/schema.js +85 -0
- package/dist/commands/schema.js.map +1 -0
- package/dist/commands/search.d.ts +3 -0
- package/dist/commands/search.d.ts.map +1 -0
- package/dist/commands/search.js +57 -0
- package/dist/commands/search.js.map +1 -0
- package/dist/commands/theme.d.ts +3 -0
- package/dist/commands/theme.d.ts.map +1 -0
- package/dist/commands/theme.js +184 -0
- package/dist/commands/theme.js.map +1 -0
- package/dist/commands/timeline.d.ts +3 -0
- package/dist/commands/timeline.d.ts.map +1 -0
- package/dist/commands/timeline.js +97 -0
- package/dist/commands/timeline.js.map +1 -0
- package/dist/commands/types.d.ts +3 -0
- package/dist/commands/types.d.ts.map +1 -0
- package/dist/commands/types.js +139 -0
- package/dist/commands/types.js.map +1 -0
- package/dist/commands/versions.d.ts +3 -0
- package/dist/commands/versions.d.ts.map +1 -0
- package/dist/commands/versions.js +120 -0
- package/dist/commands/versions.js.map +1 -0
- package/dist/commands/workspace.d.ts +13 -0
- package/dist/commands/workspace.d.ts.map +1 -0
- package/dist/commands/workspace.js +176 -0
- package/dist/commands/workspace.js.map +1 -0
- package/dist/lib/api-client.d.ts +45 -0
- package/dist/lib/api-client.d.ts.map +1 -0
- package/dist/lib/api-client.js +198 -0
- package/dist/lib/api-client.js.map +1 -0
- package/dist/lib/auth-store.d.ts +47 -0
- package/dist/lib/auth-store.d.ts.map +1 -0
- package/dist/lib/auth-store.js +116 -0
- package/dist/lib/auth-store.js.map +1 -0
- package/dist/lib/brand.d.ts +32 -0
- package/dist/lib/brand.d.ts.map +1 -0
- package/dist/lib/brand.js +32 -0
- package/dist/lib/brand.js.map +1 -0
- package/dist/lib/build-schema.d.ts +97 -0
- package/dist/lib/build-schema.d.ts.map +1 -0
- package/dist/lib/build-schema.js +139 -0
- package/dist/lib/build-schema.js.map +1 -0
- package/dist/lib/canvas-read.d.ts +54 -0
- package/dist/lib/canvas-read.d.ts.map +1 -0
- package/dist/lib/canvas-read.js +145 -0
- package/dist/lib/canvas-read.js.map +1 -0
- package/dist/lib/claude-session.d.ts +73 -0
- package/dist/lib/claude-session.d.ts.map +1 -0
- package/dist/lib/claude-session.js +104 -0
- package/dist/lib/claude-session.js.map +1 -0
- package/dist/lib/config.d.ts +28 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +66 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/env.d.ts +14 -0
- package/dist/lib/env.d.ts.map +1 -0
- package/dist/lib/env.js +16 -0
- package/dist/lib/env.js.map +1 -0
- package/dist/lib/errors.d.ts +47 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +194 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/fs-utils.d.ts +2 -0
- package/dist/lib/fs-utils.d.ts.map +1 -0
- package/dist/lib/fs-utils.js +16 -0
- package/dist/lib/fs-utils.js.map +1 -0
- package/dist/lib/install-hook.d.ts +86 -0
- package/dist/lib/install-hook.d.ts.map +1 -0
- package/dist/lib/install-hook.js +168 -0
- package/dist/lib/install-hook.js.map +1 -0
- package/dist/lib/install-mcp.d.ts +21 -0
- package/dist/lib/install-mcp.d.ts.map +1 -0
- package/dist/lib/install-mcp.js +133 -0
- package/dist/lib/install-mcp.js.map +1 -0
- package/dist/lib/install-skill.d.ts +49 -0
- package/dist/lib/install-skill.d.ts.map +1 -0
- package/dist/lib/install-skill.js +113 -0
- package/dist/lib/install-skill.js.map +1 -0
- package/dist/lib/output.d.ts +29 -0
- package/dist/lib/output.d.ts.map +1 -0
- package/dist/lib/output.js +78 -0
- package/dist/lib/output.js.map +1 -0
- package/dist/lib/resolve-note.d.ts +7 -0
- package/dist/lib/resolve-note.d.ts.map +1 -0
- package/dist/lib/resolve-note.js +23 -0
- package/dist/lib/resolve-note.js.map +1 -0
- package/dist/lib/stdin.d.ts +5 -0
- package/dist/lib/stdin.d.ts.map +1 -0
- package/dist/lib/stdin.js +11 -0
- package/dist/lib/stdin.js.map +1 -0
- package/dist/lib/style.d.ts +10 -0
- package/dist/lib/style.d.ts.map +1 -0
- package/dist/lib/style.js +17 -0
- package/dist/lib/style.js.map +1 -0
- package/dist/lib/themes.d.ts +44 -0
- package/dist/lib/themes.d.ts.map +1 -0
- package/dist/lib/themes.js +168 -0
- package/dist/lib/themes.js.map +1 -0
- package/dist/mcp-server.d.ts +3 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +782 -0
- package/dist/mcp-server.js.map +1 -0
- package/package.json +66 -0
- package/skills/cnotes/SKILL.md +680 -0
|
@@ -0,0 +1,706 @@
|
|
|
1
|
+
import { select } from "@inquirer/prompts";
|
|
2
|
+
import { ApiClient } from "../lib/api-client.js";
|
|
3
|
+
import { resolveConfig } from "../lib/config.js";
|
|
4
|
+
import { handleError, NotFoundError, ValidationError } from "../lib/errors.js";
|
|
5
|
+
import { readFileSafe } from "../lib/fs-utils.js";
|
|
6
|
+
import { outputJson, outputTable, outputQuiet, timeAgo, truncate } from "../lib/output.js";
|
|
7
|
+
import { resolveNoteId } from "../lib/resolve-note.js";
|
|
8
|
+
export function registerNotesCommands(program) {
|
|
9
|
+
const notes = program
|
|
10
|
+
.command("notes")
|
|
11
|
+
.alias("n")
|
|
12
|
+
.description("Manage notes");
|
|
13
|
+
notes
|
|
14
|
+
.command("list")
|
|
15
|
+
.description("List notes in the current workspace")
|
|
16
|
+
.option("--search <query>", "Full-text search")
|
|
17
|
+
.option("--type <type>", "Filter by note type")
|
|
18
|
+
.option("--tags <tags>", "Filter by tags (comma-separated)")
|
|
19
|
+
.option("--pinned", "Only show pinned notes")
|
|
20
|
+
.option("--limit <n>", "Maximum number of results", "20")
|
|
21
|
+
.addHelpText("after", `
|
|
22
|
+
Examples:
|
|
23
|
+
$ cnotes notes list --type Insight --limit 5
|
|
24
|
+
$ cnotes notes list --search "auth flow" --json
|
|
25
|
+
$ cnotes notes list --fields displayId,title,type --json
|
|
26
|
+
`)
|
|
27
|
+
.action(async (opts) => {
|
|
28
|
+
const cfg = resolveConfig(program.opts());
|
|
29
|
+
const api = new ApiClient(cfg.serverUrl, cfg.token);
|
|
30
|
+
try {
|
|
31
|
+
const params = {
|
|
32
|
+
workspaceId: cfg.workspaceId,
|
|
33
|
+
search: opts.search,
|
|
34
|
+
types: opts.type,
|
|
35
|
+
tags: opts.tags,
|
|
36
|
+
onlyPinned: opts.pinned ? "true" : undefined,
|
|
37
|
+
excludeDrafts: "true",
|
|
38
|
+
excludeContent: "true",
|
|
39
|
+
};
|
|
40
|
+
const notesList = await api.get("/api/notes", params);
|
|
41
|
+
// The server caps results (and ignores `limit` when a search/type
|
|
42
|
+
// filter is active), so apply the requested limit here. This also
|
|
43
|
+
// fixes the JSON path, which previously emitted the entire result
|
|
44
|
+
// set regardless of --limit.
|
|
45
|
+
const limit = parseInt(opts.limit, 10);
|
|
46
|
+
const all = Array.isArray(notesList) ? notesList : [];
|
|
47
|
+
if (cfg.json) {
|
|
48
|
+
const shown = all.slice(0, limit);
|
|
49
|
+
if (all.length > shown.length) {
|
|
50
|
+
// stderr keeps stdout valid JSON while still signalling to the
|
|
51
|
+
// caller (often an agent) that the list was truncated.
|
|
52
|
+
console.error(`Showing ${shown.length} of ${all.length} — raise --limit to see more.`);
|
|
53
|
+
}
|
|
54
|
+
outputJson(shown, cfg.fields);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
await displayNotesList(all, limit, cfg, api, "notes found — select to view:");
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
handleError(error, cfg.json);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
notes
|
|
64
|
+
.command("get")
|
|
65
|
+
.description("Get one or more notes by display ID. Always returns an array, in input order, " +
|
|
66
|
+
"in a single round-trip. Pass one ID for one note, or many IDs to batch-fetch.")
|
|
67
|
+
.argument("<ids...>", "One or more display IDs (e.g. MEETING-12 PRD-3 IDEA-7)")
|
|
68
|
+
.option("--all-versions", "Include all versions")
|
|
69
|
+
.option("--content-only", "Print only the raw markdown content (no metadata, no JSON envelope). " +
|
|
70
|
+
"Best for reading large notes/transcripts without a giant clipped JSON line.")
|
|
71
|
+
.addHelpText("after", `
|
|
72
|
+
Examples:
|
|
73
|
+
$ cnotes notes get MEETING-12
|
|
74
|
+
$ cnotes notes get MEETING-12 PRD-3 IDEA-7 --json
|
|
75
|
+
$ cnotes notes get TRANSCRIPT-1 --content-only # raw markdown, no JSON to parse
|
|
76
|
+
`)
|
|
77
|
+
.action(async (ids, opts) => {
|
|
78
|
+
const cfg = resolveConfig(program.opts());
|
|
79
|
+
const api = new ApiClient(cfg.serverUrl, cfg.token);
|
|
80
|
+
try {
|
|
81
|
+
const notesList = await api.get(`/api/notes`, {
|
|
82
|
+
workspaceId: cfg.workspaceId,
|
|
83
|
+
includeVersions: opts.allVersions ? "true" : undefined,
|
|
84
|
+
exact_ids: ids.join(","),
|
|
85
|
+
format: "markdown",
|
|
86
|
+
});
|
|
87
|
+
const found = Array.isArray(notesList) ? notesList : [];
|
|
88
|
+
const byDisplayId = new Map();
|
|
89
|
+
for (const n of found) {
|
|
90
|
+
if (typeof n.displayId === "string")
|
|
91
|
+
byDisplayId.set(n.displayId, n);
|
|
92
|
+
}
|
|
93
|
+
const missing = ids.filter((id) => !byDisplayId.has(id));
|
|
94
|
+
if (missing.length > 0) {
|
|
95
|
+
throw new NotFoundError(`Notes not found: ${missing.join(", ")}`, "Run `cnotes notes list` to see valid display IDs in this workspace.");
|
|
96
|
+
}
|
|
97
|
+
// Preserve input order so callers can index by position.
|
|
98
|
+
const ordered = ids.map((id) => byDisplayId.get(id));
|
|
99
|
+
if (opts.contentOnly) {
|
|
100
|
+
// Raw markdown only — no JSON envelope, no chrome. When several notes
|
|
101
|
+
// are requested, delimit each with an HTML comment (valid markdown,
|
|
102
|
+
// renders to nothing) so the stream stays parseable.
|
|
103
|
+
const parts = ordered.map((note) => {
|
|
104
|
+
// Mirror printNote's resolution: the API populates top-level
|
|
105
|
+
// `content`, but fall back to latestVersion in case a shape only
|
|
106
|
+
// carries it there.
|
|
107
|
+
const raw = note.content ?? note.latestVersion?.content;
|
|
108
|
+
const content = typeof raw === "string" ? raw : "";
|
|
109
|
+
return ordered.length > 1
|
|
110
|
+
? `<!-- ${String(note.displayId)} -->\n${content}`
|
|
111
|
+
: content;
|
|
112
|
+
});
|
|
113
|
+
process.stdout.write(parts.join("\n\n") + "\n");
|
|
114
|
+
}
|
|
115
|
+
else if (cfg.json) {
|
|
116
|
+
outputJson(ordered, cfg.fields);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
ordered.forEach((note) => printNote(note));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
handleError(error, cfg.json);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
notes
|
|
127
|
+
.command("delete")
|
|
128
|
+
.description("Delete (archive) a note")
|
|
129
|
+
.argument("<id>", "Note display ID or Convex ID")
|
|
130
|
+
.addHelpText("after", `
|
|
131
|
+
Examples:
|
|
132
|
+
$ cnotes notes delete MEETING-12
|
|
133
|
+
`)
|
|
134
|
+
.action(async (id) => {
|
|
135
|
+
const cfg = resolveConfig(program.opts());
|
|
136
|
+
const api = new ApiClient(cfg.serverUrl, cfg.token);
|
|
137
|
+
try {
|
|
138
|
+
const noteId = await resolveNoteId(api, cfg.workspaceId, id);
|
|
139
|
+
const data = await api.delete(`/api/notes/${noteId}`);
|
|
140
|
+
if (cfg.json) {
|
|
141
|
+
outputJson(data);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
console.log(`Deleted note ${id}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
handleError(error, cfg.json);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
notes
|
|
152
|
+
.command("update")
|
|
153
|
+
.description("Update note metadata (type, tags, pin/archive status). For content edits, use `cnotes versions create`.")
|
|
154
|
+
.argument("<id>", "Note display ID or Convex ID")
|
|
155
|
+
.option("--type <type>", "New note type (e.g. Insight, Painpoint)")
|
|
156
|
+
.option("--tags <tags>", "Replace tags (comma-separated). Use --tags '' to clear.")
|
|
157
|
+
.option("--pin", "Pin the note")
|
|
158
|
+
.option("--unpin", "Unpin the note")
|
|
159
|
+
.option("--archive", "Archive the note")
|
|
160
|
+
.option("--unarchive", "Unarchive the note")
|
|
161
|
+
.addHelpText("after", `
|
|
162
|
+
Examples:
|
|
163
|
+
$ cnotes notes update MEETING-12 --type Insight
|
|
164
|
+
$ cnotes notes update MEETING-12 --tags "auth,security" --pin
|
|
165
|
+
`)
|
|
166
|
+
.action(async (id, opts) => {
|
|
167
|
+
const cfg = resolveConfig(program.opts());
|
|
168
|
+
const api = new ApiClient(cfg.serverUrl, cfg.token);
|
|
169
|
+
try {
|
|
170
|
+
if (opts.pin && opts.unpin) {
|
|
171
|
+
throw new ValidationError("--pin and --unpin are mutually exclusive");
|
|
172
|
+
}
|
|
173
|
+
if (opts.archive && opts.unarchive) {
|
|
174
|
+
throw new ValidationError("--archive and --unarchive are mutually exclusive");
|
|
175
|
+
}
|
|
176
|
+
const body = {};
|
|
177
|
+
if (opts.type !== undefined)
|
|
178
|
+
body.type = opts.type;
|
|
179
|
+
if (opts.tags !== undefined) {
|
|
180
|
+
body.tags = opts.tags === ""
|
|
181
|
+
? []
|
|
182
|
+
: opts.tags.split(",").map((t) => t.trim()).filter(Boolean);
|
|
183
|
+
}
|
|
184
|
+
if (opts.pin)
|
|
185
|
+
body.isPinned = true;
|
|
186
|
+
if (opts.unpin)
|
|
187
|
+
body.isPinned = false;
|
|
188
|
+
if (opts.archive)
|
|
189
|
+
body.isArchived = true;
|
|
190
|
+
if (opts.unarchive)
|
|
191
|
+
body.isArchived = false;
|
|
192
|
+
if (Object.keys(body).length === 0) {
|
|
193
|
+
throw new ValidationError("At least one of --type, --tags, --pin, --unpin, --archive, or --unarchive is required");
|
|
194
|
+
}
|
|
195
|
+
const noteId = await resolveNoteId(api, cfg.workspaceId, id);
|
|
196
|
+
const data = await api.patch(`/api/notes/${noteId}`, body);
|
|
197
|
+
const note = data.note ?? {};
|
|
198
|
+
if (cfg.quiet) {
|
|
199
|
+
outputQuiet(String(note.displayId || note.id || ""));
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (cfg.json) {
|
|
203
|
+
outputJson(data);
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
console.log(`Updated note: ${note.displayId || note.id || id}`);
|
|
207
|
+
if (body.type !== undefined)
|
|
208
|
+
console.log(`Type: ${body.type}`);
|
|
209
|
+
if (body.tags !== undefined)
|
|
210
|
+
console.log(`Tags: ${body.tags.join(", ") || "(none)"}`);
|
|
211
|
+
if (body.isPinned !== undefined)
|
|
212
|
+
console.log(`Pinned: ${body.isPinned}`);
|
|
213
|
+
if (body.isArchived !== undefined)
|
|
214
|
+
console.log(`Archived: ${body.isArchived}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
handleError(error, cfg.json);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
notes
|
|
222
|
+
.command("bulk-archive")
|
|
223
|
+
.description("Archive or unarchive multiple notes in one operation")
|
|
224
|
+
.requiredOption("--ids <json>", "JSON array of note display IDs or Convex IDs, e.g. '[\"MEETING-1\",\"PRD-3\"]'")
|
|
225
|
+
.option("--unarchive", "Unarchive instead of archive")
|
|
226
|
+
.addHelpText("after", `
|
|
227
|
+
Examples:
|
|
228
|
+
$ cnotes notes bulk-archive --ids '["MEETING-1","PRD-3","IDEA-7"]'
|
|
229
|
+
$ cnotes notes bulk-archive --ids '["NOTE-9"]' --unarchive
|
|
230
|
+
`)
|
|
231
|
+
.action(async (opts) => {
|
|
232
|
+
const cfg = resolveConfig(program.opts());
|
|
233
|
+
const api = new ApiClient(cfg.serverUrl, cfg.token);
|
|
234
|
+
try {
|
|
235
|
+
let ids;
|
|
236
|
+
try {
|
|
237
|
+
ids = JSON.parse(opts.ids);
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
throw new ValidationError("--ids must be valid JSON array, e.g. '[\"MEETING-1\",\"PRD-3\"]'");
|
|
241
|
+
}
|
|
242
|
+
if (!Array.isArray(ids) || ids.length === 0) {
|
|
243
|
+
throw new ValidationError("--ids must be a non-empty JSON array of strings");
|
|
244
|
+
}
|
|
245
|
+
// Resolve display IDs to Convex IDs
|
|
246
|
+
const noteIds = await Promise.all(ids.map((id) => resolveNoteId(api, cfg.workspaceId, String(id))));
|
|
247
|
+
const isArchived = !opts.unarchive;
|
|
248
|
+
const data = await api.post("/api/notes/bulk", {
|
|
249
|
+
action: "bulkArchive",
|
|
250
|
+
noteIds,
|
|
251
|
+
isArchived,
|
|
252
|
+
});
|
|
253
|
+
if (cfg.json) {
|
|
254
|
+
outputJson(data);
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
const results = (data.results || []);
|
|
258
|
+
const verb = isArchived ? "Archived" : "Unarchived";
|
|
259
|
+
const successStatus = isArchived ? "archived" : "unarchived";
|
|
260
|
+
const succeeded = results.filter((r) => r.status === successStatus).length;
|
|
261
|
+
const failed = results.filter((r) => r.status === "error").length;
|
|
262
|
+
console.log(`${verb} ${succeeded} notes${failed > 0 ? ` (${failed} failed)` : ""}`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
handleError(error, cfg.json);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
notes
|
|
270
|
+
.command("create")
|
|
271
|
+
.description("Create one or more interlinked notes in a single transaction.\n" +
|
|
272
|
+
" Pass --notes either as an array of items or a single item object.\n" +
|
|
273
|
+
" Relationship link syntax (use inside markdown):\n" +
|
|
274
|
+
" [@<key>: Title](relationship:references) → batch placeholder; @<key> is\n" +
|
|
275
|
+
" replaced with the new note's\n" +
|
|
276
|
+
" display ID after creation.\n" +
|
|
277
|
+
" [NOTE-12: Title](relationship:references) → existing-note reference.\n" +
|
|
278
|
+
" Always include the `: Title` part — the title-less form `[NOTE-12](relationship:references)`\n" +
|
|
279
|
+
" produces a chip with `title: null`, which renders as \"Untitled\" in the UI.\n" +
|
|
280
|
+
" The relationship name is free-form (e.g. references, supports, triggers); use\n" +
|
|
281
|
+
" `references` if unsure.")
|
|
282
|
+
.requiredOption("--notes <json>", "JSON for items to create — pass a single item object (treated as a\n" +
|
|
283
|
+
" one-item batch) or an array of {key, type, markdown|markdownFile, tags?}.\n" +
|
|
284
|
+
" Examples:\n" +
|
|
285
|
+
" '{\"key\":\"A\",\"type\":\"Insight\",\"markdown\":\"# Solo note\"}'\n" +
|
|
286
|
+
" '[{\"key\":\"PARENT\",\"type\":\"Insight\",\"markdown\":\"# Parent\\nSee [@CHILD: Child](relationship:references).\"},\n" +
|
|
287
|
+
" {\"key\":\"CHILD\",\"type\":\"Insight\",\"markdown\":\"# Child\\nRelated to [@PARENT: Parent](relationship:references).\"}]'")
|
|
288
|
+
.addHelpText("after", `
|
|
289
|
+
Examples:
|
|
290
|
+
$ cnotes notes create --notes '{"key":"A","type":"Insight","markdown":"# Solo note"}'
|
|
291
|
+
$ cnotes notes create --notes '[{"key":"P","type":"Insight","markdown":"# Parent\\nSee [@C: Child](relationship:references)."},{"key":"C","type":"Insight","markdown":"# Child"}]'
|
|
292
|
+
|
|
293
|
+
Output (--json):
|
|
294
|
+
Returns an object, not an array. Created notes are under the "results" key
|
|
295
|
+
(not "notes"/"ok"); each carries displayId + title. Read them with:
|
|
296
|
+
cnotes notes create --json --notes '…' | jq -r '.results[].displayId'
|
|
297
|
+
Success is the exit code (0). Do NOT re-run on a missing key; a re-run
|
|
298
|
+
duplicates the batch. A non-zero exit prints { error, code, exitCode, hint }.
|
|
299
|
+
`)
|
|
300
|
+
.action(async (opts) => {
|
|
301
|
+
const cfg = resolveConfig(program.opts());
|
|
302
|
+
const api = new ApiClient(cfg.serverUrl, cfg.token);
|
|
303
|
+
try {
|
|
304
|
+
let parsed;
|
|
305
|
+
try {
|
|
306
|
+
parsed = JSON.parse(opts.notes);
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
throw new ValidationError("--notes must be valid JSON (an item object or an array of item objects)");
|
|
310
|
+
}
|
|
311
|
+
// Auto-wrap a single object into a one-item array.
|
|
312
|
+
const items = Array.isArray(parsed)
|
|
313
|
+
? parsed
|
|
314
|
+
: parsed && typeof parsed === "object"
|
|
315
|
+
? [parsed]
|
|
316
|
+
: [];
|
|
317
|
+
if (items.length === 0) {
|
|
318
|
+
throw new ValidationError("--notes must contain at least one item (object or non-empty array)");
|
|
319
|
+
}
|
|
320
|
+
const localErrors = [];
|
|
321
|
+
const notes = [];
|
|
322
|
+
items.forEach((raw, index) => {
|
|
323
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
324
|
+
localErrors.push({ index, field: "item", message: "Item must be a JSON object" });
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
const obj = raw;
|
|
328
|
+
const key = typeof obj.key === "string" ? obj.key.trim() : "";
|
|
329
|
+
const type = typeof obj.type === "string" ? obj.type.trim() : "";
|
|
330
|
+
const markdownInline = typeof obj.markdown === "string" ? obj.markdown : undefined;
|
|
331
|
+
const markdownFile = typeof obj.markdownFile === "string" ? obj.markdownFile : undefined;
|
|
332
|
+
if (!key) {
|
|
333
|
+
localErrors.push({ index, field: "key", message: "key is required (non-empty string used to cross-reference items in this batch)" });
|
|
334
|
+
}
|
|
335
|
+
if (!type) {
|
|
336
|
+
localErrors.push({ index, key: key || undefined, field: "type", message: "type is required (e.g. \"Insight\", \"Meeting\")" });
|
|
337
|
+
}
|
|
338
|
+
let markdown = markdownInline;
|
|
339
|
+
if (!markdown && markdownFile) {
|
|
340
|
+
try {
|
|
341
|
+
markdown = readFileSafe(markdownFile);
|
|
342
|
+
}
|
|
343
|
+
catch (e) {
|
|
344
|
+
const reason = e instanceof Error ? e.message : "could not read file";
|
|
345
|
+
localErrors.push({ index, key: key || undefined, field: "markdownFile", message: reason });
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if (!markdown) {
|
|
349
|
+
localErrors.push({ index, key: key || undefined, field: "markdown", message: "markdown or markdownFile is required" });
|
|
350
|
+
}
|
|
351
|
+
let tags;
|
|
352
|
+
if (obj.tags !== undefined && obj.tags !== null) {
|
|
353
|
+
if (!Array.isArray(obj.tags) || !obj.tags.every((t) => typeof t === "string")) {
|
|
354
|
+
localErrors.push({ index, key: key || undefined, field: "tags", message: "tags must be an array of strings" });
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
tags = obj.tags;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (key && type && markdown) {
|
|
361
|
+
notes.push({ key, type, markdown, tags });
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
if (localErrors.length > 0) {
|
|
365
|
+
const summary = `${localErrors.length} of ${items.length} item${items.length === 1 ? "" : "s"} invalid`;
|
|
366
|
+
const lines = localErrors.map((e) => ` - item ${e.index} (${e.key ?? "?"}): ${e.field}: ${e.message}`);
|
|
367
|
+
throw new ValidationError(`${summary}\n${lines.join("\n")}`);
|
|
368
|
+
}
|
|
369
|
+
const data = await api.post("/api/notes/bulk", {
|
|
370
|
+
action: "bulkCreate",
|
|
371
|
+
workspaceId: cfg.workspaceId,
|
|
372
|
+
notes,
|
|
373
|
+
});
|
|
374
|
+
if (cfg.quiet) {
|
|
375
|
+
const results = (data.results || []);
|
|
376
|
+
results.forEach((r) => outputQuiet(String(r.displayId || r.noteId || "")));
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
if (cfg.json) {
|
|
380
|
+
outputJson(data);
|
|
381
|
+
}
|
|
382
|
+
else {
|
|
383
|
+
const results = (data.results || []);
|
|
384
|
+
const relCount = data.relationshipsCreated || 0;
|
|
385
|
+
const newTypes = (data.relationshipTypesCreated || []);
|
|
386
|
+
const noun = results.length === 1 ? "note" : "notes";
|
|
387
|
+
console.log(`Created ${results.length} ${noun} (${relCount} relationships)`);
|
|
388
|
+
if (newTypes.length > 0) {
|
|
389
|
+
console.log(` + created ${newTypes.length} new relationship type${newTypes.length === 1 ? "" : "s"}: ${newTypes.join(", ")}`);
|
|
390
|
+
}
|
|
391
|
+
for (const r of results) {
|
|
392
|
+
console.log(` ${r.displayId}: ${r.title || "(untitled)"}`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
catch (error) {
|
|
397
|
+
handleError(error, cfg.json);
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
notes
|
|
401
|
+
.command("search")
|
|
402
|
+
.description("Search notes by text")
|
|
403
|
+
.argument("<query>", "Search query")
|
|
404
|
+
.option("--type <type>", "Filter by note type")
|
|
405
|
+
.option("--limit <n>", "Maximum results", "10")
|
|
406
|
+
.addHelpText("after", `
|
|
407
|
+
Examples:
|
|
408
|
+
$ cnotes notes search "rate limit"
|
|
409
|
+
$ cnotes notes search "onboarding" --type Insight --json
|
|
410
|
+
`)
|
|
411
|
+
.action(async (query, opts) => {
|
|
412
|
+
const cfg = resolveConfig(program.opts());
|
|
413
|
+
const api = new ApiClient(cfg.serverUrl, cfg.token);
|
|
414
|
+
try {
|
|
415
|
+
const notesList = await api.get("/api/notes", {
|
|
416
|
+
workspaceId: cfg.workspaceId,
|
|
417
|
+
search: query,
|
|
418
|
+
types: opts.type,
|
|
419
|
+
excludeContent: "true",
|
|
420
|
+
});
|
|
421
|
+
const limit = parseInt(opts.limit, 10);
|
|
422
|
+
const all = Array.isArray(notesList) ? notesList : [];
|
|
423
|
+
if (cfg.json) {
|
|
424
|
+
const shown = all.slice(0, limit);
|
|
425
|
+
if (all.length > shown.length) {
|
|
426
|
+
console.error(`Showing ${shown.length} of ${all.length} — raise --limit to see more.`);
|
|
427
|
+
}
|
|
428
|
+
outputJson(shown, cfg.fields);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
await displayNotesList(all, limit, cfg, api, "results — select to view:");
|
|
432
|
+
}
|
|
433
|
+
catch (error) {
|
|
434
|
+
handleError(error, cfg.json);
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
notes
|
|
438
|
+
.command("retype")
|
|
439
|
+
.description("Change a note's type (atomic: creates new note, archives old one, cross-links both)")
|
|
440
|
+
.argument("<displayId>", "Display ID of the note to retype (e.g., NOTE-1)")
|
|
441
|
+
.requiredOption("--type <type>", "Target type name (must be an existing supertag)")
|
|
442
|
+
.addHelpText("after", `
|
|
443
|
+
Examples:
|
|
444
|
+
$ cnotes notes retype NOTE-1 --type Insight
|
|
445
|
+
`)
|
|
446
|
+
.action(async (displayId, opts) => {
|
|
447
|
+
const cfg = resolveConfig(program.opts());
|
|
448
|
+
const api = new ApiClient(cfg.serverUrl, cfg.token);
|
|
449
|
+
try {
|
|
450
|
+
const data = await api.post("/api/notes/retype", {
|
|
451
|
+
workspaceId: cfg.workspaceId,
|
|
452
|
+
noteId: displayId,
|
|
453
|
+
targetType: opts.type,
|
|
454
|
+
});
|
|
455
|
+
if (cfg.quiet) {
|
|
456
|
+
outputQuiet(data.newNote.displayId);
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
if (cfg.json) {
|
|
460
|
+
outputJson(data);
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
console.log(`Retyped ${displayId} → ${data.newNote.displayId}`);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
catch (error) {
|
|
467
|
+
handleError(error, cfg.json);
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
notes
|
|
471
|
+
.command("bulk-retype")
|
|
472
|
+
.description("Retype multiple notes to a new type in one operation")
|
|
473
|
+
.requiredOption("--ids <json>", "JSON array of note display IDs or Convex IDs, e.g. '[\"NOTE-1\",\"NOTE-3\"]'")
|
|
474
|
+
.requiredOption("--type <type>", "Target type name (must be an existing supertag)")
|
|
475
|
+
.addHelpText("after", `
|
|
476
|
+
Examples:
|
|
477
|
+
$ cnotes notes bulk-retype --ids '["NOTE-1","NOTE-3"]' --type Insight
|
|
478
|
+
`)
|
|
479
|
+
.action(async (opts) => {
|
|
480
|
+
const cfg = resolveConfig(program.opts());
|
|
481
|
+
const api = new ApiClient(cfg.serverUrl, cfg.token);
|
|
482
|
+
try {
|
|
483
|
+
let ids;
|
|
484
|
+
try {
|
|
485
|
+
ids = JSON.parse(opts.ids);
|
|
486
|
+
}
|
|
487
|
+
catch {
|
|
488
|
+
throw new ValidationError("--ids must be valid JSON array, e.g. '[\"NOTE-1\",\"NOTE-3\"]'");
|
|
489
|
+
}
|
|
490
|
+
if (!Array.isArray(ids) || ids.length === 0) {
|
|
491
|
+
throw new ValidationError("--ids must be a non-empty JSON array of strings");
|
|
492
|
+
}
|
|
493
|
+
const data = await api.post("/api/notes/bulk", {
|
|
494
|
+
action: "bulkRetype",
|
|
495
|
+
workspaceId: cfg.workspaceId,
|
|
496
|
+
noteIds: ids,
|
|
497
|
+
targetType: opts.type,
|
|
498
|
+
});
|
|
499
|
+
if (cfg.json) {
|
|
500
|
+
outputJson(data);
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
const results = (data.results || []);
|
|
504
|
+
const succeeded = results.filter((r) => r.status === "retyped").length;
|
|
505
|
+
const failed = results.filter((r) => r.status === "error").length;
|
|
506
|
+
if (cfg.quiet) {
|
|
507
|
+
results
|
|
508
|
+
.filter((r) => r.status === "retyped")
|
|
509
|
+
.forEach((r) => outputQuiet(String(r.newDisplayId || "")));
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
console.log(`Retyped ${succeeded} notes → ${opts.type}${failed > 0 ? ` (${failed} failed)` : ""}`);
|
|
513
|
+
for (const r of results) {
|
|
514
|
+
if (r.status === "retyped") {
|
|
515
|
+
console.log(` ${r.displayId} → ${r.newDisplayId}`);
|
|
516
|
+
}
|
|
517
|
+
else {
|
|
518
|
+
console.log(` ${r.displayId}: error — ${r.error}`);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
catch (error) {
|
|
523
|
+
handleError(error, cfg.json);
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
notes
|
|
527
|
+
.command("move")
|
|
528
|
+
.description("Move a note to another workspace (atomic: copies content to the target, archives the source, cross-links both)")
|
|
529
|
+
.argument("<displayId>", "Display ID of the note to move (e.g., NOTE-1)")
|
|
530
|
+
.requiredOption("--to <workspaceId>", "Target workspace ID to move the note into")
|
|
531
|
+
.option("--type <type>", "Target type name in the destination workspace (auto-created from the source type if missing; defaults to the source note's type)")
|
|
532
|
+
.addHelpText("after", `
|
|
533
|
+
Examples:
|
|
534
|
+
$ cnotes notes move NOTE-1 --to pd72y6fyhj9mway3y647bv02r183xk5j
|
|
535
|
+
$ cnotes notes move INS-7 --to <workspaceId> --type Insight
|
|
536
|
+
`)
|
|
537
|
+
.action(async (displayId, opts) => {
|
|
538
|
+
const cfg = resolveConfig(program.opts());
|
|
539
|
+
const api = new ApiClient(cfg.serverUrl, cfg.token);
|
|
540
|
+
if (opts.to === cfg.workspaceId) {
|
|
541
|
+
handleError(new ValidationError("Source and target workspaces are the same — use `cnotes notes retype` to change type within a workspace"), cfg.json);
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
try {
|
|
545
|
+
const data = await api.post("/api/notes/move", {
|
|
546
|
+
workspaceId: cfg.workspaceId,
|
|
547
|
+
noteId: displayId,
|
|
548
|
+
targetWorkspaceId: opts.to,
|
|
549
|
+
targetType: opts.type,
|
|
550
|
+
});
|
|
551
|
+
if (cfg.quiet) {
|
|
552
|
+
outputQuiet(data.newNote.displayId);
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
if (cfg.json) {
|
|
556
|
+
outputJson(data);
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
559
|
+
console.log(`Moved ${displayId} → ${data.newNote.displayId} in ${opts.to} (source archived)`);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
catch (error) {
|
|
563
|
+
handleError(error, cfg.json);
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Display a list of notes with interactive selection or plain table.
|
|
569
|
+
*/
|
|
570
|
+
async function displayNotesList(notesList, limit, cfg, api, message) {
|
|
571
|
+
const sliced = Array.isArray(notesList) ? notesList.slice(0, limit) : [];
|
|
572
|
+
if (sliced.length === 0) {
|
|
573
|
+
console.log("No notes found.");
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
if (process.stdout.isTTY) {
|
|
577
|
+
const chosenIndex = await select({
|
|
578
|
+
message: `${sliced.length} ${message}`,
|
|
579
|
+
choices: sliced.map((n, i) => ({
|
|
580
|
+
name: `${String(n.displayId || "").padEnd(14)} ${truncate(String(n.title || "(untitled)"), 52).padEnd(54)} ${n.updatedAt ? timeAgo(n.updatedAt) : ""}`,
|
|
581
|
+
value: i,
|
|
582
|
+
})),
|
|
583
|
+
loop: true,
|
|
584
|
+
});
|
|
585
|
+
const selected = sliced[chosenIndex];
|
|
586
|
+
const displayId = String(selected.displayId || "");
|
|
587
|
+
if (displayId) {
|
|
588
|
+
await viewNoteInteractive(api, cfg.workspaceId, displayId);
|
|
589
|
+
}
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
const rows = sliced.map((n) => [
|
|
593
|
+
String(n.displayId || ""),
|
|
594
|
+
truncate(String(n.title || "(untitled)"), 50),
|
|
595
|
+
n.updatedAt ? timeAgo(n.updatedAt) : "",
|
|
596
|
+
]);
|
|
597
|
+
outputTable(["ID", "TITLE", "UPDATED"], rows);
|
|
598
|
+
if (notesList.length > sliced.length) {
|
|
599
|
+
console.log(`\nShowing ${sliced.length} of ${notesList.length} — raise --limit to see more.`);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Interactive note viewer: fetch note, then let user choose TLDR or Full Content.
|
|
604
|
+
* TLDR is pre-selected so double-enter from list goes straight to summary.
|
|
605
|
+
*/
|
|
606
|
+
async function viewNoteInteractive(api, workspaceId, displayId) {
|
|
607
|
+
const noteData = await fetchNoteData(api, workspaceId, displayId);
|
|
608
|
+
if (!noteData) {
|
|
609
|
+
console.log("Could not fetch note.");
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
const { note, latestVersion } = noteData;
|
|
613
|
+
// Print note header
|
|
614
|
+
console.log(`\n${note.displayId || note._id || note.id}`);
|
|
615
|
+
console.log(`Title: ${note.title || "(untitled)"}`);
|
|
616
|
+
console.log(`Type: ${note.type || "Note"}`);
|
|
617
|
+
if (note.tags && Array.isArray(note.tags) && note.tags.length > 0) {
|
|
618
|
+
console.log(`Tags: ${note.tags.join(", ")}`);
|
|
619
|
+
}
|
|
620
|
+
if (note.updatedAt) {
|
|
621
|
+
console.log(`Updated: ${timeAgo(note.updatedAt)}`);
|
|
622
|
+
}
|
|
623
|
+
const hasSummary = latestVersion?.searchContent || latestVersion?.summary;
|
|
624
|
+
const markdown = (latestVersion?.content || note.content);
|
|
625
|
+
if (!hasSummary && !markdown) {
|
|
626
|
+
console.log("\n(no content)");
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
const viewMode = await select({
|
|
630
|
+
message: "View:",
|
|
631
|
+
choices: [
|
|
632
|
+
{ name: "TLDR", value: "tldr" },
|
|
633
|
+
{ name: "Full Content", value: "full" },
|
|
634
|
+
],
|
|
635
|
+
});
|
|
636
|
+
if (viewMode === "tldr") {
|
|
637
|
+
if (latestVersion?.summary) {
|
|
638
|
+
console.log(`\n${latestVersion.summary}`);
|
|
639
|
+
}
|
|
640
|
+
else if (markdown) {
|
|
641
|
+
// No AI summary — show first ~8 lines of markdown as preview
|
|
642
|
+
const lines = markdown.split("\n").filter(Boolean);
|
|
643
|
+
console.log(`\n${lines.slice(0, 8).join("\n")}${lines.length > 8 ? "\n\n..." : ""}`);
|
|
644
|
+
}
|
|
645
|
+
else {
|
|
646
|
+
console.log("\n(no summary available)");
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
else {
|
|
650
|
+
if (markdown) {
|
|
651
|
+
console.log(`\n${markdown}`);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Fetch note data (metadata + latest version) by display ID.
|
|
657
|
+
*/
|
|
658
|
+
async function fetchNoteData(api, workspaceId, displayId) {
|
|
659
|
+
try {
|
|
660
|
+
const notesList = await api.get("/api/notes", {
|
|
661
|
+
workspaceId,
|
|
662
|
+
exact_id: displayId,
|
|
663
|
+
format: "markdown",
|
|
664
|
+
});
|
|
665
|
+
const note = (Array.isArray(notesList) ? notesList : []).find((n) => n.displayId === displayId) || (Array.isArray(notesList) ? notesList[0] : null);
|
|
666
|
+
if (!note)
|
|
667
|
+
return null;
|
|
668
|
+
// latestVersion may be a nested object (cookie auth) or content may be top-level (CLI auth)
|
|
669
|
+
const lv = note.latestVersion || null;
|
|
670
|
+
return {
|
|
671
|
+
note,
|
|
672
|
+
latestVersion: lv || (note.content ? { content: note.content } : null),
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
catch {
|
|
676
|
+
return null;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
function printNote(note, version) {
|
|
680
|
+
console.log(`\n${note.displayId || note._id || note.id}`);
|
|
681
|
+
console.log(`Title: ${note.title || "(untitled)"}`);
|
|
682
|
+
console.log(`Type: ${note.type || "Note"}`);
|
|
683
|
+
if (note.tags && Array.isArray(note.tags) && note.tags.length > 0) {
|
|
684
|
+
console.log(`Tags: ${note.tags.join(", ")}`);
|
|
685
|
+
}
|
|
686
|
+
if (note.isPinned)
|
|
687
|
+
console.log("Pinned: yes");
|
|
688
|
+
if (note.isArchived) {
|
|
689
|
+
const archivedAt = note.archivedAt;
|
|
690
|
+
console.log(`Archived: ${archivedAt ? timeAgo(archivedAt) : "yes"}`);
|
|
691
|
+
}
|
|
692
|
+
if (note.updatedAt) {
|
|
693
|
+
console.log(`Updated: ${timeAgo(note.updatedAt)}`);
|
|
694
|
+
}
|
|
695
|
+
// Content arrives as a markdown string (server renders TipTap → markdown for ?format=markdown).
|
|
696
|
+
const markdown = (version?.content || note.latestVersion?.content || note.content);
|
|
697
|
+
if (typeof markdown === "string" && markdown.length > 0) {
|
|
698
|
+
console.log(`\n${markdown}`);
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
const searchContent = note.latestVersion?.searchContent || version?.searchContent;
|
|
702
|
+
if (searchContent) {
|
|
703
|
+
console.log(`\n${searchContent}`);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
//# sourceMappingURL=notes.js.map
|