@soleri/cli 1.6.0 → 1.8.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/create.js +15 -5
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/doctor.js +7 -1
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/extend.d.ts +2 -0
- package/dist/commands/extend.js +167 -0
- package/dist/commands/extend.js.map +1 -0
- package/dist/commands/list.js +52 -17
- package/dist/commands/list.js.map +1 -1
- package/dist/main.js +6 -1
- package/dist/main.js.map +1 -1
- package/dist/prompts/archetypes.d.ts +21 -0
- package/dist/prompts/archetypes.js +153 -0
- package/dist/prompts/archetypes.js.map +1 -0
- package/dist/prompts/create-wizard.d.ts +1 -1
- package/dist/prompts/create-wizard.js +299 -76
- package/dist/prompts/create-wizard.js.map +1 -1
- package/dist/prompts/playbook.d.ts +63 -0
- package/dist/prompts/playbook.js +154 -0
- package/dist/prompts/playbook.js.map +1 -0
- package/package.json +1 -1
- package/src/commands/create.ts +17 -6
- package/src/commands/doctor.ts +9 -2
- package/src/commands/extend.ts +197 -0
- package/src/commands/list.ts +60 -21
- package/src/main.ts +7 -1
- package/src/prompts/archetypes.ts +212 -0
- package/src/prompts/create-wizard.ts +345 -69
- package/src/prompts/playbook.ts +301 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Playbook data for the guided wizard.
|
|
3
|
+
* Provides curated options with self-explanatory hints,
|
|
4
|
+
* organized by category. Each list also supports a "custom" escape hatch
|
|
5
|
+
* with examples and anti-examples so the user is never staring at a blank cursor.
|
|
6
|
+
*/
|
|
7
|
+
export const DOMAIN_OPTIONS = [
|
|
8
|
+
{ value: 'security', label: 'security', hint: 'Vulnerability scanning, threat modeling, secrets detection' },
|
|
9
|
+
{ value: 'code-review', label: 'code-review', hint: 'Pattern enforcement, anti-pattern detection, PR review' },
|
|
10
|
+
{ value: 'testing', label: 'testing', hint: 'Test generation, coverage analysis, mutation testing' },
|
|
11
|
+
{ value: 'api-design', label: 'api-design', hint: 'REST/GraphQL contracts, versioning, error handling' },
|
|
12
|
+
{ value: 'performance', label: 'performance', hint: 'Budgets, profiling, bundle size, query optimization' },
|
|
13
|
+
{ value: 'accessibility', label: 'accessibility', hint: 'WCAG compliance, screen readers, keyboard navigation' },
|
|
14
|
+
{ value: 'architecture', label: 'architecture', hint: 'System design, boundaries, dependency management' },
|
|
15
|
+
{ value: 'database', label: 'database', hint: 'Schema design, migrations, indexing, query tuning' },
|
|
16
|
+
{ value: 'documentation', label: 'documentation', hint: 'API docs, READMEs, changelogs, code comments' },
|
|
17
|
+
{ value: 'devops', label: 'devops', hint: 'CI/CD pipelines, infrastructure as code, deployment' },
|
|
18
|
+
];
|
|
19
|
+
export const CUSTOM_DOMAIN_GUIDANCE = {
|
|
20
|
+
instruction: 'Define a custom domain (kebab-case)',
|
|
21
|
+
examples: [
|
|
22
|
+
'graphql-federation — Schema stitching, subgraph validation, entity resolution',
|
|
23
|
+
'data-pipeline — ETL jobs, stream processing, data quality checks',
|
|
24
|
+
'mobile-ux — Touch targets, gesture handling, responsive layouts',
|
|
25
|
+
],
|
|
26
|
+
antiExamples: [
|
|
27
|
+
'stuff — too vague, what kind of stuff?',
|
|
28
|
+
'MyDomain — must be kebab-case, not camelCase',
|
|
29
|
+
],
|
|
30
|
+
};
|
|
31
|
+
export const PRINCIPLE_CATEGORIES = [
|
|
32
|
+
{
|
|
33
|
+
label: 'Quality',
|
|
34
|
+
options: [
|
|
35
|
+
{ value: 'Simplicity over cleverness', label: 'Simplicity over cleverness' },
|
|
36
|
+
{ value: 'Convention over configuration', label: 'Convention over configuration' },
|
|
37
|
+
{ value: 'Test everything that can break', label: 'Test everything that can break' },
|
|
38
|
+
{ value: 'Respect existing patterns', label: 'Respect existing patterns' },
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
label: 'Safety',
|
|
43
|
+
options: [
|
|
44
|
+
{ value: 'Security first', label: 'Security first' },
|
|
45
|
+
{ value: 'Fail closed, not open', label: 'Fail closed, not open' },
|
|
46
|
+
{ value: 'Zero trust by default', label: 'Zero trust by default' },
|
|
47
|
+
{ value: 'Least privilege always', label: 'Least privilege always' },
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
label: 'Developer Experience',
|
|
52
|
+
options: [
|
|
53
|
+
{ value: 'Actionable feedback only', label: 'Actionable feedback only' },
|
|
54
|
+
{ value: 'Explain the why, not just the what', label: 'Explain the why, not just the what' },
|
|
55
|
+
{ value: 'Every comment includes a fix suggestion', label: 'Every comment includes a fix suggestion' },
|
|
56
|
+
{ value: 'Design for the consumer, not the implementer', label: 'Design for the consumer, not the implementer' },
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
label: 'Reliability',
|
|
61
|
+
options: [
|
|
62
|
+
{ value: 'Graceful degradation over hard failures', label: 'Graceful degradation over hard failures' },
|
|
63
|
+
{ value: 'Automate everything repeatable', label: 'Automate everything repeatable' },
|
|
64
|
+
{ value: 'Observability built in from day one', label: 'Observability built in from day one' },
|
|
65
|
+
{ value: 'Every migration must be reversible', label: 'Every migration must be reversible' },
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
];
|
|
69
|
+
export const CUSTOM_PRINCIPLE_GUIDANCE = {
|
|
70
|
+
instruction: 'Write a custom principle',
|
|
71
|
+
examples: [
|
|
72
|
+
'Never suggest any in production without a feature flag',
|
|
73
|
+
'Prefer composition over inheritance',
|
|
74
|
+
'Every public API must have a deprecation path before removal',
|
|
75
|
+
],
|
|
76
|
+
antiExamples: [
|
|
77
|
+
'Write good code — too vague, what does "good" mean?',
|
|
78
|
+
'Follow best practices — which ones? Be specific.',
|
|
79
|
+
],
|
|
80
|
+
};
|
|
81
|
+
// ─── Skills ─────────────────────────────────────────────────
|
|
82
|
+
/** Core skills — always included, never shown in picker. */
|
|
83
|
+
export const CORE_SKILLS = [
|
|
84
|
+
'brainstorming',
|
|
85
|
+
'systematic-debugging',
|
|
86
|
+
'verification-before-completion',
|
|
87
|
+
'health-check',
|
|
88
|
+
'context-resume',
|
|
89
|
+
];
|
|
90
|
+
export const SKILL_CATEGORIES = [
|
|
91
|
+
{
|
|
92
|
+
label: 'Planning & Execution',
|
|
93
|
+
options: [
|
|
94
|
+
{ value: 'writing-plans', label: 'writing-plans', hint: 'Structured multi-step planning before code changes' },
|
|
95
|
+
{ value: 'executing-plans', label: 'executing-plans', hint: 'Execute approved plans with review checkpoints' },
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
label: 'Knowledge & Learning',
|
|
100
|
+
options: [
|
|
101
|
+
{ value: 'vault-navigator', label: 'vault-navigator', hint: 'Deep-dive vault search and exploration' },
|
|
102
|
+
{ value: 'vault-capture', label: 'vault-capture', hint: 'Persist lessons learned to the knowledge vault' },
|
|
103
|
+
{ value: 'knowledge-harvest', label: 'knowledge-harvest', hint: 'Extract patterns from completed work' },
|
|
104
|
+
{ value: 'brain-debrief', label: 'brain-debrief', hint: 'Post-task intelligence summary and debriefing' },
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
label: 'Code Quality',
|
|
109
|
+
options: [
|
|
110
|
+
{ value: 'code-patrol', label: 'code-patrol', hint: 'Scan for anti-patterns and code violations' },
|
|
111
|
+
{ value: 'test-driven-development', label: 'test-driven-development', hint: 'TDD workflow: red, green, refactor' },
|
|
112
|
+
{ value: 'fix-and-learn', label: 'fix-and-learn', hint: 'Fix bugs and capture the lesson for next time' },
|
|
113
|
+
],
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
label: 'Team & Process',
|
|
117
|
+
options: [
|
|
118
|
+
{ value: 'retrospective', label: 'retrospective', hint: 'End-of-session retrospective and reflection' },
|
|
119
|
+
{ value: 'second-opinion', label: 'second-opinion', hint: 'Get a fresh perspective on tough decisions' },
|
|
120
|
+
{ value: 'onboard-me', label: 'onboard-me', hint: 'Guided codebase onboarding for new team members' },
|
|
121
|
+
],
|
|
122
|
+
},
|
|
123
|
+
];
|
|
124
|
+
/** Flat list of all optional skill values. */
|
|
125
|
+
export const ALL_OPTIONAL_SKILLS = SKILL_CATEGORIES.flatMap((c) => c.options.map((o) => o.value));
|
|
126
|
+
export const TONE_OPTIONS = [
|
|
127
|
+
{ value: 'precise', label: 'Precise', hint: 'Direct, factual, minimal commentary' },
|
|
128
|
+
{ value: 'mentor', label: 'Mentor', hint: 'Educational, explains the "why" behind suggestions' },
|
|
129
|
+
{ value: 'pragmatic', label: 'Pragmatic', hint: 'Balanced, focuses on actionable outcomes' },
|
|
130
|
+
];
|
|
131
|
+
// ─── Custom field guidance (role, description, greeting) ────
|
|
132
|
+
export const CUSTOM_ROLE_GUIDANCE = {
|
|
133
|
+
instruction: 'Describe what your agent does (one sentence)',
|
|
134
|
+
examples: [
|
|
135
|
+
'Enforces accessibility standards across React components',
|
|
136
|
+
'Generates and maintains API documentation from code',
|
|
137
|
+
'Monitors performance budgets and flags regressions',
|
|
138
|
+
],
|
|
139
|
+
};
|
|
140
|
+
export const CUSTOM_DESCRIPTION_GUIDANCE = {
|
|
141
|
+
instruction: 'Describe your agent in detail (10-500 characters)',
|
|
142
|
+
examples: [
|
|
143
|
+
'This agent validates GraphQL schemas against federation rules, checks for breaking changes, and ensures consistent naming conventions across subgraphs.',
|
|
144
|
+
],
|
|
145
|
+
};
|
|
146
|
+
export const CUSTOM_GREETING_GUIDANCE = {
|
|
147
|
+
instruction: 'Write a custom greeting (first thing users see)',
|
|
148
|
+
examples: [
|
|
149
|
+
"Hola! I'm Salvador — your design system guardian.",
|
|
150
|
+
'Ready to review. Drop a PR link or describe the issue.',
|
|
151
|
+
"Hey! Let's make sure your APIs are rock solid.",
|
|
152
|
+
],
|
|
153
|
+
};
|
|
154
|
+
//# sourceMappingURL=playbook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"playbook.js","sourceRoot":"","sources":["../../src/prompts/playbook.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/commands/create.ts
CHANGED
|
@@ -12,8 +12,9 @@ export function registerCreate(program: Command): void {
|
|
|
12
12
|
.command('create')
|
|
13
13
|
.argument('[name]', 'Agent ID (kebab-case)')
|
|
14
14
|
.option('-c, --config <path>', 'Path to JSON config file (skip interactive prompts)')
|
|
15
|
+
.option('-y, --yes', 'Skip confirmation prompts (use with --config for fully non-interactive)')
|
|
15
16
|
.description('Create a new Soleri agent')
|
|
16
|
-
.action(async (name?: string, opts?: { config?: string }) => {
|
|
17
|
+
.action(async (name?: string, opts?: { config?: string; yes?: boolean }) => {
|
|
17
18
|
try {
|
|
18
19
|
let config;
|
|
19
20
|
|
|
@@ -40,6 +41,8 @@ export function registerCreate(program: Command): void {
|
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
const nonInteractive = !!(opts?.yes || opts?.config);
|
|
45
|
+
|
|
43
46
|
// Hook packs — from config file or interactive prompt
|
|
44
47
|
let selectedPacks: string[] = [];
|
|
45
48
|
if (config.hookPacks && config.hookPacks.length > 0) {
|
|
@@ -56,7 +59,7 @@ export function registerCreate(program: Command): void {
|
|
|
56
59
|
}
|
|
57
60
|
selectedPacks = selectedPacks.filter((pk) => available.includes(pk));
|
|
58
61
|
}
|
|
59
|
-
} else if (!
|
|
62
|
+
} else if (!nonInteractive) {
|
|
60
63
|
const packs = listPacks();
|
|
61
64
|
const packChoices = packs.map((pk) => ({
|
|
62
65
|
value: pk.name,
|
|
@@ -81,14 +84,22 @@ export function registerCreate(program: Command): void {
|
|
|
81
84
|
p.log.info(`Will create ${preview.files.length} files in ${preview.agentDir}`);
|
|
82
85
|
p.log.info(`Facades: ${preview.facades.map((f) => f.name).join(', ')}`);
|
|
83
86
|
p.log.info(`Domains: ${preview.domains.join(', ')}`);
|
|
87
|
+
if (config.tone) {
|
|
88
|
+
p.log.info(`Tone: ${config.tone}`);
|
|
89
|
+
}
|
|
90
|
+
if (config.skills?.length) {
|
|
91
|
+
p.log.info(`Skills: ${config.skills.length} selected`);
|
|
92
|
+
}
|
|
84
93
|
if (selectedPacks.length > 0) {
|
|
85
94
|
p.log.info(`Hook packs: ${selectedPacks.join(', ')}`);
|
|
86
95
|
}
|
|
87
96
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
p.
|
|
91
|
-
|
|
97
|
+
if (!nonInteractive) {
|
|
98
|
+
const confirmed = await p.confirm({ message: 'Create agent?' });
|
|
99
|
+
if (p.isCancel(confirmed) || !confirmed) {
|
|
100
|
+
p.outro('Cancelled.');
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
92
103
|
}
|
|
93
104
|
|
|
94
105
|
// Scaffold + auto-build
|
package/src/commands/doctor.ts
CHANGED
|
@@ -11,11 +11,14 @@ export function registerDoctor(program: Command): void {
|
|
|
11
11
|
|
|
12
12
|
const results = runAllChecks();
|
|
13
13
|
let hasFailures = false;
|
|
14
|
+
let hasWarnings = false;
|
|
14
15
|
|
|
15
16
|
for (const r of results) {
|
|
16
17
|
if (r.status === 'pass') log.pass(r.label, r.detail);
|
|
17
|
-
else if (r.status === 'warn')
|
|
18
|
-
|
|
18
|
+
else if (r.status === 'warn') {
|
|
19
|
+
log.warn(r.label, r.detail);
|
|
20
|
+
hasWarnings = true;
|
|
21
|
+
} else {
|
|
19
22
|
log.fail(r.label, r.detail);
|
|
20
23
|
hasFailures = true;
|
|
21
24
|
}
|
|
@@ -26,6 +29,10 @@ export function registerDoctor(program: Command): void {
|
|
|
26
29
|
if (hasFailures) {
|
|
27
30
|
log.info('Some checks failed. Fix the issues above and run soleri doctor again.');
|
|
28
31
|
process.exit(1);
|
|
32
|
+
} else if (hasWarnings) {
|
|
33
|
+
log.info(
|
|
34
|
+
'All checks passed with warnings. Run from an agent directory to check agent-specific health.',
|
|
35
|
+
);
|
|
29
36
|
} else {
|
|
30
37
|
log.info('All checks passed!');
|
|
31
38
|
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import * as p from '@clack/prompts';
|
|
3
|
+
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { detectAgent } from '../utils/agent-context.js';
|
|
6
|
+
|
|
7
|
+
export function registerExtend(program: Command): void {
|
|
8
|
+
const extend = program
|
|
9
|
+
.command('extend')
|
|
10
|
+
.description('Manage agent extensions — custom ops, facades, middleware');
|
|
11
|
+
|
|
12
|
+
extend
|
|
13
|
+
.command('init')
|
|
14
|
+
.description('Initialize the extensions directory (if not already present)')
|
|
15
|
+
.action(async () => {
|
|
16
|
+
const ctx = detectAgent();
|
|
17
|
+
if (!ctx) {
|
|
18
|
+
p.log.error('No agent project detected. Run this from an agent root.');
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const extDir = join(ctx.agentPath, 'src', 'extensions');
|
|
23
|
+
if (existsSync(join(extDir, 'index.ts'))) {
|
|
24
|
+
p.log.info('Extensions directory already exists.');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const dirs = ['', 'ops', 'facades', 'middleware'];
|
|
29
|
+
for (const d of dirs) {
|
|
30
|
+
mkdirSync(join(extDir, d), { recursive: true });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const { generateExtensionsIndex, generateExampleOp } = await import('@soleri/forge/lib');
|
|
34
|
+
const config = { id: ctx.agentId, name: ctx.agentId } as Parameters<
|
|
35
|
+
typeof generateExtensionsIndex
|
|
36
|
+
>[0];
|
|
37
|
+
writeFileSync(join(extDir, 'index.ts'), generateExtensionsIndex(config), 'utf-8');
|
|
38
|
+
writeFileSync(join(extDir, 'ops', 'example.ts'), generateExampleOp(config), 'utf-8');
|
|
39
|
+
|
|
40
|
+
p.log.success('Extensions directory created at src/extensions/');
|
|
41
|
+
p.log.info(
|
|
42
|
+
'Edit src/extensions/index.ts to register your custom ops, facades, and middleware.',
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
extend
|
|
47
|
+
.command('add-op')
|
|
48
|
+
.argument('<name>', 'Operation name in snake_case (e.g., "summarize_pr")')
|
|
49
|
+
.description('Scaffold a new custom op')
|
|
50
|
+
.action(async (name: string) => {
|
|
51
|
+
const ctx = detectAgent();
|
|
52
|
+
if (!ctx) {
|
|
53
|
+
p.log.error('No agent project detected. Run this from an agent root.');
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const opsDir = join(ctx.agentPath, 'src', 'extensions', 'ops');
|
|
58
|
+
mkdirSync(opsDir, { recursive: true });
|
|
59
|
+
|
|
60
|
+
const fileName = name.replace(/_/g, '-');
|
|
61
|
+
const filePath = join(opsDir, `${fileName}.ts`);
|
|
62
|
+
|
|
63
|
+
if (existsSync(filePath)) {
|
|
64
|
+
p.log.error(`File already exists: src/extensions/ops/${fileName}.ts`);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const fnName =
|
|
69
|
+
'create' +
|
|
70
|
+
name
|
|
71
|
+
.split('_')
|
|
72
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
73
|
+
.join('') +
|
|
74
|
+
'Op';
|
|
75
|
+
|
|
76
|
+
const content = `import { z } from 'zod';
|
|
77
|
+
import type { OpDefinition, AgentRuntime } from '@soleri/core';
|
|
78
|
+
|
|
79
|
+
export function ${fnName}(runtime: AgentRuntime): OpDefinition {
|
|
80
|
+
return {
|
|
81
|
+
name: '${name}',
|
|
82
|
+
description: 'TODO: describe what this op does',
|
|
83
|
+
auth: 'read',
|
|
84
|
+
schema: z.object({
|
|
85
|
+
// TODO: define your parameters
|
|
86
|
+
}),
|
|
87
|
+
handler: async (params) => {
|
|
88
|
+
// TODO: implement your logic
|
|
89
|
+
// You have access to runtime.vault, runtime.brain, runtime.planner, etc.
|
|
90
|
+
return { status: 'ok' };
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
`;
|
|
95
|
+
|
|
96
|
+
writeFileSync(filePath, content, 'utf-8');
|
|
97
|
+
p.log.success(`Created src/extensions/ops/${fileName}.ts`);
|
|
98
|
+
p.log.info(`Import ${fnName} in src/extensions/index.ts and add it to the ops array.`);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
extend
|
|
102
|
+
.command('add-facade')
|
|
103
|
+
.argument('<name>', 'Facade name in kebab-case (e.g., "github")')
|
|
104
|
+
.description('Scaffold a new custom facade')
|
|
105
|
+
.action(async (name: string) => {
|
|
106
|
+
const ctx = detectAgent();
|
|
107
|
+
if (!ctx) {
|
|
108
|
+
p.log.error('No agent project detected. Run this from an agent root.');
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const facadesDir = join(ctx.agentPath, 'src', 'extensions', 'facades');
|
|
113
|
+
mkdirSync(facadesDir, { recursive: true });
|
|
114
|
+
|
|
115
|
+
const filePath = join(facadesDir, `${name}.ts`);
|
|
116
|
+
if (existsSync(filePath)) {
|
|
117
|
+
p.log.error(`File already exists: src/extensions/facades/${name}.ts`);
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const facadeName = `${ctx.agentId}_${name.replace(/-/g, '_')}`;
|
|
122
|
+
const className = name
|
|
123
|
+
.split('-')
|
|
124
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
125
|
+
.join('');
|
|
126
|
+
|
|
127
|
+
const content = `import { z } from 'zod';
|
|
128
|
+
import type { FacadeConfig, AgentRuntime } from '@soleri/core';
|
|
129
|
+
|
|
130
|
+
export function create${className}Facade(runtime: AgentRuntime): FacadeConfig {
|
|
131
|
+
return {
|
|
132
|
+
name: '${facadeName}',
|
|
133
|
+
description: 'TODO: describe this facade',
|
|
134
|
+
ops: [
|
|
135
|
+
{
|
|
136
|
+
name: 'status',
|
|
137
|
+
description: 'TODO: describe this op',
|
|
138
|
+
auth: 'read',
|
|
139
|
+
schema: z.object({}),
|
|
140
|
+
handler: async () => {
|
|
141
|
+
return { status: 'ok' };
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
`;
|
|
148
|
+
|
|
149
|
+
writeFileSync(filePath, content, 'utf-8');
|
|
150
|
+
p.log.success(`Created src/extensions/facades/${name}.ts`);
|
|
151
|
+
p.log.info(`Import and add the facade to src/extensions/index.ts facades array.`);
|
|
152
|
+
p.log.info(`This will register as MCP tool: ${facadeName}`);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
extend
|
|
156
|
+
.command('add-middleware')
|
|
157
|
+
.argument('<name>', 'Middleware name in kebab-case (e.g., "audit-logger")')
|
|
158
|
+
.description('Scaffold a new middleware')
|
|
159
|
+
.action(async (name: string) => {
|
|
160
|
+
const ctx = detectAgent();
|
|
161
|
+
if (!ctx) {
|
|
162
|
+
p.log.error('No agent project detected. Run this from an agent root.');
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const mwDir = join(ctx.agentPath, 'src', 'extensions', 'middleware');
|
|
167
|
+
mkdirSync(mwDir, { recursive: true });
|
|
168
|
+
|
|
169
|
+
const filePath = join(mwDir, `${name}.ts`);
|
|
170
|
+
if (existsSync(filePath)) {
|
|
171
|
+
p.log.error(`File already exists: src/extensions/middleware/${name}.ts`);
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const varName = name.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
176
|
+
|
|
177
|
+
const content = `import type { OpMiddleware } from '@soleri/core';
|
|
178
|
+
|
|
179
|
+
export const ${varName}: OpMiddleware = {
|
|
180
|
+
name: '${name}',
|
|
181
|
+
before: async (ctx) => {
|
|
182
|
+
// Runs before every op. Return modified params or throw to reject.
|
|
183
|
+
// console.error(\`[\${ctx.facade}.\${ctx.op}] called\`);
|
|
184
|
+
return ctx.params;
|
|
185
|
+
},
|
|
186
|
+
after: async (ctx) => {
|
|
187
|
+
// Runs after every op. Return modified result or throw.
|
|
188
|
+
return ctx.result;
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
`;
|
|
192
|
+
|
|
193
|
+
writeFileSync(filePath, content, 'utf-8');
|
|
194
|
+
p.log.success(`Created src/extensions/middleware/${name}.ts`);
|
|
195
|
+
p.log.info('Import and add to src/extensions/index.ts middleware array.');
|
|
196
|
+
});
|
|
197
|
+
}
|
package/src/commands/list.ts
CHANGED
|
@@ -1,42 +1,81 @@
|
|
|
1
1
|
import { resolve } from 'node:path';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
2
4
|
import type { Command } from 'commander';
|
|
3
5
|
import { listAgents } from '@soleri/forge/lib';
|
|
6
|
+
import type { AgentInfo } from '@soleri/forge/lib';
|
|
4
7
|
import * as log from '../utils/logger.js';
|
|
5
8
|
|
|
6
9
|
function pad(s: string, len: number): string {
|
|
7
10
|
return s.length >= len ? s.slice(0, len) : s + ' '.repeat(len - s.length);
|
|
8
11
|
}
|
|
9
12
|
|
|
13
|
+
function printAgentTable(agents: AgentInfo[]): void {
|
|
14
|
+
console.log(` ${pad('ID', 16)}${pad('Domains', 26)}${pad('Built', 8)}${pad('Deps', 8)}Path`);
|
|
15
|
+
console.log(' ' + '-'.repeat(80));
|
|
16
|
+
|
|
17
|
+
for (const agent of agents) {
|
|
18
|
+
const built = agent.hasDistDir ? '✓' : '✗';
|
|
19
|
+
const deps = agent.hasNodeModules ? '✓' : '✗';
|
|
20
|
+
const domains = agent.domains.join(', ') || '(none)';
|
|
21
|
+
const truncDomains = domains.length > 25 ? domains.slice(0, 22) + '...' : domains;
|
|
22
|
+
console.log(
|
|
23
|
+
` ${pad(agent.id, 16)}${pad(truncDomains, 26)}${pad(built, 8)}${pad(deps, 8)}${agent.path}`,
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
10
28
|
export function registerList(program: Command): void {
|
|
11
29
|
program
|
|
12
30
|
.command('list')
|
|
13
|
-
.argument('[dir]', 'Directory to scan for agents'
|
|
14
|
-
.description('List all Soleri agents in a directory')
|
|
15
|
-
.action((dir
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
31
|
+
.argument('[dir]', 'Directory to scan for agents')
|
|
32
|
+
.description('List all Soleri agents in a directory (scans common locations if none given)')
|
|
33
|
+
.action((dir?: string) => {
|
|
34
|
+
if (dir) {
|
|
35
|
+
// Explicit directory — scan only that
|
|
36
|
+
const targetDir = resolve(dir);
|
|
37
|
+
const agents = listAgents(targetDir);
|
|
38
|
+
|
|
39
|
+
if (agents.length === 0) {
|
|
40
|
+
log.info(`No agents found in ${targetDir}`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
log.heading(`Agents in ${targetDir}`);
|
|
45
|
+
printAgentTable(agents);
|
|
46
|
+
console.log(`\n ${agents.length} agent(s) found`);
|
|
21
47
|
return;
|
|
22
48
|
}
|
|
23
49
|
|
|
24
|
-
|
|
50
|
+
// No directory given — scan common locations
|
|
51
|
+
const home = homedir();
|
|
52
|
+
const scanDirs = [
|
|
53
|
+
process.cwd(),
|
|
54
|
+
home,
|
|
55
|
+
resolve(home, 'agents'),
|
|
56
|
+
resolve(home, 'projects'),
|
|
57
|
+
].filter((d, i, arr) => existsSync(d) && arr.indexOf(d) === i);
|
|
58
|
+
|
|
59
|
+
const allAgents: AgentInfo[] = [];
|
|
60
|
+
for (const d of scanDirs) {
|
|
61
|
+
allAgents.push(...listAgents(d));
|
|
62
|
+
}
|
|
25
63
|
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
|
|
64
|
+
// Deduplicate by path
|
|
65
|
+
const seen = new Set<string>();
|
|
66
|
+
const unique = allAgents.filter((a) => {
|
|
67
|
+
if (seen.has(a.path)) return false;
|
|
68
|
+
seen.add(a.path);
|
|
69
|
+
return true;
|
|
70
|
+
});
|
|
29
71
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const domains = agent.domains.join(', ') || '(none)';
|
|
34
|
-
const truncDomains = domains.length > 25 ? domains.slice(0, 22) + '...' : domains;
|
|
35
|
-
console.log(
|
|
36
|
-
` ${pad(agent.id, 16)}${pad(truncDomains, 26)}${pad(built, 8)}${pad(deps, 8)}${agent.path}`,
|
|
37
|
-
);
|
|
72
|
+
if (unique.length === 0) {
|
|
73
|
+
log.info('No agents found. Create one with: soleri create');
|
|
74
|
+
return;
|
|
38
75
|
}
|
|
39
76
|
|
|
40
|
-
|
|
77
|
+
log.heading('Soleri Agents');
|
|
78
|
+
printAgentTable(unique);
|
|
79
|
+
console.log(`\n ${unique.length} agent(s) found`);
|
|
41
80
|
});
|
|
42
81
|
}
|
package/src/main.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
3
4
|
import { Command } from 'commander';
|
|
4
5
|
import { registerCreate } from './commands/create.js';
|
|
5
6
|
import { registerList } from './commands/list.js';
|
|
@@ -11,13 +12,17 @@ import { registerHooks } from './commands/hooks.js';
|
|
|
11
12
|
import { registerGovernance } from './commands/governance.js';
|
|
12
13
|
import { registerTest } from './commands/test.js';
|
|
13
14
|
import { registerUpgrade } from './commands/upgrade.js';
|
|
15
|
+
import { registerExtend } from './commands/extend.js';
|
|
16
|
+
|
|
17
|
+
const require = createRequire(import.meta.url);
|
|
18
|
+
const { version } = require('../package.json');
|
|
14
19
|
|
|
15
20
|
const program = new Command();
|
|
16
21
|
|
|
17
22
|
program
|
|
18
23
|
.name('soleri')
|
|
19
24
|
.description('Developer CLI for creating and managing Soleri AI agents')
|
|
20
|
-
.version(
|
|
25
|
+
.version(version);
|
|
21
26
|
|
|
22
27
|
registerCreate(program);
|
|
23
28
|
registerList(program);
|
|
@@ -29,5 +34,6 @@ registerHooks(program);
|
|
|
29
34
|
registerGovernance(program);
|
|
30
35
|
registerTest(program);
|
|
31
36
|
registerUpgrade(program);
|
|
37
|
+
registerExtend(program);
|
|
32
38
|
|
|
33
39
|
program.parse();
|