ente-cli 0.1.0 → 0.4.54
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 +274 -0
- package/dist/SKILL.md +344 -0
- package/dist/cli.mjs +16485 -0
- package/dist/cli.mjs.map +7 -0
- package/package.json +28 -15
- package/dist/api.d.ts +0 -221
- package/dist/api.js +0 -182
- package/dist/commands/activity.d.ts +0 -16
- package/dist/commands/activity.js +0 -345
- package/dist/commands/auth.d.ts +0 -7
- package/dist/commands/auth.js +0 -86
- package/dist/commands/entity.d.ts +0 -13
- package/dist/commands/entity.js +0 -205
- package/dist/commands/search.d.ts +0 -3
- package/dist/commands/search.js +0 -13
- package/dist/commands/task.d.ts +0 -3
- package/dist/commands/task.js +0 -305
- package/dist/commands/update.d.ts +0 -7
- package/dist/commands/update.js +0 -104
- package/dist/deadline.d.ts +0 -6
- package/dist/deadline.js +0 -171
- package/dist/format.d.ts +0 -21
- package/dist/format.js +0 -287
- package/dist/guide/index.d.ts +0 -2
- package/dist/guide/index.js +0 -27
- package/dist/guide/topics.d.ts +0 -5
- package/dist/guide/topics.js +0 -208
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -54
- package/dist/resolve.d.ts +0 -11
- package/dist/resolve.js +0 -66
- package/dist/utils.d.ts +0 -4
- package/dist/utils.js +0 -18
package/dist/format.js
DELETED
|
@@ -1,287 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Human-readable output formatting.
|
|
3
|
-
*/
|
|
4
|
-
const TZ = "Europe/Berlin";
|
|
5
|
-
export function formatDate(ts) {
|
|
6
|
-
if (!ts)
|
|
7
|
-
return "";
|
|
8
|
-
const d = new Date(ts);
|
|
9
|
-
return d.toLocaleDateString("de-DE", {
|
|
10
|
-
timeZone: TZ,
|
|
11
|
-
year: "numeric",
|
|
12
|
-
month: "2-digit",
|
|
13
|
-
day: "2-digit",
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
export function formatDateTime(ts) {
|
|
17
|
-
if (!ts)
|
|
18
|
-
return "";
|
|
19
|
-
const d = new Date(ts);
|
|
20
|
-
return d.toLocaleString("de-DE", {
|
|
21
|
-
timeZone: TZ,
|
|
22
|
-
year: "numeric",
|
|
23
|
-
month: "2-digit",
|
|
24
|
-
day: "2-digit",
|
|
25
|
-
hour: "2-digit",
|
|
26
|
-
minute: "2-digit",
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
function truncate(s, max) {
|
|
30
|
-
if (s.length <= max)
|
|
31
|
-
return s;
|
|
32
|
-
return s.slice(0, max - 1) + "…";
|
|
33
|
-
}
|
|
34
|
-
function padEnd(s, len) {
|
|
35
|
-
if (s.length >= len)
|
|
36
|
-
return s;
|
|
37
|
-
return s + " ".repeat(len - s.length);
|
|
38
|
-
}
|
|
39
|
-
// ---------------------------------------------------------------------------
|
|
40
|
-
// Entity list (search results)
|
|
41
|
-
// ---------------------------------------------------------------------------
|
|
42
|
-
function formatRelativeDate(ts) {
|
|
43
|
-
const now = new Date();
|
|
44
|
-
const d = new Date(ts);
|
|
45
|
-
// Compare in Berlin timezone
|
|
46
|
-
const fmt = (date) => date.toLocaleDateString("en-CA", { timeZone: TZ }); // YYYY-MM-DD
|
|
47
|
-
const todayStr = fmt(now);
|
|
48
|
-
const dateStr = fmt(d);
|
|
49
|
-
if (dateStr === todayStr)
|
|
50
|
-
return "today";
|
|
51
|
-
const yesterday = new Date(now);
|
|
52
|
-
yesterday.setDate(yesterday.getDate() - 1);
|
|
53
|
-
if (dateStr === fmt(yesterday))
|
|
54
|
-
return "yesterday";
|
|
55
|
-
// Same year → "Mar 25"; different year → "Dec 2024"
|
|
56
|
-
const nowYear = now.toLocaleDateString("en-US", { timeZone: TZ, year: "numeric" });
|
|
57
|
-
const dYear = d.toLocaleDateString("en-US", { timeZone: TZ, year: "numeric" });
|
|
58
|
-
if (dYear === nowYear) {
|
|
59
|
-
return d.toLocaleDateString("en-US", { timeZone: TZ, month: "short", day: "numeric" });
|
|
60
|
-
}
|
|
61
|
-
return d.toLocaleDateString("en-US", { timeZone: TZ, month: "short", year: "numeric" });
|
|
62
|
-
}
|
|
63
|
-
export function formatEntityList(entities, spaces) {
|
|
64
|
-
if (entities.length === 0)
|
|
65
|
-
return "No entities found.";
|
|
66
|
-
const rows = entities.map((e) => {
|
|
67
|
-
let spaceNames = "";
|
|
68
|
-
if (spaces && e.spaceIds.length > 0) {
|
|
69
|
-
spaceNames = e.spaceIds
|
|
70
|
-
.map((sid) => spaces[sid]?.spaceName ?? sid)
|
|
71
|
-
.join(", ");
|
|
72
|
-
}
|
|
73
|
-
const aliases = e.entity.aliases?.length ? e.entity.aliases : undefined;
|
|
74
|
-
const activityCount = e.activityCount ?? 0;
|
|
75
|
-
const lastActivityAt = e.lastActivityAt;
|
|
76
|
-
return {
|
|
77
|
-
name: e.entity.name,
|
|
78
|
-
type: `(${e.entity.type})`,
|
|
79
|
-
space: spaceNames,
|
|
80
|
-
id: e.entity._id,
|
|
81
|
-
aliases,
|
|
82
|
-
activityCount,
|
|
83
|
-
lastActivityAt,
|
|
84
|
-
};
|
|
85
|
-
});
|
|
86
|
-
const nameWidth = Math.max(...rows.map((r) => r.name.length), 4);
|
|
87
|
-
const typeWidth = Math.max(...rows.map((r) => r.type.length), 4);
|
|
88
|
-
const showSpaces = spaces && rows.some((r) => r.space.length > 0);
|
|
89
|
-
const spaceWidth = showSpaces ? Math.max(...rows.map((r) => r.space.length), 5) : 0;
|
|
90
|
-
const lines = [];
|
|
91
|
-
for (let i = 0; i < rows.length; i++) {
|
|
92
|
-
const r = rows[i];
|
|
93
|
-
let line = `${padEnd(r.name, nameWidth)} ${padEnd(r.type, typeWidth)}`;
|
|
94
|
-
if (showSpaces)
|
|
95
|
-
line += ` ${padEnd(r.space, spaceWidth)}`;
|
|
96
|
-
line += ` ${r.id}`;
|
|
97
|
-
lines.push(line);
|
|
98
|
-
// Second line: aliases, activity count, last activity date
|
|
99
|
-
const parts = [];
|
|
100
|
-
if (r.aliases)
|
|
101
|
-
parts.push(`aka: ${r.aliases.join(", ")}`);
|
|
102
|
-
if (r.activityCount > 0)
|
|
103
|
-
parts.push(`${r.activityCount} activities`);
|
|
104
|
-
if (r.lastActivityAt)
|
|
105
|
-
parts.push(`last: ${formatRelativeDate(r.lastActivityAt)}`);
|
|
106
|
-
if (parts.length > 0) {
|
|
107
|
-
lines.push(` ${parts.join(" · ")}`);
|
|
108
|
-
}
|
|
109
|
-
const openTasks = entities[i].openTasks;
|
|
110
|
-
if (openTasks && openTasks.length > 0) {
|
|
111
|
-
for (const t of openTasks) {
|
|
112
|
-
const label = t.shortId ? `#${t.shortId}` : t.activityId.slice(0, 10);
|
|
113
|
-
lines.push(` Open: ${label} ${t.title || "(untitled)"}`);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
return lines.join("\n");
|
|
118
|
-
}
|
|
119
|
-
// ---------------------------------------------------------------------------
|
|
120
|
-
// Entity detail
|
|
121
|
-
// ---------------------------------------------------------------------------
|
|
122
|
-
export function formatEntityDetail(data) {
|
|
123
|
-
const e = data.entity;
|
|
124
|
-
const lines = [];
|
|
125
|
-
lines.push(`${e.name} (${e.type})`);
|
|
126
|
-
lines.push(`ID: ${e._id}`);
|
|
127
|
-
if (e.aliases && e.aliases.length > 0) {
|
|
128
|
-
lines.push(`Aliases: ${e.aliases.join(", ")}`);
|
|
129
|
-
}
|
|
130
|
-
if (e.tags && e.tags.length > 0) {
|
|
131
|
-
lines.push(`Tags: ${e.tags.join(", ")}`);
|
|
132
|
-
}
|
|
133
|
-
if (e.attributes && Object.keys(e.attributes).length > 0) {
|
|
134
|
-
for (const [k, v] of Object.entries(e.attributes)) {
|
|
135
|
-
lines.push(` ${k}: ${String(v)}`);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
if (data.descriptionPlainText) {
|
|
139
|
-
lines.push("");
|
|
140
|
-
lines.push(truncate(data.descriptionPlainText, 200));
|
|
141
|
-
}
|
|
142
|
-
if (data.pins.length > 0) {
|
|
143
|
-
lines.push("");
|
|
144
|
-
lines.push("Pins:");
|
|
145
|
-
for (const p of data.pins) {
|
|
146
|
-
lines.push(` ${p.name} (${p.type})`);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
if (data.openTasks && data.openTasks.length > 0) {
|
|
150
|
-
lines.push("");
|
|
151
|
-
lines.push("Open tasks:");
|
|
152
|
-
for (const t of data.openTasks) {
|
|
153
|
-
const label = t.shortId ? `#${t.shortId}` : t.activityId.slice(0, 10);
|
|
154
|
-
lines.push(` ${label} ${t.title || "(untitled)"}`);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
if (data.activities.length > 0) {
|
|
158
|
-
lines.push("");
|
|
159
|
-
lines.push("Recent activities:");
|
|
160
|
-
for (const a of data.activities.slice(0, 10)) {
|
|
161
|
-
const prefix = a.shortId ? `#${a.shortId}` : a.type;
|
|
162
|
-
const title = a.title || truncate(a.contentPlainText || "", 50);
|
|
163
|
-
const date = formatDate(a.createdAt);
|
|
164
|
-
lines.push(` ${prefix} ${title} ${date}`);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
return lines.join("\n");
|
|
168
|
-
}
|
|
169
|
-
// ---------------------------------------------------------------------------
|
|
170
|
-
// Task list
|
|
171
|
-
// ---------------------------------------------------------------------------
|
|
172
|
-
export function formatTaskList(tasks, users) {
|
|
173
|
-
if (tasks.length === 0)
|
|
174
|
-
return "No tasks found.";
|
|
175
|
-
const rows = tasks.map((t) => {
|
|
176
|
-
const shortId = t.activity.shortId ? `#${t.activity.shortId}` : t.activity._id.slice(0, 8);
|
|
177
|
-
const status = t.activity.completed ? "✓" : " ";
|
|
178
|
-
const title = t.activity.title || "(untitled)";
|
|
179
|
-
const deadline = formatDate(t.activity.deadline);
|
|
180
|
-
const entities = t.entities.map((e) => e.name).join(", ");
|
|
181
|
-
return { shortId, status, title, deadline, entities };
|
|
182
|
-
});
|
|
183
|
-
const idWidth = Math.max(...rows.map((r) => r.shortId.length), 2);
|
|
184
|
-
const titleWidth = Math.min(Math.max(...rows.map((r) => r.title.length), 5), 50);
|
|
185
|
-
return rows
|
|
186
|
-
.map((r) => `${padEnd(r.shortId, idWidth)} ${r.status} ${padEnd(truncate(r.title, 50), titleWidth)} ${padEnd(r.deadline, 10)} ${r.entities}`)
|
|
187
|
-
.join("\n");
|
|
188
|
-
}
|
|
189
|
-
// ---------------------------------------------------------------------------
|
|
190
|
-
// Task detail
|
|
191
|
-
// ---------------------------------------------------------------------------
|
|
192
|
-
export function formatTaskDetail(task, users) {
|
|
193
|
-
const a = task.activity;
|
|
194
|
-
const lines = [];
|
|
195
|
-
const shortId = a.shortId ? `#${a.shortId}` : a._id;
|
|
196
|
-
const status = a.completed ? "[done]" : "[open]";
|
|
197
|
-
lines.push(`${shortId} ${status} ${a.title || "(untitled)"}`);
|
|
198
|
-
lines.push(`ID: ${a._id}`);
|
|
199
|
-
if (a.deadline) {
|
|
200
|
-
lines.push(`Deadline: ${formatDateTime(a.deadline)}`);
|
|
201
|
-
}
|
|
202
|
-
if (task.entities.length > 0) {
|
|
203
|
-
lines.push(`Entities: ${task.entities.map((e) => e.name).join(", ")}`);
|
|
204
|
-
}
|
|
205
|
-
if (a.assigneeIds && a.assigneeIds.length > 0 && users) {
|
|
206
|
-
const names = a.assigneeIds.map((id) => users[id]?.name || users[id]?.email || id);
|
|
207
|
-
lines.push(`Assignees: ${names.join(", ")}`);
|
|
208
|
-
}
|
|
209
|
-
if (a.contentPlainText) {
|
|
210
|
-
lines.push("");
|
|
211
|
-
lines.push(a.contentPlainText);
|
|
212
|
-
}
|
|
213
|
-
return lines.join("\n");
|
|
214
|
-
}
|
|
215
|
-
// ---------------------------------------------------------------------------
|
|
216
|
-
// Activity list
|
|
217
|
-
// ---------------------------------------------------------------------------
|
|
218
|
-
export function formatActivityList(activities) {
|
|
219
|
-
if (activities.length === 0)
|
|
220
|
-
return "No activities found.";
|
|
221
|
-
const rows = activities.map((a) => {
|
|
222
|
-
const id = a.shortId ? `#${a.shortId}` : a._id.slice(0, 10);
|
|
223
|
-
const type = a.type;
|
|
224
|
-
const title = a.title || truncate(a.contentPlainText || "", 40);
|
|
225
|
-
const date = formatDate(a.createdAt);
|
|
226
|
-
return { id, type, title, date };
|
|
227
|
-
});
|
|
228
|
-
const idWidth = Math.max(...rows.map((r) => r.id.length), 2);
|
|
229
|
-
const typeWidth = Math.max(...rows.map((r) => r.type.length), 4);
|
|
230
|
-
const titleWidth = Math.min(Math.max(...rows.map((r) => r.title.length), 5), 50);
|
|
231
|
-
return rows
|
|
232
|
-
.map((r) => `${padEnd(r.id, idWidth)} ${padEnd(r.type, typeWidth)} ${padEnd(truncate(r.title, 50), titleWidth)} ${r.date}`)
|
|
233
|
-
.join("\n");
|
|
234
|
-
}
|
|
235
|
-
// ---------------------------------------------------------------------------
|
|
236
|
-
// Activity detail
|
|
237
|
-
// ---------------------------------------------------------------------------
|
|
238
|
-
export function formatActivityDetail(activity) {
|
|
239
|
-
const a = activity;
|
|
240
|
-
const lines = [];
|
|
241
|
-
lines.push(`${a.type}: ${a.title || "(untitled)"}`);
|
|
242
|
-
lines.push(`ID: ${a._id}`);
|
|
243
|
-
lines.push(`Created: ${formatDateTime(a.createdAt)}`);
|
|
244
|
-
if (a.deadline) {
|
|
245
|
-
lines.push(`Deadline: ${formatDateTime(a.deadline)}`);
|
|
246
|
-
}
|
|
247
|
-
if (a.completed !== undefined) {
|
|
248
|
-
lines.push(`Status: ${a.completed ? "completed" : "open"}`);
|
|
249
|
-
}
|
|
250
|
-
if (a.attributes && Object.keys(a.attributes).length > 0) {
|
|
251
|
-
lines.push("Attributes:");
|
|
252
|
-
for (const [k, v] of Object.entries(a.attributes)) {
|
|
253
|
-
lines.push(` ${k}: ${String(v)}`);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
if (a.contentPlainText) {
|
|
257
|
-
lines.push("");
|
|
258
|
-
lines.push(a.contentPlainText);
|
|
259
|
-
}
|
|
260
|
-
return lines.join("\n");
|
|
261
|
-
}
|
|
262
|
-
// ---------------------------------------------------------------------------
|
|
263
|
-
// Spaces footer
|
|
264
|
-
// ---------------------------------------------------------------------------
|
|
265
|
-
export function formatSpacesFooter(spaces) {
|
|
266
|
-
const entries = Object.entries(spaces);
|
|
267
|
-
if (entries.length === 0)
|
|
268
|
-
return "";
|
|
269
|
-
const lines = ["\nSpaces:"];
|
|
270
|
-
for (const [id, s] of entries) {
|
|
271
|
-
lines.push(` ${s.spaceName} (${id}) — members: ${s.memberEmails.join(", ")}`);
|
|
272
|
-
}
|
|
273
|
-
return lines.join("\n");
|
|
274
|
-
}
|
|
275
|
-
// ---------------------------------------------------------------------------
|
|
276
|
-
// JSON output
|
|
277
|
-
// ---------------------------------------------------------------------------
|
|
278
|
-
export function jsonOutputWithSpaces(data, spaces) {
|
|
279
|
-
if (typeof data === "object" && data !== null) {
|
|
280
|
-
return JSON.stringify({ ...data, spaces }, null, 2);
|
|
281
|
-
}
|
|
282
|
-
return JSON.stringify({ data, spaces }, null, 2);
|
|
283
|
-
}
|
|
284
|
-
export function jsonOutput(data) {
|
|
285
|
-
return JSON.stringify(data, null, 2);
|
|
286
|
-
}
|
|
287
|
-
//# sourceMappingURL=format.js.map
|
package/dist/guide/index.d.ts
DELETED
package/dist/guide/index.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { TOPICS, TOPIC_NAMES } from "./topics.js";
|
|
2
|
-
export function registerGuideCommand(program) {
|
|
3
|
-
program
|
|
4
|
-
.command("guide [topic]")
|
|
5
|
-
.description("Show usage guide. Topics: " + TOPIC_NAMES.join(", "))
|
|
6
|
-
.action((topic) => {
|
|
7
|
-
if (!topic) {
|
|
8
|
-
// Show overview + list of topics
|
|
9
|
-
const overview = TOPICS.overview;
|
|
10
|
-
console.log(`\n ${overview.title}\n`);
|
|
11
|
-
console.log(overview.content);
|
|
12
|
-
console.log(`\nTopics:\n${TOPIC_NAMES.filter((t) => t !== "overview")
|
|
13
|
-
.map((t) => ` ${t.padEnd(12)} ${TOPICS[t].title}`)
|
|
14
|
-
.join("\n")}`);
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
const key = topic.toLowerCase();
|
|
18
|
-
const entry = TOPICS[key];
|
|
19
|
-
if (!entry) {
|
|
20
|
-
console.error(`Unknown topic: '${topic}'. Available: ${TOPIC_NAMES.join(", ")}`);
|
|
21
|
-
process.exit(1);
|
|
22
|
-
}
|
|
23
|
-
console.log(`\n ${entry.title}\n`);
|
|
24
|
-
console.log(entry.content);
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
//# sourceMappingURL=index.js.map
|
package/dist/guide/topics.d.ts
DELETED
package/dist/guide/topics.js
DELETED
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
export const TOPICS = {
|
|
2
|
-
overview: {
|
|
3
|
-
title: "ente CLI — Quick Reference",
|
|
4
|
-
content: `ente is a shared knowledge graph for people, orgs, projects, topics, and their activities.
|
|
5
|
-
|
|
6
|
-
Core concepts:
|
|
7
|
-
• Entities (nouns): person, org, project, topic
|
|
8
|
-
• Activities (verbs): note, conversation, call, message, task
|
|
9
|
-
• Pins: directional links between entities ("Sarah works at Acme")
|
|
10
|
-
• Aliases: searchable alternative names on entities
|
|
11
|
-
|
|
12
|
-
Every activity belongs to one or more entities. Always link ALL relevant entities.
|
|
13
|
-
|
|
14
|
-
Commands:
|
|
15
|
-
ente search <query> Fuzzy search entities
|
|
16
|
-
ente entity create|get|update|pin|unpin
|
|
17
|
-
ente task list|get|create|update|done|assign|unassign
|
|
18
|
-
ente activity create|list|get|update|delete
|
|
19
|
-
ente guide [topic] This help
|
|
20
|
-
|
|
21
|
-
Run \`ente <command> --help\` for detailed usage on any command.
|
|
22
|
-
Run \`ente guide <topic>\` for deep dives: entities, tasks, activities, search, patterns`,
|
|
23
|
-
},
|
|
24
|
-
entities: {
|
|
25
|
-
title: "Entities — People, Orgs, Projects, Topics",
|
|
26
|
-
content: `Types: person | org | project | topic
|
|
27
|
-
|
|
28
|
-
Creating entities:
|
|
29
|
-
ente entity create "Jane Doe" --type person --pin "Acme Corp" --alias "JD"
|
|
30
|
-
|
|
31
|
-
⚠ ALWAYS search before creating:
|
|
32
|
-
ente search "Jane Doe"
|
|
33
|
-
Duplicates are hard to clean up. Even if you think an entity doesn't exist, search first.
|
|
34
|
-
|
|
35
|
-
Aliases:
|
|
36
|
-
Searchable alternative names. Add them when entities have common abbreviations:
|
|
37
|
-
ente entity update "Helmholtz-Zentrum" --alias-add "HZI"
|
|
38
|
-
|
|
39
|
-
Proactively add aliases when you notice someone being referred to by a nickname,
|
|
40
|
-
abbreviation, or alternate name. This prevents duplicate entity creation.
|
|
41
|
-
|
|
42
|
-
Examples of good aliases:
|
|
43
|
-
• "TiVA" → alias for "Tierversuchsantrags-Software"
|
|
44
|
-
• "Val" → alias for "Valentin Döring"
|
|
45
|
-
• "KI" → alias for "AI Strategy" topic
|
|
46
|
-
|
|
47
|
-
Pins (relationships):
|
|
48
|
-
ente entity pin "Jane Doe" "Acme Corp" # Jane works at Acme
|
|
49
|
-
|
|
50
|
-
When creating a new person, immediately pin them to their org + relevant projects.
|
|
51
|
-
Don't leave unlinked people.
|
|
52
|
-
|
|
53
|
-
Attributes:
|
|
54
|
-
Free key-value pairs on entities (email, phone, role, etc.):
|
|
55
|
-
ente entity update "Jane" --attr email=jane@acme.com --attr role=CEO
|
|
56
|
-
|
|
57
|
-
Attributes are merged on update — existing keys are preserved.`,
|
|
58
|
-
},
|
|
59
|
-
tasks: {
|
|
60
|
-
title: "Tasks — Deadlines, Assignments, Proposals",
|
|
61
|
-
content: `Tasks are activities with type "task". They have deadline, completed, assigneeIds.
|
|
62
|
-
|
|
63
|
-
Creating tasks:
|
|
64
|
-
ente task create "Reply to Ashley" --entity "Ashley" --entity "TiVA" --deadline "tomorrow"
|
|
65
|
-
ente task create "Fix bug" --deadline "+3 days" --attr github-issue=https://...
|
|
66
|
-
|
|
67
|
-
Every task needs a deadline. If not specified, defaults to tomorrow 22:00 Berlin.
|
|
68
|
-
|
|
69
|
-
Deadlines (human-friendly):
|
|
70
|
-
"tomorrow" → tomorrow 22:00 Berlin
|
|
71
|
-
"tomorrow 14:00" → tomorrow 14:00 Berlin
|
|
72
|
-
"today" → today 22:00 Berlin
|
|
73
|
-
"+3 days" / "+1 week" → relative, 22:00 Berlin
|
|
74
|
-
"2026-03-15" → that date 22:00 Berlin
|
|
75
|
-
"2026-03-15 14:00" → that date+time Berlin
|
|
76
|
-
|
|
77
|
-
ShortIds:
|
|
78
|
-
Tasks get a #N number when they get a deadline. Use it everywhere:
|
|
79
|
-
ente task get 5
|
|
80
|
-
ente task done 5
|
|
81
|
-
ente task update 5 --content "new proposal"
|
|
82
|
-
|
|
83
|
-
Task content convention:
|
|
84
|
-
When creating tasks from conversations or brain dumps:
|
|
85
|
-
• Put context under a "Context" heading
|
|
86
|
-
• Do NOT add an "Actions" section — the heartbeat pipeline writes that
|
|
87
|
-
• Do NOT set proposal-ready=true — only the heartbeat sets this
|
|
88
|
-
|
|
89
|
-
Example:
|
|
90
|
-
ente task create "Reply to Louisa" --entity "Louisa" --entity "Preclinics" \\
|
|
91
|
-
--deadline "tomorrow" --attr gmail-thread-id=abc123 \\
|
|
92
|
-
--content "Context: Louisa reported a crash in Predator when creating a substance."
|
|
93
|
-
|
|
94
|
-
Tasks as running lists:
|
|
95
|
-
Shopping lists, checklists, idea collections — model these as tasks, not notes.
|
|
96
|
-
Why: tasks have shortIds (#N), show in the active working set, and can be
|
|
97
|
-
completed when done. "Add X to the shopping list" → find the open task, append.
|
|
98
|
-
|
|
99
|
-
Example:
|
|
100
|
-
ente task create "Einkaufsliste Foudrais" --entity "Foudrais" --deadline "tomorrow" \\
|
|
101
|
-
--content "- Hausschuhe\\n- Wasser\\n- Lebensmittel"
|
|
102
|
-
|
|
103
|
-
# Later: append items
|
|
104
|
-
ente task update 25 --content "$(ente task get 25 --json | jq -r .content)\\n- Milch"
|
|
105
|
-
|
|
106
|
-
# Done shopping:
|
|
107
|
-
ente task done 25
|
|
108
|
-
|
|
109
|
-
Assignees:
|
|
110
|
-
ente task assign 5 <userId> # add assignee (must be org member)
|
|
111
|
-
ente task unassign 5 <userId> # remove assignee
|
|
112
|
-
|
|
113
|
-
Default: unassigned. In personal orgs, all tasks are implicitly yours.
|
|
114
|
-
Use assignees in shared orgs for explicit responsibility.
|
|
115
|
-
|
|
116
|
-
Completing:
|
|
117
|
-
ente task done 5 # marks completed, frees shortId`,
|
|
118
|
-
},
|
|
119
|
-
activities: {
|
|
120
|
-
title: "Activities — Notes, Conversations, Calls, Messages",
|
|
121
|
-
content: `Activity types: note | conversation | call | message | task
|
|
122
|
-
|
|
123
|
-
Creating activities:
|
|
124
|
-
ente activity create --type conversation --title "Call with Erik" \\
|
|
125
|
-
--entity "Erik Stahl" --entity "Preclinics" \\
|
|
126
|
-
--content "Discussed QM system. Follow up on export feature."
|
|
127
|
-
|
|
128
|
-
Golden rule:
|
|
129
|
-
When logging a meeting with a person who works at a company, link BOTH.
|
|
130
|
-
If Sarah is pinned to Acme, the activity gets Sarah + Acme as entityIds.
|
|
131
|
-
|
|
132
|
-
Think: who was involved? What org? What project? Link them all.
|
|
133
|
-
|
|
134
|
-
Listing:
|
|
135
|
-
ente activity list --entity "Erik" --hours 168 # last 7 days
|
|
136
|
-
ente activity list --limit 20 # most recent
|
|
137
|
-
|
|
138
|
-
Title rules:
|
|
139
|
-
• Never put a date in the title — activities have timestamps
|
|
140
|
-
• Keep titles concise and descriptive
|
|
141
|
-
|
|
142
|
-
Content:
|
|
143
|
-
Use markdown. "content" is the preferred field.
|
|
144
|
-
For activities logging reflections or thoughts, always link to the person's
|
|
145
|
-
diary entity + all relevant topic/org/person entities.`,
|
|
146
|
-
},
|
|
147
|
-
search: {
|
|
148
|
-
title: "Search — Fuzzy, Multi-Strategy",
|
|
149
|
-
content: `Search is fuzzy and multi-strategy. A single query searches across:
|
|
150
|
-
|
|
151
|
-
1. Name (exact > prefix > Levenshtein fuzzy)
|
|
152
|
-
2. Aliases (same fuzzy matching, slightly lower rank than name)
|
|
153
|
-
3. Attributes (email, phone — use local part only, not full address)
|
|
154
|
-
4. Description text
|
|
155
|
-
|
|
156
|
-
Usage:
|
|
157
|
-
ente search "Ashley" # fuzzy name search
|
|
158
|
-
ente search "Ashley" --type person # filter by type
|
|
159
|
-
ente search "HZI" # finds via alias
|
|
160
|
-
ente search "lernst" # finds via email attribute
|
|
161
|
-
|
|
162
|
-
Tips:
|
|
163
|
-
• Typos work: "Scharkowsky" finds "Scharkowski"
|
|
164
|
-
• Name stripping: "Haschke" finds "Guido Haschke - Tierschutzbeauftragter"
|
|
165
|
-
• For email search, use local part only (e.g. "lernst" not "lernst@ukaachen.de")
|
|
166
|
-
• Use --json for full API response with all entity details`,
|
|
167
|
-
},
|
|
168
|
-
patterns: {
|
|
169
|
-
title: "Common Patterns",
|
|
170
|
-
content: `Log a meeting:
|
|
171
|
-
1. ente search "person name" # find entity, check pins
|
|
172
|
-
2. ente activity create --type conversation --title "..." \\
|
|
173
|
-
--entity "person" --entity "their org" --content "..."
|
|
174
|
-
3. If follow-ups: ente task create "..." --entity "..." --deadline "..."
|
|
175
|
-
|
|
176
|
-
Prepare for a meeting:
|
|
177
|
-
1. ente entity get "person name" # see recent activities + pins
|
|
178
|
-
2. ente task list --entity "person" # open tasks related to them
|
|
179
|
-
3. Summarize: recent context + open actions + relationships
|
|
180
|
-
|
|
181
|
-
Brain dump → tasks:
|
|
182
|
-
1. For each actionable item:
|
|
183
|
-
ente task create "title" --entity "..." --deadline "..." --content "Context: ..."
|
|
184
|
-
2. Don't add Actions section — heartbeat pipeline does that
|
|
185
|
-
|
|
186
|
-
Email → task:
|
|
187
|
-
ente task create "Re: Subject" --entity "sender" --entity "their org" \\
|
|
188
|
-
--deadline "tomorrow" --attr gmail-thread-id=<threadId> \\
|
|
189
|
-
--content "Context: sender asked about X. Need to reply with Y."
|
|
190
|
-
|
|
191
|
-
Find-or-create entity:
|
|
192
|
-
1. ente search "name"
|
|
193
|
-
2. If not found: ente entity create "name" --type person --pin "org" --alias "nick"
|
|
194
|
-
3. Always search first — duplicates are painful
|
|
195
|
-
|
|
196
|
-
Memory split:
|
|
197
|
-
┌─────────────────────────────┬──────────────────────────────┐
|
|
198
|
-
│ Belongs in ente │ Belongs in MEMORY.md │
|
|
199
|
-
├─────────────────────────────┼──────────────────────────────┤
|
|
200
|
-
│ People, meetings, orgs │ How I (the agent) work │
|
|
201
|
-
│ Tasks and follow-ups │ System config, env setup │
|
|
202
|
-
│ Project status │ Agent preferences │
|
|
203
|
-
│ Relationship history │ Dev environment details │
|
|
204
|
-
└─────────────────────────────┴──────────────────────────────┘`,
|
|
205
|
-
},
|
|
206
|
-
};
|
|
207
|
-
export const TOPIC_NAMES = Object.keys(TOPICS);
|
|
208
|
-
//# sourceMappingURL=topics.js.map
|
package/dist/index.d.ts
DELETED
package/dist/index.js
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { Command } from "commander";
|
|
3
|
-
import { EnteClient, getConfig } from "./api.js";
|
|
4
|
-
import { registerSearchCommand } from "./commands/search.js";
|
|
5
|
-
import { registerEntityCommands } from "./commands/entity.js";
|
|
6
|
-
import { registerTaskCommands } from "./commands/task.js";
|
|
7
|
-
import { registerActivityCommands, runActivityList } from "./commands/activity.js";
|
|
8
|
-
import { registerGuideCommand } from "./guide/index.js";
|
|
9
|
-
import { registerAuth } from "./commands/auth.js";
|
|
10
|
-
import { registerUpdate, checkForUpdates } from "./commands/update.js";
|
|
11
|
-
const program = new Command();
|
|
12
|
-
program
|
|
13
|
-
.name("ente")
|
|
14
|
-
.description("CLI for ente — relationship manager")
|
|
15
|
-
.version("0.1.0");
|
|
16
|
-
let clientInstance;
|
|
17
|
-
function getClient() {
|
|
18
|
-
if (!clientInstance) {
|
|
19
|
-
clientInstance = new EnteClient(getConfig());
|
|
20
|
-
}
|
|
21
|
-
return clientInstance;
|
|
22
|
-
}
|
|
23
|
-
registerAuth(program);
|
|
24
|
-
registerUpdate(program);
|
|
25
|
-
registerSearchCommand(program, getClient);
|
|
26
|
-
registerEntityCommands(program, getClient);
|
|
27
|
-
registerTaskCommands(program, getClient);
|
|
28
|
-
registerActivityCommands(program, getClient);
|
|
29
|
-
registerGuideCommand(program);
|
|
30
|
-
// -- Top-level convenience alias: `ente tasks` ----------------------------
|
|
31
|
-
program
|
|
32
|
-
.command("tasks")
|
|
33
|
-
.description("List open tasks sorted by deadline (alias for activity list --type task --only-open --sort deadline)")
|
|
34
|
-
.option("--json", "Output raw JSON")
|
|
35
|
-
.action(async (opts) => {
|
|
36
|
-
const client = getClient();
|
|
37
|
-
await runActivityList(client, {
|
|
38
|
-
type: "task",
|
|
39
|
-
onlyOpen: true,
|
|
40
|
-
sort: "deadline",
|
|
41
|
-
json: opts.json,
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
// Check for updates in background (non-blocking, cached 24h)
|
|
45
|
-
// Skip for auth/update commands to avoid noise
|
|
46
|
-
const subcommand = process.argv[2];
|
|
47
|
-
if (subcommand && !["auth", "update", "--help", "-h", "--version", "-V"].includes(subcommand)) {
|
|
48
|
-
checkForUpdates().catch(() => { }); // fire and forget
|
|
49
|
-
}
|
|
50
|
-
program.parseAsync(process.argv).catch((err) => {
|
|
51
|
-
console.error(`Error: ${err.message}`);
|
|
52
|
-
process.exit(1);
|
|
53
|
-
});
|
|
54
|
-
//# sourceMappingURL=index.js.map
|
package/dist/resolve.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Smart entity resolution: ID pass-through or fuzzy search.
|
|
3
|
-
*/
|
|
4
|
-
import { EnteClient } from "./api.js";
|
|
5
|
-
export declare function resolveEntity(client: EnteClient, input: string): Promise<string>;
|
|
6
|
-
export declare function resolveEntities(client: EnteClient, inputs: string[]): Promise<string[]>;
|
|
7
|
-
/**
|
|
8
|
-
* Resolve a space name or ID to a spaceId.
|
|
9
|
-
* Searches the spaces map from the entities API.
|
|
10
|
-
*/
|
|
11
|
-
export declare function resolveSpaceByName(client: EnteClient, input: string): Promise<string>;
|
package/dist/resolve.js
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Smart entity resolution: ID pass-through or fuzzy search.
|
|
3
|
-
*/
|
|
4
|
-
/** Convex IDs typically start with a table-hash prefix and are 32+ chars */
|
|
5
|
-
function looksLikeConvexId(input) {
|
|
6
|
-
return /^[a-z]\w{0,3}[0-9a-z]{28,}$/i.test(input) && input.length >= 20;
|
|
7
|
-
}
|
|
8
|
-
export async function resolveEntity(client, input) {
|
|
9
|
-
if (looksLikeConvexId(input)) {
|
|
10
|
-
return input;
|
|
11
|
-
}
|
|
12
|
-
const res = await client.searchEntities(input, { limit: 10 });
|
|
13
|
-
const matches = res.data.entities;
|
|
14
|
-
if (matches.length === 0) {
|
|
15
|
-
throw new Error(`No entity found matching '${input}'.`);
|
|
16
|
-
}
|
|
17
|
-
if (matches.length === 1) {
|
|
18
|
-
return matches[0].entity._id;
|
|
19
|
-
}
|
|
20
|
-
// Check for exact name match (case-insensitive)
|
|
21
|
-
const exact = matches.filter((m) => m.entity.name.toLowerCase() === input.toLowerCase());
|
|
22
|
-
if (exact.length === 1) {
|
|
23
|
-
return exact[0].entity._id;
|
|
24
|
-
}
|
|
25
|
-
const list = matches
|
|
26
|
-
.slice(0, 5)
|
|
27
|
-
.map((m) => ` ${m.entity.name} (${m.entity.type}) ${m.entity._id}`)
|
|
28
|
-
.join("\n");
|
|
29
|
-
throw new Error(`Ambiguous match for '${input}':\n${list}\nUse --entity <ID> to be specific.`);
|
|
30
|
-
}
|
|
31
|
-
export async function resolveEntities(client, inputs) {
|
|
32
|
-
return Promise.all(inputs.map((input) => resolveEntity(client, input)));
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Resolve a space name or ID to a spaceId.
|
|
36
|
-
* Searches the spaces map from the entities API.
|
|
37
|
-
*/
|
|
38
|
-
export async function resolveSpaceByName(client, input) {
|
|
39
|
-
if (looksLikeConvexId(input)) {
|
|
40
|
-
return input;
|
|
41
|
-
}
|
|
42
|
-
const res = await client.searchEntities(undefined, { limit: 200 });
|
|
43
|
-
const spaces = res.data.spaces;
|
|
44
|
-
const lowerInput = input.toLowerCase();
|
|
45
|
-
// Exact match first
|
|
46
|
-
for (const [spaceId, space] of Object.entries(spaces)) {
|
|
47
|
-
if (space.spaceName.toLowerCase() === lowerInput) {
|
|
48
|
-
return spaceId;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
// Partial match — collect all and require unambiguous result
|
|
52
|
-
const partialMatches = [];
|
|
53
|
-
for (const [spaceId, space] of Object.entries(spaces)) {
|
|
54
|
-
if (space.spaceName.toLowerCase().includes(lowerInput)) {
|
|
55
|
-
partialMatches.push([spaceId, space.spaceName]);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
if (partialMatches.length === 1)
|
|
59
|
-
return partialMatches[0][0];
|
|
60
|
-
if (partialMatches.length > 1) {
|
|
61
|
-
const list = partialMatches.map(([id, name]) => ` ${name} ${id}`).join("\n");
|
|
62
|
-
throw new Error(`Ambiguous space match for '${input}':\n${list}\nUse --space <ID> to be specific.`);
|
|
63
|
-
}
|
|
64
|
-
throw new Error(`No space found matching '${input}'.`);
|
|
65
|
-
}
|
|
66
|
-
//# sourceMappingURL=resolve.js.map
|
package/dist/utils.d.ts
DELETED