@foundation0/api 1.0.1 → 1.1.1

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # F0 MCP Server
1
+ # example-org MCP Server
2
2
 
3
- Install and register the F0 MCP server with the MCP-enabled agent/tooling you use.
3
+ Install and register the example-org MCP server with the MCP-enabled agent/tooling you use.
4
4
 
5
5
  ## 1) Install
6
6
 
@@ -23,9 +23,13 @@ The runnable entrypoint is `f0-mcp` (available from `bin` when installed globall
23
23
  The server supports:
24
24
 
25
25
  - `GITEA_HOST` (recommended, required by git-backed tools)
26
- - `F0_GITEA_HOST` (fallback host variable)
26
+ - `FALLBACK_GITEA_HOST` (fallback host variable)
27
27
  - `GITEA_TOKEN` (for authenticated actions)
28
28
  - `MCP_TOOLS_PREFIX` (or `--tools-prefix`) to namespace tools in clients
29
+ - `MCP_ALLOWED_ROOT_ENDPOINTS` (or `--allowed-root-endpoints`) to whitelist API root endpoints
30
+ - `MCP_DISABLE_WRITE` (or `--disable-write`) to expose read-only tools only
31
+ - `MCP_ENABLE_ISSUES` (or `--enable-issues`) to allow issue endpoints when write mode is disabled
32
+ - `MCP_ADMIN` (or `--admin`) to expose admin-only destructive endpoints
29
33
 
30
34
  Useful defaults:
31
35
 
@@ -37,7 +41,7 @@ Useful defaults:
37
41
  ### A) `.codex/config.toml`
38
42
 
39
43
  ```toml
40
- [mcp_servers.f0]
44
+ [mcp_servers.example]
41
45
  command = "bun"
42
46
  args = ["x", "@foundation0/api", "f0-mcp"]
43
47
  enabled = true
@@ -45,7 +49,7 @@ startup_timeout_ms = 20_000
45
49
  env = {
46
50
  GITEA_HOST = "https://gitea.example.com",
47
51
  GITEA_TOKEN = "your-token",
48
- MCP_TOOLS_PREFIX = "f0"
52
+ MCP_TOOLS_PREFIX = "example"
49
53
  }
50
54
  ```
51
55
 
@@ -61,9 +65,9 @@ args = []
61
65
  ```json
62
66
  {
63
67
  "mcpServers": {
64
- "f0": {
68
+ "example": {
65
69
  "command": "bun",
66
- "args": ["x", "@foundation0/api", "f0-mcp", "--tools-prefix", "f0"],
70
+ "args": ["x", "@foundation0/api", "f0-mcp", "--tools-prefix", "example"],
67
71
  "env": {
68
72
  "GITEA_HOST": "https://gitea.example.com",
69
73
  "GITEA_TOKEN": "your-token"
@@ -82,7 +86,7 @@ Use the same command/args/env block in your MCP server configuration area:
82
86
  ```json
83
87
  {
84
88
  "command": "bun",
85
- "args": ["x", "@foundation0/api", "f0-mcp", "--tools-prefix", "f0"],
89
+ "args": ["x", "@foundation0/api", "f0-mcp", "--tools-prefix", "example"],
86
90
  "env": {
87
91
  "GITEA_HOST": "https://gitea.example.com",
88
92
  "GITEA_TOKEN": "your-token"
@@ -105,9 +109,20 @@ Add environment variables for host/token and optional `MCP_TOOLS_PREFIX`.
105
109
  f0-mcp --help
106
110
 
107
111
  # examples
108
- f0-mcp --tools-prefix=f0 --server-name=my-f0
112
+ f0-mcp --tools-prefix=example --server-name=my-example
109
113
  f0-mcp --tools-prefix api --server-version 1.2.3
114
+ f0-mcp --allowed-root-endpoints projects
115
+ f0-mcp --allowed-root-endpoints agents,projects
116
+ f0-mcp --disable-write
117
+ f0-mcp --allowed-root-endpoints projects --disable-write
118
+ f0-mcp --disable-write --enable-issues
119
+ f0-mcp --admin
120
+ f0-mcp --admin --disable-write --enable-issues
110
121
  ```
111
122
 
112
123
  - `--tools-prefix` is useful when running multiple MCP servers side-by-side.
113
124
  - `--server-name` and `--server-version` are mostly metadata but can help identify logs and client tool sets.
125
+ - `--allowed-root-endpoints` restricts exposed tools to selected root namespaces (`agents`, `projects`).
126
+ - `--disable-write` removes write-capable tools (for example create/update/delete/sync/set/main/run operations).
127
+ - `--enable-issues` is a special-case override for `--disable-write`: issue endpoints remain enabled (`fetchGitTasks`, `readGitTask`, `writeGitTask`).
128
+ - `projects.syncTasks` and `projects.clearIssues` are admin-only and hidden by default; they are exposed only with `--admin` (or `MCP_ADMIN=true`).
package/agents.ts CHANGED
@@ -23,12 +23,25 @@ export interface AgentLoadResult {
23
23
  loaded: unknown
24
24
  }
25
25
 
26
+ export interface AgentRunResult {
27
+ agentName: string
28
+ exitCode: number
29
+ }
30
+
31
+ export interface AgentCreateResult {
32
+ agentName: string
33
+ agentRoot: string
34
+ createdPaths: string[]
35
+ }
36
+
26
37
  interface ActiveConfigInput {
27
38
  path: string
28
39
  required: boolean
29
40
  }
30
41
 
31
- const CLI_NAME = 'f0'
42
+ const CLI_NAME = 'example'
43
+ const AGENT_INITIAL_VERSION = 'v0.0.1'
44
+ const DEFAULT_SKILL_NAME = 'coding-standards'
32
45
  const VERSION_RE = 'v?\\d+(?:\\.\\d+){2,}'
33
46
  const VERSION_RE_CORE = `(?:${VERSION_RE})`
34
47
  const VERSION_WITH_EXT_RE = new RegExp(`^(.+)\\.(${VERSION_RE})\\.([A-Za-z][A-Za-z0-9_-]*)$`)
@@ -36,13 +49,16 @@ const VERSION_ONLY_RE = new RegExp(`^(.+)\\.(${VERSION_RE})$`)
36
49
  const PRIORITIZED_EXTS = ['.md', '.json', '.yaml', '.yml', '.ts', '.js', '.txt']
37
50
 
38
51
  export function usage(): string {
39
- return `Usage:\n ${CLI_NAME} agents <agent-name> <file-path> --set-active\n ${CLI_NAME} agents --list\n ${CLI_NAME} agents <agent-name> load\n\n` +
40
- `Examples:\n ${CLI_NAME} agents coder /system/boot.v0.0.1 --set-active\n ${CLI_NAME} agents coder /system/boot --set-active --latest\n ${CLI_NAME} agents coder /skills/coding-standards.v0.0.1 --set-active\n ${CLI_NAME} agents --list\n` +
52
+ return `Usage:\n ${CLI_NAME} agents create <agent-name>\n ${CLI_NAME} agents <agent-name> <file-path> --set-active\n ${CLI_NAME} agents --list\n ${CLI_NAME} agents <agent-name> load\n ${CLI_NAME} agents <agent-name> run [codex-args...]\n\n` +
53
+ `Examples:\n ${CLI_NAME} agents create reviewer\n ${CLI_NAME} agents coder /system/boot.v0.0.1 --set-active\n ${CLI_NAME} agents coder /system/boot --set-active --latest\n ${CLI_NAME} agents coder /skills/coding-standards.v0.0.1 --set-active\n ${CLI_NAME} agents --list\n` +
41
54
  ` ${CLI_NAME} agents coder load\n` +
55
+ ` ${CLI_NAME} agents coder run --model gpt-5\n` +
42
56
  `\n` +
43
57
  `file-path is relative to the agent root (leading slash required).\n` +
44
58
  `Use --latest to resolve /file-name to the latest version.\n` +
45
- `The active file created is [file].active.<ext>.\n`
59
+ `The active file created is [file].active.<ext>.\n` +
60
+ `Use "run" to start codex with developer_instructions from system/prompt.ts.\n` +
61
+ `Set EXAMPLE_CODEX_BIN to pin a specific codex binary.\n`
46
62
  }
47
63
 
48
64
  export function resolveAgentsRoot(processRoot: string = process.cwd()): string {
@@ -76,6 +92,200 @@ export function listAgents(processRoot: string = process.cwd()): string[] {
76
92
  .sort((a, b) => a.localeCompare(b))
77
93
  }
78
94
 
95
+ function normalizeAgentName(agentName: string): string {
96
+ if (!agentName || typeof agentName !== 'string') {
97
+ throw new Error('agent-name is required.')
98
+ }
99
+
100
+ const normalized = agentName.trim()
101
+ if (!normalized) {
102
+ throw new Error('agent-name is required.')
103
+ }
104
+
105
+ if (normalized.startsWith('-')) {
106
+ throw new Error(`Invalid agent-name: ${agentName}`)
107
+ }
108
+
109
+ if (normalized === '.' || normalized === '..' || normalized.includes('/') || normalized.includes('\\')) {
110
+ throw new Error(`Invalid agent-name: ${agentName}`)
111
+ }
112
+
113
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(normalized)) {
114
+ throw new Error(`Invalid agent-name: ${agentName}`)
115
+ }
116
+
117
+ return normalized
118
+ }
119
+
120
+ function titleCaseAgentName(agentName: string): string {
121
+ return agentName
122
+ .split(/[-_]+/)
123
+ .filter(Boolean)
124
+ .map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`)
125
+ .join(' ')
126
+ }
127
+
128
+ function buildBootDocument(agentName: string): string {
129
+ const title = titleCaseAgentName(agentName) || agentName
130
+ return `# ${title} Agent Boot (${AGENT_INITIAL_VERSION})
131
+
132
+ You are the \`${agentName}\` agent.
133
+
134
+ ## Role
135
+ - Follow the active workflow and execute scoped tasks.
136
+ - Keep outputs concise, concrete, and implementation-focused.
137
+ - Avoid changing unrelated files or behavior.
138
+
139
+ ## Startup Sequence
140
+ 1. Read active boot/workflow docs and enabled skills.
141
+ 2. Confirm scope and constraints before changes.
142
+ 3. Apply the smallest safe change set.
143
+ 4. Report changed files and outcomes.
144
+ `
145
+ }
146
+
147
+ function buildWorkflowDocument(agentName: string): string {
148
+ return `# ${titleCaseAgentName(agentName) || agentName} Workflow (${AGENT_INITIAL_VERSION})
149
+
150
+ 1. Parse request and constraints.
151
+ 2. Identify exact files and minimal edits.
152
+ 3. Implement and verify changes.
153
+ 4. If blocked, stop and report the blocker.
154
+ 5. Return result summary with next actions if needed.
155
+ `
156
+ }
157
+
158
+ function buildToolsConfig(): string {
159
+ return `${JSON.stringify({
160
+ tools: [
161
+ {
162
+ name: 'shell_command',
163
+ enabled: true,
164
+ description: 'Execute shell commands for file and workspace operations.',
165
+ },
166
+ {
167
+ name: 'apply_patch',
168
+ enabled: true,
169
+ description: 'Apply focused file edits safely.',
170
+ },
171
+ {
172
+ name: 'read_file',
173
+ enabled: true,
174
+ description: 'Read files when needed for context.',
175
+ },
176
+ ],
177
+ permissions: {
178
+ network: false,
179
+ write: true,
180
+ delete: true,
181
+ },
182
+ }, null, 2)}
183
+ `
184
+ }
185
+
186
+ function buildModelConfig(): string {
187
+ return `${JSON.stringify({
188
+ provider: 'openai',
189
+ model: 'gpt-5',
190
+ temperature: 0.2,
191
+ max_tokens: 4096,
192
+ top_p: 0.95,
193
+ }, null, 2)}
194
+ `
195
+ }
196
+
197
+ function buildDefaultSkillDocument(): string {
198
+ return `# Coding Standards (${AGENT_INITIAL_VERSION})
199
+
200
+ - Keep changes minimal and focused on the request.
201
+ - Preserve existing behavior unless explicitly changing it.
202
+ - Prefer readable, deterministic implementations.
203
+ `
204
+ }
205
+
206
+ const SYSTEM_PROMPT_TS_TEMPLATE = `import * as fs from 'fs'
207
+ import * as path from 'path'
208
+
209
+ const base = path.join(__dirname)
210
+ const readActive = (p: string) => fs.readFileSync(path.join(base, p), 'utf8')
211
+
212
+ export const boot = readActive('boot.active.md')
213
+ export const workflow = readActive('workflow.active.md')
214
+ export const tools = JSON.parse(readActive('tools.active.json'))
215
+ export const model = JSON.parse(readActive('model.active.json'))
216
+
217
+ export const prompt = [boot, workflow].join('\\n\\n')
218
+
219
+ export const agentConfig = {
220
+ boot,
221
+ workflow,
222
+ tools,
223
+ model,
224
+ prompt,
225
+ }
226
+ `
227
+
228
+ const SKILLS_TS_TEMPLATE = `import * as fs from 'fs'
229
+ import * as path from 'path'
230
+
231
+ export interface SkillFile {
232
+ id: string
233
+ version: string
234
+ activeFile: string
235
+ resolvedFile: string
236
+ content: string
237
+ }
238
+
239
+ const skillsDir = path.join(__dirname)
240
+ const enabledListPath = path.join(skillsDir, 'enabled-skills.md')
241
+
242
+ const resolveVersionAndId = (activeFile: string) => {
243
+ const baseName = path.basename(activeFile, '.active.md')
244
+ const resolved = \`\${baseName}.${AGENT_INITIAL_VERSION}.md\`
245
+ return {
246
+ id: baseName,
247
+ version: '${AGENT_INITIAL_VERSION}',
248
+ resolvedFile: resolved,
249
+ }
250
+ }
251
+
252
+ const enabledLines = fs
253
+ .readFileSync(enabledListPath, 'utf8')
254
+ .split(/\\r?\\n/)
255
+ .map((line) => line.trim())
256
+ .filter(Boolean)
257
+
258
+ export const skills: SkillFile[] = enabledLines.map((activeFile) => {
259
+ const resolved = resolveVersionAndId(activeFile)
260
+ return {
261
+ ...resolved,
262
+ activeFile,
263
+ content: fs.readFileSync(path.join(skillsDir, resolved.resolvedFile), 'utf8'),
264
+ }
265
+ })
266
+ `
267
+
268
+ const LOAD_TS_TEMPLATE = `import { readFileSync } from 'fs'
269
+ import * as path from 'path'
270
+
271
+ import { agentConfig } from './system/prompt'
272
+ import { skills } from './skills/skills'
273
+
274
+ const activeDoc = path.join(__dirname, 'system')
275
+
276
+ const loadActive = (name: string) =>
277
+ readFileSync(path.join(activeDoc, name), 'utf8').trim()
278
+
279
+ export const load = () => ({
280
+ ...agentConfig,
281
+ bootDoc: loadActive('boot.active.md'),
282
+ workflowDoc: loadActive('workflow.active.md'),
283
+ skills,
284
+ })
285
+
286
+ export type AgentBundle = ReturnType<typeof load>
287
+ `
288
+
79
289
  export function parseTargetSpec(spec: string): VersionedFileSpec {
80
290
  if (!spec || typeof spec !== 'string') {
81
291
  throw new Error('file-path is required.')
@@ -285,6 +495,68 @@ function createWindowsSymlink(activeFile: string, sourceFile: string): boolean {
285
495
  return result.status === 0
286
496
  }
287
497
 
498
+ export function createAgent(agentName: string, processRoot: string = process.cwd()): AgentCreateResult {
499
+ const normalizedAgentName = normalizeAgentName(agentName)
500
+ const agentsRoot = resolveAgentsRootFrom(processRoot)
501
+ const agentRoot = path.join(agentsRoot, normalizedAgentName)
502
+
503
+ if (fs.existsSync(agentRoot)) {
504
+ throw new Error(`Agent folder already exists: ${agentRoot}`)
505
+ }
506
+
507
+ const systemDir = path.join(agentRoot, 'system')
508
+ const skillsDir = path.join(agentRoot, 'skills')
509
+
510
+ fs.mkdirSync(systemDir, { recursive: true })
511
+ fs.mkdirSync(skillsDir, { recursive: true })
512
+
513
+ const createdPaths: string[] = []
514
+ const writeFile = (relativePath: string, content: string) => {
515
+ const absolutePath = path.join(agentRoot, relativePath)
516
+ fs.mkdirSync(path.dirname(absolutePath), { recursive: true })
517
+ fs.writeFileSync(absolutePath, content, 'utf8')
518
+ createdPaths.push(absolutePath)
519
+ }
520
+
521
+ const bootFileName = `boot.${AGENT_INITIAL_VERSION}.md`
522
+ const workflowFileName = `workflow.${AGENT_INITIAL_VERSION}.md`
523
+ const toolsFileName = `tools.${AGENT_INITIAL_VERSION}.json`
524
+ const modelFileName = `model.${AGENT_INITIAL_VERSION}.json`
525
+ const skillFileName = `${DEFAULT_SKILL_NAME}.${AGENT_INITIAL_VERSION}.md`
526
+ const activeSkillFileName = `${DEFAULT_SKILL_NAME}.active.md`
527
+
528
+ writeFile(path.join('system', bootFileName), buildBootDocument(normalizedAgentName))
529
+ writeFile(path.join('system', workflowFileName), buildWorkflowDocument(normalizedAgentName))
530
+ writeFile(path.join('system', toolsFileName), buildToolsConfig())
531
+ writeFile(path.join('system', modelFileName), buildModelConfig())
532
+ writeFile(path.join('system', 'prompt.ts'), SYSTEM_PROMPT_TS_TEMPLATE)
533
+
534
+ writeFile(path.join('skills', skillFileName), buildDefaultSkillDocument())
535
+ writeFile(path.join('skills', 'enabled-skills.md'), `${activeSkillFileName}\n`)
536
+ writeFile(path.join('skills', 'skills.ts'), SKILLS_TS_TEMPLATE)
537
+
538
+ writeFile('load.ts', LOAD_TS_TEMPLATE)
539
+
540
+ const createActiveLink = (versionedFile: string, activeFile: string) => {
541
+ const source = path.join(agentRoot, versionedFile)
542
+ const target = path.join(agentRoot, activeFile)
543
+ setActiveLink(source, target)
544
+ createdPaths.push(target)
545
+ }
546
+
547
+ createActiveLink(path.join('system', bootFileName), path.join('system', 'boot.active.md'))
548
+ createActiveLink(path.join('system', workflowFileName), path.join('system', 'workflow.active.md'))
549
+ createActiveLink(path.join('system', toolsFileName), path.join('system', 'tools.active.json'))
550
+ createActiveLink(path.join('system', modelFileName), path.join('system', 'model.active.json'))
551
+ createActiveLink(path.join('skills', skillFileName), path.join('skills', activeSkillFileName))
552
+
553
+ return {
554
+ agentName: normalizedAgentName,
555
+ agentRoot,
556
+ createdPaths: createdPaths.sort((a, b) => a.localeCompare(b)),
557
+ }
558
+ }
559
+
288
560
  export function setActive(
289
561
  agentName: string,
290
562
  target: string,
@@ -362,6 +634,135 @@ export async function loadAgent(agentName: string, processRoot: string = process
362
634
  }
363
635
  }
364
636
 
637
+ type SpawnCodexResult = {
638
+ status: number | null
639
+ signal: NodeJS.Signals | null
640
+ error?: Error
641
+ }
642
+
643
+ type SpawnCodexFn = (args: string[]) => SpawnCodexResult
644
+
645
+ function getCodexCommand(): string {
646
+ const override = process.env.EXAMPLE_CODEX_BIN?.trim()
647
+ if (override) {
648
+ return override
649
+ }
650
+ if (process.platform === 'win32') {
651
+ // Prefer npm/pnpm cmd shim over stale codex.exe binaries.
652
+ return 'codex.cmd'
653
+ }
654
+ return 'codex'
655
+ }
656
+
657
+ const defaultSpawnCodex: SpawnCodexFn = (args) => {
658
+ const spawnOptions = {
659
+ stdio: 'inherit' as const,
660
+ env: process.env,
661
+ }
662
+
663
+ const primaryCommand = getCodexCommand()
664
+ const primary = spawnSync(primaryCommand, args, spawnOptions)
665
+ if (primary.error) {
666
+ const err = primary.error as NodeJS.ErrnoException
667
+ if (
668
+ process.platform === 'win32'
669
+ && !process.env.EXAMPLE_CODEX_BIN
670
+ && primaryCommand === 'codex.cmd'
671
+ && err.code === 'ENOENT'
672
+ ) {
673
+ return spawnSync('codex', args, spawnOptions)
674
+ }
675
+ }
676
+
677
+ return primary
678
+ }
679
+
680
+ function toExitCode(signal: NodeJS.Signals | null): number {
681
+ if (!signal) {
682
+ return 1
683
+ }
684
+ if (signal === 'SIGINT') {
685
+ return 130
686
+ }
687
+ if (signal === 'SIGTERM') {
688
+ return 143
689
+ }
690
+ return 1
691
+ }
692
+
693
+ function isRecord(value: unknown): value is Record<string, unknown> {
694
+ return typeof value === 'object' && value !== null
695
+ }
696
+
697
+ async function resolvePromptValue(value: unknown): Promise<unknown> {
698
+ if (typeof value === 'function') {
699
+ return await value()
700
+ }
701
+ return value
702
+ }
703
+
704
+ function extractPromptString(value: unknown): string | null {
705
+ if (typeof value === 'string') {
706
+ return value
707
+ }
708
+ if (isRecord(value) && typeof value.prompt === 'string') {
709
+ return value.prompt
710
+ }
711
+ return null
712
+ }
713
+
714
+ export async function loadAgentPrompt(agentName: string, processRoot: string = process.cwd()): Promise<string> {
715
+ const agentsRoot = resolveAgentsRoot(processRoot)
716
+ const agentDir = path.join(agentsRoot, agentName)
717
+
718
+ if (!existsDir(agentDir)) {
719
+ throw new Error(`Agent folder not found: ${agentDir}`)
720
+ }
721
+
722
+ const promptFile = path.join(agentDir, 'system', 'prompt.ts')
723
+ if (!existsFile(promptFile)) {
724
+ throw new Error(`Prompt file not found for agent '${agentName}': ${promptFile}`)
725
+ }
726
+
727
+ const promptModuleUrl = pathToFileURL(promptFile).href
728
+ const promptModule = await import(promptModuleUrl)
729
+ const candidates = [
730
+ await resolvePromptValue(promptModule.prompt),
731
+ await resolvePromptValue(promptModule.agentConfig),
732
+ await resolvePromptValue(promptModule.default),
733
+ ]
734
+
735
+ for (const candidate of candidates) {
736
+ const promptText = extractPromptString(candidate)
737
+ if (promptText !== null) {
738
+ return promptText
739
+ }
740
+ }
741
+
742
+ throw new Error(`Prompt module for agent '${agentName}' must export a prompt string.`)
743
+ }
744
+
745
+ export async function runAgent(
746
+ agentName: string,
747
+ codexArgs: string[] = [],
748
+ processRoot: string = process.cwd(),
749
+ options: { spawnCodex?: SpawnCodexFn } = {}
750
+ ): Promise<AgentRunResult> {
751
+ const prompt = await loadAgentPrompt(agentName, processRoot)
752
+ const spawnCodex = options.spawnCodex ?? defaultSpawnCodex
753
+ const args = ['--config', `developer_instructions=${prompt}`, ...codexArgs]
754
+ const result = spawnCodex(args)
755
+
756
+ if (result.error) {
757
+ throw new Error(`Failed to start codex: ${result.error.message}`)
758
+ }
759
+
760
+ return {
761
+ agentName,
762
+ exitCode: typeof result.status === 'number' ? result.status : toExitCode(result.signal),
763
+ }
764
+ }
765
+
365
766
  function validateActiveConfigInputs(agentDir: string): void {
366
767
  const issues: string[] = []
367
768
 
@@ -432,6 +833,7 @@ export async function main(argv: string[], processRoot: string = process.cwd()):
432
833
 
433
834
  const [scope, maybeAgentOrFlag, maybeTarget, ...rest] = argv
434
835
  const listMode = scope === 'agents' && maybeAgentOrFlag === '--list'
836
+ const createMode = scope === 'agents' && maybeAgentOrFlag === 'create'
435
837
 
436
838
  if (scope !== 'agents') {
437
839
  throw new Error('Expected command `agents` as first positional argument.')
@@ -445,9 +847,24 @@ export async function main(argv: string[], processRoot: string = process.cwd()):
445
847
  return listAgents(processRoot).join('\n')
446
848
  }
447
849
 
850
+ if (createMode) {
851
+ if (!maybeTarget) {
852
+ throw new Error('Missing required argument: <agent-name>.')
853
+ }
854
+
855
+ const unknownFlags = rest.filter(Boolean)
856
+ if (unknownFlags.length > 0) {
857
+ throw new Error(`Unknown flags for create mode: ${unknownFlags.join(', ')}`)
858
+ }
859
+
860
+ const result = createAgent(maybeTarget, processRoot)
861
+ const displayPath = path.relative(processRoot, result.agentRoot) || result.agentRoot
862
+ return `[${CLI_NAME}] created agent: ${result.agentName} (${displayPath})`
863
+ }
864
+
448
865
  const agentName = maybeAgentOrFlag
449
866
  const target = maybeTarget
450
- const setActive = rest.includes('--set-active')
867
+ const setActiveRequested = rest.includes('--set-active')
451
868
  const useLatest = rest.includes('--latest')
452
869
 
453
870
  if (target === 'load') {
@@ -458,11 +875,19 @@ export async function main(argv: string[], processRoot: string = process.cwd()):
458
875
  return JSON.stringify(result.loaded, null, 2)
459
876
  }
460
877
 
878
+ if (target === 'run') {
879
+ const result = await runAgent(agentName, rest, processRoot)
880
+ if (result.exitCode !== 0) {
881
+ process.exitCode = result.exitCode
882
+ }
883
+ return ''
884
+ }
885
+
461
886
  if (!agentName || !target) {
462
887
  throw new Error('Missing required arguments: <agent-name> and <file-path>.')
463
888
  }
464
889
 
465
- if (!setActive) {
890
+ if (!setActiveRequested) {
466
891
  throw new Error('`--set-active` is required for this operation.')
467
892
  }
468
893
 
package/git.ts CHANGED
@@ -1,31 +1,20 @@
1
- export type GitServiceApiExecutionResult<T = unknown> = {
2
- mapping: Record<string, unknown>
3
- request: {
4
- url: string
5
- method: string
6
- headers: Record<string, string>
7
- query: string[]
8
- body?: unknown
9
- }
10
- response: {
11
- headers: Record<string, string>
12
- }
13
- status: number
14
- ok: boolean
15
- body: T
16
- }
17
-
18
- export type GitServiceApiMethod = (...args: unknown[]) => Promise<GitServiceApiExecutionResult>
19
-
20
- export type GitServiceApi = {
21
- [key: string]: GitServiceApi | GitServiceApiMethod
22
- }
1
+ export type {
2
+ GitLabelManagementApi,
3
+ GitLabelManagementDefaults,
4
+ GitRepositoryLabel,
5
+ GitServiceApi,
6
+ GitServiceApiExecutionResult,
7
+ GitServiceApiMethod,
8
+ } from '../git/packages/git/src/index.ts'
23
9
 
24
10
  export {
11
+ attachGitLabelManagementApi,
25
12
  buildGitApiMockResponse,
26
13
  callIssueDependenciesApi,
14
+ createGitLabelManagementApi,
27
15
  createGitServiceApi,
16
+ extractRepositoryLabels,
28
17
  extractDependencyIssueNumbers,
29
18
  resolveProjectRepoIdentity,
30
19
  syncIssueDependencies,
31
- } from './dist/git.js'
20
+ } from '../git/packages/git/src/index.ts'
package/mcp/cli.mjs CHANGED
@@ -32,6 +32,6 @@ try {
32
32
  process.exit(1)
33
33
  }
34
34
 
35
- console.error('Failed to launch F0 MCP server', error)
35
+ console.error('Failed to launch example-org MCP server', error)
36
36
  process.exit(1)
37
37
  }