termdock 1.4.68 → 1.4.70
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.
- package/.desktop-dist/main.js +254 -49
- package/README.md +13 -0
- package/dist/client/assets/{ModelPreview-BgnWlhMf.js → ModelPreview-Cs14IQuQ.js} +1 -1
- package/dist/client/assets/{abnfDiagram-VRR7QNED-C3RDwPoE.js → abnfDiagram-VRR7QNED-DuDfdv-6.js} +1 -1
- package/dist/client/assets/{arc-n-PrqSp_.js → arc-CSYzGV_2.js} +1 -1
- package/dist/client/assets/{architectureDiagram-ZJ3FMSHR-D-l8Q2rF.js → architectureDiagram-ZJ3FMSHR-YiZ0HXNi.js} +1 -1
- package/dist/client/assets/{blockDiagram-677ZJIJ3-D5b5kvhB.js → blockDiagram-677ZJIJ3-IaQMzmYv.js} +1 -1
- package/dist/client/assets/{c4Diagram-LMCZKHZV-CsvG6hAc.js → c4Diagram-LMCZKHZV-BwyuIPRL.js} +1 -1
- package/dist/client/assets/channel-BotxtiNE.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-CcTcAD2X.js → chunk-2Q5K7J3B-BtHQYLBT.js} +1 -1
- package/dist/client/assets/{chunk-32BRIVSS-c--MtlS9.js → chunk-32BRIVSS-CXrK07sW.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-BCdLlLPD.js → chunk-5VM5RSS4-BfBshn11.js} +1 -1
- package/dist/client/assets/{chunk-EX3LRPZG-BXYeFL7Q.js → chunk-EX3LRPZG-BJbilNaf.js} +1 -1
- package/dist/client/assets/{chunk-JWPE2WC7-Cakio9E1.js → chunk-JWPE2WC7-wgcmSRlL.js} +1 -1
- package/dist/client/assets/{chunk-MOJQB5TN-CR9m_lTv.js → chunk-MOJQB5TN-8VZVUeDI.js} +1 -1
- package/dist/client/assets/{chunk-RYQCIY6F-DvPE2m9c.js → chunk-RYQCIY6F-B900YBFa.js} +1 -1
- package/dist/client/assets/{chunk-V7JOEXUC-BdHIv-k_.js → chunk-V7JOEXUC-BK4_RLz-.js} +1 -1
- package/dist/client/assets/{chunk-VR4S4FIN-CNYv3_OD.js → chunk-VR4S4FIN-Kj5_ycjB.js} +1 -1
- package/dist/client/assets/{chunk-XXDRQBXY-Ddhpk5bE.js → chunk-XXDRQBXY-BolvPLRz.js} +1 -1
- package/dist/client/assets/classDiagram-OUVF2IWQ-CLWlbTSC.js +1 -0
- package/dist/client/assets/classDiagram-v2-EOCWNBFH-CLWlbTSC.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-vc2IL5Qo.js → cose-bilkent-JH36ORCC-9_9_SLqv.js} +1 -1
- package/dist/client/assets/{cynefin-VYW2F7L2-Cr5U2Llm.js → cynefin-VYW2F7L2-yEpoi3vG.js} +1 -1
- package/dist/client/assets/{cynefinDiagram-TSTJHNR4-DaSVhxBB.js → cynefinDiagram-TSTJHNR4-Dy-gvup_.js} +1 -1
- package/dist/client/assets/{dagre-VKFMJZFB-B8Ao-OKf.js → dagre-VKFMJZFB-CNjJ5pRK.js} +1 -1
- package/dist/client/assets/{diagram-FQU43EPY-a-sl4C9b.js → diagram-FQU43EPY-uZ73Rj3T.js} +1 -1
- package/dist/client/assets/{diagram-G47NLZAW-CmezqIfj.js → diagram-G47NLZAW-Cxt7TYZT.js} +1 -1
- package/dist/client/assets/{diagram-NH7WQ7WH-rM4l3wBG.js → diagram-NH7WQ7WH-B4TKinuZ.js} +1 -1
- package/dist/client/assets/{diagram-OA4YK3LP-DS3rHIQ7.js → diagram-OA4YK3LP-CGSuenHU.js} +1 -1
- package/dist/client/assets/{diagram-WEI45ONY-BUyvO5qF.js → diagram-WEI45ONY-DqqzDaUZ.js} +1 -1
- package/dist/client/assets/{ebnfDiagram-CCIWWBDH-C6KPNqQ0.js → ebnfDiagram-CCIWWBDH-B2SgXL5w.js} +1 -1
- package/dist/client/assets/{erDiagram-Q63AITRT-u9pQXlID.js → erDiagram-Q63AITRT-D-ibkr8y.js} +1 -1
- package/dist/client/assets/{flowDiagram-23GEKE2U-h9QkExto.js → flowDiagram-23GEKE2U-BPeyMqr-.js} +1 -1
- package/dist/client/assets/{ganttDiagram-NO4QXBWP-C9AXkH3Y.js → ganttDiagram-NO4QXBWP-DcEqEzzu.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-IHSO6WYX-CXYfDg8b.js → gitGraphDiagram-IHSO6WYX-DTZ6UCQY.js} +1 -1
- package/dist/client/assets/index-DhfIiBLW.css +1 -0
- package/dist/client/assets/index-ivnGnbVg.js +451 -0
- package/dist/client/assets/{infoDiagram-FWYZ7A6U-B_Kb0Chj.js → infoDiagram-FWYZ7A6U-BHrzVXeX.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-FXEZZL3T-B2kT_9-T.js → ishikawaDiagram-FXEZZL3T-CwuoQJku.js} +1 -1
- package/dist/client/assets/{journeyDiagram-5HDEW3XC-DdOISfLz.js → journeyDiagram-5HDEW3XC-ZEcy0rDZ.js} +1 -1
- package/dist/client/assets/{kanban-definition-HUTT4EX6-CdIIjjXO.js → kanban-definition-HUTT4EX6-BDBawNbr.js} +1 -1
- package/dist/client/assets/{linear-CWpzI-ns.js → linear-smYQWzDV.js} +1 -1
- package/dist/client/assets/{mermaid.core-BLHPinUj.js → mermaid.core-eJs7Ouvw.js} +4 -4
- package/dist/client/assets/{mindmap-definition-LN4V7U3C-qFnTyqQA.js → mindmap-definition-LN4V7U3C-BK1IZ9zg.js} +1 -1
- package/dist/client/assets/{pegDiagram-2B236MQR-Bu7ijeZf.js → pegDiagram-2B236MQR-B3-mVNgX.js} +1 -1
- package/dist/client/assets/{pieDiagram-ENE6RG2P-BygAm24V.js → pieDiagram-ENE6RG2P-Bkeeyx5l.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-ABIIQ3AL-3-9rkPqr.js → quadrantDiagram-ABIIQ3AL-DouIklEO.js} +1 -1
- package/dist/client/assets/{railroadDiagram-RFXS5EU6-BAsvFjIm.js → railroadDiagram-RFXS5EU6-BB_jXQd2.js} +1 -1
- package/dist/client/assets/{requirementDiagram-TGXJPOKE-H-BySumI.js → requirementDiagram-TGXJPOKE-u56IHfiR.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-HTMAVEWB-D2PDv2gA.js → sankeyDiagram-HTMAVEWB-eDi2cs5E.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-DBY2YBRQ-C9pvFRUG.js → sequenceDiagram-DBY2YBRQ-DnAA2xYa.js} +1 -1
- package/dist/client/assets/{sizeCapture-X5ZJPWSS-Cx0TrKvi.js → sizeCapture-X5ZJPWSS-BogPui79.js} +1 -1
- package/dist/client/assets/{stateDiagram-2N3HPSRC-Ciytmucj.js → stateDiagram-2N3HPSRC--EeXDy2M.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-6OUMAXLB-Dc-3HKTs.js +1 -0
- package/dist/client/assets/{swimlanes-5IMT3BWC-Dwk151xG.js → swimlanes-5IMT3BWC-DZAw7-kY.js} +2 -2
- package/dist/client/assets/swimlanesDiagram-G3AALYLV-CI1Wlr0V.js +8 -0
- package/dist/client/assets/{timeline-definition-FHXFAJF6-Bym0jj2o.js → timeline-definition-FHXFAJF6-DkcnpB-Y.js} +1 -1
- package/dist/client/assets/{vennDiagram-L72KCM5P-CToPJelW.js → vennDiagram-L72KCM5P-BStcDGEf.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-EHGQE667-BZ6V3CA9.js → wardleyDiagram-EHGQE667-Cj4CQn1M.js} +1 -1
- package/dist/client/assets/{xychartDiagram-FW5EYKEG-D5NzFpxi.js → xychartDiagram-FW5EYKEG-_uBCjai8.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/sw.js +1 -1
- package/dist/server/agent/autoTitle.js +118 -0
- package/dist/server/agent/installers.js +11 -10
- package/dist/server/agent/plugins.js +90 -4
- package/dist/server/agent/registry.js +1 -0
- package/dist/server/agent/session.js +18 -2
- package/dist/server/agent/titleNamerCatalog.js +203 -0
- package/dist/server/agentHook.js +2 -1
- package/dist/server/cli.js +255 -30
- package/dist/server/routes/filesystem.js +11 -8
- package/dist/server/routes/terminal.js +351 -10
- package/dist/server/utils/autoUpdate.js +175 -0
- package/dist/server/utils/npmUpdate.js +139 -0
- package/dist/server/utils/settings.js +44 -0
- package/package.json +1 -1
- package/runtime-manifest.json +2 -2
- package/dist/client/assets/channel-DqH6WWOA.js +0 -1
- package/dist/client/assets/classDiagram-OUVF2IWQ-DbsEzwLQ.js +0 -1
- package/dist/client/assets/classDiagram-v2-EOCWNBFH-DbsEzwLQ.js +0 -1
- package/dist/client/assets/index-ChhyERKX.css +0 -1
- package/dist/client/assets/index-DVo7u2vf.js +0 -442
- package/dist/client/assets/stateDiagram-v2-6OUMAXLB-C9GGeb7o.js +0 -1
- package/dist/client/assets/swimlanesDiagram-G3AALYLV-BRRLZYN8.js +0 -8
package/dist/server/cli.js
CHANGED
|
@@ -16,14 +16,16 @@ import https from 'https';
|
|
|
16
16
|
import path from 'path';
|
|
17
17
|
import readline from 'readline';
|
|
18
18
|
import { createHash, randomUUID } from 'crypto';
|
|
19
|
-
import { spawn, execFile } from 'child_process';
|
|
19
|
+
import { spawn, spawnSync, execFile } from 'child_process';
|
|
20
20
|
import { promisify } from 'util';
|
|
21
21
|
import { Writable } from 'stream';
|
|
22
22
|
import { createRequire } from 'module';
|
|
23
|
+
import { fileURLToPath } from 'url';
|
|
23
24
|
import { PORT, DEFAULT_HOST, TMUX } from './config.js';
|
|
24
25
|
import { isFirstRunCompleted, markFirstRunCompleted, normalizeLocalAccessName, setLocalAccessSetting, getLocalAccessSetting, getPreventSleepSetting } from './utils/settings.js';
|
|
25
26
|
import { runBootChecks, formatBootCheckReport } from './utils/bootCheck.js';
|
|
26
27
|
import { localAccessManager, getLanIPv4Addresses } from './utils/localAccess.js';
|
|
28
|
+
import { OFFICIAL_NPM_REGISTRY, updateTermdockFromOfficialRegistry } from './utils/npmUpdate.js';
|
|
27
29
|
import { clearAuthFile, destroyAllSessions, hashPassword, isAuthEnabled, isEnvPasswordSet, writeAuthFile, } from './utils/authProtection.js';
|
|
28
30
|
const execFileAsync = promisify(execFile);
|
|
29
31
|
const TERMDOCK_VERSION = (() => {
|
|
@@ -118,13 +120,15 @@ Options:
|
|
|
118
120
|
--foreground Run in the foreground
|
|
119
121
|
--status Show background server status
|
|
120
122
|
--stop Stop the background server
|
|
123
|
+
--update Update the global CLI from the official npm registry
|
|
121
124
|
-v, --version Print version and exit
|
|
122
125
|
--set-password Set or update the access password (interactive prompt)
|
|
123
126
|
--clear-password Remove the access password and disable authentication
|
|
124
127
|
--tls List termdock-managed tmux sessions (reads tmux directly,
|
|
125
128
|
no server connection required)
|
|
126
129
|
-a, --all With --tls/--attach-tmux: include tmux sessions not stamped by termdock
|
|
127
|
-
--json With --tls: emit JSON
|
|
130
|
+
--json With --tls: emit session JSON; with --plugin-init:
|
|
131
|
+
emit the machine-readable Agent integration protocol
|
|
128
132
|
--attach-tmux [n] Attach to a termdock-managed tmux session.
|
|
129
133
|
With no name: interactive picker.
|
|
130
134
|
With name: attach directly (e.g. --attach-tmux wt-foo).
|
|
@@ -152,7 +156,7 @@ Options:
|
|
|
152
156
|
--change-audit-explain <id> [cwd]
|
|
153
157
|
Read a natural-language explanation from stdin and inject
|
|
154
158
|
it for the specified Termdock hunk ID.
|
|
155
|
-
--plugin-init Print the
|
|
159
|
+
--plugin-init Print the Agent integration protocol reference
|
|
156
160
|
--plugin-create <file>
|
|
157
161
|
Create a plugin from a manifest JSON file
|
|
158
162
|
--plugin-list List installed agent plugins
|
|
@@ -161,7 +165,12 @@ Options:
|
|
|
161
165
|
-h, --help Show this help message
|
|
162
166
|
|
|
163
167
|
Short commands:
|
|
164
|
-
|
|
168
|
+
agent-plugin [--json]
|
|
169
|
+
Explain how an Agent integrates itself. JSON mode is
|
|
170
|
+
machine-readable and intended for Agent consumption.
|
|
171
|
+
agent-event <slug> <event> [status]
|
|
172
|
+
Emit one lifecycle/status event from an Agent hook.
|
|
173
|
+
pi Same as agent-plugin
|
|
165
174
|
plugin-create <file>
|
|
166
175
|
Same as --plugin-create <file>
|
|
167
176
|
plugin-list Same as --plugin-list
|
|
@@ -171,6 +180,8 @@ Short commands:
|
|
|
171
180
|
st Same as --status
|
|
172
181
|
x Same as --stop
|
|
173
182
|
stop Same as --stop
|
|
183
|
+
update Update the global CLI from the official npm registry
|
|
184
|
+
upgrade Same as update
|
|
174
185
|
l Same as --tls
|
|
175
186
|
ls Same as --tls
|
|
176
187
|
la Same as --tls --all
|
|
@@ -481,6 +492,7 @@ function parseArgs(argv) {
|
|
|
481
492
|
let foreground = false;
|
|
482
493
|
let status = false;
|
|
483
494
|
let stop = false;
|
|
495
|
+
let update = false;
|
|
484
496
|
let setPassword = false;
|
|
485
497
|
let clearPassword = false;
|
|
486
498
|
let tls = false;
|
|
@@ -499,9 +511,11 @@ function parseArgs(argv) {
|
|
|
499
511
|
let changeAuditShow;
|
|
500
512
|
let injectChangeAuditHunk;
|
|
501
513
|
let pluginInit = false;
|
|
514
|
+
let pluginInitJson = false;
|
|
502
515
|
let pluginCreate;
|
|
503
516
|
let pluginList = false;
|
|
504
517
|
let pluginRemove;
|
|
518
|
+
let agentEvent;
|
|
505
519
|
// Short command aliases for the common path. Keep these positional-only so
|
|
506
520
|
// long-form flags remain the single source of truth for option semantics.
|
|
507
521
|
if (argv[0] && !argv[0].startsWith('-')) {
|
|
@@ -524,6 +538,10 @@ function parseArgs(argv) {
|
|
|
524
538
|
stop = true;
|
|
525
539
|
argv = argv.slice(1);
|
|
526
540
|
}
|
|
541
|
+
else if (command === 'update' || command === 'upgrade') {
|
|
542
|
+
update = true;
|
|
543
|
+
argv = argv.slice(1);
|
|
544
|
+
}
|
|
527
545
|
else if (command === 'p' || command === 'pw') {
|
|
528
546
|
setPassword = true;
|
|
529
547
|
argv = argv.slice(1);
|
|
@@ -597,9 +615,22 @@ function parseArgs(argv) {
|
|
|
597
615
|
injectChangeAuditHunk = { id: next, cwd };
|
|
598
616
|
argv = argv.slice(cwd ? 3 : 2);
|
|
599
617
|
}
|
|
600
|
-
else if (command === 'pi' || command === 'plugin-init') {
|
|
618
|
+
else if (command === 'pi' || command === 'plugin-init' || command === 'agent-plugin') {
|
|
601
619
|
pluginInit = true;
|
|
602
|
-
|
|
620
|
+
pluginInitJson = next === '--json';
|
|
621
|
+
argv = argv.slice(pluginInitJson ? 2 : 1);
|
|
622
|
+
}
|
|
623
|
+
else if (command === 'agent-event') {
|
|
624
|
+
const event = argv[2];
|
|
625
|
+
const customStatus = argv[3];
|
|
626
|
+
if (next && event && !next.startsWith('-') && !event.startsWith('-')) {
|
|
627
|
+
agentEvent = { slug: next, event, status: customStatus && !customStatus.startsWith('-') ? customStatus : undefined };
|
|
628
|
+
argv = argv.slice(agentEvent.status ? 4 : 3);
|
|
629
|
+
}
|
|
630
|
+
else {
|
|
631
|
+
console.error(`${ICON.err} ${c.red('Usage: td agent-event <slug> <event> [status]')}`);
|
|
632
|
+
process.exit(1);
|
|
633
|
+
}
|
|
603
634
|
}
|
|
604
635
|
else if (command === 'pcreate' || command === 'plugin-create') {
|
|
605
636
|
if (next && !next.startsWith('-')) {
|
|
@@ -683,6 +714,10 @@ function parseArgs(argv) {
|
|
|
683
714
|
stop = true;
|
|
684
715
|
continue;
|
|
685
716
|
}
|
|
717
|
+
if (arg === '--update') {
|
|
718
|
+
update = true;
|
|
719
|
+
continue;
|
|
720
|
+
}
|
|
686
721
|
if (arg === '--set-password') {
|
|
687
722
|
setPassword = true;
|
|
688
723
|
continue;
|
|
@@ -700,7 +735,10 @@ function parseArgs(argv) {
|
|
|
700
735
|
continue;
|
|
701
736
|
}
|
|
702
737
|
if (arg === '--json') {
|
|
703
|
-
|
|
738
|
+
if (pluginInit)
|
|
739
|
+
pluginInitJson = true;
|
|
740
|
+
else
|
|
741
|
+
tlsJson = true;
|
|
704
742
|
continue;
|
|
705
743
|
}
|
|
706
744
|
if (arg === '--attach-tmux') {
|
|
@@ -855,6 +893,7 @@ function parseArgs(argv) {
|
|
|
855
893
|
foreground,
|
|
856
894
|
status,
|
|
857
895
|
stop,
|
|
896
|
+
update,
|
|
858
897
|
setPassword,
|
|
859
898
|
clearPassword,
|
|
860
899
|
tls,
|
|
@@ -873,9 +912,11 @@ function parseArgs(argv) {
|
|
|
873
912
|
changeAuditShow,
|
|
874
913
|
injectChangeAuditHunk,
|
|
875
914
|
pluginInit,
|
|
915
|
+
pluginInitJson,
|
|
876
916
|
pluginCreate,
|
|
877
917
|
pluginList,
|
|
878
918
|
pluginRemove,
|
|
919
|
+
agentEvent,
|
|
879
920
|
};
|
|
880
921
|
}
|
|
881
922
|
// Reads a single line from stdin without echoing keystrokes. Used for password
|
|
@@ -1650,10 +1691,124 @@ function requireRunningServer() {
|
|
|
1650
1691
|
}
|
|
1651
1692
|
return { baseUrl: resolveServerBaseUrl(state), token };
|
|
1652
1693
|
}
|
|
1694
|
+
const AGENT_EVENT_KINDS = [
|
|
1695
|
+
'session-start',
|
|
1696
|
+
'prompt-submit',
|
|
1697
|
+
'permission-request',
|
|
1698
|
+
'question-asked',
|
|
1699
|
+
'tool-complete',
|
|
1700
|
+
'notification',
|
|
1701
|
+
'stop',
|
|
1702
|
+
'session-end',
|
|
1703
|
+
];
|
|
1704
|
+
const AGENT_PLUGIN_EXAMPLE = {
|
|
1705
|
+
version: 2,
|
|
1706
|
+
slug: 'my-agent',
|
|
1707
|
+
displayName: 'My Agent',
|
|
1708
|
+
aliases: ['my-agent'],
|
|
1709
|
+
accentColor: '#4385BE',
|
|
1710
|
+
iconMode: 'mask',
|
|
1711
|
+
statuses: [
|
|
1712
|
+
{ id: 'thinking', phase: 'working', label: 'Thinking', indicator: 'spinner', tone: 'info' },
|
|
1713
|
+
{ id: 'approval', phase: 'waiting', label: 'Needs approval', indicator: 'question', tone: 'warning' },
|
|
1714
|
+
{ id: 'complete', phase: 'done', label: 'Complete', indicator: 'badge', tone: 'success' },
|
|
1715
|
+
],
|
|
1716
|
+
hooks: {
|
|
1717
|
+
target: '~/.my-agent/settings.json',
|
|
1718
|
+
events: [
|
|
1719
|
+
{ hook: 'UserPromptSubmit', event: 'prompt-submit', status: 'thinking' },
|
|
1720
|
+
{ hook: 'PermissionRequest', event: 'permission-request', status: 'approval' },
|
|
1721
|
+
{ hook: 'Stop', event: 'stop', status: 'complete' },
|
|
1722
|
+
],
|
|
1723
|
+
},
|
|
1724
|
+
resume: {
|
|
1725
|
+
command: 'my-agent --resume {sessionId}',
|
|
1726
|
+
staleFlags: ['--resume'],
|
|
1727
|
+
},
|
|
1728
|
+
};
|
|
1729
|
+
function runPluginInitJson() {
|
|
1730
|
+
console.log(JSON.stringify({
|
|
1731
|
+
protocol: 'termdock-agent-plugin',
|
|
1732
|
+
version: 2,
|
|
1733
|
+
purpose: 'Register this Agent with Termdock and publish lifecycle/status updates.',
|
|
1734
|
+
commands: {
|
|
1735
|
+
guide: 'td agent-plugin --json',
|
|
1736
|
+
register: 'td plugin-create ./manifest.json',
|
|
1737
|
+
emit: 'td agent-event <slug> <event> [status]',
|
|
1738
|
+
inspect: 'td plugin-list',
|
|
1739
|
+
remove: 'td plugin-remove <slug>',
|
|
1740
|
+
},
|
|
1741
|
+
ui: {
|
|
1742
|
+
location: 'Settings > Detection Rules',
|
|
1743
|
+
registration: 'A successfully created plugin appears immediately without restarting Termdock.',
|
|
1744
|
+
install: 'The user clicks Install to write the declared hook mappings into hooks.target.',
|
|
1745
|
+
uninstall: 'The user clicks Uninstall to remove only Termdock-owned hook entries.',
|
|
1746
|
+
},
|
|
1747
|
+
lifecycleEvents: AGENT_EVENT_KINDS,
|
|
1748
|
+
phases: ['idle', 'working', 'waiting', 'done'],
|
|
1749
|
+
indicators: ['spinner', 'pulse', 'dot', 'ring', 'badge', 'terminal', 'question'],
|
|
1750
|
+
tones: ['neutral', 'info', 'success', 'warning', 'danger', 'accent'],
|
|
1751
|
+
statusModel: {
|
|
1752
|
+
phase: 'Stable Termdock behavior used for notifications and attention.',
|
|
1753
|
+
id: 'Agent-defined fine-grained status referenced by hook mappings or agent-event.',
|
|
1754
|
+
label: 'Human-readable status text.',
|
|
1755
|
+
indicator: 'Theme-safe visual indicator.',
|
|
1756
|
+
tone: 'Theme token; arbitrary status colors are not accepted.',
|
|
1757
|
+
},
|
|
1758
|
+
migrationV1: {
|
|
1759
|
+
errorCode: 'AGENT_PLUGIN_MANIFEST_V1_UNSUPPORTED',
|
|
1760
|
+
guideCommand: 'td agent-plugin --json',
|
|
1761
|
+
aiPrompt: 'Please migrate the attached Termdock Agent plugin manifest from v1 to v2. Preserve slug, displayName, aliases, accentColor, iconMode, hooks.target, and resume. Add statuses[] entries with id, phase (idle|working|waiting|done), label, indicator, and tone. Add a status reference to each relevant hooks.events[] mapping while keeping its core event value. Use the output of `td agent-plugin --json` as the authoritative schema. Return only the corrected manifest JSON.',
|
|
1762
|
+
},
|
|
1763
|
+
hookPayload: {
|
|
1764
|
+
stdin: 'Optional JSON object.',
|
|
1765
|
+
recognizedFields: ['session_id', 'sessionId', 'message', 'cwd'],
|
|
1766
|
+
example: 'printf \'%s\' \'{"session_id":"abc","message":"Indexing"}\' | td agent-event my-agent prompt-submit thinking',
|
|
1767
|
+
},
|
|
1768
|
+
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.',
|
|
1771
|
+
'Ask the user to open Settings > Detection Rules and click Install for this Agent. They can uninstall it there later.',
|
|
1772
|
+
'If the Agent hook format is not a Claude-style hooks map, configure native hooks to call td agent-event directly.',
|
|
1773
|
+
'Run td plugin-list to verify registration.',
|
|
1774
|
+
],
|
|
1775
|
+
manifest: AGENT_PLUGIN_EXAMPLE,
|
|
1776
|
+
}, null, 2));
|
|
1777
|
+
}
|
|
1778
|
+
function runAgentEvent(input) {
|
|
1779
|
+
const slugPattern = /^[a-z][a-z0-9-]{0,39}$/;
|
|
1780
|
+
if (!slugPattern.test(input.slug)) {
|
|
1781
|
+
console.error(`${ICON.err} ${c.red('agent-event slug must be lowercase alphanumeric with hyphens')}`);
|
|
1782
|
+
process.exit(1);
|
|
1783
|
+
}
|
|
1784
|
+
if (!AGENT_EVENT_KINDS.includes(input.event)) {
|
|
1785
|
+
console.error(`${ICON.err} ${c.red(`Unknown agent event: ${input.event}`)}`);
|
|
1786
|
+
console.error(` ${c.dim(`Expected one of: ${AGENT_EVENT_KINDS.join(', ')}`)}`);
|
|
1787
|
+
process.exit(1);
|
|
1788
|
+
}
|
|
1789
|
+
if (input.status && !slugPattern.test(input.status)) {
|
|
1790
|
+
console.error(`${ICON.err} ${c.red('agent-event status must be lowercase alphanumeric with hyphens')}`);
|
|
1791
|
+
process.exit(1);
|
|
1792
|
+
}
|
|
1793
|
+
const script = fileURLToPath(new URL('./agentHook.js', import.meta.url));
|
|
1794
|
+
const result = spawnSync(process.execPath, [script, 'agent-hook', input.slug, input.event, ...(input.status ? [input.status] : [])], {
|
|
1795
|
+
stdio: 'inherit',
|
|
1796
|
+
env: process.env,
|
|
1797
|
+
});
|
|
1798
|
+
if (result.error) {
|
|
1799
|
+
console.error(`${ICON.err} ${c.red(`Failed to emit Agent event: ${result.error.message}`)}`);
|
|
1800
|
+
process.exit(1);
|
|
1801
|
+
}
|
|
1802
|
+
process.exit(result.status ?? 0);
|
|
1803
|
+
}
|
|
1653
1804
|
function runPluginInit() {
|
|
1654
1805
|
console.log(`\
|
|
1655
|
-
${c.bold('Agent
|
|
1656
|
-
${c.dim('
|
|
1806
|
+
${c.bold('Termdock Agent Integration Protocol v2')}
|
|
1807
|
+
${c.dim('──────────────────────────────────────')}
|
|
1808
|
+
|
|
1809
|
+
Machine-readable form for an Agent to inspect itself:
|
|
1810
|
+
|
|
1811
|
+
${c.cyan('td agent-plugin --json')}
|
|
1657
1812
|
|
|
1658
1813
|
A plugin is a directory under ${c.cyan('~/.termdock/agent-plugins/<slug>/')} containing:
|
|
1659
1814
|
|
|
@@ -1664,8 +1819,8 @@ ${c.bold('manifest.json schema')}
|
|
|
1664
1819
|
${c.dim('───────────────────')}
|
|
1665
1820
|
|
|
1666
1821
|
{
|
|
1667
|
-
${c.dim('// Manifest version. Must be')} ${c.green('
|
|
1668
|
-
"version":
|
|
1822
|
+
${c.dim('// Manifest version. Must be')} ${c.green('2')}${c.dim('.')}
|
|
1823
|
+
"version": 2,
|
|
1669
1824
|
|
|
1670
1825
|
${c.dim('// Unique slug (lowercase alphanum + hyphens, max 40 chars).')}
|
|
1671
1826
|
${c.dim('// Must not collide with built-in agent slugs.')}
|
|
@@ -1688,6 +1843,13 @@ ${c.dim('───────────────────')}
|
|
|
1688
1843
|
${c.dim('// "native" - SVG rendered directly, preserving original fills/gradients.')}
|
|
1689
1844
|
"iconMode": "native",
|
|
1690
1845
|
|
|
1846
|
+
${c.dim('// Optional fine-grained states. phase drives behavior; the rest drives UI.')}
|
|
1847
|
+
"statuses": [
|
|
1848
|
+
{ "id": "thinking", "phase": "working", "label": "Thinking", "indicator": "spinner", "tone": "info" },
|
|
1849
|
+
{ "id": "approval", "phase": "waiting", "label": "Needs approval", "indicator": "question", "tone": "warning" },
|
|
1850
|
+
{ "id": "complete", "phase": "done", "label": "Complete", "indicator": "badge", "tone": "success" }
|
|
1851
|
+
],
|
|
1852
|
+
|
|
1691
1853
|
${c.dim('// ── optional: hook installation (bring your own hooks.json surface) ──')}
|
|
1692
1854
|
"hooks": {
|
|
1693
1855
|
${c.dim("// Path to the agent's hooks config file (~-abbreviated).")}
|
|
@@ -1698,10 +1860,10 @@ ${c.dim('───────────────────')}
|
|
|
1698
1860
|
${c.dim('// Optional matcher narrows the subscription (e.g. permission type).')}
|
|
1699
1861
|
"events": [
|
|
1700
1862
|
{ "hook": "SessionStart", "event": "session-start" },
|
|
1701
|
-
{ "hook": "UserPromptSubmit", "event": "prompt-submit" },
|
|
1702
|
-
{ "hook": "Notification", "event": "notification", "matcher": "elicitation_dialog" },
|
|
1863
|
+
{ "hook": "UserPromptSubmit", "event": "prompt-submit", "status": "thinking" },
|
|
1864
|
+
{ "hook": "Notification", "event": "notification", "status": "approval", "matcher": "elicitation_dialog" },
|
|
1703
1865
|
{ "hook": "PostToolUse", "event": "tool-complete" },
|
|
1704
|
-
{ "hook": "Stop", "event": "stop" },
|
|
1866
|
+
{ "hook": "Stop", "event": "stop", "status": "complete" },
|
|
1705
1867
|
{ "hook": "SessionEnd", "event": "session-end" }
|
|
1706
1868
|
]
|
|
1707
1869
|
},
|
|
@@ -1730,28 +1892,48 @@ ${c.dim('─────────────────────')}
|
|
|
1730
1892
|
${c.bold('Creating a plugin')}
|
|
1731
1893
|
${c.dim('──────────────────')}
|
|
1732
1894
|
|
|
1733
|
-
#
|
|
1734
|
-
mkdir -p ~/.termdock/agent-plugins/trae
|
|
1735
|
-
cp manifest.json icon.svg ~/.termdock/agent-plugins/trae/
|
|
1736
|
-
|
|
1737
|
-
${c.dim('# 2. Install the plugin via CLI (needs running server)')}
|
|
1738
|
-
td plugin-create ~/.termdock/agent-plugins/trae/manifest.json
|
|
1739
|
-
|
|
1740
|
-
${c.dim('# 3. Or create directly from the manifest JSON')}
|
|
1895
|
+
${c.dim('# Inject the Agent definition; it appears in the UI immediately')}
|
|
1741
1896
|
td plugin-create ./trae-manifest.json
|
|
1742
1897
|
|
|
1743
|
-
${c.dim('#
|
|
1898
|
+
${c.dim('# The user then opens Settings > Detection Rules and clicks Install.')}
|
|
1899
|
+
${c.dim('# The same UI button can uninstall the hooks later.')}
|
|
1900
|
+
|
|
1901
|
+
${c.dim('# Verify registration')}
|
|
1744
1902
|
td plugin-list
|
|
1745
1903
|
|
|
1746
|
-
${c.dim('#
|
|
1904
|
+
${c.dim('# Remove')}
|
|
1747
1905
|
td plugin-remove trae
|
|
1748
1906
|
|
|
1907
|
+
${c.bold('Direct event bridge for any hook system')}
|
|
1908
|
+
${c.dim('───────────────────────────────────────')}
|
|
1909
|
+
|
|
1910
|
+
If the Agent does not use a Claude-style hooks JSON file, configure its
|
|
1911
|
+
native hooks to invoke this stable public CLI command instead:
|
|
1912
|
+
|
|
1913
|
+
td agent-event trae prompt-submit thinking
|
|
1914
|
+
td agent-event trae permission-request approval
|
|
1915
|
+
td agent-event trae stop complete
|
|
1916
|
+
|
|
1917
|
+
Hook JSON may be piped on stdin. Termdock recognizes session_id/sessionId,
|
|
1918
|
+
message, and cwd:
|
|
1919
|
+
|
|
1920
|
+
printf '%s' '{"session_id":"abc","message":"Indexing"}' | td agent-event trae prompt-submit thinking
|
|
1921
|
+
|
|
1749
1922
|
${c.bold('Manifest validation at startup')}
|
|
1750
1923
|
${c.dim('───────────────────────────────')}
|
|
1751
1924
|
|
|
1752
|
-
Termdock validates every plugin manifest
|
|
1925
|
+
Termdock validates every plugin manifest before registration. Errors are logged
|
|
1753
1926
|
to ${c.cyan('~/.termdock/server.log')} and visible via ${c.cyan('td plugin-list')}${c.dim('.')}
|
|
1754
1927
|
Slugs/aliases that collide with built-in agents are skipped.
|
|
1928
|
+
|
|
1929
|
+
${c.bold('Migrating a v1 plugin')}
|
|
1930
|
+
${c.dim('─────────────────────')}
|
|
1931
|
+
|
|
1932
|
+
v1 manifests fail with ${c.cyan('AGENT_PLUGIN_MANIFEST_V1_UNSUPPORTED')} and an
|
|
1933
|
+
AI-ready repair prompt. Copy that prompt together with the old manifest to
|
|
1934
|
+
an AI, or let the AI inspect the current schema directly:
|
|
1935
|
+
|
|
1936
|
+
${c.cyan('td agent-plugin --json')}
|
|
1755
1937
|
`);
|
|
1756
1938
|
}
|
|
1757
1939
|
async function runPluginCreate(manifestPath) {
|
|
@@ -1776,12 +1958,20 @@ async function runPluginCreate(manifestPath) {
|
|
|
1776
1958
|
const response = await postLocalJson(baseUrl, token, '/api/terminal/agent-plugins', manifest);
|
|
1777
1959
|
const body = JSON.parse(response.body || '{}');
|
|
1778
1960
|
if (response.statusCode < 200 || response.statusCode >= 300) {
|
|
1779
|
-
console.error(`${ICON.err} ${c.red(body.
|
|
1961
|
+
console.error(`${ICON.err} ${c.red(body.code ? `${body.code}:` : 'Plugin validation failed:')}`);
|
|
1962
|
+
console.error(body.error || 'Failed to create plugin');
|
|
1963
|
+
if (body.migration?.guideCommand) {
|
|
1964
|
+
console.error(`\n${c.bold('Current schema:')} ${c.cyan(body.migration.guideCommand)}`);
|
|
1965
|
+
}
|
|
1966
|
+
if (body.migration?.aiPrompt) {
|
|
1967
|
+
console.error(`\n${c.bold('Copy this to your AI:')}\n${body.migration.aiPrompt}`);
|
|
1968
|
+
}
|
|
1780
1969
|
process.exit(1);
|
|
1781
1970
|
}
|
|
1782
1971
|
console.log(`${ICON.ok} ${c.green(`Plugin "${body.slug}" created.`)}`);
|
|
1783
1972
|
console.log(` ${c.dim('Directory:')} ${c.cyan(body.dir ?? resolved)}`);
|
|
1784
|
-
console.log(` ${c.dim('
|
|
1973
|
+
console.log(` ${c.dim('Visible immediately in Settings > Detection Rules; no restart required.')}`);
|
|
1974
|
+
console.log(` ${c.dim('The user can click Install or Uninstall there.')}`);
|
|
1785
1975
|
}
|
|
1786
1976
|
async function runPluginList() {
|
|
1787
1977
|
const { baseUrl, token } = requireRunningServer();
|
|
@@ -1794,7 +1984,7 @@ async function runPluginList() {
|
|
|
1794
1984
|
const plugins = body.plugins ?? [];
|
|
1795
1985
|
if (plugins.length === 0) {
|
|
1796
1986
|
console.log(`${c.dim('No plugins installed.')}`);
|
|
1797
|
-
console.log(` ${c.dim('See:')} ${c.cyan('td plugin
|
|
1987
|
+
console.log(` ${c.dim('See:')} ${c.cyan('td agent-plugin')}`);
|
|
1798
1988
|
}
|
|
1799
1989
|
else {
|
|
1800
1990
|
console.log(`${c.bold('Installed plugins')} ${c.dim(`(${plugins.length})`)}`);
|
|
@@ -1817,7 +2007,13 @@ async function runPluginList() {
|
|
|
1817
2007
|
console.log('');
|
|
1818
2008
|
console.log(`${c.yellow('Validation errors')}`);
|
|
1819
2009
|
for (const e of errors) {
|
|
1820
|
-
console.log(` ${c.red('✗')} ${c.cyan(e.slug)}${c.dim(':')} ${e.errors.join('; ')}`);
|
|
2010
|
+
console.log(` ${c.red('✗')} ${c.cyan(e.slug)}${c.dim(':')} ${e.code ? c.yellow(e.code) : e.errors.join('; ')}`);
|
|
2011
|
+
for (const message of e.errors)
|
|
2012
|
+
console.log(` ${message}`);
|
|
2013
|
+
if (e.migration?.guideCommand)
|
|
2014
|
+
console.log(` ${c.dim('Current schema:')} ${c.cyan(e.migration.guideCommand)}`);
|
|
2015
|
+
if (e.migration?.aiPrompt)
|
|
2016
|
+
console.log(` ${c.dim('Copy this to your AI:')} ${e.migration.aiPrompt}`);
|
|
1821
2017
|
}
|
|
1822
2018
|
}
|
|
1823
2019
|
}
|
|
@@ -2714,6 +2910,29 @@ async function refreshDefaultHttpsCertificateSafely() {
|
|
|
2714
2910
|
}
|
|
2715
2911
|
}
|
|
2716
2912
|
async function main() {
|
|
2913
|
+
if (options.update) {
|
|
2914
|
+
console.log(`${ICON.info} ${c.dim(`Checking ${OFFICIAL_NPM_REGISTRY} for updates...`)}`);
|
|
2915
|
+
const result = await updateTermdockFromOfficialRegistry(TERMDOCK_VERSION, (stage, error) => {
|
|
2916
|
+
const action = stage === 'query' ? 'query' : 'installation';
|
|
2917
|
+
console.warn(`${ICON.warn} ${c.yellow(`Official npm registry ${action} failed; retrying with your configured npm registry.`)}`);
|
|
2918
|
+
console.warn(` ${c.dim(error.message)}`);
|
|
2919
|
+
});
|
|
2920
|
+
if (result.status === 'current') {
|
|
2921
|
+
console.log(`${ICON.ok} ${c.green(`Termdock ${result.currentVersion} is already up to date.`)}`);
|
|
2922
|
+
process.exit(0);
|
|
2923
|
+
}
|
|
2924
|
+
if (result.status === 'newer-than-registry') {
|
|
2925
|
+
console.log(`${ICON.info} ${c.dim(`Installed Termdock ${result.currentVersion} is newer than npm latest ${result.latestVersion}; nothing changed.`)}`);
|
|
2926
|
+
process.exit(0);
|
|
2927
|
+
}
|
|
2928
|
+
const sourceLabel = result.source === 'official' ? 'official npm registry' : 'configured npm registry';
|
|
2929
|
+
console.log(`${ICON.ok} ${c.green(`Updated Termdock ${result.currentVersion} → ${result.latestVersion} via ${sourceLabel}.`)}`);
|
|
2930
|
+
if (getRunningState()) {
|
|
2931
|
+
console.log(`${ICON.info} ${c.yellow('The running server is still using the previous version.')}`);
|
|
2932
|
+
console.log(` ${c.dim('Restart when convenient with:')} ${c.cyan('td --stop && td')}`);
|
|
2933
|
+
}
|
|
2934
|
+
process.exit(0);
|
|
2935
|
+
}
|
|
2717
2936
|
if (options.setupLocalHttps) {
|
|
2718
2937
|
try {
|
|
2719
2938
|
await runSetupLocalHttps();
|
|
@@ -2772,8 +2991,14 @@ async function main() {
|
|
|
2772
2991
|
runClearPassword();
|
|
2773
2992
|
process.exit(0);
|
|
2774
2993
|
}
|
|
2994
|
+
if (options.agentEvent) {
|
|
2995
|
+
runAgentEvent(options.agentEvent);
|
|
2996
|
+
}
|
|
2775
2997
|
if (options.pluginInit) {
|
|
2776
|
-
|
|
2998
|
+
if (options.pluginInitJson)
|
|
2999
|
+
runPluginInitJson();
|
|
3000
|
+
else
|
|
3001
|
+
runPluginInit();
|
|
2777
3002
|
process.exit(0);
|
|
2778
3003
|
}
|
|
2779
3004
|
if (options.pluginCreate !== undefined) {
|
|
@@ -905,7 +905,7 @@ function buildNestedRepoDisplayRootSet(workspaceRoot, repositories) {
|
|
|
905
905
|
async function buildGitBundle(resolvedCwd, gitRoot, signal, options = {}) {
|
|
906
906
|
const [branchOutput, changedResult] = await Promise.all([
|
|
907
907
|
execGit(['branch', '--show-current'], gitRoot, signal, options.gitTimeoutMs).catch(emptyOnNonAbortGitError),
|
|
908
|
-
getChangedFiles(gitRoot, signal, { includeUntracked:
|
|
908
|
+
getChangedFiles(gitRoot, signal, { includeUntracked: true, gitTimeoutMs: options.gitTimeoutMs }),
|
|
909
909
|
]);
|
|
910
910
|
const files = changedResult.files;
|
|
911
911
|
const annotatedFiles = annotateRepoFiles(files, gitRoot, gitRoot);
|
|
@@ -1012,7 +1012,6 @@ function mergeChangedFilesByPath(current, incoming) {
|
|
|
1012
1012
|
function updateGitBundleCachesWithUntracked(repoRoot, files) {
|
|
1013
1013
|
if (files.length === 0)
|
|
1014
1014
|
return;
|
|
1015
|
-
const now = Date.now();
|
|
1016
1015
|
for (const [cacheKey, cached] of gitBundleCache.entries()) {
|
|
1017
1016
|
const bundle = cached.bundle;
|
|
1018
1017
|
const repositories = bundle.repositories;
|
|
@@ -1046,7 +1045,7 @@ function updateGitBundleCachesWithUntracked(repoRoot, files) {
|
|
|
1046
1045
|
repoFilters: buildGitRepositoryFilters(primary?.root ?? repoRoot, nextRepositories),
|
|
1047
1046
|
untrackedDeferred: nextRepositories.some((repo) => repo.untrackedDeferred),
|
|
1048
1047
|
};
|
|
1049
|
-
gitBundleCache.set(cacheKey, { bundle: nextBundle, expiresAt:
|
|
1048
|
+
gitBundleCache.set(cacheKey, { bundle: nextBundle, expiresAt: cached.expiresAt, updatedAt: cached.updatedAt });
|
|
1050
1049
|
continue;
|
|
1051
1050
|
}
|
|
1052
1051
|
if (bundle.context?.root !== repoRoot)
|
|
@@ -1063,7 +1062,7 @@ function updateGitBundleCachesWithUntracked(repoRoot, files) {
|
|
|
1063
1062
|
context: nextContext,
|
|
1064
1063
|
untrackedDeferred: false,
|
|
1065
1064
|
};
|
|
1066
|
-
gitBundleCache.set(cacheKey, { bundle: nextBundle, expiresAt:
|
|
1065
|
+
gitBundleCache.set(cacheKey, { bundle: nextBundle, expiresAt: cached.expiresAt, updatedAt: cached.updatedAt });
|
|
1067
1066
|
}
|
|
1068
1067
|
}
|
|
1069
1068
|
async function buildWorkspaceGitBundle(resolvedCwd, gitRoot, includeNested, signal, options = {}) {
|
|
@@ -1138,6 +1137,7 @@ async function getCachedGitBundle(resolvedCwd, gitRoot, includeNested, refresh,
|
|
|
1138
1137
|
cached: true,
|
|
1139
1138
|
stale: cached.expiresAt <= now,
|
|
1140
1139
|
cacheAgeMs,
|
|
1140
|
+
cacheUpdatedAt: cached.updatedAt,
|
|
1141
1141
|
};
|
|
1142
1142
|
}
|
|
1143
1143
|
const canReusePending = !refresh && !signal;
|
|
@@ -1148,8 +1148,9 @@ async function getCachedGitBundle(resolvedCwd, gitRoot, includeNested, refresh,
|
|
|
1148
1148
|
}
|
|
1149
1149
|
const promise = buildWorkspaceGitBundle(resolvedCwd, gitRoot, includeNested, signal)
|
|
1150
1150
|
.then((bundle) => {
|
|
1151
|
-
|
|
1152
|
-
|
|
1151
|
+
const updatedAt = Date.now();
|
|
1152
|
+
gitBundleCache.set(cacheKey, { bundle, expiresAt: updatedAt + GIT_BUNDLE_CACHE_TTL_MS, updatedAt });
|
|
1153
|
+
return { ...bundle, cacheUpdatedAt: updatedAt };
|
|
1153
1154
|
})
|
|
1154
1155
|
.finally(() => {
|
|
1155
1156
|
if (gitBundleBuildPromises.get(cacheKey) === promise)
|
|
@@ -1166,8 +1167,9 @@ async function refreshGitBundleCacheDetached(resolvedCwd, gitRoot, includeNested
|
|
|
1166
1167
|
return pending;
|
|
1167
1168
|
const promise = buildWorkspaceGitBundle(resolvedCwd, gitRoot, includeNested, undefined, options)
|
|
1168
1169
|
.then((bundle) => {
|
|
1169
|
-
|
|
1170
|
-
|
|
1170
|
+
const updatedAt = Date.now();
|
|
1171
|
+
gitBundleCache.set(cacheKey, { bundle, expiresAt: updatedAt + GIT_BUNDLE_CACHE_TTL_MS, updatedAt });
|
|
1172
|
+
return { ...bundle, cacheUpdatedAt: updatedAt };
|
|
1171
1173
|
})
|
|
1172
1174
|
.finally(() => {
|
|
1173
1175
|
if (gitBundleBuildPromises.get(cacheKey) === promise)
|
|
@@ -1190,6 +1192,7 @@ function getGitBundleCache(gitRoot, includeNested, allowStale = false) {
|
|
|
1190
1192
|
cached: true,
|
|
1191
1193
|
stale: cached.expiresAt <= now,
|
|
1192
1194
|
cacheAgeMs,
|
|
1195
|
+
cacheUpdatedAt: cached.updatedAt,
|
|
1193
1196
|
};
|
|
1194
1197
|
}
|
|
1195
1198
|
function getSinglePath(paths) {
|