opensyn 0.1.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.
- package/README.md +73 -0
- package/README.zh-CN.md +65 -0
- package/index.ts +3507 -0
- package/openclaw.plugin.json +173 -0
- package/package.json +33 -0
- package/runtime/bin/linux-x64/synapse-cli +0 -0
- package/runtime/bin/linux-x64/synapse-daemon +0 -0
- package/runtime/opensyn-cli +43 -0
- package/runtime/opensyn-daemon +43 -0
- package/runtime/opensyn-host-distiller.mjs +173 -0
- package/runtime/opensyn-ingest-command +124 -0
- package/runtime/opensyn-run +99 -0
- package/runtime/postinstall.mjs +85 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "opensyn",
|
|
3
|
+
"name": "OpenSyn",
|
|
4
|
+
"description": "Expose local OpenSyn context to OpenClaw through native plugin tools.",
|
|
5
|
+
"version": "0.1.0",
|
|
6
|
+
"configSchema": {
|
|
7
|
+
"type": "object",
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {
|
|
10
|
+
"runtimeRoot": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"description": "Optional OpenSyn home directory. Defaults to ~/.opensyn and stores the database plus managed runtime assets."
|
|
13
|
+
},
|
|
14
|
+
"dbPath": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"description": "Optional absolute path to the OpenSyn SQLite database. Defaults to <runtimeRoot>/opensyn.db."
|
|
17
|
+
},
|
|
18
|
+
"cliBin": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"description": "Optional absolute path to the synapse-cli binary or OpenSyn CLI wrapper."
|
|
21
|
+
},
|
|
22
|
+
"daemonBin": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"description": "Optional absolute path to the synapse-daemon binary or OpenSyn daemon wrapper."
|
|
25
|
+
},
|
|
26
|
+
"helperPath": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"description": "Optional absolute path to the shell-hook ingest helper."
|
|
29
|
+
},
|
|
30
|
+
"runnerPath": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"description": "Optional absolute path to the command wrapper used by the shell hook."
|
|
33
|
+
},
|
|
34
|
+
"defaultShell": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"description": "Optional shell name used when plugin-managed collection repairs the local shell hook."
|
|
37
|
+
},
|
|
38
|
+
"defaultProjectRoot": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"description": "Optional default project root when the caller does not pass one."
|
|
41
|
+
},
|
|
42
|
+
"defaultWindowSecs": {
|
|
43
|
+
"type": "integer",
|
|
44
|
+
"minimum": 1,
|
|
45
|
+
"default": 300
|
|
46
|
+
},
|
|
47
|
+
"defaultSearchLimit": {
|
|
48
|
+
"type": "integer",
|
|
49
|
+
"minimum": 1,
|
|
50
|
+
"default": 10
|
|
51
|
+
},
|
|
52
|
+
"daemonTimeoutMs": {
|
|
53
|
+
"type": "integer",
|
|
54
|
+
"minimum": 1000,
|
|
55
|
+
"default": 10000,
|
|
56
|
+
"description": "Maximum time to wait for synapse-daemon before the tool call fails."
|
|
57
|
+
},
|
|
58
|
+
"projectionBundleRoot": {
|
|
59
|
+
"type": "string",
|
|
60
|
+
"description": "Optional projection root. For OpenClaw host mode this should usually be the OpenClaw workspace root, for example ~/.openclaw/workspace."
|
|
61
|
+
},
|
|
62
|
+
"projectionHost": {
|
|
63
|
+
"type": "string",
|
|
64
|
+
"description": "Optional projection host flavor: openclaw, claudecode, or generic."
|
|
65
|
+
},
|
|
66
|
+
"projectionAutoApplyApproved": {
|
|
67
|
+
"type": "boolean",
|
|
68
|
+
"description": "Optional default for whether approved assets should be projected automatically."
|
|
69
|
+
},
|
|
70
|
+
"watchIdleWindowSecs": {
|
|
71
|
+
"type": "integer",
|
|
72
|
+
"minimum": 1,
|
|
73
|
+
"description": "Optional idle window for the plugin-managed background watch-linux collector. Defaults to 10 seconds."
|
|
74
|
+
},
|
|
75
|
+
"watchPollIntervalSecs": {
|
|
76
|
+
"type": "integer",
|
|
77
|
+
"minimum": 1,
|
|
78
|
+
"description": "Optional poll interval for the plugin-managed background watch-linux collector. Defaults to 2 seconds."
|
|
79
|
+
},
|
|
80
|
+
"openclawBin": {
|
|
81
|
+
"type": "string",
|
|
82
|
+
"description": "Optional path to the OpenClaw CLI used by the plugin-managed host-model distillation worker. Defaults to 'openclaw'."
|
|
83
|
+
},
|
|
84
|
+
"distillationWorkerEnabled": {
|
|
85
|
+
"type": "boolean",
|
|
86
|
+
"description": "Whether the plugin-managed host-model distillation worker should run automatically. Defaults to true."
|
|
87
|
+
},
|
|
88
|
+
"distillationWorkerIntervalSecs": {
|
|
89
|
+
"type": "integer",
|
|
90
|
+
"minimum": 5,
|
|
91
|
+
"description": "Optional interval for the plugin-managed host-model distillation worker. Defaults to 60 seconds."
|
|
92
|
+
},
|
|
93
|
+
"distillationWorkerAgentId": {
|
|
94
|
+
"type": "string",
|
|
95
|
+
"description": "Optional OpenClaw agent id used by the plugin-managed host-model distillation worker. Defaults to 'main'."
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
"uiHints": {
|
|
100
|
+
"dbPath": {
|
|
101
|
+
"label": "OpenSyn DB Path",
|
|
102
|
+
"placeholder": "/home/user/.opensyn/opensyn.db"
|
|
103
|
+
},
|
|
104
|
+
"runtimeRoot": {
|
|
105
|
+
"label": "OpenSyn Home",
|
|
106
|
+
"placeholder": "/home/user/.opensyn"
|
|
107
|
+
},
|
|
108
|
+
"cliBin": {
|
|
109
|
+
"label": "OpenSyn CLI Path",
|
|
110
|
+
"placeholder": "/path/to/opensyn-cli"
|
|
111
|
+
},
|
|
112
|
+
"daemonBin": {
|
|
113
|
+
"label": "OpenSyn Daemon Path",
|
|
114
|
+
"placeholder": "/path/to/opensyn-daemon"
|
|
115
|
+
},
|
|
116
|
+
"helperPath": {
|
|
117
|
+
"label": "Helper Path",
|
|
118
|
+
"placeholder": "/path/to/synapse-ingest-command"
|
|
119
|
+
},
|
|
120
|
+
"runnerPath": {
|
|
121
|
+
"label": "Runner Path",
|
|
122
|
+
"placeholder": "/path/to/synapse-run"
|
|
123
|
+
},
|
|
124
|
+
"defaultShell": {
|
|
125
|
+
"label": "Default Shell",
|
|
126
|
+
"placeholder": "bash"
|
|
127
|
+
},
|
|
128
|
+
"defaultProjectRoot": {
|
|
129
|
+
"label": "Default Project Root",
|
|
130
|
+
"placeholder": "/path/to/project"
|
|
131
|
+
},
|
|
132
|
+
"daemonTimeoutMs": {
|
|
133
|
+
"label": "Daemon Timeout (ms)",
|
|
134
|
+
"placeholder": "10000"
|
|
135
|
+
},
|
|
136
|
+
"projectionBundleRoot": {
|
|
137
|
+
"label": "Projection Root",
|
|
138
|
+
"placeholder": "/home/user/.openclaw/workspace"
|
|
139
|
+
},
|
|
140
|
+
"projectionHost": {
|
|
141
|
+
"label": "Projection Host",
|
|
142
|
+
"placeholder": "openclaw"
|
|
143
|
+
},
|
|
144
|
+
"projectionAutoApplyApproved": {
|
|
145
|
+
"label": "Auto Apply Approved Assets",
|
|
146
|
+
"placeholder": "false"
|
|
147
|
+
},
|
|
148
|
+
"watchIdleWindowSecs": {
|
|
149
|
+
"label": "Background Idle Window (s)",
|
|
150
|
+
"placeholder": "10"
|
|
151
|
+
},
|
|
152
|
+
"watchPollIntervalSecs": {
|
|
153
|
+
"label": "Background Poll Interval (s)",
|
|
154
|
+
"placeholder": "2"
|
|
155
|
+
},
|
|
156
|
+
"openclawBin": {
|
|
157
|
+
"label": "OpenClaw CLI Path",
|
|
158
|
+
"placeholder": "openclaw"
|
|
159
|
+
},
|
|
160
|
+
"distillationWorkerEnabled": {
|
|
161
|
+
"label": "Host Distillation Worker",
|
|
162
|
+
"placeholder": "true"
|
|
163
|
+
},
|
|
164
|
+
"distillationWorkerIntervalSecs": {
|
|
165
|
+
"label": "Worker Interval (s)",
|
|
166
|
+
"placeholder": "60"
|
|
167
|
+
},
|
|
168
|
+
"distillationWorkerAgentId": {
|
|
169
|
+
"label": "Worker Agent Id",
|
|
170
|
+
"placeholder": "main"
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opensyn",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OpenClaw plugin for OpenSyn autonomous local collection, distillation, and host overlay projection.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"private": false,
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"index.ts",
|
|
12
|
+
"openclaw.plugin.json",
|
|
13
|
+
"README.md",
|
|
14
|
+
"README.zh-CN.md",
|
|
15
|
+
"runtime/**/*"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"postinstall": "node ./runtime/postinstall.mjs",
|
|
19
|
+
"prepack": "cargo build --manifest-path ../../Cargo.toml -p synapse-daemon -p synapse-cli --release && bash ../../scripts/stage-opensyn-plugin-runtime --profile release --plugin-dir \"$PWD\"",
|
|
20
|
+
"release:check": "npm pack --dry-run",
|
|
21
|
+
"release:publish": "npm publish --access public",
|
|
22
|
+
"stage:runtime:debug": "bash ../../scripts/stage-opensyn-plugin-runtime --profile debug --plugin-dir \"$PWD\"",
|
|
23
|
+
"stage:runtime:release": "bash ../../scripts/stage-opensyn-plugin-runtime --profile release --plugin-dir \"$PWD\""
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@sinclair/typebox": "^0.34.41"
|
|
27
|
+
},
|
|
28
|
+
"openclaw": {
|
|
29
|
+
"extensions": [
|
|
30
|
+
"./index.ts"
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
|
|
6
|
+
platform="$(uname -s | tr '[:upper:]' '[:lower:]')"
|
|
7
|
+
arch_raw="$(uname -m)"
|
|
8
|
+
case "$arch_raw" in
|
|
9
|
+
x86_64|amd64)
|
|
10
|
+
arch="x64"
|
|
11
|
+
;;
|
|
12
|
+
aarch64|arm64)
|
|
13
|
+
arch="arm64"
|
|
14
|
+
;;
|
|
15
|
+
*)
|
|
16
|
+
arch="$arch_raw"
|
|
17
|
+
;;
|
|
18
|
+
esac
|
|
19
|
+
|
|
20
|
+
candidates=()
|
|
21
|
+
if [[ -n "${OPENSYN_CLI_BIN:-}" ]]; then
|
|
22
|
+
candidates+=("${OPENSYN_CLI_BIN}")
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
candidates+=(
|
|
26
|
+
"$script_dir/bin/${platform}-${arch}/synapse-cli"
|
|
27
|
+
"$script_dir/bin/synapse-cli"
|
|
28
|
+
"$script_dir/../../../target/debug/synapse-cli"
|
|
29
|
+
"$script_dir/../../../target/release/synapse-cli"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
for candidate in "${candidates[@]}"; do
|
|
33
|
+
if [[ -n "$candidate" && -x "$candidate" ]]; then
|
|
34
|
+
exec "$candidate" "$@"
|
|
35
|
+
fi
|
|
36
|
+
done
|
|
37
|
+
|
|
38
|
+
echo "OpenSyn CLI runtime not found. Expected one of:" >&2
|
|
39
|
+
for candidate in "${candidates[@]}"; do
|
|
40
|
+
echo " - $candidate" >&2
|
|
41
|
+
done
|
|
42
|
+
echo "Stage package-local binaries with scripts/stage-opensyn-plugin-runtime, or set OPENSYN_CLI_BIN." >&2
|
|
43
|
+
exit 1
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
|
|
6
|
+
platform="$(uname -s | tr '[:upper:]' '[:lower:]')"
|
|
7
|
+
arch_raw="$(uname -m)"
|
|
8
|
+
case "$arch_raw" in
|
|
9
|
+
x86_64|amd64)
|
|
10
|
+
arch="x64"
|
|
11
|
+
;;
|
|
12
|
+
aarch64|arm64)
|
|
13
|
+
arch="arm64"
|
|
14
|
+
;;
|
|
15
|
+
*)
|
|
16
|
+
arch="$arch_raw"
|
|
17
|
+
;;
|
|
18
|
+
esac
|
|
19
|
+
|
|
20
|
+
candidates=()
|
|
21
|
+
if [[ -n "${OPENSYN_DAEMON_BIN:-}" ]]; then
|
|
22
|
+
candidates+=("${OPENSYN_DAEMON_BIN}")
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
candidates+=(
|
|
26
|
+
"$script_dir/bin/${platform}-${arch}/synapse-daemon"
|
|
27
|
+
"$script_dir/bin/synapse-daemon"
|
|
28
|
+
"$script_dir/../../../target/debug/synapse-daemon"
|
|
29
|
+
"$script_dir/../../../target/release/synapse-daemon"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
for candidate in "${candidates[@]}"; do
|
|
33
|
+
if [[ -n "$candidate" && -x "$candidate" ]]; then
|
|
34
|
+
exec "$candidate" "$@"
|
|
35
|
+
fi
|
|
36
|
+
done
|
|
37
|
+
|
|
38
|
+
echo "OpenSyn daemon runtime not found. Expected one of:" >&2
|
|
39
|
+
for candidate in "${candidates[@]}"; do
|
|
40
|
+
echo " - $candidate" >&2
|
|
41
|
+
done
|
|
42
|
+
echo "Stage package-local binaries with scripts/stage-opensyn-plugin-runtime, or set OPENSYN_DAEMON_BIN." >&2
|
|
43
|
+
exit 1
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
|
|
7
|
+
function parseArgs(argv) {
|
|
8
|
+
const options = {
|
|
9
|
+
openclawBin: "openclaw",
|
|
10
|
+
agentId: "main",
|
|
11
|
+
intervalSecs: 60,
|
|
12
|
+
timeoutSecs: 120,
|
|
13
|
+
projectRoot: process.cwd(),
|
|
14
|
+
statePath: "",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
18
|
+
const arg = argv[i];
|
|
19
|
+
const next = argv[i + 1];
|
|
20
|
+
if (arg === "--openclaw-bin" && next) {
|
|
21
|
+
options.openclawBin = next;
|
|
22
|
+
i += 1;
|
|
23
|
+
} else if (arg === "--agent-id" && next) {
|
|
24
|
+
options.agentId = next;
|
|
25
|
+
i += 1;
|
|
26
|
+
} else if (arg === "--interval-secs" && next) {
|
|
27
|
+
options.intervalSecs = Number.parseInt(next, 10) || options.intervalSecs;
|
|
28
|
+
i += 1;
|
|
29
|
+
} else if (arg === "--timeout-secs" && next) {
|
|
30
|
+
options.timeoutSecs = Number.parseInt(next, 10) || options.timeoutSecs;
|
|
31
|
+
i += 1;
|
|
32
|
+
} else if (arg === "--project-root" && next) {
|
|
33
|
+
options.projectRoot = next;
|
|
34
|
+
i += 1;
|
|
35
|
+
} else if (arg === "--state-path" && next) {
|
|
36
|
+
options.statePath = next;
|
|
37
|
+
i += 1;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return options;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function sleep(ms) {
|
|
45
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function updateState(statePath, payload) {
|
|
49
|
+
if (!statePath) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
let previous = {};
|
|
53
|
+
if (fs.existsSync(statePath)) {
|
|
54
|
+
try {
|
|
55
|
+
previous = JSON.parse(fs.readFileSync(statePath, "utf8"));
|
|
56
|
+
} catch {
|
|
57
|
+
previous = {};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
fs.mkdirSync(path.dirname(statePath), { recursive: true });
|
|
61
|
+
fs.writeFileSync(
|
|
62
|
+
statePath,
|
|
63
|
+
`${JSON.stringify({ ...previous, ...payload }, null, 2)}\n`,
|
|
64
|
+
"utf8",
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function buildPrompt(projectRoot) {
|
|
69
|
+
return [
|
|
70
|
+
`Run exactly one OpenSyn autonomous distillation cycle for project ${projectRoot}.`,
|
|
71
|
+
"Rules:",
|
|
72
|
+
"- Use only the OpenClaw-configured host model already available to this local runtime.",
|
|
73
|
+
"- Do not browse, do not call external network tools, and do not use any model outside OpenClaw.",
|
|
74
|
+
"- First call opensyn_autonomous_tick with no arguments.",
|
|
75
|
+
"- If automation_state is idle_waiting_for_activity, stop immediately.",
|
|
76
|
+
"- If automation_state is distillation_work_ready, follow cycle_plan.primary_steps in order.",
|
|
77
|
+
"- If the primary path cannot complete safely, follow cycle_plan.fallback_steps.",
|
|
78
|
+
"- Always execute cycle_plan.resume_steps before ending when they are available.",
|
|
79
|
+
"- Complete at most one queued work packet in this run.",
|
|
80
|
+
"- End with one short sentence describing the outcome.",
|
|
81
|
+
].join("\n");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function main() {
|
|
85
|
+
const options = parseArgs(process.argv.slice(2));
|
|
86
|
+
const prompt = buildPrompt(options.projectRoot);
|
|
87
|
+
const usesNodeShim =
|
|
88
|
+
options.openclawBin.endsWith(".mjs") || options.openclawBin.endsWith(".js");
|
|
89
|
+
let stopped = false;
|
|
90
|
+
|
|
91
|
+
process.on("SIGTERM", () => {
|
|
92
|
+
stopped = true;
|
|
93
|
+
});
|
|
94
|
+
process.on("SIGINT", () => {
|
|
95
|
+
stopped = true;
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
while (!stopped) {
|
|
99
|
+
const startedAt = new Date().toISOString();
|
|
100
|
+
const command = usesNodeShim ? process.execPath : options.openclawBin;
|
|
101
|
+
const commandArgs = usesNodeShim
|
|
102
|
+
? [
|
|
103
|
+
options.openclawBin,
|
|
104
|
+
"agent",
|
|
105
|
+
"--local",
|
|
106
|
+
"--agent",
|
|
107
|
+
options.agentId,
|
|
108
|
+
"--thinking",
|
|
109
|
+
"minimal",
|
|
110
|
+
"--timeout",
|
|
111
|
+
String(options.timeoutSecs),
|
|
112
|
+
"--message",
|
|
113
|
+
prompt,
|
|
114
|
+
"--json",
|
|
115
|
+
]
|
|
116
|
+
: [
|
|
117
|
+
"agent",
|
|
118
|
+
"--local",
|
|
119
|
+
"--agent",
|
|
120
|
+
options.agentId,
|
|
121
|
+
"--thinking",
|
|
122
|
+
"minimal",
|
|
123
|
+
"--timeout",
|
|
124
|
+
String(options.timeoutSecs),
|
|
125
|
+
"--message",
|
|
126
|
+
prompt,
|
|
127
|
+
"--json",
|
|
128
|
+
];
|
|
129
|
+
const result = spawnSync(
|
|
130
|
+
command,
|
|
131
|
+
commandArgs,
|
|
132
|
+
{
|
|
133
|
+
cwd: options.projectRoot,
|
|
134
|
+
env: process.env,
|
|
135
|
+
encoding: "utf8",
|
|
136
|
+
},
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const finishedAt = new Date().toISOString();
|
|
140
|
+
const payload = {
|
|
141
|
+
started_at: startedAt,
|
|
142
|
+
finished_at: finishedAt,
|
|
143
|
+
command,
|
|
144
|
+
command_args: commandArgs,
|
|
145
|
+
exit_code: result.status,
|
|
146
|
+
signal: result.signal,
|
|
147
|
+
error: result.error ? String(result.error.message || result.error) : "",
|
|
148
|
+
stdout: result.stdout?.trim() || "",
|
|
149
|
+
stderr: result.stderr?.trim() || "",
|
|
150
|
+
};
|
|
151
|
+
updateState(options.statePath, {
|
|
152
|
+
last_started_at: startedAt,
|
|
153
|
+
last_finished_at: finishedAt,
|
|
154
|
+
last_exit_code: result.status,
|
|
155
|
+
last_signal: result.signal,
|
|
156
|
+
last_error: payload.error || payload.stderr || "",
|
|
157
|
+
last_stdout: payload.stdout,
|
|
158
|
+
last_command: payload.command,
|
|
159
|
+
last_command_args: payload.command_args,
|
|
160
|
+
});
|
|
161
|
+
process.stdout.write(`${JSON.stringify(payload)}\n`);
|
|
162
|
+
|
|
163
|
+
if (stopped) {
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
await sleep(Math.max(5, options.intervalSecs) * 1000);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
main().catch((error) => {
|
|
171
|
+
process.stderr.write(`${error instanceof Error ? error.stack || error.message : String(error)}\n`);
|
|
172
|
+
process.exitCode = 1;
|
|
173
|
+
});
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
db=""
|
|
5
|
+
daemon_bin=""
|
|
6
|
+
cwd=""
|
|
7
|
+
command_line=""
|
|
8
|
+
exit_code=""
|
|
9
|
+
project_root=""
|
|
10
|
+
source_kind="${OPENSYN_SOURCE_KIND:-host}"
|
|
11
|
+
device_id="${OPENSYN_DEVICE_ID:-}"
|
|
12
|
+
stdout_tail="${OPENSYN_STDOUT_TAIL:-}"
|
|
13
|
+
stderr_tail="${OPENSYN_STDERR_TAIL:-}"
|
|
14
|
+
stdout_file=""
|
|
15
|
+
stderr_file=""
|
|
16
|
+
max_bytes="${OPENSYN_MAX_TAIL_BYTES:-4000}"
|
|
17
|
+
|
|
18
|
+
while [[ $# -gt 0 ]]; do
|
|
19
|
+
case "$1" in
|
|
20
|
+
--db)
|
|
21
|
+
db="$2"
|
|
22
|
+
shift 2
|
|
23
|
+
;;
|
|
24
|
+
--daemon-bin)
|
|
25
|
+
daemon_bin="$2"
|
|
26
|
+
shift 2
|
|
27
|
+
;;
|
|
28
|
+
--cwd)
|
|
29
|
+
cwd="$2"
|
|
30
|
+
shift 2
|
|
31
|
+
;;
|
|
32
|
+
--command-line)
|
|
33
|
+
command_line="$2"
|
|
34
|
+
shift 2
|
|
35
|
+
;;
|
|
36
|
+
--exit-code)
|
|
37
|
+
exit_code="$2"
|
|
38
|
+
shift 2
|
|
39
|
+
;;
|
|
40
|
+
--project-root)
|
|
41
|
+
project_root="$2"
|
|
42
|
+
shift 2
|
|
43
|
+
;;
|
|
44
|
+
--source-kind)
|
|
45
|
+
source_kind="$2"
|
|
46
|
+
shift 2
|
|
47
|
+
;;
|
|
48
|
+
--device-id)
|
|
49
|
+
device_id="$2"
|
|
50
|
+
shift 2
|
|
51
|
+
;;
|
|
52
|
+
--stdout-tail)
|
|
53
|
+
stdout_tail="$2"
|
|
54
|
+
shift 2
|
|
55
|
+
;;
|
|
56
|
+
--stderr-tail)
|
|
57
|
+
stderr_tail="$2"
|
|
58
|
+
shift 2
|
|
59
|
+
;;
|
|
60
|
+
--stdout-file)
|
|
61
|
+
stdout_file="$2"
|
|
62
|
+
shift 2
|
|
63
|
+
;;
|
|
64
|
+
--stderr-file)
|
|
65
|
+
stderr_file="$2"
|
|
66
|
+
shift 2
|
|
67
|
+
;;
|
|
68
|
+
--max-bytes)
|
|
69
|
+
max_bytes="$2"
|
|
70
|
+
shift 2
|
|
71
|
+
;;
|
|
72
|
+
*)
|
|
73
|
+
echo "unknown argument: $1" >&2
|
|
74
|
+
exit 2
|
|
75
|
+
;;
|
|
76
|
+
esac
|
|
77
|
+
done
|
|
78
|
+
|
|
79
|
+
if [[ -z "$db" || -z "$daemon_bin" || -z "$cwd" || -z "$command_line" || -z "$exit_code" ]]; then
|
|
80
|
+
echo "missing required arguments" >&2
|
|
81
|
+
exit 2
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
if [[ -z "$project_root" ]]; then
|
|
85
|
+
project_root="$cwd"
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
if [[ -z "$stdout_tail" && -n "$stdout_file" && -f "$stdout_file" ]]; then
|
|
89
|
+
stdout_tail="$(tail -c "$max_bytes" "$stdout_file" 2>/dev/null || true)"
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
if [[ -z "$stderr_tail" && -n "$stderr_file" && -f "$stderr_file" ]]; then
|
|
93
|
+
stderr_tail="$(tail -c "$max_bytes" "$stderr_file" 2>/dev/null || true)"
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
payload="$(
|
|
97
|
+
python3 - "$project_root" "$cwd" "$command_line" "$source_kind" "$exit_code" "$device_id" "$stdout_tail" "$stderr_tail" <<'PY'
|
|
98
|
+
import json
|
|
99
|
+
import shlex
|
|
100
|
+
import sys
|
|
101
|
+
|
|
102
|
+
project_root, cwd, command_line, source_kind, exit_code, device_id, stdout_tail, stderr_tail = sys.argv[1:]
|
|
103
|
+
parts = shlex.split(command_line)
|
|
104
|
+
command = parts[0] if parts else command_line
|
|
105
|
+
argv = parts[1:] if parts else []
|
|
106
|
+
payload = {
|
|
107
|
+
"project_root": project_root,
|
|
108
|
+
"cwd": cwd,
|
|
109
|
+
"command": command,
|
|
110
|
+
"argv": argv,
|
|
111
|
+
"source_kind": source_kind,
|
|
112
|
+
"exit_code": int(exit_code),
|
|
113
|
+
}
|
|
114
|
+
if stdout_tail:
|
|
115
|
+
payload["stdout_tail"] = stdout_tail
|
|
116
|
+
if stderr_tail:
|
|
117
|
+
payload["stderr_tail"] = stderr_tail
|
|
118
|
+
if device_id:
|
|
119
|
+
payload["device_id"] = device_id
|
|
120
|
+
print(json.dumps(payload, ensure_ascii=True))
|
|
121
|
+
PY
|
|
122
|
+
)"
|
|
123
|
+
|
|
124
|
+
"$daemon_bin" ingest-command-json --db "$db" --payload "$payload"
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
db=""
|
|
5
|
+
daemon_bin=""
|
|
6
|
+
cwd=""
|
|
7
|
+
project_root=""
|
|
8
|
+
source_kind="${OPENSYN_SOURCE_KIND:-host}"
|
|
9
|
+
device_id="${OPENSYN_DEVICE_ID:-}"
|
|
10
|
+
max_bytes="${OPENSYN_MAX_TAIL_BYTES:-4000}"
|
|
11
|
+
|
|
12
|
+
while [[ $# -gt 0 ]]; do
|
|
13
|
+
case "$1" in
|
|
14
|
+
--db)
|
|
15
|
+
db="$2"
|
|
16
|
+
shift 2
|
|
17
|
+
;;
|
|
18
|
+
--daemon-bin)
|
|
19
|
+
daemon_bin="$2"
|
|
20
|
+
shift 2
|
|
21
|
+
;;
|
|
22
|
+
--cwd)
|
|
23
|
+
cwd="$2"
|
|
24
|
+
shift 2
|
|
25
|
+
;;
|
|
26
|
+
--project-root)
|
|
27
|
+
project_root="$2"
|
|
28
|
+
shift 2
|
|
29
|
+
;;
|
|
30
|
+
--source-kind)
|
|
31
|
+
source_kind="$2"
|
|
32
|
+
shift 2
|
|
33
|
+
;;
|
|
34
|
+
--device-id)
|
|
35
|
+
device_id="$2"
|
|
36
|
+
shift 2
|
|
37
|
+
;;
|
|
38
|
+
--)
|
|
39
|
+
shift
|
|
40
|
+
break
|
|
41
|
+
;;
|
|
42
|
+
*)
|
|
43
|
+
echo "unknown argument: $1" >&2
|
|
44
|
+
exit 2
|
|
45
|
+
;;
|
|
46
|
+
esac
|
|
47
|
+
done
|
|
48
|
+
|
|
49
|
+
if [[ -z "$db" || -z "$daemon_bin" || -z "$cwd" || $# -eq 0 ]]; then
|
|
50
|
+
echo "missing required arguments" >&2
|
|
51
|
+
exit 2
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
if [[ -z "$project_root" ]]; then
|
|
55
|
+
project_root="$cwd"
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
tmpdir="$(mktemp -d)"
|
|
59
|
+
stdout_file="$tmpdir/stdout.log"
|
|
60
|
+
stderr_file="$tmpdir/stderr.log"
|
|
61
|
+
trap 'rm -rf "$tmpdir"' EXIT
|
|
62
|
+
|
|
63
|
+
command_line=""
|
|
64
|
+
for arg in "$@"; do
|
|
65
|
+
printf -v quoted '%q' "$arg"
|
|
66
|
+
command_line+="${command_line:+ }$quoted"
|
|
67
|
+
done
|
|
68
|
+
|
|
69
|
+
set +e
|
|
70
|
+
(
|
|
71
|
+
cd "$cwd"
|
|
72
|
+
"$@"
|
|
73
|
+
) \
|
|
74
|
+
> >(tee "$stdout_file") \
|
|
75
|
+
2> >(tee "$stderr_file" >&2)
|
|
76
|
+
exit_code=$?
|
|
77
|
+
set -e
|
|
78
|
+
|
|
79
|
+
helper_args=(
|
|
80
|
+
--db "$db"
|
|
81
|
+
--daemon-bin "$daemon_bin"
|
|
82
|
+
--project-root "$project_root"
|
|
83
|
+
--cwd "$cwd"
|
|
84
|
+
--source-kind "$source_kind"
|
|
85
|
+
--command-line "$command_line"
|
|
86
|
+
--exit-code "$exit_code"
|
|
87
|
+
--stdout-file "$stdout_file"
|
|
88
|
+
--stderr-file "$stderr_file"
|
|
89
|
+
--max-bytes "$max_bytes"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
if [[ -n "$device_id" ]]; then
|
|
93
|
+
helper_args+=(--device-id "$device_id")
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
"$(dirname "$0")/opensyn-ingest-command" \
|
|
97
|
+
"${helper_args[@]}"
|
|
98
|
+
|
|
99
|
+
exit "$exit_code"
|