termdock 1.4.70 → 1.4.72

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 (81) hide show
  1. package/README.md +36 -0
  2. package/dist/client/assets/{ModelPreview-Cs14IQuQ.js → ModelPreview-i9dS1A3I.js} +1 -1
  3. package/dist/client/assets/{abnfDiagram-VRR7QNED-DuDfdv-6.js → abnfDiagram-VRR7QNED-Cs-ty-HA.js} +1 -1
  4. package/dist/client/assets/{arc-CSYzGV_2.js → arc-CIX5k_JD.js} +1 -1
  5. package/dist/client/assets/{architectureDiagram-ZJ3FMSHR-YiZ0HXNi.js → architectureDiagram-ZJ3FMSHR-DnPnQoJy.js} +1 -1
  6. package/dist/client/assets/{blockDiagram-677ZJIJ3-IaQMzmYv.js → blockDiagram-677ZJIJ3-Dhew-L5v.js} +1 -1
  7. package/dist/client/assets/{c4Diagram-LMCZKHZV-BwyuIPRL.js → c4Diagram-LMCZKHZV-BSsEMeWn.js} +1 -1
  8. package/dist/client/assets/channel-CASnt1pP.js +1 -0
  9. package/dist/client/assets/{chunk-2Q5K7J3B-BtHQYLBT.js → chunk-2Q5K7J3B-BWm--1Mo.js} +1 -1
  10. package/dist/client/assets/{chunk-32BRIVSS-CXrK07sW.js → chunk-32BRIVSS-D_WFmuvb.js} +1 -1
  11. package/dist/client/assets/{chunk-5VM5RSS4-BfBshn11.js → chunk-5VM5RSS4-CQdzqlQg.js} +1 -1
  12. package/dist/client/assets/{chunk-EX3LRPZG-BJbilNaf.js → chunk-EX3LRPZG-Cm3zLQpt.js} +1 -1
  13. package/dist/client/assets/{chunk-JWPE2WC7-wgcmSRlL.js → chunk-JWPE2WC7-Cmcv5sEN.js} +1 -1
  14. package/dist/client/assets/{chunk-MOJQB5TN-8VZVUeDI.js → chunk-MOJQB5TN-Bam44EwJ.js} +1 -1
  15. package/dist/client/assets/{chunk-RYQCIY6F-B900YBFa.js → chunk-RYQCIY6F-DIVOuE8y.js} +1 -1
  16. package/dist/client/assets/{chunk-V7JOEXUC-BK4_RLz-.js → chunk-V7JOEXUC-BfOTf7yg.js} +1 -1
  17. package/dist/client/assets/{chunk-VR4S4FIN-Kj5_ycjB.js → chunk-VR4S4FIN-DbiXn0dd.js} +1 -1
  18. package/dist/client/assets/{chunk-XXDRQBXY-BolvPLRz.js → chunk-XXDRQBXY-C4pOe4OC.js} +1 -1
  19. package/dist/client/assets/classDiagram-OUVF2IWQ-Da_SQWEq.js +1 -0
  20. package/dist/client/assets/classDiagram-v2-EOCWNBFH-Da_SQWEq.js +1 -0
  21. package/dist/client/assets/{cose-bilkent-JH36ORCC-9_9_SLqv.js → cose-bilkent-JH36ORCC-CCoSPmoc.js} +1 -1
  22. package/dist/client/assets/{cynefin-VYW2F7L2-yEpoi3vG.js → cynefin-VYW2F7L2-B2lYz7sl.js} +1 -1
  23. package/dist/client/assets/{cynefinDiagram-TSTJHNR4-Dy-gvup_.js → cynefinDiagram-TSTJHNR4-Cd7crN9s.js} +1 -1
  24. package/dist/client/assets/{dagre-VKFMJZFB-CNjJ5pRK.js → dagre-VKFMJZFB-q9Fj9HuQ.js} +1 -1
  25. package/dist/client/assets/{diagram-FQU43EPY-uZ73Rj3T.js → diagram-FQU43EPY-iqHy5YHW.js} +1 -1
  26. package/dist/client/assets/{diagram-G47NLZAW-Cxt7TYZT.js → diagram-G47NLZAW-BE-jlh1B.js} +1 -1
  27. package/dist/client/assets/{diagram-NH7WQ7WH-B4TKinuZ.js → diagram-NH7WQ7WH-DmP7_WOC.js} +1 -1
  28. package/dist/client/assets/{diagram-OA4YK3LP-CGSuenHU.js → diagram-OA4YK3LP-CHx_k8Ar.js} +1 -1
  29. package/dist/client/assets/{diagram-WEI45ONY-DqqzDaUZ.js → diagram-WEI45ONY-D0wImpAF.js} +1 -1
  30. package/dist/client/assets/{ebnfDiagram-CCIWWBDH-B2SgXL5w.js → ebnfDiagram-CCIWWBDH-DyCB0rF8.js} +1 -1
  31. package/dist/client/assets/{erDiagram-Q63AITRT-D-ibkr8y.js → erDiagram-Q63AITRT-SRcd1B2Z.js} +1 -1
  32. package/dist/client/assets/{flowDiagram-23GEKE2U-BPeyMqr-.js → flowDiagram-23GEKE2U-D0i9mFwt.js} +1 -1
  33. package/dist/client/assets/{ganttDiagram-NO4QXBWP-DcEqEzzu.js → ganttDiagram-NO4QXBWP-BfwGIYDn.js} +1 -1
  34. package/dist/client/assets/{gitGraphDiagram-IHSO6WYX-DTZ6UCQY.js → gitGraphDiagram-IHSO6WYX-BXkJQP7O.js} +1 -1
  35. package/dist/client/assets/index-BA-DqJM2.js +451 -0
  36. package/dist/client/assets/{infoDiagram-FWYZ7A6U-BHrzVXeX.js → infoDiagram-FWYZ7A6U-CM-Ebv9R.js} +1 -1
  37. package/dist/client/assets/{ishikawaDiagram-FXEZZL3T-CwuoQJku.js → ishikawaDiagram-FXEZZL3T-U-qnP7xi.js} +1 -1
  38. package/dist/client/assets/{journeyDiagram-5HDEW3XC-ZEcy0rDZ.js → journeyDiagram-5HDEW3XC-D4RxvsuL.js} +1 -1
  39. package/dist/client/assets/{kanban-definition-HUTT4EX6-BDBawNbr.js → kanban-definition-HUTT4EX6-Dm_asCYK.js} +1 -1
  40. package/dist/client/assets/{linear-smYQWzDV.js → linear-Zhe8d635.js} +1 -1
  41. package/dist/client/assets/{mermaid.core-eJs7Ouvw.js → mermaid.core-D_NWIuMY.js} +4 -4
  42. package/dist/client/assets/{mindmap-definition-LN4V7U3C-BK1IZ9zg.js → mindmap-definition-LN4V7U3C-DXpNidd5.js} +1 -1
  43. package/dist/client/assets/{pegDiagram-2B236MQR-B3-mVNgX.js → pegDiagram-2B236MQR-DdOwOEgu.js} +1 -1
  44. package/dist/client/assets/{pieDiagram-ENE6RG2P-Bkeeyx5l.js → pieDiagram-ENE6RG2P-D_A9HoY_.js} +1 -1
  45. package/dist/client/assets/{quadrantDiagram-ABIIQ3AL-DouIklEO.js → quadrantDiagram-ABIIQ3AL-D-Y3cLEF.js} +1 -1
  46. package/dist/client/assets/{railroadDiagram-RFXS5EU6-BB_jXQd2.js → railroadDiagram-RFXS5EU6-DD6eFV82.js} +1 -1
  47. package/dist/client/assets/{requirementDiagram-TGXJPOKE-u56IHfiR.js → requirementDiagram-TGXJPOKE-B59HJgL2.js} +1 -1
  48. package/dist/client/assets/{sankeyDiagram-HTMAVEWB-eDi2cs5E.js → sankeyDiagram-HTMAVEWB-C2BvHMH8.js} +1 -1
  49. package/dist/client/assets/{sequenceDiagram-DBY2YBRQ-DnAA2xYa.js → sequenceDiagram-DBY2YBRQ-3004tCRm.js} +1 -1
  50. package/dist/client/assets/{sizeCapture-X5ZJPWSS-BogPui79.js → sizeCapture-X5ZJPWSS-DCBhg8QW.js} +1 -1
  51. package/dist/client/assets/{stateDiagram-2N3HPSRC--EeXDy2M.js → stateDiagram-2N3HPSRC-BVqiKVe0.js} +1 -1
  52. package/dist/client/assets/stateDiagram-v2-6OUMAXLB-Z0GHWyoF.js +1 -0
  53. package/dist/client/assets/{swimlanes-5IMT3BWC-DZAw7-kY.js → swimlanes-5IMT3BWC-DS6-sdEW.js} +2 -2
  54. package/dist/client/assets/swimlanesDiagram-G3AALYLV-el4HDX0G.js +8 -0
  55. package/dist/client/assets/{timeline-definition-FHXFAJF6-DkcnpB-Y.js → timeline-definition-FHXFAJF6-wVOEmQwG.js} +1 -1
  56. package/dist/client/assets/{vennDiagram-L72KCM5P-BStcDGEf.js → vennDiagram-L72KCM5P-DMZiSJDk.js} +1 -1
  57. package/dist/client/assets/{wardleyDiagram-EHGQE667-Cj4CQn1M.js → wardleyDiagram-EHGQE667-Dx5Aob4Y.js} +1 -1
  58. package/dist/client/assets/{xychartDiagram-FW5EYKEG-_uBCjai8.js → xychartDiagram-FW5EYKEG-BqMhkhmW.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 +16 -36
  62. package/dist/server/agent/installers.js +98 -15
  63. package/dist/server/agent/pluginPackages.js +289 -0
  64. package/dist/server/agent/plugins.js +164 -15
  65. package/dist/server/agent/registry.js +1 -1
  66. package/dist/server/agent/session.js +10 -1
  67. package/dist/server/agent/titleNamerCatalog.js +131 -2
  68. package/dist/server/agentHook.js +47 -0
  69. package/dist/server/cli.js +224 -13
  70. package/dist/server/entry.js +11 -2
  71. package/dist/server/routes/terminal.js +267 -37
  72. package/dist/server/utils/csrfProtection.js +4 -1
  73. package/dist/server/utils/settings.js +18 -5
  74. package/package.json +1 -1
  75. package/runtime-manifest.json +2 -2
  76. package/dist/client/assets/channel-BotxtiNE.js +0 -1
  77. package/dist/client/assets/classDiagram-OUVF2IWQ-CLWlbTSC.js +0 -1
  78. package/dist/client/assets/classDiagram-v2-EOCWNBFH-CLWlbTSC.js +0 -1
  79. package/dist/client/assets/index-ivnGnbVg.js +0 -451
  80. package/dist/client/assets/stateDiagram-v2-6OUMAXLB-Dc-3HKTs.js +0 -1
  81. package/dist/client/assets/swimlanesDiagram-G3AALYLV-CI1Wlr0V.js +0 -8
@@ -159,7 +159,15 @@ 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-hooks <slug> <install|uninstall>
170
+ Manage the Agent-native hook entries for a plugin
163
171
  --plugin-remove <slug>
164
172
  Remove an installed agent plugin
165
173
  -h, --help Show this help message
@@ -173,7 +181,16 @@ Short commands:
173
181
  pi Same as agent-plugin
174
182
  plugin-create <file>
175
183
  Same as --plugin-create <file>
176
- plugin-list Same as --plugin-list
184
+ plugin-install <source>
185
+ Install a shareable plugin repository or local package
186
+ plugin-list [--json]
187
+ List plugin packages and their recorded sources
188
+ plugin-check <slug>
189
+ Check one plugin for updates
190
+ plugin-update <slug>
191
+ Update one plugin from its recorded source
192
+ plugin-hooks <slug> <install|uninstall>
193
+ Install or uninstall only that plugin's Agent hooks
177
194
  plugin-remove <slug>
178
195
  Same as --plugin-remove <slug>
179
196
  s Same as --status
@@ -513,7 +530,12 @@ function parseArgs(argv) {
513
530
  let pluginInit = false;
514
531
  let pluginInitJson = false;
515
532
  let pluginCreate;
533
+ let pluginInstall;
516
534
  let pluginList = false;
535
+ let pluginListJson = false;
536
+ let pluginCheck;
537
+ let pluginUpdate;
538
+ let pluginHooks;
517
539
  let pluginRemove;
518
540
  let agentEvent;
519
541
  // Short command aliases for the common path. Keep these positional-only so
@@ -638,13 +660,37 @@ function parseArgs(argv) {
638
660
  argv = argv.slice(2);
639
661
  }
640
662
  else {
641
- pluginCreate = undefined;
663
+ pluginCreate = '';
642
664
  argv = argv.slice(1);
643
665
  }
644
666
  }
667
+ else if (command === 'pinstall' || command === 'plugin-install') {
668
+ pluginInstall = next && !next.startsWith('-') ? next : '';
669
+ argv = argv.slice(pluginInstall ? 2 : 1);
670
+ }
645
671
  else if (command === 'pinfo' || command === 'plugin-list') {
646
672
  pluginList = true;
647
- argv = argv.slice(1);
673
+ pluginListJson = next === '--json';
674
+ argv = argv.slice(pluginListJson ? 2 : 1);
675
+ }
676
+ else if (command === 'pcheck' || command === 'plugin-check') {
677
+ pluginCheck = next && !next.startsWith('-') ? next : '';
678
+ argv = argv.slice(pluginCheck ? 2 : 1);
679
+ }
680
+ else if (command === 'pupdate' || command === 'plugin-update') {
681
+ pluginUpdate = next && !next.startsWith('-') ? next : '';
682
+ argv = argv.slice(pluginUpdate ? 2 : 1);
683
+ }
684
+ else if (command === 'phooks' || command === 'plugin-hooks') {
685
+ const action = argv[2];
686
+ if (next && (action === 'install' || action === 'uninstall')) {
687
+ pluginHooks = { slug: next, action };
688
+ argv = argv.slice(3);
689
+ }
690
+ else {
691
+ console.error(`${ICON.err} ${c.red('Usage: td plugin-hooks <slug> <install|uninstall>')}`);
692
+ process.exit(1);
693
+ }
648
694
  }
649
695
  else if (command === 'premove' || command === 'plugin-remove') {
650
696
  if (next && !next.startsWith('-')) {
@@ -652,7 +698,7 @@ function parseArgs(argv) {
652
698
  argv = argv.slice(2);
653
699
  }
654
700
  else {
655
- pluginRemove = undefined;
701
+ pluginRemove = '';
656
702
  argv = argv.slice(1);
657
703
  }
658
704
  }
@@ -737,6 +783,8 @@ function parseArgs(argv) {
737
783
  if (arg === '--json') {
738
784
  if (pluginInit)
739
785
  pluginInitJson = true;
786
+ else if (pluginList)
787
+ pluginListJson = true;
740
788
  else
741
789
  tlsJson = true;
742
790
  continue;
@@ -850,7 +898,7 @@ function parseArgs(argv) {
850
898
  index += 1;
851
899
  }
852
900
  else {
853
- pluginCreate = undefined;
901
+ pluginCreate = '';
854
902
  }
855
903
  continue;
856
904
  }
@@ -858,6 +906,41 @@ function parseArgs(argv) {
858
906
  pluginList = true;
859
907
  continue;
860
908
  }
909
+ if (arg === '--plugin-install' || arg === '--plugin-check' || arg === '--plugin-update') {
910
+ const next = argv[index + 1];
911
+ if (next && !next.startsWith('-')) {
912
+ if (arg === '--plugin-install')
913
+ pluginInstall = next;
914
+ else if (arg === '--plugin-check')
915
+ pluginCheck = next;
916
+ else
917
+ pluginUpdate = next;
918
+ index += 1;
919
+ }
920
+ else if (arg === '--plugin-install') {
921
+ pluginInstall = '';
922
+ }
923
+ else if (arg === '--plugin-check') {
924
+ pluginCheck = '';
925
+ }
926
+ else {
927
+ pluginUpdate = '';
928
+ }
929
+ continue;
930
+ }
931
+ if (arg === '--plugin-hooks') {
932
+ const slug = argv[index + 1];
933
+ const action = argv[index + 2];
934
+ if (slug && (action === 'install' || action === 'uninstall')) {
935
+ pluginHooks = { slug, action };
936
+ index += 2;
937
+ }
938
+ else {
939
+ console.error(`${ICON.err} ${c.red('Usage: td --plugin-hooks <slug> <install|uninstall>')}`);
940
+ process.exit(1);
941
+ }
942
+ continue;
943
+ }
861
944
  if (arg === '--plugin-remove') {
862
945
  const next = argv[index + 1];
863
946
  if (next && !next.startsWith('-')) {
@@ -865,7 +948,7 @@ function parseArgs(argv) {
865
948
  index += 1;
866
949
  }
867
950
  else {
868
- pluginRemove = undefined;
951
+ pluginRemove = '';
869
952
  }
870
953
  continue;
871
954
  }
@@ -914,7 +997,12 @@ function parseArgs(argv) {
914
997
  pluginInit,
915
998
  pluginInitJson,
916
999
  pluginCreate,
1000
+ pluginInstall,
917
1001
  pluginList,
1002
+ pluginListJson,
1003
+ pluginCheck,
1004
+ pluginUpdate,
1005
+ pluginHooks,
918
1006
  pluginRemove,
919
1007
  agentEvent,
920
1008
  };
@@ -1725,6 +1813,14 @@ const AGENT_PLUGIN_EXAMPLE = {
1725
1813
  command: 'my-agent --resume {sessionId}',
1726
1814
  staleFlags: ['--resume'],
1727
1815
  },
1816
+ titleNamer: {
1817
+ command: 'my-agent',
1818
+ args: ['title', '--json', '--prompt', '{prompt}', '--model={model}'],
1819
+ models: {
1820
+ command: 'my-agent',
1821
+ args: ['models', '--json'],
1822
+ },
1823
+ },
1728
1824
  };
1729
1825
  function runPluginInitJson() {
1730
1826
  console.log(JSON.stringify({
@@ -1734,8 +1830,12 @@ function runPluginInitJson() {
1734
1830
  commands: {
1735
1831
  guide: 'td agent-plugin --json',
1736
1832
  register: 'td plugin-create ./manifest.json',
1833
+ installPackage: 'td plugin-install https://github.com/owner/repository',
1834
+ checkUpdate: 'td plugin-check <slug>',
1835
+ updatePackage: 'td plugin-update <slug>',
1737
1836
  emit: 'td agent-event <slug> <event> [status]',
1738
- inspect: 'td plugin-list',
1837
+ inspect: 'td plugin-list --json',
1838
+ hooks: 'td plugin-hooks <slug> <install|uninstall>',
1739
1839
  remove: 'td plugin-remove <slug>',
1740
1840
  },
1741
1841
  ui: {
@@ -1755,6 +1855,13 @@ function runPluginInitJson() {
1755
1855
  indicator: 'Theme-safe visual indicator.',
1756
1856
  tone: 'Theme token; arbitrary status colors are not accepted.',
1757
1857
  },
1858
+ security: {
1859
+ source: 'Only public HTTPS repository roots on approved Git hosts or explicit local paths are accepted; URL credentials are rejected.',
1860
+ package: 'Symlinks, active/external SVG content, oversized files, submodules, and excessive unpacked size are rejected.',
1861
+ hooks: 'hooks.target must be a non-symlinked ~/ JSON path. Termdock generates the hook command; manifests cannot inject shell.',
1862
+ titleCommands: 'Installing or inspecting a plugin never executes its title/model commands. They run only after the user enables automatic titles for that plugin.',
1863
+ updates: 'Changes to hook or title command declarations revoke the corresponding prior opt-in.',
1864
+ },
1758
1865
  migrationV1: {
1759
1866
  errorCode: 'AGENT_PLUGIN_MANIFEST_V1_UNSUPPORTED',
1760
1867
  guideCommand: 'td agent-plugin --json',
@@ -1766,10 +1873,14 @@ function runPluginInitJson() {
1766
1873
  example: 'printf \'%s\' \'{"session_id":"abc","message":"Indexing"}\' | td agent-event my-agent prompt-submit thinking',
1767
1874
  },
1768
1875
  integrationSteps: [
1769
- 'Generate a version 2 manifest using this schema and save it as JSON.',
1770
- 'Run td plugin-create <manifest-file>; the Agent appears in Termdock immediately.',
1876
+ 'Create a repository with manifest.json at its root; icon.svg and helper files are optional.',
1877
+ 'Publish the repository, then install it with td plugin-install <https-git-url>. Local directories are also supported.',
1878
+ 'For manifest-only development, td plugin-create <manifest-file> remains available.',
1771
1879
  'Ask the user to open Settings > Detection Rules and click Install for this Agent. They can uninstall it there later.',
1772
1880
  'If the Agent hook format is not a Claude-style hooks map, configure native hooks to call td agent-event directly.',
1881
+ 'Map the completed-turn hook to event "stop"; this is also the automatic-title trigger when the user enables titles for the Agent.',
1882
+ 'Optionally declare titleNamer so this Agent can generate titles and dynamically publish its own model catalog.',
1883
+ 'Tell the user that enabling a plugin title provider executes that plugin\'s declared CLI; installation alone never does.',
1773
1884
  'Run td plugin-list to verify registration.',
1774
1885
  ],
1775
1886
  manifest: AGENT_PLUGIN_EXAMPLE,
@@ -1852,7 +1963,7 @@ ${c.dim('───────────────────')}
1852
1963
 
1853
1964
  ${c.dim('// ── optional: hook installation (bring your own hooks.json surface) ──')}
1854
1965
  "hooks": {
1855
- ${c.dim("// Path to the agent's hooks config file (~-abbreviated).")}
1966
+ ${c.dim("// JSON path under ~/ only; symlink traversal is rejected.")}
1856
1967
  ${c.dim('// termdock inserts marker-carrying entries; leaves user entries untouched.')}
1857
1968
  "target": "~/.trae/settings.json",
1858
1969
 
@@ -1874,6 +1985,16 @@ ${c.dim('───────────────────')}
1874
1985
  "command": "trae --resume {sessionId}",
1875
1986
  ${c.dim('// Flags to strip from the original launch argv when replaying the resume.')}
1876
1987
  "staleFlags": ["--resume", "-r", "--session-id", "--continue"]
1988
+ },
1989
+
1990
+ ${c.dim('// ── optional: injectable automatic-title provider ──')}
1991
+ "titleNamer": {
1992
+ ${c.dim('// No shell expansion. Commands run only after the user enables this title provider.')}
1993
+ ${c.dim('// {prompt} is required; a bare {model} arg is omitted when automatic.')}
1994
+ "command": "trae",
1995
+ "args": ["title", "--prompt", "{prompt}", "--model={model}"],
1996
+ ${c.dim('// Must print JSON: strings or {id, displayName, description, isDefault} objects.')}
1997
+ "models": { "command": "trae", "args": ["models", "--json"] }
1877
1998
  }
1878
1999
  }
1879
2000
 
@@ -1887,6 +2008,7 @@ ${c.dim('─────────────────────')}
1887
2008
  tool-complete A tool execution completed (unblocks waiting state)
1888
2009
  notification Generic notification from the agent (heuristic gated)
1889
2010
  stop Agent turn finished / went idle
2011
+ Also triggers automatic title generation when enabled
1890
2012
  session-end Agent session closed / binary exited
1891
2013
 
1892
2014
  ${c.bold('Creating a plugin')}
@@ -1895,11 +2017,19 @@ ${c.dim('──────────────────')}
1895
2017
  ${c.dim('# Inject the Agent definition; it appears in the UI immediately')}
1896
2018
  td plugin-create ./trae-manifest.json
1897
2019
 
2020
+ ${c.dim('# Or install a complete, updateable package from a public HTTPS Git repository')}
2021
+ td plugin-install https://github.com/owner/trae-termdock-plugin
2022
+
1898
2023
  ${c.dim('# The user then opens Settings > Detection Rules and clicks Install.')}
1899
2024
  ${c.dim('# The same UI button can uninstall the hooks later.')}
1900
2025
 
1901
2026
  ${c.dim('# Verify registration')}
1902
- td plugin-list
2027
+ td plugin-list --json
2028
+
2029
+ ${c.dim('# Manage the package and its hooks independently')}
2030
+ td plugin-check trae
2031
+ td plugin-update trae
2032
+ td plugin-hooks trae install
1903
2033
 
1904
2034
  ${c.dim('# Remove')}
1905
2035
  td plugin-remove trae
@@ -1973,7 +2103,21 @@ async function runPluginCreate(manifestPath) {
1973
2103
  console.log(` ${c.dim('Visible immediately in Settings > Detection Rules; no restart required.')}`);
1974
2104
  console.log(` ${c.dim('The user can click Install or Uninstall there.')}`);
1975
2105
  }
1976
- async function runPluginList() {
2106
+ async function runPluginInstall(source) {
2107
+ const { baseUrl, token } = requireRunningServer();
2108
+ const normalizedSource = /^https:\/\//i.test(source) ? source : path.resolve(source);
2109
+ const response = await postLocalJson(baseUrl, token, '/api/terminal/agent-plugins/install', { source: normalizedSource });
2110
+ const body = JSON.parse(response.body || '{}');
2111
+ if (response.statusCode < 200 || response.statusCode >= 300) {
2112
+ console.error(`${ICON.err} ${c.red(body.code ? `${body.code}: ${body.error ?? 'Plugin installation failed'}` : body.error ?? 'Plugin installation failed')}`);
2113
+ process.exit(1);
2114
+ }
2115
+ console.log(`${ICON.ok} ${c.green(`Plugin "${body.slug}" installed.`)}`);
2116
+ if (body.dir)
2117
+ console.log(` ${c.dim('Directory:')} ${c.cyan(body.dir)}`);
2118
+ console.log(` ${c.dim('Next:')} ${c.cyan(`td plugin-hooks ${body.slug} install`)} ${c.dim('or use Settings > Detection Rules')}`);
2119
+ }
2120
+ async function runPluginList(json = false) {
1977
2121
  const { baseUrl, token } = requireRunningServer();
1978
2122
  const response = await getLocalJson(baseUrl, token, '/api/terminal/agent-plugins');
1979
2123
  const body = JSON.parse(response.body || '{}');
@@ -1981,6 +2125,10 @@ async function runPluginList() {
1981
2125
  console.error(`${ICON.err} ${c.red('Failed to list plugins')}`);
1982
2126
  process.exit(1);
1983
2127
  }
2128
+ if (json) {
2129
+ console.log(JSON.stringify(body, null, 2));
2130
+ return;
2131
+ }
1984
2132
  const plugins = body.plugins ?? [];
1985
2133
  if (plugins.length === 0) {
1986
2134
  console.log(`${c.dim('No plugins installed.')}`);
@@ -1999,7 +2147,11 @@ async function runPluginList() {
1999
2147
  flags.push('resume');
2000
2148
  if (p.iconMode !== 'mask')
2001
2149
  flags.push(p.iconMode);
2150
+ if (p.updateAvailable)
2151
+ flags.push('update');
2002
2152
  console.log(` ${c.cyan(p.slug)} ${c.dim(p.displayName)} ${c.gray(p.accentColor)} ${c.dim(flags.length > 0 ? `[${flags.join(', ')}]` : '')}`);
2153
+ if (p.source)
2154
+ console.log(` ${c.dim(`${p.sourceType ?? 'source'}: ${p.source}${p.revision ? ` @ ${p.revision.slice(0, 8)}` : ''}`)}`);
2003
2155
  }
2004
2156
  }
2005
2157
  const errors = body.errors ?? [];
@@ -2017,6 +2169,37 @@ async function runPluginList() {
2017
2169
  }
2018
2170
  }
2019
2171
  }
2172
+ async function runPluginSourceAction(slug, action) {
2173
+ const { baseUrl, token } = requireRunningServer();
2174
+ const response = await postLocalJson(baseUrl, token, `/api/terminal/agent-plugins/${encodeURIComponent(slug)}/${action}`, {});
2175
+ const body = JSON.parse(response.body || '{}');
2176
+ if (response.statusCode < 200 || response.statusCode >= 300) {
2177
+ console.error(`${ICON.err} ${c.red(body.error ?? `Plugin ${action} failed`)}`);
2178
+ process.exit(1);
2179
+ }
2180
+ if (action === 'check-update') {
2181
+ console.log(body.updateAvailable
2182
+ ? `${ICON.warn} ${c.yellow(`Update available for ${slug}`)} ${c.dim(`${body.revision?.slice(0, 8) ?? '?'} → ${body.latestRevision?.slice(0, 8) ?? '?'}`)}`
2183
+ : `${ICON.ok} ${c.green(`${slug} is up to date.`)}`);
2184
+ }
2185
+ else {
2186
+ console.log(`${ICON.ok} ${c.green(`Plugin "${slug}" updated.`)}`);
2187
+ if (body.hookWarning)
2188
+ console.log(`${ICON.warn} ${c.yellow(`Hooks need attention: ${body.hookWarning}`)}`);
2189
+ if (body.titleWarning)
2190
+ console.log(`${ICON.warn} ${c.yellow(`Title commands need attention: ${body.titleWarning}`)}`);
2191
+ }
2192
+ }
2193
+ async function runPluginHooks(slug, action) {
2194
+ const { baseUrl, token } = requireRunningServer();
2195
+ const response = await postLocalJson(baseUrl, token, `/api/terminal/agent-hooks/${encodeURIComponent(slug)}/${action}`, {});
2196
+ const body = JSON.parse(response.body || '{}');
2197
+ if (response.statusCode < 200 || response.statusCode >= 300) {
2198
+ console.error(`${ICON.err} ${c.red(body.error ?? `Plugin hook ${action} failed`)}`);
2199
+ process.exit(1);
2200
+ }
2201
+ console.log(`${ICON.ok} ${c.green(body.summary ?? `Hooks ${action === 'install' ? 'installed' : 'removed'}.`)}`);
2202
+ }
2020
2203
  async function runPluginRemove(slug) {
2021
2204
  const { baseUrl, token } = requireRunningServer();
2022
2205
  const response = await new Promise((resolve, reject) => {
@@ -3009,8 +3192,36 @@ async function main() {
3009
3192
  await runPluginCreate(options.pluginCreate);
3010
3193
  process.exit(0);
3011
3194
  }
3195
+ if (options.pluginInstall !== undefined) {
3196
+ if (!options.pluginInstall) {
3197
+ console.error(`${ICON.err} ${c.red('plugin-install requires an HTTPS Git URL or local package path')}`);
3198
+ process.exit(1);
3199
+ }
3200
+ await runPluginInstall(options.pluginInstall);
3201
+ process.exit(0);
3202
+ }
3012
3203
  if (options.pluginList) {
3013
- await runPluginList();
3204
+ await runPluginList(options.pluginListJson);
3205
+ process.exit(0);
3206
+ }
3207
+ if (options.pluginCheck !== undefined) {
3208
+ if (!options.pluginCheck) {
3209
+ console.error(`${ICON.err} ${c.red('plugin-check requires a plugin slug')}`);
3210
+ process.exit(1);
3211
+ }
3212
+ await runPluginSourceAction(options.pluginCheck, 'check-update');
3213
+ process.exit(0);
3214
+ }
3215
+ if (options.pluginUpdate !== undefined) {
3216
+ if (!options.pluginUpdate) {
3217
+ console.error(`${ICON.err} ${c.red('plugin-update requires a plugin slug')}`);
3218
+ process.exit(1);
3219
+ }
3220
+ await runPluginSourceAction(options.pluginUpdate, 'update');
3221
+ process.exit(0);
3222
+ }
3223
+ if (options.pluginHooks) {
3224
+ await runPluginHooks(options.pluginHooks.slug, options.pluginHooks.action);
3014
3225
  process.exit(0);
3015
3226
  }
3016
3227
  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 保护)