@ikarmic/forge-core 5.0.0 → 5.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.
Files changed (163) hide show
  1. package/README.md +1 -1
  2. package/dist/agent/agent.d.ts +8 -0
  3. package/dist/agent/agent.js +148 -24
  4. package/dist/agent/background.d.ts +58 -0
  5. package/dist/agent/background.js +139 -0
  6. package/dist/agent/enhance-prompt.d.ts +3 -1
  7. package/dist/agent/enhance-prompt.js +6 -2
  8. package/dist/agent/goal.d.ts +54 -0
  9. package/dist/agent/goal.js +65 -0
  10. package/dist/agent/prompts.d.ts +25 -1
  11. package/dist/agent/prompts.js +61 -17
  12. package/dist/agents/agent-files.d.ts +43 -0
  13. package/dist/agents/agent-files.js +109 -0
  14. package/dist/checkpoints/store.js +5 -1
  15. package/dist/commands/custom-commands.d.ts +9 -4
  16. package/dist/commands/custom-commands.js +13 -2
  17. package/dist/config-file.d.ts +43 -0
  18. package/dist/config-file.js +249 -5
  19. package/dist/config-merge.d.ts +13 -0
  20. package/dist/config-merge.js +67 -0
  21. package/dist/config-schema.d.ts +340 -0
  22. package/dist/config-schema.js +126 -0
  23. package/dist/config.js +2 -1
  24. package/dist/context/compaction.js +4 -0
  25. package/dist/context/mask.js +1 -1
  26. package/dist/custom-providers.d.ts +95 -0
  27. package/dist/custom-providers.js +120 -0
  28. package/dist/git/auto-commit.d.ts +2 -2
  29. package/dist/git/auto-commit.js +5 -4
  30. package/dist/github/github-tools.d.ts +2 -2
  31. package/dist/hooks/runner.js +3 -2
  32. package/dist/import/claude-code.d.ts +39 -0
  33. package/dist/import/claude-code.js +254 -0
  34. package/dist/index.d.ts +34 -5
  35. package/dist/index.js +27 -4
  36. package/dist/journey/answers-block.d.ts +17 -0
  37. package/dist/journey/answers-block.js +15 -0
  38. package/dist/journey/index.d.ts +9 -0
  39. package/dist/journey/index.js +8 -0
  40. package/dist/journey/journey.d.ts +13 -0
  41. package/dist/journey/journey.js +40 -0
  42. package/dist/journey/offer.d.ts +25 -0
  43. package/dist/journey/offer.js +48 -0
  44. package/dist/journey/questions.d.ts +23 -0
  45. package/dist/journey/questions.js +49 -0
  46. package/dist/journey/stages.d.ts +13 -0
  47. package/dist/journey/stages.js +107 -0
  48. package/dist/journey/survey.d.ts +10 -0
  49. package/dist/journey/survey.js +203 -0
  50. package/dist/journey/trigger.d.ts +19 -0
  51. package/dist/journey/trigger.js +79 -0
  52. package/dist/journey/types.d.ts +72 -0
  53. package/dist/journey/types.js +1 -0
  54. package/dist/lsp/client.d.ts +5 -0
  55. package/dist/lsp/client.js +39 -3
  56. package/dist/mcp/index.d.ts +5 -1
  57. package/dist/mcp/index.js +3 -1
  58. package/dist/mcp/manager.d.ts +17 -3
  59. package/dist/mcp/manager.js +27 -2
  60. package/dist/mcp/marketplace.d.ts +2 -0
  61. package/dist/mcp/marketplace.js +10 -4
  62. package/dist/mcp/mcp-client.d.ts +12 -2
  63. package/dist/mcp/mcp-client.js +74 -17
  64. package/dist/mcp/mcp-config.d.ts +2 -0
  65. package/dist/mcp/mcp-config.js +1 -0
  66. package/dist/mcp/oauth.d.ts +55 -0
  67. package/dist/mcp/oauth.js +197 -0
  68. package/dist/mcp/resource-tools.d.ts +8 -0
  69. package/dist/mcp/resource-tools.js +56 -0
  70. package/dist/memory/session-digest.d.ts +12 -0
  71. package/dist/memory/session-digest.js +47 -0
  72. package/dist/memory/stale.js +2 -1
  73. package/dist/model-alias.d.ts +18 -2
  74. package/dist/model-alias.js +27 -3
  75. package/dist/model-catalog.js +0 -1
  76. package/dist/model-list.js +2 -1
  77. package/dist/model-overrides.d.ts +30 -0
  78. package/dist/model-overrides.js +48 -0
  79. package/dist/orchestrator/specialists-registry.js +2 -3
  80. package/dist/orchestrator/specialists.js +4 -4
  81. package/dist/policy/ca-certs.d.ts +8 -0
  82. package/dist/policy/ca-certs.js +30 -0
  83. package/dist/policy/proxy.d.ts +10 -3
  84. package/dist/policy/proxy.js +33 -6
  85. package/dist/pricing.js +4 -0
  86. package/dist/providers.d.ts +7 -0
  87. package/dist/providers.js +39 -5
  88. package/dist/semantic/embedder.d.ts +15 -0
  89. package/dist/semantic/embedder.js +25 -0
  90. package/dist/semantic/semantic-index.d.ts +10 -2
  91. package/dist/semantic/semantic-index.js +16 -7
  92. package/dist/sessions/session-store.js +2 -1
  93. package/dist/skills/builtin.d.ts +2 -0
  94. package/dist/skills/builtin.js +68 -0
  95. package/dist/skills/index.d.ts +2 -0
  96. package/dist/skills/index.js +2 -0
  97. package/dist/skills/loader.d.ts +10 -3
  98. package/dist/skills/loader.js +20 -7
  99. package/dist/skills/marketplace.js +2 -1
  100. package/dist/skills/remote.d.ts +14 -0
  101. package/dist/skills/remote.js +131 -0
  102. package/dist/skills/types.d.ts +10 -0
  103. package/dist/specs/generate.d.ts +18 -9
  104. package/dist/specs/generate.js +174 -14
  105. package/dist/specs/index.d.ts +2 -2
  106. package/dist/specs/index.js +1 -1
  107. package/dist/specs/store.d.ts +14 -2
  108. package/dist/specs/store.js +26 -1
  109. package/dist/specs/types.d.ts +11 -0
  110. package/dist/steering/extra-instructions.d.ts +8 -0
  111. package/dist/steering/extra-instructions.js +104 -0
  112. package/dist/steering/folder-instructions.d.ts +8 -0
  113. package/dist/steering/folder-instructions.js +28 -0
  114. package/dist/steering/select.js +2 -1
  115. package/dist/timed-fetch.d.ts +12 -0
  116. package/dist/timed-fetch.js +83 -0
  117. package/dist/tools/apply-patch.d.ts +52 -0
  118. package/dist/tools/apply-patch.js +197 -0
  119. package/dist/tools/ask-user.d.ts +27 -0
  120. package/dist/tools/ask-user.js +60 -0
  121. package/dist/tools/browser-tools.d.ts +6 -6
  122. package/dist/tools/browser-tools.js +3 -13
  123. package/dist/tools/codebase-search.d.ts +4 -2
  124. package/dist/tools/codebase-search.js +2 -2
  125. package/dist/tools/edit-file.js +5 -3
  126. package/dist/tools/fetch-url.d.ts +7 -0
  127. package/dist/tools/fetch-url.js +52 -19
  128. package/dist/tools/find-symbol.d.ts +2 -2
  129. package/dist/tools/ignore-rules.js +2 -1
  130. package/dist/tools/index.d.ts +5 -1
  131. package/dist/tools/index.js +10 -1
  132. package/dist/tools/memory.d.ts +6 -6
  133. package/dist/tools/notebook.d.ts +2 -2
  134. package/dist/tools/permission-rules.d.ts +32 -0
  135. package/dist/tools/permission-rules.js +182 -0
  136. package/dist/tools/read-file.d.ts +5 -0
  137. package/dist/tools/read-file.js +12 -2
  138. package/dist/tools/read-only.d.ts +1 -1
  139. package/dist/tools/read-only.js +2 -0
  140. package/dist/tools/run-command.d.ts +4 -2
  141. package/dist/tools/run-command.js +12 -8
  142. package/dist/tools/sandbox-loader.js +3 -13
  143. package/dist/tools/search-files.d.ts +8 -2
  144. package/dist/tools/search-files.js +43 -0
  145. package/dist/tools/shared.d.ts +5 -0
  146. package/dist/tools/shared.js +30 -2
  147. package/dist/tools/shell-env.d.ts +7 -0
  148. package/dist/tools/shell-env.js +9 -0
  149. package/dist/tools/sub-agent-wait.d.ts +29 -0
  150. package/dist/tools/sub-agent-wait.js +60 -0
  151. package/dist/tools/sub-agent.d.ts +50 -4
  152. package/dist/tools/sub-agent.js +213 -125
  153. package/dist/tools/text-encoding.d.ts +10 -0
  154. package/dist/tools/text-encoding.js +40 -0
  155. package/dist/tools/todo.d.ts +36 -0
  156. package/dist/tools/todo.js +42 -0
  157. package/dist/tools/write-file.js +15 -9
  158. package/dist/tools/write-tools.d.ts +12 -0
  159. package/dist/tools/write-tools.js +18 -0
  160. package/dist/types.d.ts +98 -0
  161. package/dist/worktree/worktree.d.ts +3 -0
  162. package/dist/worktree/worktree.js +22 -0
  163. package/package.json +1 -1
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Named providers beyond the built-in eleven: a table of well-known OpenAI-compatible services plus whatever the
3
+ * user defines under `customProviders` in `~/.forge/config.json`. A model on one of them is written `id:model`
4
+ * (e.g. `groq:llama-3.3-70b-versatile`) and resolves to an endpoint (base URL, key, headers) that
5
+ * `createLanguageModel` calls with the OpenAI chat or Anthropic message style. Pure; nothing here reads files.
6
+ */
7
+ /** Defaults for services that speak the OpenAI chat protocol. Any field can be overridden under `customProviders`. */
8
+ export const PROVIDER_PRESETS = {
9
+ groq: { name: 'Groq', baseUrl: 'https://api.groq.com/openai/v1', apiKeyEnv: 'GROQ_API_KEY' },
10
+ deepseek: { name: 'DeepSeek', baseUrl: 'https://api.deepseek.com/v1', apiKeyEnv: 'DEEPSEEK_API_KEY' },
11
+ mistral: { name: 'Mistral', baseUrl: 'https://api.mistral.ai/v1', apiKeyEnv: 'MISTRAL_API_KEY' },
12
+ xai: { name: 'xAI', baseUrl: 'https://api.x.ai/v1', apiKeyEnv: 'XAI_API_KEY' },
13
+ cerebras: { name: 'Cerebras', baseUrl: 'https://api.cerebras.ai/v1', apiKeyEnv: 'CEREBRAS_API_KEY' },
14
+ fireworks: { name: 'Fireworks', baseUrl: 'https://api.fireworks.ai/inference/v1', apiKeyEnv: 'FIREWORKS_API_KEY' },
15
+ together: { name: 'Together AI', baseUrl: 'https://api.together.xyz/v1', apiKeyEnv: 'TOGETHER_API_KEY' },
16
+ moonshot: { name: 'Moonshot', baseUrl: 'https://api.moonshot.ai/v1', apiKeyEnv: 'MOONSHOT_API_KEY' },
17
+ nebius: { name: 'Nebius', baseUrl: 'https://api.studio.nebius.com/v1', apiKeyEnv: 'NEBIUS_API_KEY' },
18
+ vercel: { name: 'Vercel AI Gateway', baseUrl: 'https://ai-gateway.vercel.sh/v1', apiKeyEnv: 'AI_GATEWAY_API_KEY' },
19
+ perplexity: { name: 'Perplexity', baseUrl: 'https://api.perplexity.ai', apiKeyEnv: 'PERPLEXITY_API_KEY' },
20
+ deepinfra: { name: 'DeepInfra', baseUrl: 'https://api.deepinfra.com/v1/openai', apiKeyEnv: 'DEEPINFRA_API_KEY' },
21
+ sambanova: { name: 'SambaNova', baseUrl: 'https://api.sambanova.ai/v1', apiKeyEnv: 'SAMBANOVA_API_KEY' },
22
+ hyperbolic: { name: 'Hyperbolic', baseUrl: 'https://api.hyperbolic.xyz/v1', apiKeyEnv: 'HYPERBOLIC_API_KEY' },
23
+ huggingface: { name: 'Hugging Face', baseUrl: 'https://router.huggingface.co/v1', apiKeyEnv: 'HF_TOKEN' },
24
+ alibaba: { name: 'Alibaba Qwen', baseUrl: 'https://dashscope-intl.aliyuncs.com/compatible-mode/v1', apiKeyEnv: 'DASHSCOPE_API_KEY' },
25
+ venice: { name: 'Venice', baseUrl: 'https://api.venice.ai/api/v1', apiKeyEnv: 'VENICE_API_KEY' },
26
+ requesty: { name: 'Requesty', baseUrl: 'https://router.requesty.ai/v1', apiKeyEnv: 'REQUESTY_API_KEY' },
27
+ poolside: { name: 'Poolside', baseUrl: 'https://inference.poolside.ai/v1', apiKeyEnv: 'POOLSIDE_API_KEY' },
28
+ zenmux: { name: 'ZenMux', baseUrl: 'https://zenmux.ai/api/v1', apiKeyEnv: 'ZENMUX_API_KEY' },
29
+ };
30
+ const BUILTIN_IDS = new Set(['openai', 'anthropic', 'google', 'ollama', 'openrouter', 'minimax', 'bedrock', 'vertex', 'azure', 'lmstudio', 'openai-compatible', 'auto']);
31
+ const ID = /^[a-z0-9][a-z0-9_-]*$/;
32
+ const trimSlash = (url) => url.replace(/\/+$/, '');
33
+ const positive = (n) => typeof n === 'number' && Number.isFinite(n) && n > 0;
34
+ /** Why an entry of `customProviders` cannot be used, one problem per bad entry. A preset id may omit its URL. */
35
+ export function customProviderProblems(custom) {
36
+ const out = [];
37
+ for (const [id, c] of Object.entries(custom ?? {})) {
38
+ if (BUILTIN_IDS.has(id))
39
+ out.push({ id, message: `"${id}" is a built-in provider name; pick another id.` });
40
+ else if (!ID.test(id))
41
+ out.push({ id, message: `"${id}" is not a valid id: use lowercase letters, digits, - and _.` });
42
+ else if (!c || typeof c !== 'object')
43
+ out.push({ id, message: `"${id}" must be an object.` });
44
+ else {
45
+ const url = c.baseUrl ?? PROVIDER_PRESETS[id]?.baseUrl;
46
+ if (!url)
47
+ out.push({ id, message: `"${id}" needs a baseUrl.` });
48
+ else if (!/^https?:\/\//i.test(url))
49
+ out.push({ id, message: `"${id}" baseUrl must start with http:// or https://.` });
50
+ else if (c.timeoutMs !== undefined && !positive(c.timeoutMs))
51
+ out.push({ id, message: `"${id}" timeoutMs must be a positive number.` });
52
+ else if (c.chunkTimeoutMs !== undefined && !positive(c.chunkTimeoutMs))
53
+ out.push({ id, message: `"${id}" chunkTimeoutMs must be a positive number.` });
54
+ }
55
+ }
56
+ return out;
57
+ }
58
+ let registered;
59
+ /**
60
+ * The `customProviders` of the running host, so every place that turns a model name into a model (aliases,
61
+ * the cheap model, sub-agents, the picker) sees them without each call site passing the config along.
62
+ * Hosts register at the start of every run; tests reset with `undefined`.
63
+ */
64
+ export function registerCustomProviders(custom) {
65
+ registered = custom;
66
+ }
67
+ export function registeredCustomProviders() {
68
+ return registered;
69
+ }
70
+ let registeredFilters;
71
+ /** `modelFilters` of the running host (per provider id), read by the model pickers. */
72
+ export function registerModelFilters(filters) {
73
+ registeredFilters = filters;
74
+ }
75
+ /** The filter for a provider id: the registered `modelFilters` entry, else the whitelist/blacklist on its custom provider. */
76
+ export function modelFilterFor(providerId) {
77
+ return registeredFilters?.[providerId] ?? (registered?.[providerId]?.whitelist || registered?.[providerId]?.blacklist
78
+ ? { ...(registered[providerId].whitelist ? { whitelist: registered[providerId].whitelist } : {}), ...(registered[providerId].blacklist ? { blacklist: registered[providerId].blacklist } : {}) }
79
+ : undefined);
80
+ }
81
+ /** Preset ids and custom ids, once each; a built-in id never appears. */
82
+ export function knownProviderIds(custom) {
83
+ const ids = new Set(Object.keys(PROVIDER_PRESETS));
84
+ for (const id of Object.keys(custom ?? {}))
85
+ if (!BUILTIN_IDS.has(id))
86
+ ids.add(id);
87
+ return [...ids];
88
+ }
89
+ /** The endpoint for a provider id, or undefined when the id is built-in, unknown, or has no usable URL. */
90
+ export function resolveProviderEndpoint(id, custom, env = process.env) {
91
+ if (BUILTIN_IDS.has(id))
92
+ return undefined;
93
+ const preset = PROVIDER_PRESETS[id];
94
+ const c = custom?.[id];
95
+ const baseUrl = c?.baseUrl ?? preset?.baseUrl;
96
+ if (!baseUrl || (!c && !preset))
97
+ return undefined;
98
+ const keyEnv = c?.apiKeyEnv ?? preset?.apiKeyEnv;
99
+ const apiKey = c?.apiKey ?? (keyEnv ? env[keyEnv] : undefined);
100
+ return {
101
+ id,
102
+ name: c?.name ?? preset?.name ?? id,
103
+ baseUrl: trimSlash(baseUrl),
104
+ ...(apiKey ? { apiKey } : {}),
105
+ ...(c?.headers ? { headers: c.headers } : {}),
106
+ api: c?.api ?? preset?.api ?? 'openai-compatible',
107
+ ...(c?.timeoutMs ? { timeoutMs: c.timeoutMs } : {}),
108
+ ...(c?.chunkTimeoutMs ? { chunkTimeoutMs: c.chunkTimeoutMs } : {}),
109
+ };
110
+ }
111
+ /** The id the API expects for a model key (`models.<key>.id`), or the key itself. */
112
+ export function apiModelId(providerId, modelKey, custom) {
113
+ return custom?.[providerId]?.models?.[modelKey]?.id ?? modelKey;
114
+ }
115
+ /** Apply a provider's `whitelist` (only these) then `blacklist` (not these) to a model list. */
116
+ export function filterModels(entries, filter) {
117
+ const allow = filter?.whitelist?.length ? new Set(filter.whitelist) : undefined;
118
+ const block = filter?.blacklist?.length ? new Set(filter.blacklist) : undefined;
119
+ return entries.filter((e) => (!allow || allow.has(e.id)) && (!block || !block.has(e.id)));
120
+ }
@@ -27,11 +27,11 @@ export declare function commitDirty(cwd: string, options?: GitCommitOptions): Pr
27
27
  export declare function diffForFiles(cwd: string, files: string[]): Promise<string>;
28
28
  /** Deterministic message used when no model is available or it fails. */
29
29
  export declare function fallbackCommitMessage(files: string[]): string;
30
- export declare function commitMessagePrompt(diff: string, task?: string): string;
30
+ export declare function commitMessagePrompt(diff: string, task?: string, instruction?: string): string;
31
31
  /** Strip an inline `<think>` block (MiniMax and other reasoning models) and the fences/quotes a model may wrap around the message. */
32
32
  export declare function cleanCommitMessage(text: string): string;
33
33
  /** Draft a commit message with `model` (typically the cheap model); never throws. */
34
- export declare function generateCommitMessage(diff: string, files: string[], model: ModelConfig | undefined, task?: string, modelFactory?: (cfg: ModelConfig) => LanguageModel): Promise<string>;
34
+ export declare function generateCommitMessage(diff: string, files: string[], model: ModelConfig | undefined, task?: string, modelFactory?: (cfg: ModelConfig) => LanguageModel, instruction?: string): Promise<string>;
35
35
  export type UndoResult = {
36
36
  ok: true;
37
37
  commit: string;
@@ -112,9 +112,10 @@ export function fallbackCommitMessage(files) {
112
112
  return `forge: update ${shown}${more}`;
113
113
  }
114
114
  const MAX_DIFF_CHARS = 12_000;
115
- export function commitMessagePrompt(diff, task) {
115
+ export function commitMessagePrompt(diff, task, instruction) {
116
116
  const clipped = diff.length > MAX_DIFF_CHARS ? `${diff.slice(0, MAX_DIFF_CHARS)}\n[diff truncated]` : diff;
117
- return `Write a git commit message for this diff in conventional-commit style (type(scope): subject, imperative mood, under 72 characters). Add a short body only if the change needs explaining. Reply with the message only, no quotes or code fences.${task ? `\n\nThe change was made for this request: ${task}` : ''}\n\n${clipped}`;
117
+ const lead = instruction?.trim() || 'Write a git commit message for this diff in conventional-commit style (type(scope): subject, imperative mood, under 72 characters). Add a short body only if the change needs explaining. Reply with the message only, no quotes or code fences.';
118
+ return `${lead}${task ? `\n\nThe change was made for this request: ${task}` : ''}\n\n${clipped}`;
118
119
  }
119
120
  /** Strip an inline `<think>` block (MiniMax and other reasoning models) and the fences/quotes a model may wrap around the message. */
120
121
  export function cleanCommitMessage(text) {
@@ -125,11 +126,11 @@ export function cleanCommitMessage(text) {
125
126
  .trim();
126
127
  }
127
128
  /** Draft a commit message with `model` (typically the cheap model); never throws. */
128
- export async function generateCommitMessage(diff, files, model, task, modelFactory = createLanguageModel) {
129
+ export async function generateCommitMessage(diff, files, model, task, modelFactory = createLanguageModel, instruction) {
129
130
  if (!model || !diff.trim())
130
131
  return fallbackCommitMessage(files);
131
132
  try {
132
- const result = await generateText({ model: modelFactory(model), prompt: commitMessagePrompt(diff, task) });
133
+ const result = await generateText({ model: modelFactory(model), prompt: commitMessagePrompt(diff, task, instruction) });
133
134
  const message = cleanCommitMessage(result.text);
134
135
  return message || fallbackCommitMessage(files);
135
136
  }
@@ -62,14 +62,14 @@ declare const createParams: z.ZodObject<{
62
62
  head: z.ZodOptional<z.ZodString>;
63
63
  draft: z.ZodOptional<z.ZodBoolean>;
64
64
  }, "strip", z.ZodTypeAny, {
65
- title: string;
66
65
  body: string;
66
+ title: string;
67
67
  draft?: boolean | undefined;
68
68
  base?: string | undefined;
69
69
  head?: string | undefined;
70
70
  }, {
71
- title: string;
72
71
  body: string;
72
+ title: string;
73
73
  draft?: boolean | undefined;
74
74
  base?: string | undefined;
75
75
  head?: string | undefined;
@@ -15,6 +15,7 @@
15
15
  import { spawn } from 'node:child_process';
16
16
  import { execFileSync } from 'node:child_process';
17
17
  import { redact } from '../tools/redact.js';
18
+ import { shellEnv } from '../tools/shell-env.js';
18
19
  /** Compute the env-var map for an event. */
19
20
  export function eventToEnv(event) {
20
21
  const env = {
@@ -98,11 +99,11 @@ export function runHook(event, config) {
98
99
  proc = spawn(config.command, [], {
99
100
  shell: true,
100
101
  detached: process.platform !== 'win32',
101
- env: {
102
+ env: shellEnv({
102
103
  ...process.env,
103
104
  ...(config.env ?? {}),
104
105
  ...eventToEnv(event),
105
- },
106
+ }),
106
107
  stdio: ['ignore', 'pipe', 'pipe'],
107
108
  windowsHide: true,
108
109
  });
@@ -0,0 +1,39 @@
1
+ import type { McpServerSpec } from '../mcp/mcp-config.js';
2
+ export type ImportScope = 'user' | 'project';
3
+ export interface ClaudeImportPlan {
4
+ mcp: Array<{
5
+ name: string;
6
+ spec: McpServerSpec;
7
+ scope: ImportScope;
8
+ from: string;
9
+ }>;
10
+ /** Folders to add to `skillPaths` (`~/...` or relative to the workspace). */
11
+ skillPaths: string[];
12
+ agents: Array<{
13
+ name: string;
14
+ scope: ImportScope;
15
+ text: string;
16
+ from: string;
17
+ }>;
18
+ commands: Array<{
19
+ name: string;
20
+ scope: ImportScope;
21
+ text: string;
22
+ from: string;
23
+ }>;
24
+ notes: string[];
25
+ }
26
+ export interface ImportRoots {
27
+ home: string;
28
+ cwd: string;
29
+ }
30
+ export declare function planClaudeImport(roots: ImportRoots): Promise<ClaudeImportPlan>;
31
+ export interface ApplyOptions extends ImportRoots {
32
+ /** Defaults to `<home>/.forge/config.json`. */
33
+ configPath?: string;
34
+ }
35
+ export interface ApplyResult {
36
+ written: string[];
37
+ skipped: string[];
38
+ }
39
+ export declare function applyClaudeImport(plan: ClaudeImportPlan, opts: ApplyOptions): Promise<ApplyResult>;
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Bring a Claude Code setup over to Forge: MCP servers, skill folders, sub-agents and slash commands.
3
+ * `planClaudeImport` only reads; `applyClaudeImport` writes into Forge's own locations and never replaces
4
+ * something that is already there. What Forge has no equivalent for (permissions, hooks) is listed in `notes`.
5
+ * A converted agent is never given more tools than the original: tools that map to nothing leave it read-only.
6
+ */
7
+ import { mkdir, readdir, readFile, writeFile } from 'node:fs/promises';
8
+ import path from 'node:path';
9
+ import { loadForgeConfigFile, saveForgeConfigFile } from '../config-file.js';
10
+ import { parseFrontmatter } from '../skills/parser.js';
11
+ const TOOL_MAP = {
12
+ Read: ['read_file'],
13
+ Grep: ['search_files'],
14
+ Glob: ['list_files'],
15
+ LS: ['list_files'],
16
+ Write: ['write_file'],
17
+ Edit: ['edit_file'],
18
+ MultiEdit: ['edit_file'],
19
+ NotebookEdit: ['edit_notebook'],
20
+ Bash: ['run_command'],
21
+ WebFetch: ['fetch_url'],
22
+ WebSearch: ['web_search'],
23
+ TodoWrite: ['todo_write', 'todo_read'],
24
+ };
25
+ const READ_ONLY_FALLBACK = ['read_file', 'list_files', 'search_files'];
26
+ const COMMAND_NAME = /^[a-zA-Z][a-zA-Z0-9_-]*$/;
27
+ /** A command body that runs a shell line (!`cmd`) or pulls in a file (@path.ext) in Claude Code. */
28
+ const CLAUDE_INLINE = /(^|\s)!`|(^|\s)@\S+\.\w+/;
29
+ const isObject = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
30
+ async function readJson(file) {
31
+ try {
32
+ return JSON.parse(await readFile(file, 'utf8'));
33
+ }
34
+ catch {
35
+ return undefined;
36
+ }
37
+ }
38
+ async function isDir(dir) {
39
+ try {
40
+ await readdir(dir);
41
+ return true;
42
+ }
43
+ catch {
44
+ return false;
45
+ }
46
+ }
47
+ async function mdFiles(dir) {
48
+ try {
49
+ return (await readdir(dir)).filter((n) => n.toLowerCase().endsWith('.md')).sort();
50
+ }
51
+ catch {
52
+ return [];
53
+ }
54
+ }
55
+ /** One Claude Code server entry as a Forge spec, or undefined when it has neither a command nor a URL. */
56
+ function convertServer(raw) {
57
+ if (!isObject(raw))
58
+ return undefined;
59
+ const str = (v) => (typeof v === 'string' && v ? v : undefined);
60
+ const strList = (v) => (Array.isArray(v) ? v.filter((x) => typeof x === 'string') : undefined);
61
+ const strMap = (v) => isObject(v) ? Object.fromEntries(Object.entries(v).filter((e) => typeof e[1] === 'string')) : undefined;
62
+ const command = str(raw.command);
63
+ const url = str(raw.url);
64
+ if (!command && !url)
65
+ return undefined;
66
+ const type = raw.type === 'http' ? 'streamable-http' : raw.type === 'sse' ? 'sse' : raw.type === 'stdio' ? 'stdio' : undefined;
67
+ const args = strList(raw.args);
68
+ const env = strMap(raw.env);
69
+ const headers = strMap(raw.headers);
70
+ return {
71
+ ...(type ? { type } : {}),
72
+ ...(command ? { command } : {}),
73
+ ...(args ? { args } : {}),
74
+ ...(env && Object.keys(env).length ? { env } : {}),
75
+ ...(url ? { url } : {}),
76
+ ...(headers && Object.keys(headers).length ? { headers } : {}),
77
+ };
78
+ }
79
+ function collectServers(servers, scope, from, plan, seen) {
80
+ if (!isObject(servers))
81
+ return;
82
+ for (const [name, raw] of Object.entries(servers)) {
83
+ const key = `${scope}:${name}`;
84
+ if (seen.has(key))
85
+ continue;
86
+ const spec = convertServer(raw);
87
+ if (!spec) {
88
+ plan.notes.push(`${from}: MCP server "${name}" has no command or url; not imported`);
89
+ continue;
90
+ }
91
+ seen.add(key);
92
+ plan.mcp.push({ name, spec, scope, from });
93
+ }
94
+ }
95
+ const norm = (p) => p.replace(/\\/g, '/').replace(/\/$/, '').toLowerCase();
96
+ function agentSlug(name) {
97
+ return name.toLowerCase().replace(/[^a-z0-9_-]+/g, '-').replace(/^[^a-z0-9]+|-+$/g, '').slice(0, 64);
98
+ }
99
+ function oneLine(s) {
100
+ return s.replace(/\\n/g, ' ').replace(/\s+/g, ' ').trim();
101
+ }
102
+ function convertAgent(file, text, notes) {
103
+ const { fields, body } = parseFrontmatter(text.replace(/\r\n/g, '\n'));
104
+ const label = path.basename(file);
105
+ const description = oneLine(fields.get('description') ?? '');
106
+ if (!description) {
107
+ notes.push(`${label}: no description; agent not imported`);
108
+ return undefined;
109
+ }
110
+ const name = agentSlug(fields.get('name') || label.replace(/\.md$/i, ''));
111
+ if (!name) {
112
+ notes.push(`${label}: no usable name; agent not imported`);
113
+ return undefined;
114
+ }
115
+ let tools;
116
+ const rawTools = fields.get('tools');
117
+ if (rawTools !== undefined && rawTools.trim()) {
118
+ const wanted = rawTools.split(',').map((s) => s.trim()).filter(Boolean);
119
+ const mapped = new Set();
120
+ for (const w of wanted) {
121
+ const base = w.replace(/\(.*$/, '');
122
+ if (base !== w)
123
+ notes.push(`${label}: "${w}" limited a tool by pattern in Claude Code; Forge permission rules decide that now`);
124
+ for (const t of TOOL_MAP[base] ?? [])
125
+ mapped.add(t);
126
+ if (!TOOL_MAP[base] && base !== 'Task')
127
+ notes.push(`${label}: tool "${base}" has no Forge equivalent; left out`);
128
+ }
129
+ tools = mapped.size > 0 ? [...mapped] : READ_ONLY_FALLBACK;
130
+ if (mapped.size === 0)
131
+ notes.push(`${label}: none of its tools map to Forge; imported as read-only`);
132
+ }
133
+ const front = ['---', `description: ${description}`, 'mode: subagent', ...(tools ? [`tools: ${tools.join(', ')}`] : []), '---'];
134
+ return { name, text: `${front.join('\n')}\n${body.trim()}\n` };
135
+ }
136
+ function convertCommand(file, text, notes) {
137
+ const label = path.basename(file);
138
+ const name = label.replace(/\.md$/i, '');
139
+ if (!COMMAND_NAME.test(name)) {
140
+ notes.push(`${label}: not a valid command name (letters, digits, - and _); not imported`);
141
+ return undefined;
142
+ }
143
+ const { fields, body } = parseFrontmatter(text.replace(/\r\n/g, '\n'));
144
+ const description = oneLine(fields.get('description') ?? '');
145
+ if (CLAUDE_INLINE.test(body))
146
+ notes.push(`${label}: runs a shell line or pulls in a file inside the prompt in Claude Code; Forge sends that text as it is`);
147
+ const head = description ? `---\ndescription: ${description}\n---\n` : '';
148
+ return { name, text: `${head}${body.replace(/\s+$/, '')}\n` };
149
+ }
150
+ export async function planClaudeImport(roots) {
151
+ const { home, cwd } = roots;
152
+ const plan = { mcp: [], skillPaths: [], agents: [], commands: [], notes: [] };
153
+ const seen = new Set();
154
+ const claudeJson = await readJson(path.join(home, '.claude.json'));
155
+ if (isObject(claudeJson)) {
156
+ collectServers(claudeJson.mcpServers, 'user', '~/.claude.json', plan, seen);
157
+ if (isObject(claudeJson.projects)) {
158
+ for (const [dir, value] of Object.entries(claudeJson.projects)) {
159
+ if (norm(dir) === norm(cwd) && isObject(value))
160
+ collectServers(value.mcpServers, 'project', '~/.claude.json (this project)', plan, seen);
161
+ }
162
+ }
163
+ }
164
+ const projectMcp = await readJson(path.join(cwd, '.mcp.json'));
165
+ if (isObject(projectMcp))
166
+ collectServers(projectMcp.mcpServers, 'project', '.mcp.json', plan, seen);
167
+ if (await isDir(path.join(home, '.claude', 'skills')))
168
+ plan.skillPaths.push('~/.claude/skills');
169
+ if (await isDir(path.join(cwd, '.claude', 'skills')))
170
+ plan.skillPaths.push('.claude/skills');
171
+ const places = [
172
+ ['user', path.join(home, '.claude')],
173
+ ['project', path.join(cwd, '.claude')],
174
+ ];
175
+ for (const [scope, base] of places) {
176
+ for (const n of await mdFiles(path.join(base, 'agents'))) {
177
+ const file = path.join(base, 'agents', n);
178
+ const out = convertAgent(file, await readFile(file, 'utf8'), plan.notes);
179
+ if (out)
180
+ plan.agents.push({ ...out, scope, from: file });
181
+ }
182
+ for (const n of await mdFiles(path.join(base, 'commands'))) {
183
+ const file = path.join(base, 'commands', n);
184
+ const out = convertCommand(file, await readFile(file, 'utf8'), plan.notes);
185
+ if (out)
186
+ plan.commands.push({ ...out, scope, from: file });
187
+ }
188
+ const settings = await readJson(path.join(base, 'settings.json'));
189
+ if (isObject(settings)) {
190
+ const where = `${scope === 'user' ? '~' : '.'}/.claude/settings.json`;
191
+ if (settings.permissions !== undefined)
192
+ plan.notes.push(`${where}: permissions are not imported; set Forge permission rules instead`);
193
+ if (settings.hooks !== undefined)
194
+ plan.notes.push(`${where}: hooks are not imported`);
195
+ }
196
+ }
197
+ return plan;
198
+ }
199
+ async function writeNew(file, text, result) {
200
+ await mkdir(path.dirname(file), { recursive: true });
201
+ try {
202
+ await writeFile(file, text, { encoding: 'utf8', flag: 'wx' });
203
+ result.written.push(file);
204
+ }
205
+ catch (err) {
206
+ if (err.code === 'EEXIST')
207
+ result.skipped.push(`${file} already exists`);
208
+ else
209
+ throw err;
210
+ }
211
+ }
212
+ export async function applyClaudeImport(plan, opts) {
213
+ const { home, cwd } = opts;
214
+ const result = { written: [], skipped: [] };
215
+ const forgeDir = (scope) => path.join(scope === 'user' ? home : cwd, '.forge');
216
+ for (const scope of ['user', 'project']) {
217
+ const items = plan.mcp.filter((m) => m.scope === scope);
218
+ if (items.length === 0)
219
+ continue;
220
+ const file = path.join(forgeDir(scope), 'mcp.json');
221
+ const existing = await readJson(file);
222
+ const servers = isObject(existing) && isObject(existing.mcpServers) ? { ...existing.mcpServers } : {};
223
+ let added = 0;
224
+ for (const m of items) {
225
+ if (m.name in servers) {
226
+ result.skipped.push(`MCP server "${m.name}" already in ${file}`);
227
+ }
228
+ else {
229
+ servers[m.name] = m.spec;
230
+ added += 1;
231
+ }
232
+ }
233
+ if (added > 0) {
234
+ await mkdir(path.dirname(file), { recursive: true });
235
+ await writeFile(file, JSON.stringify({ ...(isObject(existing) ? existing : {}), mcpServers: servers }, null, 2) + '\n', 'utf8');
236
+ result.written.push(file);
237
+ }
238
+ }
239
+ for (const a of plan.agents)
240
+ await writeNew(path.join(forgeDir(a.scope), 'agents', `${a.name}.md`), a.text, result);
241
+ for (const c of plan.commands)
242
+ await writeNew(path.join(forgeDir(c.scope), 'commands', `${c.name}.md`), c.text, result);
243
+ if (plan.skillPaths.length > 0) {
244
+ const configPath = opts.configPath ?? path.join(home, '.forge', 'config.json');
245
+ const { config } = await loadForgeConfigFile(configPath);
246
+ const current = config.skillPaths ?? [];
247
+ const add = plan.skillPaths.filter((p) => !current.includes(p));
248
+ if (add.length > 0) {
249
+ await saveForgeConfigFile({ skillPaths: [...current, ...add] }, configPath);
250
+ result.written.push(configPath);
251
+ }
252
+ }
253
+ return result;
254
+ }
package/dist/index.d.ts CHANGED
@@ -36,17 +36,17 @@ export type { MemoryForgetResult, MemoryReadResult, MemoryWriteResult } from './
36
36
  export { buildLlmSummarizer, compactMessages, estimateMessageTokens, estimateTokens, getModelWindowTokens, shouldCompact, } from './context/compaction.js';
37
37
  export type { CompactionResult, Summarizer } from './context/compaction.js';
38
38
  export { defaultModelFor, detectProvider, envVarForProvider, fastDefaultFor, hasProviderKey, readModelConfigFromEnv, resolveApiKey, SUPPORTED_PROVIDERS, } from './config.js';
39
- export { loadMcpConfig, parseMcpConfig, connectAll, connectOne, closeAll, mcpResultToValue, jsonSchemaToZod, } from './mcp/index.js';
39
+ export { loadMcpConfig, parseMcpConfig, connectAll, connectOne, closeAll, mcpResultToValue, mcpInstructionsBlock, createMcpResourceTools, jsonSchemaToZod, } from './mcp/index.js';
40
40
  export type { McpConfigFile, McpServerSpec, ConnectedMcpServer, } from './mcp/index.js';
41
41
  export { createLanguageModel, formatModelId, pickAvailableProvider, resolveModelFromEnv, resolveModelFromEnvAsync } from './providers.js';
42
42
  export { defaultSessionDir, generateSessionId, saveSession, loadSession, deleteSession, listSessions, findLatestSession, formatSessionMarkdown, formatSessionJson, appendedSince, summarizeMessage, watchSession, searchSessions, sessionText, pruneSessions, pruneByRetention, } from './sessions/index.js';
43
43
  export type { Session, ListEntry, FormatOptions, SessionUpdate, WatchSessionOptions, SessionMatch, SearchSessionsOptions, SessionPruneOptions, SessionPruneResult } from './sessions/index.js';
44
44
  export { commandDirs, expandCustomCommand, formatCustomCommands, loadCustomCommands, parseCommandFile, type CustomCommand, } from './commands/custom-commands.js';
45
45
  export { BUILTIN_TOOLS, editFileTool, fetchUrlTool, findSymbolTool, listFilesTool, memoryReadTool, memoryForgetTool, memoryWriteTool, readFileTool, runCommandTool, searchFilesTool, subAgentTool, writeFileTool, checkPolicyViolation, globToRegex, mergeToolSandbox, normalizePathPattern, pathMatchesAny, redact, countRedactions, buildRules, ToolResultCache, buildCacheKey, cachedExecute, } from './tools/index.js';
46
- export type { AgentConfig, AgentEvent, AgentRunOptions, AgentRunReason, AgentRunResult, AnyLanguageModel, ApprovalDecision, CommandRunner, ModelRouter, VerifyConfig, ApprovalRequest, LanguageModelFactory, ModelConfig, PricePerMillionTokens, ProviderName, RedactOptions, ToolContext, ToolDefinition, ToolPolicy, ToolSandbox, } from './types.js';
46
+ export type { AgentConfig, AgentEvent, AgentRunOptions, AgentRunReason, AgentRunResult, AnyLanguageModel, ApprovalDecision, CommandRunner, ModelRouter, VerifyConfig, ApprovalRequest, LanguageModelFactory, ModelConfig, PricePerMillionTokens, ProviderName, RedactOptions, TodoItem, ToolContext, UserQuestion, ToolDefinition, ToolPolicy, ToolSandbox, } from './types.js';
47
47
  export type { CacheKeyParts, CacheStats } from './tools/index.js';
48
48
  export type { RedactionRule, RedactionResult } from './tools/index.js';
49
- export { buildSkillsBlock, composeSkillsPrompt, discoverSkills, fetchSkillsCatalog, installSkillFromCatalog, loadSkillsRegistry, loadSkillsRegistryAsync, parseSkill, parseSkillsDir, resolveSkillRoots, scoreSkill, searchSkills, selectSkills, skillsRegistryFromList, DEFAULT_SKILL_CATALOG_URL, GLOBAL_SKILLS_DIR, LOCAL_SKILLS_DIR, PROJECT_SKILLS_DIR, SKILL_CATALOG_VERSION, } from './skills/index.js';
49
+ export { buildSkillsBlock, builtinSkills, composeSkillsPrompt, discoverSkills, fetchSkillsCatalog, installSkillFromCatalog, loadSkillsRegistry, loadSkillsRegistryAsync, parseSkill, parseSkillsDir, resolveSkillRoots, scoreSkill, searchSkills, selectSkills, skillsRegistryFromList, DEFAULT_SKILL_CATALOG_URL, GLOBAL_SKILLS_DIR, LOCAL_SKILLS_DIR, PROJECT_SKILLS_DIR, SKILL_CATALOG_VERSION, } from './skills/index.js';
50
50
  export type { Skill, SkillCatalog, SkillCatalogEntry, SkillRegistry, SkillRoots, SkillSearchResult, SkillSource, } from './skills/index.js';
51
51
  export { cosine, embedderFromEnv, hashingEmbedder, openAiCompatibleEmbedder, } from './semantic/embedder.js';
52
52
  export type { Embedder, OpenAiCompatibleEmbedderOptions } from './semantic/embedder.js';
@@ -63,7 +63,13 @@ export { PolicyError, applyPolicy, assertProviderAllowed, filterMcpServers, getO
63
63
  export type { OrgPolicy } from './policy/policy.js';
64
64
  export { createFileAuditSink, defaultAuditFile, scrubArgs } from './policy/audit.js';
65
65
  export type { AuditEntry, AuditSink } from './policy/audit.js';
66
- export { hasProxy, installProxy, proxyFromEnv } from './policy/proxy.js';
66
+ export { hasProxy, installProxy, installedFetch, proxyFromEnv } from './policy/proxy.js';
67
+ export { readCaCertificates } from './policy/ca-certs.js';
68
+ export { BackgroundAgents, MAX_RUNNING_BACKGROUND, backgroundResultsBlock } from './agent/background.js';
69
+ export type { BackgroundAgent, BackgroundState } from './agent/background.js';
70
+ export { applyClaudeImport, planClaudeImport } from './import/claude-code.js';
71
+ export type { ApplyResult, ClaudeImportPlan } from './import/claude-code.js';
72
+ export type { CaCertificates } from './policy/ca-certs.js';
67
73
  export type { ProxySettings } from './policy/proxy.js';
68
74
  export { AGENT_HOOK_TOOLS, createAgentHookRunner, DEFAULT_AGENT_HOOK_TIMEOUT_MS, DEFAULT_HOOK_TIMEOUT_MS, HooksBus, eventFilePath, eventToEnv, eventToolName, globalSettingsPath, hooksBusFromConfig, loadHooksConfig, matcherMatches, projectSettingsPath, runHook, validateHooksConfig, HOOK_EVENT_NAMES, shouldIgnoreWatchPath, FileChangeCoalescer, watchFilesystem, } from './hooks/index.js';
69
75
  export type { AgentHookConfig, AgentHookRunner, AgentHookRunnerOptions, ShellHookConfig, ForgeSettings, HookConfig, HookDispatchLog, HookEvent, HookEventName, FileChangeType, HookMatcherEntry, HookRunResult, HooksConfig, LoadHooksResult, } from './hooks/index.js';
@@ -74,7 +80,7 @@ export type { InstalledMcpServer, InstalledServerMetadata, MarketplaceCatalog, M
74
80
  export { MARKETPLACE_CATALOG_VERSION } from './mcp/marketplace-types.js';
75
81
  export { CheckpointStore, type CheckpointStoreLimits } from './checkpoints/index.js';
76
82
  export type { CaptureOptions, Checkpoint, CheckpointFile, CheckpointFileStatus, CheckpointSummary, CheckpointTrigger, PruneResult, RestoreResult, } from './checkpoints/index.js';
77
- export { SpecStore, isSafeSpecId, runKey, slugifyTaskForSpecId, SPEC_READONLY_TOOLS, splitSpecSections, convertToCheckboxes, parseTasks, setTaskDone, changedSince, changeSize, keepDoneTasks, referencedPaths, specDrift, syncSpecTask } from './specs/index.js';
83
+ export { SpecStore, isSafeSpecId, runKey, slugifyTaskForSpecId, SPEC_READONLY_TOOLS, splitRequirementsDesign, splitSpecSections, splitTasksSection, convertToCheckboxes, parseTasks, setTaskDone, changedSince, changeSize, keepDoneTasks, referencedPaths, specDrift, syncSpecTask } from './specs/index.js';
78
84
  export type { SpecDraft, SpecRunRecord, SpecRuns, SpecSections, SpecSummary, SpecTask, SpecDrift } from './specs/index.js';
79
85
  export { BUILTIN_SPECIALISTS, defaultConfigPath, effectiveToolsFor, BOARD_TOOL_NAMES, createBoardTools, DEFAULT_BOARD_CAP, getRegisteredSpecialist, getSpecialist, MessageBoard, listSpecialists, loadSpecialistsFromConfig, parseRouterOutput, registerSpecialist, resolveEffectiveModel, ROUTER_SYSTEM_PROMPT, runOrchestrator, runRouter, saveSpecialistsToConfig, unregisterSpecialist, validateSpecialistConfig, _resetSpecialistsForTests, _customSpecialistCount, } from './orchestrator/index.js';
80
86
  export type { BoardMessage } from './orchestrator/index.js';
@@ -87,10 +93,21 @@ export { branchChangedFiles, branchDiff, commitAllInWorktree, compareWorktreeCon
87
93
  export type { MergeResult, WorktreeCommit, CreateOptions, RemoveOptions, RemoveResult, WorktreeContextSnapshot, WorktreeInfo, WorktreeMismatchKind, WorktreeMismatchResult, } from './worktree/index.js';
88
94
  export { formatSteeringLine, isSafeSteeringName, loadSteering, overrideFilePath, parseSteering, pathMatchesGlob, readOverride, selectAtStart, selectOnToolTouch, setSteeringDisabled, stripSteering, wrapSteering, } from './steering/index.js';
89
95
  export type { StartSelection, SteeringConfig, SteeringDoc, SteeringInclusion, SteeringLoadResult, SteeringSkip, } from './steering/index.js';
96
+ export { CONFIG_KEY_COVERAGE, FORGE_CONFIG_SCHEMA } from './config-schema.js';
97
+ export { FILE_WRITE_TOOL_NAMES, filesTouchedBy } from './tools/write-tools.js';
98
+ export { shellEnv } from './tools/shell-env.js';
99
+ export { previewPatch } from './tools/apply-patch.js';
100
+ export type { PatchPreview } from './tools/apply-patch.js';
101
+ export { applyPatchTool, parsePatch, patchedPaths } from './tools/apply-patch.js';
102
+ export { buildEmbedder, type EmbedderSpec } from './semantic/embedder.js';
103
+ export { parseExtensions, type SemanticOptions } from './semantic/semantic-index.js';
104
+ export { cleanDigest, generateSessionDigest, shouldDigest, type DigestItem } from './memory/session-digest.js';
105
+ export { buildExtraInstructionsBlock } from './steering/extra-instructions.js';
90
106
  export { buildProjectInstructionsBlock, PROJECT_INSTRUCTION_FILES } from './steering/project-instructions.js';
91
107
  export { aiCommentTask, findAiComments, triggerOf } from './ai-comments/index.js';
92
108
  export type { AiComment, AiCommentAction, AiCommentFile } from './ai-comments/index.js';
93
109
  export * from './git/index.js';
110
+ export * from './journey/index.js';
94
111
  export * from './repomap/index.js';
95
112
  export * from './lint/index.js';
96
113
  export { findSyntaxErrors } from './code-index/parser.js';
@@ -98,7 +115,19 @@ export type { SyntaxProblem } from './code-index/parser.js';
98
115
  export * from './lsp/index.js';
99
116
  export * from './plugins/index.js';
100
117
  export { READ_ONLY_TOOLS, resolveAutoApprove } from './tools/read-only.js';
118
+ export { compilePermissionRules, evaluatePermission, isSensitiveFile, permissionFor, splitShellCommands, touchesOutsideCwd } from './tools/permission-rules.js';
119
+ export type { PermissionAction, PermissionConfig, PermissionRule } from './tools/permission-rules.js';
101
120
  export { autoRunCommands, autoRunFingerprint, detectProjectChecks, formatProjectCommandsBlock, projectScriptsFingerprint } from './verify/index.js';
102
121
  export { isAlwaysAllowable, isListedCommand, needsScriptFingerprint, normalizeCommand } from './tools/command-allow.js';
103
122
  export type { CheckKind, DetectedCheck, DetectedChecks } from './verify/index.js';
104
123
  export { IgnoreRules, IGNORE_FILE_NAMES, findGitRoot } from './tools/ignore-rules.js';
124
+ export { MAX_OBJECTIVE_CHARS, applyTurnOutcome, createGoalReportTool, goalPrompt, pauseGoal, resumeGoal, startGoal } from './agent/goal.js';
125
+ export type { GoalReport, GoalState, GoalStatus, TurnOutcome } from './agent/goal.js';
126
+ export { loadMergedForgeConfig, projectConfigPath } from './config-merge.js';
127
+ export { PROVIDER_PRESETS, apiModelId, customProviderProblems, filterModels, knownProviderIds, modelFilterFor, registerCustomProviders, registerModelFilters, registeredCustomProviders, resolveProviderEndpoint, } from './custom-providers.js';
128
+ export type { CustomModelConfig, CustomProviderConfig, EndpointApi, ProviderEndpoint, ProviderPreset } from './custom-providers.js';
129
+ export { withTimeouts, type FetchTimeouts } from './timed-fetch.js';
130
+ export { createEndpointModel } from './providers.js';
131
+ export { agentToSpecialist, loadAgentFiles, parseAgentFile, registerAgentDefs, registeredAgent, registeredAgents, type AgentDef } from './agents/agent-files.js';
132
+ export { applyResolved, type ResolvedModelRef } from './model-alias.js';
133
+ export { getModelOverride, overridesFromProviders, registerModelOverrides, type ModelOverride, type ModelOverrides } from './model-overrides.js';