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.
@@ -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
- if (trimmed.startsWith('/add node ') && !trimmed.includes(' ... ')) {
191
- const title = trimmed.slice('/add node '.length).trim();
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 = resolveNode(ref, nodes);
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
- if (trimmed.startsWith('/delete node ') && !trimmed.includes(' ... ')) {
272
- const ref = trimmed.slice('/delete node '.length).trim();
273
- const node = resolveNode(ref, nodes);
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 = resolveNode(ref, nodes);
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 or "task title"' }],
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 or "task title"' }],
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 or "task title"' },
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 ... <property>
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: 'Edit node title',
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 ... tag',
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 tag',
184
+ description: 'Edit note → /edit node 1 note: My note here',
212
185
  },
186
+ // ── /add node REF PROPERTY: VALUE ──────────────────────────────────────────
213
187
  {
214
- command: '/edit node ... note',
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: 'Edit note',
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-date',
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 date',
197
+ description: 'Add start time → /add node 1 start-time: 09:00 AM',
231
198
  },
232
199
  {
233
- command: '/add node ... start-time',
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 start time (default 12:00 AM)',
203
+ description: 'Add end date → /add node 1 end-date: 25-05-2026',
240
204
  },
241
205
  {
242
- command: '/add node ... end-date',
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 date',
209
+ description: 'Add end time → /add node 1 end-time: 05:00 PM',
249
210
  },
250
211
  {
251
- command: '/add node ... end-time',
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 end time (default 12:00 AM)',
215
+ description: 'Add note → /add node 1 note: My note here',
258
216
  },
259
217
  {
260
- command: '/add node ... tag',
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: 'Add tag to node',
221
+ description: 'Set status → /add node 1 status: IN-PROGRESS',
267
222
  },
268
223
  {
269
- command: '/add node ... note',
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: 'Add note',
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 or "task title"' }],
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 or "task title"' }],
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 or "task title"' }],
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 or "task title"' }],
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 tag from node',
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 or "task title"' }],
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 or "task title"' }],
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 or "task title"' }],
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 page hash-routed, ?session= is inside the hash
35
- const separator = AUTH_URL_BASE.includes('?') ? '&' : '?';
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stratanodex",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Keyboard-driven terminal task manager — hierarchical folders, lists & nodes with a full TUI",
5
5
  "homepage": "https://stratanodex.online",
6
6
  "repository": {