@ramarivera/coding-agent-langfuse 0.1.29 → 0.1.30
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 +47 -0
- package/bin/coding-agent-langfuse.mjs +1 -1
- package/dist/backfill.d.ts +3 -1
- package/dist/backfill.js +1 -1
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +12 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/service.d.ts +35 -0
- package/dist/service.js +422 -0
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -34,3 +34,50 @@ npx @ramarivera/coding-agent-langfuse@latest \
|
|
|
34
34
|
|
|
35
35
|
The importer is fail-fast: the first failed OTLP POST stops the run, prints the
|
|
36
36
|
real network cause, and preserves local state so reruns resume cleanly.
|
|
37
|
+
|
|
38
|
+
## Follow as a host service
|
|
39
|
+
|
|
40
|
+
Install a live follower directly from npm. The generated service keeps inference
|
|
41
|
+
outside any gateway: agents keep calling their normal providers, while this tool
|
|
42
|
+
tails local histories and posts Langfuse OTLP traces.
|
|
43
|
+
|
|
44
|
+
Preview the service without touching the host:
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
npx @ramarivera/coding-agent-langfuse@latest service print \
|
|
48
|
+
--platform linux \
|
|
49
|
+
--agents codex,pi \
|
|
50
|
+
--endpoint https://langfuse.ai.roxasroot.net/otel/v1/traces
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Install and start it on the current host:
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
npx @ramarivera/coding-agent-langfuse@latest service install \
|
|
57
|
+
--agents codex,pi \
|
|
58
|
+
--endpoint https://langfuse.ai.roxasroot.net/otel/v1/traces
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The service installer supports:
|
|
62
|
+
|
|
63
|
+
- macOS: LaunchAgent under `~/Library/LaunchAgents`
|
|
64
|
+
- Linux: systemd user unit under `~/.config/systemd/user`
|
|
65
|
+
- Windows: scheduled task installer script under `%APPDATA%\\coding-agent-langfuse`
|
|
66
|
+
|
|
67
|
+
Use `--dry-run` to print the exact file and commands, `--no-start` to only write
|
|
68
|
+
the service file, and `service uninstall` to remove the service registration.
|
|
69
|
+
|
|
70
|
+
## Backfill windows
|
|
71
|
+
|
|
72
|
+
Backfill only a timeframe when repairing a host or replaying a recent window:
|
|
73
|
+
|
|
74
|
+
```sh
|
|
75
|
+
npx @ramarivera/coding-agent-langfuse@latest \
|
|
76
|
+
--agents claude,codex,grok,pi,opencode \
|
|
77
|
+
--since 2026-05-31T00:00:00Z \
|
|
78
|
+
--until 2026-06-01T00:00:00Z \
|
|
79
|
+
--endpoint https://langfuse.ai.roxasroot.net/otel/v1/traces
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Deduplication is state-file based and keyed by agent, session id, and source
|
|
83
|
+
record id. Reuse the same `--state` path for repeat repairs on a host.
|
package/dist/backfill.d.ts
CHANGED
|
@@ -60,6 +60,8 @@ type FollowSummary = RunSummary & {
|
|
|
60
60
|
iterations: number;
|
|
61
61
|
follow: true;
|
|
62
62
|
};
|
|
63
|
+
declare const allAgents: AgentName[];
|
|
64
|
+
declare const defaultEndpoint = "https://langfuse.ai.roxasroot.net/otel/v1/traces";
|
|
63
65
|
declare function parseArgs(argv: string[]): BackfillOptions;
|
|
64
66
|
declare function codexEvents(homeDir: string): BackfillEvent[];
|
|
65
67
|
declare function claudeEvents(homeDir: string): BackfillEvent[];
|
|
@@ -78,4 +80,4 @@ declare function discoverEvents(options: BackfillOptions): BackfillEvent[];
|
|
|
78
80
|
declare function run(options: BackfillOptions): Promise<RunSummary>;
|
|
79
81
|
declare function follow(options: BackfillOptions): Promise<FollowSummary>;
|
|
80
82
|
declare function main(argv?: string[]): Promise<RunSummary | FollowSummary>;
|
|
81
|
-
export { type BackfillEvent, type BackfillOptions, claudeEvents, codexEvents, discoverEvents, fingerprint, follow, grokEvents, main, opencodeEvents, parseArgs, piEvents, run, toOtlp, };
|
|
83
|
+
export { type BackfillEvent, type BackfillOptions, type AgentName, allAgents, claudeEvents, codexEvents, defaultEndpoint, discoverEvents, fingerprint, follow, grokEvents, main, opencodeEvents, parseArgs, piEvents, run, toOtlp, };
|
package/dist/backfill.js
CHANGED
|
@@ -1593,4 +1593,4 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
1593
1593
|
process.exit(1);
|
|
1594
1594
|
}
|
|
1595
1595
|
}
|
|
1596
|
-
export { claudeEvents, codexEvents, discoverEvents, fingerprint, follow, grokEvents, main, opencodeEvents, parseArgs, piEvents, run, toOtlp, };
|
|
1596
|
+
export { allAgents, claudeEvents, codexEvents, defaultEndpoint, discoverEvents, fingerprint, follow, grokEvents, main, opencodeEvents, parseArgs, piEvents, run, toOtlp, };
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { main as backfillMain } from "./backfill.js";
|
|
3
|
+
import { serviceMain } from "./service.js";
|
|
4
|
+
async function main(argv = process.argv.slice(2)) {
|
|
5
|
+
if (argv[0] === "service") {
|
|
6
|
+
await serviceMain(argv.slice(1));
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
await backfillMain(argv);
|
|
10
|
+
}
|
|
11
|
+
await main();
|
|
12
|
+
export { main };
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
type AgentName = "claude" | "codex" | "grok" | "opencode" | "pi";
|
|
2
|
+
type ServicePlatform = "darwin" | "linux" | "win32";
|
|
3
|
+
type ServiceAction = "install" | "uninstall" | "print";
|
|
4
|
+
type ServiceOptions = {
|
|
5
|
+
action: ServiceAction;
|
|
6
|
+
platform: ServicePlatform;
|
|
7
|
+
agents: AgentName[];
|
|
8
|
+
endpoint: string;
|
|
9
|
+
statePath: string;
|
|
10
|
+
homeDir: string;
|
|
11
|
+
name: string;
|
|
12
|
+
packageSpec: string;
|
|
13
|
+
batchSize: number;
|
|
14
|
+
pollIntervalMs: number;
|
|
15
|
+
postDelayMs: number;
|
|
16
|
+
since?: string;
|
|
17
|
+
dryRun: boolean;
|
|
18
|
+
start: boolean;
|
|
19
|
+
workingDirectory: string;
|
|
20
|
+
pathEnv: string;
|
|
21
|
+
};
|
|
22
|
+
type ServicePlan = {
|
|
23
|
+
platform: ServicePlatform;
|
|
24
|
+
action: ServiceAction;
|
|
25
|
+
name: string;
|
|
26
|
+
path: string;
|
|
27
|
+
content?: string;
|
|
28
|
+
command: string[];
|
|
29
|
+
postInstallCommands: string[][];
|
|
30
|
+
uninstallCommands: string[][];
|
|
31
|
+
};
|
|
32
|
+
declare function parseServiceArgs(argv: string[]): ServiceOptions;
|
|
33
|
+
declare function buildServicePlan(options: ServiceOptions): ServicePlan;
|
|
34
|
+
declare function serviceMain(argv?: string[]): Promise<ServicePlan>;
|
|
35
|
+
export { type ServiceOptions, type ServicePlan, buildServicePlan, parseServiceArgs, serviceMain, };
|
package/dist/service.js
ADDED
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import { homedir, platform as osPlatform } from "node:os";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
const defaultPackageSpec = "@ramarivera/coding-agent-langfuse@latest";
|
|
6
|
+
const defaultEndpoint = "https://langfuse.ai.roxasroot.net/otel/v1/traces";
|
|
7
|
+
const allAgents = ["claude", "codex", "grok", "opencode", "pi"];
|
|
8
|
+
function serviceUsage() {
|
|
9
|
+
return `Usage:
|
|
10
|
+
coding-agent-langfuse service install [options]
|
|
11
|
+
coding-agent-langfuse service print [options]
|
|
12
|
+
coding-agent-langfuse service uninstall [options]
|
|
13
|
+
|
|
14
|
+
Service options:
|
|
15
|
+
--platform NAME Target platform: darwin, linux, win32 (default: current)
|
|
16
|
+
--name NAME Service name/label/unit name
|
|
17
|
+
--agents LIST Comma-separated agents: claude,codex,grok,opencode,pi
|
|
18
|
+
--endpoint URL OTLP HTTP traces endpoint (default: ${defaultEndpoint})
|
|
19
|
+
--state PATH Dedupe state file
|
|
20
|
+
--home PATH Home directory to scan (default: current user home)
|
|
21
|
+
--package-spec SPEC npx package spec (default: ${defaultPackageSpec})
|
|
22
|
+
--batch-size N OTLP spans per POST (default: 10)
|
|
23
|
+
--poll-interval-ms N Delay between --follow scans (default: 5000)
|
|
24
|
+
--post-delay-ms N Delay after each successful OTLP POST (default: 0)
|
|
25
|
+
--since ISO_OR_MS Optional lower bound for events the follower may send
|
|
26
|
+
--working-directory DIR Directory the service starts in (default: --home)
|
|
27
|
+
--path VALUE PATH value injected into the service environment
|
|
28
|
+
--dry-run Print the service plan without writing or running commands
|
|
29
|
+
--no-start Write the service but do not enable/start it
|
|
30
|
+
--help Show this help
|
|
31
|
+
`;
|
|
32
|
+
}
|
|
33
|
+
function parseServiceArgs(argv) {
|
|
34
|
+
const action = parseServiceAction(argv[0]);
|
|
35
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
36
|
+
console.log(serviceUsage());
|
|
37
|
+
process.exit(0);
|
|
38
|
+
}
|
|
39
|
+
let platform = currentServicePlatform();
|
|
40
|
+
let agents = [...allAgents];
|
|
41
|
+
let endpoint = process.env.LANGFUSE_BACKFILL_ENDPOINT ?? defaultEndpoint;
|
|
42
|
+
let homeDir = process.env.HOME ?? homedir();
|
|
43
|
+
let statePath = "";
|
|
44
|
+
let name = "";
|
|
45
|
+
let packageSpec = defaultPackageSpec;
|
|
46
|
+
let batchSize = 10;
|
|
47
|
+
let pollIntervalMs = 5_000;
|
|
48
|
+
let postDelayMs = 0;
|
|
49
|
+
let since;
|
|
50
|
+
let dryRun = false;
|
|
51
|
+
let start = true;
|
|
52
|
+
let workingDirectory = "";
|
|
53
|
+
let pathEnv = "";
|
|
54
|
+
for (let i = 1; i < argv.length; i++) {
|
|
55
|
+
const arg = argv[i];
|
|
56
|
+
const next = () => {
|
|
57
|
+
const value = argv[++i];
|
|
58
|
+
if (!value)
|
|
59
|
+
throw new Error(`Missing value for ${arg}`);
|
|
60
|
+
return value;
|
|
61
|
+
};
|
|
62
|
+
if (arg === "--platform") {
|
|
63
|
+
platform = parsePlatform(next());
|
|
64
|
+
}
|
|
65
|
+
else if (arg === "--name") {
|
|
66
|
+
name = next();
|
|
67
|
+
}
|
|
68
|
+
else if (arg === "--agents") {
|
|
69
|
+
agents = parseAgents(next());
|
|
70
|
+
}
|
|
71
|
+
else if (arg === "--endpoint") {
|
|
72
|
+
endpoint = next();
|
|
73
|
+
}
|
|
74
|
+
else if (arg === "--state") {
|
|
75
|
+
statePath = next();
|
|
76
|
+
}
|
|
77
|
+
else if (arg === "--home") {
|
|
78
|
+
homeDir = next();
|
|
79
|
+
}
|
|
80
|
+
else if (arg === "--package-spec") {
|
|
81
|
+
packageSpec = next();
|
|
82
|
+
}
|
|
83
|
+
else if (arg === "--batch-size") {
|
|
84
|
+
batchSize = parsePositiveInt(arg, next());
|
|
85
|
+
}
|
|
86
|
+
else if (arg === "--poll-interval-ms") {
|
|
87
|
+
pollIntervalMs = parsePositiveInt(arg, next());
|
|
88
|
+
}
|
|
89
|
+
else if (arg === "--post-delay-ms") {
|
|
90
|
+
postDelayMs = parseNonNegativeInt(arg, next());
|
|
91
|
+
}
|
|
92
|
+
else if (arg === "--since") {
|
|
93
|
+
since = next();
|
|
94
|
+
}
|
|
95
|
+
else if (arg === "--working-directory") {
|
|
96
|
+
workingDirectory = next();
|
|
97
|
+
}
|
|
98
|
+
else if (arg === "--path") {
|
|
99
|
+
pathEnv = next();
|
|
100
|
+
}
|
|
101
|
+
else if (arg === "--dry-run") {
|
|
102
|
+
dryRun = true;
|
|
103
|
+
}
|
|
104
|
+
else if (arg === "--no-start") {
|
|
105
|
+
start = false;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
throw new Error(`Unknown service argument '${arg}'`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (agents.length === 0)
|
|
112
|
+
throw new Error("--agents must include at least one agent");
|
|
113
|
+
name ||= defaultServiceName(agents, platform);
|
|
114
|
+
workingDirectory ||= homeDir;
|
|
115
|
+
pathEnv ||= defaultPathEnv(homeDir, platform);
|
|
116
|
+
statePath ||= join(homeDir, ".local/state/coding-agent-langfuse", `${name}.json`);
|
|
117
|
+
return {
|
|
118
|
+
action,
|
|
119
|
+
platform,
|
|
120
|
+
agents,
|
|
121
|
+
endpoint,
|
|
122
|
+
statePath,
|
|
123
|
+
homeDir,
|
|
124
|
+
name,
|
|
125
|
+
packageSpec,
|
|
126
|
+
batchSize,
|
|
127
|
+
pollIntervalMs,
|
|
128
|
+
postDelayMs,
|
|
129
|
+
since,
|
|
130
|
+
dryRun,
|
|
131
|
+
start,
|
|
132
|
+
workingDirectory,
|
|
133
|
+
pathEnv,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function buildServicePlan(options) {
|
|
137
|
+
const command = buildFollowCommand(options);
|
|
138
|
+
if (options.platform === "darwin") {
|
|
139
|
+
const path = join(options.homeDir, "Library/LaunchAgents", `${options.name}.plist`);
|
|
140
|
+
return {
|
|
141
|
+
platform: options.platform,
|
|
142
|
+
action: options.action,
|
|
143
|
+
name: options.name,
|
|
144
|
+
path,
|
|
145
|
+
content: renderLaunchdPlist(options, command),
|
|
146
|
+
command,
|
|
147
|
+
postInstallCommands: options.start
|
|
148
|
+
? [
|
|
149
|
+
["launchctl", "bootstrap", `gui/${process.getuid?.() ?? 501}`, path],
|
|
150
|
+
["launchctl", "kickstart", "-k", `gui/${process.getuid?.() ?? 501}/${options.name}`],
|
|
151
|
+
]
|
|
152
|
+
: [],
|
|
153
|
+
uninstallCommands: [
|
|
154
|
+
["launchctl", "bootout", `gui/${process.getuid?.() ?? 501}`, path],
|
|
155
|
+
],
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
if (options.platform === "linux") {
|
|
159
|
+
const path = join(options.homeDir, ".config/systemd/user", `${options.name}.service`);
|
|
160
|
+
return {
|
|
161
|
+
platform: options.platform,
|
|
162
|
+
action: options.action,
|
|
163
|
+
name: options.name,
|
|
164
|
+
path,
|
|
165
|
+
content: renderSystemdUnit(options, command),
|
|
166
|
+
command,
|
|
167
|
+
postInstallCommands: options.start
|
|
168
|
+
? [
|
|
169
|
+
["systemctl", "--user", "daemon-reload"],
|
|
170
|
+
["systemctl", "--user", "enable", "--now", `${options.name}.service`],
|
|
171
|
+
]
|
|
172
|
+
: [["systemctl", "--user", "daemon-reload"]],
|
|
173
|
+
uninstallCommands: [
|
|
174
|
+
["systemctl", "--user", "disable", "--now", `${options.name}.service`],
|
|
175
|
+
["systemctl", "--user", "daemon-reload"],
|
|
176
|
+
],
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
const path = join(process.env.APPDATA ?? join(options.homeDir, "AppData/Roaming"), "coding-agent-langfuse", `${options.name}.ps1`);
|
|
180
|
+
const taskName = `CodingAgentLangfuse-${options.name}`;
|
|
181
|
+
return {
|
|
182
|
+
platform: options.platform,
|
|
183
|
+
action: options.action,
|
|
184
|
+
name: options.name,
|
|
185
|
+
path,
|
|
186
|
+
content: renderWindowsScript(command),
|
|
187
|
+
command,
|
|
188
|
+
postInstallCommands: options.start
|
|
189
|
+
? [
|
|
190
|
+
[
|
|
191
|
+
"powershell.exe",
|
|
192
|
+
"-NoProfile",
|
|
193
|
+
"-ExecutionPolicy",
|
|
194
|
+
"Bypass",
|
|
195
|
+
"-File",
|
|
196
|
+
path,
|
|
197
|
+
"-Install",
|
|
198
|
+
"-TaskName",
|
|
199
|
+
taskName,
|
|
200
|
+
],
|
|
201
|
+
]
|
|
202
|
+
: [],
|
|
203
|
+
uninstallCommands: [
|
|
204
|
+
[
|
|
205
|
+
"powershell.exe",
|
|
206
|
+
"-NoProfile",
|
|
207
|
+
"-Command",
|
|
208
|
+
`Unregister-ScheduledTask -TaskName ${powershellString(taskName)} -Confirm:$false -ErrorAction SilentlyContinue`,
|
|
209
|
+
],
|
|
210
|
+
],
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
async function serviceMain(argv = process.argv.slice(2)) {
|
|
214
|
+
const options = parseServiceArgs(argv);
|
|
215
|
+
const plan = buildServicePlan(options);
|
|
216
|
+
if (options.action === "print" || options.dryRun) {
|
|
217
|
+
console.log(JSON.stringify(plan, null, 2));
|
|
218
|
+
return plan;
|
|
219
|
+
}
|
|
220
|
+
if (options.action === "install") {
|
|
221
|
+
if (!plan.content)
|
|
222
|
+
throw new Error("Service plan is missing content");
|
|
223
|
+
mkdirSync(dirname(plan.path), { recursive: true });
|
|
224
|
+
writeFileSync(plan.path, plan.content);
|
|
225
|
+
runCommands(plan.postInstallCommands);
|
|
226
|
+
console.log(JSON.stringify(plan, null, 2));
|
|
227
|
+
return plan;
|
|
228
|
+
}
|
|
229
|
+
runCommands(plan.uninstallCommands, { ignoreFailure: true });
|
|
230
|
+
if (existsSync(plan.path))
|
|
231
|
+
rmSync(plan.path);
|
|
232
|
+
console.log(JSON.stringify(plan, null, 2));
|
|
233
|
+
return plan;
|
|
234
|
+
}
|
|
235
|
+
function buildFollowCommand(options) {
|
|
236
|
+
const command = [
|
|
237
|
+
options.platform === "win32" ? "npx.cmd" : "npx",
|
|
238
|
+
"--yes",
|
|
239
|
+
options.packageSpec,
|
|
240
|
+
"--agents",
|
|
241
|
+
options.agents.join(","),
|
|
242
|
+
"--endpoint",
|
|
243
|
+
options.endpoint,
|
|
244
|
+
"--state",
|
|
245
|
+
options.statePath,
|
|
246
|
+
"--home",
|
|
247
|
+
options.homeDir,
|
|
248
|
+
"--batch-size",
|
|
249
|
+
String(options.batchSize),
|
|
250
|
+
"--poll-interval-ms",
|
|
251
|
+
String(options.pollIntervalMs),
|
|
252
|
+
"--post-delay-ms",
|
|
253
|
+
String(options.postDelayMs),
|
|
254
|
+
"--follow",
|
|
255
|
+
];
|
|
256
|
+
if (options.since)
|
|
257
|
+
command.push("--since", options.since);
|
|
258
|
+
return command;
|
|
259
|
+
}
|
|
260
|
+
function renderSystemdUnit(options, command) {
|
|
261
|
+
return `[Unit]
|
|
262
|
+
Description=Coding Agent Langfuse follower (${options.agents.join(",")})
|
|
263
|
+
After=network-online.target
|
|
264
|
+
Wants=network-online.target
|
|
265
|
+
|
|
266
|
+
[Service]
|
|
267
|
+
Type=simple
|
|
268
|
+
ExecStart=${systemdCommand(command)}
|
|
269
|
+
Restart=always
|
|
270
|
+
RestartSec=15
|
|
271
|
+
StartLimitIntervalSec=60
|
|
272
|
+
StartLimitBurst=10
|
|
273
|
+
WorkingDirectory=${systemdQuote(options.workingDirectory)}
|
|
274
|
+
Environment=${systemdQuote(`PATH=${options.pathEnv}`)}
|
|
275
|
+
Environment=LANGFUSE_BACKFILL_ENDPOINT=${options.endpoint}
|
|
276
|
+
|
|
277
|
+
[Install]
|
|
278
|
+
WantedBy=default.target
|
|
279
|
+
`;
|
|
280
|
+
}
|
|
281
|
+
function renderLaunchdPlist(options, command) {
|
|
282
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
283
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
284
|
+
<plist version="1.0">
|
|
285
|
+
<dict>
|
|
286
|
+
<key>Label</key>
|
|
287
|
+
<string>${escapeXml(options.name)}</string>
|
|
288
|
+
<key>ProgramArguments</key>
|
|
289
|
+
<array>
|
|
290
|
+
${command.map((part) => ` <string>${escapeXml(part)}</string>`).join("\n")}
|
|
291
|
+
</array>
|
|
292
|
+
<key>EnvironmentVariables</key>
|
|
293
|
+
<dict>
|
|
294
|
+
<key>PATH</key>
|
|
295
|
+
<string>${escapeXml(options.pathEnv)}</string>
|
|
296
|
+
<key>LANGFUSE_BACKFILL_ENDPOINT</key>
|
|
297
|
+
<string>${escapeXml(options.endpoint)}</string>
|
|
298
|
+
</dict>
|
|
299
|
+
<key>WorkingDirectory</key>
|
|
300
|
+
<string>${escapeXml(options.workingDirectory)}</string>
|
|
301
|
+
<key>RunAtLoad</key>
|
|
302
|
+
<true/>
|
|
303
|
+
<key>KeepAlive</key>
|
|
304
|
+
<true/>
|
|
305
|
+
<key>StandardOutPath</key>
|
|
306
|
+
<string>${escapeXml(join(options.homeDir, "Library/Logs", `${options.name}.out.log`))}</string>
|
|
307
|
+
<key>StandardErrorPath</key>
|
|
308
|
+
<string>${escapeXml(join(options.homeDir, "Library/Logs", `${options.name}.err.log`))}</string>
|
|
309
|
+
</dict>
|
|
310
|
+
</plist>
|
|
311
|
+
`;
|
|
312
|
+
}
|
|
313
|
+
function renderWindowsScript(command) {
|
|
314
|
+
const commandArray = command.map(powershellString).join(", ");
|
|
315
|
+
return `param(
|
|
316
|
+
[switch]$Install,
|
|
317
|
+
[string]$TaskName = "CodingAgentLangfuse"
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
$Command = @(${commandArray})
|
|
321
|
+
$Action = New-ScheduledTaskAction -Execute $Command[0] -Argument (($Command | Select-Object -Skip 1) -join " ")
|
|
322
|
+
$Trigger = New-ScheduledTaskTrigger -AtLogOn
|
|
323
|
+
$Settings = New-ScheduledTaskSettingsSet -RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1)
|
|
324
|
+
|
|
325
|
+
if ($Install) {
|
|
326
|
+
Register-ScheduledTask -TaskName $TaskName -Action $Action -Trigger $Trigger -Settings $Settings -Force | Out-Null
|
|
327
|
+
Start-ScheduledTask -TaskName $TaskName
|
|
328
|
+
}
|
|
329
|
+
`;
|
|
330
|
+
}
|
|
331
|
+
function parseServiceAction(value) {
|
|
332
|
+
if (value === "install" || value === "uninstall" || value === "print")
|
|
333
|
+
return value;
|
|
334
|
+
throw new Error(`Expected service action install, uninstall, or print; got '${value ?? ""}'`);
|
|
335
|
+
}
|
|
336
|
+
function parsePlatform(value) {
|
|
337
|
+
if (value === "darwin" || value === "linux" || value === "win32")
|
|
338
|
+
return value;
|
|
339
|
+
throw new Error(`Unsupported platform '${value}'`);
|
|
340
|
+
}
|
|
341
|
+
function currentServicePlatform() {
|
|
342
|
+
return parsePlatform(osPlatform());
|
|
343
|
+
}
|
|
344
|
+
function parseAgents(value) {
|
|
345
|
+
return value.split(",").map((item) => {
|
|
346
|
+
const agent = item.trim();
|
|
347
|
+
if (!allAgents.includes(agent))
|
|
348
|
+
throw new Error(`Unknown agent '${item}'`);
|
|
349
|
+
return agent;
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
function defaultServiceName(agents, platform) {
|
|
353
|
+
const suffix = agents.join("-");
|
|
354
|
+
return platform === "darwin"
|
|
355
|
+
? `net.roxasroot.coding-agent-langfuse.${suffix}`
|
|
356
|
+
: `coding-agent-langfuse-${suffix}`;
|
|
357
|
+
}
|
|
358
|
+
function defaultPathEnv(homeDir, platform) {
|
|
359
|
+
if (platform === "win32") {
|
|
360
|
+
return [
|
|
361
|
+
"%APPDATA%\\npm",
|
|
362
|
+
"%ProgramFiles%\\nodejs",
|
|
363
|
+
"%SystemRoot%\\System32",
|
|
364
|
+
"%SystemRoot%",
|
|
365
|
+
].join(";");
|
|
366
|
+
}
|
|
367
|
+
return [
|
|
368
|
+
join(homeDir, ".local/share/mise/shims"),
|
|
369
|
+
join(homeDir, ".local/bin"),
|
|
370
|
+
"/opt/homebrew/bin",
|
|
371
|
+
"/usr/local/bin",
|
|
372
|
+
"/usr/bin",
|
|
373
|
+
"/bin",
|
|
374
|
+
"/usr/sbin",
|
|
375
|
+
"/sbin",
|
|
376
|
+
].join(":");
|
|
377
|
+
}
|
|
378
|
+
function parsePositiveInt(flag, value) {
|
|
379
|
+
const parsed = Number.parseInt(value, 10);
|
|
380
|
+
if (!Number.isFinite(parsed) || parsed < 1) {
|
|
381
|
+
throw new Error(`${flag} must be a positive integer`);
|
|
382
|
+
}
|
|
383
|
+
return parsed;
|
|
384
|
+
}
|
|
385
|
+
function parseNonNegativeInt(flag, value) {
|
|
386
|
+
const parsed = Number.parseInt(value, 10);
|
|
387
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
388
|
+
throw new Error(`${flag} must be a non-negative integer`);
|
|
389
|
+
}
|
|
390
|
+
return parsed;
|
|
391
|
+
}
|
|
392
|
+
function runCommands(commands, options = {}) {
|
|
393
|
+
for (const command of commands) {
|
|
394
|
+
try {
|
|
395
|
+
execFileSync(command[0], command.slice(1), { stdio: "inherit" });
|
|
396
|
+
}
|
|
397
|
+
catch (error) {
|
|
398
|
+
if (!options.ignoreFailure)
|
|
399
|
+
throw error;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
function systemdCommand(command) {
|
|
404
|
+
return ["/usr/bin/env", ...command].map(systemdQuote).join(" ");
|
|
405
|
+
}
|
|
406
|
+
function systemdQuote(value) {
|
|
407
|
+
if (/^[A-Za-z0-9_@%+=:,./-]+$/.test(value))
|
|
408
|
+
return value;
|
|
409
|
+
return `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
|
|
410
|
+
}
|
|
411
|
+
function powershellString(value) {
|
|
412
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
413
|
+
}
|
|
414
|
+
function escapeXml(value) {
|
|
415
|
+
return value
|
|
416
|
+
.replaceAll("&", "&")
|
|
417
|
+
.replaceAll("<", "<")
|
|
418
|
+
.replaceAll(">", ">")
|
|
419
|
+
.replaceAll('"', """)
|
|
420
|
+
.replaceAll("'", "'");
|
|
421
|
+
}
|
|
422
|
+
export { buildServicePlan, parseServiceArgs, serviceMain, };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ramarivera/coding-agent-langfuse",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.30",
|
|
4
4
|
"description": "Universal coding-agent Langfuse backfiller and live OTLP helpers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
},
|
|
12
12
|
"exports": {
|
|
13
13
|
".": "./dist/index.js",
|
|
14
|
-
"./backfill": "./dist/backfill.js"
|
|
14
|
+
"./backfill": "./dist/backfill.js",
|
|
15
|
+
"./service": "./dist/service.js"
|
|
15
16
|
},
|
|
16
17
|
"files": [
|
|
17
18
|
"bin",
|
|
@@ -23,7 +24,7 @@
|
|
|
23
24
|
"build": "tsc -p tsconfig.build.json",
|
|
24
25
|
"check": "tsc --noEmit",
|
|
25
26
|
"test": "node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON --experimental-strip-types --test test/**/*.test.ts",
|
|
26
|
-
"test:e2e": "node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON --experimental-strip-types --test e2e/test/**/*.test.ts",
|
|
27
|
+
"test:e2e": "npm run build && node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON --experimental-strip-types --test e2e/test/**/*.test.ts",
|
|
27
28
|
"pack:dry-run": "npm pack --dry-run",
|
|
28
29
|
"prepack": "npm run build"
|
|
29
30
|
},
|