termdock 1.4.71 → 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 (77) hide show
  1. package/README.md +36 -0
  2. package/dist/client/assets/{ModelPreview-CG6vLwyw.js → ModelPreview-i9dS1A3I.js} +1 -1
  3. package/dist/client/assets/{abnfDiagram-VRR7QNED-CrXEZLWX.js → abnfDiagram-VRR7QNED-Cs-ty-HA.js} +1 -1
  4. package/dist/client/assets/{arc-DbKGR7Ex.js → arc-CIX5k_JD.js} +1 -1
  5. package/dist/client/assets/{architectureDiagram-ZJ3FMSHR-PF5BoUbI.js → architectureDiagram-ZJ3FMSHR-DnPnQoJy.js} +1 -1
  6. package/dist/client/assets/{blockDiagram-677ZJIJ3-D1gRzVWB.js → blockDiagram-677ZJIJ3-Dhew-L5v.js} +1 -1
  7. package/dist/client/assets/{c4Diagram-LMCZKHZV-CwvOLync.js → c4Diagram-LMCZKHZV-BSsEMeWn.js} +1 -1
  8. package/dist/client/assets/channel-CASnt1pP.js +1 -0
  9. package/dist/client/assets/{chunk-2Q5K7J3B-BeRdP-xP.js → chunk-2Q5K7J3B-BWm--1Mo.js} +1 -1
  10. package/dist/client/assets/{chunk-32BRIVSS-CrqR5rvl.js → chunk-32BRIVSS-D_WFmuvb.js} +1 -1
  11. package/dist/client/assets/{chunk-5VM5RSS4-CjbUr-ko.js → chunk-5VM5RSS4-CQdzqlQg.js} +1 -1
  12. package/dist/client/assets/{chunk-EX3LRPZG-BVEk3Wma.js → chunk-EX3LRPZG-Cm3zLQpt.js} +1 -1
  13. package/dist/client/assets/{chunk-JWPE2WC7-WitJe8kR.js → chunk-JWPE2WC7-Cmcv5sEN.js} +1 -1
  14. package/dist/client/assets/{chunk-MOJQB5TN-BCewlLYk.js → chunk-MOJQB5TN-Bam44EwJ.js} +1 -1
  15. package/dist/client/assets/{chunk-RYQCIY6F-CoP0AA4G.js → chunk-RYQCIY6F-DIVOuE8y.js} +1 -1
  16. package/dist/client/assets/{chunk-V7JOEXUC-CSZrnmp_.js → chunk-V7JOEXUC-BfOTf7yg.js} +1 -1
  17. package/dist/client/assets/{chunk-VR4S4FIN-BB-7zz-9.js → chunk-VR4S4FIN-DbiXn0dd.js} +1 -1
  18. package/dist/client/assets/{chunk-XXDRQBXY-CcZZlCik.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-DcmzUDwh.js → cose-bilkent-JH36ORCC-CCoSPmoc.js} +1 -1
  22. package/dist/client/assets/{cynefin-VYW2F7L2-C1XlDVoJ.js → cynefin-VYW2F7L2-B2lYz7sl.js} +1 -1
  23. package/dist/client/assets/{cynefinDiagram-TSTJHNR4-zY6Aet2S.js → cynefinDiagram-TSTJHNR4-Cd7crN9s.js} +1 -1
  24. package/dist/client/assets/{dagre-VKFMJZFB-DqGTi6zR.js → dagre-VKFMJZFB-q9Fj9HuQ.js} +1 -1
  25. package/dist/client/assets/{diagram-FQU43EPY-DStcsU7S.js → diagram-FQU43EPY-iqHy5YHW.js} +1 -1
  26. package/dist/client/assets/{diagram-G47NLZAW-Cs6IMNip.js → diagram-G47NLZAW-BE-jlh1B.js} +1 -1
  27. package/dist/client/assets/{diagram-NH7WQ7WH-yA2bpWN7.js → diagram-NH7WQ7WH-DmP7_WOC.js} +1 -1
  28. package/dist/client/assets/{diagram-OA4YK3LP-Dzf9M0PI.js → diagram-OA4YK3LP-CHx_k8Ar.js} +1 -1
  29. package/dist/client/assets/{diagram-WEI45ONY-D75lpQKA.js → diagram-WEI45ONY-D0wImpAF.js} +1 -1
  30. package/dist/client/assets/{ebnfDiagram-CCIWWBDH-CkQvIpWy.js → ebnfDiagram-CCIWWBDH-DyCB0rF8.js} +1 -1
  31. package/dist/client/assets/{erDiagram-Q63AITRT-Ca00Vjxz.js → erDiagram-Q63AITRT-SRcd1B2Z.js} +1 -1
  32. package/dist/client/assets/{flowDiagram-23GEKE2U-CGrnSTEp.js → flowDiagram-23GEKE2U-D0i9mFwt.js} +1 -1
  33. package/dist/client/assets/{ganttDiagram-NO4QXBWP-0SHfOIP4.js → ganttDiagram-NO4QXBWP-BfwGIYDn.js} +1 -1
  34. package/dist/client/assets/{gitGraphDiagram-IHSO6WYX-BeigYWvs.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-CiY1-xjN.js → infoDiagram-FWYZ7A6U-CM-Ebv9R.js} +1 -1
  37. package/dist/client/assets/{ishikawaDiagram-FXEZZL3T-CmtDjUm_.js → ishikawaDiagram-FXEZZL3T-U-qnP7xi.js} +1 -1
  38. package/dist/client/assets/{journeyDiagram-5HDEW3XC-B7qOr9LY.js → journeyDiagram-5HDEW3XC-D4RxvsuL.js} +1 -1
  39. package/dist/client/assets/{kanban-definition-HUTT4EX6-DO2P2Um3.js → kanban-definition-HUTT4EX6-Dm_asCYK.js} +1 -1
  40. package/dist/client/assets/{linear-B832hJt0.js → linear-Zhe8d635.js} +1 -1
  41. package/dist/client/assets/{mermaid.core-CsAzdiCU.js → mermaid.core-D_NWIuMY.js} +4 -4
  42. package/dist/client/assets/{mindmap-definition-LN4V7U3C-HI__vXhQ.js → mindmap-definition-LN4V7U3C-DXpNidd5.js} +1 -1
  43. package/dist/client/assets/{pegDiagram-2B236MQR--rZIwjXq.js → pegDiagram-2B236MQR-DdOwOEgu.js} +1 -1
  44. package/dist/client/assets/{pieDiagram-ENE6RG2P-D9jPf5x9.js → pieDiagram-ENE6RG2P-D_A9HoY_.js} +1 -1
  45. package/dist/client/assets/{quadrantDiagram-ABIIQ3AL-DTCP5ji2.js → quadrantDiagram-ABIIQ3AL-D-Y3cLEF.js} +1 -1
  46. package/dist/client/assets/{railroadDiagram-RFXS5EU6-D2Gspvrl.js → railroadDiagram-RFXS5EU6-DD6eFV82.js} +1 -1
  47. package/dist/client/assets/{requirementDiagram-TGXJPOKE-DEJC02IW.js → requirementDiagram-TGXJPOKE-B59HJgL2.js} +1 -1
  48. package/dist/client/assets/{sankeyDiagram-HTMAVEWB-opOhKPap.js → sankeyDiagram-HTMAVEWB-C2BvHMH8.js} +1 -1
  49. package/dist/client/assets/{sequenceDiagram-DBY2YBRQ-_sz2CRAG.js → sequenceDiagram-DBY2YBRQ-3004tCRm.js} +1 -1
  50. package/dist/client/assets/{sizeCapture-X5ZJPWSS-D0Q2QkYF.js → sizeCapture-X5ZJPWSS-DCBhg8QW.js} +1 -1
  51. package/dist/client/assets/{stateDiagram-2N3HPSRC-VgeSQTtW.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-ClvBvSl0.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-BXNsrECV.js → timeline-definition-FHXFAJF6-wVOEmQwG.js} +1 -1
  56. package/dist/client/assets/{vennDiagram-L72KCM5P-BgwiqZd-.js → vennDiagram-L72KCM5P-DMZiSJDk.js} +1 -1
  57. package/dist/client/assets/{wardleyDiagram-EHGQE667-DiqN5ERx.js → wardleyDiagram-EHGQE667-Dx5Aob4Y.js} +1 -1
  58. package/dist/client/assets/{xychartDiagram-FW5EYKEG-DQjFVqC6.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 +3 -0
  62. package/dist/server/agent/pluginPackages.js +289 -0
  63. package/dist/server/agent/plugins.js +123 -19
  64. package/dist/server/agent/titleNamerCatalog.js +39 -8
  65. package/dist/server/cli.js +205 -14
  66. package/dist/server/entry.js +11 -2
  67. package/dist/server/routes/terminal.js +232 -29
  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,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
  };
@@ -1742,8 +1830,12 @@ function runPluginInitJson() {
1742
1830
  commands: {
1743
1831
  guide: 'td agent-plugin --json',
1744
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>',
1745
1836
  emit: 'td agent-event <slug> <event> [status]',
1746
- inspect: 'td plugin-list',
1837
+ inspect: 'td plugin-list --json',
1838
+ hooks: 'td plugin-hooks <slug> <install|uninstall>',
1747
1839
  remove: 'td plugin-remove <slug>',
1748
1840
  },
1749
1841
  ui: {
@@ -1763,6 +1855,13 @@ function runPluginInitJson() {
1763
1855
  indicator: 'Theme-safe visual indicator.',
1764
1856
  tone: 'Theme token; arbitrary status colors are not accepted.',
1765
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
+ },
1766
1865
  migrationV1: {
1767
1866
  errorCode: 'AGENT_PLUGIN_MANIFEST_V1_UNSUPPORTED',
1768
1867
  guideCommand: 'td agent-plugin --json',
@@ -1774,12 +1873,14 @@ function runPluginInitJson() {
1774
1873
  example: 'printf \'%s\' \'{"session_id":"abc","message":"Indexing"}\' | td agent-event my-agent prompt-submit thinking',
1775
1874
  },
1776
1875
  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.',
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.',
1779
1879
  'Ask the user to open Settings > Detection Rules and click Install for this Agent. They can uninstall it there later.',
1780
1880
  'If the Agent hook format is not a Claude-style hooks map, configure native hooks to call td agent-event directly.',
1781
1881
  'Map the completed-turn hook to event "stop"; this is also the automatic-title trigger when the user enables titles for the Agent.',
1782
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.',
1783
1884
  'Run td plugin-list to verify registration.',
1784
1885
  ],
1785
1886
  manifest: AGENT_PLUGIN_EXAMPLE,
@@ -1862,7 +1963,7 @@ ${c.dim('───────────────────')}
1862
1963
 
1863
1964
  ${c.dim('// ── optional: hook installation (bring your own hooks.json surface) ──')}
1864
1965
  "hooks": {
1865
- ${c.dim("// Path to the agent's hooks config file (~-abbreviated).")}
1966
+ ${c.dim("// JSON path under ~/ only; symlink traversal is rejected.")}
1866
1967
  ${c.dim('// termdock inserts marker-carrying entries; leaves user entries untouched.')}
1867
1968
  "target": "~/.trae/settings.json",
1868
1969
 
@@ -1888,7 +1989,8 @@ ${c.dim('───────────────────')}
1888
1989
 
1889
1990
  ${c.dim('// ── optional: injectable automatic-title provider ──')}
1890
1991
  "titleNamer": {
1891
- ${c.dim('// No shell expansion. {prompt} is required; a bare {model} arg is omitted when automatic.')}
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.')}
1892
1994
  "command": "trae",
1893
1995
  "args": ["title", "--prompt", "{prompt}", "--model={model}"],
1894
1996
  ${c.dim('// Must print JSON: strings or {id, displayName, description, isDefault} objects.')}
@@ -1915,11 +2017,19 @@ ${c.dim('──────────────────')}
1915
2017
  ${c.dim('# Inject the Agent definition; it appears in the UI immediately')}
1916
2018
  td plugin-create ./trae-manifest.json
1917
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
+
1918
2023
  ${c.dim('# The user then opens Settings > Detection Rules and clicks Install.')}
1919
2024
  ${c.dim('# The same UI button can uninstall the hooks later.')}
1920
2025
 
1921
2026
  ${c.dim('# Verify registration')}
1922
- 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
1923
2033
 
1924
2034
  ${c.dim('# Remove')}
1925
2035
  td plugin-remove trae
@@ -1993,7 +2103,21 @@ async function runPluginCreate(manifestPath) {
1993
2103
  console.log(` ${c.dim('Visible immediately in Settings > Detection Rules; no restart required.')}`);
1994
2104
  console.log(` ${c.dim('The user can click Install or Uninstall there.')}`);
1995
2105
  }
1996
- 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) {
1997
2121
  const { baseUrl, token } = requireRunningServer();
1998
2122
  const response = await getLocalJson(baseUrl, token, '/api/terminal/agent-plugins');
1999
2123
  const body = JSON.parse(response.body || '{}');
@@ -2001,6 +2125,10 @@ async function runPluginList() {
2001
2125
  console.error(`${ICON.err} ${c.red('Failed to list plugins')}`);
2002
2126
  process.exit(1);
2003
2127
  }
2128
+ if (json) {
2129
+ console.log(JSON.stringify(body, null, 2));
2130
+ return;
2131
+ }
2004
2132
  const plugins = body.plugins ?? [];
2005
2133
  if (plugins.length === 0) {
2006
2134
  console.log(`${c.dim('No plugins installed.')}`);
@@ -2019,7 +2147,11 @@ async function runPluginList() {
2019
2147
  flags.push('resume');
2020
2148
  if (p.iconMode !== 'mask')
2021
2149
  flags.push(p.iconMode);
2150
+ if (p.updateAvailable)
2151
+ flags.push('update');
2022
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)}` : ''}`)}`);
2023
2155
  }
2024
2156
  }
2025
2157
  const errors = body.errors ?? [];
@@ -2037,6 +2169,37 @@ async function runPluginList() {
2037
2169
  }
2038
2170
  }
2039
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
+ }
2040
2203
  async function runPluginRemove(slug) {
2041
2204
  const { baseUrl, token } = requireRunningServer();
2042
2205
  const response = await new Promise((resolve, reject) => {
@@ -3029,8 +3192,36 @@ async function main() {
3029
3192
  await runPluginCreate(options.pluginCreate);
3030
3193
  process.exit(0);
3031
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
+ }
3032
3203
  if (options.pluginList) {
3033
- 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);
3034
3225
  process.exit(0);
3035
3226
  }
3036
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 保护)