dexto 1.6.14 → 1.6.15
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/deploy/entry-agent.d.ts +1 -1
- package/dist/cli/commands/deploy/entry-agent.d.ts.map +1 -1
- package/dist/cli/commands/deploy/entry-agent.js +50 -2
- package/dist/cli/commands/deploy/index.d.ts.map +1 -1
- package/dist/cli/commands/deploy/index.js +16 -2
- package/dist/cli/commands/index.d.ts +1 -0
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/index.js +1 -0
- package/dist/cli/commands/init.d.ts +96 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +834 -0
- package/dist/index-main.js +7 -1
- package/package.json +11 -11
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export declare function isAgentYamlPath(filePath: string): boolean;
|
|
2
|
-
export declare function discoverPrimaryWorkspaceAgent(workspaceRoot: string): string | null
|
|
2
|
+
export declare function discoverPrimaryWorkspaceAgent(workspaceRoot: string): Promise<string | null>;
|
|
3
3
|
//# sourceMappingURL=entry-agent.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entry-agent.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/deploy/entry-agent.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"entry-agent.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/deploy/entry-agent.ts"],"names":[],"mappings":"AAiFA,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED,wBAAsB,6BAA6B,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAuBjG"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { findProjectRegistryPath, getDefaultProjectRegistryEntry, readProjectRegistry, } from '@dexto/agent-management';
|
|
2
|
+
import { existsSync, promises as fs } from 'fs';
|
|
2
3
|
import path from 'path';
|
|
3
4
|
import { normalizeWorkspaceRelativePath } from './config.js';
|
|
4
5
|
const PRIMARY_WORKSPACE_AGENT_PATHS = [
|
|
@@ -7,15 +8,62 @@ const PRIMARY_WORKSPACE_AGENT_PATHS = [
|
|
|
7
8
|
path.join('agents', 'coding-agent.yml'),
|
|
8
9
|
path.join('agents', 'coding-agent.yaml'),
|
|
9
10
|
];
|
|
11
|
+
async function loadWorkspaceProjectRegistry(workspaceRoot) {
|
|
12
|
+
const registryPath = await findProjectRegistryPath(workspaceRoot);
|
|
13
|
+
if (!registryPath) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
const registry = await readProjectRegistry(registryPath);
|
|
17
|
+
const defaultEntry = getDefaultProjectRegistryEntry(registry, registryPath);
|
|
18
|
+
if (!defaultEntry) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
registryPath,
|
|
23
|
+
defaultConfigPath: await resolveWorkspaceRegistryConfigPath(workspaceRoot, registryPath, defaultEntry.configPath, defaultEntry.id),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
async function resolveWorkspaceRegistryConfigPath(workspaceRoot, registryPath, configPath, agentId) {
|
|
27
|
+
const normalizedPath = normalizeWorkspaceRelativePath(configPath);
|
|
28
|
+
const absolutePath = path.resolve(path.dirname(registryPath), normalizedPath);
|
|
29
|
+
const relativeToWorkspace = path.relative(workspaceRoot, absolutePath);
|
|
30
|
+
if (relativeToWorkspace.startsWith('..') ||
|
|
31
|
+
path.isAbsolute(relativeToWorkspace) ||
|
|
32
|
+
relativeToWorkspace === '') {
|
|
33
|
+
throw new Error(`Agent '${agentId}' in ${registryPath} has invalid configPath '${configPath}': path must stay inside the workspace root.`);
|
|
34
|
+
}
|
|
35
|
+
let stat;
|
|
36
|
+
try {
|
|
37
|
+
stat = await fs.stat(absolutePath);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
if (error.code === 'ENOENT') {
|
|
41
|
+
throw new Error(`Agent '${agentId}' in ${registryPath} has invalid configPath '${configPath}': file does not exist.`);
|
|
42
|
+
}
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
if (!stat.isFile()) {
|
|
46
|
+
throw new Error(`Agent '${agentId}' in ${registryPath} has invalid configPath '${configPath}': path must point to a file.`);
|
|
47
|
+
}
|
|
48
|
+
return absolutePath;
|
|
49
|
+
}
|
|
10
50
|
export function isAgentYamlPath(filePath) {
|
|
11
51
|
return /\.(ya?ml)$/i.test(filePath);
|
|
12
52
|
}
|
|
13
|
-
export function discoverPrimaryWorkspaceAgent(workspaceRoot) {
|
|
53
|
+
export async function discoverPrimaryWorkspaceAgent(workspaceRoot) {
|
|
54
|
+
const loadedRegistry = await loadWorkspaceProjectRegistry(workspaceRoot);
|
|
55
|
+
if (loadedRegistry) {
|
|
56
|
+
return normalizeWorkspaceRelativePath(path.relative(workspaceRoot, loadedRegistry.defaultConfigPath).replace(/\\/g, '/'));
|
|
57
|
+
}
|
|
14
58
|
for (const relativePath of PRIMARY_WORKSPACE_AGENT_PATHS) {
|
|
15
59
|
const absolutePath = path.join(workspaceRoot, relativePath);
|
|
16
60
|
if (!existsSync(absolutePath)) {
|
|
17
61
|
continue;
|
|
18
62
|
}
|
|
63
|
+
const stat = await fs.stat(absolutePath);
|
|
64
|
+
if (!stat.isFile()) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
19
67
|
return normalizeWorkspaceRelativePath(relativePath);
|
|
20
68
|
}
|
|
21
69
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/deploy/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/deploy/index.ts"],"names":[],"mappings":"AA4BA,UAAU,kBAAkB;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;CACzB;AAyHD,wBAAsB,mBAAmB,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CA8ErF;AAED,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,IAAI,CAAC,CAiD7D;AAED,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,IAAI,CAAC,CAqB7D;AAED,wBAAsB,yBAAyB,IAAI,OAAO,CAAC,IAAI,CAAC,CAkB/D;AAED,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,IAAI,CAAC,CA4B7D;AAED,wBAAsB,yBAAyB,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CA2C3F"}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { existsSync } from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import * as p from '@clack/prompts';
|
|
4
|
-
import { findDextoProjectRoot } from '@dexto/agent-management';
|
|
4
|
+
import { findDextoProjectRoot, loadAgentConfig } from '@dexto/agent-management';
|
|
5
5
|
import chalk from 'chalk';
|
|
6
6
|
import open from 'open';
|
|
7
7
|
import { confirmOrExit } from '../../utils/prompt-helpers.js';
|
|
8
|
+
import { validateAgentConfig } from '../../utils/config-validation.js';
|
|
8
9
|
import { createCloudDefaultDeployConfig, createWorkspaceDeployConfig, getDeployConfigPath, isWorkspaceDeployAgent, loadDeployConfig, resolveWorkspaceDeployAgentPath, saveDeployConfig, } from './config.js';
|
|
9
10
|
import { createDeployClient } from './client.js';
|
|
10
11
|
import { discoverPrimaryWorkspaceAgent, isAgentYamlPath } from './entry-agent.js';
|
|
@@ -86,12 +87,25 @@ function ensureWorkspaceAgentExists(workspaceRoot, config) {
|
|
|
86
87
|
}
|
|
87
88
|
return entryAgentPath;
|
|
88
89
|
}
|
|
90
|
+
async function validateWorkspaceAgent(entryAgentPath, workspaceAgentPath) {
|
|
91
|
+
const config = await loadAgentConfig(entryAgentPath);
|
|
92
|
+
const validation = await validateAgentConfig(config, false, {
|
|
93
|
+
agentPath: workspaceAgentPath,
|
|
94
|
+
credentialPolicy: 'error',
|
|
95
|
+
});
|
|
96
|
+
if (!validation.success) {
|
|
97
|
+
throw new Error(`Workspace agent validation failed for ${workspaceAgentPath}. Fix the issues above before deploying.`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
89
100
|
export async function handleDeployCommand(options) {
|
|
90
101
|
void options;
|
|
91
102
|
p.intro(chalk.inverse('Deploy Workspace'));
|
|
92
103
|
const workspaceRoot = resolveWorkspaceRoot();
|
|
93
104
|
const deployConfig = await resolveDeployConfig(workspaceRoot);
|
|
94
|
-
ensureWorkspaceAgentExists(workspaceRoot, deployConfig);
|
|
105
|
+
const entryAgentPath = ensureWorkspaceAgentExists(workspaceRoot, deployConfig);
|
|
106
|
+
if (entryAgentPath && isWorkspaceDeployAgent(deployConfig.agent)) {
|
|
107
|
+
await validateWorkspaceAgent(entryAgentPath, deployConfig.agent.path);
|
|
108
|
+
}
|
|
95
109
|
const deployLink = await loadWorkspaceDeployLink(workspaceRoot);
|
|
96
110
|
const spinner = p.spinner();
|
|
97
111
|
const client = createDeployClient();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { createDextoProject, type CreateAppOptions } from './create-app.js';
|
|
2
2
|
export { createImage } from './create-image.js';
|
|
3
|
+
export { handleInitCommand, handleInitAgentCommand, handleInitPrimaryCommand, handleInitSkillCommand, } from './init.js';
|
|
3
4
|
export { getUserInputToInitDextoApp, initDexto, postInitDexto } from './init-app.js';
|
|
4
5
|
export { handleSetupCommand, type CLISetupOptions, type CLISetupOptionsInput } from './setup.js';
|
|
5
6
|
export { handleInstallCommand, type InstallCommandOptions } from './agents/install.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,kBAAkB,EAAE,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAE5E,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,EAAE,0BAA0B,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAErF,OAAO,EAAE,kBAAkB,EAAE,KAAK,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,YAAY,CAAC;AACjG,OAAO,EAAE,oBAAoB,EAAE,KAAK,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACvF,OAAO,EAAE,sBAAsB,EAAE,KAAK,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAC7F,OAAO,EAAE,oBAAoB,EAAE,KAAK,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAChF,OAAO,EAAE,yBAAyB,EAAE,KAAK,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;AAC5F,OAAO,EACH,uBAAuB,EACvB,KAAK,wBAAwB,EAC7B,KAAK,6BAA6B,GACrC,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,kBAAkB,EAAE,KAAK,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAC1E,OAAO,EACH,uBAAuB,EACvB,mBAAmB,EACnB,KAAK,wBAAwB,GAChC,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACH,yBAAyB,EACzB,sBAAsB,EACtB,qBAAqB,EACrB,wBAAwB,EACxB,wBAAwB,EACxB,KAAK,0BAA0B,EAC/B,KAAK,+BAA+B,GACvC,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAG/F,OAAO,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AAGhE,OAAO,EACH,uBAAuB,EACvB,0BAA0B,EAC1B,4BAA4B,EAC5B,2BAA2B,EAE3B,2BAA2B,EAC3B,8BAA8B,EAC9B,8BAA8B,EAC9B,4BAA4B,EAC5B,+BAA+B,EAC/B,+BAA+B,EAC/B,KAAK,wBAAwB,EAC7B,KAAK,6BAA6B,EAClC,KAAK,2BAA2B,EAChC,KAAK,gCAAgC,EACrC,KAAK,6BAA6B,EAClC,KAAK,kCAAkC,EACvC,KAAK,4BAA4B,EACjC,KAAK,iCAAiC,EAEtC,KAAK,iCAAiC,EACtC,KAAK,oCAAoC,EACzC,KAAK,oCAAoC,EACzC,KAAK,kCAAkC,EACvC,KAAK,qCAAqC,GAC7C,MAAM,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,kBAAkB,EAAE,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAE5E,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,EACH,iBAAiB,EACjB,sBAAsB,EACtB,wBAAwB,EACxB,sBAAsB,GACzB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,0BAA0B,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAErF,OAAO,EAAE,kBAAkB,EAAE,KAAK,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,YAAY,CAAC;AACjG,OAAO,EAAE,oBAAoB,EAAE,KAAK,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACvF,OAAO,EAAE,sBAAsB,EAAE,KAAK,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAC7F,OAAO,EAAE,oBAAoB,EAAE,KAAK,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAChF,OAAO,EAAE,yBAAyB,EAAE,KAAK,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;AAC5F,OAAO,EACH,uBAAuB,EACvB,KAAK,wBAAwB,EAC7B,KAAK,6BAA6B,GACrC,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,kBAAkB,EAAE,KAAK,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAC1E,OAAO,EACH,uBAAuB,EACvB,mBAAmB,EACnB,KAAK,wBAAwB,GAChC,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACH,yBAAyB,EACzB,sBAAsB,EACtB,qBAAqB,EACrB,wBAAwB,EACxB,wBAAwB,EACxB,KAAK,0BAA0B,EAC/B,KAAK,+BAA+B,GACvC,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAG/F,OAAO,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AAGhE,OAAO,EACH,uBAAuB,EACvB,0BAA0B,EAC1B,4BAA4B,EAC5B,2BAA2B,EAE3B,2BAA2B,EAC3B,8BAA8B,EAC9B,8BAA8B,EAC9B,4BAA4B,EAC5B,+BAA+B,EAC/B,+BAA+B,EAC/B,KAAK,wBAAwB,EAC7B,KAAK,6BAA6B,EAClC,KAAK,2BAA2B,EAChC,KAAK,gCAAgC,EACrC,KAAK,6BAA6B,EAClC,KAAK,kCAAkC,EACvC,KAAK,4BAA4B,EACjC,KAAK,iCAAiC,EAEtC,KAAK,iCAAiC,EACtC,KAAK,oCAAoC,EACzC,KAAK,oCAAoC,EACzC,KAAK,kCAAkC,EACvC,KAAK,qCAAqC,GAC7C,MAAM,aAAa,CAAC"}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Project setup commands
|
|
3
3
|
export { createDextoProject } from './create-app.js';
|
|
4
4
|
export { createImage } from './create-image.js';
|
|
5
|
+
export { handleInitCommand, handleInitAgentCommand, handleInitPrimaryCommand, handleInitSkillCommand, } from './init.js';
|
|
5
6
|
export { getUserInputToInitDextoApp, initDexto, postInitDexto } from './init-app.js';
|
|
6
7
|
export { handleSetupCommand } from './setup.js';
|
|
7
8
|
export { handleInstallCommand } from './agents/install.js';
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
type ScaffoldEntryStatus = 'created' | 'existing';
|
|
3
|
+
type RegistryUpdateStatus = 'created' | 'existing' | 'updated';
|
|
4
|
+
export interface WorkspaceScaffoldResult {
|
|
5
|
+
root: string;
|
|
6
|
+
agentsFile: {
|
|
7
|
+
path: string;
|
|
8
|
+
status: ScaffoldEntryStatus;
|
|
9
|
+
};
|
|
10
|
+
directories: Array<{
|
|
11
|
+
path: string;
|
|
12
|
+
status: ScaffoldEntryStatus;
|
|
13
|
+
}>;
|
|
14
|
+
}
|
|
15
|
+
export interface WorkspaceAgentScaffoldResult {
|
|
16
|
+
workspace: WorkspaceScaffoldResult;
|
|
17
|
+
registry: {
|
|
18
|
+
path: string;
|
|
19
|
+
status: RegistryUpdateStatus;
|
|
20
|
+
};
|
|
21
|
+
agentConfig: {
|
|
22
|
+
path: string;
|
|
23
|
+
status: ScaffoldEntryStatus;
|
|
24
|
+
};
|
|
25
|
+
primaryAgent: {
|
|
26
|
+
id: string | null;
|
|
27
|
+
status: 'set' | 'unchanged';
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export interface WorkspaceSkillScaffoldResult {
|
|
31
|
+
workspace: WorkspaceScaffoldResult;
|
|
32
|
+
skillFile: {
|
|
33
|
+
path: string;
|
|
34
|
+
status: ScaffoldEntryStatus;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export interface WorkspacePrimaryAgentResult {
|
|
38
|
+
workspace: WorkspaceScaffoldResult;
|
|
39
|
+
registry: {
|
|
40
|
+
path: string;
|
|
41
|
+
status: Exclude<RegistryUpdateStatus, 'created'>;
|
|
42
|
+
};
|
|
43
|
+
primaryAgent: {
|
|
44
|
+
id: string;
|
|
45
|
+
status: 'set' | 'existing';
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export interface WorkspaceSubagentLinkResult {
|
|
49
|
+
workspace: WorkspaceScaffoldResult;
|
|
50
|
+
registry: {
|
|
51
|
+
path: string;
|
|
52
|
+
status: 'updated' | 'existing';
|
|
53
|
+
};
|
|
54
|
+
subagentId: string;
|
|
55
|
+
parentAgentId: string | null;
|
|
56
|
+
status: 'set' | 'existing' | 'no-primary';
|
|
57
|
+
}
|
|
58
|
+
export interface WorkspaceStatusResult {
|
|
59
|
+
workspaceRoot: string;
|
|
60
|
+
agentsFilePresent: boolean;
|
|
61
|
+
agentsDirectoryPresent: boolean;
|
|
62
|
+
skillsDirectoryPresent: boolean;
|
|
63
|
+
registryPath: string | null;
|
|
64
|
+
primaryAgentId: string | null;
|
|
65
|
+
allowGlobalAgents: boolean | null;
|
|
66
|
+
agents: Array<{
|
|
67
|
+
id: string;
|
|
68
|
+
isPrimary: boolean;
|
|
69
|
+
isSubagent: boolean;
|
|
70
|
+
parentAgentId: string | null;
|
|
71
|
+
}>;
|
|
72
|
+
skills: string[];
|
|
73
|
+
deployConfigPath: string | null;
|
|
74
|
+
effectiveDeploySummary: string;
|
|
75
|
+
}
|
|
76
|
+
export interface InitCommandRegisterContext {
|
|
77
|
+
program: Command;
|
|
78
|
+
}
|
|
79
|
+
type InitAgentCommandOptions = {
|
|
80
|
+
subagent?: boolean;
|
|
81
|
+
primary?: boolean;
|
|
82
|
+
};
|
|
83
|
+
export declare function createWorkspaceScaffold(workspaceRoot?: string): Promise<WorkspaceScaffoldResult>;
|
|
84
|
+
export declare function createWorkspaceAgentScaffold(agentIdInput: string, options?: InitAgentCommandOptions, workspaceRoot?: string): Promise<WorkspaceAgentScaffoldResult>;
|
|
85
|
+
export declare function createWorkspaceSkillScaffold(skillIdInput: string, workspaceRoot?: string): Promise<WorkspaceSkillScaffoldResult>;
|
|
86
|
+
export declare function setWorkspacePrimaryAgent(agentIdInput: string, workspaceRoot?: string): Promise<WorkspacePrimaryAgentResult>;
|
|
87
|
+
export declare function linkWorkspaceSubagentToPrimaryAgent(subagentIdInput: string, workspaceRoot?: string): Promise<WorkspaceSubagentLinkResult>;
|
|
88
|
+
export declare function inspectWorkspaceStatus(workspaceRoot?: string): Promise<WorkspaceStatusResult>;
|
|
89
|
+
export declare function handleInitCommand(workspaceRoot?: string): Promise<void>;
|
|
90
|
+
export declare function handleInitAgentCommand(agentIdInput: string | undefined, options?: InitAgentCommandOptions, workspaceRoot?: string): Promise<void>;
|
|
91
|
+
export declare function handleInitSkillCommand(skillId: string, workspaceRoot?: string): Promise<void>;
|
|
92
|
+
export declare function handleInitPrimaryCommand(agentIdInput: string | undefined, workspaceRoot?: string): Promise<void>;
|
|
93
|
+
export declare function handleInitStatusCommand(workspaceRoot?: string): Promise<void>;
|
|
94
|
+
export declare function registerInitCommand({ program }: InitCommandRegisterContext): void;
|
|
95
|
+
export {};
|
|
96
|
+
//# sourceMappingURL=init.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,834 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import { deriveDisplayName, findProjectRegistryPath as findSharedProjectRegistryPath, getPrimaryApiKeyEnvVar, getProjectRegistryPath as getCanonicalProjectRegistryPath, ProjectRegistrySchema, readProjectRegistry as readSharedProjectRegistry, writeConfigFile, } from '@dexto/agent-management';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { promises as fs } from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { ExitSignal, safeExit, withAnalytics } from '../../analytics/wrapper.js';
|
|
7
|
+
import { getDeployConfigPath, isWorkspaceDeployAgent, loadDeployConfig } from './deploy/config.js';
|
|
8
|
+
import { discoverPrimaryWorkspaceAgent } from './deploy/entry-agent.js';
|
|
9
|
+
import { selectOrExit, textOrExit } from '../utils/prompt-helpers.js';
|
|
10
|
+
const AGENTS_FILENAME = 'AGENTS.md';
|
|
11
|
+
const WORKSPACE_DIRECTORIES = ['agents', 'skills'];
|
|
12
|
+
const DEFAULT_AGENT_PROVIDER = 'openai';
|
|
13
|
+
const DEFAULT_AGENT_MODEL = 'gpt-5.3-codex';
|
|
14
|
+
const DEFAULT_AGENT_VERSION = '0.1.0';
|
|
15
|
+
const DEFAULT_AGENTS_MD = `<!-- dexto-workspace -->
|
|
16
|
+
|
|
17
|
+
# Dexto Workspace
|
|
18
|
+
|
|
19
|
+
This workspace can define project-specific agents and skills.
|
|
20
|
+
|
|
21
|
+
## Structure
|
|
22
|
+
- Put custom agents and subagents in \`agents/\`
|
|
23
|
+
- Put custom skills in \`skills/<skill-id>/SKILL.md\`
|
|
24
|
+
- Use \`.dexto/\` only for Dexto-managed state and installed assets
|
|
25
|
+
|
|
26
|
+
## Defaults
|
|
27
|
+
- If no workspace agent is defined, Dexto uses your global default agent locally
|
|
28
|
+
- Cloud deploys without a workspace agent use the managed cloud default agent
|
|
29
|
+
`;
|
|
30
|
+
const ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
31
|
+
function isSubagentEntry(entry) {
|
|
32
|
+
return (entry.tags?.includes('subagent') ?? false) || Boolean(entry.parentAgentId);
|
|
33
|
+
}
|
|
34
|
+
function isPrimaryCandidate(entry) {
|
|
35
|
+
return !isSubagentEntry(entry);
|
|
36
|
+
}
|
|
37
|
+
function getEffectiveWorkspacePrimaryAgentId(registry) {
|
|
38
|
+
if (registry.primaryAgent) {
|
|
39
|
+
return registry.primaryAgent;
|
|
40
|
+
}
|
|
41
|
+
const candidates = registry.agents.filter(isPrimaryCandidate);
|
|
42
|
+
if (candidates.length === 1) {
|
|
43
|
+
return candidates[0]?.id ?? null;
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
function getWorkspaceAgentEntry(registry, agentId) {
|
|
48
|
+
return registry.agents.find((entry) => entry.id === agentId) ?? null;
|
|
49
|
+
}
|
|
50
|
+
async function ensureWorkspaceRoot(root) {
|
|
51
|
+
const stat = await fs.stat(root);
|
|
52
|
+
if (!stat.isDirectory()) {
|
|
53
|
+
throw new Error(`${root} exists and is not a directory`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async function getExistingEntryType(entryPath) {
|
|
57
|
+
try {
|
|
58
|
+
const stat = await fs.stat(entryPath);
|
|
59
|
+
if (stat.isDirectory()) {
|
|
60
|
+
return 'directory';
|
|
61
|
+
}
|
|
62
|
+
if (stat.isFile()) {
|
|
63
|
+
return 'file';
|
|
64
|
+
}
|
|
65
|
+
return 'file';
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
if (error.code === 'ENOENT') {
|
|
69
|
+
return 'missing';
|
|
70
|
+
}
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function normalizeScaffoldId(id, kind) {
|
|
75
|
+
const normalized = id.trim();
|
|
76
|
+
if (!ID_PATTERN.test(normalized)) {
|
|
77
|
+
throw new Error(`Invalid ${kind} id '${id}'. Use kebab-case like '${kind === 'agent' ? 'coding-agent' : 'code-review'}'.`);
|
|
78
|
+
}
|
|
79
|
+
return normalized;
|
|
80
|
+
}
|
|
81
|
+
async function ensureDirectory(dirPath) {
|
|
82
|
+
try {
|
|
83
|
+
const stat = await fs.stat(dirPath);
|
|
84
|
+
if (!stat.isDirectory()) {
|
|
85
|
+
throw new Error(`${dirPath} exists and is not a directory`);
|
|
86
|
+
}
|
|
87
|
+
return 'existing';
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
if (error.code !== 'ENOENT') {
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
95
|
+
return 'created';
|
|
96
|
+
}
|
|
97
|
+
async function ensureFile(filePath, content) {
|
|
98
|
+
try {
|
|
99
|
+
const stat = await fs.stat(filePath);
|
|
100
|
+
if (!stat.isFile()) {
|
|
101
|
+
throw new Error(`${filePath} exists and is not a file`);
|
|
102
|
+
}
|
|
103
|
+
return 'existing';
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
if (error.code !== 'ENOENT') {
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
111
|
+
return 'created';
|
|
112
|
+
}
|
|
113
|
+
async function loadInitialAgentLlmConfig() {
|
|
114
|
+
return {
|
|
115
|
+
provider: DEFAULT_AGENT_PROVIDER,
|
|
116
|
+
model: DEFAULT_AGENT_MODEL,
|
|
117
|
+
apiKey: `$${getPrimaryApiKeyEnvVar(DEFAULT_AGENT_PROVIDER)}`,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function buildAgentDescription(agentId, options) {
|
|
121
|
+
if (options.subagent) {
|
|
122
|
+
return `Workspace sub-agent '${agentId}' for delegated tasks.`;
|
|
123
|
+
}
|
|
124
|
+
if (agentId === 'coding-agent') {
|
|
125
|
+
return 'Primary workspace agent for this project.';
|
|
126
|
+
}
|
|
127
|
+
return `Workspace agent '${agentId}' for this project.`;
|
|
128
|
+
}
|
|
129
|
+
async function buildAgentConfig(agentId, options) {
|
|
130
|
+
const llmConfig = await loadInitialAgentLlmConfig();
|
|
131
|
+
const displayName = deriveDisplayName(agentId);
|
|
132
|
+
const description = buildAgentDescription(agentId, options);
|
|
133
|
+
const llm = {
|
|
134
|
+
provider: llmConfig.provider,
|
|
135
|
+
model: llmConfig.model,
|
|
136
|
+
apiKey: llmConfig.apiKey,
|
|
137
|
+
};
|
|
138
|
+
return {
|
|
139
|
+
image: '@dexto/image-local',
|
|
140
|
+
agentId,
|
|
141
|
+
agentCard: {
|
|
142
|
+
name: displayName,
|
|
143
|
+
description,
|
|
144
|
+
url: `https://example.com/agents/${agentId}`,
|
|
145
|
+
version: DEFAULT_AGENT_VERSION,
|
|
146
|
+
},
|
|
147
|
+
systemPrompt: options.subagent
|
|
148
|
+
? [
|
|
149
|
+
`You are ${displayName}, a specialized sub-agent for this workspace.`,
|
|
150
|
+
'',
|
|
151
|
+
'Complete delegated tasks efficiently and concisely.',
|
|
152
|
+
'Read the relevant files before responding.',
|
|
153
|
+
'Return a clear result to the parent agent with concrete findings or next steps.',
|
|
154
|
+
].join('\n')
|
|
155
|
+
: [
|
|
156
|
+
`You are ${displayName}, the workspace agent for this project.`,
|
|
157
|
+
'',
|
|
158
|
+
'Help the user understand, edit, run, and deploy the files in this workspace.',
|
|
159
|
+
'Read relevant files before making changes.',
|
|
160
|
+
'Keep changes focused and explain what changed.',
|
|
161
|
+
].join('\n'),
|
|
162
|
+
greeting: options.subagent
|
|
163
|
+
? `Ready to help as ${displayName}.`
|
|
164
|
+
: 'Ready to work in this workspace.',
|
|
165
|
+
llm,
|
|
166
|
+
permissions: {
|
|
167
|
+
mode: 'manual',
|
|
168
|
+
allowedToolsStorage: 'storage',
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function buildRegistryEntry(agentId, options) {
|
|
173
|
+
const description = buildAgentDescription(agentId, options);
|
|
174
|
+
return {
|
|
175
|
+
id: agentId,
|
|
176
|
+
name: deriveDisplayName(agentId),
|
|
177
|
+
description,
|
|
178
|
+
configPath: `./${agentId}/${agentId}.yml`,
|
|
179
|
+
...(options.subagent ? { tags: ['subagent'] } : {}),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function addSubagentTag(entry) {
|
|
183
|
+
const tags = new Set(entry.tags ?? []);
|
|
184
|
+
tags.add('subagent');
|
|
185
|
+
return {
|
|
186
|
+
...entry,
|
|
187
|
+
tags: Array.from(tags).sort(),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function validateInitAgentOptions(options) {
|
|
191
|
+
if (options.primary && options.subagent) {
|
|
192
|
+
throw new Error('A sub-agent cannot also be the primary workspace agent.');
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
async function promptForAgentId(kind) {
|
|
196
|
+
const placeholder = kind === 'subagent'
|
|
197
|
+
? 'explore-agent'
|
|
198
|
+
: kind === 'primary'
|
|
199
|
+
? 'review-agent'
|
|
200
|
+
: 'helper-agent';
|
|
201
|
+
return await textOrExit({
|
|
202
|
+
message: 'Agent id',
|
|
203
|
+
placeholder,
|
|
204
|
+
validate(value) {
|
|
205
|
+
try {
|
|
206
|
+
normalizeScaffoldId(value, 'agent');
|
|
207
|
+
return undefined;
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
return error instanceof Error ? error.message : 'Invalid agent id';
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
}, 'Agent initialization cancelled');
|
|
214
|
+
}
|
|
215
|
+
async function resolveInitAgentInput(agentIdInput, options, workspaceRoot) {
|
|
216
|
+
validateInitAgentOptions(options);
|
|
217
|
+
if (agentIdInput) {
|
|
218
|
+
return {
|
|
219
|
+
agentId: normalizeScaffoldId(agentIdInput, 'agent'),
|
|
220
|
+
options,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
if (options.subagent || options.primary) {
|
|
224
|
+
const kind = options.subagent ? 'subagent' : 'primary';
|
|
225
|
+
return {
|
|
226
|
+
agentId: normalizeScaffoldId(await promptForAgentId(kind), 'agent'),
|
|
227
|
+
options,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
const registryState = await loadWorkspaceProjectRegistry(path.resolve(workspaceRoot));
|
|
231
|
+
const currentPrimaryAgentId = getEffectiveWorkspacePrimaryAgentId(registryState.registry);
|
|
232
|
+
const kind = await selectOrExit({
|
|
233
|
+
message: 'What kind of agent do you want to create?',
|
|
234
|
+
initialValue: currentPrimaryAgentId ? 'agent' : 'primary',
|
|
235
|
+
options: [
|
|
236
|
+
{
|
|
237
|
+
value: 'primary',
|
|
238
|
+
label: 'Primary agent',
|
|
239
|
+
hint: currentPrimaryAgentId
|
|
240
|
+
? `Replace current primary (${currentPrimaryAgentId})`
|
|
241
|
+
: 'Main workspace agent used by default',
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
value: 'agent',
|
|
245
|
+
label: 'Additional agent',
|
|
246
|
+
hint: 'Workspace agent that is available but not the default',
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
value: 'subagent',
|
|
250
|
+
label: 'Subagent',
|
|
251
|
+
hint: 'Delegated helper agent for the primary workspace agent',
|
|
252
|
+
},
|
|
253
|
+
],
|
|
254
|
+
}, 'Agent initialization cancelled');
|
|
255
|
+
const resolvedOptions = kind === 'primary' ? { primary: true } : kind === 'subagent' ? { subagent: true } : {};
|
|
256
|
+
return {
|
|
257
|
+
agentId: normalizeScaffoldId(await promptForAgentId(kind), 'agent'),
|
|
258
|
+
options: resolvedOptions,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
function buildSkillTemplate(skillId) {
|
|
262
|
+
const displayName = deriveDisplayName(skillId);
|
|
263
|
+
return `---
|
|
264
|
+
name: "${skillId}"
|
|
265
|
+
description: "TODO: Describe when to use this skill."
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
# ${displayName}
|
|
269
|
+
|
|
270
|
+
## Purpose
|
|
271
|
+
Describe what this skill helps the agent accomplish.
|
|
272
|
+
|
|
273
|
+
## Inputs
|
|
274
|
+
- The task or context that should trigger this skill
|
|
275
|
+
- Relevant files, paths, or constraints
|
|
276
|
+
|
|
277
|
+
## Steps
|
|
278
|
+
1. Review the relevant context.
|
|
279
|
+
2. Apply the workflow for this skill.
|
|
280
|
+
3. Return a concise result with any important follow-up actions.
|
|
281
|
+
|
|
282
|
+
## Output Format
|
|
283
|
+
- Summary of what was found or changed
|
|
284
|
+
- Key decisions or recommendations
|
|
285
|
+
- Follow-up actions, if any
|
|
286
|
+
`;
|
|
287
|
+
}
|
|
288
|
+
async function loadWorkspaceProjectRegistry(workspaceRoot) {
|
|
289
|
+
const existingPath = await findSharedProjectRegistryPath(workspaceRoot);
|
|
290
|
+
if (!existingPath) {
|
|
291
|
+
return {
|
|
292
|
+
path: getCanonicalProjectRegistryPath(workspaceRoot),
|
|
293
|
+
registry: { allowGlobalAgents: false, agents: [] },
|
|
294
|
+
status: 'created',
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
return {
|
|
298
|
+
path: existingPath,
|
|
299
|
+
registry: await readSharedProjectRegistry(existingPath),
|
|
300
|
+
status: 'existing',
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
async function saveWorkspaceProjectRegistry(registryPath, registry) {
|
|
304
|
+
await fs.mkdir(path.dirname(registryPath), { recursive: true });
|
|
305
|
+
const validatedRegistry = ProjectRegistrySchema.parse(registry);
|
|
306
|
+
await fs.writeFile(registryPath, `${JSON.stringify(validatedRegistry, null, 2)}\n`, 'utf8');
|
|
307
|
+
}
|
|
308
|
+
export async function createWorkspaceScaffold(workspaceRoot = process.cwd()) {
|
|
309
|
+
const root = path.resolve(workspaceRoot);
|
|
310
|
+
const agentsFilePath = path.join(root, AGENTS_FILENAME);
|
|
311
|
+
await ensureWorkspaceRoot(root);
|
|
312
|
+
const agentsFileType = await getExistingEntryType(agentsFilePath);
|
|
313
|
+
if (agentsFileType === 'directory') {
|
|
314
|
+
throw new Error(`${agentsFilePath} exists and is not a file`);
|
|
315
|
+
}
|
|
316
|
+
for (const directory of WORKSPACE_DIRECTORIES) {
|
|
317
|
+
const dirPath = path.join(root, directory);
|
|
318
|
+
const entryType = await getExistingEntryType(dirPath);
|
|
319
|
+
if (entryType === 'file') {
|
|
320
|
+
throw new Error(`${dirPath} exists and is not a directory`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
const agentsFileStatus = await ensureFile(agentsFilePath, DEFAULT_AGENTS_MD);
|
|
324
|
+
const directories = [];
|
|
325
|
+
for (const directory of WORKSPACE_DIRECTORIES) {
|
|
326
|
+
const dirPath = path.join(root, directory);
|
|
327
|
+
const status = await ensureDirectory(dirPath);
|
|
328
|
+
directories.push({ path: dirPath, status });
|
|
329
|
+
}
|
|
330
|
+
return {
|
|
331
|
+
root,
|
|
332
|
+
agentsFile: { path: agentsFilePath, status: agentsFileStatus },
|
|
333
|
+
directories,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
export async function createWorkspaceAgentScaffold(agentIdInput, options = {}, workspaceRoot = process.cwd()) {
|
|
337
|
+
validateInitAgentOptions(options);
|
|
338
|
+
const agentId = normalizeScaffoldId(agentIdInput, 'agent');
|
|
339
|
+
const workspace = await createWorkspaceScaffold(workspaceRoot);
|
|
340
|
+
const agentDirPath = path.join(workspace.root, 'agents', agentId);
|
|
341
|
+
const agentConfigPath = path.join(agentDirPath, `${agentId}.yml`);
|
|
342
|
+
const registryState = await loadWorkspaceProjectRegistry(workspace.root);
|
|
343
|
+
const existingEntry = registryState.registry.agents.find((entry) => entry.id === agentId);
|
|
344
|
+
const expectedRegistryEntry = buildRegistryEntry(agentId, options);
|
|
345
|
+
const currentPrimaryAgentId = getEffectiveWorkspacePrimaryAgentId(registryState.registry);
|
|
346
|
+
if (existingEntry && existingEntry.configPath !== expectedRegistryEntry.configPath) {
|
|
347
|
+
throw new Error(`Agent '${agentId}' already exists in ${registryState.path} with configPath '${existingEntry.configPath}'.`);
|
|
348
|
+
}
|
|
349
|
+
await ensureDirectory(agentDirPath);
|
|
350
|
+
let agentConfigStatus;
|
|
351
|
+
const agentConfigEntryType = await getExistingEntryType(agentConfigPath);
|
|
352
|
+
if (agentConfigEntryType === 'directory') {
|
|
353
|
+
throw new Error(`${agentConfigPath} exists and is not a file`);
|
|
354
|
+
}
|
|
355
|
+
if (agentConfigEntryType === 'file') {
|
|
356
|
+
agentConfigStatus = 'existing';
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
const config = await buildAgentConfig(agentId, options);
|
|
360
|
+
await writeConfigFile(agentConfigPath, config);
|
|
361
|
+
agentConfigStatus = 'created';
|
|
362
|
+
}
|
|
363
|
+
if (existingEntry) {
|
|
364
|
+
let registryUpdated = false;
|
|
365
|
+
if (options.subagent && !isSubagentEntry(existingEntry)) {
|
|
366
|
+
if (currentPrimaryAgentId === agentId) {
|
|
367
|
+
throw new Error(`Agent '${agentId}' is currently the workspace primary agent. Set another primary agent before converting it to a subagent.`);
|
|
368
|
+
}
|
|
369
|
+
registryState.registry.agents = registryState.registry.agents.map((entry) => entry.id === agentId ? addSubagentTag(entry) : entry);
|
|
370
|
+
registryUpdated = true;
|
|
371
|
+
}
|
|
372
|
+
let primaryAgentStatus = 'unchanged';
|
|
373
|
+
if (options.primary && registryState.registry.primaryAgent !== agentId) {
|
|
374
|
+
const updatedEntry = getWorkspaceAgentEntry(registryState.registry, agentId);
|
|
375
|
+
if (!updatedEntry || !isPrimaryCandidate(updatedEntry)) {
|
|
376
|
+
throw new Error(`Agent '${agentId}' is marked as a subagent and cannot be selected as the workspace primary agent.`);
|
|
377
|
+
}
|
|
378
|
+
registryState.registry.primaryAgent = agentId;
|
|
379
|
+
primaryAgentStatus = 'set';
|
|
380
|
+
registryUpdated = true;
|
|
381
|
+
}
|
|
382
|
+
if (registryUpdated) {
|
|
383
|
+
await saveWorkspaceProjectRegistry(registryState.path, registryState.registry);
|
|
384
|
+
}
|
|
385
|
+
return {
|
|
386
|
+
workspace,
|
|
387
|
+
registry: {
|
|
388
|
+
path: registryState.path,
|
|
389
|
+
status: registryUpdated ? 'updated' : registryState.status,
|
|
390
|
+
},
|
|
391
|
+
agentConfig: { path: agentConfigPath, status: agentConfigStatus },
|
|
392
|
+
primaryAgent: {
|
|
393
|
+
id: getEffectiveWorkspacePrimaryAgentId(registryState.registry),
|
|
394
|
+
status: primaryAgentStatus,
|
|
395
|
+
},
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
registryState.registry.agents.push(expectedRegistryEntry);
|
|
399
|
+
registryState.registry.agents.sort((left, right) => left.id.localeCompare(right.id));
|
|
400
|
+
const primaryCandidatesAfterAdd = registryState.registry.agents.filter(isPrimaryCandidate);
|
|
401
|
+
let primaryAgentStatus = 'unchanged';
|
|
402
|
+
if (!options.subagent &&
|
|
403
|
+
(options.primary || (!currentPrimaryAgentId && primaryCandidatesAfterAdd.length === 1)) &&
|
|
404
|
+
registryState.registry.primaryAgent !== agentId) {
|
|
405
|
+
registryState.registry.primaryAgent = agentId;
|
|
406
|
+
primaryAgentStatus = 'set';
|
|
407
|
+
}
|
|
408
|
+
await saveWorkspaceProjectRegistry(registryState.path, registryState.registry);
|
|
409
|
+
return {
|
|
410
|
+
workspace,
|
|
411
|
+
registry: {
|
|
412
|
+
path: registryState.path,
|
|
413
|
+
status: registryState.status === 'created' ? 'created' : 'updated',
|
|
414
|
+
},
|
|
415
|
+
agentConfig: { path: agentConfigPath, status: agentConfigStatus },
|
|
416
|
+
primaryAgent: {
|
|
417
|
+
id: getEffectiveWorkspacePrimaryAgentId(registryState.registry),
|
|
418
|
+
status: primaryAgentStatus,
|
|
419
|
+
},
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
export async function createWorkspaceSkillScaffold(skillIdInput, workspaceRoot = process.cwd()) {
|
|
423
|
+
const skillId = normalizeScaffoldId(skillIdInput, 'skill');
|
|
424
|
+
const workspace = await createWorkspaceScaffold(workspaceRoot);
|
|
425
|
+
const skillDirPath = path.join(workspace.root, 'skills', skillId);
|
|
426
|
+
const skillFilePath = path.join(skillDirPath, 'SKILL.md');
|
|
427
|
+
await ensureDirectory(skillDirPath);
|
|
428
|
+
const skillFileStatus = await ensureFile(skillFilePath, buildSkillTemplate(skillId));
|
|
429
|
+
return {
|
|
430
|
+
workspace,
|
|
431
|
+
skillFile: {
|
|
432
|
+
path: skillFilePath,
|
|
433
|
+
status: skillFileStatus,
|
|
434
|
+
},
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
export async function setWorkspacePrimaryAgent(agentIdInput, workspaceRoot = process.cwd()) {
|
|
438
|
+
const agentId = normalizeScaffoldId(agentIdInput, 'agent');
|
|
439
|
+
const workspace = await createWorkspaceScaffold(workspaceRoot);
|
|
440
|
+
const registryState = await loadWorkspaceProjectRegistry(workspace.root);
|
|
441
|
+
const existingEntry = registryState.registry.agents.find((entry) => entry.id === agentId);
|
|
442
|
+
if (!existingEntry) {
|
|
443
|
+
throw new Error(`Agent '${agentId}' is not registered in ${path.relative(workspace.root, registryState.path)}. Run \`dexto init agent ${agentId}\` first or update the registry manually.`);
|
|
444
|
+
}
|
|
445
|
+
if (!isPrimaryCandidate(existingEntry)) {
|
|
446
|
+
throw new Error(`Agent '${agentId}' is marked as a subagent and cannot be selected as the workspace primary agent.`);
|
|
447
|
+
}
|
|
448
|
+
if (registryState.registry.primaryAgent === agentId) {
|
|
449
|
+
return {
|
|
450
|
+
workspace,
|
|
451
|
+
registry: { path: registryState.path, status: 'existing' },
|
|
452
|
+
primaryAgent: { id: agentId, status: 'existing' },
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
registryState.registry.primaryAgent = agentId;
|
|
456
|
+
await saveWorkspaceProjectRegistry(registryState.path, registryState.registry);
|
|
457
|
+
return {
|
|
458
|
+
workspace,
|
|
459
|
+
registry: { path: registryState.path, status: 'updated' },
|
|
460
|
+
primaryAgent: { id: agentId, status: 'set' },
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
export async function linkWorkspaceSubagentToPrimaryAgent(subagentIdInput, workspaceRoot = process.cwd()) {
|
|
464
|
+
const subagentId = normalizeScaffoldId(subagentIdInput, 'agent');
|
|
465
|
+
const workspace = await createWorkspaceScaffold(workspaceRoot);
|
|
466
|
+
const registryState = await loadWorkspaceProjectRegistry(workspace.root);
|
|
467
|
+
const subagentEntry = getWorkspaceAgentEntry(registryState.registry, subagentId);
|
|
468
|
+
if (!subagentEntry) {
|
|
469
|
+
throw new Error(`Agent '${subagentId}' is not registered in ${path.relative(workspace.root, registryState.path)}.`);
|
|
470
|
+
}
|
|
471
|
+
const primaryAgentId = getEffectiveWorkspacePrimaryAgentId(registryState.registry);
|
|
472
|
+
if (!primaryAgentId || primaryAgentId === subagentId) {
|
|
473
|
+
return {
|
|
474
|
+
workspace,
|
|
475
|
+
registry: { path: registryState.path, status: 'existing' },
|
|
476
|
+
subagentId,
|
|
477
|
+
parentAgentId: primaryAgentId,
|
|
478
|
+
status: 'no-primary',
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
const needsSubagentTag = !isSubagentEntry(subagentEntry);
|
|
482
|
+
if (subagentEntry.parentAgentId === primaryAgentId && !needsSubagentTag) {
|
|
483
|
+
return {
|
|
484
|
+
workspace,
|
|
485
|
+
registry: { path: registryState.path, status: 'existing' },
|
|
486
|
+
subagentId,
|
|
487
|
+
parentAgentId: primaryAgentId,
|
|
488
|
+
status: 'existing',
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
registryState.registry.agents = registryState.registry.agents.map((entry) => entry.id === subagentId
|
|
492
|
+
? { ...addSubagentTag(entry), parentAgentId: primaryAgentId }
|
|
493
|
+
: entry);
|
|
494
|
+
await saveWorkspaceProjectRegistry(registryState.path, registryState.registry);
|
|
495
|
+
return {
|
|
496
|
+
workspace,
|
|
497
|
+
registry: { path: registryState.path, status: 'updated' },
|
|
498
|
+
subagentId,
|
|
499
|
+
parentAgentId: primaryAgentId,
|
|
500
|
+
status: 'set',
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
function formatCreatedPaths(result) {
|
|
504
|
+
const createdPaths = [];
|
|
505
|
+
if (result.agentsFile.status === 'created') {
|
|
506
|
+
createdPaths.push(path.relative(result.root, result.agentsFile.path) || AGENTS_FILENAME);
|
|
507
|
+
}
|
|
508
|
+
for (const directory of result.directories) {
|
|
509
|
+
if (directory.status === 'created') {
|
|
510
|
+
createdPaths.push(path.relative(result.root, directory.path) || path.basename(directory.path));
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return createdPaths;
|
|
514
|
+
}
|
|
515
|
+
function formatAgentPaths(result) {
|
|
516
|
+
const createdPaths = formatCreatedPaths(result.workspace);
|
|
517
|
+
if (result.registry.status === 'created' || result.registry.status === 'updated') {
|
|
518
|
+
createdPaths.push(path.relative(result.workspace.root, result.registry.path));
|
|
519
|
+
}
|
|
520
|
+
if (result.agentConfig.status === 'created') {
|
|
521
|
+
createdPaths.push(path.relative(result.workspace.root, result.agentConfig.path));
|
|
522
|
+
}
|
|
523
|
+
return createdPaths;
|
|
524
|
+
}
|
|
525
|
+
function formatSkillPaths(result) {
|
|
526
|
+
const createdPaths = formatCreatedPaths(result.workspace);
|
|
527
|
+
if (result.skillFile.status === 'created') {
|
|
528
|
+
createdPaths.push(path.relative(result.workspace.root, result.skillFile.path));
|
|
529
|
+
}
|
|
530
|
+
return createdPaths;
|
|
531
|
+
}
|
|
532
|
+
async function listWorkspaceSkillIds(workspaceRoot) {
|
|
533
|
+
const skillsRoot = path.join(workspaceRoot, 'skills');
|
|
534
|
+
try {
|
|
535
|
+
const entries = await fs.readdir(skillsRoot, { withFileTypes: true });
|
|
536
|
+
const skillIds = [];
|
|
537
|
+
for (const entry of entries) {
|
|
538
|
+
if (!entry.isDirectory()) {
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
const skillFilePath = path.join(skillsRoot, entry.name, 'SKILL.md');
|
|
542
|
+
try {
|
|
543
|
+
const stat = await fs.stat(skillFilePath);
|
|
544
|
+
if (stat.isFile()) {
|
|
545
|
+
skillIds.push(entry.name);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
catch (error) {
|
|
549
|
+
if (error.code === 'ENOENT') {
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
throw error;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
return skillIds.sort((left, right) => left.localeCompare(right));
|
|
556
|
+
}
|
|
557
|
+
catch (error) {
|
|
558
|
+
if (error.code === 'ENOENT') {
|
|
559
|
+
return [];
|
|
560
|
+
}
|
|
561
|
+
throw error;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
function describeEffectiveDeployAgent(input) {
|
|
565
|
+
if (input.deployConfig) {
|
|
566
|
+
const configuredAgent = isWorkspaceDeployAgent(input.deployConfig.agent)
|
|
567
|
+
? `workspace agent (${input.deployConfig.agent.path})`
|
|
568
|
+
: 'default cloud agent';
|
|
569
|
+
return `${configuredAgent} via ${input.deployConfigPath ?? '.dexto/deploy.json'}`;
|
|
570
|
+
}
|
|
571
|
+
if (input.implicitWorkspaceAgent) {
|
|
572
|
+
return `workspace agent (${input.implicitWorkspaceAgent}) if you run \`dexto deploy\``;
|
|
573
|
+
}
|
|
574
|
+
return 'default cloud agent if you run `dexto deploy`';
|
|
575
|
+
}
|
|
576
|
+
export async function inspectWorkspaceStatus(workspaceRoot = process.cwd()) {
|
|
577
|
+
const root = path.resolve(workspaceRoot);
|
|
578
|
+
await ensureWorkspaceRoot(root);
|
|
579
|
+
const agentsFilePresent = (await getExistingEntryType(path.join(root, AGENTS_FILENAME))) === 'file';
|
|
580
|
+
const agentsDirectoryPresent = (await getExistingEntryType(path.join(root, 'agents'))) === 'directory';
|
|
581
|
+
const skillsDirectoryPresent = (await getExistingEntryType(path.join(root, 'skills'))) === 'directory';
|
|
582
|
+
const registryPath = await findSharedProjectRegistryPath(root);
|
|
583
|
+
const registry = registryPath ? await readSharedProjectRegistry(registryPath) : null;
|
|
584
|
+
const primaryAgentId = registry ? getEffectiveWorkspacePrimaryAgentId(registry) : null;
|
|
585
|
+
const agents = registry?.agents
|
|
586
|
+
.map((entry) => ({
|
|
587
|
+
id: entry.id,
|
|
588
|
+
isPrimary: primaryAgentId === entry.id,
|
|
589
|
+
isSubagent: isSubagentEntry(entry),
|
|
590
|
+
parentAgentId: entry.parentAgentId ?? null,
|
|
591
|
+
}))
|
|
592
|
+
.sort((left, right) => left.id.localeCompare(right.id)) ?? [];
|
|
593
|
+
const skills = await listWorkspaceSkillIds(root);
|
|
594
|
+
const deployConfigPath = getDeployConfigPath(root);
|
|
595
|
+
const deployConfig = await loadDeployConfig(root);
|
|
596
|
+
const implicitWorkspaceAgent = await discoverPrimaryWorkspaceAgent(root);
|
|
597
|
+
return {
|
|
598
|
+
workspaceRoot: root,
|
|
599
|
+
agentsFilePresent,
|
|
600
|
+
agentsDirectoryPresent,
|
|
601
|
+
skillsDirectoryPresent,
|
|
602
|
+
registryPath,
|
|
603
|
+
primaryAgentId,
|
|
604
|
+
allowGlobalAgents: registry ? registry.allowGlobalAgents : null,
|
|
605
|
+
agents,
|
|
606
|
+
skills,
|
|
607
|
+
deployConfigPath: deployConfig ? deployConfigPath : null,
|
|
608
|
+
effectiveDeploySummary: describeEffectiveDeployAgent({
|
|
609
|
+
deployConfigPath: deployConfig ? deployConfigPath : null,
|
|
610
|
+
deployConfig,
|
|
611
|
+
implicitWorkspaceAgent,
|
|
612
|
+
}),
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
function formatWorkspaceStatus(result) {
|
|
616
|
+
return [
|
|
617
|
+
`Workspace: ${result.workspaceRoot}`,
|
|
618
|
+
`AGENTS.md: ${result.agentsFilePresent ? 'present' : 'missing'}`,
|
|
619
|
+
`agents/: ${result.agentsDirectoryPresent ? 'present' : 'missing'}`,
|
|
620
|
+
`skills/: ${result.skillsDirectoryPresent ? 'present' : 'missing'}`,
|
|
621
|
+
`Registry: ${result.registryPath ? path.relative(result.workspaceRoot, result.registryPath) : 'none'}`,
|
|
622
|
+
`Primary agent: ${result.primaryAgentId ?? 'none (global default used locally)'}`,
|
|
623
|
+
`Allow global agents: ${result.allowGlobalAgents === null
|
|
624
|
+
? 'n/a (no workspace registry)'
|
|
625
|
+
: String(result.allowGlobalAgents)}`,
|
|
626
|
+
`Deploy: ${result.effectiveDeploySummary}`,
|
|
627
|
+
'',
|
|
628
|
+
'Agents:',
|
|
629
|
+
...(result.agents.length > 0
|
|
630
|
+
? result.agents.map((agent) => {
|
|
631
|
+
const details = [
|
|
632
|
+
agent.isPrimary ? 'primary' : null,
|
|
633
|
+
agent.isSubagent ? 'subagent' : null,
|
|
634
|
+
agent.parentAgentId ? `parent: ${agent.parentAgentId}` : null,
|
|
635
|
+
].filter(Boolean);
|
|
636
|
+
return `- ${agent.id}${details.length > 0 ? ` (${details.join(', ')})` : ''}`;
|
|
637
|
+
})
|
|
638
|
+
: ['- none']),
|
|
639
|
+
'',
|
|
640
|
+
'Skills:',
|
|
641
|
+
...(result.skills.length > 0 ? result.skills.map((skillId) => `- ${skillId}`) : ['- none']),
|
|
642
|
+
].join('\n');
|
|
643
|
+
}
|
|
644
|
+
async function resolvePrimaryAgentSelection(agentIdInput, workspaceRoot) {
|
|
645
|
+
if (agentIdInput) {
|
|
646
|
+
return normalizeScaffoldId(agentIdInput, 'agent');
|
|
647
|
+
}
|
|
648
|
+
const workspace = await createWorkspaceScaffold(workspaceRoot);
|
|
649
|
+
const registryState = await loadWorkspaceProjectRegistry(workspace.root);
|
|
650
|
+
const primaryCandidates = registryState.registry.agents.filter(isPrimaryCandidate);
|
|
651
|
+
if (primaryCandidates.length === 0) {
|
|
652
|
+
throw new Error('No primary-capable workspace agents found. Run `dexto init agent` first.');
|
|
653
|
+
}
|
|
654
|
+
if (primaryCandidates.length === 1) {
|
|
655
|
+
return primaryCandidates[0]?.id ?? '';
|
|
656
|
+
}
|
|
657
|
+
const currentPrimaryAgentId = getEffectiveWorkspacePrimaryAgentId(registryState.registry);
|
|
658
|
+
const initialValue = primaryCandidates.some((entry) => entry.id === currentPrimaryAgentId)
|
|
659
|
+
? currentPrimaryAgentId
|
|
660
|
+
: undefined;
|
|
661
|
+
return await selectOrExit({
|
|
662
|
+
message: 'Which agent should be the workspace primary?',
|
|
663
|
+
initialValue,
|
|
664
|
+
options: primaryCandidates.map((entry) => ({
|
|
665
|
+
value: entry.id,
|
|
666
|
+
label: `${entry.name} (${entry.id})`,
|
|
667
|
+
hint: registryState.registry.primaryAgent === entry.id
|
|
668
|
+
? 'Current primary'
|
|
669
|
+
: entry.description,
|
|
670
|
+
})),
|
|
671
|
+
}, 'Primary agent selection cancelled');
|
|
672
|
+
}
|
|
673
|
+
export async function handleInitCommand(workspaceRoot = process.cwd()) {
|
|
674
|
+
p.intro(chalk.inverse('Dexto Init'));
|
|
675
|
+
const result = await createWorkspaceScaffold(workspaceRoot);
|
|
676
|
+
const createdPaths = formatCreatedPaths(result);
|
|
677
|
+
if (createdPaths.length === 0) {
|
|
678
|
+
p.outro(chalk.green('Workspace already initialized.'));
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
p.note(createdPaths.map((item) => `- ${item}`).join('\n'), 'Created');
|
|
682
|
+
p.outro(chalk.green('Workspace initialized.'));
|
|
683
|
+
}
|
|
684
|
+
export async function handleInitAgentCommand(agentIdInput, options = {}, workspaceRoot = process.cwd()) {
|
|
685
|
+
p.intro(chalk.inverse('Dexto Init Agent'));
|
|
686
|
+
const resolved = await resolveInitAgentInput(agentIdInput, options, workspaceRoot);
|
|
687
|
+
const result = await createWorkspaceAgentScaffold(resolved.agentId, resolved.options, workspaceRoot);
|
|
688
|
+
const subagentLinkResult = resolved.options.subagent
|
|
689
|
+
? await linkWorkspaceSubagentToPrimaryAgent(resolved.agentId, workspaceRoot)
|
|
690
|
+
: null;
|
|
691
|
+
const createdPaths = formatAgentPaths(result);
|
|
692
|
+
if (createdPaths.length === 0) {
|
|
693
|
+
p.outro(chalk.green([
|
|
694
|
+
`Agent '${resolved.agentId}' already initialized.`,
|
|
695
|
+
...(subagentLinkResult?.status === 'set' && subagentLinkResult.parentAgentId
|
|
696
|
+
? [`Linked to primary agent: ${subagentLinkResult.parentAgentId}`]
|
|
697
|
+
: subagentLinkResult?.status === 'no-primary'
|
|
698
|
+
? ['No primary agent found to link this subagent.']
|
|
699
|
+
: []),
|
|
700
|
+
].join('\n')));
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
p.note(createdPaths.map((item) => `- ${item}`).join('\n'), 'Created');
|
|
704
|
+
p.outro(chalk.green([
|
|
705
|
+
resolved.options.subagent
|
|
706
|
+
? `Sub-agent '${resolved.agentId}' initialized.`
|
|
707
|
+
: `Agent '${resolved.agentId}' initialized.`,
|
|
708
|
+
...(result.primaryAgent.status === 'set' && result.primaryAgent.id
|
|
709
|
+
? [`Primary agent: ${result.primaryAgent.id}`]
|
|
710
|
+
: []),
|
|
711
|
+
...(subagentLinkResult?.status === 'set' && subagentLinkResult.parentAgentId
|
|
712
|
+
? [`Linked to primary agent: ${subagentLinkResult.parentAgentId}`]
|
|
713
|
+
: subagentLinkResult?.status === 'no-primary'
|
|
714
|
+
? [
|
|
715
|
+
'No primary agent found to link this subagent. Use `dexto init primary <id>` and rerun if needed.',
|
|
716
|
+
]
|
|
717
|
+
: []),
|
|
718
|
+
].join('\n')));
|
|
719
|
+
}
|
|
720
|
+
export async function handleInitSkillCommand(skillId, workspaceRoot = process.cwd()) {
|
|
721
|
+
p.intro(chalk.inverse('Dexto Init Skill'));
|
|
722
|
+
const result = await createWorkspaceSkillScaffold(skillId, workspaceRoot);
|
|
723
|
+
const createdPaths = formatSkillPaths(result);
|
|
724
|
+
if (createdPaths.length === 0) {
|
|
725
|
+
p.outro(chalk.green(`Skill '${skillId}' already initialized.`));
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
p.note(createdPaths.map((item) => `- ${item}`).join('\n'), 'Created');
|
|
729
|
+
p.outro(chalk.green(`Skill '${skillId}' initialized.`));
|
|
730
|
+
}
|
|
731
|
+
export async function handleInitPrimaryCommand(agentIdInput, workspaceRoot = process.cwd()) {
|
|
732
|
+
p.intro(chalk.inverse('Dexto Init Primary'));
|
|
733
|
+
const agentId = await resolvePrimaryAgentSelection(agentIdInput, workspaceRoot);
|
|
734
|
+
const result = await setWorkspacePrimaryAgent(agentId, workspaceRoot);
|
|
735
|
+
if (result.primaryAgent.status === 'existing') {
|
|
736
|
+
p.outro(chalk.green(`'${agentId}' is already the workspace primary agent.`));
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
p.note(path.relative(result.workspace.root, result.registry.path), 'Updated');
|
|
740
|
+
p.outro(chalk.green(`Primary agent set to '${agentId}'.`));
|
|
741
|
+
}
|
|
742
|
+
export async function handleInitStatusCommand(workspaceRoot = process.cwd()) {
|
|
743
|
+
p.intro(chalk.inverse('Dexto Init Status'));
|
|
744
|
+
const result = await inspectWorkspaceStatus(workspaceRoot);
|
|
745
|
+
p.outro(formatWorkspaceStatus(result));
|
|
746
|
+
}
|
|
747
|
+
export function registerInitCommand({ program }) {
|
|
748
|
+
const initCommand = program
|
|
749
|
+
.command('init')
|
|
750
|
+
.description('Initialize the current folder as a Dexto workspace');
|
|
751
|
+
initCommand.addHelpText('after', `
|
|
752
|
+
Examples:
|
|
753
|
+
$ dexto init
|
|
754
|
+
$ dexto init agent
|
|
755
|
+
$ dexto init agent explore-agent --subagent
|
|
756
|
+
$ dexto init primary review-agent
|
|
757
|
+
$ dexto init skill code-review
|
|
758
|
+
$ dexto init status
|
|
759
|
+
`);
|
|
760
|
+
initCommand.action(withAnalytics('init', async () => {
|
|
761
|
+
try {
|
|
762
|
+
await handleInitCommand();
|
|
763
|
+
safeExit('init', 0);
|
|
764
|
+
}
|
|
765
|
+
catch (err) {
|
|
766
|
+
if (err instanceof ExitSignal)
|
|
767
|
+
throw err;
|
|
768
|
+
console.error(`❌ dexto init command failed: ${err}`);
|
|
769
|
+
safeExit('init', 1, 'error');
|
|
770
|
+
}
|
|
771
|
+
}));
|
|
772
|
+
initCommand
|
|
773
|
+
.command('agent [id]')
|
|
774
|
+
.description('Create a workspace agent scaffold')
|
|
775
|
+
.option('--subagent', 'Create a specialized sub-agent scaffold')
|
|
776
|
+
.option('--primary', 'Set this agent as the workspace primary')
|
|
777
|
+
.action(withAnalytics('init agent', async (id, options) => {
|
|
778
|
+
try {
|
|
779
|
+
await handleInitAgentCommand(id, options);
|
|
780
|
+
safeExit('init agent', 0);
|
|
781
|
+
}
|
|
782
|
+
catch (err) {
|
|
783
|
+
if (err instanceof ExitSignal)
|
|
784
|
+
throw err;
|
|
785
|
+
console.error(`❌ dexto init agent command failed: ${err}`);
|
|
786
|
+
safeExit('init agent', 1, 'error');
|
|
787
|
+
}
|
|
788
|
+
}));
|
|
789
|
+
initCommand
|
|
790
|
+
.command('primary [id]')
|
|
791
|
+
.description('Set the workspace primary agent')
|
|
792
|
+
.action(withAnalytics('init primary', async (id) => {
|
|
793
|
+
try {
|
|
794
|
+
await handleInitPrimaryCommand(id);
|
|
795
|
+
safeExit('init primary', 0);
|
|
796
|
+
}
|
|
797
|
+
catch (err) {
|
|
798
|
+
if (err instanceof ExitSignal)
|
|
799
|
+
throw err;
|
|
800
|
+
console.error(`❌ dexto init primary command failed: ${err}`);
|
|
801
|
+
safeExit('init primary', 1, 'error');
|
|
802
|
+
}
|
|
803
|
+
}));
|
|
804
|
+
initCommand
|
|
805
|
+
.command('skill <id>')
|
|
806
|
+
.description('Create a workspace skill scaffold')
|
|
807
|
+
.action(withAnalytics('init skill', async (id) => {
|
|
808
|
+
try {
|
|
809
|
+
await handleInitSkillCommand(id);
|
|
810
|
+
safeExit('init skill', 0);
|
|
811
|
+
}
|
|
812
|
+
catch (err) {
|
|
813
|
+
if (err instanceof ExitSignal)
|
|
814
|
+
throw err;
|
|
815
|
+
console.error(`❌ dexto init skill command failed: ${err}`);
|
|
816
|
+
safeExit('init skill', 1, 'error');
|
|
817
|
+
}
|
|
818
|
+
}));
|
|
819
|
+
initCommand
|
|
820
|
+
.command('status')
|
|
821
|
+
.description('Show the current workspace configuration and deploy preview')
|
|
822
|
+
.action(withAnalytics('init status', async () => {
|
|
823
|
+
try {
|
|
824
|
+
await handleInitStatusCommand();
|
|
825
|
+
safeExit('init status', 0);
|
|
826
|
+
}
|
|
827
|
+
catch (err) {
|
|
828
|
+
if (err instanceof ExitSignal)
|
|
829
|
+
throw err;
|
|
830
|
+
console.error(`❌ dexto init status command failed: ${err}`);
|
|
831
|
+
safeExit('init status', 1, 'error');
|
|
832
|
+
}
|
|
833
|
+
}));
|
|
834
|
+
}
|
package/dist/index-main.js
CHANGED
|
@@ -45,7 +45,7 @@ const cliVersion = resolveCliVersion();
|
|
|
45
45
|
process.env.DEXTO_CLI_VERSION = cliVersion;
|
|
46
46
|
import { logger, getProviderFromModel, getAllSupportedModels, startLlmRegistryAutoUpdate, DextoAgent, isPath, resolveApiKeyForProvider, getPrimaryApiKeyEnvVar, } from '@dexto/core';
|
|
47
47
|
import { applyImageDefaults, cleanNullValues, AgentConfigSchema, loadImage, resolveServicesFromConfig, setImageImporter, toDextoAgentOptions, } from '@dexto/agent-config';
|
|
48
|
-
import { getDextoPackageRoot, resolveAgentPath, loadAgentConfig, globalPreferencesExist, loadGlobalPreferences, resolveBundledScript, enrichAgentConfig, isDextoAuthEnabled, } from '@dexto/agent-management';
|
|
48
|
+
import { getDextoPackageRoot, resolveAgentPath, loadAgentConfig, findDextoProjectRoot, globalPreferencesExist, loadGlobalPreferences, resolveBundledScript, enrichAgentConfig, isDextoAuthEnabled, } from '@dexto/agent-management';
|
|
49
49
|
import { validateCliOptions, handleCliOptionsError } from './cli/utils/options.js';
|
|
50
50
|
import { validateAgentConfig } from './cli/utils/config-validation.js';
|
|
51
51
|
import { applyCLIOverrides, applyUserPreferences } from './config/cli-overrides.js';
|
|
@@ -59,6 +59,7 @@ import { registerImageCommand } from './cli/commands/image/register.js';
|
|
|
59
59
|
import { registerPluginCommand } from './cli/commands/plugin/register.js';
|
|
60
60
|
import { registerAgentsCommand } from './cli/commands/agents/register.js';
|
|
61
61
|
import { registerDeployCommand } from './cli/commands/deploy/register.js';
|
|
62
|
+
import { registerInitCommand } from './cli/commands/init.js';
|
|
62
63
|
const program = new Command();
|
|
63
64
|
let imageImporterConfigured = false;
|
|
64
65
|
let dextoApiKeyBootstrapped = false;
|
|
@@ -148,6 +149,7 @@ program
|
|
|
148
149
|
}));
|
|
149
150
|
registerImageCommand({ program });
|
|
150
151
|
registerDeployCommand({ program });
|
|
152
|
+
registerInitCommand({ program });
|
|
151
153
|
// 4) `init-app` SUB-COMMAND
|
|
152
154
|
program
|
|
153
155
|
.command('init-app')
|
|
@@ -294,6 +296,7 @@ async function bootstrapAgentFromGlobalOpts(options) {
|
|
|
294
296
|
inferredApiKey = apiKey;
|
|
295
297
|
}
|
|
296
298
|
const resolvedPath = await resolveAgentPath(globalOpts.agent, globalOpts.autoInstall !== false);
|
|
299
|
+
const workspaceRoot = findDextoProjectRoot(process.cwd()) ?? process.cwd();
|
|
297
300
|
const rawConfig = await loadAgentConfig(resolvedPath);
|
|
298
301
|
const mergedConfig = applyCLIOverrides(rawConfig, {
|
|
299
302
|
...globalOpts,
|
|
@@ -330,6 +333,7 @@ async function bootstrapAgentFromGlobalOpts(options) {
|
|
|
330
333
|
// Headless run keeps output deterministic and noise-free.
|
|
331
334
|
// Other non-interactive commands keep visible logs.
|
|
332
335
|
logLevel: isHeadlessRun ? 'error' : 'info',
|
|
336
|
+
workspaceRoot,
|
|
333
337
|
});
|
|
334
338
|
if (isHeadlessRun) {
|
|
335
339
|
// Force silent transport in headless mode even when agent config defines logger settings.
|
|
@@ -699,9 +703,11 @@ program
|
|
|
699
703
|
// Enrichment adds filesystem paths to storage (schema has in-memory defaults)
|
|
700
704
|
// Interactive CLI mode: only log to file (console would interfere with chat UI)
|
|
701
705
|
const isInteractiveCli = opts.mode === 'cli';
|
|
706
|
+
const workspaceRoot = findDextoProjectRoot(process.cwd()) ?? process.cwd();
|
|
702
707
|
const enrichedConfig = enrichAgentConfig(configWithImageDefaults, resolvedPath, {
|
|
703
708
|
isInteractiveCli,
|
|
704
709
|
logLevel: 'info', // CLI uses info-level logging for visibility
|
|
710
|
+
workspaceRoot,
|
|
705
711
|
});
|
|
706
712
|
// Validate enriched config + preflight credentials.
|
|
707
713
|
// - Interactive modes (web/cli): warn and continue when credentials are missing
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dexto",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.15",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"dexto": "./dist/index.js"
|
|
@@ -34,16 +34,16 @@
|
|
|
34
34
|
"ws": "^8.18.1",
|
|
35
35
|
"yaml": "^2.7.1",
|
|
36
36
|
"zod": "^3.25.0",
|
|
37
|
-
"@dexto/agent-config": "1.6.
|
|
38
|
-
"@dexto/agent-management": "1.6.
|
|
39
|
-
"@dexto/analytics": "1.6.
|
|
40
|
-
"@dexto/core": "1.6.
|
|
41
|
-
"@dexto/image-local": "1.6.
|
|
42
|
-
"@dexto/image-logger-agent": "1.6.
|
|
43
|
-
"@dexto/registry": "1.6.
|
|
44
|
-
"@dexto/server": "1.6.
|
|
45
|
-
"@dexto/storage": "1.6.
|
|
46
|
-
"@dexto/tui": "1.6.
|
|
37
|
+
"@dexto/agent-config": "1.6.15",
|
|
38
|
+
"@dexto/agent-management": "1.6.15",
|
|
39
|
+
"@dexto/analytics": "1.6.15",
|
|
40
|
+
"@dexto/core": "1.6.15",
|
|
41
|
+
"@dexto/image-local": "1.6.15",
|
|
42
|
+
"@dexto/image-logger-agent": "1.6.15",
|
|
43
|
+
"@dexto/registry": "1.6.15",
|
|
44
|
+
"@dexto/server": "1.6.15",
|
|
45
|
+
"@dexto/storage": "1.6.15",
|
|
46
|
+
"@dexto/tui": "1.6.15"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@types/ws": "^8.5.11",
|