@actalk/inkos-studio 1.3.10 → 1.3.11-canary.41.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 (65) hide show
  1. package/dist/api/book-create.d.ts +1 -0
  2. package/dist/api/book-create.d.ts.map +1 -1
  3. package/dist/api/book-create.js.map +1 -1
  4. package/dist/api/server.d.ts.map +1 -1
  5. package/dist/api/server.js +231 -42
  6. package/dist/api/server.js.map +1 -1
  7. package/dist/assets/{_baseUniq-BIUIXKF-.js → _baseUniq-7G2LyAeZ.js} +1 -1
  8. package/dist/assets/{arc-aMAvM09U.js → arc-_dxZ6Uaw.js} +1 -1
  9. package/dist/assets/{architectureDiagram-Q4EWVU46-D_lBhs6P.js → architectureDiagram-Q4EWVU46-DG9F2FBp.js} +1 -1
  10. package/dist/assets/{blockDiagram-DXYQGD6D-KR-H0WWB.js → blockDiagram-DXYQGD6D-D-CY5CJN.js} +1 -1
  11. package/dist/assets/{c4Diagram-AHTNJAMY-Cwu8C3Zc.js → c4Diagram-AHTNJAMY-DMYQ0GVk.js} +1 -1
  12. package/dist/assets/channel-CSdpem6d.js +1 -0
  13. package/dist/assets/{chunk-4BX2VUAB-D08-gave.js → chunk-4BX2VUAB-CQaukBhH.js} +1 -1
  14. package/dist/assets/{chunk-4TB4RGXK-B1iuP-xq.js → chunk-4TB4RGXK-DmrBJvqL.js} +1 -1
  15. package/dist/assets/{chunk-55IACEB6-CPT6hIdV.js → chunk-55IACEB6-CU4wdVK-.js} +1 -1
  16. package/dist/assets/{chunk-EDXVE4YY-BGhU0HmZ.js → chunk-EDXVE4YY-8z43jT1h.js} +1 -1
  17. package/dist/assets/{chunk-FMBD7UC4-BbfWy7cs.js → chunk-FMBD7UC4-1BEu2BaJ.js} +1 -1
  18. package/dist/assets/{chunk-OYMX7WX6-DDlL2KHi.js → chunk-OYMX7WX6-DB0Db5P1.js} +1 -1
  19. package/dist/assets/{chunk-QZHKN3VN-MBgry6KJ.js → chunk-QZHKN3VN-iJXHP1Sf.js} +1 -1
  20. package/dist/assets/{chunk-YZCP3GAM-CbVklXFK.js → chunk-YZCP3GAM-CiMYHKaZ.js} +1 -1
  21. package/dist/assets/classDiagram-6PBFFD2Q-B13AJwWX.js +1 -0
  22. package/dist/assets/classDiagram-v2-HSJHXN6E-B13AJwWX.js +1 -0
  23. package/dist/assets/clone-BaUjTciQ.js +1 -0
  24. package/dist/assets/{cose-bilkent-S5V4N54A-DqDcpKPh.js → cose-bilkent-S5V4N54A-BAVfrwUP.js} +1 -1
  25. package/dist/assets/{dagre-KV5264BT-B9xLUzSB.js → dagre-KV5264BT-COBdfx_y.js} +1 -1
  26. package/dist/assets/{diagram-5BDNPKRD-DL4-J1SV.js → diagram-5BDNPKRD-B4NwbGUz.js} +1 -1
  27. package/dist/assets/{diagram-G4DWMVQ6-DC-cxfUZ.js → diagram-G4DWMVQ6-fH0CQPnB.js} +1 -1
  28. package/dist/assets/{diagram-MMDJMWI5-D5Aj0XZi.js → diagram-MMDJMWI5-D40FXwBs.js} +1 -1
  29. package/dist/assets/{diagram-TYMM5635-Cf-PA8ze.js → diagram-TYMM5635-iCuM3F7o.js} +1 -1
  30. package/dist/assets/{erDiagram-SMLLAGMA-KpouMvBQ.js → erDiagram-SMLLAGMA-DrzCmbj5.js} +1 -1
  31. package/dist/assets/{flowDiagram-DWJPFMVM-ChC9iSz9.js → flowDiagram-DWJPFMVM-BQvPE59f.js} +1 -1
  32. package/dist/assets/{ganttDiagram-T4ZO3ILL-Ba7mX0Y_.js → ganttDiagram-T4ZO3ILL-D9PnE2oQ.js} +1 -1
  33. package/dist/assets/{gitGraphDiagram-UUTBAWPF-VgeSu4pJ.js → gitGraphDiagram-UUTBAWPF-C-QllrHf.js} +1 -1
  34. package/dist/assets/{graph-tzWJpM37.js → graph-Dywc2VEx.js} +1 -1
  35. package/dist/assets/{highlighted-body-OFNGDK62-mJKtZPbz.js → highlighted-body-OFNGDK62-DaFK7awa.js} +1 -1
  36. package/dist/assets/{index-B5Wyr9-b.js → index-C2YvCS7q.js} +235 -235
  37. package/dist/assets/index-DQJevqxF.css +1 -0
  38. package/dist/assets/{infoDiagram-42DDH7IO-D0QACLUg.js → infoDiagram-42DDH7IO-2hFeNxWi.js} +1 -1
  39. package/dist/assets/{ishikawaDiagram-UXIWVN3A-BHyP0O_G.js → ishikawaDiagram-UXIWVN3A-CVJY8gu8.js} +1 -1
  40. package/dist/assets/{journeyDiagram-VCZTEJTY-Bz7DUjEr.js → journeyDiagram-VCZTEJTY-CKwHhVW2.js} +1 -1
  41. package/dist/assets/{kanban-definition-6JOO6SKY-DQ_d7Tz2.js → kanban-definition-6JOO6SKY-2GDDzYma.js} +1 -1
  42. package/dist/assets/{layout-DSXDnsbV.js → layout-BYHHetdW.js} +1 -1
  43. package/dist/assets/{linear-BjDS5uge.js → linear-D6iQrqq7.js} +1 -1
  44. package/dist/assets/{min-DTqcDUV8.js → min-D9uYkhrp.js} +1 -1
  45. package/dist/assets/{mindmap-definition-QFDTVHPH-BnSR0r7K.js → mindmap-definition-QFDTVHPH-luhQnNv0.js} +1 -1
  46. package/dist/assets/{pieDiagram-DEJITSTG-Bz7EEyv2.js → pieDiagram-DEJITSTG-jkwzyOiV.js} +1 -1
  47. package/dist/assets/{quadrantDiagram-34T5L4WZ-65j7Dqyo.js → quadrantDiagram-34T5L4WZ-BFGvMOfi.js} +1 -1
  48. package/dist/assets/{requirementDiagram-MS252O5E-D3RqbgZ0.js → requirementDiagram-MS252O5E-BbwkmIU4.js} +1 -1
  49. package/dist/assets/{sankeyDiagram-XADWPNL6-qI55uI06.js → sankeyDiagram-XADWPNL6-DRxiWtKC.js} +1 -1
  50. package/dist/assets/{sequenceDiagram-FGHM5R23-DWiM9nfv.js → sequenceDiagram-FGHM5R23-DfCjYThV.js} +1 -1
  51. package/dist/assets/{stateDiagram-FHFEXIEX-esMtXi1p.js → stateDiagram-FHFEXIEX-BQG8YiO-.js} +1 -1
  52. package/dist/assets/stateDiagram-v2-QKLJ7IA2-AwixmqPB.js +1 -0
  53. package/dist/assets/{timeline-definition-GMOUNBTQ-D-nc3pMe.js → timeline-definition-GMOUNBTQ-DBfF6YXh.js} +1 -1
  54. package/dist/assets/{vennDiagram-DHZGUBPP-D-lm61Mn.js → vennDiagram-DHZGUBPP-DrPJ3v19.js} +1 -1
  55. package/dist/assets/{wardley-RL74JXVD-DvBsmvaJ.js → wardley-RL74JXVD-ChBtSh1E.js} +1 -1
  56. package/dist/assets/{wardleyDiagram-NUSXRM2D-BZH1nsAY.js → wardleyDiagram-NUSXRM2D-Cq06_btE.js} +1 -1
  57. package/dist/assets/{xychartDiagram-5P7HB3ND-gFMR57LI.js → xychartDiagram-5P7HB3ND-CUxp1BUy.js} +1 -1
  58. package/dist/index.html +2 -2
  59. package/package.json +2 -2
  60. package/dist/assets/channel-BnEvL0hC.js +0 -1
  61. package/dist/assets/classDiagram-6PBFFD2Q-DeOSGvih.js +0 -1
  62. package/dist/assets/classDiagram-v2-HSJHXN6E-DeOSGvih.js +0 -1
  63. package/dist/assets/clone-nC-ZF2rq.js +0 -1
  64. package/dist/assets/index-Cks9xRnD.css +0 -1
  65. package/dist/assets/stateDiagram-v2-QKLJ7IA2-BP_Hh6U7.js +0 -1
@@ -6,6 +6,7 @@ export interface StudioCreateBookBody {
6
6
  readonly platform?: string;
7
7
  readonly chapterWordCount?: number;
8
8
  readonly targetChapters?: number;
9
+ readonly blurb?: string;
9
10
  }
10
11
  export interface StudioBookConfigDraft {
11
12
  readonly id: string;
@@ -1 +1 @@
1
- {"version":3,"file":"book-create.d.ts","sourceRoot":"","sources":["../../src/api/book-create.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE7E,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,UAAU,gBAAgB;IACxB,QAAQ,CAAC,IAAI,EAAE;QAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IACvC,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;IAC1C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,UAAU,6BAA6B;IACrC,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IAClC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,wBAAgB,uBAAuB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,QAAQ,CAEnE;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,oBAAoB,EAAE,GAAG,EAAE,MAAM,GAAG,qBAAqB,CAqBpG;AAMD,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,6BAAkC,GAC1C,OAAO,CAAC,gBAAgB,CAAC,CAqB3B"}
1
+ {"version":3,"file":"book-create.d.ts","sourceRoot":"","sources":["../../src/api/book-create.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE7E,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,UAAU,gBAAgB;IACxB,QAAQ,CAAC,IAAI,EAAE;QAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IACvC,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;IAC1C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,UAAU,6BAA6B;IACrC,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IAClC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,wBAAgB,uBAAuB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,QAAQ,CAEnE;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,oBAAoB,EAAE,GAAG,EAAE,MAAM,GAAG,qBAAqB,CAqBpG;AAMD,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,6BAAkC,GAC1C,OAAO,CAAC,gBAAgB,CAAC,CAqB3B"}
@@ -1 +1 @@
1
- {"version":3,"file":"book-create.js","sourceRoot":"","sources":["../../src/api/book-create.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAiB,MAAM,oBAAoB,CAAC;AAqC7E,MAAM,UAAU,uBAAuB,CAAC,QAAiB;IACvD,OAAO,wBAAwB,CAAC,QAAQ,CAAC,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,IAA0B,EAAE,GAAW;IAC3E,OAAO;QACL,EAAE,EAAE,IAAI,CAAC,KAAK;aACX,WAAW,EAAE;aACb,OAAO,CAAC,yBAAyB,EAAE,GAAG,CAAC;aACvC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;aACnB,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;QACf,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,QAAQ,EAAE,uBAAuB,CAAC,IAAI,CAAC,QAAQ,CAAC;QAChD,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,MAAM,EAAE,WAAW;QACnB,cAAc,EAAE,IAAI,CAAC,cAAc,IAAI,GAAG;QAC1C,gBAAgB,EAAE,IAAI,CAAC,gBAAgB,IAAI,IAAI;QAC/C,GAAG,CAAC,IAAI,CAAC,QAAQ,KAAK,IAAI;YACxB,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAa,EAAE;YAC7B,CAAC,CAAC,IAAI,CAAC,QAAQ,KAAK,IAAI;gBACtB,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAa,EAAE;gBAC7B,CAAC,CAAC,EAAE,CAAC;QACT,SAAS,EAAE,GAAG;QACd,SAAS,EAAE,GAAG;KACf,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,OAAe;IAClC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;AAChE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,MAAc,EACd,UAAyC,EAAE;IAE3C,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,KAAK,CAAC;IAC7C,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,WAAW,CAAC;IACzC,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,CAAC,CAAC;IAC7C,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,GAAG,CAAC;IAEjD,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,WAAW,EAAE,OAAO,IAAI,CAAC,EAAE,CAAC;QAC3D,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,iBAAiB,kBAAkB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAChF,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;YAChB,OAAO,MAAM,QAAQ,CAAC,IAAI,EAAsB,CAAC;QACnD,CAAC;QAED,IAAI,OAAO,GAAG,WAAW,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACrD,MAAM,IAAI,CAAC,YAAY,CAAC,CAAC;YACzB,SAAS;QACX,CAAC;QAED,MAAM;IACR,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,SAAS,MAAM,yBAAyB,WAAW,YAAY,CAAC,CAAC;AACnF,CAAC"}
1
+ {"version":3,"file":"book-create.js","sourceRoot":"","sources":["../../src/api/book-create.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAiB,MAAM,oBAAoB,CAAC;AAsC7E,MAAM,UAAU,uBAAuB,CAAC,QAAiB;IACvD,OAAO,wBAAwB,CAAC,QAAQ,CAAC,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,IAA0B,EAAE,GAAW;IAC3E,OAAO;QACL,EAAE,EAAE,IAAI,CAAC,KAAK;aACX,WAAW,EAAE;aACb,OAAO,CAAC,yBAAyB,EAAE,GAAG,CAAC;aACvC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;aACnB,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;QACf,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,QAAQ,EAAE,uBAAuB,CAAC,IAAI,CAAC,QAAQ,CAAC;QAChD,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,MAAM,EAAE,WAAW;QACnB,cAAc,EAAE,IAAI,CAAC,cAAc,IAAI,GAAG;QAC1C,gBAAgB,EAAE,IAAI,CAAC,gBAAgB,IAAI,IAAI;QAC/C,GAAG,CAAC,IAAI,CAAC,QAAQ,KAAK,IAAI;YACxB,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAa,EAAE;YAC7B,CAAC,CAAC,IAAI,CAAC,QAAQ,KAAK,IAAI;gBACtB,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAa,EAAE;gBAC7B,CAAC,CAAC,EAAE,CAAC;QACT,SAAS,EAAE,GAAG;QACd,SAAS,EAAE,GAAG;KACf,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,OAAe;IAClC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;AAChE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,MAAc,EACd,UAAyC,EAAE;IAE3C,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,KAAK,CAAC;IAC7C,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,WAAW,CAAC;IACzC,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,CAAC,CAAC;IAC7C,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,GAAG,CAAC;IAEjD,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,WAAW,EAAE,OAAO,IAAI,CAAC,EAAE,CAAC;QAC3D,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,iBAAiB,kBAAkB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAChF,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;YAChB,OAAO,MAAM,QAAQ,CAAC,IAAI,EAAsB,CAAC;QACnD,CAAC;QAED,IAAI,OAAO,GAAG,WAAW,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACrD,MAAM,IAAI,CAAC,YAAY,CAAC,CAAC;YACzB,SAAS;QACX,CAAC;QAED,MAAM;IACR,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,SAAS,MAAM,yBAAyB,WAAW,YAAY,CAAC,CAAC;AACnF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/api/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAI5B,OAAO,EAqCL,KAAK,aAAa,EAGnB,MAAM,oBAAoB,CAAC;AA2sB5B,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,8EAmrE5E;AAID,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,IAAI,SAAO,EACX,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GACxC,OAAO,CAAC,IAAI,CAAC,CA8Cf"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/api/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAI5B,OAAO,EAsCL,KAAK,aAAa,EAGnB,MAAM,oBAAoB,CAAC;AAk3B5B,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,8EAouE5E;AAID,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,IAAI,SAAO,EACX,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GACxC,OAAO,CAAC,IAAI,CAAC,CA8Cf"}
@@ -2,8 +2,8 @@ import { Hono } from "hono";
2
2
  import { cors } from "hono/cors";
3
3
  import { streamSSE } from "hono/streaming";
4
4
  import { serve } from "@hono/node-server";
5
- import { StateManager, PipelineRunner, createLLMClient, createLogger, createInteractionToolsFromDeps, computeAnalytics, loadProjectConfig, loadProjectSession, processProjectInteractionRequest, resolveSessionActiveBook, listBookSessions, loadBookSession, appendManualSessionMessages, createAndPersistBookSession, renameBookSession, deleteBookSession, migrateBookSession, SessionAlreadyMigratedError, runAgentSession, buildAgentSystemPrompt, resolveServicePreset, resolveServiceProviderFamily, resolveServiceModelsBaseUrl, resolveServiceModel, loadSecrets, saveSecrets, listModelsForService, isApiKeyOptionalForEndpoint, getAllEndpoints, probeModelsFromUpstream, fetchWithProxy, chatCompletion, buildExportArtifact, GLOBAL_ENV_PATH, } from "@actalk/inkos-core";
6
- import { access, readFile, readdir, writeFile } from "node:fs/promises";
5
+ import { StateManager, PipelineRunner, createLLMClient, createLogger, createInteractionToolsFromDeps, computeAnalytics, loadProjectConfig, loadProjectSession, processProjectInteractionRequest, resolveSessionActiveBook, listBookSessions, loadBookSession, appendManualSessionMessages, createAndPersistBookSession, renameBookSession, deleteBookSession, migrateBookSession, SessionAlreadyMigratedError, runAgentSession, buildAgentSystemPrompt, resolveServicePreset, resolveServiceProviderFamily, resolveServiceModelsBaseUrl, resolveServiceModel, loadSecrets, saveSecrets, listModelsForService, isApiKeyOptionalForEndpoint, getAllEndpoints, probeModelsFromUpstream, fetchWithProxy, chatCompletion, buildExportArtifact, GLOBAL_ENV_PATH, Scheduler, } from "@actalk/inkos-core";
6
+ import { access, mkdir, readFile, readdir, writeFile } from "node:fs/promises";
7
7
  import { isAbsolute, join, relative, resolve } from "node:path";
8
8
  import { isSafeBookId } from "./safety.js";
9
9
  import { ApiError } from "./errors.js";
@@ -49,6 +49,18 @@ function summarizeResult(result) {
49
49
  }
50
50
  return String(result).slice(0, 200);
51
51
  }
52
+ function compareServiceListItems(left, right) {
53
+ if (left.service === "kkaiapi" && right.service !== "kkaiapi")
54
+ return -1;
55
+ if (right.service === "kkaiapi" && left.service !== "kkaiapi")
56
+ return 1;
57
+ return 0;
58
+ }
59
+ function isHeaderSafeApiKey(value) {
60
+ if (!value)
61
+ return true;
62
+ return /^[\x21-\x7E]+$/.test(value);
63
+ }
52
64
  const NON_TEXT_MODEL_ID_PARTS = [
53
65
  "image",
54
66
  "embedding",
@@ -59,6 +71,10 @@ const NON_TEXT_MODEL_ID_PARTS = [
59
71
  "audio",
60
72
  "moderation",
61
73
  ];
74
+ const SERVICE_MODELS_PROBE_TIMEOUT_MS = 4_000;
75
+ const SERVICE_CHAT_PROBE_TIMEOUT_MS = 8_000;
76
+ const MAX_DISCOVERED_MODELS_TO_PING = 2;
77
+ const MAX_GENERIC_FALLBACK_MODELS_TO_PING = 2;
62
78
  function isTextChatModelId(modelId) {
63
79
  const normalized = modelId.trim().toLowerCase();
64
80
  if (!normalized)
@@ -344,15 +360,12 @@ function buildProbePlans(preferredApiFormat, preferredStream) {
344
360
  };
345
361
  if (preferredApiFormat) {
346
362
  push(preferredApiFormat, preferredStream ?? false);
347
- push(preferredApiFormat, !(preferredStream ?? false));
363
+ if (preferredStream)
364
+ push(preferredApiFormat, false);
365
+ return candidates;
348
366
  }
349
- const alternate = preferredApiFormat === "responses" ? "chat" : "responses";
350
- push(alternate, false);
351
- push(alternate, true);
352
367
  push("chat", false);
353
- push("chat", true);
354
368
  push("responses", false);
355
- push("responses", true);
356
369
  return candidates;
357
370
  }
358
371
  function buildModelCandidates(args) {
@@ -370,17 +383,103 @@ function buildModelCandidates(args) {
370
383
  push(args.preferredModel);
371
384
  push(args.configModel);
372
385
  push(args.envModel ?? undefined);
373
- for (const model of args.discoveredModels)
386
+ for (const model of args.discoveredModels.slice(0, MAX_DISCOVERED_MODELS_TO_PING))
374
387
  push(model.id);
375
388
  if (args.includeGenericFallbacks === false)
376
389
  return candidates;
377
- push("gpt-5.4");
378
- push("gpt-4o");
379
- push("claude-sonnet-4-6");
380
- push("MiniMax-M2.7");
381
- push("kimi-k2.5");
390
+ for (const fallback of [
391
+ "gpt-5.4",
392
+ "gpt-4o",
393
+ "claude-sonnet-4-6",
394
+ "MiniMax-M2.7",
395
+ "kimi-k2.5",
396
+ ].slice(0, MAX_GENERIC_FALLBACK_MODELS_TO_PING)) {
397
+ push(fallback);
398
+ }
382
399
  return candidates;
383
400
  }
401
+ function yamlScalar(value) {
402
+ return JSON.stringify(String(value ?? ""));
403
+ }
404
+ function radarTimestampForFilename(value) {
405
+ const date = value ? new Date(value) : new Date();
406
+ const safeDate = Number.isNaN(date.getTime()) ? new Date() : date;
407
+ return safeDate.toISOString().replace(/[:.]/g, "-");
408
+ }
409
+ async function saveRadarScan(root, result) {
410
+ const radarDir = join(root, "radar");
411
+ await mkdir(radarDir, { recursive: true });
412
+ const timestamp = typeof result === "object" && result !== null && "timestamp" in result
413
+ ? String(result.timestamp ?? "")
414
+ : "";
415
+ const fileName = `scan-${radarTimestampForFilename(timestamp)}.json`;
416
+ const filePath = join(radarDir, fileName);
417
+ await writeFile(filePath, JSON.stringify(result, null, 2), "utf-8");
418
+ return filePath;
419
+ }
420
+ async function loadRadarHistory(root) {
421
+ const radarDir = join(root, "radar");
422
+ let files = [];
423
+ try {
424
+ files = await readdir(radarDir);
425
+ }
426
+ catch {
427
+ return [];
428
+ }
429
+ const scans = await Promise.all(files
430
+ .filter((file) => /^scan-.+\.json$/.test(file))
431
+ .map(async (file) => {
432
+ try {
433
+ const raw = await readFile(join(radarDir, file), "utf-8");
434
+ const result = JSON.parse(raw);
435
+ const timestamp = typeof result.timestamp === "string"
436
+ ? result.timestamp
437
+ : file.replace(/^scan-/, "").replace(/\.json$/, "");
438
+ const marketSummary = typeof result.marketSummary === "string" ? result.marketSummary : "";
439
+ return {
440
+ file,
441
+ timestamp,
442
+ marketSummary,
443
+ summaryPreview: marketSummary.slice(0, 100),
444
+ result,
445
+ };
446
+ }
447
+ catch {
448
+ return null;
449
+ }
450
+ }));
451
+ return scans
452
+ .filter((item) => item !== null)
453
+ .sort((a, b) => b.file.localeCompare(a.file));
454
+ }
455
+ function fallbackTextModelsForEndpoint(endpoint, preset) {
456
+ const endpointModels = endpoint?.models
457
+ .filter((model) => model.enabled !== false)
458
+ .filter((model) => isTextChatModelId(model.id))
459
+ .map((model) => ({ id: model.id, name: model.id }))
460
+ ?? [];
461
+ if (endpointModels.length > 0)
462
+ return endpointModels;
463
+ return preset?.knownModels?.map((id) => ({ id, name: id })) ?? [];
464
+ }
465
+ function shouldTrustStaticModelsWhenLiveListUnavailable(endpoint) {
466
+ return endpoint?.group === "aggregator";
467
+ }
468
+ async function withTimeout(promise, timeoutMs, label) {
469
+ let timeout;
470
+ try {
471
+ return await Promise.race([
472
+ promise,
473
+ new Promise((_, reject) => {
474
+ timeout = setTimeout(() => reject(new Error(`${label} 超时(${timeoutMs}ms)`)), timeoutMs);
475
+ }),
476
+ ]);
477
+ }
478
+ finally {
479
+ if (timeout)
480
+ clearTimeout(timeout);
481
+ }
482
+ }
384
483
  function formatServiceProbeError(args) {
385
484
  const rawDetail = args.error
386
485
  .replace(/\n\s*\(baseUrl:[\s\S]*?\)$/m, "")
@@ -434,8 +533,8 @@ async function fetchModelsFromServiceBaseUrl(serviceId, baseUrl, apiKey, proxyUr
434
533
  const modelsUrl = modelsBaseUrl.replace(/\/$/, "") + "/models";
435
534
  try {
436
535
  const res = await fetchWithProxy(modelsUrl, {
437
- headers: { Authorization: `Bearer ${apiKey}` },
438
- signal: AbortSignal.timeout(10_000),
536
+ headers: buildBearerAuthHeaders(apiKey),
537
+ signal: AbortSignal.timeout(SERVICE_MODELS_PROBE_TIMEOUT_MS),
439
538
  }, proxyUrl);
440
539
  if (!res.ok) {
441
540
  const body = await res.text().catch(() => "");
@@ -457,6 +556,15 @@ async function fetchModelsFromServiceBaseUrl(serviceId, baseUrl, apiKey, proxyUr
457
556
  };
458
557
  }
459
558
  }
559
+ function buildBearerAuthHeaders(apiKey) {
560
+ const trimmed = apiKey?.trim() ?? "";
561
+ if (!trimmed)
562
+ return {};
563
+ if (!/^[\x20-\x7e]+$/.test(trimmed)) {
564
+ throw new Error("API Key 只能包含英文、数字和常见 ASCII 符号,请检查是否误粘贴了中文说明。");
565
+ }
566
+ return { Authorization: `Bearer ${trimmed}` };
567
+ }
460
568
  async function probeServiceCapabilities(args) {
461
569
  const rawConfig = await loadRawConfig(args.root).catch(() => ({}));
462
570
  const llm = rawConfig.llm ?? {};
@@ -476,14 +584,54 @@ async function probeServiceCapabilities(args) {
476
584
  };
477
585
  }
478
586
  const discoveredModels = modelsResponse.models;
479
- // For bank services, probe with the service's own check model first — not the global default.
480
587
  const endpoint = getAllEndpoints().find((ep) => ep.id === baseService);
481
588
  const preset = resolveServicePreset(baseService);
589
+ const discoveredFirstModel = discoveredModels.find((model) => isTextChatModelId(model.id))?.id
590
+ ?? discoveredModels[0]?.id;
591
+ if (discoveredModels.length > 0) {
592
+ if (!discoveredFirstModel || !isTextChatModelId(discoveredFirstModel)) {
593
+ return {
594
+ ok: false,
595
+ models: discoveredModels,
596
+ error: "模型列表可访问,但没有发现可用于文本对话的模型。",
597
+ };
598
+ }
599
+ return {
600
+ ok: true,
601
+ models: discoveredModels,
602
+ selectedModel: discoveredFirstModel,
603
+ apiFormat: args.preferredApiFormat ?? "chat",
604
+ stream: args.preferredStream ?? false,
605
+ baseUrl: args.baseUrl,
606
+ modelsSource: "api",
607
+ };
608
+ }
609
+ if (shouldTrustStaticModelsWhenLiveListUnavailable(endpoint)) {
610
+ const models = fallbackTextModelsForEndpoint(endpoint, preset);
611
+ const selectedModel = endpoint?.checkModel && models.some((model) => model.id === endpoint.checkModel)
612
+ ? endpoint.checkModel
613
+ : models[0]?.id;
614
+ if (selectedModel) {
615
+ return {
616
+ ok: true,
617
+ models,
618
+ selectedModel,
619
+ apiFormat: args.preferredApiFormat ?? "chat",
620
+ stream: args.preferredStream ?? false,
621
+ baseUrl: args.baseUrl,
622
+ modelsSource: "fallback",
623
+ };
624
+ }
625
+ }
626
+ // Prefer live /models results; if unavailable, probe with the service's own check model before global defaults.
482
627
  const serviceFirstModel = endpoint?.checkModel
483
628
  ?? preset?.knownModels?.[0]
484
629
  ?? endpoint?.models.find((model) => model.enabled !== false)?.id;
485
630
  const useDynamicLocalModels = baseService === "ollama";
486
- const useEndpointCheckModel = !useDynamicLocalModels && !isCustomServiceId(args.service) && Boolean(endpoint?.checkModel);
631
+ const useEndpointCheckModel = !useDynamicLocalModels
632
+ && !isCustomServiceId(args.service)
633
+ && discoveredModels.length === 0
634
+ && Boolean(endpoint?.checkModel);
487
635
  const configService = typeof llm.service === "string" ? llm.service : undefined;
488
636
  const configModel = !useEndpointCheckModel && configService === args.service
489
637
  ? typeof llm.defaultModel === "string"
@@ -492,9 +640,9 @@ async function probeServiceCapabilities(args) {
492
640
  ? llm.model
493
641
  : undefined
494
642
  : undefined;
495
- const useCustomFallbacks = isCustomServiceId(args.service);
643
+ const useCustomFallbacks = false;
496
644
  const modelCandidates = buildModelCandidates({
497
- preferredModel: args.preferredModel ?? (useDynamicLocalModels ? discoveredModels[0]?.id ?? serviceFirstModel : serviceFirstModel),
645
+ preferredModel: args.preferredModel ?? serviceFirstModel,
498
646
  configModel,
499
647
  envModel: useCustomFallbacks ? envModel : undefined,
500
648
  discoveredModels: useEndpointCheckModel ? [] : discoveredModels,
@@ -518,25 +666,20 @@ async function probeServiceCapabilities(args) {
518
666
  apiKey: args.apiKey.trim(),
519
667
  model,
520
668
  temperature: 0.7,
521
- maxTokens: 2048,
669
+ maxTokens: 16,
522
670
  thinkingBudget: 0,
523
671
  proxyUrl: args.proxyUrl,
524
672
  apiFormat: plan.apiFormat,
525
673
  stream: plan.stream,
526
674
  });
527
675
  try {
528
- await chatCompletion(client, model, [{ role: "user", content: "ping" }], { maxTokens: 2048 });
676
+ await withTimeout(chatCompletion(client, model, [{ role: "user", content: "Reply with OK only." }], { maxTokens: 16 }), SERVICE_CHAT_PROBE_TIMEOUT_MS, "service connection test");
529
677
  const models = discoveredModels.length > 0
530
678
  ? discoveredModels
531
- : endpoint?.models
532
- .filter((m) => m.enabled !== false)
533
- .filter((m) => isTextChatModelId(m.id))
534
- .map((m) => ({ id: m.id, name: m.id }))
535
- ?? preset?.knownModels?.map((id) => ({ id, name: id }))
536
- ?? [{ id: model, name: model }];
679
+ : fallbackTextModelsForEndpoint(endpoint, preset);
537
680
  return {
538
681
  ok: true,
539
- models,
682
+ models: models.length > 0 ? models : [{ id: model, name: model }],
540
683
  selectedModel: model,
541
684
  apiFormat: plan.apiFormat,
542
685
  stream: plan.stream,
@@ -574,6 +717,11 @@ export function createStudioServer(initialConfig, root) {
574
717
  if (error instanceof ApiError) {
575
718
  return c.json({ error: { code: error.code, message: error.message } }, error.status);
576
719
  }
720
+ const message = error instanceof Error ? error.message : String(error);
721
+ if (message.includes("LLM API key not set") || message.includes("INKOS_LLM_API_KEY not set")) {
722
+ return c.json({ error: { code: "LLM_CONFIG_ERROR", message } }, 400);
723
+ }
724
+ console.error("[studio] Unexpected server error", error);
577
725
  return c.json({ error: { code: "INTERNAL_ERROR", message: "Unexpected server error." } }, 500);
578
726
  });
579
727
  // BookId validation middleware — blocks path traversal on all book routes
@@ -712,6 +860,7 @@ export function createStudioServer(initialConfig, root) {
712
860
  platform: body.platform,
713
861
  chapterWordCount: body.chapterWordCount,
714
862
  targetChapters: body.targetChapters,
863
+ blurb: body.blurb,
715
864
  },
716
865
  tools,
717
866
  }).then(async (result) => {
@@ -971,7 +1120,7 @@ export function createStudioServer(initialConfig, root) {
971
1120
  label: ep.label,
972
1121
  group: ep.group,
973
1122
  connected: Boolean(secrets.services[ep.id]?.apiKey),
974
- }));
1123
+ })).sort(compareServiceListItems);
975
1124
  // Add custom services from inkos.json
976
1125
  try {
977
1126
  const config = await loadRawConfig(root);
@@ -1031,6 +1180,27 @@ export function createStudioServer(initialConfig, root) {
1031
1180
  await saveRawConfig(root, config);
1032
1181
  return c.json({ ok: true });
1033
1182
  });
1183
+ app.delete("/api/v1/services/:service", async (c) => {
1184
+ const service = c.req.param("service");
1185
+ const config = await loadRawConfig(root);
1186
+ const llm = config.llm ?? {};
1187
+ const existingServices = normalizeServiceConfig(llm.services);
1188
+ const nextServices = existingServices.filter((entry) => serviceConfigKey(entry) !== service);
1189
+ if (!config.llm)
1190
+ config.llm = {};
1191
+ const nextLlm = config.llm;
1192
+ nextLlm.services = nextServices;
1193
+ if (nextLlm.service === service) {
1194
+ delete nextLlm.service;
1195
+ delete nextLlm.defaultModel;
1196
+ }
1197
+ await saveRawConfig(root, config);
1198
+ const secrets = await loadSecrets(root);
1199
+ delete secrets.services[service];
1200
+ await saveSecrets(root, secrets);
1201
+ modelListCache.clear();
1202
+ return c.json({ ok: true, service });
1203
+ });
1034
1204
  app.post("/api/v1/services/:service/test", async (c) => {
1035
1205
  const service = c.req.param("service");
1036
1206
  const { apiKey, baseUrl, apiFormat, stream } = await c.req.json();
@@ -1044,7 +1214,10 @@ export function createStudioServer(initialConfig, root) {
1044
1214
  baseUrl: resolvedBaseUrl,
1045
1215
  });
1046
1216
  if (!apiKey?.trim() && !apiKeyOptional) {
1047
- return c.json({ ok: false, error: "API Key 不能为空" }, 400);
1217
+ return c.json({
1218
+ ok: false,
1219
+ error: "API Key 不能为空",
1220
+ }, 400);
1048
1221
  }
1049
1222
  const rawConfig = await loadRawConfig(root).catch(() => ({}));
1050
1223
  const llm = rawConfig.llm ?? {};
@@ -1091,8 +1264,15 @@ export function createStudioServer(initialConfig, root) {
1091
1264
  const service = c.req.param("service");
1092
1265
  const { apiKey } = await c.req.json();
1093
1266
  const secrets = await loadSecrets(root);
1094
- if (apiKey?.trim()) {
1095
- secrets.services[service] = { apiKey: apiKey.trim() };
1267
+ const trimmedKey = apiKey?.trim() ?? "";
1268
+ if (trimmedKey) {
1269
+ if (!isHeaderSafeApiKey(trimmedKey)) {
1270
+ return c.json({
1271
+ ok: false,
1272
+ error: "API Key 只能包含可放进 HTTP Authorization header 的非空白 ASCII 字符;请不要粘贴连接失败提示或诊断文本。",
1273
+ }, 400);
1274
+ }
1275
+ secrets.services[service] = { apiKey: trimmedKey };
1096
1276
  }
1097
1277
  else {
1098
1278
  delete secrets.services[service];
@@ -1294,7 +1474,6 @@ export function createStudioServer(initialConfig, root) {
1294
1474
  return c.json({ error: "Daemon already running" }, 400);
1295
1475
  }
1296
1476
  try {
1297
- const { Scheduler } = await import("@actalk/inkos-core");
1298
1477
  const currentConfig = await loadCurrentProjectConfig();
1299
1478
  const scheduler = new Scheduler({
1300
1479
  ...(await buildPipelineConfig()),
@@ -2264,15 +2443,15 @@ export function createStudioServer(initialConfig, root) {
2264
2443
  await mkdirFs(genresDir, { recursive: true });
2265
2444
  const frontmatter = [
2266
2445
  "---",
2267
- `name: ${body.name}`,
2268
- `id: ${body.id}`,
2269
- `language: ${body.language ?? "zh"}`,
2446
+ `name: ${yamlScalar(body.name)}`,
2447
+ `id: ${yamlScalar(body.id)}`,
2448
+ `language: ${yamlScalar(body.language ?? "zh")}`,
2270
2449
  `chapterTypes: ${JSON.stringify(body.chapterTypes ?? [])}`,
2271
2450
  `fatigueWords: ${JSON.stringify(body.fatigueWords ?? [])}`,
2272
2451
  `numericalSystem: ${body.numericalSystem ?? false}`,
2273
2452
  `powerScaling: ${body.powerScaling ?? false}`,
2274
2453
  `eraResearch: ${body.eraResearch ?? false}`,
2275
- `pacingRule: "${body.pacingRule ?? ""}"`,
2454
+ `pacingRule: ${yamlScalar(body.pacingRule ?? "")}`,
2276
2455
  `satisfactionTypes: ${JSON.stringify(body.satisfactionTypes ?? [])}`,
2277
2456
  `auditDimensions: ${JSON.stringify(body.auditDimensions ?? [])}`,
2278
2457
  "---",
@@ -2295,15 +2474,15 @@ export function createStudioServer(initialConfig, root) {
2295
2474
  const p = body.profile;
2296
2475
  const frontmatter = [
2297
2476
  "---",
2298
- `name: ${p.name ?? genreId}`,
2299
- `id: ${p.id ?? genreId}`,
2300
- `language: ${p.language ?? "zh"}`,
2477
+ `name: ${yamlScalar(p.name ?? genreId)}`,
2478
+ `id: ${yamlScalar(p.id ?? genreId)}`,
2479
+ `language: ${yamlScalar(p.language ?? "zh")}`,
2301
2480
  `chapterTypes: ${JSON.stringify(p.chapterTypes ?? [])}`,
2302
2481
  `fatigueWords: ${JSON.stringify(p.fatigueWords ?? [])}`,
2303
2482
  `numericalSystem: ${p.numericalSystem ?? false}`,
2304
2483
  `powerScaling: ${p.powerScaling ?? false}`,
2305
2484
  `eraResearch: ${p.eraResearch ?? false}`,
2306
- `pacingRule: "${p.pacingRule ?? ""}"`,
2485
+ `pacingRule: ${yamlScalar(p.pacingRule ?? "")}`,
2307
2486
  `satisfactionTypes: ${JSON.stringify(p.satisfactionTypes ?? [])}`,
2308
2487
  `auditDimensions: ${JSON.stringify(p.auditDimensions ?? [])}`,
2309
2488
  "---",
@@ -2469,6 +2648,7 @@ export function createStudioServer(initialConfig, root) {
2469
2648
  try {
2470
2649
  const pipeline = new PipelineRunner(await buildPipelineConfig());
2471
2650
  const result = await pipeline.runRadar();
2651
+ await saveRadarScan(root, result);
2472
2652
  broadcast("radar:complete", { result });
2473
2653
  return c.json(result);
2474
2654
  }
@@ -2477,6 +2657,15 @@ export function createStudioServer(initialConfig, root) {
2477
2657
  return c.json({ error: String(e) }, 500);
2478
2658
  }
2479
2659
  });
2660
+ app.get("/api/v1/radar/history", async (c) => {
2661
+ try {
2662
+ const items = await loadRadarHistory(root);
2663
+ return c.json({ items });
2664
+ }
2665
+ catch (e) {
2666
+ return c.json({ error: String(e) }, 500);
2667
+ }
2668
+ });
2480
2669
  // --- Doctor (environment health check) ---
2481
2670
  app.get("/api/v1/doctor", async (c) => {
2482
2671
  const { existsSync } = await import("node:fs");