quoxcli 0.6.0 → 0.6.1

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 (83) hide show
  1. package/CHANGELOG.md +18 -3
  2. package/lib/api/endpoints/admin.js +21 -0
  3. package/lib/api/endpoints/calendarq.js +8 -0
  4. package/lib/api/endpoints/index.js +2 -0
  5. package/lib/api/endpoints/lens.js +34 -0
  6. package/lib/api/endpoints/lucid.js +2 -1
  7. package/lib/api/endpoints/mon.js +6 -7
  8. package/lib/api/endpoints/v1.js +57 -0
  9. package/lib/commands/admin.js +86 -0
  10. package/lib/commands/approvals.js +5 -1
  11. package/lib/commands/calendar.js +27 -0
  12. package/lib/commands/chat.js +1 -1
  13. package/lib/commands/fleet.js +164 -0
  14. package/lib/commands/fleetRecipe.js +174 -0
  15. package/lib/commands/lens.js +268 -0
  16. package/lib/commands/lucidGoals.js +36 -2
  17. package/lib/commands/mail.js +21 -2
  18. package/lib/commands/monitoring.js +5 -1
  19. package/lib/commands/plan.js +36 -1
  20. package/lib/commands/research.js +78 -0
  21. package/lib/commands/settings.js +7 -1
  22. package/lib/commands/wazuh.js +9 -0
  23. package/lib/commands/wazuhIntake.js +11 -3
  24. package/lib/core/commandRegistrars.js +1 -0
  25. package/lib/core/version.js +15 -2
  26. package/lib/dragon/select.js +2 -2
  27. package/lib/quoxon/app.js +1 -1
  28. package/lib/quoxon/commands.js +1 -1
  29. package/lib/quoxon/dragonCompanion.js +24 -12
  30. package/lib/quoxon/main.js +1 -1
  31. package/lib/quoxon/onboarding/view.js +1 -1
  32. package/lib/quoxon/slash/cliLeaves.json +19 -0
  33. package/lib/quoxon/whatsnew.jsonl +1 -0
  34. package/package.json +1 -1
  35. package/vendor/dragonlab/SOURCE +3 -3
  36. package/vendor/dragonlab/design/architect/v2/architect-master.svg +67 -0
  37. package/vendor/dragonlab/design/architect/v2/character.mjs +83 -0
  38. package/vendor/dragonlab/design/architect/v2/review.mjs +35 -0
  39. package/vendor/dragonlab/design/architect/v2/watch.mjs +56 -0
  40. package/vendor/dragonlab/design/archivist/v2/archivist-master.svg +77 -0
  41. package/vendor/dragonlab/design/archivist/v3/character.mjs +56 -0
  42. package/vendor/dragonlab/design/archivist/v4/character.mjs +46 -0
  43. package/vendor/dragonlab/design/archivist/v4/review.mjs +35 -0
  44. package/vendor/dragonlab/design/archivist/v4/watch.mjs +56 -0
  45. package/vendor/dragonlab/design/argus/v2/character.mjs +69 -0
  46. package/vendor/dragonlab/design/argus/v3/argus-master.svg +68 -0
  47. package/vendor/dragonlab/design/argus/v3/character.mjs +63 -0
  48. package/vendor/dragonlab/design/argus/v3/review.mjs +35 -0
  49. package/vendor/dragonlab/design/argus/v3/watch.mjs +56 -0
  50. package/vendor/dragonlab/design/daedalus/v2/character.mjs +49 -0
  51. package/vendor/dragonlab/design/daedalus/v3/daedalus-master.svg +65 -0
  52. package/vendor/dragonlab/design/daedalus/v4/character.mjs +47 -0
  53. package/vendor/dragonlab/design/daedalus/v5/character.mjs +66 -0
  54. package/vendor/dragonlab/design/daedalus/v6/character.mjs +32 -0
  55. package/vendor/dragonlab/design/daedalus/v6/review.mjs +35 -0
  56. package/vendor/dragonlab/design/daedalus/v6/watch.mjs +56 -0
  57. package/vendor/dragonlab/design/janus/v1/janus-master.svg +70 -0
  58. package/vendor/dragonlab/design/janus/v2/character.mjs +56 -0
  59. package/vendor/dragonlab/design/janus/v3/character.mjs +7 -0
  60. package/vendor/dragonlab/design/janus/v4/character.mjs +150 -0
  61. package/vendor/dragonlab/design/janus/v5/character.mjs +55 -0
  62. package/vendor/dragonlab/design/janus/v5/review.mjs +35 -0
  63. package/vendor/dragonlab/design/janus/v5/watch.mjs +56 -0
  64. package/vendor/dragonlab/design/roady/v1/roady-master.svg +67 -0
  65. package/vendor/dragonlab/design/roady/v2/character.mjs +69 -0
  66. package/vendor/dragonlab/design/roady/v3/character.mjs +91 -0
  67. package/vendor/dragonlab/design/roady/v3/review.mjs +35 -0
  68. package/vendor/dragonlab/design/roady/v3/watch.mjs +56 -0
  69. package/vendor/dragonlab/design/weaver/v3/weaver-master.svg +57 -0
  70. package/vendor/dragonlab/design/weaver/v4/character.mjs +56 -0
  71. package/vendor/dragonlab/design/weaver/v5/character.mjs +52 -0
  72. package/vendor/dragonlab/design/weaver/v6/character.mjs +88 -0
  73. package/vendor/dragonlab/design/weaver/v6/review.mjs +35 -0
  74. package/vendor/dragonlab/design/weaver/v6/watch.mjs +56 -0
  75. package/vendor/dragonlab/integration/companions-v1/architect.mjs +2 -0
  76. package/vendor/dragonlab/integration/companions-v1/archivist.mjs +2 -0
  77. package/vendor/dragonlab/integration/companions-v1/argus.mjs +2 -0
  78. package/vendor/dragonlab/integration/companions-v1/characters.mjs +86 -0
  79. package/vendor/dragonlab/integration/companions-v1/daedalus.mjs +2 -0
  80. package/vendor/dragonlab/integration/companions-v1/janus.mjs +2 -0
  81. package/vendor/dragonlab/integration/companions-v1/roady.mjs +2 -0
  82. package/vendor/dragonlab/integration/companions-v1/view.mjs +4 -1
  83. package/vendor/dragonlab/integration/companions-v1/weaver.mjs +2 -0
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs/promises';
3
+ import {parseSvgRig} from '../../../factory/svg-rig.mjs';
4
+ import {renderArchitect,ARCHITECT_CLIPS} from './character.mjs';
5
+ import {artSlot,composeMessage} from '../../lucid/recovery/layout.mjs';
6
+ import {cellsToAnsi} from '../../../vendor/quox-dragon/qart.js';
7
+ const args=process.argv.slice(2),values=new Set(['--cols','--rows','--terminal-cols','--terminal-rows','--message','--scene','--at']);
8
+ for(let i=0;i<args.length;i++){
9
+ if(values.has(args[i])){if(!args[i+1]||args[i+1].startsWith('--'))throw Error('Missing option value');i++;}
10
+ else if(!['--large','--mono','--help','--watch','--attach','--intro','--still','--list'].includes(args[i]))throw Error('Unknown option: '+args[i]);
11
+ }
12
+ const value=(name,fallback)=>args.includes(name)?args[args.indexOf(name)+1]:fallback;
13
+ if(args.includes('--help')){console.log('Architect V2 forge studio. --watch --attach --intro --still --scene ID --at MS --list --large --mono --message TEXT --cols N --rows N --terminal-cols N --terminal-rows N');process.exit(0);}
14
+ if(args.includes('--list')){console.log(ARCHITECT_CLIPS.map(c=>c.id+' · '+c.title).join('\n'));process.exit(0);}
15
+ const w=Number(value('--terminal-cols',process.stdout.columns||80)),h=Number(value('--terminal-rows',process.stdout.rows||24));
16
+ if(!Number.isSafeInteger(w)||!Number.isSafeInteger(h)||w<1||h<1)throw Error('Invalid terminal size');
17
+ const slot=artSlot(w,h),large=args.includes('--large')||args.includes('--intro'),explicit=large||args.includes('--cols')||args.includes('--rows');
18
+ const cols=Number(value('--cols',large?Math.min(120,w-4):Math.min(39,slot.cols))),rows=Number(value('--rows',large?Math.min(36,h-4):Math.min(11,slot.rows)));
19
+ const message=value('--message','ARCHITECT\nImagine. Shape. Illuminate.');
20
+ if(!slot.showArt&&!explicit){console.log(composeMessage(null,message,{terminalCols:w}).join('\n'));}
21
+ else{
22
+ if(cols>w-4||rows>h-2)throw Error('Requested art exceeds terminal margins');
23
+ const rig=parseSvgRig(await fs.readFile(new URL('./architect-master.svg',import.meta.url),'utf8'));
24
+ const opts={cols,rows,mono:args.includes('--mono'),still:args.includes('--still'),deck:large};
25
+ const scene=value('--scene',args.includes('--attach')?'attach':'idle');
26
+ const r=renderArchitect(rig,scene,Number(value('--at',0)),opts);
27
+ if(!r.ok)throw Error(r.code);
28
+ if(args.includes('--watch')||args.includes('--attach')||args.includes('--intro')){
29
+ const {watch}=await import('./watch.mjs');
30
+ await watch(rig,opts,{terminalCols:w,terminalRows:h,large,scene:args.includes('--scene')||args.includes('--attach')?scene:null,arrival:args.includes('--attach'),message:args.includes('--message')?message:null});
31
+ }else{
32
+ const shownMessage=args.includes('--message')?message:'ARCHITECT\n'+r.caption;
33
+ console.log((large?[...cellsToAnsi(r.frame),...composeMessage(null,shownMessage,{terminalCols:w})]:composeMessage(r.frame,shownMessage,{terminalCols:w})).join('\n'));
34
+ }
35
+ }
@@ -0,0 +1,56 @@
1
+ import {performance} from 'node:perf_hooks';
2
+ import {ARCHITECT_CLIPS,renderArchitect,renderStory,STORY_DURATION} from './character.mjs';
3
+ import {composeMessage} from '../../lucid/recovery/layout.mjs';
4
+ import {cellsToAnsi} from '../../../vendor/quox-dragon/qart.js';
5
+
6
+ /** One bounded performance. No alternate screen or full-screen clearing. */
7
+ export async function watch(paths,options,config){
8
+ if(!process.stdout.isTTY||!process.stdin.isTTY)throw new Error('--watch/--intro needs an interactive terminal. Use --scene and --at for snapshots.');
9
+ const {terminalCols,terminalRows,scene,large,message}=config;
10
+ const clip=scene?ARCHITECT_CLIPS.find(c=>c.id===scene):null;
11
+ const duration=(clip?.duration??STORY_DURATION)+(config.arrival?6000:0);
12
+ const realCols=process.stdout.columns,realRows=process.stdout.rows;
13
+ const start=performance.now();
14
+ const render=t=>{
15
+ const r=scene?renderArchitect(paths,scene,t,options):renderStory(paths,t,options);
16
+ if(!r?.ok)throw new Error(r?.code??'Invalid story frame');
17
+ const caption=message??'ARCHITECT · '+r.clip+'\n'+r.caption;
18
+ return large?[...cellsToAnsi(r.frame),...caption.split('\n')]:composeMessage(r.frame,caption,{terminalCols});
19
+ };
20
+ // Reserve enough lines for captions too; keep within the real terminal.
21
+ const height=options.rows+3;
22
+ if(height>=terminalRows)throw new Error('Animation needs room for its caption; reduce --rows.');
23
+ let ended=false,drawn=false,timer,resolveDone,rejectDone;
24
+ const wasRaw=Boolean(process.stdin.isRaw);
25
+ const done=new Promise((resolve,reject)=>{resolveDone=resolve;rejectDone=reject;});
26
+ const cleanup=(error)=>{
27
+ if(ended)return;ended=true;clearInterval(timer);
28
+ process.stdin.off('data',key);process.stdout.off('resize',resized);
29
+ process.off('SIGINT',sigint);process.off('SIGTERM',sigterm);
30
+ process.stdin.setRawMode(wasRaw);process.stdin.pause();
31
+ if(drawn)process.stdout.write(`\x1b[${height}B\r`);
32
+ process.stdout.write('\x1b[0m\x1b[?25h');
33
+ error?rejectDone(error):resolveDone();
34
+ };
35
+ const key=data=>{if(data.includes(3)||data.toString().includes('q'))cleanup();};
36
+ const sigint=()=>cleanup(),sigterm=()=>cleanup();
37
+ const resized=()=>cleanup(new Error('Terminal resized; rerun for the new art slot.'));
38
+ try{
39
+ process.stdin.setRawMode(true);process.stdin.resume();process.stdin.on('data',key);
40
+ process.stdout.on('resize',resized);process.on('SIGINT',sigint);process.on('SIGTERM',sigterm);
41
+ process.stdout.write('\x1b[?25l');
42
+ // Allocate the block once; each frame replaces only these lines.
43
+ process.stdout.write('\n'.repeat(height)+`\x1b[${height}A`);drawn=true;
44
+ const tick=()=>{
45
+ try{
46
+ if(process.stdout.columns!==realCols||process.stdout.rows!==realRows)return resized();
47
+ const t=Math.min(performance.now()-start,duration-1),lines=render(t);
48
+ if(lines.length>height)throw new Error('Message exceeds the animation block; use a shorter message or a static preview.');
49
+ process.stdout.write(Array.from({length:height},(_,i)=>'\r\x1b[2K'+(lines[i]??'')+'\n').join('')+`\x1b[${height}A`);
50
+ if(performance.now()-start>=duration)cleanup();
51
+ }catch(e){cleanup(e);}
52
+ };
53
+ tick();if(!ended)timer=setInterval(tick,1000/6);
54
+ await done;
55
+ }catch(e){cleanup(e);await done;}
56
+ }
@@ -0,0 +1,77 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 480">
2
+ <title>Archivist — keeper of remembered things</title>
3
+ <g id="far-wing">
4
+ <path id="far-sail" fill="#365e82" d="M 370 285 Q 301 166 300 92 Q 330 165 355 187 Q 335 206 346 244 L 365 275 Z"/>
5
+ <path id="far-ribs" fill="none" stroke="#c88f5d" stroke-width="8" d="M 370 285 Q 301 166 300 92 M 370 285 Q 326 185 355 187 M 370 285 L 346 244"/>
6
+ </g>
7
+ <g id="tail">
8
+ <path id="tapered-tail" fill="#287d79" d="M 257 336 C 197 335 185 410 125 406 C 75 402 64 356 89 331 Q 104 316 127 321 Q 87 325 89 355 C 90 389 131 391 158 368 Q 200 310 253 306 Z"/>
9
+ <path id="tail-ridge" fill="none" stroke="#62bdb0" stroke-width="6" d="M 241 332 C 193 339 179 394 131 394 Q 85 394 83 360"/>
10
+ </g>
11
+ <g id="body">
12
+ <path id="round-body" fill="#357f79" d="M 220 251 C 258 208 357 208 402 256 Q 450 302 412 365 Q 357 404 260 383 Q 202 361 199 309 Z"/>
13
+ <path id="belly" fill="#c4b78a" d="M 332 262 Q 401 251 411 305 Q 423 349 379 373 L 325 364 Q 297 317 332 262 Z"/>
14
+ <path id="belly-plates" fill="none" stroke="#946e4d" stroke-width="6" d="M 321 291 Q 361 309 409 295 M 315 321 Q 359 341 410 323 M 326 351 Q 360 364 396 349"/>
15
+ <path id="haunch" fill="#286663" d="M 234 301 Q 281 278 302 314 L 295 356 L 321 382 L 305 404 L 219 400 Q 195 375 212 347 Z"/>
16
+ <path id="haunch-highlight" fill="none" stroke="#67b8aa" stroke-width="8" d="M 228 342 Q 232 310 264 313 Q 285 315 283 344"/>
17
+ <path id="rear-toes" fill="#f0cd96" d="M 226 383 L 215 406 L 238 400 L 251 382 L 248 407 L 273 400 L 284 383 L 283 405 L 311 399"/>
18
+ </g>
19
+ <g id="near-wing">
20
+ <path id="folded-sail" fill="#294f77" d="M 316 277 Q 263 192 151 155 Q 189 215 153 261 Q 192 251 218 287 Q 251 269 283 308 Z"/>
21
+ <path id="folded-highlight" fill="#487fa3" d="M 312 279 Q 254 221 151 155 Q 223 224 218 269 Q 260 264 283 308 Z"/>
22
+ <path id="folded-ribs" fill="none" stroke="#d5a06c" stroke-width="8" d="M 316 277 Q 263 192 151 155 M 316 277 Q 233 219 153 261 M 316 277 Q 253 250 218 287"/>
23
+ <path id="wing-root" fill="#56a69b" d="M 303 264 Q 323 252 335 275 Q 333 299 314 303 L 296 288 Z"/>
24
+ </g>
25
+ <g id="neck">
26
+ <path id="neck-mass" fill="#47968b" d="M 350 202 L 405 192 Q 435 224 419 263 L 391 298 L 344 283 Q 370 247 350 202 Z"/>
27
+ <path id="throat" fill="#d8d1ae" d="M 386 211 L 407 211 Q 426 241 400 270 L 384 287 L 363 276 Q 398 249 386 211 Z"/>
28
+ </g>
29
+ <g id="head">
30
+
31
+
32
+ <path id="head-mass" fill="#66ada0" d="M 331 167 Q 349 136 390 141 Q 430 131 449 166 L 449 211 Q 422 242 374 231 L 336 209 Z"/>
33
+ <path id="ear" fill="#4e9690" d="M 339 170 L 298 158 L 306 184 L 291 199 Q 324 210 346 194 Z"/>
34
+ <path id="forehead" fill="#86c7b6" d="M 352 155 Q 390 138 418 152 L 400 168 L 371 166 Z"/>
35
+ <path id="far-horn" fill="#f0f2ee" d="M 349 157 Q 317 139 328 103 Q 330 128 370 143 L 369 158 Z"/>
36
+ <path id="near-horn" fill="#ffffff" d="M 402 151 Q 375 117 402 78 Q 391 112 430 133 L 430 148 Z"/>
37
+ <path id="horn-roots" fill="none" stroke="#bed4ce" stroke-width="5" d="M 350 153 Q 359 158 369 151 M 405 145 Q 419 151 428 140"/>
38
+ <path id="broad-muzzle" fill="#f2d29b" d="M 412 198 Q 452 185 476 208 Q 487 226 466 236 L 418 241 L 390 221 Z"/>
39
+ <path id="mouth" fill="#080d19" d="M 411 229 Q 440 237 475 221 L 469 234 Q 440 249 417 240 Z"/>
40
+ <path id="jaw" fill="#67a597" d="M 415 241 Q 446 251 469 235 Q 453 258 427 253 L 405 240 Z"/>
41
+ <path id="nose" fill="#080d19" d="M 462 211 Q 473 206 473 217 L 462 221 Z"/>
42
+ <path id="chin-whisker" fill="#e9d5ac" d="M 406 237 Q 396 261 416 276 Q 389 272 387 250 Z"/>
43
+ </g>
44
+ <g id="arms">
45
+ <path id="far-hand" fill="#3c817b" d="M 404 278 Q 438 263 452 291 L 484 317 L 499 327 L 485 341 L 452 330 L 414 307 Z"/>
46
+ <path id="reading-arm" fill="#59a597" d="M 358 286 Q 378 278 393 301 L 412 331 L 456 329 L 475 343 L 462 360 L 405 357 Q 384 351 373 328 Z"/>
47
+ <path id="reading-claws" fill="#f6ddb0" d="M 445 330 L 460 316 L 462 331 L 477 324 L 474 340 L 486 336 L 473 355 L 454 354 Z"/>
48
+ </g>
49
+ <g id="reading-stand">
50
+ <path id="stand" fill="none" stroke="#789da5" stroke-width="9" d="M 489 397 L 481 439 M 651 399 L 662 439 M 491 419 L 656 419"/>
51
+ </g>
52
+ <g id="book">
53
+ <path id="book-cover" fill="#2c6867" d="M 452 349 L 526 335 L 567 350 L 609 331 L 689 348 L 681 397 L 571 418 L 460 398 Z"/>
54
+ <path id="page-left" fill="#e9cd96" d="M 459 338 Q 513 326 567 350 L 567 404 Q 512 380 469 389 Z"/>
55
+ <path id="page-right" fill="#f6dfb0" d="M 567 350 Q 620 321 680 336 L 675 385 Q 616 378 567 404 Z"/>
56
+ <path id="spine" fill="none" stroke="#966846" stroke-width="6" d="M 567 350 L 567 405"/>
57
+ <path id="page-lines" fill="none" stroke="#a78459" stroke-width="4" d="M 478 352 Q 514 348 550 363 M 481 365 Q 516 362 550 376 M 587 359 Q 623 343 661 350 M 587 373 Q 620 358 658 363"/>
58
+ <path id="bookmark" fill="#439e98" d="M 607 340 L 619 337 L 619 396 L 612 388 L 606 399 Z"/>
59
+ </g>
60
+ <g id="memory">
61
+ <path id="memory-shard" fill="none" stroke="#91fff0" stroke-width="5" d="M 557 277 L 578 261 L 599 277 L 578 299 Z M 578 261 L 578 299 M 557 277 L 599 277"/>
62
+ <path id="memory-thread" fill="none" stroke="#51ddcb" stroke-width="4" d="M 574 339 Q 553 323 578 304"/>
63
+ </g>
64
+ <g id="glasses">
65
+ <path id="far-spectacle" fill="none" stroke="#f6ce76" stroke-width="5" d="M 338 184 C 338 167.98374225 349.19288125 155 363 155 C 376.80711875 155 388 167.98374225 388 184 C 388 200.01625775 376.80711875 213 363 213 C 349.19288125 213 338 200.01625775 338 184 Z"/>
66
+ <path id="near-spectacle" fill="none" stroke="#f6ce76" stroke-width="5" d="M 381 181 C 381 162.77460325 394.4314575 148 411 148 C 427.5685425 148 441 162.77460325 441 181 C 441 199.22539675 427.5685425 214 411 214 C 394.4314575 214 381 199.22539675 381 181 Z"/>
67
+ <path id="spectacle-bridge" fill="none" stroke="#f6ce76" stroke-width="5" d="M 385 179 Q 389 171 391 177 M 339 181 L 316 173 M 441 177 L 451 169"/>
68
+ </g>
69
+ <g id="eyes">
70
+ <path id="far-eye-socket" fill="#080d19" d="M 342 184 C 342 170.19288125 351.40202025 159 363 159 C 374.59797975 159 384 170.19288125 384 184 C 384 197.80711875 374.59797975 209 363 209 C 351.40202025 209 342 197.80711875 342 184 Z"/>
71
+ <path id="far-eye-white" fill="#ffffff" d="M 349 184 C 349 174.0588745 355.2680135 166 363 166 C 370.7319865 166 377 174.0588745 377 184 C 377 193.9411255 370.7319865 202 363 202 C 355.2680135 202 349 193.9411255 349 184 Z"/>
72
+ <path id="far-pupil" fill="#080d19" d="M 363 188 C 363 184.13400675 365.23857625 181 368 181 C 370.76142375 181 373 184.13400675 373 188 C 373 191.86599325 370.76142375 195 368 195 C 365.23857625 195 363 191.86599325 363 188 Z"/>
73
+ <path id="near-eye-socket" fill="#080d19" d="M 385 181 C 385 164.98374225 396.6405965 152 411 152 C 425.3594035 152 437 164.98374225 437 181 C 437 197.01625775 425.3594035 210 411 210 C 396.6405965 210 385 197.01625775 385 181 Z"/>
74
+ <path id="near-eye-white" fill="#ffffff" d="M 392 181 C 392 168.8497355 400.50658975 159 411 159 C 421.49341025 159 430 168.8497355 430 181 C 430 193.1502645 421.49341025 203 411 203 C 400.50658975 203 392 193.1502645 392 181 Z"/>
75
+ <path id="near-pupil" fill="#080d19" d="M 411 185 C 411 181.13400675 413.23857625 178 416 178 C 418.76142375 178 421 181.13400675 421 185 C 421 188.86599325 418.76142375 192 416 192 C 413.23857625 192 411 188.86599325 411 185 Z"/>
76
+ </g>
77
+ </svg>
@@ -0,0 +1,56 @@
1
+ import {renderSvgRig} from '../../../factory/svg-rig-contour.mjs';
2
+ export const ARCHIVIST_CLIPS=Object.freeze([{id:'idle',title:'Quiet reading',duration:12000},{id:'attach',title:'Look up from the pages',duration:3000},{id:'recall',title:'Recall a memory',duration:10000}]);
3
+ export const STORY_DURATION=13000;
4
+ const ease=x=>{x=Math.max(0,Math.min(1,x));return x*x*(3-2*x);};
5
+ const pulse=(t,a,b)=>t<=a||t>=b?0:Math.sin(Math.PI*(t-a)/(b-a))**2;
6
+ export function poseRig(rig,scene,ms,{deck=false}={}){
7
+ if(scene==='recall'&&ms>=10000)return poseRig(rig,'idle',ms-10000,{deck});
8
+ if(scene==='attach'&&ms>=3000)return poseRig(rig,'idle',ms-3000,{deck});
9
+ const active=scene==='recall',t=ms%12000;
10
+ const lean=active?pulse(t,0,10000):scene==='attach'?-pulse(t,0,3000):.18*pulse(t,1000,7000);
11
+ const lift=active?ease((t-1200)/2500)*(1-ease((t-7600)/2400)):0;
12
+ const paths=rig.paths.filter(p=>p.group!=='memory').map(p=>({...p,polygons:p.polygons.map(poly=>poly.map(([x,y])=>{
13
+ if(['head','eyes','glasses'].includes(p.group))return [x+4*lean,y+5*lean];
14
+ if(p.group==='neck'){const w=Math.max(0,Math.min(1,(290-y)/85));return [x+4*lean*w,y+5*lean*w];}
15
+ if(p.group==='arms'){const w=Math.max(0,Math.min(1,(x-380)/105));return [x+4*lean*w,y-10*lift*w];}
16
+ if(p.group==='tail'){const w=Math.max(0,(220-x)/160);return [x,y-3*lean*w*w];}
17
+ return [x,y];
18
+ }))}));
19
+ const add=(id,rgb,polygons,stroke=3)=>paths.push({id,group:'effect',rgb,polygons,stroke,erase:false});
20
+ if(active&&t>500&&t<2300){
21
+ const u=(t-500)/1800,x=567-95*Math.cos(Math.PI*u),y=350-42*Math.sin(Math.PI*u);
22
+ add('turning-page',[255,235,182],[[[567,350],[x,y],[x+5,y+38],[567,404]]],3);
23
+ }
24
+ if(active&&lift>.003){
25
+ const cy=350-76*lift,scale=.35+.65*lift;
26
+ const source=rig.paths.find(p=>p.id==='memory-shard');
27
+ paths.push({...source,polygons:source.polygons.map(poly=>poly.map(([x,y])=>[578+(x-578)*scale,cy+(y-277)*scale]))});
28
+ add('recall-thread',[63,181,168],[[[574,339],[565,325],[578,cy+23*scale]]],3);
29
+ if(deck){
30
+ const unfold=18*pulse(t,3500,7900);
31
+ for(const side of [-1,1]){
32
+ const x=578+side*(26+unfold),y=cy+20;
33
+ add('memory-fragment-'+side,[77,211,204],[[[x-7,y],[x,y-9],[x+7,y],[x,y+9],[x-7,y]]],3);
34
+ add('memory-link-'+side,[51,125,132],[[[578+side*18,cy+4],[x,y]]],2);
35
+ }
36
+ }
37
+ const glint=pulse(t,4400,7100);
38
+ if(glint>.01)add('memory-heart',[211,255,243],[[[578-4*glint,cy],[578,cy-6*glint],[578+4*glint,cy],[578,cy+6*glint]]],0);
39
+ }
40
+ const map=new Map(paths.map(p=>[p.id,p]));
41
+ return {...rig,paths,eyes:rig.eyes.map(e=>({...e,socket:map.get(e.socket.id),white:map.get(e.white.id),pupil:map.get(e.pupil.id)}))};
42
+ }
43
+ export function renderArchivist(rig,scene='idle',ms=0,options={}){
44
+ if(!ARCHIVIST_CLIPS.some(c=>c.id===scene))return {ok:false,code:'UNKNOWN_CLIP'};
45
+ if(!Number.isFinite(ms)||ms<0)return {ok:false,code:'INVALID_TIME'};
46
+ if(!options.still&&scene==='recall'&&ms>=10000)return renderArchivist(rig,'idle',ms-10000,options);
47
+ if(!options.still&&scene==='attach'&&ms>=3000)return renderArchivist(rig,'idle',ms-3000,options);
48
+ const t=options.still?({idle:0,attach:1500,recall:5500}[scene]):ms;
49
+ const caption=scene==='recall'?(t<1200?'Find the page.':t<3700?'Draw the memory into the light.':t<7600?'A remembered connection.':'Return it safely to the pages.'):scene==='attach'?'A moment away from the book.':'Remember. Connect. Preserve.';
50
+ return {...renderSvgRig(poseRig(rig,scene,t,options),options),scene,clip:scene,caption};
51
+ }
52
+ export function renderStory(rig,ms,options={}){
53
+ if(!Number.isFinite(ms)||ms<0)return {ok:false,code:'INVALID_TIME'};
54
+ if(options.still)return renderArchivist(rig,'recall',5500,options);
55
+ return ms<3000?renderArchivist(rig,'attach',ms,options):renderArchivist(rig,'recall',ms-3000,options);
56
+ }
@@ -0,0 +1,46 @@
1
+ import {renderSvgRig} from '../../../factory/svg-rig-contour.mjs';
2
+ import {poseRig as previousPose,renderArchivist as previousRender,ARCHIVIST_CLIPS as clips} from '../v3/character.mjs';
3
+ export const ARCHIVIST_CLIPS=Object.freeze([...clips,{id:'cross-reference',title:'Connect two memories',duration:10000}]);
4
+ export const STORY_DURATION=13000;
5
+ const ease=x=>{x=Math.max(0,Math.min(1,x));return x*x*(3-2*x);};
6
+ export function poseCrossReference(rig,t,options={}){
7
+ if(t>=10000)return previousPose(rig,'idle',t-10000,options);
8
+ const base=previousPose(rig,'recall',t,options);
9
+ const paths=base.paths.filter(p=>p.group!=='memory'&&p.group!=='effect');
10
+ const add=(id,rgb,polygons,stroke=3)=>paths.push({id,group:'effect',rgb,polygons,stroke,erase:false});
11
+ const rise=ease((t-600)/2200),returning=1-ease((t-8000)/2000),lift=rise*returning;
12
+ const y=354-85*lift,r=15*lift;
13
+ if(lift>.002){
14
+ for(const [id,x,c]of [['left',526,[99,239,215]],['right',630,[209,155,255]]]){
15
+ add(id+'-memory',c,[[[x-r,y],[x,y-r],[x+r,y],[x,y+r],[x-r,y]]],4);
16
+ if(options.deck)add(id+'-page-thread',c,[[[x,348],[x-6,320],[x,y+r]]],2);
17
+ }
18
+ const connect=ease((t-3000)/1700);
19
+ const curve=u=>[541+74*u,y-29*Math.sin(Math.PI*u)*returning];
20
+ if(connect>.002){
21
+ const points=Array.from({length:25},(_,i)=>curve(connect*i/24));
22
+ add('reference-thread',[93,180,201],[points],options.deck?3:4);
23
+ if(t>=4900&&t<7600){
24
+ const u=ease((t-4900)/2700),[x,cy]=curve(u);
25
+ add('reference-pulse',[235,255,243],[[[x-4,cy],[x,cy-6],[x+4,cy],[x,cy+6]]],0);
26
+ }
27
+ if(t>=7600&&t<8000){
28
+ for(const x of [526,630])add('linked-glint-'+x,[224,255,241],[[[x-4,y],[x,y-5],[x+4,y],[x,y+5]]],0);
29
+ }
30
+ }
31
+ }
32
+ return {...base,paths};
33
+ }
34
+ export function renderArchivist(rig,scene='idle',ms=0,options={}){
35
+ if(scene!=='cross-reference')return previousRender(rig,scene,ms,options);
36
+ if(!Number.isFinite(ms)||ms<0)return {ok:false,code:'INVALID_TIME'};
37
+ if(ms>=10000&&!options.still)return previousRender(rig,'idle',ms-10000,options);
38
+ const t=options.still?6200:ms;
39
+ const caption=t<2800?'Bring two memories into view.':t<4900?'Trace the connection.':t<7600?'Follow the reference.':t<8000?'Two memories, one connection.':'Return both to the pages.';
40
+ return {...renderSvgRig(poseCrossReference(rig,t,options),options),scene,clip:scene,caption};
41
+ }
42
+ export function renderStory(rig,ms,options={}){
43
+ if(!Number.isFinite(ms)||ms<0)return {ok:false,code:'INVALID_TIME'};
44
+ if(options.still)return renderArchivist(rig,'cross-reference',6200,options);
45
+ return ms<3000?renderArchivist(rig,'attach',ms,options):renderArchivist(rig,'cross-reference',ms-3000,options);
46
+ }
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs/promises';
3
+ import {parseSvgRig} from '../../../factory/svg-rig.mjs';
4
+ import {renderArchivist,ARCHIVIST_CLIPS} from './character.mjs';
5
+ import {artSlot,composeMessage} from '../../lucid/recovery/layout.mjs';
6
+ import {cellsToAnsi} from '../../../vendor/quox-dragon/qart.js';
7
+ const args=process.argv.slice(2),values=new Set(['--cols','--rows','--terminal-cols','--terminal-rows','--message','--scene','--at']);
8
+ for(let i=0;i<args.length;i++){
9
+ if(values.has(args[i])){if(!args[i+1]||args[i+1].startsWith('--'))throw Error('Missing option value');i++;}
10
+ else if(!['--large','--mono','--help','--watch','--attach','--intro','--still','--list'].includes(args[i]))throw Error('Unknown option: '+args[i]);
11
+ }
12
+ const value=(name,fallback)=>args.includes(name)?args[args.indexOf(name)+1]:fallback;
13
+ if(args.includes('--help')){console.log('Archivist V4 forge studio. --watch --attach --intro --still --scene ID --at MS --list --large --mono --message TEXT --cols N --rows N --terminal-cols N --terminal-rows N');process.exit(0);}
14
+ if(args.includes('--list')){console.log(ARCHIVIST_CLIPS.map(c=>c.id+' · '+c.title).join('\n'));process.exit(0);}
15
+ const w=Number(value('--terminal-cols',process.stdout.columns||80)),h=Number(value('--terminal-rows',process.stdout.rows||24));
16
+ if(!Number.isSafeInteger(w)||!Number.isSafeInteger(h)||w<1||h<1)throw Error('Invalid terminal size');
17
+ const slot=artSlot(w,h),large=args.includes('--large')||args.includes('--intro'),explicit=large||args.includes('--cols')||args.includes('--rows');
18
+ const cols=Number(value('--cols',large?Math.min(120,w-4):Math.min(39,slot.cols))),rows=Number(value('--rows',large?Math.min(36,h-4):Math.min(11,slot.rows)));
19
+ const message=value('--message','ARCHIVIST\nRemember. Connect. Preserve.');
20
+ if(!slot.showArt&&!explicit){console.log(composeMessage(null,message,{terminalCols:w}).join('\n'));}
21
+ else{
22
+ if(cols>w-4||rows>h-2)throw Error('Requested art exceeds terminal margins');
23
+ const rig=parseSvgRig(await fs.readFile(new URL('../v2/archivist-master.svg',import.meta.url),'utf8'));
24
+ const opts={cols,rows,mono:args.includes('--mono'),still:args.includes('--still'),deck:large};
25
+ const scene=value('--scene',args.includes('--attach')?'attach':'idle');
26
+ const r=renderArchivist(rig,scene,Number(value('--at',0)),opts);
27
+ if(!r.ok)throw Error(r.code);
28
+ if(args.includes('--watch')||args.includes('--attach')||args.includes('--intro')){
29
+ const {watch}=await import('./watch.mjs');
30
+ await watch(rig,opts,{terminalCols:w,terminalRows:h,large,scene:args.includes('--scene')||args.includes('--attach')?scene:null,arrival:args.includes('--attach'),message:args.includes('--message')?message:null});
31
+ }else{
32
+ const shownMessage=args.includes('--message')?message:'ARCHIVIST\n'+r.caption;
33
+ console.log((large?[...cellsToAnsi(r.frame),...composeMessage(null,shownMessage,{terminalCols:w})]:composeMessage(r.frame,shownMessage,{terminalCols:w})).join('\n'));
34
+ }
35
+ }
@@ -0,0 +1,56 @@
1
+ import {performance} from 'node:perf_hooks';
2
+ import {ARCHIVIST_CLIPS,renderArchivist,renderStory,STORY_DURATION} from './character.mjs';
3
+ import {composeMessage} from '../../lucid/recovery/layout.mjs';
4
+ import {cellsToAnsi} from '../../../vendor/quox-dragon/qart.js';
5
+
6
+ /** One bounded performance. No alternate screen or full-screen clearing. */
7
+ export async function watch(paths,options,config){
8
+ if(!process.stdout.isTTY||!process.stdin.isTTY)throw new Error('--watch/--intro needs an interactive terminal. Use --scene and --at for snapshots.');
9
+ const {terminalCols,terminalRows,scene,large,message}=config;
10
+ const clip=scene?ARCHIVIST_CLIPS.find(c=>c.id===scene):null;
11
+ const duration=(clip?.duration??STORY_DURATION)+(config.arrival?6000:0);
12
+ const realCols=process.stdout.columns,realRows=process.stdout.rows;
13
+ const start=performance.now();
14
+ const render=t=>{
15
+ const r=scene?renderArchivist(paths,scene,t,options):renderStory(paths,t,options);
16
+ if(!r?.ok)throw new Error(r?.code??'Invalid story frame');
17
+ const caption=message??'ARCHIVIST · '+r.clip+'\n'+r.caption;
18
+ return large?[...cellsToAnsi(r.frame),...caption.split('\n')]:composeMessage(r.frame,caption,{terminalCols});
19
+ };
20
+ // Reserve enough lines for captions too; keep within the real terminal.
21
+ const height=options.rows+3;
22
+ if(height>=terminalRows)throw new Error('Animation needs room for its caption; reduce --rows.');
23
+ let ended=false,drawn=false,timer,resolveDone,rejectDone;
24
+ const wasRaw=Boolean(process.stdin.isRaw);
25
+ const done=new Promise((resolve,reject)=>{resolveDone=resolve;rejectDone=reject;});
26
+ const cleanup=(error)=>{
27
+ if(ended)return;ended=true;clearInterval(timer);
28
+ process.stdin.off('data',key);process.stdout.off('resize',resized);
29
+ process.off('SIGINT',sigint);process.off('SIGTERM',sigterm);
30
+ process.stdin.setRawMode(wasRaw);process.stdin.pause();
31
+ if(drawn)process.stdout.write(`\x1b[${height}B\r`);
32
+ process.stdout.write('\x1b[0m\x1b[?25h');
33
+ error?rejectDone(error):resolveDone();
34
+ };
35
+ const key=data=>{if(data.includes(3)||data.toString().includes('q'))cleanup();};
36
+ const sigint=()=>cleanup(),sigterm=()=>cleanup();
37
+ const resized=()=>cleanup(new Error('Terminal resized; rerun for the new art slot.'));
38
+ try{
39
+ process.stdin.setRawMode(true);process.stdin.resume();process.stdin.on('data',key);
40
+ process.stdout.on('resize',resized);process.on('SIGINT',sigint);process.on('SIGTERM',sigterm);
41
+ process.stdout.write('\x1b[?25l');
42
+ // Allocate the block once; each frame replaces only these lines.
43
+ process.stdout.write('\n'.repeat(height)+`\x1b[${height}A`);drawn=true;
44
+ const tick=()=>{
45
+ try{
46
+ if(process.stdout.columns!==realCols||process.stdout.rows!==realRows)return resized();
47
+ const t=Math.min(performance.now()-start,duration-1),lines=render(t);
48
+ if(lines.length>height)throw new Error('Message exceeds the animation block; use a shorter message or a static preview.');
49
+ process.stdout.write(Array.from({length:height},(_,i)=>'\r\x1b[2K'+(lines[i]??'')+'\n').join('')+`\x1b[${height}A`);
50
+ if(performance.now()-start>=duration)cleanup();
51
+ }catch(e){cleanup(e);}
52
+ };
53
+ tick();if(!ended)timer=setInterval(tick,1000/6);
54
+ await done;
55
+ }catch(e){cleanup(e);await done;}
56
+ }
@@ -0,0 +1,69 @@
1
+ import {renderSvgRig} from '../../../factory/svg-rig-contour.mjs';
2
+ export const ARGUS_CLIPS=Object.freeze([{id:'idle',title:'Quiet watch',duration:12000},{id:'attach',title:'Acknowledge',duration:3000},{id:'sweep',title:'Sweep the fleet',duration:10000},{id:'triage',title:'Focus the alert',duration:10000},{id:'inspect',title:'Inspect the change',duration:12000}]);
3
+ export const STORY_DURATION=35000;
4
+ const ease=x=>{x=Math.max(0,Math.min(1,x));return x*x*(3-2*x);};
5
+ const pulse=(t,a,b)=>t<=a||t>=b?0:Math.sin(Math.PI*(t-a)/(b-a))**2;
6
+ const sensors=['left-0','left-1','left-2','right-2','right-1','right-0'];
7
+ const xy=[[179,173],[157,236],[242,276],[514,276],[603,234],[574,170]];
8
+ export function poseRig(rig,scene,ms,{deck=false}={}){
9
+ const duration=ARGUS_CLIPS.find(c=>c.id===scene).duration;
10
+ if(scene!=='idle'&&ms>=duration)return poseRig(rig,'idle',ms-duration,{deck});
11
+ const t=ms%duration,env=ease(t/1800)*(1-ease((t-duration+2200)/2200));
12
+ const focus=['triage','inspect'].includes(scene)?env:0;
13
+ const dx=7*focus+(scene==='sweep'?4*Math.sin(t/10000*Math.PI*2)*env:0),dy=scene==='attach'?5*pulse(t,0,3000):3*focus;
14
+ const paths=rig.paths.map(p=>({...p,rgb:p.rgb&&[...p.rgb],polygons:p.polygons.map(poly=>poly.map(([x,y])=>{
15
+ if(p.group==='head'||p.group==='eyes')return [x+dx,y+dy];
16
+ if(p.group==='neck'){const w=Math.max(0,Math.min(1,(280-y)/80));return [x+dx*w,y+dy*w];}
17
+ return [x,y];
18
+ }))}));
19
+ for(let i=0;i<sensors.length;i++){
20
+ const [side,n]=sensors[i].split('-'),p=paths.find(p=>p.id===side+'-sensor-glow-'+n);
21
+ let light=scene==='sweep'?pulse(t,600+i*1100,2600+i*1100):scene==='attach'?pulse(t,700+i*120,2500+i*50):scene==='idle'?pulse(t,1200+i*1400,2400+i*1400)*.45:focus*(i>=3?1:.15);
22
+ p.rgb=[Math.round(65+93*light),Math.round(130+114*light),Math.round(153+86*light)];
23
+ }
24
+ const add=(id,c,poly,stroke=3)=>paths.push({id,group:'effect',rgb:c,polygons:[poly],stroke:stroke*env,erase:false});
25
+ if(scene==='sweep'&&deck){
26
+ for(let i=0;i<6;i++){
27
+ const v=pulse(t,600+i*1100,2600+i*1100);if(v<.02)continue;
28
+ const [x,y]=xy[i],r=24+14*v;
29
+ add('scan-'+i,[77,Math.round(150+90*v),225],Array.from({length:20},(_,j)=>{const a=-Math.PI*.8+Math.PI*1.6*j/19;return [x+r*Math.cos(a),y+r*.65*Math.sin(a)];}),2);
30
+ }
31
+ }
32
+ if(scene==='triage'&&env>0){
33
+ const select=ease((t-3000)/2000),fade=1-select;
34
+ for(let i=0;i<3;i++){
35
+ const x=546+i*47,y=371-(i===1?17*select:0),r=(i===1?10:8*(.3+.7*fade))*env;
36
+ add('alert-'+i,i===1?[255,185,68]:[Math.round(70*fade+20),Math.round(160*fade+35),Math.round(180*fade+40)],[[x-r,y],[x,y-r],[x+r,y],[x,y+r],[x-r,y]],3);
37
+ }
38
+ if(select>0){const x=593,y=354,r=20*select*env;add('focus-bracket',[255,203,103],[[x-r,y-10],[x-r,y+12],[x+r,y+12],[x+r,y-10]],3);}
39
+ }
40
+ if(scene==='inspect'&&env>0){
41
+ const open=ease((t-600)/2000)*(1-ease((t-9800)/2200)),reveal=ease((t-3500)/1700),width=48*open;
42
+ for(let i=0;i<2;i++){
43
+ const x=534+i*71;
44
+ add('file-'+i,[91,185,203],[[x,337],[x+width,337],[x+width,407],[x,407],[x,337]],3);
45
+ for(let j=0;j<3;j++)add('line-'+i+'-'+j,i===1&&j===1&&reveal>0?[255,185,68]:[111,191,204],[[x+8*open,352+j*18],[x+(j===1&&i===1?38:31)*open,352+j*18]],i===1&&j===1?5:3);
46
+ }
47
+ if(reveal>0&&deck)add('changed-link',[255,185,68],[[582,370],[605,370]],3);
48
+ }
49
+ if(scene==='inspect')for(const p of paths)if(p.group==='effect')p.polygons=p.polygons.map(poly=>poly.map(([x,y])=>[x,372+(y-372)*env]));
50
+ const map=new Map(paths.map(p=>[p.id,p]));
51
+ return {...rig,paths,eyes:rig.eyes.map(e=>({...e,socket:map.get(e.socket.id),white:map.get(e.white.id),pupil:map.get(e.pupil.id)}))};
52
+ }
53
+ export function renderArgus(rig,scene='idle',ms=0,options={}){
54
+ const c=ARGUS_CLIPS.find(c=>c.id===scene);if(!c)return {ok:false,code:'UNKNOWN_CLIP'};
55
+ if(!Number.isFinite(ms)||ms<0)return {ok:false,code:'INVALID_TIME'};
56
+ if(!options.still&&scene!=='idle'&&ms>=c.duration)return renderArgus(rig,'idle',ms-c.duration,options);
57
+ const t=options.still?({idle:0,attach:1400,sweep:4000,triage:6000,inspect:6500}[scene]):ms;
58
+ const caption={idle:'Watching quietly.',attach:'Argus acknowledges you.',sweep:'Sweep · six watchful sensors.',triage:t<3000?'Triage · three incoming signals.':'Triage · focus the amber alert.',inspect:t<3500?'Inspect · unfold both file traces.':'Inspect · highlight the changed line.'}[scene];
59
+ return {...renderSvgRig(poseRig(rig,scene,t,options),options),scene,clip:scene,caption};
60
+ }
61
+ export function renderStory(rig,ms,options={}){
62
+ if(!Number.isFinite(ms)||ms<0)return {ok:false,code:'INVALID_TIME'};
63
+ if(options.still)return renderArgus(rig,'inspect',6500,options);
64
+ if(ms<3000)return renderArgus(rig,'attach',ms,options);
65
+ if(ms<13000)return renderArgus(rig,'sweep',ms-3000,options);
66
+ if(ms<23000)return renderArgus(rig,'triage',ms-13000,options);
67
+ if(ms<35000)return renderArgus(rig,'inspect',ms-23000,options);
68
+ return renderArgus(rig,'idle',ms-35000,options);
69
+ }
@@ -0,0 +1,68 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 480"><title>Argus — the many-eyed watcher</title>
2
+ <g id="left-wing">
3
+ <path id="left-sail" d="M 335 288 Q 241 183 118 90 Q 141 161 87 218 Q 143 216 152 290 Q 208 264 243 327 L 340 332 Z" fill="#244d6b"/>
4
+ <path id="left-web" d="M 326 291 Q 228 192 118 90 L 200 232 L 152 290 L 251 281 L 243 327 Z" fill="#356987"/>
5
+ <path id="left-ribs" d="M 335 288 Q 241 183 118 90 M 335 288 L 87 218 M 335 288 L 152 290 M 335 288 L 243 327" fill="none" stroke="#83a5b2" stroke-width="7"/>
6
+ </g>
7
+ <g id="left-sensors">
8
+ <path id="left-sensor-rim-0" d="M 157 173 Q 179 148 205 173 Q 179 198 153 173 Z" fill="#080d19"/>
9
+ <path id="left-sensor-glow-0" d="M 160 173 C 160 166.372583 168.50658975 161 179 161 C 189.49341025 161 198 166.372583 198 173 C 198 179.627417 189.49341025 185 179 185 C 168.50658975 185 160 179.627417 160 173 Z" fill="#9ef4ef"/>
10
+ <path id="left-sensor-core-0" d="M 171 173 C 171 167.4771525 174.581722 163 179 163 C 183.418278 163 187 167.4771525 187 173 C 187 178.5228475 183.418278 183 179 183 C 174.581722 183 171 178.5228475 171 173 Z" fill="#080d19"/>
11
+ <path id="left-sensor-rim-1" d="M 135 236 Q 157 211 183 236 Q 157 261 131 236 Z" fill="#080d19"/>
12
+ <path id="left-sensor-glow-1" d="M 138 236 C 138 229.372583 146.50658975 224 157 224 C 167.49341025 224 176 229.372583 176 236 C 176 242.627417 167.49341025 248 157 248 C 146.50658975 248 138 242.627417 138 236 Z" fill="#9ef4ef"/>
13
+ <path id="left-sensor-core-1" d="M 149 236 C 149 230.4771525 152.581722 226 157 226 C 161.418278 226 165 230.4771525 165 236 C 165 241.5228475 161.418278 246 157 246 C 152.581722 246 149 241.5228475 149 236 Z" fill="#080d19"/>
14
+ <path id="left-sensor-rim-2" d="M 220 276 Q 242 251 268 276 Q 242 301 216 276 Z" fill="#080d19"/>
15
+ <path id="left-sensor-glow-2" d="M 223 276 C 223 269.372583 231.50658975 264 242 264 C 252.49341025 264 261 269.372583 261 276 C 261 282.627417 252.49341025 288 242 288 C 231.50658975 288 223 282.627417 223 276 Z" fill="#9ef4ef"/>
16
+ <path id="left-sensor-core-2" d="M 234 276 C 234 270.4771525 237.581722 266 242 266 C 246.418278 266 250 270.4771525 250 276 C 250 281.5228475 246.418278 286 242 286 C 237.581722 286 234 281.5228475 234 276 Z" fill="#080d19"/>
17
+ </g>
18
+ <g id="right-wing">
19
+ <path id="right-sail" d="M 421 288 Q 502 182 632 85 Q 615 160 672 213 Q 617 216 609 288 Q 555 262 517 327 L 420 332 Z" fill="#28546c"/>
20
+ <path id="right-web" d="M 428 288 Q 531 188 632 85 L 563 229 L 609 288 L 509 280 L 517 327 Z" fill="#3b708a"/>
21
+ <path id="right-ribs" d="M 421 288 Q 502 182 632 85 M 421 288 L 672 213 M 421 288 L 609 288 M 421 288 L 517 327" fill="none" stroke="#83a5b2" stroke-width="7"/>
22
+ </g>
23
+ <g id="right-sensors">
24
+ <path id="right-sensor-rim-0" d="M 552 170 Q 574 145 600 170 Q 574 195 548 170 Z" fill="#080d19"/>
25
+ <path id="right-sensor-glow-0" d="M 555 170 C 555 163.372583 563.50658975 158 574 158 C 584.49341025 158 593 163.372583 593 170 C 593 176.627417 584.49341025 182 574 182 C 563.50658975 182 555 176.627417 555 170 Z" fill="#9ef4ef"/>
26
+ <path id="right-sensor-core-0" d="M 566 170 C 566 164.4771525 569.581722 160 574 160 C 578.418278 160 582 164.4771525 582 170 C 582 175.5228475 578.418278 180 574 180 C 569.581722 180 566 175.5228475 566 170 Z" fill="#080d19"/>
27
+ <path id="right-sensor-rim-1" d="M 581 234 Q 603 209 629 234 Q 603 259 577 234 Z" fill="#080d19"/>
28
+ <path id="right-sensor-glow-1" d="M 584 234 C 584 227.372583 592.50658975 222 603 222 C 613.49341025 222 622 227.372583 622 234 C 622 240.627417 613.49341025 246 603 246 C 592.50658975 246 584 240.627417 584 234 Z" fill="#9ef4ef"/>
29
+ <path id="right-sensor-core-1" d="M 595 234 C 595 228.4771525 598.581722 224 603 224 C 607.418278 224 611 228.4771525 611 234 C 611 239.5228475 607.418278 244 603 244 C 598.581722 244 595 239.5228475 595 234 Z" fill="#080d19"/>
30
+ <path id="right-sensor-rim-2" d="M 492 276 Q 514 251 540 276 Q 514 301 488 276 Z" fill="#080d19"/>
31
+ <path id="right-sensor-glow-2" d="M 495 276 C 495 269.372583 503.50658975 264 514 264 C 524.49341025 264 533 269.372583 533 276 C 533 282.627417 524.49341025 288 514 288 C 503.50658975 288 495 282.627417 495 276 Z" fill="#9ef4ef"/>
32
+ <path id="right-sensor-core-2" d="M 506 276 C 506 270.4771525 509.581722 266 514 266 C 518.418278 266 522 270.4771525 522 276 C 522 281.5228475 518.418278 286 514 286 C 509.581722 286 506 281.5228475 506 276 Z" fill="#080d19"/>
33
+ </g>
34
+ <g id="tail">
35
+ <path id="tail-mass" d="M 321 345 Q 252 327 223 363 C 187 409 118 422 87 380 Q 73 361 86 338 C 49 358 54 398 91 420 C 152 456 245 407 282 393 L 340 385 Z" fill="#39617b"/>
36
+ <path id="tail-ridge" d="M 313 353 Q 253 342 225 378 Q 170 432 106 409" fill="none" stroke="#54a4b6" stroke-width="6"/>
37
+ </g>
38
+ <g id="body">
39
+ <path id="torso" d="M 332 261 Q 379 239 423 266 Q 477 292 480 346 Q 477 388 419 405 L 329 403 Q 278 386 283 341 Q 286 288 332 261 Z" fill="#31566f"/>
40
+ <path id="breast" d="M 346 267 L 404 265 Q 431 312 416 358 L 379 385 L 342 360 Q 323 313 346 267 Z" fill="#7e9da7"/>
41
+ <path id="breast-plates" d="M 340 298 Q 378 317 418 296 M 338 322 Q 378 344 420 320 M 345 348 Q 380 367 414 346" fill="none" stroke="#294756" stroke-width="5"/>
42
+ <path id="haunches" d="M 309 327 Q 252 330 270 389 L 256 411 L 329 415 L 348 393 L 326 366 Z M 437 327 Q 490 322 491 377 L 510 409 L 438 415 L 414 391 L 430 367 Z" fill="#466f89"/>
43
+ <path id="forelegs" d="M 342 329 L 356 349 L 342 407 L 301 415 L 301 401 L 320 388 Z M 405 328 L 428 329 L 433 389 L 458 403 L 454 416 L 409 408 L 397 355 Z" fill="#678c9e"/>
44
+ <path id="claws" d="M 304 403 L 294 420 L 314 414 L 321 403 L 318 421 L 335 414 M 423 402 L 419 420 L 435 414 L 442 404 L 443 421 L 460 414" fill="#ede9d9"/>
45
+ </g>
46
+ <g id="neck">
47
+ <path id="neck" d="M 341 199 L 405 195 Q 404 250 430 276 Q 381 307 331 277 Q 353 244 341 199 Z" fill="#527b92"/>
48
+ </g>
49
+ <g id="head">
50
+ <path id="far-horn" d="M 343 157 Q 296 135 297 93 Q 300 111 326 120 L 359 133 Z" fill="#dddcd1"/>
51
+ <path id="near-horn" d="M 401 151 Q 450 125 450 78 Q 465 135 422 167 Z" fill="#f1eddf"/>
52
+ <path id="cheek-fins" d="M 327 169 L 293 151 L 304 193 L 292 216 L 327 221 Z M 418 170 L 451 147 L 444 189 L 456 208 L 420 222 Z" fill="#4e8caa"/>
53
+ <path id="skull" d="M 326 155 Q 376 126 420 158 L 433 193 Q 432 227 397 243 L 351 241 Q 313 222 315 188 Z" fill="#759ba9"/>
54
+ <path id="brow" d="M 321 171 Q 338 148 364 154 L 375 170 L 386 154 Q 409 152 428 170 L 403 165 L 375 179 L 349 164 Z" fill="#244558"/>
55
+ <path id="snout" d="M 342 207 Q 376 193 412 205 L 428 225 Q 410 249 374 247 Q 338 247 327 227 Z" fill="#91adb2"/>
56
+ <path id="mouth" d="M 336 229 Q 376 245 419 225" fill="none" stroke="#080d19" stroke-width="6"/>
57
+ <path id="nostrils" d="M 345 216 L 354 214 L 352 222 Z M 398 214 L 409 216 L 401 222 Z" fill="#080d19"/>
58
+ <path id="chin" d="M 359 247 L 375 266 L 391 246 Z" fill="#d7dfd5"/>
59
+ </g>
60
+ <g id="eyes">
61
+ <path id="far-eye-socket" d="M 323 187 C 323 173.19288125 332.40202025 162 344 162 C 355.59797975 162 365 173.19288125 365 187 C 365 200.80711875 355.59797975 212 344 212 C 332.40202025 212 323 200.80711875 323 187 Z" fill="#080d19"/>
62
+ <path id="far-eye-white" d="M 330 187 C 330 177.0588745 336.2680135 169 344 169 C 351.7319865 169 358 177.0588745 358 187 C 358 196.9411255 351.7319865 205 344 205 C 336.2680135 205 330 196.9411255 330 187 Z" fill="#ffffff"/>
63
+ <path id="far-pupil" d="M 341 188 C 341 184.13400675 343.23857625 181 346 181 C 348.76142375 181 351 184.13400675 351 188 C 351 191.86599325 348.76142375 195 346 195 C 343.23857625 195 341 191.86599325 341 188 Z" fill="#080d19"/>
64
+ <path id="near-eye-socket" d="M 375 186 C 375 171.08831175 386.19288125 159 400 159 C 413.80711875 159 425 171.08831175 425 186 C 425 200.91168825 413.80711875 213 400 213 C 386.19288125 213 375 200.91168825 375 186 Z" fill="#080d19"/>
65
+ <path id="near-eye-white" d="M 382 186 C 382 174.954305 390.0588745 166 400 166 C 409.9411255 166 418 174.954305 418 186 C 418 197.045695 409.9411255 206 400 206 C 390.0588745 206 382 197.045695 382 186 Z" fill="#ffffff"/>
66
+ <path id="near-pupil" d="M 397 187 C 397 183.13400675 399.23857625 180 402 180 C 404.76142375 180 407 183.13400675 407 187 C 407 190.86599325 404.76142375 194 402 194 C 399.23857625 194 397 190.86599325 397 187 Z" fill="#080d19"/>
67
+ </g>
68
+ </svg>
@@ -0,0 +1,63 @@
1
+ import {renderSvgRig} from '../../../factory/svg-rig-contour.mjs';
2
+ import {poseRig as oldPose,renderArgus as oldRender,ARGUS_CLIPS as oldClips} from '../v2/character.mjs';
3
+ export const ARGUS_CLIPS=Object.freeze([...oldClips,{id:'tongue',title:'Cheeky little blep',duration:3500},{id:'wazuh',title:'Claws up, let loose',duration:6500},{id:'fire',title:'A forward cyan puff',duration:5000}]);
4
+ export const STORY_DURATION=15000;
5
+ const framed=rig=>({...rig,bounds:{...rig.bounds,top:50}});
6
+ const ease=x=>{x=Math.max(0,Math.min(1,x));return x*x*(3-2*x);};
7
+ const env=(t,d)=>ease(t/800)*(1-ease((t-d+1200)/1200));
8
+ const rotate=(x,y,cx,cy,a)=>[cx+(x-cx)*Math.cos(a)-(y-cy)*Math.sin(a),cy+(x-cx)*Math.sin(a)+(y-cy)*Math.cos(a)];
9
+ export function poseRig(rig,scene,ms,options={}){
10
+ rig=framed(rig);
11
+ if(!['tongue','wazuh','fire'].includes(scene))return oldPose(rig,scene,ms,options);
12
+ const d=ARGUS_CLIPS.find(c=>c.id===scene).duration;if(ms>=d)return oldPose(rig,'idle',ms-d,options);
13
+ const e=env(ms,d),shake=scene==='wazuh'?.13*Math.sin(ms/150)*e:scene==='tongue'?.07*e:0;
14
+ const head=(x,y)=>rotate(x,y,375,258,shake);
15
+ const arms=(x,y)=>{const left=x<375;return rotate(x,y,left?342:411,332,(left?1:-1)*2.55*e);};
16
+ const base=oldPose(rig,'idle',0,options);
17
+ const paths=base.paths.map(p=>({...p,polygons:p.polygons.map(poly=>poly.map(([x,y])=>{
18
+ if(p.group==='head'||p.group==='eyes')return head(x,y);
19
+ if(p.group==='neck'){const w=Math.max(0,Math.min(1,(282-y)/75));return rotate(x,y,375,258,shake*w);}
20
+ if(scene==='wazuh'&&(p.id==='forelegs'||p.id==='claws'))return arms(x,y);
21
+ return [x,y];
22
+ }))}));
23
+ if(scene==='wazuh'&&e>0){
24
+ const arms=paths.filter(p=>p.id==='forelegs'||p.id==='claws');
25
+ for(const p of arms)paths.splice(paths.indexOf(p),1);
26
+ for(const p of arms){paths.push({...p,id:p.id+'-rim',rgb:[8,13,25],erase:true,stroke:7*e});paths.push({...p,rgb:p.id==='forelegs'?p.rgb.map((v,i)=>Math.round(v+([135,190,204][i]-v)*e)):p.rgb});}
27
+ }
28
+ const add=(id,rgb,poly,stroke=0,erase=false)=>paths.push({id,group:'personality',rgb,polygons:[poly],stroke,erase});
29
+ // The mouth remains the emission origin: forward-facing puff toward the viewer.
30
+ const mouth=poly=>poly.map(([x,y])=>head(x,y));
31
+ if(e>.001){
32
+ add('open-mouth',[8,13,25],mouth([[352,229],[375,225],[400,229],[394,229+19*e],[375,233+22*e],[356,229+19*e]]),0,true);
33
+ if(scene==='tongue'||scene==='wazuh'){
34
+ const poke=scene==='tongue'?e:e*.65;
35
+ add('tongue',[244,113,151],mouth([[368,237],[382,237],[384,240+28*poke],[377,248+28*poke],[368,245+28*poke]]));
36
+ add('tongue-crease',[139,46,88],mouth([[376,243],[377,249+17*poke]]),2*poke);
37
+ }
38
+ }
39
+ if(scene==='fire'){
40
+ const breath=ease((ms-1100)/400)*(1-ease((ms-3100)/900));
41
+ if(breath>.001){
42
+ const y=240,len=95*breath,w=(24+6*Math.sin(ms/85))*breath;
43
+ add('cyan-flame',[30,168,223],[[365,y],[385,y],[392+w,y+len*.7],[380+w,y+len*.55],[389+w*.6,y+len],[375,y+len*.8],[362-w*.6,y+len],[367-w,y+len*.6],[358-w,y+len*.73]]);
44
+ add('flame-heart',[179,255,244],[[370,y],[381,y],[389+w*.3,y+len*.65],[376,y+len*.48],[364-w*.2,y+len*.7]]);
45
+ }
46
+ }
47
+ const map=new Map(paths.map(p=>[p.id,p]));
48
+ return {...base,paths,eyes:rig.eyes.map(e=>({...e,socket:map.get(e.socket.id),white:map.get(e.white.id),pupil:map.get(e.pupil.id)}))};
49
+ }
50
+ export function renderArgus(rig,scene='idle',ms=0,options={}){
51
+ rig=framed(rig);
52
+ if(!['tongue','wazuh','fire'].includes(scene))return oldRender(rig,scene,ms,options);
53
+ if(!Number.isFinite(ms)||ms<0)return {ok:false,code:'INVALID_TIME'};
54
+ const d=ARGUS_CLIPS.find(c=>c.id===scene).duration;
55
+ if(!options.still&&ms>=d)return oldRender(rig,'idle',ms-d,options);
56
+ const t=options.still?({tongue:1600,wazuh:2200,fire:2400}[scene]):ms;
57
+ return {...renderSvgRig(poseRig(rig,scene,t,options),options),scene,clip:scene,caption:{tongue:'A tiny breach of decorum.',wazuh:'Claws up. Absolutely no indoor voice.',fire:'One small puff. Feeling better.'}[scene]};
58
+ }
59
+ export function renderStory(rig,ms,options={}){
60
+ if(!Number.isFinite(ms)||ms<0)return {ok:false,code:'INVALID_TIME'};
61
+ if(options.still)return renderArgus(rig,'wazuh',2200,options);
62
+ return ms<3500?renderArgus(rig,'tongue',ms,options):ms<10000?renderArgus(rig,'wazuh',ms-3500,options):renderArgus(rig,'fire',ms-10000,options);
63
+ }