toc-ocra 0.2.0

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 (101) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +70 -0
  3. package/assets/icon.svg +1 -0
  4. package/dist/plugin/renderer.css +2 -0
  5. package/dist/plugin/renderer.css.map +7 -0
  6. package/dist/plugin/renderer.js +2 -0
  7. package/dist/plugin/renderer.js.map +7 -0
  8. package/dist/src/config.d.ts +66 -0
  9. package/dist/src/config.js +173 -0
  10. package/dist/src/config.js.map +1 -0
  11. package/dist/src/control/pairing.d.ts +44 -0
  12. package/dist/src/control/pairing.js +99 -0
  13. package/dist/src/control/pairing.js.map +1 -0
  14. package/dist/src/exec/script-runner.d.ts +9 -0
  15. package/dist/src/exec/script-runner.js +110 -0
  16. package/dist/src/exec/script-runner.js.map +1 -0
  17. package/dist/src/health/server.d.ts +74 -0
  18. package/dist/src/health/server.js +248 -0
  19. package/dist/src/health/server.js.map +1 -0
  20. package/dist/src/index.d.ts +2 -0
  21. package/dist/src/index.js +173 -0
  22. package/dist/src/index.js.map +1 -0
  23. package/dist/src/logging/diagnostic-log.d.ts +16 -0
  24. package/dist/src/logging/diagnostic-log.js +128 -0
  25. package/dist/src/logging/diagnostic-log.js.map +1 -0
  26. package/dist/src/providers/cli/claude.d.ts +2 -0
  27. package/dist/src/providers/cli/claude.js +48 -0
  28. package/dist/src/providers/cli/claude.js.map +1 -0
  29. package/dist/src/providers/cli/codex.d.ts +2 -0
  30. package/dist/src/providers/cli/codex.js +67 -0
  31. package/dist/src/providers/cli/codex.js.map +1 -0
  32. package/dist/src/providers/cli/gemini.d.ts +2 -0
  33. package/dist/src/providers/cli/gemini.js +30 -0
  34. package/dist/src/providers/cli/gemini.js.map +1 -0
  35. package/dist/src/providers/cli/grok.d.ts +2 -0
  36. package/dist/src/providers/cli/grok.js +29 -0
  37. package/dist/src/providers/cli/grok.js.map +1 -0
  38. package/dist/src/providers/cli/opencode.d.ts +2 -0
  39. package/dist/src/providers/cli/opencode.js +29 -0
  40. package/dist/src/providers/cli/opencode.js.map +1 -0
  41. package/dist/src/providers/detection.d.ts +11 -0
  42. package/dist/src/providers/detection.js +96 -0
  43. package/dist/src/providers/detection.js.map +1 -0
  44. package/dist/src/providers/model-catalog-collector.d.ts +22 -0
  45. package/dist/src/providers/model-catalog-collector.js +66 -0
  46. package/dist/src/providers/model-catalog-collector.js.map +1 -0
  47. package/dist/src/providers/model-catalog.d.ts +45 -0
  48. package/dist/src/providers/model-catalog.js +382 -0
  49. package/dist/src/providers/model-catalog.js.map +1 -0
  50. package/dist/src/providers/provider.d.ts +49 -0
  51. package/dist/src/providers/provider.js +63 -0
  52. package/dist/src/providers/provider.js.map +1 -0
  53. package/dist/src/providers/registry.d.ts +31 -0
  54. package/dist/src/providers/registry.js +60 -0
  55. package/dist/src/providers/registry.js.map +1 -0
  56. package/dist/src/runs/orphan-registry.d.ts +24 -0
  57. package/dist/src/runs/orphan-registry.js +63 -0
  58. package/dist/src/runs/orphan-registry.js.map +1 -0
  59. package/dist/src/runs/process-tree.d.ts +21 -0
  60. package/dist/src/runs/process-tree.js +95 -0
  61. package/dist/src/runs/process-tree.js.map +1 -0
  62. package/dist/src/runs/run-supervisor.d.ts +63 -0
  63. package/dist/src/runs/run-supervisor.js +285 -0
  64. package/dist/src/runs/run-supervisor.js.map +1 -0
  65. package/dist/src/runtime/agent-runtime.d.ts +68 -0
  66. package/dist/src/runtime/agent-runtime.js +340 -0
  67. package/dist/src/runtime/agent-runtime.js.map +1 -0
  68. package/dist/src/runtime/agent-service.d.ts +33 -0
  69. package/dist/src/runtime/agent-service.js +181 -0
  70. package/dist/src/runtime/agent-service.js.map +1 -0
  71. package/dist/src/runtime/cli/hydrate-shell-path.d.ts +33 -0
  72. package/dist/src/runtime/cli/hydrate-shell-path.js +173 -0
  73. package/dist/src/runtime/cli/hydrate-shell-path.js.map +1 -0
  74. package/dist/src/session/terminal-line-sanitizer.d.ts +7 -0
  75. package/dist/src/session/terminal-line-sanitizer.js +21 -0
  76. package/dist/src/session/terminal-line-sanitizer.js.map +1 -0
  77. package/dist/src/session/terminal-tail-buffer.d.ts +70 -0
  78. package/dist/src/session/terminal-tail-buffer.js +149 -0
  79. package/dist/src/session/terminal-tail-buffer.js.map +1 -0
  80. package/dist/src/shared/cli-detection-types.d.ts +19 -0
  81. package/dist/src/shared/cli-detection-types.js +2 -0
  82. package/dist/src/shared/cli-detection-types.js.map +1 -0
  83. package/dist/src/shared/node-cli-command-resolution.d.ts +19 -0
  84. package/dist/src/shared/node-cli-command-resolution.js +173 -0
  85. package/dist/src/shared/node-cli-command-resolution.js.map +1 -0
  86. package/dist/src/transport/gateway-client.d.ts +38 -0
  87. package/dist/src/transport/gateway-client.js +73 -0
  88. package/dist/src/transport/gateway-client.js.map +1 -0
  89. package/dist/src/types/protocol.d.ts +172 -0
  90. package/dist/src/types/protocol.js +55 -0
  91. package/dist/src/types/protocol.js.map +1 -0
  92. package/dist/src/workspace/directory-browser.d.ts +17 -0
  93. package/dist/src/workspace/directory-browser.js +47 -0
  94. package/dist/src/workspace/directory-browser.js.map +1 -0
  95. package/dist/src/workspace/workspace-manager.d.ts +41 -0
  96. package/dist/src/workspace/workspace-manager.js +436 -0
  97. package/dist/src/workspace/workspace-manager.js.map +1 -0
  98. package/locales/en.json +66 -0
  99. package/locales/zh-CN.json +66 -0
  100. package/package.json +90 -0
  101. package/scripts/service.mjs +174 -0
package/package.json ADDED
@@ -0,0 +1,90 @@
1
+ {
2
+ "name": "toc-ocra",
3
+ "version": "0.2.0",
4
+ "description": "Echo device agent: local CLI execution service plus its desktop plugin (heartbeat + long-poll, headless CLIs)",
5
+ "type": "module",
6
+ "bin": {
7
+ "toc-ocra": "./dist/src/index.js"
8
+ },
9
+ "files": [
10
+ "dist/src/",
11
+ "dist/plugin/",
12
+ "assets/",
13
+ "locales/",
14
+ "scripts/service.mjs",
15
+ "README.md",
16
+ "LICENSE"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsc -p tsconfig.json && npm run build:plugin",
20
+ "dev": "tsx src/index.ts run",
21
+ "start": "node dist/src/index.js run",
22
+ "doctor": "tsx src/index.ts doctor",
23
+ "service:install": "node scripts/service.mjs install",
24
+ "service:uninstall": "node scripts/service.mjs uninstall",
25
+ "publish:latest": "bash ./publish-latest.sh",
26
+ "typecheck": "tsc --noEmit && tsc -p tsconfig.plugin.json",
27
+ "test": "vitest run",
28
+ "test:watch": "vitest",
29
+ "build:plugin": "toc-plugin build --entry plugin/index.tsx",
30
+ "dev:plugin": "toc-plugin build --entry plugin/index.tsx --no-minify --watch",
31
+ "validate:plugin": "toc-plugin validate"
32
+ },
33
+ "engines": {
34
+ "node": ">=20"
35
+ },
36
+ "dependencies": {
37
+ "zod": "^4.1.12"
38
+ },
39
+ "devDependencies": {
40
+ "@toc-starter/plugin-sdk": "file:../echo_web/packages/plugin-sdk",
41
+ "@types/node": "^22.15.0",
42
+ "@types/react": "18.3.12",
43
+ "tsx": "^4.20.0",
44
+ "typescript": "^5.8.0",
45
+ "vitest": "^3.2.0"
46
+ },
47
+ "license": "MIT",
48
+ "tocPlugin": {
49
+ "id": "echo.device-agent",
50
+ "displayName": "设备代理",
51
+ "description": "让这台电脑接收任务链派发的任务:连接本机 toc-ocra 服务,一键绑定到当前服务器,查看在线状态和本机 CLI。",
52
+ "publisher": "Echo",
53
+ "apiVersion": "1.x",
54
+ "platforms": [
55
+ "desktop"
56
+ ],
57
+ "icon": "./assets/icon.svg",
58
+ "entries": {
59
+ "renderer": "./dist/plugin/renderer.js",
60
+ "style": "./dist/plugin/renderer.css"
61
+ },
62
+ "locales": {
63
+ "en": "./locales/en.json",
64
+ "zh-CN": "./locales/zh-CN.json"
65
+ },
66
+ "permissions": [
67
+ "storage:plugin",
68
+ "secureStorage:plugin",
69
+ "notifications:publish",
70
+ "api:user"
71
+ ],
72
+ "contributes": {
73
+ "navigation": [
74
+ {
75
+ "id": "agent.nav",
76
+ "title": "设备代理",
77
+ "titleKey": "nav.title",
78
+ "path": "/plugins/device-agent",
79
+ "order": 30
80
+ }
81
+ ],
82
+ "routes": [
83
+ {
84
+ "id": "agent.home",
85
+ "path": "/plugins/device-agent"
86
+ }
87
+ ]
88
+ }
89
+ }
90
+ }
@@ -0,0 +1,174 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync } from 'node:fs'
3
+ import { chmod, mkdir, unlink, writeFile } from 'node:fs/promises'
4
+ import { homedir } from 'node:os'
5
+ import { dirname, join, resolve } from 'node:path'
6
+ import { fileURLToPath } from 'node:url'
7
+ import { spawnSync } from 'node:child_process'
8
+
9
+ const SERVICE_NAME = 'toc-ocra'
10
+ const LAUNCHD_LABEL = 'top.stormapex.toc-ocra'
11
+ const root = resolve(dirname(fileURLToPath(import.meta.url)), '..')
12
+ const agentEntry = join(root, 'dist', 'src', 'index.js')
13
+ const action = process.argv[2]
14
+ const configPath = resolve(readOption('--config') ?? defaultConfigPath())
15
+
16
+ if (action !== 'install' && action !== 'uninstall') usage()
17
+ if (action === 'install' && !existsSync(agentEntry)) fail('Build output is missing. Run npm run build first.')
18
+ // The service may start unenrolled: it then waits to be paired from the plugin.
19
+
20
+ if (process.platform === 'darwin') await manageLaunchd(action)
21
+ else if (process.platform === 'linux') await manageSystemd(action)
22
+ else if (process.platform === 'win32') await manageWindowsTask(action)
23
+ else fail(`Unsupported platform: ${process.platform}`)
24
+
25
+ function readOption(name) {
26
+ const index = process.argv.indexOf(name)
27
+ return index >= 0 ? process.argv[index + 1] : undefined
28
+ }
29
+
30
+ function defaultDataDir() {
31
+ if (process.platform === 'darwin') return join(homedir(), 'Library', 'Application Support', SERVICE_NAME)
32
+ if (process.platform === 'win32') return join(process.env.APPDATA ?? homedir(), SERVICE_NAME)
33
+ return join(process.env.XDG_STATE_HOME ?? join(homedir(), '.local', 'state'), SERVICE_NAME)
34
+ }
35
+
36
+ function defaultConfigPath() {
37
+ return join(defaultDataDir(), 'config.json')
38
+ }
39
+
40
+ async function manageLaunchd(mode) {
41
+ const target = join(homedir(), 'Library', 'LaunchAgents', `${LAUNCHD_LABEL}.plist`)
42
+ const domain = `gui/${process.getuid()}`
43
+ if (mode === 'uninstall') {
44
+ command('launchctl', ['bootout', domain, target], true)
45
+ await unlink(target).catch(ignoreMissing)
46
+ process.stdout.write(`Removed ${target}\n`)
47
+ return
48
+ }
49
+
50
+ const logDir = join(defaultDataDir(), 'logs')
51
+ const path = servicePath()
52
+ await mkdir(dirname(target), { recursive: true })
53
+ await mkdir(logDir, { recursive: true, mode: 0o700 })
54
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
55
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
56
+ <plist version="1.0"><dict>
57
+ <key>Label</key><string>${LAUNCHD_LABEL}</string>
58
+ <key>ProgramArguments</key><array>
59
+ <string>${xml(process.execPath)}</string><string>${xml(agentEntry)}</string><string>run</string>
60
+ </array>
61
+ <key>EnvironmentVariables</key><dict>
62
+ <key>TOC_OCRA_CONFIG</key><string>${xml(configPath)}</string>
63
+ <key>HOME</key><string>${xml(homedir())}</string>
64
+ <key>SHELL</key><string>${xml(process.env.SHELL ?? '/bin/zsh')}</string>
65
+ <key>PATH</key><string>${xml(path)}</string>
66
+ </dict>
67
+ <key>RunAtLoad</key><true/><key>KeepAlive</key><true/>
68
+ <key>StandardOutPath</key><string>${xml(join(logDir, 'service.stdout.log'))}</string>
69
+ <key>StandardErrorPath</key><string>${xml(join(logDir, 'service.stderr.log'))}</string>
70
+ </dict></plist>
71
+ `
72
+ await writeFile(target, plist, { mode: 0o600 })
73
+ command('launchctl', ['bootout', domain, target], true)
74
+ command('launchctl', ['bootstrap', domain, target])
75
+ command('launchctl', ['enable', `${domain}/${LAUNCHD_LABEL}`])
76
+ command('launchctl', ['kickstart', '-k', `${domain}/${LAUNCHD_LABEL}`])
77
+ process.stdout.write(`Installed ${target}\n`)
78
+ }
79
+
80
+ async function manageSystemd(mode) {
81
+ const target = join(homedir(), '.config', 'systemd', 'user', `${SERVICE_NAME}.service`)
82
+ if (mode === 'uninstall') {
83
+ command('systemctl', ['--user', 'disable', '--now', SERVICE_NAME], true)
84
+ await unlink(target).catch(ignoreMissing)
85
+ command('systemctl', ['--user', 'daemon-reload'])
86
+ process.stdout.write(`Removed ${target}\n`)
87
+ return
88
+ }
89
+
90
+ await mkdir(dirname(target), { recursive: true })
91
+ const unit = `[Unit]
92
+ Description=echo_cli_agent TaskMCP device agent
93
+ After=network-online.target
94
+ Wants=network-online.target
95
+
96
+ [Service]
97
+ Type=simple
98
+ Environment=TOC_OCRA_CONFIG=${systemdQuote(configPath)}
99
+ Environment=PATH=${systemdQuote(servicePath())}
100
+ ExecStart=${systemdQuote(process.execPath)} ${systemdQuote(agentEntry)} run
101
+ Restart=always
102
+ RestartSec=3
103
+ NoNewPrivileges=true
104
+
105
+ [Install]
106
+ WantedBy=default.target
107
+ `
108
+ await writeFile(target, unit, { mode: 0o600 })
109
+ command('systemctl', ['--user', 'daemon-reload'])
110
+ command('systemctl', ['--user', 'enable', '--now', SERVICE_NAME])
111
+ process.stdout.write(`Installed ${target}\n`)
112
+ }
113
+
114
+ async function manageWindowsTask(mode) {
115
+ const launcher = join(defaultDataDir(), 'run-agent.cmd')
116
+ if (mode === 'uninstall') {
117
+ command('schtasks.exe', ['/Delete', '/F', '/TN', SERVICE_NAME], true)
118
+ await unlink(launcher).catch(ignoreMissing)
119
+ process.stdout.write(`Removed scheduled task ${SERVICE_NAME}\n`)
120
+ return
121
+ }
122
+
123
+ rejectCmdMetacharacters(configPath)
124
+ rejectCmdMetacharacters(process.execPath)
125
+ rejectCmdMetacharacters(agentEntry)
126
+ await mkdir(dirname(launcher), { recursive: true })
127
+ await writeFile(launcher, `@echo off\r\nset "TOC_OCRA_CONFIG=${configPath}"\r\n"${process.execPath}" "${agentEntry}" run\r\n`, { mode: 0o600 })
128
+ await chmod(launcher, 0o700).catch(() => undefined)
129
+ command('schtasks.exe', ['/Create', '/F', '/TN', SERVICE_NAME, '/SC', 'ONLOGON', '/RL', 'LIMITED', '/TR', `"${launcher}"`])
130
+ command('schtasks.exe', ['/Run', '/TN', SERVICE_NAME])
131
+ process.stdout.write(`Installed scheduled task ${SERVICE_NAME}\n`)
132
+ }
133
+
134
+ function command(executable, args, allowFailure = false) {
135
+ const result = spawnSync(executable, args, { stdio: 'inherit', shell: false })
136
+ if (!allowFailure && result.status !== 0) fail(`${executable} failed with exit code ${result.status ?? 'unknown'}`)
137
+ }
138
+
139
+ function xml(value) {
140
+ return value.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('"', '&quot;')
141
+ }
142
+
143
+ function servicePath() {
144
+ const entries = [
145
+ dirname(process.execPath),
146
+ join(homedir(), '.local', 'bin'),
147
+ '/opt/homebrew/bin',
148
+ '/usr/local/bin',
149
+ process.env.PATH ?? ''
150
+ ]
151
+ return [...new Set(entries.flatMap((entry) => entry.split(':')).filter(Boolean))].join(':')
152
+ }
153
+
154
+ function systemdQuote(value) {
155
+ return `"${value.replaceAll('\\', '\\\\').replaceAll('"', '\\"').replaceAll('%', '%%')}"`
156
+ }
157
+
158
+ function rejectCmdMetacharacters(value) {
159
+ if (/[\r\n&|<>^]/.test(value)) fail('Windows service paths contain unsupported command characters')
160
+ }
161
+
162
+ function ignoreMissing(error) {
163
+ if (error?.code !== 'ENOENT') throw error
164
+ }
165
+
166
+ function usage() {
167
+ process.stderr.write('Usage: node scripts/service.mjs <install|uninstall> [--config /path/config.json]\n')
168
+ process.exit(2)
169
+ }
170
+
171
+ function fail(message) {
172
+ process.stderr.write(`${message}\n`)
173
+ process.exit(1)
174
+ }