@alpacakit/agents-conventions 0.1.0-beta.36 → 0.1.0-beta.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.d.ts ADDED
@@ -0,0 +1,186 @@
1
+ /**
2
+ * `@alpacakit/agents-conventions/cli` — the `agent` command surface, as DATA.
3
+ *
4
+ * There is no `bin` here and no handler: this module owns the vocabulary (the
5
+ * command path, the flags, the projection into a procedure input, and the JSON
6
+ * a result must be) so that `alpacaloop agent login`, `promptopt agent login`
7
+ * and `semantic-trigger agent login` are the same command. Each product mounts
8
+ * `AGENT_CLI_SURFACE.node` in its own tree and binds handlers that call the
9
+ * procedures from the root export and print the result.
10
+ *
11
+ * `@alpacakit/channels` and `zod` are peers: importing the procedures must not
12
+ * drag a CLI framework in.
13
+ */
14
+ import { type EntryTreeNode, type OutputContract } from "@alpacakit/channels";
15
+ import type { AgentAddOutput, AgentCredentialAddOutput, AgentListOutput, AgentLoginOutput, AgentRemoveOutput } from "./cli-output.js";
16
+ export declare const AGENT_ENTRY_NAME: {
17
+ readonly list: "agent-list";
18
+ readonly login: "agent-login";
19
+ readonly credentialAdd: "agent-credential-add";
20
+ readonly add: "agent-add";
21
+ readonly remove: "agent-remove";
22
+ };
23
+ declare const listEndpoint: import("@alpacakit/channels").EntryTreeEndpoint<import("@alpacakit/channels").EntryDefinitionWithOutput<"agent-list", readonly [], {}, {
24
+ agentsHome: string;
25
+ entries: {
26
+ agentId: string;
27
+ agentConfigId: string;
28
+ kind: string;
29
+ source: "profile" | "ambient" | "configured";
30
+ profileId: string | null;
31
+ availability: {
32
+ status: "runnable";
33
+ } | {
34
+ status: "unavailable";
35
+ reason: "command_missing" | "authentication_required";
36
+ remediation: {
37
+ detail: string;
38
+ command: {
39
+ executable: string;
40
+ args: string[];
41
+ } | null;
42
+ } | null;
43
+ } | {
44
+ status: "indeterminate";
45
+ reason: "probe_timeout" | "probe_fault";
46
+ detail: string;
47
+ } | null;
48
+ }[];
49
+ }>>;
50
+ declare const loginEndpoint: import("@alpacakit/channels").EntryTreeEndpoint<import("@alpacakit/channels").EntryDefinitionWithOutput<"agent-login", readonly [import("@alpacakit/channels").ParameterDefinition<"profileId", string, import("@alpacakit/channels").RequiredParameterRequirement>, import("@alpacakit/channels").ParameterDefinition<"agentId", "claude" | "codex", import("@alpacakit/channels").RequiredParameterRequirement>, import("@alpacakit/channels").ParameterDefinition<"method", "oauth" | "device", import("@alpacakit/channels").OptionalWithDefaultParameterRequirement<"oauth">>], import("@alpacakit/channels").ParameterValues<readonly [import("@alpacakit/channels").ParameterDefinition<"profileId", string, import("@alpacakit/channels").RequiredParameterRequirement>, import("@alpacakit/channels").ParameterDefinition<"agentId", "claude" | "codex", import("@alpacakit/channels").RequiredParameterRequirement>, import("@alpacakit/channels").ParameterDefinition<"method", "oauth" | "device", import("@alpacakit/channels").OptionalWithDefaultParameterRequirement<"oauth">>]>, {
51
+ kind: "registered";
52
+ profile: {
53
+ id: string;
54
+ agentId: string;
55
+ material: {
56
+ kind: "home-dir";
57
+ path: string;
58
+ } | {
59
+ kind: "api-key";
60
+ secret: {
61
+ resolver: string;
62
+ id: string;
63
+ };
64
+ };
65
+ };
66
+ managedHome: string;
67
+ } | {
68
+ kind: "profile_exists";
69
+ profileId: string;
70
+ } | {
71
+ kind: "unknown_agent";
72
+ agentId: string;
73
+ known: string[];
74
+ } | {
75
+ kind: "unsupported";
76
+ agentId: string;
77
+ method: string;
78
+ supported: string[];
79
+ } | {
80
+ kind: "login_failed";
81
+ detail: string;
82
+ managedHome: string;
83
+ } | {
84
+ kind: "invalid_name";
85
+ name: string;
86
+ rule: string;
87
+ }>>;
88
+ declare const credentialAddEndpoint: import("@alpacakit/channels").EntryTreeEndpoint<import("@alpacakit/channels").EntryDefinitionWithOutput<"agent-credential-add", readonly [import("@alpacakit/channels").ParameterDefinition<"profileId", string, import("@alpacakit/channels").RequiredParameterRequirement>, import("@alpacakit/channels").ParameterDefinition<"agentId", string, import("@alpacakit/channels").RequiredParameterRequirement>, import("@alpacakit/channels").ParameterDefinition<"stdin", boolean, import("@alpacakit/channels").RequiredParameterRequirement>], import("@alpacakit/channels").ParameterValues<readonly [import("@alpacakit/channels").ParameterDefinition<"profileId", string, import("@alpacakit/channels").RequiredParameterRequirement>, import("@alpacakit/channels").ParameterDefinition<"agentId", string, import("@alpacakit/channels").RequiredParameterRequirement>, import("@alpacakit/channels").ParameterDefinition<"stdin", boolean, import("@alpacakit/channels").RequiredParameterRequirement>]>, {
89
+ kind: "invalid_name";
90
+ name: string;
91
+ rule: string;
92
+ } | {
93
+ kind: "registered";
94
+ profile: {
95
+ id: string;
96
+ agentId: string;
97
+ material: {
98
+ kind: "home-dir";
99
+ path: string;
100
+ } | {
101
+ kind: "api-key";
102
+ secret: {
103
+ resolver: string;
104
+ id: string;
105
+ };
106
+ };
107
+ };
108
+ } | {
109
+ kind: "profile_exists";
110
+ profileId: string;
111
+ } | {
112
+ kind: "empty_key";
113
+ profileId: string;
114
+ }>>;
115
+ declare const addEndpoint: import("@alpacakit/channels").EntryTreeEndpoint<import("@alpacakit/channels").EntryDefinitionWithOutput<"agent-add", readonly [import("@alpacakit/channels").ParameterDefinition<"id", string, import("@alpacakit/channels").RequiredParameterRequirement>, import("@alpacakit/channels").ParameterDefinition<"kind", "openai-compatible", import("@alpacakit/channels").RequiredParameterRequirement>, import("@alpacakit/channels").ParameterDefinition<"baseUrl", string, import("@alpacakit/channels").RequiredParameterRequirement>, import("@alpacakit/channels").ParameterDefinition<"model", string, import("@alpacakit/channels").RequiredParameterRequirement>, import("@alpacakit/channels").ParameterDefinition<"apiKeyEnv", string, import("@alpacakit/channels").OptionalParameterRequirement>, import("@alpacakit/channels").ParameterDefinition<"engine", "fetch" | "pi", import("@alpacakit/channels").OptionalParameterRequirement>], import("@alpacakit/channels").ParameterValues<readonly [import("@alpacakit/channels").ParameterDefinition<"id", string, import("@alpacakit/channels").RequiredParameterRequirement>, import("@alpacakit/channels").ParameterDefinition<"kind", "openai-compatible", import("@alpacakit/channels").RequiredParameterRequirement>, import("@alpacakit/channels").ParameterDefinition<"baseUrl", string, import("@alpacakit/channels").RequiredParameterRequirement>, import("@alpacakit/channels").ParameterDefinition<"model", string, import("@alpacakit/channels").RequiredParameterRequirement>, import("@alpacakit/channels").ParameterDefinition<"apiKeyEnv", string, import("@alpacakit/channels").OptionalParameterRequirement>, import("@alpacakit/channels").ParameterDefinition<"engine", "fetch" | "pi", import("@alpacakit/channels").OptionalParameterRequirement>]>, {
116
+ kind: "invalid_name";
117
+ name: string;
118
+ rule: string;
119
+ } | {
120
+ kind: "added";
121
+ agentConfig: {
122
+ id: string;
123
+ kind: string;
124
+ enabled: boolean;
125
+ payload: Record<string, import("@alpacakit/agents").JsonValue>;
126
+ };
127
+ } | {
128
+ kind: "exists";
129
+ id: string;
130
+ }>>;
131
+ declare const removeEndpoint: import("@alpacakit/channels").EntryTreeEndpoint<import("@alpacakit/channels").EntryDefinitionWithOutput<"agent-remove", readonly [import("@alpacakit/channels").ParameterDefinition<"name", string, import("@alpacakit/channels").RequiredParameterRequirement>], import("@alpacakit/channels").ParameterValues<readonly [import("@alpacakit/channels").ParameterDefinition<"name", string, import("@alpacakit/channels").RequiredParameterRequirement>]>, {
132
+ kind: "invalid_name";
133
+ name: string;
134
+ rule: string;
135
+ } | {
136
+ kind: "removed_profile";
137
+ profile: {
138
+ id: string;
139
+ agentId: string;
140
+ material: {
141
+ kind: "home-dir";
142
+ path: string;
143
+ } | {
144
+ kind: "api-key";
145
+ secret: {
146
+ resolver: string;
147
+ id: string;
148
+ };
149
+ };
150
+ };
151
+ removedManagedHome: string | null;
152
+ removedSecret: boolean;
153
+ } | {
154
+ kind: "removed_agent_config";
155
+ agentConfig: {
156
+ id: string;
157
+ kind: string;
158
+ enabled: boolean;
159
+ payload: Record<string, import("@alpacakit/agents").JsonValue>;
160
+ };
161
+ } | {
162
+ kind: "not_found";
163
+ name: string;
164
+ }>>;
165
+ export type AgentCliSurface = {
166
+ /** Mount this in the product's own entry tree. */
167
+ readonly node: EntryTreeNode;
168
+ readonly endpoints: {
169
+ readonly list: typeof listEndpoint;
170
+ readonly login: typeof loginEndpoint;
171
+ readonly credentialAdd: typeof credentialAddEndpoint;
172
+ readonly add: typeof addEndpoint;
173
+ readonly remove: typeof removeEndpoint;
174
+ };
175
+ /** The JSON each result must be, for products that emit machine output. */
176
+ readonly outputs: {
177
+ readonly list: OutputContract<AgentListOutput>;
178
+ readonly login: OutputContract<AgentLoginOutput>;
179
+ readonly credentialAdd: OutputContract<AgentCredentialAddOutput>;
180
+ readonly add: OutputContract<AgentAddOutput>;
181
+ readonly remove: OutputContract<AgentRemoveOutput>;
182
+ };
183
+ };
184
+ export declare const AGENT_CLI_SURFACE: AgentCliSurface;
185
+ export {};
186
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,OAAO,EAOL,KAAK,aAAa,EAClB,KAAK,cAAc,EAIpB,MAAM,qBAAqB,CAAC;AAO7B,OAAO,KAAK,EACV,cAAc,EACd,wBAAwB,EACxB,eAAe,EACf,gBAAgB,EAChB,iBAAiB,EAClB,MAAM,iBAAiB,CAAC;AAczB,eAAO,MAAM,gBAAgB;;;;;;CAMnB,CAAC;AAsMX,QAAA,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;GAGjB,CAAC;AAEF,QAAA,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GASlB,CAAC;AAEF,QAAA,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;GAe1B,CAAC;AAEF,QAAA,MAAM,WAAW;;;;;;;;;;;;;;;GAYhB,CAAC;AAEF,QAAA,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAOnB,CAAC;AA8CF,MAAM,MAAM,eAAe,GAAG;IAC5B,kDAAkD;IAClD,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE;QAClB,QAAQ,CAAC,IAAI,EAAE,OAAO,YAAY,CAAC;QACnC,QAAQ,CAAC,KAAK,EAAE,OAAO,aAAa,CAAC;QACrC,QAAQ,CAAC,aAAa,EAAE,OAAO,qBAAqB,CAAC;QACrD,QAAQ,CAAC,GAAG,EAAE,OAAO,WAAW,CAAC;QACjC,QAAQ,CAAC,MAAM,EAAE,OAAO,cAAc,CAAC;KACxC,CAAC;IACF,2EAA2E;IAC3E,QAAQ,CAAC,OAAO,EAAE;QAChB,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC,eAAe,CAAC,CAAC;QAC/C,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC,gBAAgB,CAAC,CAAC;QACjD,QAAQ,CAAC,aAAa,EAAE,cAAc,CAAC,wBAAwB,CAAC,CAAC;QACjE,QAAQ,CAAC,GAAG,EAAE,cAAc,CAAC,cAAc,CAAC,CAAC;QAC7C,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC,iBAAiB,CAAC,CAAC;KACpD,CAAC;CACH,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,eAgB/B,CAAC"}
package/dist/cli.js ADDED
@@ -0,0 +1,307 @@
1
+ /**
2
+ * `@alpacakit/agents-conventions/cli` — the `agent` command surface, as DATA.
3
+ *
4
+ * There is no `bin` here and no handler: this module owns the vocabulary (the
5
+ * command path, the flags, the projection into a procedure input, and the JSON
6
+ * a result must be) so that `alpacaloop agent login`, `promptopt agent login`
7
+ * and `semantic-trigger agent login` are the same command. Each product mounts
8
+ * `AGENT_CLI_SURFACE.node` in its own tree and binds handlers that call the
9
+ * procedures from the root export and print the result.
10
+ *
11
+ * `@alpacakit/channels` and `zod` are peers: importing the procedures must not
12
+ * drag a CLI framework in.
13
+ */
14
+ import { BUILTIN_AGENT_ID, LOGIN_METHOD } from "@alpacakit/agents";
15
+ import { OPENAI_COMPATIBLE_ENGINE } from "@alpacakit/agents/config";
16
+ import { codecs, createOutputContract, defineEntry, defineEntryEndpoint, defineEntryNode, defineParameter, optional, required, VALUE_SHAPE_KIND, } from "@alpacakit/channels";
17
+ import { bindCliEntry, CLI_BOOLEAN_MODE, overrideCliSegment, } from "@alpacakit/channels/cli";
18
+ import { z } from "zod";
19
+ import { AGENTS_HOME_RECORD_NAME_PATTERN, AGENTS_HOME_RECORD_NAME_RULE, } from "./names.js";
20
+ import { AGENT_ADD_KIND } from "./values.js";
21
+ import { agentAddOutputSchema, agentCredentialAddOutputSchema, agentListOutputSchema, agentLoginOutputSchema, agentRemoveOutputSchema, } from "./cli-output.js";
22
+ export const AGENT_ENTRY_NAME = {
23
+ list: "agent-list",
24
+ login: "agent-login",
25
+ credentialAdd: "agent-credential-add",
26
+ add: "agent-add",
27
+ remove: "agent-remove",
28
+ };
29
+ const AGENT_CLI_TOKEN = {
30
+ agent: "agent",
31
+ list: "list",
32
+ login: "login",
33
+ credential: "credential",
34
+ add: "add",
35
+ remove: "remove",
36
+ };
37
+ const LOGIN_AGENT_IDS = [
38
+ BUILTIN_AGENT_ID.claude,
39
+ BUILTIN_AGENT_ID.codex,
40
+ ];
41
+ const LOGIN_METHODS = [LOGIN_METHOD.oauth, LOGIN_METHOD.device];
42
+ const ENGINES = [
43
+ OPENAI_COMPATIBLE_ENGINE.fetch,
44
+ OPENAI_COMPATIBLE_ENGINE.pi,
45
+ ];
46
+ const ADD_KINDS = [AGENT_ADD_KIND.openaiCompatible];
47
+ const AGENT_CLI_DESCRIPTION = {
48
+ agent: "Shared agents home: list, sign in, add, and remove agents.",
49
+ credential: "Agent credentials.",
50
+ list: "List registered agents and their execution readiness.",
51
+ login: "Sign in to an agent and register the credential as a profile.",
52
+ credentialAdd: "Store an API key for an agent and register it as a profile.",
53
+ add: "Add an agent that speaks an OpenAI-compatible endpoint.",
54
+ remove: "Remove a profile or an added agent by name.",
55
+ };
56
+ const AGENT_CLI_PARAMETER_DESCRIPTION = {
57
+ profileId: "Profile name to create.",
58
+ loginAgentId: `Agent to sign in: ${LOGIN_AGENT_IDS.join(", ")}.`,
59
+ credentialAgentId: "Agent the key authenticates; a built-in id or an added agent id.",
60
+ method: `Sign-in method: ${LOGIN_METHODS.join(", ")}. Defaults to ${LOGIN_METHOD.oauth}.`,
61
+ stdin: "Read the API key from standard input.",
62
+ addId: "Agent name to create; also the agent id runs refer to.",
63
+ addKind: `Agent kind: ${ADD_KINDS.join(", ")}.`,
64
+ baseUrl: "OpenAI-compatible endpoint base URL.",
65
+ model: "Model the agent requests.",
66
+ apiKeyEnv: "Environment variable holding the API key; stored as a template, never the key itself.",
67
+ engine: `Execution engine: ${ENGINES.join(", ")}. Defaults to ${OPENAI_COMPATIBLE_ENGINE.fetch}.`,
68
+ removeName: "Profile name, or the name of an added agent.",
69
+ };
70
+ /**
71
+ * Record names reach the filesystem and the keychain, so the surface refuses
72
+ * anything outside the family grammar here rather than letting a handler
73
+ * discover it. The procedures repeat the check for callers that skip the CLI.
74
+ */
75
+ function recordNameCodec() {
76
+ return codecs.define({
77
+ schema: z
78
+ .string()
79
+ .regex(AGENTS_HOME_RECORD_NAME_PATTERN, AGENTS_HOME_RECORD_NAME_RULE),
80
+ shape: { kind: VALUE_SHAPE_KIND.string },
81
+ });
82
+ }
83
+ const AGENT_CLI_HELP = {
84
+ agent: {
85
+ examples: [
86
+ {
87
+ title: "Inspect the shared agents home",
88
+ useWhen: "checking which agents this machine can run",
89
+ command: "agent list",
90
+ },
91
+ ],
92
+ },
93
+ };
94
+ const listEntry = defineEntry({
95
+ name: AGENT_ENTRY_NAME.list,
96
+ parameters: [],
97
+ project: () => ({}),
98
+ output: createOutputContract(agentListOutputSchema),
99
+ });
100
+ const loginEntry = defineEntry({
101
+ name: AGENT_ENTRY_NAME.login,
102
+ parameters: [
103
+ defineParameter({
104
+ name: "profileId",
105
+ codec: recordNameCodec(),
106
+ requirement: required(),
107
+ description: AGENT_CLI_PARAMETER_DESCRIPTION.profileId,
108
+ }),
109
+ defineParameter({
110
+ name: "agentId",
111
+ codec: codecs.stringEnum(LOGIN_AGENT_IDS),
112
+ requirement: required(),
113
+ description: AGENT_CLI_PARAMETER_DESCRIPTION.loginAgentId,
114
+ }),
115
+ defineParameter({
116
+ name: "method",
117
+ codec: codecs.stringEnum(LOGIN_METHODS),
118
+ requirement: optional({ default: LOGIN_METHOD.oauth }),
119
+ description: AGENT_CLI_PARAMETER_DESCRIPTION.method,
120
+ }),
121
+ ],
122
+ project: ({ values }) => values,
123
+ output: createOutputContract(agentLoginOutputSchema),
124
+ });
125
+ const credentialAddEntry = defineEntry({
126
+ name: AGENT_ENTRY_NAME.credentialAdd,
127
+ parameters: [
128
+ defineParameter({
129
+ name: "profileId",
130
+ codec: recordNameCodec(),
131
+ requirement: required(),
132
+ description: AGENT_CLI_PARAMETER_DESCRIPTION.profileId,
133
+ }),
134
+ // Not an enum: `agent add` mints ids this command must be able to name.
135
+ defineParameter({
136
+ name: "agentId",
137
+ codec: codecs.string(),
138
+ requirement: required(),
139
+ description: AGENT_CLI_PARAMETER_DESCRIPTION.credentialAgentId,
140
+ }),
141
+ defineParameter({
142
+ // Required, not defaulted: stdin is the ONLY way in for the key, so
143
+ // `false` could never be a request this command can serve.
144
+ name: "stdin",
145
+ codec: codecs.boolean(),
146
+ requirement: required(),
147
+ description: AGENT_CLI_PARAMETER_DESCRIPTION.stdin,
148
+ }),
149
+ ],
150
+ project: ({ values }) => values,
151
+ output: createOutputContract(agentCredentialAddOutputSchema),
152
+ });
153
+ const addEntry = defineEntry({
154
+ name: AGENT_ENTRY_NAME.add,
155
+ parameters: [
156
+ defineParameter({
157
+ name: "id",
158
+ codec: recordNameCodec(),
159
+ requirement: required(),
160
+ description: AGENT_CLI_PARAMETER_DESCRIPTION.addId,
161
+ }),
162
+ defineParameter({
163
+ name: "kind",
164
+ codec: codecs.stringEnum(ADD_KINDS),
165
+ requirement: required(),
166
+ description: AGENT_CLI_PARAMETER_DESCRIPTION.addKind,
167
+ }),
168
+ defineParameter({
169
+ name: "baseUrl",
170
+ codec: codecs.string(),
171
+ requirement: required(),
172
+ description: AGENT_CLI_PARAMETER_DESCRIPTION.baseUrl,
173
+ }),
174
+ defineParameter({
175
+ name: "model",
176
+ codec: codecs.string(),
177
+ requirement: required(),
178
+ description: AGENT_CLI_PARAMETER_DESCRIPTION.model,
179
+ }),
180
+ defineParameter({
181
+ name: "apiKeyEnv",
182
+ codec: codecs.string(),
183
+ requirement: optional(),
184
+ description: AGENT_CLI_PARAMETER_DESCRIPTION.apiKeyEnv,
185
+ }),
186
+ defineParameter({
187
+ // Optional with NO default: omitting `--engine` must reach
188
+ // `addAgentConfig` as an absent field, so the agent-config payload
189
+ // stays the one place that decides what "unset" means.
190
+ name: "engine",
191
+ codec: codecs.stringEnum(ENGINES),
192
+ requirement: optional(),
193
+ description: AGENT_CLI_PARAMETER_DESCRIPTION.engine,
194
+ }),
195
+ ],
196
+ project: ({ values }) => values,
197
+ output: createOutputContract(agentAddOutputSchema),
198
+ });
199
+ const removeEntry = defineEntry({
200
+ name: AGENT_ENTRY_NAME.remove,
201
+ parameters: [
202
+ defineParameter({
203
+ name: "name",
204
+ codec: recordNameCodec(),
205
+ requirement: required(),
206
+ description: AGENT_CLI_PARAMETER_DESCRIPTION.removeName,
207
+ }),
208
+ ],
209
+ project: ({ values }) => values,
210
+ output: createOutputContract(agentRemoveOutputSchema),
211
+ });
212
+ const listEndpoint = defineEntryEndpoint(listEntry, bindCliEntry({ parameters: [] }));
213
+ const loginEndpoint = defineEntryEndpoint(loginEntry, bindCliEntry({
214
+ parameters: [
215
+ { kind: "positional", parameterName: "profileId", valueName: "PROFILE" },
216
+ { kind: "option", parameterName: "agentId", flags: ["--agent"] },
217
+ { kind: "option", parameterName: "method", flags: ["--method"] },
218
+ ],
219
+ }));
220
+ const credentialAddEndpoint = defineEntryEndpoint(credentialAddEntry, bindCliEntry({
221
+ parameters: [
222
+ { kind: "positional", parameterName: "profileId", valueName: "PROFILE" },
223
+ { kind: "option", parameterName: "agentId", flags: ["--agent"] },
224
+ {
225
+ kind: "option",
226
+ parameterName: "stdin",
227
+ flags: ["--stdin"],
228
+ // A required boolean would otherwise take a value (`--stdin true`).
229
+ booleanMode: CLI_BOOLEAN_MODE.flag,
230
+ },
231
+ ],
232
+ }));
233
+ const addEndpoint = defineEntryEndpoint(addEntry, bindCliEntry({
234
+ parameters: [
235
+ { kind: "positional", parameterName: "id", valueName: "ID" },
236
+ { kind: "option", parameterName: "kind", flags: ["--kind"] },
237
+ { kind: "option", parameterName: "baseUrl", flags: ["--base-url"] },
238
+ { kind: "option", parameterName: "model", flags: ["--model"] },
239
+ { kind: "option", parameterName: "apiKeyEnv", flags: ["--api-key-env"] },
240
+ { kind: "option", parameterName: "engine", flags: ["--engine"] },
241
+ ],
242
+ }));
243
+ const removeEndpoint = defineEntryEndpoint(removeEntry, bindCliEntry({
244
+ parameters: [
245
+ { kind: "positional", parameterName: "name", valueName: "NAME" },
246
+ ],
247
+ }));
248
+ const agentNode = defineEntryNode({
249
+ name: AGENT_CLI_TOKEN.agent,
250
+ description: AGENT_CLI_DESCRIPTION.agent,
251
+ segments: [
252
+ overrideCliSegment({
253
+ token: AGENT_CLI_TOKEN.agent,
254
+ help: AGENT_CLI_HELP.agent,
255
+ }),
256
+ ],
257
+ children: [
258
+ defineEntryNode({
259
+ name: AGENT_CLI_TOKEN.list,
260
+ description: AGENT_CLI_DESCRIPTION.list,
261
+ endpoints: [listEndpoint],
262
+ }),
263
+ defineEntryNode({
264
+ name: AGENT_CLI_TOKEN.login,
265
+ description: AGENT_CLI_DESCRIPTION.login,
266
+ endpoints: [loginEndpoint],
267
+ }),
268
+ defineEntryNode({
269
+ name: AGENT_CLI_TOKEN.credential,
270
+ description: AGENT_CLI_DESCRIPTION.credential,
271
+ children: [
272
+ defineEntryNode({
273
+ name: AGENT_CLI_TOKEN.add,
274
+ description: AGENT_CLI_DESCRIPTION.credentialAdd,
275
+ endpoints: [credentialAddEndpoint],
276
+ }),
277
+ ],
278
+ }),
279
+ defineEntryNode({
280
+ name: AGENT_CLI_TOKEN.add,
281
+ description: AGENT_CLI_DESCRIPTION.add,
282
+ endpoints: [addEndpoint],
283
+ }),
284
+ defineEntryNode({
285
+ name: AGENT_CLI_TOKEN.remove,
286
+ description: AGENT_CLI_DESCRIPTION.remove,
287
+ endpoints: [removeEndpoint],
288
+ }),
289
+ ],
290
+ });
291
+ export const AGENT_CLI_SURFACE = {
292
+ node: agentNode,
293
+ endpoints: {
294
+ list: listEndpoint,
295
+ login: loginEndpoint,
296
+ credentialAdd: credentialAddEndpoint,
297
+ add: addEndpoint,
298
+ remove: removeEndpoint,
299
+ },
300
+ outputs: {
301
+ list: listEntry.output,
302
+ login: loginEntry.output,
303
+ credentialAdd: credentialAddEntry.output,
304
+ add: addEntry.output,
305
+ remove: removeEntry.output,
306
+ },
307
+ };
package/dist/home.d.ts ADDED
@@ -0,0 +1,65 @@
1
+ /**
2
+ * The shared seam every family procedure opens: one `~/.alpaca/agents` home,
3
+ * one runtime bound to the caller's environment, and one keychain store.
4
+ * Nothing here reaches for ambient state — `homeDir`, `env`, ports and the
5
+ * keyring are all inbound, so a test drives the whole procedure against a
6
+ * temp directory and fakes.
7
+ */
8
+ import { type AgentAvailability, type AgentRuntime, type HostLocation, type Ports } from "@alpacakit/agents";
9
+ import { type AgentsConfig, type AgentsRuntimeConfig, type CandidateSource } from "@alpacakit/agents/config";
10
+ import { type KeyringSecretStore } from "@alpacakit/core/keyring";
11
+ import { type AgentsConfigOptionsInput } from "./paths.js";
12
+ /**
13
+ * What every procedure takes. `ports` and `keyring` exist so tests can drive
14
+ * the real code path without a subprocess or a real keychain; both default to
15
+ * the production implementations.
16
+ */
17
+ export type AgentsHomeInput = AgentsConfigOptionsInput & {
18
+ readonly ports?: Partial<Ports>;
19
+ readonly keyring?: KeyringSecretStore;
20
+ };
21
+ export type AgentsHomeEntry = {
22
+ readonly agentId: string;
23
+ readonly agentConfigId: string;
24
+ /** The agent config kind the entry comes from (`codex`, `openai-compatible`, …). */
25
+ readonly kind: string;
26
+ readonly source: CandidateSource;
27
+ readonly profileId: string | null;
28
+ /** `null` when the agent declares no availability probe (I3 `agent add` kinds). */
29
+ readonly availability: AgentAvailability | null;
30
+ };
31
+ export type AgentsHomeListing = {
32
+ readonly agentsHome: string;
33
+ readonly entries: readonly AgentsHomeEntry[];
34
+ };
35
+ export type OpenedAgentsHome = {
36
+ readonly agentsHome: string;
37
+ readonly config: AgentsConfig;
38
+ readonly location: HostLocation;
39
+ readonly keyring: KeyringSecretStore;
40
+ readonly ports: Partial<Ports>;
41
+ readonly baseEnv: Record<string, string>;
42
+ };
43
+ export declare function openAgentsHome(input: AgentsHomeInput): OpenedAgentsHome;
44
+ /**
45
+ * Every agent this machine can currently reach, in the home's own order, with
46
+ * the readiness each one actually has: a profile-backed candidate is probed
47
+ * with that profile's environment applied, exactly as `createFleet` applies it
48
+ * before a run — otherwise `agent list` would report the ambient login for a
49
+ * profile that has its own.
50
+ */
51
+ export declare function listAgentsHome(input: AgentsHomeInput): Promise<AgentsHomeListing>;
52
+ export type RuntimeFactory = {
53
+ /** The runtime bound to the caller's own environment. */
54
+ readonly ambient: AgentRuntime;
55
+ /** A runtime bound to the caller's environment plus one profile's patch. */
56
+ withEnvPatch(baseEnv: Record<string, string>): AgentRuntime;
57
+ readonly baseEnv: Record<string, string>;
58
+ };
59
+ /**
60
+ * Builds runtimes over the home's agents. The config's own secret resolvers
61
+ * are merged into `ports.secrets`, so an api-key profile resolves through the
62
+ * family keyring resolver rather than failing as an unknown resolver name.
63
+ */
64
+ export declare function createRuntimeFactory(home: OpenedAgentsHome, config: AgentsRuntimeConfig): RuntimeFactory;
65
+ //# sourceMappingURL=home.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"home.d.ts","sourceRoot":"","sources":["../src/home.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,KAAK,iBAAiB,EAGtB,KAAK,YAAY,EAIjB,KAAK,YAAY,EACjB,KAAK,KAAK,EAEX,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EAGrB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,KAAK,kBAAkB,EAExB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,KAAK,wBAAwB,EAG9B,MAAM,YAAY,CAAC;AAGpB;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,wBAAwB,GAAG;IACvD,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IAChC,QAAQ,CAAC,OAAO,CAAC,EAAE,kBAAkB,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,oFAAoF;IACpF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,mFAAmF;IACnF,QAAQ,CAAC,YAAY,EAAE,iBAAiB,GAAG,IAAI,CAAC;CACjD,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,SAAS,eAAe,EAAE,CAAC;CAC9C,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAChC,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC1C,CAAC;AAOF,wBAAgB,cAAc,CAAC,KAAK,EAAE,eAAe,GAAG,gBAAgB,CAUvE;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,iBAAiB,CAAC,CAgB5B;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,yDAAyD;IACzD,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,4EAA4E;IAC5E,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,YAAY,CAAC;IAC5D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC1C,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,gBAAgB,EACtB,MAAM,EAAE,mBAAmB,GAC1B,cAAc,CAahB"}
package/dist/home.js ADDED
@@ -0,0 +1,105 @@
1
+ /**
2
+ * The shared seam every family procedure opens: one `~/.alpaca/agents` home,
3
+ * one runtime bound to the caller's environment, and one keychain store.
4
+ * Nothing here reaches for ambient state — `homeDir`, `env`, ports and the
5
+ * keyring are all inbound, so a test drives the whole procedure against a
6
+ * temp directory and fakes.
7
+ */
8
+ import { AGENT_AVAILABILITY_STATUS, AGENT_INDETERMINATE_REASON, applyEnvPatch, createAgentRuntime, resolvePorts, } from "@alpacakit/agents";
9
+ import { openAgentsConfig, secretsForRuntime, } from "@alpacakit/agents/config";
10
+ import { keyringSecretStore, } from "@alpacakit/core/keyring";
11
+ import { resolveAgentsConfigOptions, resolveAgentsHome, } from "./paths.js";
12
+ import { AGENTS_KEYRING_SERVICE } from "./values.js";
13
+ const AVAILABILITY_DETAIL = {
14
+ profileEnvUnresolved: "Profile credentials could not be resolved, so availability was not probed.",
15
+ };
16
+ export function openAgentsHome(input) {
17
+ return {
18
+ agentsHome: resolveAgentsHome(input),
19
+ config: openAgentsConfig(resolveAgentsConfigOptions(input)),
20
+ location: { homeDir: input.homeDir },
21
+ keyring: input.keyring ?? keyringSecretStore({ service: AGENTS_KEYRING_SERVICE }),
22
+ ports: input.ports ?? {},
23
+ baseEnv: definedEnv(input.env),
24
+ };
25
+ }
26
+ /**
27
+ * Every agent this machine can currently reach, in the home's own order, with
28
+ * the readiness each one actually has: a profile-backed candidate is probed
29
+ * with that profile's environment applied, exactly as `createFleet` applies it
30
+ * before a run — otherwise `agent list` would report the ambient login for a
31
+ * profile that has its own.
32
+ */
33
+ export async function listAgentsHome(input) {
34
+ const home = openAgentsHome(input);
35
+ const config = await home.config.loadRuntimeConfig();
36
+ const runtimes = createRuntimeFactory(home, config);
37
+ const entries = [];
38
+ for (const { candidate, source } of config.candidates) {
39
+ entries.push({
40
+ agentId: candidate.agentId,
41
+ agentConfigId: source.agentConfigId,
42
+ kind: source.agentConfigKind,
43
+ source: source.kind,
44
+ profileId: source.profileId,
45
+ availability: await probeCandidate(runtimes, home.location, candidate),
46
+ });
47
+ }
48
+ return { agentsHome: home.agentsHome, entries };
49
+ }
50
+ /**
51
+ * Builds runtimes over the home's agents. The config's own secret resolvers
52
+ * are merged into `ports.secrets`, so an api-key profile resolves through the
53
+ * family keyring resolver rather than failing as an unknown resolver name.
54
+ */
55
+ export function createRuntimeFactory(home, config) {
56
+ const resolved = resolvePorts(home.ports);
57
+ const ports = {
58
+ ...resolved,
59
+ secrets: { ...resolved.secrets, ...secretsForRuntime(config) },
60
+ };
61
+ const withEnvPatch = (baseEnv) => createAgentRuntime({ agents: config.agents, baseEnv, ports });
62
+ return {
63
+ ambient: withEnvPatch(home.baseEnv),
64
+ withEnvPatch,
65
+ baseEnv: home.baseEnv,
66
+ };
67
+ }
68
+ async function probeCandidate(runtimes, location, candidate) {
69
+ const agent = runtimes.ambient.agent(candidate.agentId);
70
+ if (agent.availability === null) {
71
+ return null;
72
+ }
73
+ const { profile } = candidate;
74
+ if (profile === undefined) {
75
+ return await agent.availability.probe(location);
76
+ }
77
+ if (agent.auth === null) {
78
+ return null;
79
+ }
80
+ let scoped;
81
+ try {
82
+ scoped = runtimes.withEnvPatch(applyEnvPatch(runtimes.baseEnv, await agent.auth.toEnv(profile.material)));
83
+ }
84
+ catch {
85
+ // A credential we cannot read is not an answer about the agent: report
86
+ // that the probe could not run rather than failing the whole listing or
87
+ // silently reporting the ambient login instead of the profile's.
88
+ return {
89
+ status: AGENT_AVAILABILITY_STATUS.indeterminate,
90
+ reason: AGENT_INDETERMINATE_REASON.probeFault,
91
+ detail: AVAILABILITY_DETAIL.profileEnvUnresolved,
92
+ };
93
+ }
94
+ return ((await scoped.agent(candidate.agentId).availability?.probe(location)) ??
95
+ null);
96
+ }
97
+ function definedEnv(env) {
98
+ const defined = {};
99
+ for (const [name, value] of Object.entries(env)) {
100
+ if (value !== undefined) {
101
+ defined[name] = value;
102
+ }
103
+ }
104
+ return defined;
105
+ }