@dinoxx/dinox-cli 2.4.0 → 2.6.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/commands/gateway/index.js +26 -4
- package/dist/commands/schema/renderSkills.js +1 -1
- package/dist/daemon/ensure.js +10 -19
- package/dist/daemon/env.d.ts +8 -0
- package/dist/daemon/env.js +81 -0
- package/dist/daemon/index.d.ts +27 -0
- package/dist/daemon/index.js +240 -57
- package/dist/daemon/launch.d.ts +27 -0
- package/dist/daemon/launch.js +87 -0
- package/dist/daemon/log.d.ts +12 -0
- package/dist/daemon/log.js +44 -0
- package/dist/daemon/server.js +18 -1
- package/dist/daemon/supervisor.d.ts +59 -0
- package/dist/daemon/supervisor.js +253 -0
- package/dist/daemon/types.d.ts +2 -0
- package/dist/gateway/adapters/acp.d.ts +10 -5
- package/dist/gateway/adapters/acp.js +30 -13
- package/dist/gateway/adapters/index.js +3 -2
- package/dist/gateway/config.d.ts +11 -1
- package/dist/gateway/config.js +38 -0
- package/dist/gateway/conversationContext.d.ts +44 -0
- package/dist/gateway/conversationContext.js +75 -0
- package/dist/gateway/deviceHost.d.ts +10 -1
- package/dist/gateway/deviceHost.js +119 -8
- package/dist/gateway/keepAwake.d.ts +24 -0
- package/dist/gateway/keepAwake.js +64 -0
- package/dist/gateway/mcpServer.js +20 -0
- package/dist/gateway/protocol.d.ts +88 -1
- package/dist/gateway/protocol.js +55 -0
- package/dist/gateway/runExecutor.d.ts +24 -0
- package/dist/gateway/runExecutor.js +118 -12
- package/dist/gateway/service.d.ts +4 -0
- package/dist/gateway/service.js +44 -15
- package/dist/gateway/threadStore.d.ts +71 -6
- package/dist/gateway/threadStore.js +189 -22
- package/dist/gateway/types.d.ts +22 -3
- package/dist/gateway/types.js +3 -2
- package/dist/gateway/workspaceFolders.d.ts +33 -0
- package/dist/gateway/workspaceFolders.js +129 -0
- package/dist/utils/errorCodes.d.ts +8 -0
- package/dist/utils/errorCodes.js +2 -0
- package/package.json +1 -1
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { isDaemonRunning, sendDaemonRequest } from '../../daemon/client.js';
|
|
1
|
+
import { getDaemonInfo, isDaemonRunning, sendDaemonRequest } from '../../daemon/client.js';
|
|
2
|
+
import { getDaemonService } from '../../daemon/supervisor.js';
|
|
2
3
|
import { ensureDaemonRunning } from '../../daemon/ensure.js';
|
|
3
4
|
import { loadConfig } from '../../config/store.js';
|
|
4
5
|
import { addGateway, adoptDetectedAgent, getGatewaysFilePath, loadGateways, removeGateway, } from '../../gateway/config.js';
|
|
5
6
|
import { detectAcpAgents, inferAgentId } from '../../gateway/detect.js';
|
|
6
7
|
import { serveDinoxMcpOverStdio } from '../../gateway/mcpServer.js';
|
|
7
8
|
import { DinoxError } from '../../utils/errors.js';
|
|
8
|
-
import { getRequestedStructuredOutputFormat, printHumanReadableLine, printSuccess } from '../../utils/output.js';
|
|
9
|
+
import { getRequestedStructuredOutputFormat, printHumanReadableLine, printHumanReadableWarning, printSuccess, } from '../../utils/output.js';
|
|
9
10
|
import { registerCommandGroup } from '../descriptor/register.js';
|
|
10
11
|
import { FORMAT_OPTIONS } from '../schema/globalOptions.js';
|
|
11
12
|
const GATEWAY_KINDS = ['acp', 'echo'];
|
|
@@ -42,13 +43,32 @@ function describeGateway(gateway) {
|
|
|
42
43
|
enabled: gateway.enabled,
|
|
43
44
|
};
|
|
44
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Gateways live exactly as long as the daemon. One not owned by a service
|
|
48
|
+
* manager belongs to the shell or agent session that started it and goes
|
|
49
|
+
* offline with it, so say so where it matters (stderr, so JSON stays clean).
|
|
50
|
+
*/
|
|
51
|
+
async function warnIfDaemonUnmanaged() {
|
|
52
|
+
if (!getDaemonService())
|
|
53
|
+
return;
|
|
54
|
+
const info = await getDaemonInfo();
|
|
55
|
+
if (info && !info.supervisor) {
|
|
56
|
+
printHumanReadableWarning('warning: gateways run in a daemon owned by the shell or agent session that started it, and go offline when that session ends. Run `dino daemon install` to keep them online.');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
45
59
|
/** Applies the account's gateways to the running daemon, starting it when needed. */
|
|
46
60
|
async function applyToDaemon() {
|
|
61
|
+
let gateways;
|
|
47
62
|
if (!(await isDaemonRunning())) {
|
|
48
63
|
await ensureDaemonRunning();
|
|
49
|
-
|
|
64
|
+
gateways = (await sendDaemonRequest('gateway.status')).gateways;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
gateways = (await sendDaemonRequest('gateway.reload', undefined, { timeoutMs: 30_000 })).gateways;
|
|
50
68
|
}
|
|
51
|
-
|
|
69
|
+
if (gateways.length > 0)
|
|
70
|
+
await warnIfDaemonUnmanaged();
|
|
71
|
+
return gateways;
|
|
52
72
|
}
|
|
53
73
|
function printStatuses(statuses) {
|
|
54
74
|
if (statuses.length === 0) {
|
|
@@ -162,6 +182,8 @@ async function runRemove(context) {
|
|
|
162
182
|
async function runStatus() {
|
|
163
183
|
await ensureDaemonRunning();
|
|
164
184
|
const { gateways } = (await sendDaemonRequest('gateway.status'));
|
|
185
|
+
if (gateways.length > 0)
|
|
186
|
+
await warnIfDaemonUnmanaged();
|
|
165
187
|
if (getRequestedStructuredOutputFormat()) {
|
|
166
188
|
printSuccess({ gateways });
|
|
167
189
|
return;
|
|
@@ -4,7 +4,7 @@ import { listErrorCodes } from '../../utils/errorCodes.js';
|
|
|
4
4
|
import { listCommandSchemas } from './spec.js';
|
|
5
5
|
const SECTION_ORDER = [
|
|
6
6
|
{ title: 'Auth', paths: ['auth.login', 'auth.logout', 'auth.status'] },
|
|
7
|
-
{ title: 'Daemon', paths: ['daemon.start', 'daemon.status', 'daemon.restart', 'daemon.stop'] },
|
|
7
|
+
{ title: 'Daemon', paths: ['daemon.start', 'daemon.status', 'daemon.restart', 'daemon.stop', 'daemon.install', 'daemon.uninstall'] },
|
|
8
8
|
{
|
|
9
9
|
title: 'Agent Gateways',
|
|
10
10
|
paths: ['gateway.detect', 'gateway.add', 'gateway.list', 'gateway.remove', 'gateway.status', 'gateway.reload', 'gateway.mcp'],
|
package/dist/daemon/ensure.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { spawn } from 'child_process';
|
|
2
1
|
import { getDaemonInfo, isDaemonFresh, waitForFreshDaemon } from './client.js';
|
|
3
2
|
import { stopDaemonLocked } from './index.js';
|
|
4
3
|
import { DEFAULT_DAEMON_PORT } from './types.js';
|
|
4
|
+
import { launchDaemon } from './launch.js';
|
|
5
|
+
import { getDaemonLogPath } from './log.js';
|
|
5
6
|
import { acquireDaemonStartLock, } from './pid.js';
|
|
6
7
|
import { loadConfig } from '../config/store.js';
|
|
7
8
|
import { fingerprintDaemonRuntimeConfig, resolveConfig } from '../config/resolve.js';
|
|
@@ -9,8 +10,8 @@ import { hydrateResolvedAuthIdentity } from '../auth/userInfo.js';
|
|
|
9
10
|
import { getPackageVersion } from '../utils/version.js';
|
|
10
11
|
import { printHumanReadableWarning } from '../utils/output.js';
|
|
11
12
|
import { withAsyncRelease } from '../utils/asyncRelease.js';
|
|
13
|
+
import { DinoxError } from '../utils/errors.js';
|
|
12
14
|
const DAEMON_READY_TIMEOUT_MS = 10_000;
|
|
13
|
-
const DAEMON_CHILD_ENV = 'DINOX_INTERNAL_DAEMON_CHILD';
|
|
14
15
|
async function hydrateDaemonIdentity(baseConfig) {
|
|
15
16
|
const { config } = await hydrateResolvedAuthIdentity(baseConfig, { timeoutMs: baseConfig.sync.timeoutMs });
|
|
16
17
|
return {
|
|
@@ -19,20 +20,6 @@ async function hydrateDaemonIdentity(baseConfig) {
|
|
|
19
20
|
runtimeConfigFingerprint: fingerprintDaemonRuntimeConfig(config),
|
|
20
21
|
};
|
|
21
22
|
}
|
|
22
|
-
function spawnDetachedDaemon() {
|
|
23
|
-
const args = ['daemon', 'start', '--port', String(DEFAULT_DAEMON_PORT), '--no-detach'];
|
|
24
|
-
const entrypoint = process.argv[1];
|
|
25
|
-
if (!entrypoint) {
|
|
26
|
-
throw new Error('Cannot determine the CLI entrypoint for daemon startup');
|
|
27
|
-
}
|
|
28
|
-
const child = spawn(process.execPath, [...process.execArgv, entrypoint, ...args], {
|
|
29
|
-
detached: true,
|
|
30
|
-
stdio: 'ignore',
|
|
31
|
-
env: { ...process.env, [DAEMON_CHILD_ENV]: '1' },
|
|
32
|
-
});
|
|
33
|
-
child.unref();
|
|
34
|
-
return child;
|
|
35
|
-
}
|
|
36
23
|
function matchesRuntimeTokenBinding(info, version, config) {
|
|
37
24
|
return Boolean(config.auth.authorization
|
|
38
25
|
&& info.authenticated
|
|
@@ -73,13 +60,17 @@ async function ensureDaemonRunningUnlocked() {
|
|
|
73
60
|
else {
|
|
74
61
|
printHumanReadableWarning('Starting daemon...');
|
|
75
62
|
}
|
|
76
|
-
const
|
|
63
|
+
const launch = await launchDaemon(DEFAULT_DAEMON_PORT);
|
|
77
64
|
if (await waitForFreshDaemon(expectedVersion, expectedIdentity.userId, expectedIdentity.tokenFingerprint, expectedIdentity.runtimeConfigFingerprint, DAEMON_READY_TIMEOUT_MS)) {
|
|
78
65
|
printHumanReadableWarning('Daemon started');
|
|
79
66
|
return;
|
|
80
67
|
}
|
|
81
|
-
|
|
82
|
-
throw new
|
|
68
|
+
launch.abort();
|
|
69
|
+
throw new DinoxError('Failed to start daemon', {
|
|
70
|
+
code: 'DAEMON_START_FAILED',
|
|
71
|
+
details: { service: launch.service, logPath: getDaemonLogPath() },
|
|
72
|
+
suggestion: `Inspect ${getDaemonLogPath()} for the daemon's startup error.`,
|
|
73
|
+
});
|
|
83
74
|
}
|
|
84
75
|
export async function ensureDaemonRunning() {
|
|
85
76
|
await withAsyncRelease(() => acquireDaemonStartLock(), () => ensureDaemonRunningUnlocked());
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Makes sure the Node runtime running the CLI is on PATH, so agents installed
|
|
3
|
+
* as `#!/usr/bin/env node` scripts still start under a service manager's
|
|
4
|
+
* minimal PATH.
|
|
5
|
+
*/
|
|
6
|
+
export declare function withRuntimeOnPath(pathValue: string | undefined, execPath?: string): string;
|
|
7
|
+
/** The environment a daemon process starts with: an allow-list of the caller's. */
|
|
8
|
+
export declare function buildDaemonEnv(source?: NodeJS.ProcessEnv, execPath?: string): Record<string, string>;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
/**
|
|
3
|
+
* Variables the daemon may inherit from whoever starts it. Everything else is
|
|
4
|
+
* dropped: the daemon outlives its caller, so it must not keep a coding agent's
|
|
5
|
+
* session markers (CLAUDECODE, CLAUDE_CODE_SESSION_ID, ...), credentials
|
|
6
|
+
* (ANTHROPIC_AUTH_TOKEN, ...) or app identity (XPC_SERVICE_NAME,
|
|
7
|
+
* __CFBundleIdentifier) and hand them on to the agents its gateways spawn.
|
|
8
|
+
* Agent credentials belong in `dino gateway add --env KEY=VALUE`.
|
|
9
|
+
*/
|
|
10
|
+
const INHERITED_NAMES = new Set([
|
|
11
|
+
'HOME',
|
|
12
|
+
'USER',
|
|
13
|
+
'LOGNAME',
|
|
14
|
+
'SHELL',
|
|
15
|
+
'PATH',
|
|
16
|
+
'TMPDIR',
|
|
17
|
+
'LANG',
|
|
18
|
+
'TZ',
|
|
19
|
+
'XDG_CONFIG_HOME',
|
|
20
|
+
'XDG_DATA_HOME',
|
|
21
|
+
'XDG_STATE_HOME',
|
|
22
|
+
'XDG_CACHE_HOME',
|
|
23
|
+
'XDG_RUNTIME_DIR',
|
|
24
|
+
'HTTP_PROXY',
|
|
25
|
+
'HTTPS_PROXY',
|
|
26
|
+
'ALL_PROXY',
|
|
27
|
+
'NO_PROXY',
|
|
28
|
+
'NODE_EXTRA_CA_CERTS',
|
|
29
|
+
'SSL_CERT_FILE',
|
|
30
|
+
'SSL_CERT_DIR',
|
|
31
|
+
'POWERSYNC_TOKEN_ENDPOINT',
|
|
32
|
+
// Windows process essentials.
|
|
33
|
+
'APPDATA',
|
|
34
|
+
'LOCALAPPDATA',
|
|
35
|
+
'USERPROFILE',
|
|
36
|
+
'HOMEDRIVE',
|
|
37
|
+
'HOMEPATH',
|
|
38
|
+
'SystemRoot',
|
|
39
|
+
'windir',
|
|
40
|
+
'ComSpec',
|
|
41
|
+
'PATHEXT',
|
|
42
|
+
'TEMP',
|
|
43
|
+
'TMP',
|
|
44
|
+
'ProgramData',
|
|
45
|
+
'ProgramFiles',
|
|
46
|
+
'ProgramFiles(x86)',
|
|
47
|
+
]);
|
|
48
|
+
const INHERITED_PREFIXES = ['DINOX_', 'LC_'];
|
|
49
|
+
/** Internal markers the parent sets for the child; never inherited from outside. */
|
|
50
|
+
const INTERNAL_NAMES = new Set(['DINOX_INTERNAL_DAEMON_CHILD', 'DINOX_DAEMON_SUPERVISOR']);
|
|
51
|
+
const INHERITED_UPPER = new Set([...INHERITED_NAMES].map((name) => name.toUpperCase()));
|
|
52
|
+
// Compared upper-cased: Windows names are case-insensitive (`Path`, `SystemRoot`).
|
|
53
|
+
function isInherited(name) {
|
|
54
|
+
const upper = name.toUpperCase();
|
|
55
|
+
if (INTERNAL_NAMES.has(upper))
|
|
56
|
+
return false;
|
|
57
|
+
return INHERITED_UPPER.has(upper) || INHERITED_PREFIXES.some((prefix) => upper.startsWith(prefix));
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Makes sure the Node runtime running the CLI is on PATH, so agents installed
|
|
61
|
+
* as `#!/usr/bin/env node` scripts still start under a service manager's
|
|
62
|
+
* minimal PATH.
|
|
63
|
+
*/
|
|
64
|
+
export function withRuntimeOnPath(pathValue, execPath = process.execPath) {
|
|
65
|
+
const runtimeDir = path.dirname(execPath);
|
|
66
|
+
const entries = (pathValue ?? '').split(path.delimiter).filter(Boolean);
|
|
67
|
+
if (!entries.includes(runtimeDir))
|
|
68
|
+
entries.unshift(runtimeDir);
|
|
69
|
+
return entries.join(path.delimiter);
|
|
70
|
+
}
|
|
71
|
+
/** The environment a daemon process starts with: an allow-list of the caller's. */
|
|
72
|
+
export function buildDaemonEnv(source = process.env, execPath = process.execPath) {
|
|
73
|
+
const env = {};
|
|
74
|
+
for (const [name, value] of Object.entries(source)) {
|
|
75
|
+
if (typeof value === 'string' && isInherited(name))
|
|
76
|
+
env[name] = value;
|
|
77
|
+
}
|
|
78
|
+
const pathKey = Object.keys(env).find((name) => name.toUpperCase() === 'PATH') ?? 'PATH';
|
|
79
|
+
env[pathKey] = withRuntimeOnPath(env[pathKey], execPath);
|
|
80
|
+
return env;
|
|
81
|
+
}
|
package/dist/daemon/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import type { CommandGroupDescriptor } from '../commands/descriptor/types.js';
|
|
3
|
+
import { type DaemonServiceKind } from './supervisor.js';
|
|
3
4
|
type DaemonStartResult = {
|
|
4
5
|
running: boolean;
|
|
5
6
|
port: number;
|
|
@@ -7,6 +8,8 @@ type DaemonStartResult = {
|
|
|
7
8
|
transport: 'socket';
|
|
8
9
|
detached: boolean;
|
|
9
10
|
alreadyRunning: boolean;
|
|
11
|
+
/** Service manager that started it; null for a detached child or foreground run. */
|
|
12
|
+
service: DaemonServiceKind | null;
|
|
10
13
|
};
|
|
11
14
|
type DaemonStartOptions = {
|
|
12
15
|
quiet?: boolean;
|
|
@@ -17,6 +20,20 @@ type DaemonStopResult = {
|
|
|
17
20
|
wasRunning: boolean;
|
|
18
21
|
stalePidFile: boolean;
|
|
19
22
|
};
|
|
23
|
+
type DaemonInstallResult = {
|
|
24
|
+
installed: boolean;
|
|
25
|
+
service: DaemonServiceKind;
|
|
26
|
+
label: string;
|
|
27
|
+
definitionPath: string;
|
|
28
|
+
logPath: string;
|
|
29
|
+
running: boolean;
|
|
30
|
+
replacedRunning: boolean;
|
|
31
|
+
};
|
|
32
|
+
type DaemonUninstallResult = {
|
|
33
|
+
uninstalled: boolean;
|
|
34
|
+
service: DaemonServiceKind | null;
|
|
35
|
+
definitionPath: string | null;
|
|
36
|
+
};
|
|
20
37
|
export declare function startDaemonLocked(port: number, detached: boolean, options?: DaemonStartOptions): Promise<DaemonStartResult>;
|
|
21
38
|
/** Caller must hold the daemon start lock. */
|
|
22
39
|
export declare function stopDaemonLocked(): Promise<DaemonStopResult>;
|
|
@@ -31,6 +48,7 @@ export declare function restartDaemonLocked(port?: number, options?: {
|
|
|
31
48
|
transport: 'socket';
|
|
32
49
|
detached: boolean;
|
|
33
50
|
alreadyRunning: boolean;
|
|
51
|
+
service: DaemonServiceKind | null;
|
|
34
52
|
}>;
|
|
35
53
|
export declare function restartDaemon(port?: number, options?: {
|
|
36
54
|
quiet?: boolean;
|
|
@@ -42,7 +60,16 @@ export declare function restartDaemon(port?: number, options?: {
|
|
|
42
60
|
transport: 'socket';
|
|
43
61
|
detached: boolean;
|
|
44
62
|
alreadyRunning: boolean;
|
|
63
|
+
service: DaemonServiceKind | null;
|
|
45
64
|
}>;
|
|
65
|
+
/**
|
|
66
|
+
* Hands the daemon to the platform's service manager so it no longer belongs
|
|
67
|
+
* to whichever process first started it, then waits until the managed
|
|
68
|
+
* instance answers with the current identity.
|
|
69
|
+
*/
|
|
70
|
+
export declare function installDaemonService(port?: number): Promise<DaemonInstallResult>;
|
|
71
|
+
/** Stops the managed daemon and removes its service definition. */
|
|
72
|
+
export declare function uninstallDaemonService(): Promise<DaemonUninstallResult>;
|
|
46
73
|
export declare const daemonCommandGroup: CommandGroupDescriptor;
|
|
47
74
|
export declare function registerDaemonCommands(program: Command): void;
|
|
48
75
|
export {};
|