@fastagent-sh/fastagent 0.29.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (310) hide show
  1. package/README.md +9 -4
  2. package/dist/atomic-write.d.ts +4 -5
  3. package/dist/atomic-write.js +4 -5
  4. package/dist/authoring.d.ts +32 -15
  5. package/dist/authoring.js +147 -127
  6. package/dist/channels/agentcore-protocol.d.ts +8 -3
  7. package/dist/channels/agentcore-protocol.js +3 -1
  8. package/dist/channels/agentcore-service.d.ts +1 -0
  9. package/dist/channels/agentcore-service.js +14 -4
  10. package/dist/channels/agentcore.d.ts +9 -0
  11. package/dist/channels/agentcore.js +29 -4
  12. package/dist/channels/context.d.ts +2 -0
  13. package/dist/channels/context.js +5 -1
  14. package/dist/channels/feishu/feishu-api.d.ts +2 -1
  15. package/dist/channels/feishu/feishu.d.ts +0 -3
  16. package/dist/channels/feishu/feishu.js +10 -10
  17. package/dist/channels/feishu/register-app.js +3 -5
  18. package/dist/channels/feishu/scaffold/channel.ts +32 -34
  19. package/dist/channels/feishu/scaffold/feishu-send.ts +5 -5
  20. package/dist/channels/feishu/scaffold/feishu-threads.ts +3 -3
  21. package/dist/channels/feishu/setup-mode.d.ts +2 -12
  22. package/dist/channels/feishu/setup-mode.js +2 -22
  23. package/dist/channels/feishu/setup.d.ts +12 -0
  24. package/dist/channels/feishu/setup.js +197 -0
  25. package/dist/channels/kit/abortable.d.ts +8 -0
  26. package/dist/channels/kit/abortable.js +22 -0
  27. package/dist/channels/lark/lark.d.ts +0 -3
  28. package/dist/channels/lark/lark.js +0 -5
  29. package/dist/channels/lark/scaffold/channel.ts +33 -35
  30. package/dist/channels/lark/scaffold/lark-send.ts +5 -5
  31. package/dist/channels/lark/scaffold/lark-threads.ts +3 -3
  32. package/dist/channels/lark/setup.d.ts +9 -0
  33. package/dist/channels/lark/setup.js +57 -0
  34. package/dist/channels/setup.d.ts +112 -0
  35. package/dist/channels/setup.js +31 -0
  36. package/dist/channels/slack/history.js +1 -1
  37. package/dist/channels/slack/manifest.d.ts +17 -7
  38. package/dist/channels/slack/manifest.js +12 -16
  39. package/dist/channels/slack/scaffold/channel.ts +44 -30
  40. package/dist/channels/slack/scaffold/slack-send.ts +8 -8
  41. package/dist/channels/slack/scaffold/slack-threads.ts +4 -4
  42. package/dist/channels/slack/setup.d.ts +7 -0
  43. package/dist/channels/slack/setup.js +119 -0
  44. package/dist/channels/slack/slack-api.d.ts +2 -0
  45. package/dist/channels/slack/slack-api.js +4 -0
  46. package/dist/channels/slack/slack.d.ts +21 -2
  47. package/dist/channels/slack/slack.js +398 -338
  48. package/dist/channels/slack/socket.d.ts +57 -0
  49. package/dist/channels/slack/socket.js +305 -0
  50. package/dist/channels/telegram/polling.d.ts +27 -0
  51. package/dist/channels/telegram/polling.js +105 -0
  52. package/dist/channels/telegram/scaffold/channel.ts +52 -32
  53. package/dist/channels/telegram/scaffold/telegram-send.ts +9 -9
  54. package/dist/channels/telegram/setup.d.ts +2 -0
  55. package/dist/channels/telegram/setup.js +42 -0
  56. package/dist/channels/telegram/telegram-api.d.ts +27 -1
  57. package/dist/channels/telegram/telegram-api.js +7 -6
  58. package/dist/channels/telegram/telegram.d.ts +11 -3
  59. package/dist/channels/telegram/telegram.js +184 -157
  60. package/dist/channels/webhook-evidence.d.ts +11 -0
  61. package/dist/channels/webhook-evidence.js +71 -0
  62. package/dist/channels/webhooks.d.ts +37 -0
  63. package/dist/channels/webhooks.js +70 -0
  64. package/dist/child-env.d.ts +16 -0
  65. package/dist/child-env.js +30 -0
  66. package/dist/cli/box-login.js +4 -1
  67. package/dist/cli/channel-app.d.ts +3 -0
  68. package/dist/cli/channel-app.js +35 -0
  69. package/dist/cli/channel-setup.d.ts +38 -0
  70. package/dist/cli/channel-setup.js +195 -0
  71. package/dist/cli/commands/add.d.ts +6 -14
  72. package/dist/cli/commands/add.js +45 -303
  73. package/dist/cli/commands/auth.js +8 -2
  74. package/dist/cli/commands/build.d.ts +5 -0
  75. package/dist/cli/commands/build.js +16 -0
  76. package/dist/cli/commands/channels.d.ts +19 -0
  77. package/dist/cli/commands/channels.js +230 -0
  78. package/dist/cli/commands/content.d.ts +7 -3
  79. package/dist/cli/commands/content.js +16 -10
  80. package/dist/cli/commands/deploy/agentcore.js +55 -23
  81. package/dist/cli/commands/deploy/channel-apps.d.ts +17 -0
  82. package/dist/cli/commands/deploy/channel-apps.js +30 -0
  83. package/dist/cli/commands/deploy/docker.js +32 -65
  84. package/dist/cli/commands/deploy/fly.js +28 -12
  85. package/dist/cli/commands/deploy/railway.js +38 -13
  86. package/dist/cli/commands/deploy/secrets-step.d.ts +19 -0
  87. package/dist/cli/commands/deploy/secrets-step.js +66 -0
  88. package/dist/cli/commands/deploy/shared.d.ts +16 -5
  89. package/dist/cli/commands/deploy/shared.js +25 -8
  90. package/dist/cli/commands/deploy.js +53 -60
  91. package/dist/cli/commands/dev.d.ts +2 -0
  92. package/dist/cli/commands/dev.js +15 -3
  93. package/dist/cli/commands/env.js +1 -1
  94. package/dist/cli/commands/info.d.ts +2 -0
  95. package/dist/cli/commands/info.js +60 -25
  96. package/dist/cli/commands/init.js +3 -1
  97. package/dist/cli/commands/invoke.d.ts +2 -0
  98. package/dist/cli/commands/invoke.js +20 -7
  99. package/dist/cli/commands/login.d.ts +0 -7
  100. package/dist/cli/commands/login.js +22 -10
  101. package/dist/cli/commands/logout.js +1 -1
  102. package/dist/cli/commands/mcp.js +14 -6
  103. package/dist/cli/commands/models.js +3 -2
  104. package/dist/cli/commands/schedules.d.ts +4 -2
  105. package/dist/cli/commands/schedules.js +14 -10
  106. package/dist/cli/commands/secrets.d.ts +14 -0
  107. package/dist/cli/commands/secrets.js +213 -0
  108. package/dist/cli/commands/start.d.ts +14 -14
  109. package/dist/cli/commands/start.js +46 -60
  110. package/dist/cli/content-view.d.ts +4 -6
  111. package/dist/cli/content-view.js +5 -11
  112. package/dist/cli/first-run-channels.d.ts +3 -0
  113. package/dist/cli/first-run-channels.js +63 -0
  114. package/dist/cli/instance.d.ts +41 -0
  115. package/dist/cli/instance.js +155 -0
  116. package/dist/cli/invoke-stream.d.ts +5 -2
  117. package/dist/cli/invoke-stream.js +7 -2
  118. package/dist/cli/program.js +282 -110
  119. package/dist/cli/serve.d.ts +4 -5
  120. package/dist/cli/serve.js +4 -7
  121. package/dist/cli/shared.d.ts +11 -2
  122. package/dist/cli/shared.js +32 -19
  123. package/dist/cli/ticket.js +50 -6
  124. package/dist/content/declare.d.ts +31 -37
  125. package/dist/content/declare.js +59 -60
  126. package/dist/content/file.d.ts +23 -12
  127. package/dist/content/file.js +48 -35
  128. package/dist/content/git.d.ts +0 -10
  129. package/dist/content/git.js +5 -39
  130. package/dist/content/resolve.d.ts +18 -22
  131. package/dist/content/resolve.js +32 -66
  132. package/dist/content/source.d.ts +7 -8
  133. package/dist/content/source.js +10 -28
  134. package/dist/credentials-page.d.ts +26 -0
  135. package/dist/credentials-page.js +185 -0
  136. package/dist/declared-secrets.d.ts +10 -3
  137. package/dist/declared-secrets.js +8 -4
  138. package/dist/deploy/agentcore/destroy.d.ts +4 -1
  139. package/dist/deploy/agentcore/destroy.js +25 -1
  140. package/dist/deploy/agentcore/forwarder.js +1 -1
  141. package/dist/deploy/agentcore/plan.d.ts +7 -21
  142. package/dist/deploy/agentcore/plan.js +47 -49
  143. package/dist/deploy/agentcore/run.d.ts +18 -10
  144. package/dist/deploy/agentcore/run.js +93 -70
  145. package/dist/deploy/agentcore/runtime-secrets.d.ts +7 -0
  146. package/dist/deploy/agentcore/runtime-secrets.js +108 -0
  147. package/dist/deploy/agentcore/shell.js +12 -19
  148. package/dist/deploy/agentcore/sigv4.d.ts +7 -0
  149. package/dist/deploy/agentcore/sigv4.js +15 -0
  150. package/dist/deploy/agentcore/slr.d.ts +18 -0
  151. package/dist/deploy/agentcore/slr.js +27 -0
  152. package/dist/deploy/agentcore/store.d.ts +7 -0
  153. package/dist/deploy/agentcore/store.js +115 -0
  154. package/dist/deploy/api.d.ts +14 -14
  155. package/dist/deploy/api.js +26 -24
  156. package/dist/deploy/box-shell.d.ts +3 -1
  157. package/dist/deploy/build-context.d.ts +4 -4
  158. package/dist/deploy/build-context.js +11 -15
  159. package/dist/deploy/channel-ingress.d.ts +35 -9
  160. package/dist/deploy/channel-ingress.js +119 -22
  161. package/dist/deploy/container.d.ts +35 -10
  162. package/dist/deploy/container.js +79 -37
  163. package/dist/deploy/delivered-text.d.ts +13 -0
  164. package/dist/deploy/delivered-text.js +86 -0
  165. package/dist/deploy/docker/plan.d.ts +7 -8
  166. package/dist/deploy/docker/plan.js +31 -52
  167. package/dist/deploy/docker/run.d.ts +5 -43
  168. package/dist/deploy/docker/run.js +9 -84
  169. package/dist/deploy/file-store.d.ts +2 -0
  170. package/dist/deploy/file-store.js +17 -0
  171. package/dist/deploy/fly/plan.d.ts +3 -2
  172. package/dist/deploy/fly/plan.js +20 -9
  173. package/dist/deploy/fly/run.d.ts +18 -8
  174. package/dist/deploy/fly/run.js +24 -42
  175. package/dist/deploy/fly/store.d.ts +12 -0
  176. package/dist/deploy/fly/store.js +83 -0
  177. package/dist/deploy/hosts.d.ts +1 -2
  178. package/dist/deploy/hosts.js +1 -2
  179. package/dist/deploy/preflight.d.ts +18 -5
  180. package/dist/deploy/preflight.js +135 -63
  181. package/dist/deploy/railway/ignore.d.ts +8 -0
  182. package/dist/deploy/railway/ignore.js +57 -0
  183. package/dist/deploy/railway/plan.d.ts +1 -2
  184. package/dist/deploy/railway/plan.js +8 -6
  185. package/dist/deploy/railway/run.d.ts +21 -15
  186. package/dist/deploy/railway/run.js +32 -78
  187. package/dist/deploy/railway/store.d.ts +29 -0
  188. package/dist/deploy/railway/store.js +143 -0
  189. package/dist/deploy/runner.d.ts +5 -2
  190. package/dist/deploy/runner.js +4 -3
  191. package/dist/deploy/secret-store.d.ts +63 -0
  192. package/dist/deploy/secret-store.js +98 -0
  193. package/dist/deploy/secrets.d.ts +29 -46
  194. package/dist/deploy/secrets.js +49 -122
  195. package/dist/deploy/workspace.d.ts +6 -12
  196. package/dist/deploy/workspace.js +18 -94
  197. package/dist/dev-supervisor.js +6 -8
  198. package/dist/environment/declare.js +3 -2
  199. package/dist/environment/mise.d.ts +3 -1
  200. package/dist/environment/mise.js +4 -2
  201. package/dist/feishu.d.ts +1 -1
  202. package/dist/feishu.js +1 -1
  203. package/dist/harnesses/models/catalog.d.ts +1 -0
  204. package/dist/harnesses/models/catalog.js +1 -2
  205. package/dist/harnesses/models/environment.d.ts +7 -2
  206. package/dist/harnesses/models/environment.js +9 -5
  207. package/dist/harnesses/models/host.d.ts +0 -7
  208. package/dist/harnesses/models/host.js +10 -1
  209. package/dist/harnesses/models/login.d.ts +0 -8
  210. package/dist/harnesses/models/login.js +5 -4
  211. package/dist/harnesses/models/models.d.ts +7 -2
  212. package/dist/harnesses/models/models.js +28 -4
  213. package/dist/harnesses/pi-durable/agent.js +59 -38
  214. package/dist/harnesses/pi-durable/build.d.ts +12 -0
  215. package/dist/harnesses/pi-durable/build.js +270 -0
  216. package/dist/harnesses/pi-durable/built.d.ts +24 -0
  217. package/dist/harnesses/pi-durable/built.js +136 -0
  218. package/dist/harnesses/pi-durable/config.d.ts +81 -0
  219. package/dist/harnesses/pi-durable/config.js +266 -0
  220. package/dist/harnesses/pi-durable/define.d.ts +63 -13
  221. package/dist/harnesses/pi-durable/define.js +23 -2
  222. package/dist/harnesses/pi-durable/definition.d.ts +72 -0
  223. package/dist/harnesses/pi-durable/definition.js +143 -0
  224. package/dist/harnesses/pi-durable/events.d.ts +4 -3
  225. package/dist/harnesses/pi-durable/events.js +179 -136
  226. package/dist/harnesses/pi-durable/execution-env.d.ts +17 -0
  227. package/dist/harnesses/pi-durable/execution-env.js +11 -0
  228. package/dist/harnesses/pi-durable/mcp.js +1 -3
  229. package/dist/harnesses/pi-durable/open.d.ts +35 -37
  230. package/dist/harnesses/pi-durable/open.js +91 -153
  231. package/dist/harnesses/pi-durable/reads.d.ts +0 -1
  232. package/dist/harnesses/pi-durable/reads.js +1 -3
  233. package/dist/harnesses/pi-durable/registry.d.ts +4 -6
  234. package/dist/harnesses/pi-durable/registry.js +29 -26
  235. package/dist/harnesses/pi-durable/release.d.ts +2 -2
  236. package/dist/harnesses/pi-durable/release.js +10 -7
  237. package/dist/harnesses/pi-durable/serve.d.ts +91 -15
  238. package/dist/harnesses/pi-durable/serve.js +109 -47
  239. package/dist/harnesses/pi-durable/sessions.js +15 -12
  240. package/dist/harnesses/pi-durable/text-sources.d.ts +30 -0
  241. package/dist/harnesses/pi-durable/text-sources.js +55 -0
  242. package/dist/harnesses/pi-durable/text.d.ts +16 -4
  243. package/dist/harnesses/pi-durable/text.js +115 -59
  244. package/dist/harnesses/pi-durable/wake.js +8 -8
  245. package/dist/http/client.js +9 -3
  246. package/dist/http/contract.d.ts +3 -3
  247. package/dist/http/server.d.ts +1 -1
  248. package/dist/http/server.js +1 -1
  249. package/dist/index.d.ts +8 -5
  250. package/dist/index.js +4 -1
  251. package/dist/instance-name.d.ts +7 -0
  252. package/dist/instance-name.js +26 -0
  253. package/dist/lark.d.ts +1 -1
  254. package/dist/lark.js +1 -1
  255. package/dist/mcp/client.js +11 -2
  256. package/dist/mcp/config.d.ts +70 -18
  257. package/dist/mcp/config.js +136 -53
  258. package/dist/mcp/host.d.ts +5 -2
  259. package/dist/mcp/host.js +14 -6
  260. package/dist/mcp/sign-in.js +2 -2
  261. package/dist/mcp-api.d.ts +14 -0
  262. package/dist/paths.d.ts +22 -8
  263. package/dist/paths.js +44 -11
  264. package/dist/runtime-websocket.d.ts +14 -0
  265. package/dist/runtime-websocket.js +39 -0
  266. package/dist/scaffold/add-channel.d.ts +9 -16
  267. package/dist/scaffold/add-channel.js +13 -83
  268. package/dist/scaffold/templates/env.example +4 -5
  269. package/dist/scaffold/templates/fastagent.config.ts +20 -14
  270. package/dist/scaffold/templates/gitignore +9 -6
  271. package/dist/scaffold/templates.d.ts +6 -2
  272. package/dist/scaffold/templates.js +7 -2
  273. package/dist/scaffold/vendor-skill.js +4 -2
  274. package/dist/service.d.ts +44 -6
  275. package/dist/service.js +110 -10
  276. package/dist/serving-command.d.ts +10 -0
  277. package/dist/serving-command.js +1 -0
  278. package/dist/slack.d.ts +1 -1
  279. package/dist/slack.js +1 -1
  280. package/dist/strip-edges.d.ts +10 -0
  281. package/dist/strip-edges.js +22 -0
  282. package/dist/telegram.d.ts +1 -1
  283. package/dist/telegram.js +1 -1
  284. package/dist/tunnel.d.ts +0 -1
  285. package/dist/tunnel.js +2 -8
  286. package/dist/value-file.d.ts +11 -6
  287. package/dist/value-file.js +21 -9
  288. package/dist/version.d.ts +7 -1
  289. package/dist/version.js +9 -3
  290. package/package.json +6 -5
  291. package/dist/channels/lark/credentials-page.d.ts +0 -25
  292. package/dist/channels/lark/credentials-page.js +0 -206
  293. package/dist/channels/lark/onboard.d.ts +0 -52
  294. package/dist/channels/lark/onboard.js +0 -86
  295. package/dist/channels/slack/config-api.d.ts +0 -69
  296. package/dist/channels/slack/config-api.js +0 -162
  297. package/dist/channels/slack/onboard.d.ts +0 -40
  298. package/dist/channels/slack/onboard.js +0 -136
  299. package/dist/channels/slack/onboarding-state.d.ts +0 -31
  300. package/dist/channels/slack/onboarding-state.js +0 -64
  301. package/dist/channels/slack/register-webhook.d.ts +0 -12
  302. package/dist/channels/slack/register-webhook.js +0 -67
  303. package/dist/channels/slack/setup-server.d.ts +0 -32
  304. package/dist/channels/slack/setup-server.js +0 -246
  305. package/dist/cli/add-feishu.d.ts +0 -41
  306. package/dist/cli/add-feishu.js +0 -391
  307. package/dist/cli/add-slack.d.ts +0 -18
  308. package/dist/cli/add-slack.js +0 -216
  309. package/dist/harnesses/pi-durable/parked.d.ts +0 -18
  310. package/dist/harnesses/pi-durable/parked.js +0 -27
@@ -1,32 +1,43 @@
1
1
  import { type DeclaredEnvironment } from "../environment/declare.ts";
2
- import { type BoundContent, type ContentBinding, type ContentDeclaration, type ContentEntry, type DeclaredContent } from "./declare.ts";
2
+ import { type ContentEntry, type DeclaredContent } from "./declare.ts";
3
3
  /** `context.json` as read: the content entries as written and as declared, and the environment. */
4
4
  export interface ContextFile {
5
5
  $schema?: string;
6
6
  content: Record<string, ContentEntry>;
7
7
  declared: DeclaredContent[];
8
8
  environment?: DeclaredEnvironment;
9
- }
10
- /** `context.local.json` as read: the bindings as written and as declared. */
11
- export interface LocalContextFile {
12
- content: Record<string, ContentBinding>;
13
- bound: BoundContent[];
9
+ /** The program's MCP servers as written. Their shape is the MCP reader's to check; an edit keeps them as they are. */
10
+ mcpServers?: Record<string, unknown>;
14
11
  }
15
12
  /** Read `text`, the contents of `path`, whole: what an edit, which writes the whole, reads. */
16
13
  export declare function parseContextFile(text: string, path: string): ContextFile;
17
- /** Read `text`, the contents of `path`, whole, beside the entries `context.json` declares. */
18
- export declare function parseLocalContextFile(text: string, path: string, declared: readonly DeclaredContent[], agentDir: string): LocalContextFile;
19
14
  /** The agent's `context.json`, read whole. */
20
15
  export declare function readContextFile(agentDir: string): ContextFile;
21
- /** The content of the agent in `agentDir`: what `context.json` declares, and what this machine binds. */
22
- export declare function loadContent(agentDir: string): ContentDeclaration;
16
+ /** The instance configuration's content, as far as this reads it: its `content` map and where it is from. */
17
+ export interface InstanceContent {
18
+ values: unknown;
19
+ label: string;
20
+ /** The configuration file's directory, which a relative `path` is relative to. */
21
+ directory?: string;
22
+ }
23
+ /**
24
+ * The content of the agent whose program is in `agentDir`: what its `context.json` declares, and what `instance` adds
25
+ * to it (an instance only adds: a name both declare is refused).
26
+ */
27
+ export declare function loadContent(agentDir: string, instance?: InstanceContent): DeclaredContent[];
28
+ /** What an instance configuration's `content` declares. */
29
+ export declare function instanceContent({ values, label, directory }: InstanceContent): DeclaredContent[];
23
30
  /** The environment the agent in `agentDir` declares, or undefined when it declares none. */
24
31
  export declare function loadEnvironment(agentDir: string): DeclaredEnvironment | undefined;
32
+ /** The program's `mcpServers` map as written in `context.json`, and the file's path, which names it in a refusal. */
33
+ export declare function readProgramMcpServers(agentDir: string): {
34
+ values: unknown;
35
+ path: string;
36
+ };
25
37
  /** The text `context.json` is written as. An environment that declares nothing is left out. */
26
38
  export declare function contextFileText(file: {
27
39
  $schema?: string;
28
40
  content: Record<string, ContentEntry>;
29
41
  environment?: DeclaredEnvironment;
42
+ mcpServers?: Record<string, unknown>;
30
43
  }): string;
31
- /** The text `context.local.json` is written as. */
32
- export declare function localContextFileText(content: Record<string, ContentBinding>): string;
@@ -1,19 +1,19 @@
1
1
  /**
2
- * The agent's `context.json` and this machine's `context.local.json`, read in ONE place, so the opener, `info`,
3
- * `content list`, `env`, `tool` and deploy read one declaration; and the one shape each is written in. A reader
4
- * declares the part it uses (its content, or its environment), so a part that is refused stops what uses it and is
5
- * reported where it is, not as the other's; an edit declares the whole, since it writes the whole. JSON, like
6
- * `mcp.json` and `package.json`, so a command, a client or the agent itself can edit it without touching TypeScript.
2
+ * The program's `context.json` and the content an instance's configuration adds, read in ONE place, so the opener,
3
+ * `info`, `content list`, `env`, `tool` and deploy read one declaration; and the one shape `context.json` is written
4
+ * in. A reader declares the part it uses (its content, or its environment), so a part that is refused stops what uses
5
+ * it and is reported where it is, not as the other's; an edit declares the whole, since it writes the whole. JSON,
6
+ * like `mcp.json` and `package.json`, so a command, a client or the agent itself can edit it without touching
7
+ * TypeScript.
7
8
  */
8
9
  import { readFileSync } from "node:fs";
9
10
  import { join, resolve } from "node:path";
10
- import { CONTEXT_FILE, LOCAL_CONTEXT_FILE } from "../paths.js";
11
+ import { CONTEXT_FILE } from "../paths.js";
11
12
  import { declareEnvironment } from "../environment/declare.js";
12
- import { declareBindings, declareContent, } from "./declare.js";
13
- const FILE_KEYS = ["$schema", "content", "environment"];
14
- const LOCAL_FILE_KEYS = ["content"];
13
+ import { declareContent, joinContent } from "./declare.js";
14
+ const FILE_KEYS = ["$schema", "content", "environment", "mcpServers"];
15
15
  /** `text`, the contents of `path`, as the object a context file is: refused when the file itself is wrong. */
16
- function parseFile(text, path, keys = FILE_KEYS) {
16
+ function parseFile(text, path) {
17
17
  let raw;
18
18
  try {
19
19
  raw = JSON.parse(text);
@@ -25,12 +25,17 @@ function parseFile(text, path, keys = FILE_KEYS) {
25
25
  throw new Error(`${path} must hold a JSON object`);
26
26
  const file = raw;
27
27
  for (const key of Object.keys(file)) {
28
- if (!keys.includes(key))
29
- throw new Error(`${path}: unknown key "${key}" (valid keys: ${keys.join(", ")})`);
28
+ if (!FILE_KEYS.includes(key)) {
29
+ throw new Error(`${path}: unknown key "${key}" (valid keys: ${FILE_KEYS.join(", ")})`);
30
+ }
30
31
  }
31
32
  if (file.$schema !== undefined && typeof file.$schema !== "string") {
32
33
  throw new Error(`${path}: "$schema" must be a string`);
33
34
  }
35
+ if (file.mcpServers !== undefined &&
36
+ (typeof file.mcpServers !== "object" || file.mcpServers === null || Array.isArray(file.mcpServers))) {
37
+ throw new Error(`${path}: "mcpServers" must be an object of servers by name`);
38
+ }
34
39
  return file;
35
40
  }
36
41
  /** One part of `context.json` at `path`, declared by `declare`; its refusal names the file. */
@@ -45,24 +50,19 @@ function declarePart(path, declare) {
45
50
  /** Read `text`, the contents of `path`, whole: what an edit, which writes the whole, reads. */
46
51
  export function parseContextFile(text, path) {
47
52
  const file = parseFile(text, path);
48
- const declared = declarePart(path, () => declareContent(file.content));
53
+ const declared = declarePart(path, () => declareContent(file.content, { origin: "program", file: CONTEXT_FILE }));
49
54
  const environment = declarePart(path, () => declareEnvironment(file.environment));
50
55
  return {
51
56
  ...(file.$schema !== undefined ? { $schema: file.$schema } : {}),
52
57
  content: (file.content ?? {}),
53
58
  declared,
54
59
  ...(environment !== undefined ? { environment } : {}),
60
+ ...(file.mcpServers !== undefined ? { mcpServers: file.mcpServers } : {}),
55
61
  };
56
62
  }
57
- /** Read `text`, the contents of `path`, whole, beside the entries `context.json` declares. */
58
- export function parseLocalContextFile(text, path, declared, agentDir) {
59
- const file = parseFile(text, path, LOCAL_FILE_KEYS);
60
- const bound = declarePart(path, () => declareBindings(file.content, declared, resolve(agentDir)));
61
- return { content: (file.content ?? {}), bound };
62
- }
63
- /** The agent's context file `name` and its path, or undefined text when it has none (it declares nothing). */
64
- function contextFileAt(agentDir, name = CONTEXT_FILE) {
65
- const path = join(resolve(agentDir), name);
63
+ /** The agent's `context.json` and its path, or undefined text when it has none (it declares nothing). */
64
+ function contextFileAt(agentDir) {
65
+ const path = join(resolve(agentDir), CONTEXT_FILE);
66
66
  try {
67
67
  return { path, text: readFileSync(path, "utf8") };
68
68
  }
@@ -77,16 +77,25 @@ export function readContextFile(agentDir) {
77
77
  const { path, text } = contextFileAt(agentDir);
78
78
  return text === undefined ? { content: {}, declared: [] } : parseContextFile(text, path);
79
79
  }
80
- /** This machine's `context.local.json` for the agent in `agentDir`, read whole, beside what it `declared`. */
81
- function readLocalContextFile(agentDir, declared) {
82
- const { path, text } = contextFileAt(agentDir, LOCAL_CONTEXT_FILE);
83
- return text === undefined ? { content: {}, bound: [] } : parseLocalContextFile(text, path, declared, agentDir);
84
- }
85
- /** The content of the agent in `agentDir`: what `context.json` declares, and what this machine binds. */
86
- export function loadContent(agentDir) {
80
+ /**
81
+ * The content of the agent whose program is in `agentDir`: what its `context.json` declares, and what `instance` adds
82
+ * to it (an instance only adds: a name both declare is refused).
83
+ */
84
+ export function loadContent(agentDir, instance) {
87
85
  const { path, text } = contextFileAt(agentDir);
88
- const declared = text === undefined ? [] : declarePart(path, () => declareContent(parseFile(text, path).content));
89
- return { declared, bound: readLocalContextFile(agentDir, declared).bound };
86
+ const program = text === undefined
87
+ ? []
88
+ : declarePart(path, () => declareContent(parseFile(text, path).content, { origin: "program", file: CONTEXT_FILE }));
89
+ return joinContent(program, instance === undefined ? [] : instanceContent(instance));
90
+ }
91
+ /** What an instance configuration's `content` declares. */
92
+ export function instanceContent({ values, label, directory }) {
93
+ const content = typeof values === "object" && values !== null ? values.content : undefined;
94
+ return declarePart(label, () => declareContent(content, {
95
+ origin: "instance",
96
+ file: label,
97
+ ...(directory === undefined ? {} : { base: directory }),
98
+ }));
90
99
  }
91
100
  /** The environment the agent in `agentDir` declares, or undefined when it declares none. */
92
101
  export function loadEnvironment(agentDir) {
@@ -96,6 +105,11 @@ export function loadEnvironment(agentDir) {
96
105
  const file = parseFile(text, path);
97
106
  return declarePart(path, () => declareEnvironment(file.environment));
98
107
  }
108
+ /** The program's `mcpServers` map as written in `context.json`, and the file's path, which names it in a refusal. */
109
+ export function readProgramMcpServers(agentDir) {
110
+ const { path, text } = contextFileAt(agentDir);
111
+ return { values: text === undefined ? undefined : parseFile(text, path).mcpServers, path };
112
+ }
99
113
  /** The text `context.json` is written as. An environment that declares nothing is left out. */
100
114
  export function contextFileText(file) {
101
115
  const packages = file.environment?.packages ?? {};
@@ -110,10 +124,9 @@ export function contextFileText(file) {
110
124
  ...(file.$schema !== undefined ? { $schema: file.$schema } : {}),
111
125
  content: file.content,
112
126
  ...(Object.keys(environment).length > 0 ? { environment } : {}),
127
+ ...(file.mcpServers !== undefined && Object.keys(file.mcpServers).length > 0
128
+ ? { mcpServers: file.mcpServers }
129
+ : {}),
113
130
  };
114
131
  return `${JSON.stringify(out, null, 2)}\n`;
115
132
  }
116
- /** The text `context.local.json` is written as. */
117
- export function localContextFileText(content) {
118
- return `${JSON.stringify({ content }, null, 2)}\n`;
119
- }
@@ -8,16 +8,6 @@ export declare function checkoutOf(dir: string): {
8
8
  root: string;
9
9
  origin?: string;
10
10
  } | undefined;
11
- /**
12
- * Why the directory `local` cannot stand for github `repo` on this machine, or undefined when it can: it is the root
13
- * of a checkout whose `origin` is that repository.
14
- */
15
- export declare function checkoutProblem(local: string, repo: string): string | undefined;
16
- /**
17
- * What to say when the checkout `dir` is not at `ref`, or undefined when it is: on that branch, or at the commit `ref`
18
- * names. FastAgent never moves a checkout the user owns, so this is said, not acted on.
19
- */
20
- export declare function refNotice(dir: string, ref: string): string | undefined;
21
11
  /**
22
12
  * What a start did with a clone: made it, moved it to what the remote has now, found it there already, or kept it as
23
13
  * it is, for the `reason` given (git refused to touch the agent's work, or the remote could not be reached).
@@ -3,11 +3,11 @@
3
3
  * `ref`, and a clone, made or brought up to date in place. git's own configuration applies throughout (credential helpers, `url.<base>.insteadOf`), so
4
4
  * a private repository is reached the way the user's own `git clone` reaches it.
5
5
  */
6
- import { existsSync, mkdirSync, realpathSync, renameSync, rmSync, statSync } from "node:fs";
6
+ import { existsSync, mkdirSync, renameSync, rmSync, statSync } from "node:fs";
7
7
  import { basename, dirname, join } from "node:path";
8
8
  import { gitFor } from "../git.js";
9
9
  const WHY = "github content needs it on this machine";
10
- const { answer: gitAnswer } = gitFor(WHY);
10
+ const { answer: checkoutAnswer } = gitFor(WHY);
11
11
  /** The `owner/repo` a GitHub remote URL names, in any of its spellings (https, ssh, scp-like), or undefined. */
12
12
  export function githubRepoOf(url) {
13
13
  const match = /^(?:(?:https?|git|ssh):\/\/(?:[^@/]+@)?github\.com\/|(?:[^@/:]+@)?github\.com:)([\w.-]+)\/([\w.-]+?)(?:\.git)?\/?$/i.exec(url.trim());
@@ -24,46 +24,12 @@ function githubUrl(repo) {
24
24
  export function checkoutOf(dir) {
25
25
  if (!existsSync(dir) || !statSync(dir).isDirectory())
26
26
  return undefined;
27
- const root = gitAnswer(["rev-parse", "--show-toplevel"], dir);
27
+ const root = checkoutAnswer(["rev-parse", "--show-toplevel"], dir);
28
28
  if (root === undefined)
29
29
  return undefined;
30
- const origin = gitAnswer(["config", "--get", "remote.origin.url"], root);
30
+ const origin = checkoutAnswer(["config", "--get", "remote.origin.url"], root);
31
31
  return { root, ...(origin ? { origin } : {}) };
32
32
  }
33
- /**
34
- * Why the directory `local` cannot stand for github `repo` on this machine, or undefined when it can: it is the root
35
- * of a checkout whose `origin` is that repository.
36
- */
37
- export function checkoutProblem(local, repo) {
38
- if (!existsSync(local))
39
- return "does not exist";
40
- const checkout = checkoutOf(local);
41
- if (checkout === undefined)
42
- return "is not a git checkout";
43
- if (realpathSync(checkout.root) !== realpathSync(local))
44
- return `is inside the checkout ${checkout.root}, not its root`;
45
- const of = checkout.origin === undefined ? undefined : githubRepoOf(checkout.origin);
46
- if (of?.toLowerCase() !== repo.toLowerCase()) {
47
- return `is a checkout of ${checkout.origin ?? "no origin"}, not of github ${repo}`;
48
- }
49
- return undefined;
50
- }
51
- /**
52
- * What to say when the checkout `dir` is not at `ref`, or undefined when it is: on that branch, or at the commit `ref`
53
- * names. FastAgent never moves a checkout the user owns, so this is said, not acted on.
54
- */
55
- export function refNotice(dir, ref) {
56
- const branch = gitAnswer(["symbolic-ref", "--quiet", "--short", "HEAD"], dir);
57
- if (branch === ref)
58
- return undefined;
59
- const head = gitAnswer(["rev-parse", "HEAD"], dir);
60
- const target = gitAnswer(["rev-parse", "--verify", "--quiet", "--end-of-options", `${ref}^{commit}`], dir);
61
- if (head !== undefined && head === target) {
62
- return undefined;
63
- }
64
- const at = branch ?? `a detached ${head?.slice(0, 7) ?? "HEAD"}`;
65
- return `the checkout is on ${at}, declared ${ref}; it is left as it is`;
66
- }
67
33
  /** A commit named in full: fetched by its hash, since `clone --branch` takes only a branch or a tag. */
68
34
  const FULL_COMMIT = /^[0-9a-f]{40}$/i;
69
35
  /**
@@ -97,7 +63,7 @@ const HELPER_KEY = "credential.https://github.com.helper";
97
63
  */
98
64
  export async function refreshClone(repo, ref, dir, env = process.env) {
99
65
  // The agent's environment, whose GITHUB_TOKEN the clone's credential helper reads.
100
- const { run: runGit } = gitFor(WHY, env);
66
+ const { run: runGit, answer: gitAnswer } = gitFor(WHY, env);
101
67
  // A library caller can hand this anything; git reads a leading "-" as an option, and no repository or ref has one
102
68
  // (a declaration with one is refused at load, declare.ts).
103
69
  if (repo.startsWith("-") || ref?.startsWith("-")) {
@@ -1,4 +1,4 @@
1
- import { type BoundContent, type ContentDeclaration, type DeclaredContent } from "./declare.ts";
1
+ import { type DeclaredContent } from "./declare.ts";
2
2
  import { type CloneOutcome } from "./git.ts";
3
3
  /** A content entry, resolved for this instance. */
4
4
  export type ResolvedContent = {
@@ -8,42 +8,38 @@ export type ResolvedContent = {
8
8
  description?: string;
9
9
  /** Where the agent reaches it on this instance, absolute. */
10
10
  location: string;
11
- /** What to tell the user about how it resolved: a checkout off its `ref`, a clone not made yet. */
11
+ /** What to tell the user about how it resolved: a clone not made yet. */
12
12
  notices: string[];
13
13
  } & ({
14
- /** A directory only this machine has, bound in `context.local.json`. */
14
+ /** A directory of this machine, where the instance configuration says. */
15
15
  kind: "local";
16
16
  } | {
17
+ /** A clone fastagent makes at `content/<name>` and brings up to date at each start while it holds nothing of the agent's. */
17
18
  kind: "github";
18
19
  /** `owner/repo`. */
19
20
  repo: string;
20
21
  ref?: string;
21
- /**
22
- * A clone fastagent makes at `content/<name>` and brings up to date at each start while it holds nothing of the
23
- * agent's. False when this machine binds its own checkout, which is never touched.
24
- */
25
- clone: boolean;
26
22
  });
23
+ /** The directories an instance's content is kept apart from. */
24
+ export interface ContentPlaces {
25
+ /** The definition's own directory, which is released and read as it is. */
26
+ dir: string;
27
+ /** The instance's working directory, where clones are made. */
28
+ workdir: string;
29
+ }
27
30
  /** Resolve the agent's content for this instance. Refuses, naming the entry, what cannot be. */
28
- export declare function resolveContent(agentDir: string, { declared, bound }: ContentDeclaration): ResolvedContent[];
31
+ export declare function resolveContent(declared: readonly DeclaredContent[], places: ContentPlaces): ResolvedContent[];
29
32
  /**
30
- * The bindings of a directory only this machine has that point at nothing here: the directory was moved or removed.
31
- * Not an error, since the binding is this machine's alone; the agent is not told of it, and whoever opens the agent
32
- * says so.
33
+ * Whether the content `name` can be the directory `target` of this machine: one that exists, and is apart from the
34
+ * definition's directory, since a definition is released, content is kept, and one directory cannot be both. The
35
+ * working directory is the instance's own writable place, so a directory inside it is only a directory the agent
36
+ * already reaches. Refuses, naming the entry, what cannot be.
33
37
  */
34
- export declare function contentAbsentHere({ bound }: ContentDeclaration): BoundContent[];
35
- /** What is said of a binding {@link contentAbsentHere} returns, after the word "content". */
36
- export declare function absentNotice(binding: BoundContent): string;
37
- /**
38
- * Whether the content `name` can be bound to the directory `target` of this machine: a directory, apart from the agent
39
- * directory, and for a declared repository the root of a checkout of it. Returns what to tell the user (a checkout off
40
- * its `ref`); refuses, naming the entry, what cannot be.
41
- */
42
- export declare function checkBinding(agentDir: string, name: string, target: string, repository?: Pick<DeclaredContent, "repo" | "ref">): string[];
38
+ export declare function checkDirectory({ dir }: Pick<ContentPlaces, "dir">, name: string, target: string): void;
43
39
  /**
44
40
  * Make a clone real, or bring it up to date at its `ref` in place, by git's rules, which never overwrite what the
45
41
  * agent did in it (git.ts `refreshClone`): what git refuses, or a remote that cannot be reached, keeps it as it is,
46
- * with the reason. Only a process that runs the agent calls this. A checkout this machine binds is never cloned over.
42
+ * with the reason. Only a process that runs the agent calls this.
47
43
  */
48
44
  export declare function cloneContent(entry: Extract<ResolvedContent, {
49
45
  kind: "github";
@@ -1,83 +1,58 @@
1
1
  /**
2
2
  * WHERE each content entry is, for THIS instance: the one answer the prompt, the coding tools, authored tools, `info`
3
- * and `deploy` all read (docs/design/core.md §2). A repository `context.json` declares is fastagent's clone at
4
- * `content/<name>`, or this machine's own checkout of it where `context.local.json` binds one; a directory only this
5
- * machine has is where its binding says. Reads the disk (git included); never the network, never a write. Making a
6
- * clone real is `cloneContent`'s, and only a process that runs the agent asks for it.
3
+ * and `deploy` all read (docs/design/core.md §2). A repository is fastagent's clone at `content/<name>` in the
4
+ * instance's working directory; a directory is where the instance configuration says. Reads the disk (git included);
5
+ * never the network, never a write. Making a clone real is `cloneContent`'s, and only a process that runs the agent
6
+ * asks for it.
7
7
  */
8
8
  import { lstatSync, realpathSync, statSync } from "node:fs";
9
9
  import { basename, dirname, join } from "node:path";
10
- import { LOCAL_CONTEXT_FILE, contentEntryPath } from "../paths.js";
10
+ import { contentEntryPath } from "../paths.js";
11
11
  import { nestingError } from "./declare.js";
12
- import { checkoutProblem, refNotice, refreshClone } from "./git.js";
12
+ import { refreshClone } from "./git.js";
13
13
  import { ensureContentDir } from "./mount.js";
14
14
  /** Resolve the agent's content for this instance. Refuses, naming the entry, what cannot be. */
15
- export function resolveContent(agentDir, { declared, bound }) {
16
- const shared = declared.map((entry) => resolveRepository(agentDir, entry, bound.find((binding) => binding.kind === "checkout" && binding.name === entry.name)));
17
- const local = bound
18
- .filter((binding) => binding.kind === "local" && statOrMissing(binding.path) !== undefined)
19
- .map((binding) => resolveLocal(agentDir, binding));
20
- return [...shared, ...local];
15
+ export function resolveContent(declared, places) {
16
+ return declared.map((entry) => entry.kind === "github" ? resolveRepository(places, entry) : resolveLocal(places, entry));
21
17
  }
22
- /**
23
- * The bindings of a directory only this machine has that point at nothing here: the directory was moved or removed.
24
- * Not an error, since the binding is this machine's alone; the agent is not told of it, and whoever opens the agent
25
- * says so.
26
- */
27
- export function contentAbsentHere({ bound }) {
28
- return bound.filter((binding) => binding.kind === "local" && statOrMissing(binding.path) === undefined);
29
- }
30
- /** What is said of a binding {@link contentAbsentHere} returns, after the word "content". */
31
- export function absentNotice(binding) {
32
- return (`${binding.name}: bound in ${LOCAL_CONTEXT_FILE} to ${binding.path}, which does not exist here: the agent is ` +
33
- `not told of it`);
34
- }
35
- function resolveRepository(agentDir, entry, checkout) {
18
+ function resolveRepository({ workdir }, entry) {
36
19
  const { name, description } = entry;
37
- const repository = {
38
- name,
39
- ...(description !== undefined ? { description } : {}),
40
- kind: "github",
41
- repo: entry.repo,
42
- ...(entry.ref !== undefined ? { ref: entry.ref } : {}),
43
- };
44
- if (checkout !== undefined && statOrMissing(checkout.path) !== undefined) {
45
- const location = realpathSync(checkout.path);
46
- const notices = checkBinding(agentDir, name, location, entry);
47
- return { ...repository, location, notices, clone: false };
48
- }
49
- const location = contentEntryPath(agentDir, name);
20
+ const location = contentEntryPath(workdir, name);
50
21
  const found = lstatOrMissing(location);
51
22
  if (found?.isSymbolicLink()) {
52
23
  throw new Error(`content "${name}": content/${name} is a link, and content/ holds only the clones fastagent makes — remove ` +
53
- `the link, and bind a checkout of your own in ${LOCAL_CONTEXT_FILE} instead (fastagent content add <dir>)`);
24
+ `the link, and add the directory it points at as content of its own (fastagent content add <dir>)`);
54
25
  }
55
26
  if (found && !found.isDirectory())
56
27
  throw new Error(`content "${name}": ${location} is not a directory`);
57
- const notices = found ? [] : ["not cloned yet: it is cloned when the agent starts"];
58
- if (checkout !== undefined) {
59
- notices.unshift(`bound in ${LOCAL_CONTEXT_FILE} to ${checkout.path}, which does not exist here: the clone is used`);
60
- }
61
- return { ...repository, location, notices, clone: true };
62
- }
63
- function resolveLocal(agentDir, binding) {
64
- const { name, description } = binding;
65
- const location = realpathSync(binding.path);
66
- checkBinding(agentDir, name, location);
67
28
  return {
68
29
  name,
69
30
  ...(description !== undefined ? { description } : {}),
31
+ kind: "github",
32
+ repo: entry.repo,
33
+ ...(entry.ref !== undefined ? { ref: entry.ref } : {}),
70
34
  location,
71
- notices: [],
35
+ notices: found ? [] : ["not cloned yet: it is cloned when the agent starts"],
36
+ };
37
+ }
38
+ function resolveLocal(places, entry) {
39
+ const { name, description } = entry;
40
+ checkDirectory(places, name, entry.path);
41
+ return {
42
+ name,
43
+ ...(description !== undefined ? { description } : {}),
72
44
  kind: "local",
45
+ location: realpathSync(entry.path),
46
+ notices: [],
73
47
  };
74
48
  }
75
49
  /**
76
- * Whether the content `name` can be bound to the directory `target` of this machine: a directory, apart from the agent
77
- * directory, and for a declared repository the root of a checkout of it. Returns what to tell the user (a checkout off
78
- * its `ref`); refuses, naming the entry, what cannot be.
50
+ * Whether the content `name` can be the directory `target` of this machine: one that exists, and is apart from the
51
+ * definition's directory, since a definition is released, content is kept, and one directory cannot be both. The
52
+ * working directory is the instance's own writable place, so a directory inside it is only a directory the agent
53
+ * already reaches. Refuses, naming the entry, what cannot be.
79
54
  */
80
- export function checkBinding(agentDir, name, target, repository) {
55
+ export function checkDirectory({ dir }, name, target) {
81
56
  const stat = statOrMissing(target);
82
57
  if (!stat)
83
58
  throw new Error(`content "${name}": ${target} does not exist`);
@@ -85,25 +60,16 @@ export function checkBinding(agentDir, name, target, repository) {
85
60
  throw new Error(`content "${name}": ${target} is not a directory`);
86
61
  // A symlink can still put one inside the other, so ask of the real paths — including for an agent directory `init`
87
62
  // is about to create, which a symlinked ancestor still places.
88
- const nested = nestingError(realPathOf(agentDir), realpathSync(target), name);
63
+ const nested = nestingError(realPathOf(dir), realpathSync(target), name);
89
64
  if (nested)
90
65
  throw new Error(nested);
91
- if (repository === undefined)
92
- return [];
93
- const problem = checkoutProblem(target, repository.repo);
94
- if (problem)
95
- throw new Error(`content "${name}": ${target} ${problem}`);
96
- const offRef = repository.ref === undefined ? undefined : refNotice(target, repository.ref);
97
- return offRef ? [offRef] : [];
98
66
  }
99
67
  /**
100
68
  * Make a clone real, or bring it up to date at its `ref` in place, by git's rules, which never overwrite what the
101
69
  * agent did in it (git.ts `refreshClone`): what git refuses, or a remote that cannot be reached, keeps it as it is,
102
- * with the reason. Only a process that runs the agent calls this. A checkout this machine binds is never cloned over.
70
+ * with the reason. Only a process that runs the agent calls this.
103
71
  */
104
72
  export async function cloneContent(entry, env = process.env) {
105
- if (!entry.clone)
106
- throw new Error(`content "${entry.name}" is bound to the checkout ${entry.location}, not a clone`);
107
73
  ensureContentDir(dirname(entry.location));
108
74
  return refreshClone(entry.repo, entry.ref, entry.location, env).catch((error) => {
109
75
  throw new Error(`content "${entry.name}": ${error.message}`);
@@ -1,4 +1,4 @@
1
- import { type ContentBinding, type ContentEntry } from "./declare.ts";
1
+ import { type ContentEntry } from "./declare.ts";
2
2
  export interface SourceOptions {
3
3
  name?: string;
4
4
  /** A github entry's branch, tag or commit. */
@@ -6,19 +6,18 @@ export interface SourceOptions {
6
6
  description?: string;
7
7
  }
8
8
  /**
9
- * A content entry to add, by name: the repository `context.json` declares for every instance, this machine's binding
10
- * in `context.local.json`, or both (a checkout of the declared repository). At least one.
9
+ * A content entry to add, by name. It is written to the instance's `fastagent.json`, or, with `program`, to the
10
+ * program's `context.json`, which every instance of it reaches and so holds repositories only.
11
11
  */
12
12
  export interface ContentAddition {
13
13
  name: string;
14
- entry?: ContentEntry;
15
- binding?: ContentBinding;
14
+ entry: ContentEntry;
15
+ program?: boolean;
16
16
  }
17
17
  /**
18
18
  * The addition `source` asks for, with what to tell the user about how it was read. `github:owner/repo` is a
19
- * repository fastagent clones. The root of a checkout whose `origin` is on GitHub is that repository, declared for
20
- * every instance and bound to this checkout here. Any other directory, a subdirectory of such a checkout included, is
21
- * bound on this machine alone. The name defaults to the repository's or the directory's.
19
+ * repository fastagent clones. Any other source is a directory the agent works in as it is, a checkout of a GitHub
20
+ * repository included, which is then said. The name defaults to the repository's or the directory's.
22
21
  */
23
22
  export declare function readContentSource(source: string, cwd: string, options?: SourceOptions): {
24
23
  addition: ContentAddition;
@@ -1,52 +1,34 @@
1
1
  /**
2
2
  * What a command's `<source>` adds (`init --content`, `fastagent content add`; docs/design/agent-cli.md §3), read the
3
- * same way by both: `github:owner/repo`, the root of a GitHub checkout, or any other directory.
3
+ * same way by both: `github:owner/repo`, or a directory.
4
4
  */
5
- import { realpathSync } from "node:fs";
6
5
  import { basename, resolve } from "node:path";
7
6
  import { isGithubRepo } from "./declare.js";
8
7
  import { checkoutOf, githubRepoOf } from "./git.js";
9
8
  /**
10
9
  * The addition `source` asks for, with what to tell the user about how it was read. `github:owner/repo` is a
11
- * repository fastagent clones. The root of a checkout whose `origin` is on GitHub is that repository, declared for
12
- * every instance and bound to this checkout here. Any other directory, a subdirectory of such a checkout included, is
13
- * bound on this machine alone. The name defaults to the repository's or the directory's.
10
+ * repository fastagent clones. Any other source is a directory the agent works in as it is, a checkout of a GitHub
11
+ * repository included, which is then said. The name defaults to the repository's or the directory's.
14
12
  */
15
13
  export function readContentSource(source, cwd, options = {}) {
16
- const treated = {
17
- ...(options.description !== undefined ? { description: options.description } : {}),
18
- };
19
- const ref = options.ref !== undefined ? { ref: options.ref } : {};
14
+ const treated = options.description !== undefined ? { description: options.description } : {};
20
15
  const repoName = (repo) => repo.slice(repo.indexOf("/") + 1);
21
16
  if (source.startsWith("github:")) {
22
17
  const repo = source.slice("github:".length);
23
18
  if (!isGithubRepo(repo))
24
19
  throw new Error(`${source} names no repository: write github:owner/repo`);
20
+ const ref = options.ref !== undefined ? { ref: options.ref } : {};
25
21
  return {
26
22
  addition: { name: options.name ?? repoName(repo), entry: { github: repo, ...ref, ...treated } },
27
23
  notes: [],
28
24
  };
29
25
  }
26
+ if (options.ref !== undefined) {
27
+ throw new Error(`--ref applies to a repository, and ${resolve(cwd, source)} is a directory`);
28
+ }
30
29
  const dir = resolve(cwd, source);
31
30
  const checkout = checkoutOf(dir);
32
31
  const repo = checkout?.origin === undefined ? undefined : githubRepoOf(checkout.origin);
33
- const atRoot = checkout !== undefined && realpathSync(dir) === realpathSync(checkout.root);
34
- if (repo !== undefined && atRoot) {
35
- const root = checkout.root;
36
- return {
37
- addition: {
38
- name: options.name ?? repoName(repo),
39
- entry: { github: repo, ...ref, ...treated },
40
- binding: { path: root },
41
- },
42
- notes: [],
43
- };
44
- }
45
- if (options.ref !== undefined) {
46
- throw new Error(`--ref applies to a repository, and ${dir} is bound as a directory of this machine`);
47
- }
48
- const notes = repo !== undefined && !atRoot
49
- ? [`${dir} is in a checkout of github ${repo}: add github:${repo} for the whole repository`]
50
- : [];
51
- return { addition: { name: options.name ?? basename(dir), binding: { path: dir, ...treated } }, notes };
32
+ const notes = repo === undefined ? [] : [`${dir} is in a checkout of github ${repo}: add github:${repo} to have it cloned`];
33
+ return { addition: { name: options.name ?? basename(dir), entry: { path: dir, ...treated } }, notes };
52
34
  }
@@ -0,0 +1,26 @@
1
+ import type { SetupGuide } from "./channels/setup.ts";
2
+ /** What the person pastes at one step: its fields are checked together. */
3
+ export interface CredentialsStep {
4
+ label: string;
5
+ guide: SetupGuide;
6
+ fields: readonly {
7
+ name: string;
8
+ label: string;
9
+ secret?: boolean;
10
+ }[];
11
+ }
12
+ export interface CredentialsPage {
13
+ url: string;
14
+ /** Settles once every value was accepted; rejects when `accept` failed for a reason other than a refusal. */
15
+ done: Promise<void>;
16
+ close(): Promise<void>;
17
+ }
18
+ export declare function startCredentialsPage(input: {
19
+ /** "Connect Slack". */
20
+ title: string;
21
+ /** What the done page says: "Slack connected". */
22
+ doneHeading: string;
23
+ steps: readonly CredentialsStep[];
24
+ /** Check a step's values and keep them. Throws {@link SetupRefused} to stay on the form with the reason; anything else ends the page. */
25
+ accept(step: number, values: Record<string, string>): Promise<void>;
26
+ }): Promise<CredentialsPage>;