@kitelev/exocortex-cli 15.148.4 → 15.148.5

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.
Files changed (3) hide show
  1. package/README.md +172 -0
  2. package/dist/index.js +2 -2
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -298,6 +298,171 @@ exocortex command schedule "tasks/feature.md" --date "2025-12-15" --vault ~/vaul
298
298
  exocortex command set-deadline "tasks/feature.md" --date "2025-12-31" --vault ~/vault
299
299
  ```
300
300
 
301
+ ### Dynamic Commands (RFC-009)
302
+
303
+ Execute **vault-defined commands** through a single generic interpreter. Any
304
+ `exocmd:Command` asset in the vault — defined entirely in RDF (precondition +
305
+ grounding + bindings) — can be invoked from CLI, Telegram bot, plugin button,
306
+ or `cron`, without writing TypeScript.
307
+
308
+ This is the **killer feature** of Exocortex: a uniform runtime for vault-defined
309
+ commands across plugin / CLI / scripts / Claude sessions. Source of truth is
310
+ the RDF graph in your vault, not hardcoded TS logic. Architectural contract:
311
+ RFC-009 (Dynamic Command System) and the umbrella RFC
312
+ `94e520da-c6f7-48af-944c-51298d68da45` for the production-ready roadmap.
313
+ Working command definitions live in
314
+ [`docs/examples/rfc-009/`](../../docs/examples/rfc-009/).
315
+
316
+ #### Subcommands
317
+
318
+ | Subcommand | Purpose |
319
+ |------------|---------|
320
+ | `dyncommand list` | Discover all `exocmd:Command` assets in vault |
321
+ | `dyncommand show <uid>` | Show full details: precondition, grounding, bindings |
322
+ | `dyncommand exec <uid> --target <path>` | Execute the command on a target asset |
323
+ | `dyncommand validate` | Validate all command definitions; surface duplicate UIDs |
324
+
325
+ ```bash
326
+ # 1. Discover what commands the vault defines
327
+ exocortex dyncommand list --vault ~/vault
328
+
329
+ # 2. Filter to commands applicable to a specific asset
330
+ exocortex dyncommand list --target obsidian://vault/tasks/abc-123.md --vault ~/vault
331
+
332
+ # 3. Inspect a command (precondition SPARQL ASK + grounding)
333
+ exocortex dyncommand show 6e050240-58e9-4695-9dce-d73fc32cc1d7 --vault ~/vault
334
+
335
+ # 4. Execute (precondition is evaluated; non-passing ASK aborts before grounding)
336
+ exocortex dyncommand exec 6e050240-58e9-4695-9dce-d73fc32cc1d7 \
337
+ --target tasks/abc-123.md \
338
+ --vault ~/vault
339
+
340
+ # 5. Preview without writing
341
+ exocortex dyncommand exec <uid> --target tasks/abc-123.md --dry-run --vault ~/vault
342
+
343
+ # 6. Pass userInput to a service_call grounding
344
+ exocortex dyncommand exec <uid> \
345
+ --target daily/2026-05-02.md \
346
+ --input '{"label":"Lunch — vegetable soup"}' \
347
+ --vault ~/vault
348
+
349
+ # 7. Validate vault-wide command definitions
350
+ exocortex dyncommand validate --vault ~/vault --output json
351
+ ```
352
+
353
+ **Common options for all subcommands:**
354
+
355
+ - `--vault <path>` — Path to Obsidian vault (default: current directory)
356
+ - `--output <text|json>` — Response format (default: `text`)
357
+
358
+ **Additional options for `exec`:**
359
+
360
+ - `--target <filepath>` — Path to target asset, relative to vault **[required]**
361
+ - `--dry-run` — Evaluate precondition + print grounding plan; do not mutate files
362
+ - `--input <json>` — JSON object forwarded to `service_call` groundings as `userInput`
363
+
364
+ **Exit codes (exec / validate):**
365
+
366
+ - `0` — Success (executed or precondition passed in dry-run)
367
+ - `1` — Operation failed (precondition not satisfied, grounding error)
368
+ - `2` — File not found (target or vault)
369
+ - `3` — Invalid arguments (missing `--target`, malformed `--input`)
370
+
371
+ #### Example 1 — Telegram bot: complete a task by UID
372
+
373
+ A Telegram bot routes the user phrase «закрой задачу abc-123» to a single
374
+ `dyncommand exec` invocation. The bot does **not** know what "complete" means —
375
+ the semantics live entirely in the vault command asset (e.g.
376
+ `ems__Effort_status` transition `Doing → Done` plus `endTimestamp`):
377
+
378
+ ```bash
379
+ TASK_UID="abc-123"
380
+ COMPLETE_CMD_UID="6e050240-58e9-4695-9dce-d73fc32cc1d7" # from dyncommand list
381
+
382
+ exocortex dyncommand exec "$COMPLETE_CMD_UID" \
383
+ --target "tasks/${TASK_UID}.md" \
384
+ --vault ~/vault \
385
+ --output json
386
+ ```
387
+
388
+ Why this matters: adding a new vault-defined command (e.g. «отправить в backlog»,
389
+ «rollback to ToDo») requires **zero bot code changes** — only a new
390
+ `exocmd:Command` asset in the vault. This is Homoiconicity Q1 in practice
391
+ (see RFC `c78cc5c8-076c-4509-9e22-6f0257c51df4`).
392
+
393
+ #### Example 2 — CLI / Claude session: bulk-apply a command across a class
394
+
395
+ A Claude session needs to mark every `ems__Task` resolved in 2025 as archived.
396
+ Discover the command UID once, then loop over targets selected by SPARQL:
397
+
398
+ ```bash
399
+ ARCHIVE_CMD_UID=$(exocortex dyncommand list --vault ~/vault --output json \
400
+ | jq -r '.data[] | select(.label == "Archive Asset") | .uid')
401
+
402
+ exocortex sparql query \
403
+ "PREFIX ems: <https://exocortex.my/ontology/ems#>
404
+ SELECT ?path WHERE {
405
+ ?s a ems:Task ;
406
+ ems:Effort_status ems:EffortStatusDone ;
407
+ ems:Effort_endTimestamp ?ts .
408
+ FILTER(STRSTARTS(STR(?ts), '2025'))
409
+ BIND(STRAFTER(STR(?s), 'obsidian://vault/') AS ?path)
410
+ }" \
411
+ --vault ~/vault --format json \
412
+ | jq -r '.results.bindings[].path.value' \
413
+ | while read -r path; do
414
+ exocortex dyncommand exec "$ARCHIVE_CMD_UID" \
415
+ --target "$path" \
416
+ --vault ~/vault \
417
+ --output json
418
+ done
419
+ ```
420
+
421
+ The same `ARCHIVE_CMD_UID` works from a UI button (plugin), Telegram message,
422
+ or scheduled job — one definition, many runtimes.
423
+
424
+ #### Example 3 — `cron`: daily Lunch instance from a prototype
425
+
426
+ Production server runs `lunch-tracker.sh` at 13:00 every day. The script
427
+ materializes a fresh `Lunch` instance from a vault prototype using the generic
428
+ `Create Instance` command (a `create_instance` grounding):
429
+
430
+ ```bash
431
+ #!/usr/bin/env bash
432
+ # lunch-tracker.sh — cron entry: 0 13 * * * /opt/exocortex/lunch-tracker.sh
433
+ set -euo pipefail
434
+
435
+ VAULT="$HOME/vault"
436
+ DAILY="daily/$(date +%Y-%m-%d).md"
437
+ CREATE_INSTANCE_CMD="<create-instance-command-uid>"
438
+
439
+ npx @kitelev/exocortex-cli dyncommand exec "$CREATE_INSTANCE_CMD" \
440
+ --target "$DAILY" \
441
+ --input "{\"label\":\"Lunch — $(date +%Y-%m-%d)\"}" \
442
+ --vault "$VAULT" \
443
+ --output json
444
+ ```
445
+
446
+ If the command, its precondition, or its grounding ever change in the vault,
447
+ the cron job picks up the new behavior automatically — no redeploy.
448
+
449
+ #### Troubleshooting
450
+
451
+ - **`Precondition not satisfied`** — run `dyncommand show <uid>` to read the
452
+ SPARQL ASK; verify with `sparql query` that the target asset matches the
453
+ pattern. Common cause: target IRI mismatch (CLI ↔ plugin parity, RFC-027 in
454
+ progress) or an unset frontmatter property the precondition requires.
455
+ - **`Command with UID "..." not found`** — `dyncommand list` returns 0 on
456
+ UUID-wikilink-only vaults if the discovery index is stale; rebuild the
457
+ vault cache or pass `--target <iri>` to bypass class filtering.
458
+ - **Service `…` not implemented in CLI runtime** — the grounding uses a
459
+ `service_call` that is plugin-only (Phase 1 hard-fails such calls instead
460
+ of silently succeeding). Run from the plugin, or implement the service in
461
+ `@kitelev/exocortex-services`.
462
+ - **`dyncommand show` and `exec` disagree** — almost always a duplicate
463
+ `exo__Asset_uid`; run `dyncommand validate --output json` and grep for
464
+ `duplicateUids`. RCA: `docs/RCA_DYNCOMMAND_SHOW_VS_EXEC.md`.
465
+
301
466
  ## Workflow Examples
302
467
 
303
468
  ### Morning Planning
@@ -751,6 +916,13 @@ ems__Effort_status: "[[ems__EffortStatusDraft]]"
751
916
  - `exocortex command schedule` - Set planned start date
752
917
  - `exocortex command set-deadline` - Set planned end date
753
918
 
919
+ **Dynamic Commands (RFC-009):**
920
+
921
+ - `exocortex dyncommand list` - Discover vault-defined `exocmd:Command` assets
922
+ - `exocortex dyncommand show` - Show precondition / grounding / bindings for a command
923
+ - `exocortex dyncommand exec` - Execute vault-defined command on a target asset
924
+ - `exocortex dyncommand validate` - Validate command definitions; flag duplicate UIDs
925
+
754
926
  **Batch Operations:**
755
927
 
756
928
  - `exocortex batch` - Execute multiple operations in single invocation
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // @kitelev/exocortex-cli v15.148.4
2
+ // @kitelev/exocortex-cli v15.148.5
3
3
  // CLI tool for Exocortex knowledge management system - SPARQL queries, task management, and more
4
4
  // License: MIT
5
5
 
@@ -794,7 +794,7 @@ exo__BacklinksTableBlock_columns:${f==="[]"?" []":f}
794
794
  --- END PREVIEW ---
795
795
  `)}o(xK,"printDryRunReport");function CK(n,e){let r=[`--- MIGRATION ${e.apply?"APPLY":"DRY RUN"} ---`,`Migrated pairs: ${n.pairs.length}`,`Skipped configs: ${n.skipped.length}`];e.apply&&(r.push(`Files written to: ${e.outDir}/ (relative to vault root)`),r.push(`Total files written: ${n.pairs.length*2} (${n.pairs.length} Layout + ${n.pairs.length} Block)`));for(let i of n.skipped)r.push(` SKIPPED: ${i.sourcePath} \u2014 ${i.reason}`);return r.push(`--- END SUMMARY ---
796
796
  `),r.join(`
797
- `)}o(CK,"formatSummary");function IK(n,e){return{mode:e.apply?"apply":"dry-run",outDir:e.outDir,pairsCount:n.pairs.length,skippedCount:n.skipped.length,pairs:n.pairs.map(t=>({sourceUid:t.sourceUid,sourcePath:t.sourcePath,layoutUid:t.layout.uid,blockUid:t.block.uid,warnings:t.warnings})),skipped:n.skipped}}o(IK,"summariseResult");function $D(n){n.addCommand(pO()),n.addCommand(_O()),n.addCommand(SO())}o($D,"addQuerySubcommands");function BD(n){let e=new xe;e.name("exocortex").description("CLI tool for Exocortex knowledge management system").version(n??"15.148.4");let t=e.command("exoql").description("ExoQL query execution and cache management");$D(t);let r=e.command("sparql").description("(deprecated) Use 'exoql' instead");return $D(r),r.hook("preAction",()=>{console.error('\u26A0\uFE0F "sparql" is deprecated. Use "exoql" instead.')}),e.addCommand(CF()),e.addCommand(PF()),e.addCommand(OF()),e.addCommand(DF()),e.addCommand(NF()),e.addCommand($F()),e.addCommand(qF()),e.addCommand(rD()),e.addCommand(sD()),e.addCommand(uD()),e.addCommand(hD()),e.addCommand(pD()),e.addCommand(gD()),e.addCommand(CD()),e.addCommand(OD()),e.addCommand(jD()),e}o(BD,"createProgram");BD().parse();0&&(module.exports={createProgram});
797
+ `)}o(CK,"formatSummary");function IK(n,e){return{mode:e.apply?"apply":"dry-run",outDir:e.outDir,pairsCount:n.pairs.length,skippedCount:n.skipped.length,pairs:n.pairs.map(t=>({sourceUid:t.sourceUid,sourcePath:t.sourcePath,layoutUid:t.layout.uid,blockUid:t.block.uid,warnings:t.warnings})),skipped:n.skipped}}o(IK,"summariseResult");function $D(n){n.addCommand(pO()),n.addCommand(_O()),n.addCommand(SO())}o($D,"addQuerySubcommands");function BD(n){let e=new xe;e.name("exocortex").description("CLI tool for Exocortex knowledge management system").version(n??"15.148.5");let t=e.command("exoql").description("ExoQL query execution and cache management");$D(t);let r=e.command("sparql").description("(deprecated) Use 'exoql' instead");return $D(r),r.hook("preAction",()=>{console.error('\u26A0\uFE0F "sparql" is deprecated. Use "exoql" instead.')}),e.addCommand(CF()),e.addCommand(PF()),e.addCommand(OF()),e.addCommand(DF()),e.addCommand(NF()),e.addCommand($F()),e.addCommand(qF()),e.addCommand(rD()),e.addCommand(sD()),e.addCommand(uD()),e.addCommand(hD()),e.addCommand(pD()),e.addCommand(gD()),e.addCommand(CD()),e.addCommand(OD()),e.addCommand(jD()),e}o(BD,"createProgram");BD().parse();0&&(module.exports={createProgram});
798
798
  /*! Bundled license information:
799
799
 
800
800
  reflect-metadata/Reflect.js:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kitelev/exocortex-cli",
3
- "version": "15.148.4",
3
+ "version": "15.148.5",
4
4
  "description": "CLI tool for Exocortex knowledge management system - SPARQL queries, task management, and more",
5
5
  "main": "dist/index.js",
6
6
  "bin": {