stratanodex 0.1.1 → 0.1.3
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/dist/api/client.js +1 -0
- package/dist/commands/executor.js +189 -150
- package/dist/commands/registry.js +72 -147
- package/dist/tui/screens/LoginScreen.js +3 -5
- package/package.json +1 -1
package/dist/api/client.js
CHANGED
|
@@ -87,6 +87,7 @@ export const createCliSession = () => http
|
|
|
87
87
|
.post('/api/auth/cli-session')
|
|
88
88
|
.then((r) => ({
|
|
89
89
|
code: r.data.code,
|
|
90
|
+
url: r.data.url,
|
|
90
91
|
}));
|
|
91
92
|
export const pollCliSession = (code) => http.get(`/api/auth/cli-session/${code}`).then((r) => {
|
|
92
93
|
// Backend returns { pending: true } or { token: string }
|
|
@@ -187,8 +187,185 @@ export async function executeCommand(input, _screen, ctx) {
|
|
|
187
187
|
// ─── NODES commands ──────────────────────────────────────────────────────
|
|
188
188
|
const nodes = ctx.currentNodes ?? [];
|
|
189
189
|
const listId = ctx.listId;
|
|
190
|
-
|
|
191
|
-
|
|
190
|
+
// Supported property keywords for node property operations
|
|
191
|
+
const PROP_RE = 'title|start-date|end-date|start-time|end-time|tag|note|status|position|priority';
|
|
192
|
+
// Resolve a node ref — '...' means the currently selected/viewed node
|
|
193
|
+
const resolveRef = (ref) => {
|
|
194
|
+
if (ref.trim() === '...') {
|
|
195
|
+
if (!ctx.selectedNodeId)
|
|
196
|
+
return undefined;
|
|
197
|
+
return flattenTree(nodes).find((n) => n.id === ctx.selectedNodeId);
|
|
198
|
+
}
|
|
199
|
+
return resolveNode(ref, nodes);
|
|
200
|
+
};
|
|
201
|
+
// ── 1. /VERB node REF PROPERTY[: VALUE] (spec format) ───────────────────
|
|
202
|
+
// Must be checked first to prevent property cmds being caught by simpler patterns
|
|
203
|
+
const propMatch = trimmed.match(new RegExp(`^\\/(edit|add|delete) node (.+?) (${PROP_RE})(?::\\s*(.*))?$`, 'i'));
|
|
204
|
+
if (propMatch) {
|
|
205
|
+
const verb = propMatch[1].toLowerCase();
|
|
206
|
+
const ref = propMatch[2].trim();
|
|
207
|
+
const prop = propMatch[3].toLowerCase();
|
|
208
|
+
const val = (propMatch[4] ?? '').trim();
|
|
209
|
+
const node = resolveRef(ref);
|
|
210
|
+
if (!node)
|
|
211
|
+
return { ok: false, message: `Node "${ref}" not found.` };
|
|
212
|
+
try {
|
|
213
|
+
switch (prop) {
|
|
214
|
+
case 'title': {
|
|
215
|
+
if (verb === 'delete')
|
|
216
|
+
return { ok: false, message: 'Cannot delete a title.' };
|
|
217
|
+
if (!val)
|
|
218
|
+
return { ok: false, message: 'New title required.' };
|
|
219
|
+
await updateNode(node.id, { title: val });
|
|
220
|
+
ctx.refetch?.();
|
|
221
|
+
return { ok: true, message: `✓ Renamed to "${val}"` };
|
|
222
|
+
}
|
|
223
|
+
case 'status': {
|
|
224
|
+
const statusMap = {
|
|
225
|
+
'NOT-DONE': 'TODO',
|
|
226
|
+
TODO: 'TODO',
|
|
227
|
+
'IN-PROGRESS': 'IN_PROGRESS',
|
|
228
|
+
IN_PROGRESS: 'IN_PROGRESS',
|
|
229
|
+
DONE: 'DONE',
|
|
230
|
+
};
|
|
231
|
+
if (verb === 'delete') {
|
|
232
|
+
await updateNode(node.id, { status: 'TODO' });
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
const mapped = statusMap[val.toUpperCase().replace(/-/g, '_')] ?? statusMap[val.toUpperCase()];
|
|
236
|
+
if (!mapped)
|
|
237
|
+
return { ok: false, message: 'Invalid status. Use NOT-DONE, IN-PROGRESS, or DONE.' };
|
|
238
|
+
await updateNode(node.id, { status: mapped });
|
|
239
|
+
}
|
|
240
|
+
ctx.refetch?.();
|
|
241
|
+
return { ok: true, message: '✓ Status updated' };
|
|
242
|
+
}
|
|
243
|
+
case 'priority': {
|
|
244
|
+
const priorityMap = {
|
|
245
|
+
LOW: 'LOW',
|
|
246
|
+
MEDIUM: 'MEDIUM',
|
|
247
|
+
HIGH: 'HIGH',
|
|
248
|
+
};
|
|
249
|
+
if (verb === 'delete') {
|
|
250
|
+
await updateNode(node.id, { priority: null });
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
const mapped = priorityMap[val.toUpperCase()];
|
|
254
|
+
if (!mapped)
|
|
255
|
+
return { ok: false, message: 'Invalid priority. Use LOW, MEDIUM, or HIGH.' };
|
|
256
|
+
await updateNode(node.id, { priority: mapped });
|
|
257
|
+
}
|
|
258
|
+
ctx.refetch?.();
|
|
259
|
+
return { ok: true, message: '✓ Priority updated' };
|
|
260
|
+
}
|
|
261
|
+
case 'start-date': {
|
|
262
|
+
if (verb === 'delete') {
|
|
263
|
+
await updateNode(node.id, { startAt: null });
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
const iso = parseDate(val);
|
|
267
|
+
if (!iso)
|
|
268
|
+
return { ok: false, message: 'Invalid date. Use DD-MM-YYYY.' };
|
|
269
|
+
await updateNode(node.id, { startAt: iso });
|
|
270
|
+
}
|
|
271
|
+
ctx.refetch?.();
|
|
272
|
+
return { ok: true, message: '✓ Start date updated' };
|
|
273
|
+
}
|
|
274
|
+
case 'end-date': {
|
|
275
|
+
if (verb === 'delete') {
|
|
276
|
+
await updateNode(node.id, { endAt: null });
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
const iso = parseDate(val);
|
|
280
|
+
if (!iso)
|
|
281
|
+
return { ok: false, message: 'Invalid date. Use DD-MM-YYYY.' };
|
|
282
|
+
await updateNode(node.id, { endAt: iso });
|
|
283
|
+
}
|
|
284
|
+
ctx.refetch?.();
|
|
285
|
+
return { ok: true, message: '✓ End date updated' };
|
|
286
|
+
}
|
|
287
|
+
case 'start-time': {
|
|
288
|
+
if (verb === 'delete') {
|
|
289
|
+
await updateNode(node.id, { startAt: null });
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
const iso = parseTime(val, node.startAt ?? undefined);
|
|
293
|
+
if (!iso)
|
|
294
|
+
return { ok: false, message: 'Invalid time. Use HH:MM AM/PM.' };
|
|
295
|
+
await updateNode(node.id, { startAt: iso });
|
|
296
|
+
}
|
|
297
|
+
ctx.refetch?.();
|
|
298
|
+
return { ok: true, message: '✓ Start time updated' };
|
|
299
|
+
}
|
|
300
|
+
case 'end-time': {
|
|
301
|
+
if (verb === 'delete') {
|
|
302
|
+
await updateNode(node.id, { endAt: null });
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
const iso = parseTime(val, node.endAt ?? undefined);
|
|
306
|
+
if (!iso)
|
|
307
|
+
return { ok: false, message: 'Invalid time. Use HH:MM AM/PM.' };
|
|
308
|
+
await updateNode(node.id, { endAt: iso });
|
|
309
|
+
}
|
|
310
|
+
ctx.refetch?.();
|
|
311
|
+
return { ok: true, message: '✓ End time updated' };
|
|
312
|
+
}
|
|
313
|
+
case 'tag': {
|
|
314
|
+
// Tags by name need a tag ID lookup
|
|
315
|
+
return {
|
|
316
|
+
ok: false,
|
|
317
|
+
message: 'Tag operations require a tag ID. Use /tags to list available tags.',
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
case 'note': {
|
|
321
|
+
if (verb === 'delete') {
|
|
322
|
+
await updateNode(node.id, { notes: null });
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
if (!val)
|
|
326
|
+
return { ok: false, message: 'Note text required.' };
|
|
327
|
+
await updateNode(node.id, { notes: val });
|
|
328
|
+
}
|
|
329
|
+
ctx.refetch?.();
|
|
330
|
+
return { ok: true, message: '✓ Note updated' };
|
|
331
|
+
}
|
|
332
|
+
case 'position': {
|
|
333
|
+
const pos = parseInt(val, 10);
|
|
334
|
+
if (isNaN(pos))
|
|
335
|
+
return { ok: false, message: 'Position must be a number.' };
|
|
336
|
+
await moveNode(node.id, node.parentId ?? null, pos - 1);
|
|
337
|
+
ctx.refetch?.();
|
|
338
|
+
return { ok: true, message: `✓ Node moved to position ${pos}` };
|
|
339
|
+
}
|
|
340
|
+
default:
|
|
341
|
+
return { ok: false, message: `Unknown property "${prop}".` };
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
catch (e) {
|
|
345
|
+
return { ok: false, message: e.message };
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
// ── 2. /edit node REF: NEW-TITLE (title rename shorthand) ───────────────
|
|
349
|
+
const renameMatch = trimmed.match(/^\/edit node (.+?):\s*(.+)$/);
|
|
350
|
+
if (renameMatch) {
|
|
351
|
+
const ref = renameMatch[1].trim();
|
|
352
|
+
const newTitle = renameMatch[2].trim();
|
|
353
|
+
const node = resolveRef(ref);
|
|
354
|
+
if (!node)
|
|
355
|
+
return { ok: false, message: `Node "${ref}" not found.` };
|
|
356
|
+
try {
|
|
357
|
+
await updateNode(node.id, { title: newTitle });
|
|
358
|
+
ctx.refetch?.();
|
|
359
|
+
return { ok: true, message: `✓ Renamed to "${newTitle}"` };
|
|
360
|
+
}
|
|
361
|
+
catch (e) {
|
|
362
|
+
return { ok: false, message: e.message };
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
// ── 3. /add node: TITLE or /add node TITLE (create root node) ─────────
|
|
366
|
+
const createNodeMatch = trimmed.match(/^\/add node(?::\s*|\s+)(.+)$/);
|
|
367
|
+
if (createNodeMatch) {
|
|
368
|
+
const title = createNodeMatch[1].trim();
|
|
192
369
|
if (!title)
|
|
193
370
|
return { ok: false, message: 'Node title required.' };
|
|
194
371
|
if (!listId)
|
|
@@ -202,19 +379,18 @@ export async function executeCommand(input, _screen, ctx) {
|
|
|
202
379
|
return { ok: false, message: e.message };
|
|
203
380
|
}
|
|
204
381
|
}
|
|
382
|
+
// ── 4. /add sub-node [INDEX] TITLE ──────────────────────────────────────
|
|
205
383
|
if (trimmed.startsWith('/add sub-node ')) {
|
|
206
384
|
const rest = trimmed.slice('/add sub-node '.length).trim();
|
|
207
385
|
if (!rest)
|
|
208
386
|
return { ok: false, message: 'Sub-node title required.' };
|
|
209
387
|
if (!listId)
|
|
210
388
|
return { ok: false, message: 'Not inside a list.' };
|
|
211
|
-
// Check if first word is a numeric index like "1", "1.2", "1.2.1"
|
|
212
389
|
const parts = rest.split(/\s+/);
|
|
213
390
|
const indexPattern = /^\d+(\.\d+)*$/;
|
|
214
391
|
let parentId;
|
|
215
392
|
let title;
|
|
216
393
|
if (indexPattern.test(parts[0])) {
|
|
217
|
-
// /add sub-node <index> <title>
|
|
218
394
|
const idx = parts[0];
|
|
219
395
|
title = parts.slice(1).join(' ');
|
|
220
396
|
if (!title)
|
|
@@ -236,7 +412,6 @@ export async function executeCommand(input, _screen, ctx) {
|
|
|
236
412
|
return { ok: false, message: `Node "${idx}" not found.` };
|
|
237
413
|
}
|
|
238
414
|
else {
|
|
239
|
-
// /add sub-node <title> — use currently selected node
|
|
240
415
|
title = rest;
|
|
241
416
|
parentId = ctx.selectedNodeId;
|
|
242
417
|
if (!parentId)
|
|
@@ -254,9 +429,10 @@ export async function executeCommand(input, _screen, ctx) {
|
|
|
254
429
|
return { ok: false, message: e.message };
|
|
255
430
|
}
|
|
256
431
|
}
|
|
432
|
+
// ── 5. /done REF ────────────────────────────────────────────────────────
|
|
257
433
|
if (trimmed.startsWith('/done ')) {
|
|
258
434
|
const ref = trimmed.slice('/done '.length).trim();
|
|
259
|
-
const node =
|
|
435
|
+
const node = resolveRef(ref);
|
|
260
436
|
if (!node)
|
|
261
437
|
return { ok: false, message: `Node "${ref}" not found.` };
|
|
262
438
|
try {
|
|
@@ -268,9 +444,11 @@ export async function executeCommand(input, _screen, ctx) {
|
|
|
268
444
|
return { ok: false, message: e.message };
|
|
269
445
|
}
|
|
270
446
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
447
|
+
// ── 6. /delete node: REF or /delete node REF (delete whole node) ─────
|
|
448
|
+
const deleteNodeMatch = trimmed.match(/^\/delete node(?::\s*|\s+)(.+)$/);
|
|
449
|
+
if (deleteNodeMatch) {
|
|
450
|
+
const ref = deleteNodeMatch[1].trim();
|
|
451
|
+
const node = resolveRef(ref);
|
|
274
452
|
if (!node)
|
|
275
453
|
return { ok: false, message: `Node "${ref}" not found.` };
|
|
276
454
|
try {
|
|
@@ -282,6 +460,7 @@ export async function executeCommand(input, _screen, ctx) {
|
|
|
282
460
|
return { ok: false, message: e.message };
|
|
283
461
|
}
|
|
284
462
|
}
|
|
463
|
+
// ── 7. /move node REF LIST-NAME ─────────────────────────────────────────
|
|
285
464
|
if (trimmed.startsWith('/move node ')) {
|
|
286
465
|
const rest = trimmed.slice('/move node '.length).trim();
|
|
287
466
|
const spaceIdx = rest.indexOf(' ');
|
|
@@ -289,11 +468,10 @@ export async function executeCommand(input, _screen, ctx) {
|
|
|
289
468
|
return { ok: false, message: 'Usage: /move node <ref> <list-name>' };
|
|
290
469
|
const ref = rest.slice(0, spaceIdx).trim();
|
|
291
470
|
const destListName = rest.slice(spaceIdx + 1).trim();
|
|
292
|
-
const node =
|
|
471
|
+
const node = resolveRef(ref);
|
|
293
472
|
if (!node)
|
|
294
473
|
return { ok: false, message: `Node "${ref}" not found.` };
|
|
295
474
|
try {
|
|
296
|
-
// Find destination list across all folders
|
|
297
475
|
const folders = await getFolders();
|
|
298
476
|
let destListId;
|
|
299
477
|
for (const folder of folders) {
|
|
@@ -314,144 +492,5 @@ export async function executeCommand(input, _screen, ctx) {
|
|
|
314
492
|
return { ok: false, message: e.message };
|
|
315
493
|
}
|
|
316
494
|
}
|
|
317
|
-
// ── /edit node ... <property> and /add node ... <property> ───────────────
|
|
318
|
-
const editNodeMatch = trimmed.match(/^\/(edit|add|delete) node (.+?) \.\.\. (\S+)(?:\s+(.*))?$/);
|
|
319
|
-
if (editNodeMatch) {
|
|
320
|
-
const verb = editNodeMatch[1];
|
|
321
|
-
const ref = editNodeMatch[2].trim();
|
|
322
|
-
const property = editNodeMatch[3].trim();
|
|
323
|
-
const value = (editNodeMatch[4] ?? '').trim();
|
|
324
|
-
const node = resolveNode(ref, nodes);
|
|
325
|
-
if (!node)
|
|
326
|
-
return { ok: false, message: `Node "${ref}" not found.` };
|
|
327
|
-
try {
|
|
328
|
-
switch (property) {
|
|
329
|
-
case 'title':
|
|
330
|
-
if (!value)
|
|
331
|
-
return { ok: false, message: 'New title required.' };
|
|
332
|
-
await updateNode(node.id, { title: value });
|
|
333
|
-
ctx.refetch?.();
|
|
334
|
-
return { ok: true, message: `✓ Renamed to "${value}"` };
|
|
335
|
-
case 'status': {
|
|
336
|
-
const statusMap = {
|
|
337
|
-
'NOT-DONE': 'TODO',
|
|
338
|
-
'IN-PROGRESS': 'IN_PROGRESS',
|
|
339
|
-
DONE: 'DONE',
|
|
340
|
-
};
|
|
341
|
-
if (verb === 'delete') {
|
|
342
|
-
await updateNode(node.id, { status: 'TODO' });
|
|
343
|
-
}
|
|
344
|
-
else {
|
|
345
|
-
const mapped = statusMap[value.toUpperCase()];
|
|
346
|
-
if (!mapped)
|
|
347
|
-
return { ok: false, message: `Invalid status. Use NOT-DONE, IN-PROGRESS, or DONE.` };
|
|
348
|
-
await updateNode(node.id, { status: mapped });
|
|
349
|
-
}
|
|
350
|
-
ctx.refetch?.();
|
|
351
|
-
return { ok: true, message: `✓ Status updated` };
|
|
352
|
-
}
|
|
353
|
-
case 'priority': {
|
|
354
|
-
const priorityMap = { LOW: 'LOW', MEDIUM: 'MEDIUM', HIGH: 'HIGH' };
|
|
355
|
-
if (verb === 'delete') {
|
|
356
|
-
await updateNode(node.id, { priority: null });
|
|
357
|
-
}
|
|
358
|
-
else {
|
|
359
|
-
const mapped = priorityMap[value.toUpperCase()];
|
|
360
|
-
if (!mapped)
|
|
361
|
-
return { ok: false, message: `Invalid priority. Use LOW, MEDIUM, or HIGH.` };
|
|
362
|
-
await updateNode(node.id, { priority: mapped });
|
|
363
|
-
}
|
|
364
|
-
ctx.refetch?.();
|
|
365
|
-
return { ok: true, message: `✓ Priority updated` };
|
|
366
|
-
}
|
|
367
|
-
case 'start-date': {
|
|
368
|
-
if (verb === 'delete') {
|
|
369
|
-
await updateNode(node.id, { startAt: null });
|
|
370
|
-
}
|
|
371
|
-
else {
|
|
372
|
-
const iso = parseDate(value);
|
|
373
|
-
if (!iso)
|
|
374
|
-
return { ok: false, message: 'Invalid date. Use DD-MM-YYYY.' };
|
|
375
|
-
await updateNode(node.id, { startAt: iso });
|
|
376
|
-
}
|
|
377
|
-
ctx.refetch?.();
|
|
378
|
-
return { ok: true, message: `✓ Start date updated` };
|
|
379
|
-
}
|
|
380
|
-
case 'end-date': {
|
|
381
|
-
if (verb === 'delete') {
|
|
382
|
-
await updateNode(node.id, { endAt: null });
|
|
383
|
-
}
|
|
384
|
-
else {
|
|
385
|
-
const iso = parseDate(value);
|
|
386
|
-
if (!iso)
|
|
387
|
-
return { ok: false, message: 'Invalid date. Use DD-MM-YYYY.' };
|
|
388
|
-
await updateNode(node.id, { endAt: iso });
|
|
389
|
-
}
|
|
390
|
-
ctx.refetch?.();
|
|
391
|
-
return { ok: true, message: `✓ End date updated` };
|
|
392
|
-
}
|
|
393
|
-
case 'start-time': {
|
|
394
|
-
if (verb === 'delete') {
|
|
395
|
-
await updateNode(node.id, { startAt: null });
|
|
396
|
-
}
|
|
397
|
-
else {
|
|
398
|
-
const iso = parseTime(value, node.startAt ?? undefined);
|
|
399
|
-
if (!iso)
|
|
400
|
-
return { ok: false, message: 'Invalid time. Use HH:MM AM/PM.' };
|
|
401
|
-
await updateNode(node.id, { startAt: iso });
|
|
402
|
-
}
|
|
403
|
-
ctx.refetch?.();
|
|
404
|
-
return { ok: true, message: `✓ Start time updated` };
|
|
405
|
-
}
|
|
406
|
-
case 'end-time': {
|
|
407
|
-
if (verb === 'delete') {
|
|
408
|
-
await updateNode(node.id, { endAt: null });
|
|
409
|
-
}
|
|
410
|
-
else {
|
|
411
|
-
const iso = parseTime(value, node.endAt ?? undefined);
|
|
412
|
-
if (!iso)
|
|
413
|
-
return { ok: false, message: 'Invalid time. Use HH:MM AM/PM.' };
|
|
414
|
-
await updateNode(node.id, { endAt: iso });
|
|
415
|
-
}
|
|
416
|
-
ctx.refetch?.();
|
|
417
|
-
return { ok: true, message: `✓ End time updated` };
|
|
418
|
-
}
|
|
419
|
-
case 'tag': {
|
|
420
|
-
if (!value && verb !== 'delete')
|
|
421
|
-
return { ok: false, message: 'Tag name required.' };
|
|
422
|
-
// Tags by name need a tag ID. For now, signal the screen to handle.
|
|
423
|
-
return {
|
|
424
|
-
ok: false,
|
|
425
|
-
message: 'Tag operations require a tag ID. Use /tags to list available tags.',
|
|
426
|
-
};
|
|
427
|
-
}
|
|
428
|
-
case 'note': {
|
|
429
|
-
if (verb === 'delete') {
|
|
430
|
-
await updateNode(node.id, { notes: null });
|
|
431
|
-
}
|
|
432
|
-
else {
|
|
433
|
-
if (!value)
|
|
434
|
-
return { ok: false, message: 'Note text required.' };
|
|
435
|
-
await updateNode(node.id, { notes: value });
|
|
436
|
-
}
|
|
437
|
-
ctx.refetch?.();
|
|
438
|
-
return { ok: true, message: `✓ Note updated` };
|
|
439
|
-
}
|
|
440
|
-
case 'position': {
|
|
441
|
-
const pos = parseInt(value, 10);
|
|
442
|
-
if (isNaN(pos))
|
|
443
|
-
return { ok: false, message: 'Position must be a number.' };
|
|
444
|
-
await moveNode(node.id, node.parentId ?? null, pos - 1);
|
|
445
|
-
ctx.refetch?.();
|
|
446
|
-
return { ok: true, message: `✓ Node moved to position ${pos}` };
|
|
447
|
-
}
|
|
448
|
-
default:
|
|
449
|
-
return { ok: false, message: `Unknown property "${property}".` };
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
catch (e) {
|
|
453
|
-
return { ok: false, message: e.message };
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
495
|
return { ok: false, message: `Unknown command: ${trimmed}` };
|
|
457
496
|
}
|
|
@@ -96,10 +96,10 @@ export const COMMAND_REGISTRY = [
|
|
|
96
96
|
},
|
|
97
97
|
// ─── NODES SCREEN ─────────────────────────────────────────────────────────
|
|
98
98
|
{
|
|
99
|
-
command: '/add node',
|
|
99
|
+
command: '/add node:',
|
|
100
100
|
args: [{ name: 'node-title', type: 'text', placeholder: 'node title' }],
|
|
101
101
|
screens: ['nodes'],
|
|
102
|
-
description: 'Create a new root node',
|
|
102
|
+
description: 'Create a new root node → /add node: My Task',
|
|
103
103
|
},
|
|
104
104
|
{
|
|
105
105
|
command: '/add sub-node',
|
|
@@ -109,240 +109,165 @@ export const COMMAND_REGISTRY = [
|
|
|
109
109
|
},
|
|
110
110
|
{
|
|
111
111
|
command: '/done',
|
|
112
|
-
args: [{ name: 'index-or-title', type: 'index-or-title', placeholder: '1
|
|
112
|
+
args: [{ name: 'index-or-title', type: 'index-or-title', placeholder: '1 or task title' }],
|
|
113
113
|
screens: ['nodes'],
|
|
114
114
|
description: 'Shorthand: set node status to DONE',
|
|
115
115
|
},
|
|
116
116
|
{
|
|
117
|
-
command: '/delete node',
|
|
118
|
-
args: [{ name: 'index-or-title', type: 'index-or-title', placeholder: '1
|
|
117
|
+
command: '/delete node:',
|
|
118
|
+
args: [{ name: 'index-or-title', type: 'index-or-title', placeholder: '1 or task title' }],
|
|
119
119
|
screens: ['nodes'],
|
|
120
|
-
description: 'Delete a node',
|
|
120
|
+
description: 'Delete a node → /delete node: 1',
|
|
121
121
|
},
|
|
122
122
|
{
|
|
123
123
|
command: '/move node',
|
|
124
124
|
args: [
|
|
125
|
-
{ name: 'index-or-title', type: 'index-or-title', placeholder: '1
|
|
125
|
+
{ name: 'index-or-title', type: 'index-or-title', placeholder: '1 or task title' },
|
|
126
126
|
{ name: 'list-name', type: 'text', placeholder: 'destination list name' },
|
|
127
127
|
],
|
|
128
128
|
screens: ['nodes'],
|
|
129
129
|
description: 'Move node to another list',
|
|
130
130
|
},
|
|
131
|
-
// /edit node ...
|
|
131
|
+
// ── /edit node REF PROPERTY: VALUE (use ... for currently selected node) ──
|
|
132
132
|
{
|
|
133
|
-
command: '/edit node ... title',
|
|
134
|
-
args: [
|
|
135
|
-
{ name: 'index-or-title', type: 'index-or-title', placeholder: '1 or "task title"' },
|
|
136
|
-
{ name: 'new-title', type: 'text', placeholder: 'new title' },
|
|
137
|
-
],
|
|
133
|
+
command: '/edit node ... title:',
|
|
134
|
+
args: [{ name: 'new-title', type: 'text', placeholder: 'new title' }],
|
|
138
135
|
screens: ['nodes'],
|
|
139
|
-
description: '
|
|
136
|
+
description: 'Rename node → /edit node 1 title: New Name (or ... for selected)',
|
|
140
137
|
},
|
|
141
138
|
{
|
|
142
|
-
command: '/edit node ... position',
|
|
143
|
-
args: [
|
|
144
|
-
{ name: 'index-or-title', type: 'index-or-title', placeholder: '1 or "task title"' },
|
|
145
|
-
{ name: 'new-index', type: 'number', placeholder: '2' },
|
|
146
|
-
],
|
|
139
|
+
command: '/edit node ... position:',
|
|
140
|
+
args: [{ name: 'new-index', type: 'number', placeholder: '2' }],
|
|
147
141
|
screens: ['nodes'],
|
|
148
|
-
description: 'Reorder node',
|
|
142
|
+
description: 'Reorder node → /edit node 1 position: 3',
|
|
149
143
|
},
|
|
150
144
|
{
|
|
151
|
-
command: '/edit node ... status',
|
|
152
|
-
args: [
|
|
153
|
-
{ name: 'index-or-title', type: 'index-or-title', placeholder: '1 or "task title"' },
|
|
154
|
-
{ name: 'status', type: 'status', placeholder: 'NOT-DONE | IN-PROGRESS | DONE' },
|
|
155
|
-
],
|
|
145
|
+
command: '/edit node ... status:',
|
|
146
|
+
args: [{ name: 'status', type: 'status', placeholder: 'NOT-DONE | IN-PROGRESS | DONE' }],
|
|
156
147
|
screens: ['nodes'],
|
|
157
|
-
description: 'Change node status',
|
|
148
|
+
description: 'Change status → /edit node 1 status: DONE',
|
|
158
149
|
},
|
|
159
150
|
{
|
|
160
|
-
command: '/edit node ... priority',
|
|
161
|
-
args: [
|
|
162
|
-
{ name: 'index-or-title', type: 'index-or-title', placeholder: '1 or "task title"' },
|
|
163
|
-
{ name: 'priority', type: 'priority', placeholder: 'LOW | MEDIUM | HIGH' },
|
|
164
|
-
],
|
|
151
|
+
command: '/edit node ... priority:',
|
|
152
|
+
args: [{ name: 'priority', type: 'priority', placeholder: 'LOW | MEDIUM | HIGH' }],
|
|
165
153
|
screens: ['nodes'],
|
|
166
|
-
description: 'Change node priority',
|
|
154
|
+
description: 'Change priority → /edit node 1 priority: HIGH',
|
|
167
155
|
},
|
|
168
156
|
{
|
|
169
|
-
command: '/edit node ... start-date',
|
|
170
|
-
args: [
|
|
171
|
-
{ name: 'index-or-title', type: 'index-or-title', placeholder: '1 or "task title"' },
|
|
172
|
-
{ name: 'start-date', type: 'date', placeholder: 'DD-MM-YYYY' },
|
|
173
|
-
],
|
|
157
|
+
command: '/edit node ... start-date:',
|
|
158
|
+
args: [{ name: 'start-date', type: 'date', placeholder: 'DD-MM-YYYY' }],
|
|
174
159
|
screens: ['nodes'],
|
|
175
|
-
description: 'Edit start date',
|
|
160
|
+
description: 'Edit start date → /edit node 1 start-date: 24-05-2026',
|
|
176
161
|
},
|
|
177
162
|
{
|
|
178
|
-
command: '/edit node ... start-time',
|
|
179
|
-
args: [
|
|
180
|
-
{ name: 'index-or-title', type: 'index-or-title', placeholder: '1 or "task title"' },
|
|
181
|
-
{ name: 'start-time', type: 'time', placeholder: 'HH:MM AM/PM' },
|
|
182
|
-
],
|
|
163
|
+
command: '/edit node ... start-time:',
|
|
164
|
+
args: [{ name: 'start-time', type: 'time', placeholder: 'HH:MM AM/PM' }],
|
|
183
165
|
screens: ['nodes'],
|
|
184
|
-
description: 'Edit start time',
|
|
166
|
+
description: 'Edit start time → /edit node 1 start-time: 09:00 AM',
|
|
185
167
|
},
|
|
186
168
|
{
|
|
187
|
-
command: '/edit node ... end-date',
|
|
188
|
-
args: [
|
|
189
|
-
{ name: 'index-or-title', type: 'index-or-title', placeholder: '1 or "task title"' },
|
|
190
|
-
{ name: 'end-date', type: 'date', placeholder: 'DD-MM-YYYY' },
|
|
191
|
-
],
|
|
169
|
+
command: '/edit node ... end-date:',
|
|
170
|
+
args: [{ name: 'end-date', type: 'date', placeholder: 'DD-MM-YYYY' }],
|
|
192
171
|
screens: ['nodes'],
|
|
193
|
-
description: 'Edit end date',
|
|
172
|
+
description: 'Edit end date → /edit node 1 end-date: 25-05-2026',
|
|
194
173
|
},
|
|
195
174
|
{
|
|
196
|
-
command: '/edit node ... end-time',
|
|
197
|
-
args: [
|
|
198
|
-
{ name: 'index-or-title', type: 'index-or-title', placeholder: '1 or "task title"' },
|
|
199
|
-
{ name: 'end-time', type: 'time', placeholder: 'HH:MM AM/PM' },
|
|
200
|
-
],
|
|
175
|
+
command: '/edit node ... end-time:',
|
|
176
|
+
args: [{ name: 'end-time', type: 'time', placeholder: 'HH:MM AM/PM' }],
|
|
201
177
|
screens: ['nodes'],
|
|
202
|
-
description: 'Edit end time',
|
|
178
|
+
description: 'Edit end time → /edit node 1 end-time: 05:00 PM',
|
|
203
179
|
},
|
|
204
180
|
{
|
|
205
|
-
command: '/edit node ...
|
|
206
|
-
args: [
|
|
207
|
-
{ name: 'index-or-title', type: 'index-or-title', placeholder: '1 or "task title"' },
|
|
208
|
-
{ name: 'tag-name', type: 'text', placeholder: 'tag name' },
|
|
209
|
-
],
|
|
181
|
+
command: '/edit node ... note:',
|
|
182
|
+
args: [{ name: 'new-note', type: 'text', placeholder: 'note text' }],
|
|
210
183
|
screens: ['nodes'],
|
|
211
|
-
description: 'Edit node
|
|
184
|
+
description: 'Edit note → /edit node 1 note: My note here',
|
|
212
185
|
},
|
|
186
|
+
// ── /add node REF PROPERTY: VALUE ──────────────────────────────────────────
|
|
213
187
|
{
|
|
214
|
-
command: '/
|
|
215
|
-
args: [
|
|
216
|
-
{ name: 'index-or-title', type: 'index-or-title', placeholder: '1 or "task title"' },
|
|
217
|
-
{ name: 'new-note', type: 'text', placeholder: 'note text' },
|
|
218
|
-
],
|
|
188
|
+
command: '/add node ... start-date:',
|
|
189
|
+
args: [{ name: 'start-date', type: 'date', placeholder: 'DD-MM-YYYY' }],
|
|
219
190
|
screens: ['nodes'],
|
|
220
|
-
description: '
|
|
191
|
+
description: 'Add start date → /add node 1 start-date: 24-05-2026',
|
|
221
192
|
},
|
|
222
|
-
// /add node ... <property>
|
|
223
193
|
{
|
|
224
|
-
command: '/add node ... start-
|
|
225
|
-
args: [
|
|
226
|
-
{ name: 'index-or-title', type: 'index-or-title', placeholder: '1 or "task title"' },
|
|
227
|
-
{ name: 'start-date', type: 'date', placeholder: 'DD-MM-YYYY' },
|
|
228
|
-
],
|
|
194
|
+
command: '/add node ... start-time:',
|
|
195
|
+
args: [{ name: 'start-time', type: 'time', placeholder: 'HH:MM AM/PM' }],
|
|
229
196
|
screens: ['nodes'],
|
|
230
|
-
description: 'Add start
|
|
197
|
+
description: 'Add start time → /add node 1 start-time: 09:00 AM',
|
|
231
198
|
},
|
|
232
199
|
{
|
|
233
|
-
command: '/add node ...
|
|
234
|
-
args: [
|
|
235
|
-
{ name: 'index-or-title', type: 'index-or-title', placeholder: '1 or "task title"' },
|
|
236
|
-
{ name: 'start-time', type: 'time', placeholder: 'HH:MM AM/PM' },
|
|
237
|
-
],
|
|
200
|
+
command: '/add node ... end-date:',
|
|
201
|
+
args: [{ name: 'end-date', type: 'date', placeholder: 'DD-MM-YYYY' }],
|
|
238
202
|
screens: ['nodes'],
|
|
239
|
-
description: 'Add
|
|
203
|
+
description: 'Add end date → /add node 1 end-date: 25-05-2026',
|
|
240
204
|
},
|
|
241
205
|
{
|
|
242
|
-
command: '/add node ... end-
|
|
243
|
-
args: [
|
|
244
|
-
{ name: 'index-or-title', type: 'index-or-title', placeholder: '1 or "task title"' },
|
|
245
|
-
{ name: 'end-date', type: 'date', placeholder: 'DD-MM-YYYY' },
|
|
246
|
-
],
|
|
206
|
+
command: '/add node ... end-time:',
|
|
207
|
+
args: [{ name: 'end-time', type: 'time', placeholder: 'HH:MM AM/PM' }],
|
|
247
208
|
screens: ['nodes'],
|
|
248
|
-
description: 'Add end
|
|
209
|
+
description: 'Add end time → /add node 1 end-time: 05:00 PM',
|
|
249
210
|
},
|
|
250
211
|
{
|
|
251
|
-
command: '/add node ...
|
|
252
|
-
args: [
|
|
253
|
-
{ name: 'index-or-title', type: 'index-or-title', placeholder: '1 or "task title"' },
|
|
254
|
-
{ name: 'end-time', type: 'time', placeholder: 'HH:MM AM/PM' },
|
|
255
|
-
],
|
|
212
|
+
command: '/add node ... note:',
|
|
213
|
+
args: [{ name: 'note-text', type: 'text', placeholder: 'note text' }],
|
|
256
214
|
screens: ['nodes'],
|
|
257
|
-
description: 'Add
|
|
215
|
+
description: 'Add note → /add node 1 note: My note here',
|
|
258
216
|
},
|
|
259
217
|
{
|
|
260
|
-
command: '/add node ...
|
|
261
|
-
args: [
|
|
262
|
-
{ name: 'index-or-title', type: 'index-or-title', placeholder: '1 or "task title"' },
|
|
263
|
-
{ name: 'tag-name', type: 'text', placeholder: 'tag name' },
|
|
264
|
-
],
|
|
218
|
+
command: '/add node ... status:',
|
|
219
|
+
args: [{ name: 'status', type: 'status', placeholder: 'NOT-DONE | IN-PROGRESS | DONE' }],
|
|
265
220
|
screens: ['nodes'],
|
|
266
|
-
description: '
|
|
221
|
+
description: 'Set status → /add node 1 status: IN-PROGRESS',
|
|
267
222
|
},
|
|
268
223
|
{
|
|
269
|
-
command: '/add node ...
|
|
270
|
-
args: [
|
|
271
|
-
{ name: 'index-or-title', type: 'index-or-title', placeholder: '1 or "task title"' },
|
|
272
|
-
{ name: 'note-text', type: 'text', placeholder: 'note text' },
|
|
273
|
-
],
|
|
224
|
+
command: '/add node ... priority:',
|
|
225
|
+
args: [{ name: 'priority', type: 'priority', placeholder: 'LOW | MEDIUM | HIGH' }],
|
|
274
226
|
screens: ['nodes'],
|
|
275
|
-
description: '
|
|
227
|
+
description: 'Set priority → /add node 1 priority: HIGH',
|
|
276
228
|
},
|
|
277
|
-
|
|
278
|
-
command: '/add node ... status',
|
|
279
|
-
args: [
|
|
280
|
-
{ name: 'index-or-title', type: 'index-or-title', placeholder: '1 or "task title"' },
|
|
281
|
-
{ name: 'status', type: 'status', placeholder: 'NOT-DONE | IN-PROGRESS | DONE' },
|
|
282
|
-
],
|
|
283
|
-
screens: ['nodes'],
|
|
284
|
-
description: 'Set status',
|
|
285
|
-
},
|
|
286
|
-
{
|
|
287
|
-
command: '/add node ... priority',
|
|
288
|
-
args: [
|
|
289
|
-
{ name: 'index-or-title', type: 'index-or-title', placeholder: '1 or "task title"' },
|
|
290
|
-
{ name: 'priority', type: 'priority', placeholder: 'LOW | MEDIUM | HIGH' },
|
|
291
|
-
],
|
|
292
|
-
screens: ['nodes'],
|
|
293
|
-
description: 'Set priority',
|
|
294
|
-
},
|
|
295
|
-
// /delete node ... <property>
|
|
229
|
+
// ── /delete node REF PROPERTY ──────────────────────────────────────────────
|
|
296
230
|
{
|
|
297
231
|
command: '/delete node ... start-date',
|
|
298
|
-
args: [{ name: 'index-or-title', type: 'index-or-title', placeholder: '1
|
|
232
|
+
args: [{ name: 'index-or-title', type: 'index-or-title', placeholder: '1 or task title' }],
|
|
299
233
|
screens: ['nodes'],
|
|
300
|
-
description: 'Remove start date',
|
|
234
|
+
description: 'Remove start date → /delete node 1 start-date',
|
|
301
235
|
},
|
|
302
236
|
{
|
|
303
237
|
command: '/delete node ... start-time',
|
|
304
|
-
args: [{ name: 'index-or-title', type: 'index-or-title', placeholder: '1
|
|
238
|
+
args: [{ name: 'index-or-title', type: 'index-or-title', placeholder: '1 or task title' }],
|
|
305
239
|
screens: ['nodes'],
|
|
306
|
-
description: 'Remove start time',
|
|
240
|
+
description: 'Remove start time → /delete node 1 start-time',
|
|
307
241
|
},
|
|
308
242
|
{
|
|
309
243
|
command: '/delete node ... end-date',
|
|
310
|
-
args: [{ name: 'index-or-title', type: 'index-or-title', placeholder: '1
|
|
244
|
+
args: [{ name: 'index-or-title', type: 'index-or-title', placeholder: '1 or task title' }],
|
|
311
245
|
screens: ['nodes'],
|
|
312
|
-
description: 'Remove end date',
|
|
246
|
+
description: 'Remove end date → /delete node 1 end-date',
|
|
313
247
|
},
|
|
314
248
|
{
|
|
315
249
|
command: '/delete node ... end-time',
|
|
316
|
-
args: [{ name: 'index-or-title', type: 'index-or-title', placeholder: '1
|
|
317
|
-
screens: ['nodes'],
|
|
318
|
-
description: 'Remove end time',
|
|
319
|
-
},
|
|
320
|
-
{
|
|
321
|
-
command: '/delete node ... tag',
|
|
322
|
-
args: [
|
|
323
|
-
{ name: 'index-or-title', type: 'index-or-title', placeholder: '1 or "task title"' },
|
|
324
|
-
{ name: 'tag-name', type: 'text', placeholder: 'tag name' },
|
|
325
|
-
],
|
|
250
|
+
args: [{ name: 'index-or-title', type: 'index-or-title', placeholder: '1 or task title' }],
|
|
326
251
|
screens: ['nodes'],
|
|
327
|
-
description: 'Remove
|
|
252
|
+
description: 'Remove end time → /delete node 1 end-time',
|
|
328
253
|
},
|
|
329
254
|
{
|
|
330
255
|
command: '/delete node ... note',
|
|
331
|
-
args: [{ name: 'index-or-title', type: 'index-or-title', placeholder: '1
|
|
256
|
+
args: [{ name: 'index-or-title', type: 'index-or-title', placeholder: '1 or task title' }],
|
|
332
257
|
screens: ['nodes'],
|
|
333
|
-
description: 'Remove note',
|
|
258
|
+
description: 'Remove note → /delete node 1 note',
|
|
334
259
|
},
|
|
335
260
|
{
|
|
336
261
|
command: '/delete node ... status',
|
|
337
|
-
args: [{ name: 'index-or-title', type: 'index-or-title', placeholder: '1
|
|
262
|
+
args: [{ name: 'index-or-title', type: 'index-or-title', placeholder: '1 or task title' }],
|
|
338
263
|
screens: ['nodes'],
|
|
339
|
-
description: 'Remove status',
|
|
264
|
+
description: 'Remove status → /delete node 1 status',
|
|
340
265
|
},
|
|
341
266
|
{
|
|
342
267
|
command: '/delete node ... priority',
|
|
343
|
-
args: [{ name: 'index-or-title', type: 'index-or-title', placeholder: '1
|
|
268
|
+
args: [{ name: 'index-or-title', type: 'index-or-title', placeholder: '1 or task title' }],
|
|
344
269
|
screens: ['nodes'],
|
|
345
|
-
description: 'Remove priority',
|
|
270
|
+
description: 'Remove priority → /delete node 1 priority',
|
|
346
271
|
},
|
|
347
272
|
];
|
|
348
273
|
/** Returns commands valid for a given screen (global always included). */
|
|
@@ -7,7 +7,6 @@ import { createCliSession, pollCliSession } from '../../api/client.js';
|
|
|
7
7
|
import { saveToken } from '../../utils/auth.js';
|
|
8
8
|
import { ApiError } from '../../api/ApiError.js';
|
|
9
9
|
import chalk from 'chalk';
|
|
10
|
-
const AUTH_URL_BASE = process.env['STRATANODEX_AUTH_URL'] ?? 'https://stratanodex-landing-page.vercel.app';
|
|
11
10
|
const POLL_INTERVAL_MS = 2000;
|
|
12
11
|
export function LoginScreen({ replaceScreen, registerActions }) {
|
|
13
12
|
const [state, setState] = useState('creating');
|
|
@@ -28,12 +27,11 @@ export function LoginScreen({ replaceScreen, registerActions }) {
|
|
|
28
27
|
const attempt = ++attemptRef.current;
|
|
29
28
|
try {
|
|
30
29
|
// 1. Create CLI session on backend
|
|
31
|
-
const { code } = await createCliSession();
|
|
30
|
+
const { code, url } = await createCliSession();
|
|
32
31
|
if (attemptRef.current !== attempt)
|
|
33
32
|
return; // stale attempt
|
|
34
|
-
// 2. Open browser to auth
|
|
35
|
-
|
|
36
|
-
await open(`${AUTH_URL_BASE}${separator}session=${code}#auth`);
|
|
33
|
+
// 2. Open browser to the auth URL returned by the backend
|
|
34
|
+
await open(url);
|
|
37
35
|
setState('waiting');
|
|
38
36
|
// 3. Poll until complete or error
|
|
39
37
|
pollRef.current = setInterval(async () => {
|
package/package.json
CHANGED