fix(sf): expose daemon as sf-server

This commit is contained in:
Mikael Hugo 2026-05-02 22:25:24 +02:00
parent e5787794f3
commit 974d8e4b6d
9 changed files with 68 additions and 14 deletions

7
package-lock.json generated
View file

@ -61,7 +61,9 @@
},
"bin": {
"sf": "dist/loader.js",
"sf-cli": "dist/loader.js"
"sf-cli": "dist/loader.js",
"sf-daemon": "packages/daemon/dist/cli.js",
"sf-server": "packages/daemon/dist/cli.js"
},
"devDependencies": {
"@biomejs/biome": "^2.4.13",
@ -16327,7 +16329,8 @@
"zod": "^3.24.0"
},
"bin": {
"sf-daemon": "dist/cli.js"
"sf-daemon": "dist/cli.js",
"sf-server": "dist/cli.js"
},
"devDependencies": {
"@types/node": "^24.12.0",

View file

@ -18,7 +18,9 @@
],
"bin": {
"sf": "dist/loader.js",
"sf-cli": "dist/loader.js"
"sf-cli": "dist/loader.js",
"sf-daemon": "packages/daemon/dist/cli.js",
"sf-server": "packages/daemon/dist/cli.js"
},
"files": [
"dist",
@ -49,7 +51,8 @@
"build:rpc-client": "npm --workspace @singularity-forge/rpc-client run build",
"build:pi": "npm run build:native-pkg && npm run build:pi-tui && npm run build:pi-ai && npm run build:pi-agent-core && npm run build:pi-coding-agent",
"build:mcp-server": "npm --workspace @singularity-forge/mcp-server run build",
"build:core": "npm run build:pi && npm run build:rpc-client && npm run build:mcp-server && npm run check:versioned-json && tsc && npm run copy-resources && npm run copy-themes && npm run copy-export-html",
"build:daemon": "npm --workspace @singularity-forge/daemon run build",
"build:core": "npm run build:pi && npm run build:rpc-client && npm run build:daemon && npm run build:mcp-server && npm run check:versioned-json && tsc && npm run copy-resources && npm run copy-themes && npm run copy-export-html",
"build": "npm run build:core && node scripts/build-web-if-stale.cjs",
"stage:web-host": "node scripts/stage-web-standalone.cjs",
"build:web-host": "npm --prefix web run build && npm run stage:web-host",
@ -78,6 +81,7 @@
"build:native:dev": "node rust-engine/scripts/build.js --dev",
"dev": "node scripts/dev.js",
"sf": "node scripts/dev-cli.js",
"sf:server": "node packages/daemon/dist/cli.js",
"sf:web": "npm run build:pi && npm run copy-resources && node scripts/build-web-if-stale.cjs && node scripts/dev-cli.js --web",
"sf:web:stop": "node scripts/dev-cli.js web stop",
"sf:web:stop:all": "node scripts/dev-cli.js web stop all",

View file

@ -21,11 +21,12 @@
}
},
"bin": {
"sf-daemon": "./dist/cli.js"
"sf-daemon": "./dist/cli.js",
"sf-server": "./dist/cli.js"
},
"scripts": {
"build": "tsc",
"test": "node --test dist/daemon.test.js"
"test": "vitest run packages/daemon/src --root ../.. --config vitest.config.ts"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.92.0",

View file

@ -6,7 +6,11 @@ import { Logger } from './logger.js';
import { Daemon } from './daemon.js';
import { install, uninstall, status } from './launchd.js';
const USAGE = `Usage: sf-daemon [options]
const COMMAND_NAME = 'sf-server';
const USAGE = `Usage: sf-server [options]
Alias: sf-daemon
Options:
--config <path> Path to YAML config file (default: ~/.sf/daemon.yaml)
@ -46,27 +50,27 @@ async function main(): Promise<void> {
scriptPath,
configPath,
});
process.stdout.write('sf-daemon: launchd agent installed and loaded.\n');
process.stdout.write(`${COMMAND_NAME}: launchd agent installed and loaded.\n`);
process.exit(0);
}
if (values.uninstall) {
uninstall();
process.stdout.write('sf-daemon: launchd agent uninstalled.\n');
process.stdout.write(`${COMMAND_NAME}: launchd agent uninstalled.\n`);
process.exit(0);
}
if (values.status) {
const result = status();
if (!result.registered) {
process.stdout.write('sf-daemon: not registered with launchd.\n');
process.stdout.write(`${COMMAND_NAME}: not registered with launchd.\n`);
} else if (result.pid != null) {
process.stdout.write(
`sf-daemon: running (PID ${result.pid}, last exit status: ${result.lastExitStatus ?? 'n/a'})\n`,
`${COMMAND_NAME}: running (PID ${result.pid}, last exit status: ${result.lastExitStatus ?? 'n/a'})\n`,
);
} else {
process.stdout.write(
`sf-daemon: registered but not running (last exit status: ${result.lastExitStatus ?? 'n/a'})\n`,
`${COMMAND_NAME}: registered but not running (last exit status: ${result.lastExitStatus ?? 'n/a'})\n`,
);
}
process.exit(0);
@ -89,6 +93,6 @@ async function main(): Promise<void> {
main().catch((err: unknown) => {
const msg = err instanceof Error ? err.message : String(err);
process.stderr.write(`sf-daemon: fatal: ${msg}\n`);
process.stderr.write(`${COMMAND_NAME}: fatal: ${msg}\n`);
process.exit(1);
});

View file

@ -550,7 +550,8 @@ describe('CLI integration', () => {
[resolveCliPath()!, '--help'],
{ encoding: 'utf-8', timeout: 5000 },
);
assert.ok(result.includes('Usage: sf-daemon'));
assert.ok(result.includes('Usage: sf-server'));
assert.ok(result.includes('Alias: sf-daemon'));
assert.ok(result.includes('--config'));
assert.ok(result.includes('--verbose'));
});

View file

@ -859,6 +859,17 @@ describe('SessionManager', () => {
assert.equal(session.projectName, 'my-app');
});
// ---- Default command starts autonomous mode ----
it('sends autonomous command when no command is provided', async () => {
const { manager } = createManager();
await manager.startSession({ projectDir: '/tmp/default-autonomous' });
const client = manager.lastClient!;
assert.ok(client.prompted.includes('/sf autonomous'));
});
// ---- Custom command is sent instead of default ----
it('sends custom command when provided', async () => {

View file

@ -98,6 +98,7 @@ if (require.main === module) {
'pi-agent-core',
'pi-coding-agent',
'rpc-client',
'daemon',
'mcp-server',
]

View file

@ -32,6 +32,7 @@ const packageDirs = [
'pi-coding-agent',
'pi-tui',
'rpc-client',
'daemon',
'mcp-server',
]

View file

@ -97,6 +97,7 @@ try {
'dist/loader.js',
'packages/pi-coding-agent/dist/index.js',
'packages/rpc-client/dist/index.js',
'packages/daemon/dist/cli.js',
'packages/mcp-server/dist/cli.js',
'scripts/link-workspace-packages.cjs',
'dist/web/standalone/server.js',
@ -151,6 +152,7 @@ try {
const criticalPackages = [
{ scope: '@singularity-forge', name: 'pi-coding-agent' },
{ scope: '@singularity-forge', name: 'rpc-client' },
{ scope: '@singularity-forge', name: 'daemon' },
];
let resolutionFailed = false;
for (const pkg of criticalPackages) {
@ -175,7 +177,13 @@ try {
// --- Run the binary to confirm end-to-end resolution ---
console.log('==> Running installed binary (sf -v)...');
const loaderPath = join(installedRoot, 'dist', 'loader.js');
const daemonCliPath = join(installedRoot, 'packages', 'daemon', 'dist', 'cli.js');
const bundledWorkflowMcpCliPath = join(installedRoot, 'packages', 'mcp-server', 'dist', 'cli.js');
if (!existsSync(daemonCliPath)) {
console.log('ERROR: Bundled daemon CLI missing after install.');
console.log(` Expected: ${daemonCliPath}`);
process.exit(1);
}
if (!existsSync(bundledWorkflowMcpCliPath)) {
console.log('ERROR: Bundled workflow MCP CLI missing after install.');
console.log(` Expected: ${bundledWorkflowMcpCliPath}`);
@ -201,6 +209,26 @@ try {
process.exit(1);
}
console.log('==> Running installed daemon binary (sf-server --help)...');
try {
const helpOutput = execFileSync(process.execPath, [daemonCliPath, '--help'], {
cwd: installDir,
encoding: 'utf8',
stdio: ['pipe', 'pipe', 'pipe'],
timeout: 15000,
maxBuffer: DEFAULT_MAX_BUFFER,
});
if (!helpOutput.includes('Usage: sf-server')) {
console.log('ERROR: sf-server --help returned unexpected output.');
process.exit(1);
}
} catch (err) {
console.log('ERROR: Running sf-server --help failed after install.');
if (err.stdout) console.log(err.stdout);
if (err.stderr) console.log(err.stderr);
process.exit(1);
}
console.log('');
console.log('Package is installable. Safe to publish.');
process.exit(0);