termdock 1.4.71 → 1.4.73

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 (77) hide show
  1. package/README.md +59 -0
  2. package/dist/client/assets/{ModelPreview-CG6vLwyw.js → ModelPreview-BYS46cJp.js} +1 -1
  3. package/dist/client/assets/{abnfDiagram-VRR7QNED-CrXEZLWX.js → abnfDiagram-VRR7QNED-Df5iizBM.js} +1 -1
  4. package/dist/client/assets/{arc-DbKGR7Ex.js → arc-BRLIrHaj.js} +1 -1
  5. package/dist/client/assets/{architectureDiagram-ZJ3FMSHR-PF5BoUbI.js → architectureDiagram-ZJ3FMSHR-BmIKhrrq.js} +1 -1
  6. package/dist/client/assets/{blockDiagram-677ZJIJ3-D1gRzVWB.js → blockDiagram-677ZJIJ3-PBtq2qLG.js} +1 -1
  7. package/dist/client/assets/{c4Diagram-LMCZKHZV-CwvOLync.js → c4Diagram-LMCZKHZV-6pevynP4.js} +1 -1
  8. package/dist/client/assets/channel-CfecnPVa.js +1 -0
  9. package/dist/client/assets/{chunk-2Q5K7J3B-BeRdP-xP.js → chunk-2Q5K7J3B-78EZViva.js} +1 -1
  10. package/dist/client/assets/{chunk-32BRIVSS-CrqR5rvl.js → chunk-32BRIVSS-CekczK5l.js} +1 -1
  11. package/dist/client/assets/{chunk-5VM5RSS4-CjbUr-ko.js → chunk-5VM5RSS4-fMhlGbVA.js} +1 -1
  12. package/dist/client/assets/{chunk-EX3LRPZG-BVEk3Wma.js → chunk-EX3LRPZG-DOBewV4Z.js} +1 -1
  13. package/dist/client/assets/{chunk-JWPE2WC7-WitJe8kR.js → chunk-JWPE2WC7-rtk-mGC7.js} +1 -1
  14. package/dist/client/assets/{chunk-MOJQB5TN-BCewlLYk.js → chunk-MOJQB5TN-CWUZocq_.js} +1 -1
  15. package/dist/client/assets/{chunk-RYQCIY6F-CoP0AA4G.js → chunk-RYQCIY6F-DcHAjeyV.js} +1 -1
  16. package/dist/client/assets/{chunk-V7JOEXUC-CSZrnmp_.js → chunk-V7JOEXUC-C2paDSKz.js} +1 -1
  17. package/dist/client/assets/{chunk-VR4S4FIN-BB-7zz-9.js → chunk-VR4S4FIN-Cq0ZrFUq.js} +1 -1
  18. package/dist/client/assets/{chunk-XXDRQBXY-CcZZlCik.js → chunk-XXDRQBXY-BboySYmP.js} +1 -1
  19. package/dist/client/assets/classDiagram-OUVF2IWQ-CFpMarVm.js +1 -0
  20. package/dist/client/assets/classDiagram-v2-EOCWNBFH-CFpMarVm.js +1 -0
  21. package/dist/client/assets/{cose-bilkent-JH36ORCC-DcmzUDwh.js → cose-bilkent-JH36ORCC-CMnHIr6y.js} +1 -1
  22. package/dist/client/assets/{cynefin-VYW2F7L2-C1XlDVoJ.js → cynefin-VYW2F7L2-BpOtLuYh.js} +1 -1
  23. package/dist/client/assets/{cynefinDiagram-TSTJHNR4-zY6Aet2S.js → cynefinDiagram-TSTJHNR4-CmrMqA7z.js} +1 -1
  24. package/dist/client/assets/{dagre-VKFMJZFB-DqGTi6zR.js → dagre-VKFMJZFB-iXOV-rm7.js} +1 -1
  25. package/dist/client/assets/{diagram-FQU43EPY-DStcsU7S.js → diagram-FQU43EPY-D5KagnmX.js} +1 -1
  26. package/dist/client/assets/{diagram-G47NLZAW-Cs6IMNip.js → diagram-G47NLZAW-CglgkfyY.js} +1 -1
  27. package/dist/client/assets/{diagram-NH7WQ7WH-yA2bpWN7.js → diagram-NH7WQ7WH-Dos-y1pl.js} +1 -1
  28. package/dist/client/assets/{diagram-OA4YK3LP-Dzf9M0PI.js → diagram-OA4YK3LP-Cw6yJqFX.js} +1 -1
  29. package/dist/client/assets/{diagram-WEI45ONY-D75lpQKA.js → diagram-WEI45ONY-C_MHHYRA.js} +1 -1
  30. package/dist/client/assets/{ebnfDiagram-CCIWWBDH-CkQvIpWy.js → ebnfDiagram-CCIWWBDH-BB7x8X2l.js} +1 -1
  31. package/dist/client/assets/{erDiagram-Q63AITRT-Ca00Vjxz.js → erDiagram-Q63AITRT-BznrrU3P.js} +1 -1
  32. package/dist/client/assets/{flowDiagram-23GEKE2U-CGrnSTEp.js → flowDiagram-23GEKE2U-CMzPvwGa.js} +1 -1
  33. package/dist/client/assets/{ganttDiagram-NO4QXBWP-0SHfOIP4.js → ganttDiagram-NO4QXBWP-Dc9JCHv3.js} +1 -1
  34. package/dist/client/assets/{gitGraphDiagram-IHSO6WYX-BeigYWvs.js → gitGraphDiagram-IHSO6WYX-WlPpmZQm.js} +1 -1
  35. package/dist/client/assets/index-wX3Y6Ks7.js +451 -0
  36. package/dist/client/assets/{infoDiagram-FWYZ7A6U-CiY1-xjN.js → infoDiagram-FWYZ7A6U-DC6PBJiN.js} +1 -1
  37. package/dist/client/assets/{ishikawaDiagram-FXEZZL3T-CmtDjUm_.js → ishikawaDiagram-FXEZZL3T-DPfxPu0W.js} +1 -1
  38. package/dist/client/assets/{journeyDiagram-5HDEW3XC-B7qOr9LY.js → journeyDiagram-5HDEW3XC-aA2ovr_f.js} +1 -1
  39. package/dist/client/assets/{kanban-definition-HUTT4EX6-DO2P2Um3.js → kanban-definition-HUTT4EX6-_T30f7Or.js} +1 -1
  40. package/dist/client/assets/{linear-B832hJt0.js → linear-DRZcO7_m.js} +1 -1
  41. package/dist/client/assets/{mermaid.core-CsAzdiCU.js → mermaid.core-BGXjYfdb.js} +4 -4
  42. package/dist/client/assets/{mindmap-definition-LN4V7U3C-HI__vXhQ.js → mindmap-definition-LN4V7U3C-Bv0lhpqs.js} +1 -1
  43. package/dist/client/assets/{pegDiagram-2B236MQR--rZIwjXq.js → pegDiagram-2B236MQR-D-EIIOZf.js} +1 -1
  44. package/dist/client/assets/{pieDiagram-ENE6RG2P-D9jPf5x9.js → pieDiagram-ENE6RG2P-CATAcPgl.js} +1 -1
  45. package/dist/client/assets/{quadrantDiagram-ABIIQ3AL-DTCP5ji2.js → quadrantDiagram-ABIIQ3AL-2tglHVSk.js} +1 -1
  46. package/dist/client/assets/{railroadDiagram-RFXS5EU6-D2Gspvrl.js → railroadDiagram-RFXS5EU6-BMKC-e8R.js} +1 -1
  47. package/dist/client/assets/{requirementDiagram-TGXJPOKE-DEJC02IW.js → requirementDiagram-TGXJPOKE-B5J5_wFW.js} +1 -1
  48. package/dist/client/assets/{sankeyDiagram-HTMAVEWB-opOhKPap.js → sankeyDiagram-HTMAVEWB-Djlm71ae.js} +1 -1
  49. package/dist/client/assets/{sequenceDiagram-DBY2YBRQ-_sz2CRAG.js → sequenceDiagram-DBY2YBRQ-BtR8OPSJ.js} +1 -1
  50. package/dist/client/assets/{sizeCapture-X5ZJPWSS-D0Q2QkYF.js → sizeCapture-X5ZJPWSS-CDelD9h_.js} +1 -1
  51. package/dist/client/assets/{stateDiagram-2N3HPSRC-VgeSQTtW.js → stateDiagram-2N3HPSRC-C50kGru1.js} +1 -1
  52. package/dist/client/assets/stateDiagram-v2-6OUMAXLB-gIr1L4WZ.js +1 -0
  53. package/dist/client/assets/{swimlanes-5IMT3BWC-ClvBvSl0.js → swimlanes-5IMT3BWC-COHxl86-.js} +2 -2
  54. package/dist/client/assets/swimlanesDiagram-G3AALYLV-COE4p0kX.js +8 -0
  55. package/dist/client/assets/{timeline-definition-FHXFAJF6-BXNsrECV.js → timeline-definition-FHXFAJF6-br-VI8Fd.js} +1 -1
  56. package/dist/client/assets/{vennDiagram-L72KCM5P-BgwiqZd-.js → vennDiagram-L72KCM5P-zwMtEv81.js} +1 -1
  57. package/dist/client/assets/{wardleyDiagram-EHGQE667-DiqN5ERx.js → wardleyDiagram-EHGQE667-_C8v4nnr.js} +1 -1
  58. package/dist/client/assets/{xychartDiagram-FW5EYKEG-DQjFVqC6.js → xychartDiagram-FW5EYKEG-CSU2R6pt.js} +1 -1
  59. package/dist/client/index.html +1 -1
  60. package/dist/client/sw.js +1 -1
  61. package/dist/server/agent/autoTitle.js +3 -0
  62. package/dist/server/agent/pluginPackages.js +289 -0
  63. package/dist/server/agent/plugins.js +139 -20
  64. package/dist/server/agent/titleNamerCatalog.js +235 -40
  65. package/dist/server/cli.js +277 -17
  66. package/dist/server/entry.js +11 -2
  67. package/dist/server/routes/terminal.js +241 -30
  68. package/dist/server/utils/csrfProtection.js +4 -1
  69. package/dist/server/utils/settings.js +14 -0
  70. package/package.json +1 -1
  71. package/runtime-manifest.json +2 -2
  72. package/dist/client/assets/channel-JTEuKVy6.js +0 -1
  73. package/dist/client/assets/classDiagram-OUVF2IWQ-BiFJB_5s.js +0 -1
  74. package/dist/client/assets/classDiagram-v2-EOCWNBFH-BiFJB_5s.js +0 -1
  75. package/dist/client/assets/index-D5PKdbhW.js +0 -451
  76. package/dist/client/assets/stateDiagram-v2-6OUMAXLB-BVh2FRO-.js +0 -1
  77. package/dist/client/assets/swimlanesDiagram-G3AALYLV-C17V-kgm.js +0 -8
@@ -159,7 +159,17 @@ Options:
159
159
  --plugin-init Print the Agent integration protocol reference
160
160
  --plugin-create <file>
161
161
  Create a plugin from a manifest JSON file
162
+ --plugin-install <source>
163
+ Install a complete plugin package from HTTPS Git or a local path
162
164
  --plugin-list List installed agent plugins
165
+ --plugin-check <slug>
166
+ Check the installed plugin source for updates
167
+ --plugin-update <slug>
168
+ Update a plugin package from its recorded source
169
+ --plugin-doctor <slug>
170
+ Probe one plugin's title/model integration
171
+ --plugin-hooks <slug> <install|uninstall>
172
+ Manage the Agent-native hook entries for a plugin
163
173
  --plugin-remove <slug>
164
174
  Remove an installed agent plugin
165
175
  -h, --help Show this help message
@@ -173,7 +183,18 @@ Short commands:
173
183
  pi Same as agent-plugin
174
184
  plugin-create <file>
175
185
  Same as --plugin-create <file>
176
- plugin-list Same as --plugin-list
186
+ plugin-install <source>
187
+ Install a shareable plugin repository or local package
188
+ plugin-list [--json]
189
+ List plugin packages and their recorded sources
190
+ plugin-check <slug>
191
+ Check one plugin for updates
192
+ plugin-update <slug>
193
+ Update one plugin from its recorded source
194
+ plugin-doctor <slug> [--json]
195
+ Validate title capability and dynamically probe models
196
+ plugin-hooks <slug> <install|uninstall>
197
+ Install or uninstall only that plugin's Agent hooks
177
198
  plugin-remove <slug>
178
199
  Same as --plugin-remove <slug>
179
200
  s Same as --status
@@ -513,7 +534,13 @@ function parseArgs(argv) {
513
534
  let pluginInit = false;
514
535
  let pluginInitJson = false;
515
536
  let pluginCreate;
537
+ let pluginInstall;
516
538
  let pluginList = false;
539
+ let pluginListJson = false;
540
+ let pluginCheck;
541
+ let pluginUpdate;
542
+ let pluginDoctor;
543
+ let pluginHooks;
517
544
  let pluginRemove;
518
545
  let agentEvent;
519
546
  // Short command aliases for the common path. Keep these positional-only so
@@ -638,13 +665,41 @@ function parseArgs(argv) {
638
665
  argv = argv.slice(2);
639
666
  }
640
667
  else {
641
- pluginCreate = undefined;
668
+ pluginCreate = '';
642
669
  argv = argv.slice(1);
643
670
  }
644
671
  }
672
+ else if (command === 'pinstall' || command === 'plugin-install') {
673
+ pluginInstall = next && !next.startsWith('-') ? next : '';
674
+ argv = argv.slice(pluginInstall ? 2 : 1);
675
+ }
645
676
  else if (command === 'pinfo' || command === 'plugin-list') {
646
677
  pluginList = true;
647
- argv = argv.slice(1);
678
+ pluginListJson = next === '--json';
679
+ argv = argv.slice(pluginListJson ? 2 : 1);
680
+ }
681
+ else if (command === 'pcheck' || command === 'plugin-check') {
682
+ pluginCheck = next && !next.startsWith('-') ? next : '';
683
+ argv = argv.slice(pluginCheck ? 2 : 1);
684
+ }
685
+ else if (command === 'pupdate' || command === 'plugin-update') {
686
+ pluginUpdate = next && !next.startsWith('-') ? next : '';
687
+ argv = argv.slice(pluginUpdate ? 2 : 1);
688
+ }
689
+ else if (command === 'pdoctor' || command === 'plugin-doctor') {
690
+ pluginDoctor = { slug: next && !next.startsWith('-') ? next : '', json: argv[2] === '--json' };
691
+ argv = argv.slice(pluginDoctor.slug ? (pluginDoctor.json ? 3 : 2) : 1);
692
+ }
693
+ else if (command === 'phooks' || command === 'plugin-hooks') {
694
+ const action = argv[2];
695
+ if (next && (action === 'install' || action === 'uninstall')) {
696
+ pluginHooks = { slug: next, action };
697
+ argv = argv.slice(3);
698
+ }
699
+ else {
700
+ console.error(`${ICON.err} ${c.red('Usage: td plugin-hooks <slug> <install|uninstall>')}`);
701
+ process.exit(1);
702
+ }
648
703
  }
649
704
  else if (command === 'premove' || command === 'plugin-remove') {
650
705
  if (next && !next.startsWith('-')) {
@@ -652,7 +707,7 @@ function parseArgs(argv) {
652
707
  argv = argv.slice(2);
653
708
  }
654
709
  else {
655
- pluginRemove = undefined;
710
+ pluginRemove = '';
656
711
  argv = argv.slice(1);
657
712
  }
658
713
  }
@@ -737,6 +792,8 @@ function parseArgs(argv) {
737
792
  if (arg === '--json') {
738
793
  if (pluginInit)
739
794
  pluginInitJson = true;
795
+ else if (pluginList)
796
+ pluginListJson = true;
740
797
  else
741
798
  tlsJson = true;
742
799
  continue;
@@ -850,7 +907,7 @@ function parseArgs(argv) {
850
907
  index += 1;
851
908
  }
852
909
  else {
853
- pluginCreate = undefined;
910
+ pluginCreate = '';
854
911
  }
855
912
  continue;
856
913
  }
@@ -858,6 +915,46 @@ function parseArgs(argv) {
858
915
  pluginList = true;
859
916
  continue;
860
917
  }
918
+ if (arg === '--plugin-install' || arg === '--plugin-check' || arg === '--plugin-update' || arg === '--plugin-doctor') {
919
+ const next = argv[index + 1];
920
+ if (next && !next.startsWith('-')) {
921
+ if (arg === '--plugin-install')
922
+ pluginInstall = next;
923
+ else if (arg === '--plugin-check')
924
+ pluginCheck = next;
925
+ else if (arg === '--plugin-update')
926
+ pluginUpdate = next;
927
+ else
928
+ pluginDoctor = { slug: next, json: argv[index + 2] === '--json' };
929
+ index += pluginDoctor?.json && arg === '--plugin-doctor' ? 2 : 1;
930
+ }
931
+ else if (arg === '--plugin-install') {
932
+ pluginInstall = '';
933
+ }
934
+ else if (arg === '--plugin-check') {
935
+ pluginCheck = '';
936
+ }
937
+ else if (arg === '--plugin-update') {
938
+ pluginUpdate = '';
939
+ }
940
+ else {
941
+ pluginDoctor = { slug: '', json: false };
942
+ }
943
+ continue;
944
+ }
945
+ if (arg === '--plugin-hooks') {
946
+ const slug = argv[index + 1];
947
+ const action = argv[index + 2];
948
+ if (slug && (action === 'install' || action === 'uninstall')) {
949
+ pluginHooks = { slug, action };
950
+ index += 2;
951
+ }
952
+ else {
953
+ console.error(`${ICON.err} ${c.red('Usage: td --plugin-hooks <slug> <install|uninstall>')}`);
954
+ process.exit(1);
955
+ }
956
+ continue;
957
+ }
861
958
  if (arg === '--plugin-remove') {
862
959
  const next = argv[index + 1];
863
960
  if (next && !next.startsWith('-')) {
@@ -865,7 +962,7 @@ function parseArgs(argv) {
865
962
  index += 1;
866
963
  }
867
964
  else {
868
- pluginRemove = undefined;
965
+ pluginRemove = '';
869
966
  }
870
967
  continue;
871
968
  }
@@ -914,7 +1011,13 @@ function parseArgs(argv) {
914
1011
  pluginInit,
915
1012
  pluginInitJson,
916
1013
  pluginCreate,
1014
+ pluginInstall,
917
1015
  pluginList,
1016
+ pluginListJson,
1017
+ pluginCheck,
1018
+ pluginUpdate,
1019
+ pluginDoctor,
1020
+ pluginHooks,
918
1021
  pluginRemove,
919
1022
  agentEvent,
920
1023
  };
@@ -1727,7 +1830,8 @@ const AGENT_PLUGIN_EXAMPLE = {
1727
1830
  },
1728
1831
  titleNamer: {
1729
1832
  command: 'my-agent',
1730
- args: ['title', '--json', '--prompt', '{prompt}', '--model={model}'],
1833
+ modelArgs: ['--model', '{model}'],
1834
+ args: ['title', '--json', '--prompt', '{prompt}'],
1731
1835
  models: {
1732
1836
  command: 'my-agent',
1733
1837
  args: ['models', '--json'],
@@ -1742,8 +1846,13 @@ function runPluginInitJson() {
1742
1846
  commands: {
1743
1847
  guide: 'td agent-plugin --json',
1744
1848
  register: 'td plugin-create ./manifest.json',
1849
+ installPackage: 'td plugin-install https://github.com/owner/repository',
1850
+ checkUpdate: 'td plugin-check <slug>',
1851
+ updatePackage: 'td plugin-update <slug>',
1852
+ doctor: 'td plugin-doctor <slug> --json',
1745
1853
  emit: 'td agent-event <slug> <event> [status]',
1746
- inspect: 'td plugin-list',
1854
+ inspect: 'td plugin-list --json',
1855
+ hooks: 'td plugin-hooks <slug> <install|uninstall>',
1747
1856
  remove: 'td plugin-remove <slug>',
1748
1857
  },
1749
1858
  ui: {
@@ -1763,6 +1872,21 @@ function runPluginInitJson() {
1763
1872
  indicator: 'Theme-safe visual indicator.',
1764
1873
  tone: 'Theme token; arbitrary status colors are not accepted.',
1765
1874
  },
1875
+ security: {
1876
+ source: 'Only public HTTPS repository roots on approved Git hosts or explicit local paths are accepted; URL credentials are rejected.',
1877
+ package: 'Symlinks, active/external SVG content, oversized files, submodules, and excessive unpacked size are rejected.',
1878
+ hooks: 'hooks.target must be a non-symlinked ~/ JSON path. Termdock generates the hook command; manifests cannot inject shell.',
1879
+ titleCommands: 'Installing or inspecting a plugin never executes its title/model commands. They run only after the user enables automatic titles for that plugin.',
1880
+ updates: 'Changes to hook or title command declarations revoke the corresponding prior opt-in.',
1881
+ },
1882
+ titleModelContract: {
1883
+ noCatalog: 'If titleNamer.models is absent or yields no recommendation, Termdock omits modelArgs and uses the Agent CLI default model.',
1884
+ optionalArguments: 'Put every model-only flag/value in titleNamer.modelArgs. The complete group is prepended only when a model is selected.',
1885
+ acceptedOutput: 'A JSON array or {models, recommendedModel}. Entries may use id, name, or model; optional fields: displayName/display_name, description, isDefault/is_default, isEconomical/is_economical.',
1886
+ automaticSelection: 'Top-level recommendedModel, then the first isEconomical model, then the first isDefault model; otherwise omit modelArgs and use the CLI default. Termdock never guesses price from names or descriptions.',
1887
+ adapters: 'Use command: node plus {pluginDir}/scripts/list-models.mjs when the native CLI output needs normalization. Never hardcode a model catalog.',
1888
+ verify: 'Run td plugin-doctor <slug> --json. This explicit command probes models but never spends a title-generation request.',
1889
+ },
1766
1890
  migrationV1: {
1767
1891
  errorCode: 'AGENT_PLUGIN_MANIFEST_V1_UNSUPPORTED',
1768
1892
  guideCommand: 'td agent-plugin --json',
@@ -1774,12 +1898,16 @@ function runPluginInitJson() {
1774
1898
  example: 'printf \'%s\' \'{"session_id":"abc","message":"Indexing"}\' | td agent-event my-agent prompt-submit thinking',
1775
1899
  },
1776
1900
  integrationSteps: [
1777
- 'Generate a version 2 manifest using this schema and save it as JSON.',
1778
- 'Run td plugin-create <manifest-file>; the Agent appears in Termdock immediately.',
1901
+ 'Create a repository with manifest.json at its root; icon.svg and helper files are optional.',
1902
+ 'Publish the repository, then install it with td plugin-install <https-git-url>. Local directories are also supported.',
1903
+ 'For manifest-only development, td plugin-create <manifest-file> remains available.',
1779
1904
  'Ask the user to open Settings > Detection Rules and click Install for this Agent. They can uninstall it there later.',
1780
1905
  'If the Agent hook format is not a Claude-style hooks map, configure native hooks to call td agent-event directly.',
1781
1906
  'Map the completed-turn hook to event "stop"; this is also the automatic-title trigger when the user enables titles for the Agent.',
1782
1907
  'Optionally declare titleNamer so this Agent can generate titles and dynamically publish its own model catalog.',
1908
+ 'Put optional model flags in titleNamer.modelArgs; keep the always-present prompt invocation in titleNamer.args.',
1909
+ 'Run td plugin-doctor <slug> --json and fix every warning before sharing the repository.',
1910
+ 'Tell the user that enabling a plugin title provider executes that plugin\'s declared CLI; installation alone never does.',
1783
1911
  'Run td plugin-list to verify registration.',
1784
1912
  ],
1785
1913
  manifest: AGENT_PLUGIN_EXAMPLE,
@@ -1862,7 +1990,7 @@ ${c.dim('───────────────────')}
1862
1990
 
1863
1991
  ${c.dim('// ── optional: hook installation (bring your own hooks.json surface) ──')}
1864
1992
  "hooks": {
1865
- ${c.dim("// Path to the agent's hooks config file (~-abbreviated).")}
1993
+ ${c.dim("// JSON path under ~/ only; symlink traversal is rejected.")}
1866
1994
  ${c.dim('// termdock inserts marker-carrying entries; leaves user entries untouched.')}
1867
1995
  "target": "~/.trae/settings.json",
1868
1996
 
@@ -1888,10 +2016,13 @@ ${c.dim('───────────────────')}
1888
2016
 
1889
2017
  ${c.dim('// ── optional: injectable automatic-title provider ──')}
1890
2018
  "titleNamer": {
1891
- ${c.dim('// No shell expansion. {prompt} is required; a bare {model} arg is omitted when automatic.')}
2019
+ ${c.dim('// No shell expansion. Commands run only after the user enables this title provider.')}
2020
+ ${c.dim('// modelArgs is an all-or-nothing group and is omitted for CLI-default mode.')}
1892
2021
  "command": "trae",
1893
- "args": ["title", "--prompt", "{prompt}", "--model={model}"],
1894
- ${c.dim('// Must print JSON: strings or {id, displayName, description, isDefault} objects.')}
2022
+ "modelArgs": ["--model", "{model}"],
2023
+ "args": ["title", "--prompt", "{prompt}"],
2024
+ ${c.dim('// JSON array or {models,recommendedModel}; id/name/model are accepted.')}
2025
+ ${c.dim('// Use node + {pluginDir}/scripts/... when native fields need conversion.')}
1895
2026
  "models": { "command": "trae", "args": ["models", "--json"] }
1896
2027
  }
1897
2028
  }
@@ -1915,11 +2046,20 @@ ${c.dim('──────────────────')}
1915
2046
  ${c.dim('# Inject the Agent definition; it appears in the UI immediately')}
1916
2047
  td plugin-create ./trae-manifest.json
1917
2048
 
2049
+ ${c.dim('# Or install a complete, updateable package from a public HTTPS Git repository')}
2050
+ td plugin-install https://github.com/owner/trae-termdock-plugin
2051
+
1918
2052
  ${c.dim('# The user then opens Settings > Detection Rules and clicks Install.')}
1919
2053
  ${c.dim('# The same UI button can uninstall the hooks later.')}
1920
2054
 
1921
2055
  ${c.dim('# Verify registration')}
1922
- td plugin-list
2056
+ td plugin-list --json
2057
+
2058
+ ${c.dim('# Manage the package and its hooks independently')}
2059
+ td plugin-check trae
2060
+ td plugin-update trae
2061
+ td plugin-doctor trae --json
2062
+ td plugin-hooks trae install
1923
2063
 
1924
2064
  ${c.dim('# Remove')}
1925
2065
  td plugin-remove trae
@@ -1993,7 +2133,21 @@ async function runPluginCreate(manifestPath) {
1993
2133
  console.log(` ${c.dim('Visible immediately in Settings > Detection Rules; no restart required.')}`);
1994
2134
  console.log(` ${c.dim('The user can click Install or Uninstall there.')}`);
1995
2135
  }
1996
- async function runPluginList() {
2136
+ async function runPluginInstall(source) {
2137
+ const { baseUrl, token } = requireRunningServer();
2138
+ const normalizedSource = /^https:\/\//i.test(source) ? source : path.resolve(source);
2139
+ const response = await postLocalJson(baseUrl, token, '/api/terminal/agent-plugins/install', { source: normalizedSource });
2140
+ const body = JSON.parse(response.body || '{}');
2141
+ if (response.statusCode < 200 || response.statusCode >= 300) {
2142
+ console.error(`${ICON.err} ${c.red(body.code ? `${body.code}: ${body.error ?? 'Plugin installation failed'}` : body.error ?? 'Plugin installation failed')}`);
2143
+ process.exit(1);
2144
+ }
2145
+ console.log(`${ICON.ok} ${c.green(`Plugin "${body.slug}" installed.`)}`);
2146
+ if (body.dir)
2147
+ console.log(` ${c.dim('Directory:')} ${c.cyan(body.dir)}`);
2148
+ console.log(` ${c.dim('Next:')} ${c.cyan(`td plugin-hooks ${body.slug} install`)} ${c.dim('or use Settings > Detection Rules')}`);
2149
+ }
2150
+ async function runPluginList(json = false) {
1997
2151
  const { baseUrl, token } = requireRunningServer();
1998
2152
  const response = await getLocalJson(baseUrl, token, '/api/terminal/agent-plugins');
1999
2153
  const body = JSON.parse(response.body || '{}');
@@ -2001,6 +2155,10 @@ async function runPluginList() {
2001
2155
  console.error(`${ICON.err} ${c.red('Failed to list plugins')}`);
2002
2156
  process.exit(1);
2003
2157
  }
2158
+ if (json) {
2159
+ console.log(JSON.stringify(body, null, 2));
2160
+ return;
2161
+ }
2004
2162
  const plugins = body.plugins ?? [];
2005
2163
  if (plugins.length === 0) {
2006
2164
  console.log(`${c.dim('No plugins installed.')}`);
@@ -2019,7 +2177,11 @@ async function runPluginList() {
2019
2177
  flags.push('resume');
2020
2178
  if (p.iconMode !== 'mask')
2021
2179
  flags.push(p.iconMode);
2180
+ if (p.updateAvailable)
2181
+ flags.push('update');
2022
2182
  console.log(` ${c.cyan(p.slug)} ${c.dim(p.displayName)} ${c.gray(p.accentColor)} ${c.dim(flags.length > 0 ? `[${flags.join(', ')}]` : '')}`);
2183
+ if (p.source)
2184
+ console.log(` ${c.dim(`${p.sourceType ?? 'source'}: ${p.source}${p.revision ? ` @ ${p.revision.slice(0, 8)}` : ''}`)}`);
2023
2185
  }
2024
2186
  }
2025
2187
  const errors = body.errors ?? [];
@@ -2037,6 +2199,68 @@ async function runPluginList() {
2037
2199
  }
2038
2200
  }
2039
2201
  }
2202
+ async function runPluginSourceAction(slug, action) {
2203
+ const { baseUrl, token } = requireRunningServer();
2204
+ const response = await postLocalJson(baseUrl, token, `/api/terminal/agent-plugins/${encodeURIComponent(slug)}/${action}`, {});
2205
+ const body = JSON.parse(response.body || '{}');
2206
+ if (response.statusCode < 200 || response.statusCode >= 300) {
2207
+ console.error(`${ICON.err} ${c.red(body.error ?? `Plugin ${action} failed`)}`);
2208
+ process.exit(1);
2209
+ }
2210
+ if (action === 'check-update') {
2211
+ console.log(body.updateAvailable
2212
+ ? `${ICON.warn} ${c.yellow(`Update available for ${slug}`)} ${c.dim(`${body.revision?.slice(0, 8) ?? '?'} → ${body.latestRevision?.slice(0, 8) ?? '?'}`)}`
2213
+ : `${ICON.ok} ${c.green(`${slug} is up to date.`)}`);
2214
+ }
2215
+ else {
2216
+ console.log(`${ICON.ok} ${c.green(`Plugin "${slug}" updated.`)}`);
2217
+ if (body.hookWarning)
2218
+ console.log(`${ICON.warn} ${c.yellow(`Hooks need attention: ${body.hookWarning}`)}`);
2219
+ if (body.titleWarning)
2220
+ console.log(`${ICON.warn} ${c.yellow(`Title commands need attention: ${body.titleWarning}`)}`);
2221
+ }
2222
+ }
2223
+ async function runPluginDoctor(slug, json = false) {
2224
+ const { baseUrl, token } = requireRunningServer();
2225
+ const response = await postLocalJson(baseUrl, token, `/api/terminal/agent-plugins/${encodeURIComponent(slug)}/doctor`, {});
2226
+ const body = JSON.parse(response.body || '{}');
2227
+ if (response.statusCode < 200 || response.statusCode >= 300) {
2228
+ console.error(`${ICON.err} ${c.red(body.error ?? 'Plugin diagnosis failed')}`);
2229
+ process.exit(1);
2230
+ }
2231
+ if (json) {
2232
+ console.log(JSON.stringify(body, null, 2));
2233
+ }
2234
+ else {
2235
+ const ok = body.status === 'ok' || body.status === 'cli-default';
2236
+ console.log(`${ok ? ICON.ok : ICON.warn} ${ok ? c.green(body.displayName ?? slug) : c.yellow(body.displayName ?? slug)} ${c.dim(`(${body.status ?? 'unknown'})`)}`);
2237
+ console.log(` ${c.dim('Title provider:')} ${body.hasTitleNamer ? 'yes' : 'no'}`);
2238
+ console.log(` ${c.dim('Model probe:')} ${body.hasModelCommand ? `${body.models?.length ?? 0} usable model(s)` : 'not declared; CLI default'}`);
2239
+ if (body.recommendedModel)
2240
+ console.log(` ${c.dim('Automatic model:')} ${c.cyan(body.recommendedModel)}`);
2241
+ if (body.selectionBehavior)
2242
+ console.log(` ${c.dim('Behavior:')} ${body.selectionBehavior}`);
2243
+ if (body.error)
2244
+ console.log(` ${c.red('Error:')} ${body.error}`);
2245
+ for (const warning of body.warnings ?? [])
2246
+ console.log(` ${ICON.warn} ${c.yellow(warning)}`);
2247
+ for (const step of body.nextSteps ?? [])
2248
+ console.log(` ${c.dim('Next:')} ${step}`);
2249
+ }
2250
+ if (body.status === 'missing-title-namer' || body.status === 'probe-failed' || body.status === 'no-models') {
2251
+ process.exitCode = 1;
2252
+ }
2253
+ }
2254
+ async function runPluginHooks(slug, action) {
2255
+ const { baseUrl, token } = requireRunningServer();
2256
+ const response = await postLocalJson(baseUrl, token, `/api/terminal/agent-hooks/${encodeURIComponent(slug)}/${action}`, {});
2257
+ const body = JSON.parse(response.body || '{}');
2258
+ if (response.statusCode < 200 || response.statusCode >= 300) {
2259
+ console.error(`${ICON.err} ${c.red(body.error ?? `Plugin hook ${action} failed`)}`);
2260
+ process.exit(1);
2261
+ }
2262
+ console.log(`${ICON.ok} ${c.green(body.summary ?? `Hooks ${action === 'install' ? 'installed' : 'removed'}.`)}`);
2263
+ }
2040
2264
  async function runPluginRemove(slug) {
2041
2265
  const { baseUrl, token } = requireRunningServer();
2042
2266
  const response = await new Promise((resolve, reject) => {
@@ -3029,8 +3253,44 @@ async function main() {
3029
3253
  await runPluginCreate(options.pluginCreate);
3030
3254
  process.exit(0);
3031
3255
  }
3256
+ if (options.pluginInstall !== undefined) {
3257
+ if (!options.pluginInstall) {
3258
+ console.error(`${ICON.err} ${c.red('plugin-install requires an HTTPS Git URL or local package path')}`);
3259
+ process.exit(1);
3260
+ }
3261
+ await runPluginInstall(options.pluginInstall);
3262
+ process.exit(0);
3263
+ }
3032
3264
  if (options.pluginList) {
3033
- await runPluginList();
3265
+ await runPluginList(options.pluginListJson);
3266
+ process.exit(0);
3267
+ }
3268
+ if (options.pluginCheck !== undefined) {
3269
+ if (!options.pluginCheck) {
3270
+ console.error(`${ICON.err} ${c.red('plugin-check requires a plugin slug')}`);
3271
+ process.exit(1);
3272
+ }
3273
+ await runPluginSourceAction(options.pluginCheck, 'check-update');
3274
+ process.exit(0);
3275
+ }
3276
+ if (options.pluginUpdate !== undefined) {
3277
+ if (!options.pluginUpdate) {
3278
+ console.error(`${ICON.err} ${c.red('plugin-update requires a plugin slug')}`);
3279
+ process.exit(1);
3280
+ }
3281
+ await runPluginSourceAction(options.pluginUpdate, 'update');
3282
+ process.exit(0);
3283
+ }
3284
+ if (options.pluginDoctor) {
3285
+ if (!options.pluginDoctor.slug) {
3286
+ console.error(`${ICON.err} ${c.red('plugin-doctor requires a plugin slug')}`);
3287
+ process.exit(1);
3288
+ }
3289
+ await runPluginDoctor(options.pluginDoctor.slug, options.pluginDoctor.json);
3290
+ process.exit(process.exitCode ?? 0);
3291
+ }
3292
+ if (options.pluginHooks) {
3293
+ await runPluginHooks(options.pluginHooks.slug, options.pluginHooks.action);
3034
3294
  process.exit(0);
3035
3295
  }
3036
3296
  if (options.pluginRemove !== undefined) {
@@ -411,13 +411,22 @@ export function createApp(options = {}) {
411
411
  app.get('/api/home', requireAuth(), (_req, res) => {
412
412
  res.json({ home: homedir() });
413
413
  });
414
+ // A locally installed Agent/plugin CLI uses the mode-0600 local API token.
415
+ // Keep this bypass loopback-only: possession of the token must not turn the
416
+ // LAN-facing terminal API into a bearer-token endpoint.
417
+ const isTrustedLocalCliRequest = (req) => {
418
+ if (!options.localApiToken || req.header('X-Termdock-Local-Token') !== options.localApiToken)
419
+ return false;
420
+ const address = req.socket.remoteAddress ?? '';
421
+ return address === '127.0.0.1' || address === '::1' || address === '::ffff:127.0.0.1';
422
+ };
414
423
  // 应用鉴权 + CSRF保护(在终端路由之前)
415
424
  // The HTML preview route authenticates itself: document requests use the
416
425
  // session cookie, then get redirected to a short-lived URL token so the
417
426
  // sandboxed iframe's subresource requests (which browsers refuse to send
418
427
  // cookies for) can still load images/css/js.
419
- app.use('/api/terminal', requireAuth({ bypass: (req) => req.path.startsWith('/fs/preview') }));
420
- app.use('/api/terminal', csrfProtection.verifyMiddleware());
428
+ app.use('/api/terminal', requireAuth({ bypass: (req) => req.path.startsWith('/fs/preview') || isTrustedLocalCliRequest(req) }));
429
+ app.use('/api/terminal', csrfProtection.verifyMiddleware({ bypass: isTrustedLocalCliRequest }));
421
430
  // 终端路由
422
431
  app.use('/api/terminal', terminalRoutes);
423
432
  // 文件系统路由(继承 /api/terminal 上的 auth + CSRF 保护)