@exaudeus/workrail 3.32.0 → 3.33.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/dist/cli/commands/index.d.ts +1 -0
- package/dist/cli/commands/index.js +3 -1
- package/dist/cli/commands/worktrain-await.js +11 -9
- package/dist/cli/commands/worktrain-daemon-install.d.ts +35 -0
- package/dist/cli/commands/worktrain-daemon-install.js +291 -0
- package/dist/cli/commands/worktrain-daemon.d.ts +31 -0
- package/dist/cli/commands/worktrain-daemon.js +272 -0
- package/dist/cli/commands/worktrain-spawn.js +11 -9
- package/dist/cli-worktrain.js +329 -0
- package/dist/cli.js +1 -22
- package/dist/console/standalone-console.d.ts +28 -0
- package/dist/console/standalone-console.js +142 -0
- package/dist/{console/assets/index-Cb_LO718.js → console-ui/assets/index-BuJFLLfY.js} +1 -1
- package/dist/{console → console-ui}/index.html +1 -1
- package/dist/daemon/agent-loop.d.ts +26 -0
- package/dist/daemon/agent-loop.js +39 -1
- package/dist/daemon/daemon-events.d.ts +47 -1
- package/dist/daemon/workflow-runner.d.ts +3 -2
- package/dist/daemon/workflow-runner.js +205 -41
- package/dist/infrastructure/session/HttpServer.js +133 -34
- package/dist/manifest.json +118 -62
- package/dist/mcp/output-schemas.d.ts +30 -30
- package/dist/mcp/transports/bridge-events.d.ts +4 -0
- package/dist/mcp/transports/fatal-exit.js +4 -0
- package/dist/mcp/transports/http-entry.js +2 -0
- package/dist/mcp/transports/stdio-entry.js +26 -6
- package/dist/mcp/v2/tools.d.ts +4 -4
- package/dist/trigger/adapters/github-poller.d.ts +44 -0
- package/dist/trigger/adapters/github-poller.js +190 -0
- package/dist/trigger/adapters/gitlab-poller.d.ts +27 -0
- package/dist/trigger/adapters/gitlab-poller.js +81 -0
- package/dist/trigger/index.d.ts +4 -1
- package/dist/trigger/index.js +5 -1
- package/dist/trigger/polled-event-store.d.ts +22 -0
- package/dist/trigger/polled-event-store.js +173 -0
- package/dist/trigger/polling-scheduler.d.ts +20 -0
- package/dist/trigger/polling-scheduler.js +249 -0
- package/dist/trigger/trigger-listener.d.ts +3 -0
- package/dist/trigger/trigger-listener.js +47 -3
- package/dist/trigger/trigger-store.js +114 -33
- package/dist/trigger/types.d.ts +17 -1
- package/dist/v2/durable-core/schemas/export-bundle/index.d.ts +224 -224
- package/dist/v2/durable-core/schemas/session/events.d.ts +42 -42
- package/dist/v2/durable-core/schemas/session/manifest.d.ts +6 -6
- package/dist/v2/durable-core/schemas/session/validation-event.d.ts +2 -2
- package/dist/v2/durable-core/tokens/payloads.d.ts +52 -52
- package/dist/v2/usecases/console-routes.js +3 -3
- package/dist/v2/usecases/console-service.js +133 -9
- package/dist/v2/usecases/console-types.d.ts +7 -0
- package/docs/design/daemon-conversation-logging-plan.md +98 -0
- package/docs/design/daemon-conversation-logging-review.md +55 -0
- package/docs/design/daemon-conversation-logging.md +129 -0
- package/docs/design/github-polling-adapter-design-candidates.md +226 -0
- package/docs/design/github-polling-adapter-design-review-findings.md +131 -0
- package/docs/design/github-polling-adapter-implementation-plan.md +284 -0
- package/docs/design/implementation_plan.md +192 -0
- package/docs/design/workflow-id-validation-at-startup.md +146 -0
- package/docs/design/workflow-id-validation-design-review.md +87 -0
- package/docs/design/workflow-id-validation-implementation-plan.md +185 -0
- package/docs/design/worktrain-system-prompt-report-issue-candidates.md +135 -0
- package/docs/design/worktrain-system-prompt-report-issue-design-review.md +73 -0
- package/docs/ideas/backlog.md +361 -0
- package/package.json +1 -1
- package/workflows/architecture-scalability-audit.json +1 -1
- package/workflows/bug-investigation.agentic.v2.json +3 -3
- package/workflows/coding-task-workflow-agentic.json +32 -32
- package/workflows/coding-task-workflow-agentic.lean.v2.json +1 -1
- package/workflows/coding-task-workflow-agentic.v2.json +7 -7
- package/workflows/mr-review-workflow.agentic.v2.json +21 -12
- package/workflows/personal-learning-materials-creation-branched.json +2 -2
- package/workflows/production-readiness-audit.json +1 -1
- package/workflows/relocation-workflow-us.json +2 -2
- package/workflows/ui-ux-design-workflow.json +14 -14
- package/workflows/workflow-for-workflows.json +3 -3
- package/workflows/workflow-for-workflows.v2.json +2 -2
- package/workflows/wr.discovery.json +1 -1
- /package/dist/{console → console-ui}/assets/index-8dh0Psu-.css +0 -0
|
@@ -11,3 +11,4 @@ export { executeWorktrainTellCommand, type Priority, type QueuedMessage, type Wo
|
|
|
11
11
|
export { executeWorktrainInboxCommand, type OutboxMessage, type InboxCursor, type WorktrainInboxCommandDeps, type WorktrainInboxCommandOpts, } from './worktrain-inbox.js';
|
|
12
12
|
export { executeWorktrainSpawnCommand, type WorktrainSpawnCommandDeps, type WorktrainSpawnCommandOpts, } from './worktrain-spawn.js';
|
|
13
13
|
export { executeWorktrainAwaitCommand, parseDurationMs, type WorktrainAwaitCommandDeps, type WorktrainAwaitCommandOpts, type SessionOutcome, type SessionResult, type AwaitResult, } from './worktrain-await.js';
|
|
14
|
+
export { executeWorktrainDaemonCommand, type WorktrainDaemonCommandDeps, type WorktrainDaemonCommandOpts, } from './worktrain-daemon.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.parseDurationMs = exports.executeWorktrainAwaitCommand = exports.executeWorktrainSpawnCommand = exports.executeWorktrainInboxCommand = exports.executeWorktrainTellCommand = exports.executeWorktrainInitCommand = exports.detectWorkflowVersion = exports.migrateWorkflowFile = exports.migrateWorkflow = exports.executeMigrateCommand = exports.executeVersionCommand = exports.executeCleanupCommand = exports.executeStartCommand = exports.executeValidateCommand = exports.executeListCommand = exports.getWorkflowSources = exports.executeSourcesCommand = exports.executeInitConfigCommand = exports.executeInitCommand = void 0;
|
|
3
|
+
exports.executeWorktrainDaemonCommand = exports.parseDurationMs = exports.executeWorktrainAwaitCommand = exports.executeWorktrainSpawnCommand = exports.executeWorktrainInboxCommand = exports.executeWorktrainTellCommand = exports.executeWorktrainInitCommand = exports.detectWorkflowVersion = exports.migrateWorkflowFile = exports.migrateWorkflow = exports.executeMigrateCommand = exports.executeVersionCommand = exports.executeCleanupCommand = exports.executeStartCommand = exports.executeValidateCommand = exports.executeListCommand = exports.getWorkflowSources = exports.executeSourcesCommand = exports.executeInitConfigCommand = exports.executeInitCommand = void 0;
|
|
4
4
|
var init_js_1 = require("./init.js");
|
|
5
5
|
Object.defineProperty(exports, "executeInitCommand", { enumerable: true, get: function () { return init_js_1.executeInitCommand; } });
|
|
6
6
|
Object.defineProperty(exports, "executeInitConfigCommand", { enumerable: true, get: function () { return init_js_1.executeInitConfigCommand; } });
|
|
@@ -33,3 +33,5 @@ Object.defineProperty(exports, "executeWorktrainSpawnCommand", { enumerable: tru
|
|
|
33
33
|
var worktrain_await_js_1 = require("./worktrain-await.js");
|
|
34
34
|
Object.defineProperty(exports, "executeWorktrainAwaitCommand", { enumerable: true, get: function () { return worktrain_await_js_1.executeWorktrainAwaitCommand; } });
|
|
35
35
|
Object.defineProperty(exports, "parseDurationMs", { enumerable: true, get: function () { return worktrain_await_js_1.parseDurationMs; } });
|
|
36
|
+
var worktrain_daemon_js_1 = require("./worktrain-daemon.js");
|
|
37
|
+
Object.defineProperty(exports, "executeWorktrainDaemonCommand", { enumerable: true, get: function () { return worktrain_daemon_js_1.executeWorktrainDaemonCommand; } });
|
|
@@ -4,7 +4,7 @@ exports.parseDurationMs = parseDurationMs;
|
|
|
4
4
|
exports.executeWorktrainAwaitCommand = executeWorktrainAwaitCommand;
|
|
5
5
|
const cli_result_js_1 = require("../types/cli-result.js");
|
|
6
6
|
const DEFAULT_CONSOLE_PORT = 3456;
|
|
7
|
-
const
|
|
7
|
+
const LOCK_FILE_NAMES = ['daemon-console.lock', 'dashboard.lock'];
|
|
8
8
|
const DEFAULT_TIMEOUT_MS = 30 * 60 * 1000;
|
|
9
9
|
const DEFAULT_POLL_INTERVAL_MS = 3000;
|
|
10
10
|
function parseDurationMs(raw) {
|
|
@@ -34,15 +34,17 @@ async function discoverConsolePort(deps, portOverride) {
|
|
|
34
34
|
if (portOverride !== undefined && portOverride > 0) {
|
|
35
35
|
return portOverride;
|
|
36
36
|
}
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
for (const lockFileName of LOCK_FILE_NAMES) {
|
|
38
|
+
const lockPath = deps.joinPath(deps.homedir(), '.workrail', lockFileName);
|
|
39
|
+
try {
|
|
40
|
+
const raw = await deps.readFile(lockPath);
|
|
41
|
+
const parsed = JSON.parse(raw);
|
|
42
|
+
if (typeof parsed.port === 'number' && parsed.port > 0) {
|
|
43
|
+
return parsed.port;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
43
47
|
}
|
|
44
|
-
}
|
|
45
|
-
catch {
|
|
46
48
|
}
|
|
47
49
|
return DEFAULT_CONSOLE_PORT;
|
|
48
50
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { CliResult } from '../types/cli-result.js';
|
|
2
|
+
export interface WorktrainDaemonInstallCommandDeps {
|
|
3
|
+
readonly mkdir: (path: string, opts: {
|
|
4
|
+
recursive: boolean;
|
|
5
|
+
}) => Promise<void>;
|
|
6
|
+
readonly writeFile: (path: string, content: string) => Promise<void>;
|
|
7
|
+
readonly readFile: (path: string) => Promise<string>;
|
|
8
|
+
readonly unlink: (path: string) => Promise<void>;
|
|
9
|
+
readonly exists: (path: string) => Promise<boolean>;
|
|
10
|
+
readonly homedir: () => string;
|
|
11
|
+
readonly joinPath: (...paths: string[]) => string;
|
|
12
|
+
readonly resolveCliScript: () => string;
|
|
13
|
+
readonly nodeExecPath: () => string;
|
|
14
|
+
readonly execLaunchctl: (args: readonly string[]) => Promise<{
|
|
15
|
+
readonly ok: boolean;
|
|
16
|
+
readonly stderr: string;
|
|
17
|
+
}>;
|
|
18
|
+
readonly platform: string;
|
|
19
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
20
|
+
readonly print: (line: string) => void;
|
|
21
|
+
}
|
|
22
|
+
export interface WorktrainDaemonInstallCommandOpts {
|
|
23
|
+
readonly workspace?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface WorktrainDaemonUninstallCommandOpts {
|
|
26
|
+
}
|
|
27
|
+
export declare function buildPlistContent(opts: {
|
|
28
|
+
readonly nodeExecPath: string;
|
|
29
|
+
readonly cliScriptPath: string;
|
|
30
|
+
readonly workspacePath: string;
|
|
31
|
+
readonly homeDir: string;
|
|
32
|
+
readonly envVars: Readonly<Record<string, string>>;
|
|
33
|
+
}): string;
|
|
34
|
+
export declare function executeWorktrainDaemonInstallCommand(deps: WorktrainDaemonInstallCommandDeps, opts?: WorktrainDaemonInstallCommandOpts): Promise<CliResult>;
|
|
35
|
+
export declare function executeWorktrainDaemonUninstallCommand(deps: WorktrainDaemonInstallCommandDeps, _opts?: WorktrainDaemonUninstallCommandOpts): Promise<CliResult>;
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildPlistContent = buildPlistContent;
|
|
4
|
+
exports.executeWorktrainDaemonInstallCommand = executeWorktrainDaemonInstallCommand;
|
|
5
|
+
exports.executeWorktrainDaemonUninstallCommand = executeWorktrainDaemonUninstallCommand;
|
|
6
|
+
const cli_result_js_1 = require("../types/cli-result.js");
|
|
7
|
+
const SERVICE_LABEL = 'io.worktrain.daemon';
|
|
8
|
+
const PLIST_FILENAME = `${SERVICE_LABEL}.plist`;
|
|
9
|
+
const ENV_VARS_TO_BAKE = [
|
|
10
|
+
'AWS_PROFILE',
|
|
11
|
+
'AWS_ACCESS_KEY_ID',
|
|
12
|
+
'ANTHROPIC_API_KEY',
|
|
13
|
+
'WORKRAIL_TRIGGERS_ENABLED',
|
|
14
|
+
];
|
|
15
|
+
function buildPlistContent(opts) {
|
|
16
|
+
const { nodeExecPath, cliScriptPath, workspacePath, homeDir, envVars } = opts;
|
|
17
|
+
const logDir = `${homeDir}/.workrail/logs`;
|
|
18
|
+
const envEntries = Object.entries(envVars)
|
|
19
|
+
.map(([k, v]) => ` <key>${k}</key><string>${v}</string>`)
|
|
20
|
+
.join('\n');
|
|
21
|
+
const envSection = Object.keys(envVars).length > 0
|
|
22
|
+
? ` <key>EnvironmentVariables</key>\n <dict>\n${envEntries}\n </dict>\n`
|
|
23
|
+
: '';
|
|
24
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
25
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
|
|
26
|
+
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
27
|
+
<plist version="1.0">
|
|
28
|
+
<dict>
|
|
29
|
+
<key>Label</key>
|
|
30
|
+
<string>${SERVICE_LABEL}</string>
|
|
31
|
+
|
|
32
|
+
<!--
|
|
33
|
+
ProgramArguments: absolute paths only. launchd does not expand ~ or use PATH.
|
|
34
|
+
WHY node + script (not shebang): launchd's minimal launch environment may not
|
|
35
|
+
include the user's PATH, so #!/usr/bin/env node resolution can fail.
|
|
36
|
+
Using the absolute node binary from process.execPath at install time is deterministic.
|
|
37
|
+
If you upgrade Node via nvm after installing, re-run: worktrain daemon --install
|
|
38
|
+
-->
|
|
39
|
+
<key>ProgramArguments</key>
|
|
40
|
+
<array>
|
|
41
|
+
<string>${nodeExecPath}</string>
|
|
42
|
+
<string>${cliScriptPath}</string>
|
|
43
|
+
<string>daemon</string>
|
|
44
|
+
<string>--workspace</string>
|
|
45
|
+
<string>${workspacePath}</string>
|
|
46
|
+
</array>
|
|
47
|
+
|
|
48
|
+
<key>WorkingDirectory</key>
|
|
49
|
+
<string>${workspacePath}</string>
|
|
50
|
+
|
|
51
|
+
<!-- Start the service automatically at login. -->
|
|
52
|
+
<key>RunAtLoad</key>
|
|
53
|
+
<true/>
|
|
54
|
+
|
|
55
|
+
<!--
|
|
56
|
+
KeepAlive=true: launchd restarts the daemon if it exits for any reason.
|
|
57
|
+
This is the root fix: the daemon survives MCP server reconnects because it
|
|
58
|
+
is owned by launchd, not by any IDE process tree.
|
|
59
|
+
-->
|
|
60
|
+
<key>KeepAlive</key>
|
|
61
|
+
<true/>
|
|
62
|
+
|
|
63
|
+
<!--
|
|
64
|
+
ThrottleInterval: minimum seconds between restarts.
|
|
65
|
+
WHY 30s: prevents launchd from spinning in a tight restart loop if the daemon
|
|
66
|
+
exits immediately (e.g., missing credentials or bad workspace path).
|
|
67
|
+
-->
|
|
68
|
+
<key>ThrottleInterval</key>
|
|
69
|
+
<integer>30</integer>
|
|
70
|
+
|
|
71
|
+
<key>StandardOutPath</key>
|
|
72
|
+
<string>${logDir}/daemon.stdout.log</string>
|
|
73
|
+
|
|
74
|
+
<key>StandardErrorPath</key>
|
|
75
|
+
<string>${logDir}/daemon.stderr.log</string>
|
|
76
|
+
|
|
77
|
+
${envSection}</dict>
|
|
78
|
+
</plist>
|
|
79
|
+
`;
|
|
80
|
+
}
|
|
81
|
+
async function resolveWorkspace(deps, opts) {
|
|
82
|
+
if (opts.workspace) {
|
|
83
|
+
return { workspacePath: opts.workspace };
|
|
84
|
+
}
|
|
85
|
+
const configPath = deps.joinPath(deps.homedir(), '.workrail', 'config.json');
|
|
86
|
+
try {
|
|
87
|
+
const raw = await deps.readFile(configPath);
|
|
88
|
+
const config = JSON.parse(raw);
|
|
89
|
+
const configured = config['WORKRAIL_DEFAULT_WORKSPACE'];
|
|
90
|
+
if (typeof configured === 'string' && configured.trim() !== '') {
|
|
91
|
+
return { workspacePath: configured.trim() };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
error: 'No workspace configured. Provide --workspace <path> or run `worktrain init` first.',
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function collectEnvVars(env) {
|
|
101
|
+
const result = {};
|
|
102
|
+
for (const key of ENV_VARS_TO_BAKE) {
|
|
103
|
+
const value = env[key];
|
|
104
|
+
if (typeof value === 'string' && value.trim() !== '') {
|
|
105
|
+
result[key] = value;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
async function runInstallSection(deps, workspacePath) {
|
|
111
|
+
const homeDir = deps.homedir();
|
|
112
|
+
const plistDir = deps.joinPath(homeDir, 'Library', 'LaunchAgents');
|
|
113
|
+
const plistPath = deps.joinPath(plistDir, PLIST_FILENAME);
|
|
114
|
+
const logDir = deps.joinPath(homeDir, '.workrail', 'logs');
|
|
115
|
+
const envVars = collectEnvVars(deps.env);
|
|
116
|
+
const hasCredentials = !!envVars['AWS_PROFILE'] ||
|
|
117
|
+
!!envVars['AWS_ACCESS_KEY_ID'] ||
|
|
118
|
+
!!envVars['ANTHROPIC_API_KEY'];
|
|
119
|
+
try {
|
|
120
|
+
await deps.mkdir(logDir, { recursive: true });
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
return {
|
|
124
|
+
kind: 'error',
|
|
125
|
+
message: `Failed to create log directory ${logDir}: ${err instanceof Error ? err.message : String(err)}`,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
await deps.mkdir(plistDir, { recursive: true });
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
return {
|
|
133
|
+
kind: 'error',
|
|
134
|
+
message: `Failed to create LaunchAgents directory: ${err instanceof Error ? err.message : String(err)}`,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
const nodeExecPath = deps.nodeExecPath();
|
|
138
|
+
const cliScriptPath = deps.resolveCliScript();
|
|
139
|
+
const plistContent = buildPlistContent({
|
|
140
|
+
nodeExecPath,
|
|
141
|
+
cliScriptPath,
|
|
142
|
+
workspacePath,
|
|
143
|
+
homeDir,
|
|
144
|
+
envVars,
|
|
145
|
+
});
|
|
146
|
+
await deps.execLaunchctl(['unload', '-w', plistPath]);
|
|
147
|
+
try {
|
|
148
|
+
await deps.writeFile(plistPath, plistContent);
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
return {
|
|
152
|
+
kind: 'error',
|
|
153
|
+
message: `Failed to write plist to ${plistPath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
const loadResult = await deps.execLaunchctl(['load', '-w', plistPath]);
|
|
157
|
+
if (!loadResult.ok) {
|
|
158
|
+
return {
|
|
159
|
+
kind: 'error',
|
|
160
|
+
message: `launchctl load failed: ${loadResult.stderr.trim() || '(no stderr output)'}. ` +
|
|
161
|
+
`Plist written to ${plistPath} but service was not started.`,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
if (!hasCredentials) {
|
|
165
|
+
return {
|
|
166
|
+
kind: 'warning',
|
|
167
|
+
summary: `Service installed at ${plistPath} and loaded.`,
|
|
168
|
+
warning: 'No LLM credentials found in current environment. The service will fail to start ' +
|
|
169
|
+
'until you set AWS_PROFILE (Bedrock) or ANTHROPIC_API_KEY (Anthropic) and re-run: ' +
|
|
170
|
+
'worktrain daemon --install',
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
kind: 'configured',
|
|
175
|
+
summary: `Service installed at ${plistPath} and loaded. Node: ${nodeExecPath}. Script: ${cliScriptPath}.`,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
async function runUninstallSection(deps) {
|
|
179
|
+
const plistPath = deps.joinPath(deps.homedir(), 'Library', 'LaunchAgents', PLIST_FILENAME);
|
|
180
|
+
const plistExists = await deps.exists(plistPath);
|
|
181
|
+
if (!plistExists) {
|
|
182
|
+
return { kind: 'skipped', reason: `No plist found at ${plistPath} (service was not installed)` };
|
|
183
|
+
}
|
|
184
|
+
await deps.execLaunchctl(['unload', '-w', plistPath]);
|
|
185
|
+
try {
|
|
186
|
+
await deps.unlink(plistPath);
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
return {
|
|
190
|
+
kind: 'error',
|
|
191
|
+
message: `Failed to delete plist at ${plistPath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
kind: 'configured',
|
|
196
|
+
summary: `Service unloaded and plist removed: ${plistPath}`,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
async function executeWorktrainDaemonInstallCommand(deps, opts = {}) {
|
|
200
|
+
if (deps.platform !== 'darwin') {
|
|
201
|
+
return (0, cli_result_js_1.failure)(`worktrain daemon --install is macOS only (detected platform: ${deps.platform}). ` +
|
|
202
|
+
'Linux/systemd support is not yet implemented.', { suggestions: ['On Linux, start the daemon manually: workrail daemon --workspace <path>'] });
|
|
203
|
+
}
|
|
204
|
+
deps.print('');
|
|
205
|
+
deps.print('WorkTrain Daemon Install');
|
|
206
|
+
deps.print('════════════════════════════════════════');
|
|
207
|
+
deps.print('');
|
|
208
|
+
const workspaceResult = await resolveWorkspace(deps, opts);
|
|
209
|
+
if ('error' in workspaceResult) {
|
|
210
|
+
return (0, cli_result_js_1.failure)(workspaceResult.error, {
|
|
211
|
+
suggestions: ['Run `worktrain init` to configure a default workspace, or pass --workspace <path>'],
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
const { workspacePath } = workspaceResult;
|
|
215
|
+
deps.print(`[ 1/1 ] Install launchd service`);
|
|
216
|
+
deps.print(` Workspace: ${workspacePath}`);
|
|
217
|
+
const installResult = await runInstallSection(deps, workspacePath);
|
|
218
|
+
printSectionResult(deps, installResult);
|
|
219
|
+
if (installResult.kind === 'error') {
|
|
220
|
+
return (0, cli_result_js_1.failure)(installResult.message, {
|
|
221
|
+
suggestions: [
|
|
222
|
+
'Check that ~/Library/LaunchAgents/ is writable.',
|
|
223
|
+
'Check that launchctl is available (macOS only).',
|
|
224
|
+
],
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
deps.print('');
|
|
228
|
+
deps.print('Service is running. To verify:');
|
|
229
|
+
deps.print(` launchctl list ${SERVICE_LABEL}`);
|
|
230
|
+
deps.print('');
|
|
231
|
+
deps.print('Logs:');
|
|
232
|
+
deps.print(` tail -f ~/.workrail/logs/daemon.stdout.log`);
|
|
233
|
+
deps.print(` tail -f ~/.workrail/logs/daemon.stderr.log`);
|
|
234
|
+
deps.print('');
|
|
235
|
+
deps.print('Credential note:');
|
|
236
|
+
deps.print(' LLM credentials (AWS_PROFILE or ANTHROPIC_API_KEY) are baked into the');
|
|
237
|
+
deps.print(' plist at install time. Re-run --install after changing credentials.');
|
|
238
|
+
deps.print(' AWS SSO: the profile name is baked in, not STS tokens -- the SDK');
|
|
239
|
+
deps.print(' auto-refreshes tokens at runtime. Run `aws sso login` when the session expires.');
|
|
240
|
+
deps.print('');
|
|
241
|
+
deps.print('To uninstall: worktrain daemon --uninstall');
|
|
242
|
+
const detailLine = installResult.kind === 'warning'
|
|
243
|
+
? `warning: ${installResult.summary} -- ${installResult.warning}`
|
|
244
|
+
: installResult.kind === 'configured'
|
|
245
|
+
? `configured: ${installResult.summary}`
|
|
246
|
+
: `done`;
|
|
247
|
+
return (0, cli_result_js_1.success)({
|
|
248
|
+
message: 'WorkTrain daemon installed as a launchd service',
|
|
249
|
+
details: [detailLine],
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
async function executeWorktrainDaemonUninstallCommand(deps, _opts = {}) {
|
|
253
|
+
if (deps.platform !== 'darwin') {
|
|
254
|
+
return (0, cli_result_js_1.failure)(`worktrain daemon --uninstall is macOS only (detected platform: ${deps.platform}).`);
|
|
255
|
+
}
|
|
256
|
+
deps.print('');
|
|
257
|
+
deps.print('WorkTrain Daemon Uninstall');
|
|
258
|
+
deps.print('════════════════════════════════════════');
|
|
259
|
+
deps.print('');
|
|
260
|
+
deps.print('[ 1/1 ] Remove launchd service');
|
|
261
|
+
const uninstallResult = await runUninstallSection(deps);
|
|
262
|
+
printSectionResult(deps, uninstallResult);
|
|
263
|
+
if (uninstallResult.kind === 'error') {
|
|
264
|
+
return (0, cli_result_js_1.failure)(uninstallResult.message);
|
|
265
|
+
}
|
|
266
|
+
deps.print('');
|
|
267
|
+
deps.print('To reinstall: worktrain daemon --install');
|
|
268
|
+
return (0, cli_result_js_1.success)({
|
|
269
|
+
message: uninstallResult.kind === 'skipped'
|
|
270
|
+
? 'WorkTrain daemon service was not installed (nothing to remove)'
|
|
271
|
+
: 'WorkTrain daemon uninstalled',
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
function printSectionResult(deps, result) {
|
|
275
|
+
switch (result.kind) {
|
|
276
|
+
case 'skipped':
|
|
277
|
+
deps.print(` Skipped: ${result.reason}`);
|
|
278
|
+
break;
|
|
279
|
+
case 'configured':
|
|
280
|
+
deps.print(` Done: ${result.summary}`);
|
|
281
|
+
break;
|
|
282
|
+
case 'warning':
|
|
283
|
+
deps.print(` Done: ${result.summary}`);
|
|
284
|
+
deps.print(` Warning: ${result.warning}`);
|
|
285
|
+
break;
|
|
286
|
+
case 'error':
|
|
287
|
+
deps.print(` Error: ${result.message}`);
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
deps.print('');
|
|
291
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { CliResult } from '../types/cli-result.js';
|
|
2
|
+
export interface WorktrainDaemonCommandDeps {
|
|
3
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
4
|
+
readonly platform: string;
|
|
5
|
+
readonly worktrainBinPath: string;
|
|
6
|
+
readonly nodeBinPath: string;
|
|
7
|
+
readonly homedir: () => string;
|
|
8
|
+
readonly joinPath: (...paths: string[]) => string;
|
|
9
|
+
readonly mkdir: (path: string, opts: {
|
|
10
|
+
recursive: boolean;
|
|
11
|
+
}) => Promise<unknown>;
|
|
12
|
+
readonly writeFile: (path: string, content: string) => Promise<void>;
|
|
13
|
+
readonly chmod: (path: string, mode: number) => Promise<void>;
|
|
14
|
+
readonly readFile: (path: string) => Promise<string>;
|
|
15
|
+
readonly removeFile: (path: string) => Promise<void>;
|
|
16
|
+
readonly exists: (path: string) => Promise<boolean>;
|
|
17
|
+
readonly exec: (command: string, args: string[]) => Promise<{
|
|
18
|
+
readonly stdout: string;
|
|
19
|
+
readonly stderr: string;
|
|
20
|
+
readonly exitCode: number;
|
|
21
|
+
}>;
|
|
22
|
+
readonly print: (line: string) => void;
|
|
23
|
+
readonly sleep: (ms: number) => Promise<void>;
|
|
24
|
+
readonly startDaemon?: () => Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
export interface WorktrainDaemonCommandOpts {
|
|
27
|
+
readonly install?: boolean;
|
|
28
|
+
readonly uninstall?: boolean;
|
|
29
|
+
readonly status?: boolean;
|
|
30
|
+
}
|
|
31
|
+
export declare function executeWorktrainDaemonCommand(deps: WorktrainDaemonCommandDeps, opts: WorktrainDaemonCommandOpts): Promise<CliResult>;
|