@promptbook/cli 0.114.0-35 → 0.114.0-39

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.
Files changed (86) hide show
  1. package/agents/default/.core/adam.book +33 -0
  2. package/apps/agents-server/package.json +1 -1
  3. package/apps/agents-server/scripts/build-agents-server.js +4 -0
  4. package/apps/agents-server/scripts/build-e2e.js +1 -1
  5. package/apps/agents-server/src/app/swagger/SwaggerPageClient.tsx +35 -19
  6. package/apps/agents-server/src/types/swagger-ui-dist.d.ts +5 -0
  7. package/apps/agents-server/src/utils/agentReferenceResolver/AgentReferenceResolutionIssue.ts +1 -69
  8. package/apps/agents-server/src/utils/agentReferenceResolver/extractAgentReferenceTokens.ts +1 -61
  9. package/apps/agents-server/src/utils/explicitFromCommitment.ts +1 -130
  10. package/apps/agents-server/src/utils/resolveInheritedAgentSource.ts +32 -817
  11. package/esm/index.es.js +1431 -128
  12. package/esm/index.es.js.map +1 -1
  13. package/esm/scripts/run-agent-messages/messages/createAgentRunnerSystemMessage.d.ts +2 -1
  14. package/esm/scripts/run-codex-prompts/common/resolveCoderAgent.d.ts +11 -0
  15. package/esm/scripts/run-codex-prompts/git/commitInitializedAgentBooks.d.ts +5 -0
  16. package/esm/scripts/run-codex-prompts/main/resolvePromptRunner.d.ts +5 -8
  17. package/esm/scripts/run-codex-prompts/main/runPromptRound.d.ts +2 -4
  18. package/esm/scripts/run-codex-prompts/prompts/buildPromptRunTraceContent.d.ts +5 -17
  19. package/esm/scripts/run-codex-prompts/prompts/buildPromptStatusDetails.d.ts +3 -15
  20. package/esm/scripts/run-codex-prompts/prompts/formatRunnerSignature.d.ts +14 -1
  21. package/esm/scripts/run-codex-prompts/prompts/markPromptFailed.d.ts +2 -9
  22. package/esm/src/book-2.0/agent-source/AgentReferenceResolutionIssue.d.ts +45 -0
  23. package/esm/src/book-2.0/agent-source/createAgentModelRequirements.deduplication.test.d.ts +1 -0
  24. package/esm/src/book-2.0/agent-source/deduplicateSystemMessage.d.ts +14 -0
  25. package/esm/src/book-2.0/agent-source/deduplicateSystemMessage.test.d.ts +1 -0
  26. package/esm/src/book-2.0/agent-source/explicitFromCommitment.d.ts +62 -0
  27. package/esm/src/book-2.0/agent-source/extractAgentReferenceTokens.d.ts +32 -0
  28. package/esm/src/book-2.0/agent-source/resolveInheritedAgentSource.d.ts +73 -0
  29. package/esm/src/cli/$initializePromptbookCliProgram.d.ts +8 -0
  30. package/esm/src/cli/cli-commands/coder/initializeCoderProjectConfiguration.d.ts +1 -0
  31. package/esm/src/cli/cli-commands/common/LocalAgentBookCollection.d.ts +46 -0
  32. package/esm/src/cli/cli-commands/common/createLocalAgentReferenceResolver.d.ts +8 -0
  33. package/esm/src/cli/cli-commands/common/ensureAdamAgentBook.d.ts +12 -0
  34. package/esm/src/cli/cli-commands/common/resolveBundledAgentBookPath.d.ts +6 -0
  35. package/esm/src/cli/cli-commands/common/resolveLocalAgentSource.d.ts +19 -0
  36. package/esm/src/version.d.ts +1 -1
  37. package/package.json +2 -3
  38. package/src/book-2.0/agent-source/AgentReferenceResolutionIssue.ts +75 -0
  39. package/src/book-2.0/agent-source/createAgentModelRequirementsWithCommitments.ts +3 -2
  40. package/src/book-2.0/agent-source/deduplicateSystemMessage.ts +327 -0
  41. package/src/book-2.0/agent-source/explicitFromCommitment.ts +132 -0
  42. package/src/book-2.0/agent-source/extractAgentReferenceTokens.ts +65 -0
  43. package/src/book-2.0/agent-source/resolveInheritedAgentSource.ts +841 -0
  44. package/src/cli/$initializePromptbookCliProgram.ts +87 -0
  45. package/src/cli/cli-commands/coder/ensureCoderDeveloperAgentFile.ts +2 -28
  46. package/src/cli/cli-commands/coder/init.ts +3 -1
  47. package/src/cli/cli-commands/coder/initializeCoderProjectConfiguration.ts +5 -0
  48. package/src/cli/cli-commands/coder/list.ts +5 -3
  49. package/src/cli/cli-commands/coder/printInitializationSummary.ts +6 -0
  50. package/src/cli/cli-commands/coder/run.ts +4 -1
  51. package/src/cli/cli-commands/coder/server.ts +2 -1
  52. package/src/cli/cli-commands/common/LocalAgentBookCollection.ts +220 -0
  53. package/src/cli/cli-commands/common/createLocalAgentReferenceResolver.ts +62 -0
  54. package/src/cli/cli-commands/common/ensureAdamAgentBook.ts +40 -0
  55. package/src/cli/cli-commands/common/resolveBundledAgentBookPath.ts +38 -0
  56. package/src/cli/cli-commands/common/resolveLocalAgentSource.ts +127 -0
  57. package/src/cli/promptbookCli.ts +2 -87
  58. package/src/other/templates/getTemplatesPipelineCollection.ts +612 -944
  59. package/src/version.ts +2 -2
  60. package/src/versions.txt +4 -0
  61. package/umd/index.umd.js +1431 -128
  62. package/umd/index.umd.js.map +1 -1
  63. package/umd/scripts/run-agent-messages/messages/createAgentRunnerSystemMessage.d.ts +2 -1
  64. package/umd/scripts/run-codex-prompts/common/resolveCoderAgent.d.ts +11 -0
  65. package/umd/scripts/run-codex-prompts/git/commitInitializedAgentBooks.d.ts +5 -0
  66. package/umd/scripts/run-codex-prompts/main/resolvePromptRunner.d.ts +5 -8
  67. package/umd/scripts/run-codex-prompts/main/runPromptRound.d.ts +2 -4
  68. package/umd/scripts/run-codex-prompts/prompts/buildPromptRunTraceContent.d.ts +5 -17
  69. package/umd/scripts/run-codex-prompts/prompts/buildPromptStatusDetails.d.ts +3 -15
  70. package/umd/scripts/run-codex-prompts/prompts/formatRunnerSignature.d.ts +14 -1
  71. package/umd/scripts/run-codex-prompts/prompts/markPromptFailed.d.ts +2 -9
  72. package/umd/src/book-2.0/agent-source/AgentReferenceResolutionIssue.d.ts +45 -0
  73. package/umd/src/book-2.0/agent-source/createAgentModelRequirements.deduplication.test.d.ts +1 -0
  74. package/umd/src/book-2.0/agent-source/deduplicateSystemMessage.d.ts +14 -0
  75. package/umd/src/book-2.0/agent-source/deduplicateSystemMessage.test.d.ts +1 -0
  76. package/umd/src/book-2.0/agent-source/explicitFromCommitment.d.ts +62 -0
  77. package/umd/src/book-2.0/agent-source/extractAgentReferenceTokens.d.ts +32 -0
  78. package/umd/src/book-2.0/agent-source/resolveInheritedAgentSource.d.ts +73 -0
  79. package/umd/src/cli/$initializePromptbookCliProgram.d.ts +8 -0
  80. package/umd/src/cli/cli-commands/coder/initializeCoderProjectConfiguration.d.ts +1 -0
  81. package/umd/src/cli/cli-commands/common/LocalAgentBookCollection.d.ts +46 -0
  82. package/umd/src/cli/cli-commands/common/createLocalAgentReferenceResolver.d.ts +8 -0
  83. package/umd/src/cli/cli-commands/common/ensureAdamAgentBook.d.ts +12 -0
  84. package/umd/src/cli/cli-commands/common/resolveBundledAgentBookPath.d.ts +6 -0
  85. package/umd/src/cli/cli-commands/common/resolveLocalAgentSource.d.ts +19 -0
  86. package/umd/src/version.d.ts +1 -1
@@ -0,0 +1,87 @@
1
+ import type { Command } from 'commander';
2
+ import { CLAIM } from '../config';
3
+ import { PROMPTBOOK_ENGINE_VERSION } from '../version';
4
+ import { $initializeAboutCommand } from './cli-commands/about';
5
+ import { $initializeAgentCommand } from './cli-commands/agent';
6
+ import { $initializeAgentFolderCommand } from './cli-commands/agent-folder';
7
+ import { $initializeAgentsServerCommand } from './cli-commands/agents-server';
8
+ import { $initializeCoderCommand } from './cli-commands/coder';
9
+ import { $initializeHelloCommand } from './cli-commands/hello';
10
+ import { $initializeListModelsCommand } from './cli-commands/list-models';
11
+ import { $initializeListScrapersCommand } from './cli-commands/list-scrapers';
12
+ import { $initializeLoginCommand } from './cli-commands/login';
13
+ import { $initializeMakeCommand } from './cli-commands/make';
14
+ import { $initializePrettifyCommand } from './cli-commands/prettify';
15
+ import { $initializeRunCommand } from './cli-commands/run';
16
+ import { $initializeStartAgentsServerCommand } from './cli-commands/start-agents-server';
17
+ import { $initializeStartPipelinesServerCommand } from './cli-commands/start-pipelines-server';
18
+ import { $initializeTestCommand } from './cli-commands/test-command';
19
+ import { $addGlobalOptionsToCommand } from './common/$addGlobalOptionsToCommand';
20
+ import { $deprecateCliCommand } from './common/$deprecateCliCommand';
21
+
22
+ /**
23
+ * Shared deprecation text for top-level CLI commands backed by the old pipeline system.
24
+ */
25
+ const OLD_PIPELINE_SYSTEM_DEPRECATION_MESSAGE = 'This command is part of the old pipeline system.';
26
+
27
+ /**
28
+ * Shared deprecation text for top-level CLI commands backed by the old pre-agent system.
29
+ */
30
+ const OLD_SYSTEM_DEPRECATION_MESSAGE = 'This command is part of the old system.';
31
+
32
+ /**
33
+ * Deprecation guidance for top-level `ptbk` commands that remain for compatibility.
34
+ */
35
+ const DEPRECATED_TOP_LEVEL_COMMAND_MESSAGES: Readonly<Record<string, string>> = {
36
+ run: OLD_PIPELINE_SYSTEM_DEPRECATION_MESSAGE,
37
+ login: OLD_PIPELINE_SYSTEM_DEPRECATION_MESSAGE,
38
+ make: OLD_PIPELINE_SYSTEM_DEPRECATION_MESSAGE,
39
+ prettify: OLD_PIPELINE_SYSTEM_DEPRECATION_MESSAGE,
40
+ test: OLD_PIPELINE_SYSTEM_DEPRECATION_MESSAGE,
41
+ 'list-models': OLD_SYSTEM_DEPRECATION_MESSAGE,
42
+ 'list-scrapers': OLD_SYSTEM_DEPRECATION_MESSAGE,
43
+ 'start-agents-server': 'Use `ptbk agents-server start` instead.',
44
+ 'start-pipelines-server': OLD_PIPELINE_SYSTEM_DEPRECATION_MESSAGE,
45
+ };
46
+
47
+ /**
48
+ * Registers the CLI commands on a fresh Commander program without parsing arguments or running an action.
49
+ * Tests can configure output and exit handling before registration, so subcommands inherit those settings.
50
+ *
51
+ * @private internal utility of `promptbookCli`
52
+ */
53
+ export function $initializePromptbookCliProgram(program: Command): void {
54
+ program.name('promptbook');
55
+ program.alias('ptbk');
56
+ program.version(PROMPTBOOK_ENGINE_VERSION);
57
+ program.description(CLAIM);
58
+
59
+ // Commands are listed in registration order. Keep `coder` first and hide deprecated commands below.
60
+ $initializeCoderCommand(program);
61
+ $initializeAgentCommand(program);
62
+ $initializeAgentFolderCommand(program);
63
+ $initializeAgentsServerCommand(program);
64
+ $initializeAboutCommand(program);
65
+ $initializeHelloCommand(program);
66
+ $initializeRunCommand(program);
67
+ $initializeLoginCommand(program);
68
+ $initializeMakeCommand(program);
69
+ $initializePrettifyCommand(program);
70
+ $initializeTestCommand(program);
71
+ $initializeListModelsCommand(program);
72
+ $initializeListScrapersCommand(program);
73
+ $initializeStartAgentsServerCommand(program);
74
+ $initializeStartPipelinesServerCommand(program);
75
+
76
+ for (const command of program.commands) {
77
+ const deprecationMessage = DEPRECATED_TOP_LEVEL_COMMAND_MESSAGES[command.name()];
78
+
79
+ if (deprecationMessage !== undefined) {
80
+ $deprecateCliCommand(command, deprecationMessage);
81
+ }
82
+ }
83
+
84
+ program.commands.forEach($addGlobalOptionsToCommand);
85
+ }
86
+
87
+ // Note: [🟡] Code for CLI command registration [$initializePromptbookCliProgram](src/cli/$initializePromptbookCliProgram.ts) should never be published outside of `@promptbook/cli`
@@ -1,7 +1,6 @@
1
1
  import { copyFile, stat } from 'fs/promises';
2
2
  import { join } from 'path';
3
- import { spaceTrim } from 'spacetrim';
4
- import { NotAllowed } from '../../../errors/NotAllowed';
3
+ import { resolveBundledAgentBookPath } from '../common/resolveBundledAgentBookPath';
5
4
  import type { InitializationStatus } from './boilerplateTemplates';
6
5
 
7
6
  /**
@@ -36,35 +35,10 @@ export async function ensureCoderDeveloperAgentFile(projectPath: string): Promis
36
35
  return 'unchanged';
37
36
  }
38
37
 
39
- await copyFile(await resolveDefaultCoderDeveloperAgentFilePath(), absoluteFilePath);
38
+ await copyFile(await resolveBundledAgentBookPath(DEFAULT_CODER_DEVELOPER_AGENT_SOURCE_FILE_PATH), absoluteFilePath);
40
39
  return 'created';
41
40
  }
42
41
 
43
- /**
44
- * Resolves the bundled developer agent from a source checkout or generated CLI package.
45
- */
46
- async function resolveDefaultCoderDeveloperAgentFilePath(): Promise<string> {
47
- const candidates = [
48
- join(__dirname, '..', DEFAULT_CODER_DEVELOPER_AGENT_SOURCE_FILE_PATH),
49
- join(__dirname, '..', '..', '..', '..', DEFAULT_CODER_DEVELOPER_AGENT_SOURCE_FILE_PATH),
50
- ];
51
-
52
- for (const candidate of candidates) {
53
- if (await isExistingFile(candidate)) {
54
- return candidate;
55
- }
56
- }
57
-
58
- throw new NotAllowed(
59
- spaceTrim(`
60
- Cannot find the bundled Promptbook developer agent.
61
-
62
- Checked:
63
- ${candidates.map((candidate) => `- \`${candidate}\``).join('\n')}
64
- `),
65
- );
66
- }
67
-
68
42
  /**
69
43
  * Checks whether a path exists and is a file.
70
44
  */
@@ -3,6 +3,7 @@ import type {
3
3
  } from 'commander';
4
4
  import { spaceTrim } from 'spacetrim';
5
5
  import type { $side_effect } from '../../../utils/organization/$side_effect';
6
+ import { ADAM_AGENT_BOOK_RELATIVE_PATH } from '../common/ensureAdamAgentBook';
6
7
  import type { CoderGitSyncCliOptions } from '../common/coderGitSyncCliOptions';
7
8
  import {
8
9
  addCoderGitSyncOptions,
@@ -22,7 +23,7 @@ import type { PromptRunnerHarnessName } from '../common/promptRunnerCliOptions';
22
23
  import { AGENTS_FILE_PATH } from './agentsFile';
23
24
  import { DEFAULT_BOILERPLATE_COUNT } from './boilerplateCount';
24
25
  import { getDefaultCoderProjectPromptTemplateDefinitions, PROMPTS_DIRECTORY_PATH } from './boilerplateTemplates';
25
- import { CODER_DEVELOPER_AGENT_FILE_PATH } from './ensureCoderDeveloperAgentFile';
26
+ import { CODER_AGENTS_DIRECTORY_PATH, CODER_DEVELOPER_AGENT_FILE_PATH } from './ensureCoderDeveloperAgentFile';
26
27
  import { isDirectoryEmpty } from './ensureDirectory';
27
28
  import { formatDisplayPath } from './formatDisplayPath';
28
29
  import { generatePromptBoilerplate } from './generate-boilerplates';
@@ -61,6 +62,7 @@ export function $initializeCoderInitCommand(program: Program): $side_effect {
61
62
  - prompts/done/
62
63
  ${block(listDefaultCoderProjectPromptTemplateDisplayPaths())}
63
64
  - ${CODER_DEVELOPER_AGENT_FILE_PATH}
65
+ - ${CODER_AGENTS_DIRECTORY_PATH}/${ADAM_AGENT_BOOK_RELATIVE_PATH}
64
66
  - ${AGENTS_FILE_PATH}
65
67
  - .gitignore with local artifacts from every supported harness
66
68
  - package.json
@@ -1,3 +1,5 @@
1
+ import { join } from 'path';
2
+ import { ensureAdamAgentBook } from '../common/ensureAdamAgentBook';
1
3
  import type { InitializationStatus } from './boilerplateTemplates';
2
4
  import {
3
5
  PROMPTS_DIRECTORY_PATH,
@@ -24,6 +26,7 @@ export type CoderInitializationSummary = {
24
26
  readonly promptsDoneDirectoryStatus: InitializationStatus;
25
27
  readonly promptsTemplatesDirectoryStatus: InitializationStatus;
26
28
  readonly agentsDirectoryStatus: InitializationStatus;
29
+ readonly adamAgentFileStatus: InitializationStatus;
27
30
  readonly envFileStatus: InitializationStatus;
28
31
  readonly gitignoreFileStatus: InitializationStatus;
29
32
  readonly packageJsonFileStatus: InitializationStatus;
@@ -46,6 +49,7 @@ export async function initializeCoderProjectConfiguration(projectPath: string):
46
49
  const promptsDoneDirectoryStatus = await ensureDirectory(projectPath, PROMPTS_DONE_DIRECTORY_PATH);
47
50
  const promptsTemplatesDirectoryStatus = await ensureDirectory(projectPath, PROMPTS_TEMPLATES_DIRECTORY_PATH);
48
51
  const agentsDirectoryStatus = await ensureDirectory(projectPath, CODER_AGENTS_DIRECTORY_PATH);
52
+ const adamAgentFileStatus = await ensureAdamAgentBook(join(projectPath, CODER_AGENTS_DIRECTORY_PATH));
49
53
  const { envFileStatus, initializedEnvVariableNames } = await ensureCoderEnvFile(projectPath);
50
54
  const gitignoreFileStatus = await ensureCoderGitignoreFile(projectPath);
51
55
  const { status: packageJsonFileStatus, addedEntryKeys: addedPackageJsonScriptNames } =
@@ -61,6 +65,7 @@ export async function initializeCoderProjectConfiguration(projectPath: string):
61
65
  promptsDoneDirectoryStatus,
62
66
  promptsTemplatesDirectoryStatus,
63
67
  agentsDirectoryStatus,
68
+ adamAgentFileStatus,
64
69
  envFileStatus,
65
70
  gitignoreFileStatus,
66
71
  packageJsonFileStatus,
@@ -52,7 +52,8 @@ export function $initializeCoderListCommand(program: Program): $side_effect {
52
52
  readonly priority?: number;
53
53
  readonly minPriority?: number;
54
54
  readonly maxPriority?: number;
55
- } & PromptRunnerSelectionCliOptions & CoderAgentCliOptions;
55
+ } & PromptRunnerSelectionCliOptions &
56
+ CoderAgentCliOptions;
56
57
  const runnerOptions = normalizePromptRunnerSelectionCliOptions(
57
58
  cliOptions as PromptRunnerSelectionCliOptions,
58
59
  { isAgentRequired: false },
@@ -60,8 +61,9 @@ export function $initializeCoderListCommand(program: Program): $side_effect {
60
61
  const resolvedCoderAgentBook =
61
62
  agent === undefined
62
63
  ? undefined
63
- : await (await import('../../../../scripts/run-codex-prompts/common/resolveCoderAgent'))
64
- .resolveCoderAgentBook(agent, process.cwd());
64
+ : await (
65
+ await import('../../../../scripts/run-codex-prompts/common/resolveCoderAgent')
66
+ ).resolveCoderAgentBook(agent, process.cwd());
65
67
  const promptRunnerIdentity =
66
68
  runnerOptions.agentName === undefined &&
67
69
  runnerOptions.model === undefined &&
@@ -1,4 +1,6 @@
1
1
  import colors from 'colors';
2
+ import { ADAM_AGENT_BOOK_RELATIVE_PATH } from '../common/ensureAdamAgentBook';
3
+ import { CODER_AGENTS_DIRECTORY_PATH } from './ensureCoderDeveloperAgentFile';
2
4
  import type { InitializationStatus } from './boilerplateTemplates';
3
5
  import type { CoderReferencedArtifactStatus } from './coderReferencedArtifacts';
4
6
  import { formatDisplayPath } from './formatDisplayPath';
@@ -15,6 +17,10 @@ export function printInitializationSummary(summary: CoderInitializationSummary):
15
17
  printInitializationStatusLine('prompts/done/', summary.promptsDoneDirectoryStatus);
16
18
  printInitializationStatusLine('prompts/templates/', summary.promptsTemplatesDirectoryStatus);
17
19
  printInitializationStatusLine('agents/', summary.agentsDirectoryStatus);
20
+ printInitializationStatusLine(
21
+ `${CODER_AGENTS_DIRECTORY_PATH}/${ADAM_AGENT_BOOK_RELATIVE_PATH}`,
22
+ summary.adamAgentFileStatus,
23
+ );
18
24
  printInitializationStatusLine('.env', summary.envFileStatus);
19
25
  printInitializationStatusLine('.gitignore', summary.gitignoreFileStatus);
20
26
  printInitializationStatusLine('package.json', summary.packageJsonFileStatus);
@@ -54,6 +54,8 @@ export function $initializeCoderRunCommand(program: Program): $side_effect {
54
54
  ${block(QUESTIONS_DESCRIPTION)}
55
55
 
56
56
  Features:
57
+ - Resolves FROM, IMPORT and TEAM by agent name, book-relative path, project path or URL
58
+ - Creates a missing .core/adam.book beside the selected agent for default inheritance
57
59
  - Automatically stages and commits changes with agent identity unless --no-commit is used
58
60
  - Commits only the prompt file and the files the coding agent has changed, leaving unrelated changes alone
59
61
  - Refuses to start on a dirty working tree unless --git-changes says what should happen with the changes
@@ -194,7 +196,8 @@ export function $initializeCoderRunCommand(program: Program): $side_effect {
194
196
  readonly auto: boolean;
195
197
  readonly autoMigrate: boolean;
196
198
  readonly allowDestructiveAutoMigrate: boolean;
197
- } & PromptRunnerCliOptions & CoderAgentCliOptions;
199
+ } & PromptRunnerCliOptions &
200
+ CoderAgentCliOptions;
198
201
 
199
202
  const configuredTestCommand = normalizeCommandOptionValue(test);
200
203
  const testCommand = configuredTestCommand ?? (testBefore === 'no' ? undefined : DEFAULT_CODER_TEST_COMMAND);
@@ -166,7 +166,8 @@ export function $initializeCoderServerCommand(program: Program): $side_effect {
166
166
  readonly auto: boolean;
167
167
  readonly autoMigrate: boolean;
168
168
  readonly allowDestructiveAutoMigrate: boolean;
169
- } & PromptRunnerCliOptions & CoderAgentCliOptions;
169
+ } & PromptRunnerCliOptions &
170
+ CoderAgentCliOptions;
170
171
 
171
172
  const port = parseCoderServerPort(rawPort);
172
173
  const testCommand = normalizeCommandOptionValue(test);
@@ -0,0 +1,220 @@
1
+ import { readFile, readdir, realpath } from 'fs/promises';
2
+ import { dirname, isAbsolute, join, resolve } from 'path';
3
+ import { pathToFileURL } from 'url';
4
+ import { spaceTrim } from 'spacetrim';
5
+ import { normalizeAgentName } from '../../../book-2.0/agent-source/normalizeAgentName';
6
+ import { parseAgentSource } from '../../../book-2.0/agent-source/parseAgentSource';
7
+ import type { string_book } from '../../../book-2.0/agent-source/string_book';
8
+ import type { TeammateProfile } from '../../../book-2.0/agent-source/TeammateProfileResolver';
9
+ import { NotFoundError } from '../../../errors/NotFoundError';
10
+ import { ParseError } from '../../../errors/ParseError';
11
+ import { isValidAgentUrl } from '../../../utils/validators/url/isValidAgentUrl';
12
+ import { ADAM_AGENT_BOOK_RELATIVE_PATH, ensureAdamAgentBook } from './ensureAdamAgentBook';
13
+
14
+ /**
15
+ * A book's source and identity, retaining its location for nested relative references.
16
+ *
17
+ * @private internal type of CLI agent resolution
18
+ */
19
+ export type LocalAgentBook = {
20
+ readonly url: string;
21
+ readonly filePath?: string;
22
+ readonly source: string_book;
23
+ readonly profile: TeammateProfile;
24
+ };
25
+
26
+ /**
27
+ * Internal URLs allow the shared Book commitments to identify local books without an HTTP server.
28
+ * These identifiers are always intercepted by this collection and are never fetched.
29
+ */
30
+ const LOCAL_AGENT_URL_PREFIX = 'https://local-agent.promptbook/';
31
+
32
+ /**
33
+ * Directories which cannot contain project-owned agent definitions.
34
+ */
35
+ const IGNORED_DIRECTORY_NAMES = new Set(['.git', 'node_modules']);
36
+
37
+ /**
38
+ * Reads repository books and indexes their first-line names for CLI reference resolution.
39
+ *
40
+ * @private internal utility of CLI agent resolution
41
+ */
42
+ export class LocalAgentBookCollection {
43
+ private readonly booksByUrl = new Map<string, LocalAgentBook>();
44
+ private readonly booksByName = new Map<string, LocalAgentBook[]>();
45
+ /** Stable default ancestor selected from the initial repository scan. */
46
+ private adamAgentUrl: string | undefined;
47
+ /** Files initialized by this collection, for the coder's normal commit handling. */
48
+ public readonly createdAgentBookPaths: string[] = [];
49
+
50
+ /** Creates a collection rooted at the primary book's directory. */
51
+ public constructor(private readonly agentDirectoryPath: string, private readonly currentWorkingDirectory: string) {}
52
+
53
+ /** Discovers nested books, including the hidden `.core` directory. */
54
+ public async initialize(): Promise<void> {
55
+ await this.readDirectory(this.agentDirectoryPath);
56
+ }
57
+
58
+ /** Identifies the default ancestor without creating an unused book for FROM null/void. */
59
+ public getAdamAgentUrl(): string {
60
+ this.adamAgentUrl ??= this.booksByName.has(normalizeAgentName('Adam'))
61
+ ? this.findByName('Adam').url
62
+ : createLocalAgentBookUrl(join(this.agentDirectoryPath, ADAM_AGENT_BOOK_RELATIVE_PATH));
63
+ return this.adamAgentUrl;
64
+ }
65
+
66
+ /** Resolves a unique first-line name, refusing ambiguous matches. */
67
+ public findByName(name: string): LocalAgentBook {
68
+ const matches = this.booksByName.get(normalizeAgentName(name)) || [];
69
+ if (matches.length === 1) {
70
+ return matches[0]!;
71
+ }
72
+
73
+ if (matches.length > 1) {
74
+ throw new ParseError(
75
+ spaceTrim(
76
+ (block) => `
77
+ Agent reference \`${name}\` is ambiguous. Use an explicit book path.
78
+
79
+ Matching books:
80
+ ${block(matches.map((book) => `- \`${book.filePath || book.url}\``).join('\n'))}
81
+ `,
82
+ ),
83
+ );
84
+ }
85
+
86
+ throw new NotFoundError(
87
+ spaceTrim(`
88
+ Agent \`${name}\` was not found in \`${this.agentDirectoryPath}\` or its subdirectories.
89
+
90
+ References use the agent's **first-line name**. You can also use an explicit \`.book\` path or URL.
91
+ `),
92
+ );
93
+ }
94
+
95
+ /** Resolves a name, a path relative to its declaring book, or a path relative to the CLI cwd. */
96
+ public async resolveReference(reference: string, declaringBook: LocalAgentBook): Promise<string> {
97
+ const isRemoteReference = Boolean(isValidAgentUrl(reference));
98
+ if (isRemoteReference) {
99
+ return reference;
100
+ }
101
+
102
+ const isBookPath = /\.book$/i.test(reference) || isAbsolute(reference);
103
+ if (!isBookPath) {
104
+ if (normalizeAgentName(reference) === normalizeAgentName('Adam')) {
105
+ return this.getAdamAgentUrl();
106
+ }
107
+ return this.findByName(reference).url;
108
+ }
109
+
110
+ const isRelativeToBook = /^\.{1,2}[\\/]/.test(reference);
111
+ if (!declaringBook.filePath && isRelativeToBook) {
112
+ return new URL(reference.replaceAll('\\', '/'), declaringBook.url).href;
113
+ }
114
+
115
+ const directoryPath =
116
+ isRelativeToBook && declaringBook.filePath ? dirname(declaringBook.filePath) : this.currentWorkingDirectory;
117
+ return (await this.readBook(resolve(directoryPath, reference))).url;
118
+ }
119
+
120
+ /** Reads a local book once, canonicalizing symlinks so aliases cannot bypass cycle detection. */
121
+ public async readBook(filePath: string): Promise<LocalAgentBook> {
122
+ try {
123
+ const canonicalFilePath = await realpath(filePath);
124
+ const url = createLocalAgentBookUrl(canonicalFilePath);
125
+ const existingBook = this.booksByUrl.get(url);
126
+ if (existingBook) {
127
+ return existingBook;
128
+ }
129
+
130
+ return this.registerBook(url, await readFile(canonicalFilePath, 'utf-8'), canonicalFilePath);
131
+ } catch (error) {
132
+ if (['ENOENT', 'EISDIR', 'ENOTDIR'].includes((error as NodeJS.ErrnoException).code || '')) {
133
+ throw new NotFoundError(spaceTrim(`Agent book \`${filePath}\` was not found or is not a file.`));
134
+ }
135
+ throw error;
136
+ }
137
+ }
138
+
139
+ /** Loads a referenced remote book, while keeping local identifiers entirely inside the collection. */
140
+ public async getBook(url: string): Promise<LocalAgentBook> {
141
+ const existingBook = this.booksByUrl.get(url);
142
+ if (existingBook) {
143
+ return existingBook;
144
+ }
145
+ if (url === this.getAdamAgentUrl()) {
146
+ const adamBookPath = join(this.agentDirectoryPath, ADAM_AGENT_BOOK_RELATIVE_PATH);
147
+ if ((await ensureAdamAgentBook(this.agentDirectoryPath)) === 'created') {
148
+ this.createdAgentBookPaths.push(adamBookPath);
149
+ }
150
+ const adamBook = await this.readBook(adamBookPath);
151
+ this.booksByUrl.set(url, adamBook);
152
+ return adamBook;
153
+ }
154
+ if (url.startsWith(LOCAL_AGENT_URL_PREFIX)) {
155
+ throw new NotFoundError(spaceTrim(`Local agent book \`${url}\` is not registered.`));
156
+ }
157
+
158
+ const bookUrl = new URL(url);
159
+ if (!/\.(book|md)$/i.test(bookUrl.pathname) && !bookUrl.pathname.endsWith('/api/book')) {
160
+ bookUrl.pathname = bookUrl.pathname.replace(/\/$/, '') + '/api/book';
161
+ }
162
+ const response = await fetch(bookUrl.href);
163
+ if (!response.ok) {
164
+ throw new NotFoundError(
165
+ spaceTrim(`Cannot load agent book \`${url}\`: **${response.status} ${response.statusText}**.`),
166
+ );
167
+ }
168
+ let source: unknown;
169
+ if (response.headers.get('content-type')?.includes('application/json')) {
170
+ const payload: unknown = await response.json();
171
+ source = typeof payload === 'string' ? payload : (payload as { source?: unknown } | null)?.source;
172
+ } else {
173
+ source = await response.text();
174
+ }
175
+ if (typeof source !== 'string') {
176
+ throw new ParseError(spaceTrim(`Agent book \`${url}\` did not return a text source.`));
177
+ }
178
+ return this.registerBook(url, source);
179
+ }
180
+
181
+ /** Adds a book under its normalized first-line name and preserves the display name for TEAM. */
182
+ private registerBook(url: string, source: string, filePath?: string): LocalAgentBook {
183
+ const agentSource = source as string_book;
184
+ const parsedSource = parseAgentSource(agentSource);
185
+ const book: LocalAgentBook = {
186
+ url,
187
+ filePath,
188
+ source: agentSource,
189
+ profile: {
190
+ agentName: parsedSource.meta.fullname || parsedSource.agentName,
191
+ personaDescription: parsedSource.personaDescription,
192
+ },
193
+ };
194
+ this.booksByUrl.set(url, book);
195
+ // Remote titles must not shadow repository-local names.
196
+ if (filePath) {
197
+ const agentName = normalizeAgentName(parsedSource.agentName);
198
+ this.booksByName.set(agentName, [...(this.booksByName.get(agentName) || []), book]);
199
+ }
200
+ return book;
201
+ }
202
+
203
+ /** Walks real directories in deterministic order without following directory symlink loops. */
204
+ private async readDirectory(directoryPath: string): Promise<void> {
205
+ const entries = await readdir(directoryPath, { withFileTypes: true });
206
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
207
+ const entryPath = join(directoryPath, entry.name);
208
+ if (entry.isDirectory() && !IGNORED_DIRECTORY_NAMES.has(entry.name)) {
209
+ await this.readDirectory(entryPath);
210
+ } else if (entry.isFile() && entry.name.toLowerCase().endsWith('.book')) {
211
+ await this.readBook(entryPath);
212
+ }
213
+ }
214
+ }
215
+ }
216
+
217
+ /** Creates an internal URL for a local path without requiring that the file already exists. */
218
+ function createLocalAgentBookUrl(filePath: string): string {
219
+ return LOCAL_AGENT_URL_PREFIX + encodeURIComponent(pathToFileURL(filePath).href);
220
+ }
@@ -0,0 +1,62 @@
1
+ import { spaceTrim } from 'spacetrim';
2
+ import type { AgentReferenceResolver } from '../../../book-2.0/agent-source/AgentReferenceResolver';
3
+ import { extractAgentReferenceTokens } from '../../../book-2.0/agent-source/extractAgentReferenceTokens';
4
+ import {
5
+ createPseudoAgentUrl,
6
+ isPseudoAgentAllowedInCommitment,
7
+ resolvePseudoAgentKindFromReference,
8
+ VOID_PSEUDO_AGENT_REFERENCE,
9
+ } from '../../../book-2.0/agent-source/pseudoAgentReferences';
10
+ import type { BookCommitment } from '../../../commitments/_base/BookCommitment';
11
+ import { ParseError } from '../../../errors/ParseError';
12
+ import type { LocalAgentBook, LocalAgentBookCollection } from './LocalAgentBookCollection';
13
+
14
+ /**
15
+ * Creates a resolver scoped to the book which declares each reference.
16
+ *
17
+ * @private internal utility of CLI agent resolution
18
+ */
19
+ export function createLocalAgentReferenceResolver(
20
+ collection: LocalAgentBookCollection,
21
+ declaringBook: LocalAgentBook,
22
+ ): AgentReferenceResolver {
23
+ /** Resolves pseudo-agents using the same aliases and restrictions as the Agent Server. */
24
+ async function resolveReference(commitmentType: BookCommitment, reference: string): Promise<string> {
25
+ const pseudoAgentKind = resolvePseudoAgentKindFromReference(reference);
26
+ if (!pseudoAgentKind) {
27
+ return collection.resolveReference(reference, declaringBook);
28
+ }
29
+ if (!isPseudoAgentAllowedInCommitment(pseudoAgentKind, commitmentType)) {
30
+ throw new ParseError(spaceTrim(`Pseudo-agent \`${reference}\` cannot be used in \`${commitmentType}\`.`));
31
+ }
32
+ if (pseudoAgentKind === 'VOID' && commitmentType === 'FROM') {
33
+ return VOID_PSEUDO_AGENT_REFERENCE;
34
+ }
35
+ if (pseudoAgentKind === 'VOID' && (commitmentType === 'IMPORT' || commitmentType === 'IMPORTS')) {
36
+ return '';
37
+ }
38
+ return createPseudoAgentUrl(pseudoAgentKind);
39
+ }
40
+
41
+ return {
42
+ resolveCommitmentContent: async (commitmentType, content) => {
43
+ const tokens = extractAgentReferenceTokens(content);
44
+ if (tokens.length === 0) {
45
+ if (commitmentType !== 'TEAM' && content.trim()) {
46
+ return resolveReference(commitmentType, content.trim());
47
+ }
48
+ return content;
49
+ }
50
+
51
+ const parts: string[] = [];
52
+ let previousEnd = 0;
53
+ for (const token of tokens) {
54
+ parts.push(content.slice(previousEnd, token.index));
55
+ parts.push(await resolveReference(commitmentType, token.reference));
56
+ previousEnd = token.index + token.length;
57
+ }
58
+ parts.push(content.slice(previousEnd));
59
+ return parts.join('');
60
+ },
61
+ };
62
+ }
@@ -0,0 +1,40 @@
1
+ import { constants } from 'fs';
2
+ import { copyFile, mkdir, stat } from 'fs/promises';
3
+ import { dirname, join } from 'path';
4
+ import { resolveBundledAgentBookPath } from './resolveBundledAgentBookPath';
5
+
6
+ /**
7
+ * Adam's location relative to a directory of local agents.
8
+ *
9
+ * @private internal constant of CLI agent initialization
10
+ */
11
+ export const ADAM_AGENT_BOOK_RELATIVE_PATH = '.core/adam.book';
12
+
13
+ /**
14
+ * Creates the same Adam book used by the Agent Server without overwriting a project-owned book.
15
+ *
16
+ * @private internal utility of CLI agent initialization
17
+ */
18
+ export async function ensureAdamAgentBook(agentDirectoryPath: string): Promise<'created' | 'unchanged'> {
19
+ const adamBookPath = join(agentDirectoryPath, ADAM_AGENT_BOOK_RELATIVE_PATH);
20
+ if (
21
+ await stat(adamBookPath).then(
22
+ (entry) => entry.isFile(),
23
+ () => false,
24
+ )
25
+ ) {
26
+ return 'unchanged';
27
+ }
28
+
29
+ const bundledAdamBookPath = await resolveBundledAgentBookPath('agents/default/.core/adam.book');
30
+ await mkdir(dirname(adamBookPath), { recursive: true });
31
+ try {
32
+ await copyFile(bundledAdamBookPath, adamBookPath, constants.COPYFILE_EXCL);
33
+ return 'created';
34
+ } catch (error) {
35
+ if ((error as NodeJS.ErrnoException).code === 'EEXIST') {
36
+ return 'unchanged';
37
+ }
38
+ throw error;
39
+ }
40
+ }
@@ -0,0 +1,38 @@
1
+ import { stat } from 'fs/promises';
2
+ import { join } from 'path';
3
+ import { spaceTrim } from 'spacetrim';
4
+ import { NotFoundError } from '../../../errors/NotFoundError';
5
+
6
+ /**
7
+ * Locates a bundled agent in either the source checkout or the generated CLI package.
8
+ *
9
+ * @private internal utility of CLI agent initialization
10
+ */
11
+ export async function resolveBundledAgentBookPath(relativeFilePath: string): Promise<string> {
12
+ const candidates = [
13
+ join(__dirname, '..', relativeFilePath),
14
+ join(__dirname, '..', '..', '..', '..', relativeFilePath),
15
+ ];
16
+
17
+ for (const candidate of candidates) {
18
+ if (
19
+ await stat(candidate).then(
20
+ (entry) => entry.isFile(),
21
+ () => false,
22
+ )
23
+ ) {
24
+ return candidate;
25
+ }
26
+ }
27
+
28
+ throw new NotFoundError(
29
+ spaceTrim(
30
+ (block) => `
31
+ Cannot find the bundled agent book \`${relativeFilePath}\`.
32
+
33
+ Checked:
34
+ ${block(candidates.map((candidate) => `- \`${candidate}\``).join('\n'))}
35
+ `,
36
+ ),
37
+ );
38
+ }