@skillcdn/console 0.1.24 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -7
- package/dist/api.d.ts +2 -2
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +2 -2
- package/dist/api.js.map +1 -1
- package/dist/cli/cli.d.ts +1 -1
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +248 -92
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/credentials.d.ts +5 -0
- package/dist/cli/credentials.d.ts.map +1 -1
- package/dist/cli/credentials.js +2 -0
- package/dist/cli/credentials.js.map +1 -1
- package/dist/cli/directory.d.ts +7 -0
- package/dist/cli/directory.d.ts.map +1 -1
- package/dist/cli/directory.js +7 -0
- package/dist/cli/directory.js.map +1 -1
- package/dist/cli/format.d.ts +13 -28
- package/dist/cli/format.d.ts.map +1 -1
- package/dist/cli/format.js +74 -274
- package/dist/cli/format.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +36 -19
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/pack.d.ts +46 -0
- package/dist/cli/pack.d.ts.map +1 -0
- package/dist/cli/pack.js +250 -0
- package/dist/cli/pack.js.map +1 -0
- package/dist/client.d.ts +26 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +14 -1
- package/dist/client.js.map +1 -1
- package/dist/compat.d.ts +1 -1
- package/dist/compat.js +1 -1
- package/dist/components/calendar.d.ts +5 -1
- package/dist/components/calendar.d.ts.map +1 -1
- package/dist/components/calendar.js +3 -3
- package/dist/components/calendar.js.map +1 -1
- package/dist/components/decision-list.d.ts +13 -1
- package/dist/components/decision-list.d.ts.map +1 -1
- package/dist/components/decision-list.js +74 -29
- package/dist/components/decision-list.js.map +1 -1
- package/dist/components/event-feed.d.ts.map +1 -1
- package/dist/components/event-feed.js +2 -0
- package/dist/components/event-feed.js.map +1 -1
- package/dist/components/pack-connect.d.ts +23 -0
- package/dist/components/pack-connect.d.ts.map +1 -0
- package/dist/components/pack-connect.js +86 -0
- package/dist/components/pack-connect.js.map +1 -0
- package/dist/components/pack-token.d.ts +27 -0
- package/dist/components/pack-token.d.ts.map +1 -0
- package/dist/components/pack-token.js +11 -0
- package/dist/components/pack-token.js.map +1 -0
- package/dist/components/paged.d.ts +24 -0
- package/dist/components/paged.d.ts.map +1 -1
- package/dist/components/paged.js +40 -4
- package/dist/components/paged.js.map +1 -1
- package/dist/components/projects.d.ts +13 -0
- package/dist/components/projects.d.ts.map +1 -1
- package/dist/components/projects.js +37 -5
- package/dist/components/projects.js.map +1 -1
- package/dist/components/skills.d.ts.map +1 -1
- package/dist/components/skills.js +8 -4
- package/dist/components/skills.js.map +1 -1
- package/dist/components/ui.d.ts +2 -0
- package/dist/components/ui.d.ts.map +1 -1
- package/dist/components/ui.js +7 -2
- package/dist/components/ui.js.map +1 -1
- package/dist/components/work.d.ts +3 -1
- package/dist/components/work.d.ts.map +1 -1
- package/dist/components/work.js +1 -1
- package/dist/components/work.js.map +1 -1
- package/dist/components/workspace.d.ts +46 -0
- package/dist/components/workspace.d.ts.map +1 -0
- package/dist/components/workspace.js +124 -0
- package/dist/components/workspace.js.map +1 -0
- package/dist/console.css +268 -5
- package/dist/console.d.ts.map +1 -1
- package/dist/console.js +62 -23
- package/dist/console.js.map +1 -1
- package/dist/data.d.ts +16 -1
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +64 -2
- package/dist/data.js.map +1 -1
- package/dist/dates.d.ts +12 -2
- package/dist/dates.d.ts.map +1 -1
- package/dist/dates.js +20 -2
- package/dist/dates.js.map +1 -1
- package/dist/i18n/en.d.ts +66 -0
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +80 -8
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/ko.d.ts.map +1 -1
- package/dist/i18n/ko.js +75 -7
- package/dist/i18n/ko.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/router.d.ts +10 -5
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +24 -9
- package/dist/router.js.map +1 -1
- package/dist/routes.d.ts +4 -0
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +4 -0
- package/dist/routes.js.map +1 -1
- package/dist/schemas.d.ts +500 -158
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +169 -165
- package/dist/schemas.js.map +1 -1
- package/dist/vocabulary.d.ts +19 -1
- package/dist/vocabulary.d.ts.map +1 -1
- package/dist/vocabulary.js +16 -0
- package/dist/vocabulary.js.map +1 -1
- package/docs/specs/cli.md +19 -14
- package/docs/specs/rest.md +34 -19
- package/package.json +2 -1
- package/skills/building-a-console/SKILL.md +5 -5
- package/skills/building-a-console/references/the-look-from-a-website.md +39 -0
- package/skills/deploying-a-console/SKILL.md +9 -8
- package/skills/working-the-board/SKILL.md +5 -5
- package/skills/working-the-board/references/setting-up.md +1 -1
- package/skills/working-the-board/references/terms.md +2 -0
- package/skills/working-the-board/references/the-sentence.md +1 -1
- package/src/api.ts +18 -7
- package/src/cli/cli.ts +345 -109
- package/src/cli/credentials.ts +7 -0
- package/src/cli/directory.ts +9 -0
- package/src/cli/format.ts +82 -341
- package/src/cli/help.ts +43 -19
- package/src/cli/pack.ts +356 -0
- package/src/client.ts +84 -0
- package/src/compat.ts +1 -1
- package/src/components/calendar.tsx +8 -2
- package/src/components/decision-list.tsx +227 -94
- package/src/components/event-feed.tsx +2 -0
- package/src/components/pack-connect.tsx +179 -0
- package/src/components/pack-token.tsx +74 -0
- package/src/components/paged.tsx +72 -3
- package/src/components/projects.tsx +108 -9
- package/src/components/skills.tsx +51 -33
- package/src/components/ui.tsx +16 -3
- package/src/components/work.tsx +4 -1
- package/src/components/workspace.tsx +298 -0
- package/src/console.tsx +176 -29
- package/src/data.ts +89 -2
- package/src/dates.ts +23 -2
- package/src/i18n/en.ts +94 -8
- package/src/i18n/ko.ts +84 -7
- package/src/index.ts +25 -1
- package/src/router.ts +37 -14
- package/src/routes.ts +4 -0
- package/src/schemas.ts +202 -189
- package/src/styles/console.css +268 -5
- package/src/vocabulary.ts +25 -0
package/src/cli/help.ts
CHANGED
|
@@ -136,9 +136,10 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
|
|
|
136
136
|
},
|
|
137
137
|
use: {
|
|
138
138
|
usage: "use <key> [--url <origin>] [--account <login>]",
|
|
139
|
-
synopsis:
|
|
139
|
+
synopsis:
|
|
140
|
+
"Binds this directory to a project: writes .skillcdn-console.json here, and prints the sentence for the checkout's agreement.",
|
|
140
141
|
about:
|
|
141
|
-
"Says which project this directory works in: writes .skillcdn-console.json here, which the commands read from this directory or one above it, and which is meant to be committed in a private repository and left out of a public one. The file names the project; and the console, where that is not the default login here or --url says so; and the account, where the console has several logins or --account says so. Never a token. --project <key> on any command, or CONSOLE_PROJECT in the environment, wins over the project it names.",
|
|
142
|
+
"Says which project this directory works in: writes .skillcdn-console.json here, which the commands read from this directory or one above it, and which is meant to be committed in a private repository and left out of a public one. The file names the project; and the console, where that is not the default login here or --url says so; and the account, where the console has several logins or --account says so. Never a token. --project <key> on any command, or CONSOLE_PROJECT in the environment, wins over the project it names. Then prints the sentence for the checkout's agreement (AGENTS.md, or CLAUDE.md where that is the file agents read), to be copied as it is, since nothing sends an agent to the console until the checkout says so.",
|
|
142
143
|
refusals: ["project.not_found: no project has the key, or it is not yours to see"],
|
|
143
144
|
},
|
|
144
145
|
work: {
|
|
@@ -203,9 +204,26 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
|
|
|
203
204
|
},
|
|
204
205
|
people: {
|
|
205
206
|
usage: "people",
|
|
206
|
-
synopsis: "
|
|
207
|
+
synopsis: "The people you see, by login, with what each is in the workspace.",
|
|
208
|
+
about:
|
|
209
|
+
"The people of the workspace you see, by login, with their name where the host gives one and what they are in it: admin, who configures the workspace, or member, who works in the projects they are in; a person taken out is marked deactivated. Everyone, unless the workspace shows a member only the people they share a project with (console workspace says which; an administrator sees everyone). Then, where you see everyone, the logins an administrator let in that nobody has signed in with yet. The logins are what --assignee, member and person take; a login is found exactly, so an owner lists someone the list does not show.",
|
|
210
|
+
},
|
|
211
|
+
workspace: {
|
|
212
|
+
usage:
|
|
213
|
+
"workspace | workspace edit [--people-visibility <everyone|shared-projects>] [--week-start <auto|monday|sunday>] [--time-zone <zone|none>] | workspace pack-token --connect | workspace pack-token --stdin | workspace pack-token --remove",
|
|
214
|
+
overview:
|
|
215
|
+
"workspace | workspace edit [flags] | workspace pack-token --connect|--stdin|--remove",
|
|
216
|
+
synopsis:
|
|
217
|
+
"The workspace's settings; or some changed, or its SkillCDN token set, for an administrator.",
|
|
207
218
|
about:
|
|
208
|
-
"
|
|
219
|
+
"What the workspace is called; who sees whom on the People page and in the pickers (everyone; or shared-projects: a member sees only the people they share a project with, an administrator everyone; a project open to the workspace has everyone in it, so a member of one sees everyone); the day the calendars begin a week on (auto: the system default, as each device has it); the workspace's time zone, an IANA name such as Asia/Seoul, in which this command prints moments as ISO 8601 with its offset, as the login kept it at login or whoami, or none, which leaves them as the API gives them, in UTC; where the organization's skills come from; and the most days a token may last. edit changes any of the first three, for an administrator; --json leaves moments as the API gives them. pack-token is the SkillCDN token the organization's pack is read with, by every project that names no address of its own and holds no token of its own (ADR-0036), for an administrator: with --connect, the console asks SkillCDN for a code, this command prints the page and the code, a person who may open the repository approves there, signed in at SkillCDN, and the console receives the token, shown to nobody; with --stdin, a token made by hand at SkillCDN is read from standard input and never an argument; --remove takes the token away.",
|
|
220
|
+
refusals: [
|
|
221
|
+
"auth.forbidden: only an administrator changes the workspace's settings or its token",
|
|
222
|
+
"request.invalid: the time zone is not a name the console knows",
|
|
223
|
+
"workspace.pack_token_refused: SkillCDN does not serve the organization's pack to the token, or the console names no address for the organization's skills",
|
|
224
|
+
"workspace.pack_connect_refused: the console names no address for the organization's skills, the pack is on the console's machine, or SkillCDN refused to begin",
|
|
225
|
+
"workspace.pack_token_unsupported, workspace.pack_connect_unsupported: the console keeps no secret to seal a token with (AUTH_SECRET), or reaches no deployment",
|
|
226
|
+
],
|
|
209
227
|
},
|
|
210
228
|
person: {
|
|
211
229
|
usage:
|
|
@@ -224,12 +242,13 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
|
|
|
224
242
|
},
|
|
225
243
|
project: {
|
|
226
244
|
usage:
|
|
227
|
-
'project <key> | project new <key> "<name>" [--description <markdown>|--file <path>] [--visibility workspace|private] [--skills <address>|none] [--color <name>] [--symbol <text>] | project edit <key> [--name "<name>"] [the same flags; none clears] | project icon <key> <file> | project icon <key> --remove | project pack-token <key> --stdin | project pack-token <key> --remove',
|
|
245
|
+
'project <key> | project new <key> "<name>" [--description <markdown>|--file <path>] [--visibility workspace|private] [--skills <address>|none] [--color <name>] [--symbol <text>] | project edit <key> [--name "<name>"] [the same flags; none clears] | project icon <key> <file> | project icon <key> --remove | project pack-token <key> --connect | project pack-token <key> --stdin | project pack-token <key> --remove',
|
|
228
246
|
overview:
|
|
229
|
-
"project <key> | project new <key> <name> [flags] | project edit <key> [flags] | project icon <key> <file>|--remove | project pack-token <key> --stdin|--remove",
|
|
230
|
-
synopsis:
|
|
247
|
+
"project <key> | project new <key> <name> [flags] | project edit <key> [flags] | project icon <key> <file>|--remove | project pack-token <key> --connect|--stdin|--remove",
|
|
248
|
+
synopsis:
|
|
249
|
+
"One project in full; or a project made, changed, given its mark or its SkillCDN token.",
|
|
231
250
|
about:
|
|
232
|
-
"One project in full; or a project made, with you as its owner, private unless --visibility workspace says everyone of the workspace works in it; or one changed, only in what the flags say, for an owner; or its image for its mark, set from a file of this machine or taken away; or its
|
|
251
|
+
"One project in full; or a project made, with you as its owner, private unless --visibility workspace says everyone of the workspace works in it; or one changed, only in what the flags say, for an owner; or its image for its mark, set from a file of this machine or taken away; or its SkillCDN token (pack-token, ADR-0028), which the console keeps sealed to read a private pack with, and which a project that reads the organization's pack may leave to the workspace's (console workspace pack-token): with --connect, the console asks SkillCDN for a code, this command prints the page and the code, a person who may open the repository approves there, signed in at SkillCDN, and the console receives the token, shown to nobody (ADR-0036); with --stdin, a token a person made by hand at SkillCDN is read from standard input and never an argument; --remove takes the token away. The organization's skills are the project's unless --skills names an address of its own.",
|
|
233
252
|
limits: [
|
|
234
253
|
`key: ${KEY}, up to ${MAX_PROJECT_KEY_LENGTH} characters; it does not change once made`,
|
|
235
254
|
`name: one line, up to ${MAX_PROJECT_NAME_LENGTH} characters; description: up to ${MAX_PROJECT_DESCRIPTION_LENGTH}`,
|
|
@@ -243,13 +262,16 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
|
|
|
243
262
|
"project.invalid_address: the skills address is not one as SkillCDN spells it",
|
|
244
263
|
"project.invalid_icon: the file is not an image of those kinds, by its bytes",
|
|
245
264
|
"request.too_large: the image is over the limit",
|
|
265
|
+
"project.pack_connect_unsupported: the console reaches no deployment, or keeps no secret to seal a token with",
|
|
266
|
+
"project.pack_connect_refused: the project names no pack, the pack is on the console's machine, or SkillCDN refused to begin",
|
|
267
|
+
"skills.unavailable: SkillCDN could not be reached; try again later",
|
|
246
268
|
],
|
|
247
269
|
},
|
|
248
270
|
members: {
|
|
249
271
|
usage: "members",
|
|
250
|
-
synopsis: "
|
|
272
|
+
synopsis: "The members of the project, with what each is in it.",
|
|
251
273
|
about:
|
|
252
|
-
"
|
|
274
|
+
"The members of the project, by login, with what each is in it (owner, who configures it; member, who works on it) and since when. In a private project, those listed; in one open to the workspace, everyone of it, an owner where listed as one or an administrator, a member otherwise. An administrator owns every project.",
|
|
253
275
|
},
|
|
254
276
|
member: {
|
|
255
277
|
usage:
|
|
@@ -257,11 +279,12 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
|
|
|
257
279
|
synopsis:
|
|
258
280
|
"Lists a person in the project, changes what they are in it, or takes them off; for an owner.",
|
|
259
281
|
about:
|
|
260
|
-
"Lists a person of the workspace in the project, as a member unless --role says owner; changes what they are in it; or takes them off the list. For an owner of the project. With --project naming several keys with commas, member add lists the person in each of them in turn, one line per project; one listed already is said so, and the rest go on.",
|
|
282
|
+
"Lists a person of the workspace in the project, as a member unless --role says owner; changes what they are in it; or takes them off the list. For an owner of the project. In a project open to the workspace everyone is a member already: only an owner is listed (member add --role owner, or member role <login> owner), and an owner made a member again stays the member everyone is. With --project naming several keys with commas, member add lists the person in each of them in turn, one line per project; one listed already is said so, and the rest go on.",
|
|
261
283
|
refusals: [
|
|
262
284
|
"auth.forbidden: only an owner of the project lists its members",
|
|
263
285
|
"member.invalid_person: the person is not of the workspace",
|
|
264
286
|
"member.exists: the person is listed already; member role changes what they are",
|
|
287
|
+
"member.open_project: the project is open to the workspace, so everyone is a member already; list an owner with --role owner",
|
|
265
288
|
"member.not_found: the person is not listed in the project",
|
|
266
289
|
],
|
|
267
290
|
},
|
|
@@ -285,11 +308,12 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
|
|
|
285
308
|
],
|
|
286
309
|
},
|
|
287
310
|
skills: {
|
|
288
|
-
usage:
|
|
311
|
+
usage:
|
|
312
|
+
"skills | skills <folder> | skills --search <words> [<folder>] [--limit <n>] [--cursor <cursor>]",
|
|
289
313
|
synopsis:
|
|
290
314
|
"The project's pack: its skills; what a folder of it holds; or what words find in it.",
|
|
291
315
|
about:
|
|
292
|
-
"The project's pack,
|
|
316
|
+
"The project's pack, read by the console at the project's address, or the organization's: at a SkillCDN deployment, with the pack token the project holds where the pack is private (ADR-0028), or from a directory on the console's machine for a local address (ADR-0031). Alone: where the pack is and what a client is told of it on connect, then every skill with the path of its SKILL.md and every document, as a connection of your own would list them, and where the console's own skills are on this machine. With a folder (. for the root): what it holds, whole, as browse_repo lays it out for a model: its skills, its folders with what each holds and the README that introduces it, its files. With --search: what the words find, as search_repo does: skills first, with the files of theirs that matched, then documents, in relevance order; words, not meaning, in the language the pack is written in; within the folder when one is named; 10 unless --limit says otherwise, up to 25, and a nextCursor line says when more matched, which --cursor continues. The text is the one an MCP client's model is given, with the reads named as these commands. --json answers with the console's own JSON: what the pack answered, in the shapes of the main repository's REST API, the pages of a folder joined.",
|
|
293
317
|
limits: ["search: 1 to 500 characters; limit: 1 to 25"],
|
|
294
318
|
refusals: [
|
|
295
319
|
"status not_found: the pack is not served to the console; private, with no pack token on the project",
|
|
@@ -301,11 +325,11 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
|
|
|
301
325
|
synopsis:
|
|
302
326
|
"One skill of the pack whole, its rules first; or with --file a file of the pack, or what a folder holds.",
|
|
303
327
|
about:
|
|
304
|
-
"One skill of the project's pack, whole, as load_skill lays it out for
|
|
328
|
+
"One skill of the project's pack, whole, as load_skill lays it out for a model: the rules of the repository from its root to the nearest manifest, the skill's instructions, the files it includes, then its supporting files and what it refers to; the pages it came in are joined. Read by the console from the pack, with the pack token the project holds where the pack is private, so you need no connection of your own. <path> is the path of the SKILL.md from the repository's root, as console skills lists it, or the URI the pack names it by (skill://<host>/<owner>/<name>/<path>). --file <path> reads a file of the pack instead, whole, as read_repo_file does, or what a folder holds, and takes no skill's path beside it; console skills <folder> shows a folder with more. --json answers with what the pack answered, in the shapes of the main repository's REST API, the pages joined.",
|
|
305
329
|
refusals: [
|
|
306
330
|
"status not_found: the pack is not served to the console; private, with no pack token on the project",
|
|
307
331
|
"status token_invalid: the pack token the project holds no longer works; an owner sets a new one",
|
|
308
|
-
"status failed, for a file:
|
|
332
|
+
"status failed, for a file: the pack will not carry it, too large or not text",
|
|
309
333
|
],
|
|
310
334
|
},
|
|
311
335
|
rules: {
|
|
@@ -327,7 +351,7 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
|
|
|
327
351
|
usage: "take <number|id> [--agent <name>]",
|
|
328
352
|
synopsis: "Takes a task: a run begins, and the task is in progress and the person's.",
|
|
329
353
|
about:
|
|
330
|
-
"Takes a task: a run begins, and the task is in progress and the person's; the
|
|
354
|
+
"Takes a task: a run begins, and the task is in progress and the person's; the run's id and the task's id are printed on a line of their own, then the task, then the rules of the board. A run still open on the task is taken over, since the board cannot know whether its agent still runs: it ends as abandoned, the feed says who took over, and that agent is refused on its next command; console task <number> shows the runs on a task, with when each was last heard from, before it is taken. --agent is what people see at work; left out, it is what the person called the token.",
|
|
331
355
|
limits: [`agent: one line, up to ${MAX_AGENT_LENGTH} characters`],
|
|
332
356
|
refusals: [
|
|
333
357
|
"task.not_found: no task has that number or id",
|
|
@@ -370,7 +394,7 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
|
|
|
370
394
|
'ask "<question>" --option "<label>" --option "<label>" [...] [--body <markdown>|--file <path>] [--wait <seconds>]',
|
|
371
395
|
synopsis:
|
|
372
396
|
"Raises a decision a person must make, with options, and waits a while for the answer.",
|
|
373
|
-
about: `Raises a decision a person must make: the question, the options to choose from, and what they need to know. The run waits for the answer. The command waits 60 seconds for it unless --wait says otherwise, and exits with 3 if it is still to come: keep waiting with \`console decision <id> --wait 100\`, within what your shell gives one command. The decision's id is printed as soon as it is raised. ${ON_THE_RUN}`,
|
|
397
|
+
about: `Raises a decision a person must make: the question, the options to choose from, and what they need to know. The run waits for the answer. The command waits 60 seconds for it unless --wait says otherwise, and exits with 3 if it is still to come: keep waiting with \`console decision <id> --wait 100\`, within what your shell gives one command. The decision's id is printed as soon as it is raised. The person chooses one of the options, or answers in their own words: the answer then names one more option, their words, marked own; act on it as given. ${ON_THE_RUN}`,
|
|
374
398
|
limits: [
|
|
375
399
|
`question: one line, up to ${MAX_QUESTION_LENGTH} characters`,
|
|
376
400
|
`options: ${MIN_OPTIONS} to ${MAX_OPTIONS}, each one line up to ${MAX_OPTION_LABEL_LENGTH} characters`,
|
|
@@ -383,7 +407,7 @@ export const COMMAND_HELP: Readonly<Record<string, CommandHelp>> = {
|
|
|
383
407
|
synopsis:
|
|
384
408
|
"One decision in full; waits for its answer with --wait; writes what followed with --outcome.",
|
|
385
409
|
about:
|
|
386
|
-
"One decision, with its context, its options, its answer and what followed. With --wait, holds on for the answer that long, and exits with 3 if it is still to come. With --outcome, writes down what followed the decision, so that it reads as a record.",
|
|
410
|
+
"One decision, with its context, its options, its answer (one of the options, or the person's own words as one more, marked own) and what followed. With --wait, holds on for the answer that long, and exits with 3 if it is still to come. With --outcome, writes down what followed the decision, so that it reads as a record.",
|
|
387
411
|
limits: [`outcome: ${MARKDOWN}`],
|
|
388
412
|
refusals: ["decision.not_found: no decision has that id"],
|
|
389
413
|
},
|
|
@@ -603,7 +627,7 @@ const SECTIONS: readonly { readonly title: string; readonly commands: readonly s
|
|
|
603
627
|
{
|
|
604
628
|
title:
|
|
605
629
|
"Configuring, as the person: what an administrator or an owner does on the pages, their agent does here, named on it",
|
|
606
|
-
commands: ["people", "person", "project", "members", "member", "category"],
|
|
630
|
+
commands: ["workspace", "people", "person", "project", "members", "member", "category"],
|
|
607
631
|
},
|
|
608
632
|
];
|
|
609
633
|
|
package/src/cli/pack.ts
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type BrowseResult,
|
|
3
|
+
browseBody,
|
|
4
|
+
browseResultOf,
|
|
5
|
+
catalogStateOf,
|
|
6
|
+
type DirectoryResult,
|
|
7
|
+
directoryBody,
|
|
8
|
+
directoryResultOf,
|
|
9
|
+
type FileResult,
|
|
10
|
+
type FindItem,
|
|
11
|
+
type FindResult,
|
|
12
|
+
fileBody,
|
|
13
|
+
fileResultOf,
|
|
14
|
+
findResultOf,
|
|
15
|
+
joinSkillPages,
|
|
16
|
+
type MountSummary,
|
|
17
|
+
mountSummaryOf,
|
|
18
|
+
parseAddress,
|
|
19
|
+
parseRepoPath,
|
|
20
|
+
parseSkillUri,
|
|
21
|
+
type RestBrowse,
|
|
22
|
+
type RestFile,
|
|
23
|
+
type RestMount,
|
|
24
|
+
type RestSkill,
|
|
25
|
+
renderBrowseResult,
|
|
26
|
+
renderDirectoryResult,
|
|
27
|
+
renderFileResult,
|
|
28
|
+
renderFindResult,
|
|
29
|
+
renderInstructions,
|
|
30
|
+
renderSkillResult,
|
|
31
|
+
type SkillResult,
|
|
32
|
+
skillOutcome,
|
|
33
|
+
skillResultOf,
|
|
34
|
+
type ToolWords,
|
|
35
|
+
} from "@skillcdn/core";
|
|
36
|
+
import type {
|
|
37
|
+
RestSkillBrowse,
|
|
38
|
+
RestSkillFile,
|
|
39
|
+
RestSkillFind,
|
|
40
|
+
RestSkillLoad,
|
|
41
|
+
RestSkills,
|
|
42
|
+
} from "../schemas.js";
|
|
43
|
+
import type { SkillsStatus } from "../vocabulary.js";
|
|
44
|
+
|
|
45
|
+
// The pack of the project, as the command prints it (ADR-0028, ADR-0031): the console answers
|
|
46
|
+
// what the pack's REST API answered, in the shapes of @skillcdn/core, and the command renders
|
|
47
|
+
// it with the functions a deployment's own tools render for a model, naming the reads as its
|
|
48
|
+
// own commands, so that an agent reads here what it would read over MCP, or with the skillcdn
|
|
49
|
+
// command. The pages a read came in are joined first: a skill, a folder and a file arrive whole.
|
|
50
|
+
|
|
51
|
+
/** How the results name the four reads, so that an agent is told the command and not a tool. */
|
|
52
|
+
export const CONSOLE_WORDS: ToolWords = {
|
|
53
|
+
browse: "console skills <folder>",
|
|
54
|
+
search: "console skills --search",
|
|
55
|
+
load: "console skill",
|
|
56
|
+
read: "console skill --file",
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/** Where the reads of a pack are rendered at: what the listing said the address serves. */
|
|
60
|
+
export type PackSummary = MountSummary;
|
|
61
|
+
|
|
62
|
+
export const NO_ADDRESS = "No skills address: neither this project nor the console names one.";
|
|
63
|
+
|
|
64
|
+
/** The words of a read that did not happen, by status, with what to do about it. */
|
|
65
|
+
const SKILLS_WORDS: Readonly<Record<Exclude<SkillsStatus, "none" | "ready">, string>> = {
|
|
66
|
+
indexing: "The pack is still being indexed; ask again in a moment.",
|
|
67
|
+
failed:
|
|
68
|
+
"The pack could not be indexed, or this is a file the pack will not carry: too large, or not text.",
|
|
69
|
+
not_found:
|
|
70
|
+
"Nothing is served to the console at this address or path: it is not there, or the pack is private and the project holds no pack token for it (console project pack-token <key> --stdin, for an owner).",
|
|
71
|
+
token_invalid:
|
|
72
|
+
"The pack token the project holds no longer works at the deployment: an owner sets a new one (console project pack-token <key> --stdin).",
|
|
73
|
+
unavailable: "The pack could not be reached; ask again later.",
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
type Envelope = Pick<RestSkills, "address" | "source" | "page" | "status" | "refusal">;
|
|
77
|
+
|
|
78
|
+
/** The first line of an answer about the pack: what was read, where the pack is, and how it went. */
|
|
79
|
+
function head(what: string, answer: Envelope): string {
|
|
80
|
+
const where =
|
|
81
|
+
answer.source === null ? "read from the console's machine" : `served by ${answer.source}`;
|
|
82
|
+
return `${what} at ${answer.address}, ${where}: ${answer.status}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** A read that did not happen: the head, the words of its status, and the pack's own refusal when it gave one. */
|
|
86
|
+
function refused(what: string, answer: Envelope): string {
|
|
87
|
+
const lines = [head(what, answer)];
|
|
88
|
+
if (answer.status !== "none" && answer.status !== "ready") {
|
|
89
|
+
lines.push(SKILLS_WORDS[answer.status]);
|
|
90
|
+
}
|
|
91
|
+
if (answer.refusal !== null) {
|
|
92
|
+
lines.push(answer.refusal.message);
|
|
93
|
+
}
|
|
94
|
+
return lines.join("\n");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** What the listing said the address serves, for the reads to be rendered at; nothing when it did not answer. */
|
|
98
|
+
export function summaryOf(listing: RestSkills): PackSummary | undefined {
|
|
99
|
+
return listing.mount === null ? undefined : mountSummaryOf(listing.mount);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Every skill and document the address serves, as one listing: what `search_repo` without words answers. */
|
|
103
|
+
function listingOf(mount: RestMount, summary: PackSummary): FindResult | undefined {
|
|
104
|
+
if (mount.index.status !== "ready") {
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
const { index } = mount;
|
|
108
|
+
const items: FindItem[] = [];
|
|
109
|
+
for (const skill of index.skills) {
|
|
110
|
+
const directory = parseRepoPath(skill.directory);
|
|
111
|
+
if (!directory.ok) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const path = parseRepoPath(
|
|
115
|
+
skill.path ?? (skill.directory === "" ? "SKILL.md" : `${skill.directory}/SKILL.md`),
|
|
116
|
+
);
|
|
117
|
+
items.push({
|
|
118
|
+
kind: "skill",
|
|
119
|
+
...(path.ok ? { path: path.value } : {}),
|
|
120
|
+
name: skill.name,
|
|
121
|
+
directory: directory.value,
|
|
122
|
+
description: skill.description,
|
|
123
|
+
files: [],
|
|
124
|
+
moreFiles: 0,
|
|
125
|
+
// For people, and not rendered for a model.
|
|
126
|
+
translations: {},
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
for (const document of index.documents) {
|
|
130
|
+
const path = parseRepoPath(document.path);
|
|
131
|
+
if (path.ok) {
|
|
132
|
+
items.push({
|
|
133
|
+
kind: "document",
|
|
134
|
+
path: path.value,
|
|
135
|
+
title: document.title ?? undefined,
|
|
136
|
+
summary: document.summary ?? undefined,
|
|
137
|
+
skillDirectory: undefined,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const state = catalogStateOf(mount);
|
|
142
|
+
return {
|
|
143
|
+
mount: summary,
|
|
144
|
+
query: undefined,
|
|
145
|
+
items,
|
|
146
|
+
// The counts stand in the instructions above; without totals here, nothing suggests a cursor.
|
|
147
|
+
totals: undefined,
|
|
148
|
+
diagnostics: state.status === "ready" ? state.catalog.diagnostics : [],
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* The pack: where it is and how it answered; what a client is told on connect, as the
|
|
154
|
+
* deployment tells one; every skill and document; where a person connects an agent of its own;
|
|
155
|
+
* and where the console's own skills are on this machine.
|
|
156
|
+
*/
|
|
157
|
+
export function formatSkills(skills: RestSkills, ownSkills?: string): string {
|
|
158
|
+
const own =
|
|
159
|
+
ownSkills === undefined
|
|
160
|
+
? []
|
|
161
|
+
: ["", `The console's own skills, for working the board: ${ownSkills}`];
|
|
162
|
+
if (skills.status === "none" || skills.address === null) {
|
|
163
|
+
return [NO_ADDRESS, ...own].join("\n");
|
|
164
|
+
}
|
|
165
|
+
const summary = summaryOf(skills);
|
|
166
|
+
if (skills.status !== "ready" || skills.mount === null || summary === undefined) {
|
|
167
|
+
return [refused("skills", skills), ...own].join("\n");
|
|
168
|
+
}
|
|
169
|
+
const lines = [
|
|
170
|
+
head("skills", skills),
|
|
171
|
+
"",
|
|
172
|
+
renderInstructions(catalogStateOf(skills.mount), CONSOLE_WORDS),
|
|
173
|
+
];
|
|
174
|
+
const listing = listingOf(skills.mount, summary);
|
|
175
|
+
if (listing !== undefined) {
|
|
176
|
+
lines.push("", renderFindResult(listing, CONSOLE_WORDS));
|
|
177
|
+
}
|
|
178
|
+
if (skills.page !== null) {
|
|
179
|
+
lines.push(
|
|
180
|
+
"",
|
|
181
|
+
`A host with a SkillCDN connection of its own loads a skill by its URI, which a person makes at ${skills.page}; through the console, ${CONSOLE_WORDS.load} takes the URI too. --json adds the page of each skill, for a person.`,
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
lines.push(...own);
|
|
185
|
+
return lines.join("\n");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* A skill's path as given, or taken out of the URI the listing names it by
|
|
190
|
+
* (`skill://<host>/<owner>/<name>/<path>`); a URI of another pack would read this pack's skill
|
|
191
|
+
* at the same path, or nothing, so it is said instead.
|
|
192
|
+
*/
|
|
193
|
+
export function skillPathIn(
|
|
194
|
+
given: string,
|
|
195
|
+
packAddress: string | null,
|
|
196
|
+
): { readonly path: string } | { readonly error: string } {
|
|
197
|
+
const uri = parseSkillUri(given);
|
|
198
|
+
if (uri === undefined) {
|
|
199
|
+
return given.startsWith("skill://")
|
|
200
|
+
? {
|
|
201
|
+
error: `Not a skill's URI the console knows: ${given}. Say the path of its SKILL.md from the repository's root, or the URI console skills lists.`,
|
|
202
|
+
}
|
|
203
|
+
: { path: given };
|
|
204
|
+
}
|
|
205
|
+
const pack = packAddress === null ? undefined : parseAddress(packAddress);
|
|
206
|
+
if (
|
|
207
|
+
pack?.ok === true &&
|
|
208
|
+
(uri.address.host !== pack.value.host ||
|
|
209
|
+
uri.address.owner !== pack.value.owner ||
|
|
210
|
+
uri.address.repo !== pack.value.repo)
|
|
211
|
+
) {
|
|
212
|
+
return {
|
|
213
|
+
error: `That URI is of another pack, /${uri.address.host}/${uri.address.owner}/${uri.address.repo}; this project's pack is at ${packAddress}. Say the path of the SKILL.md, or a URI console skills lists.`,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
return { path: uri.path };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** The pages of a skill, as the console answered them, as the one skill they were cut from. */
|
|
220
|
+
export function joinSkill(
|
|
221
|
+
pages: readonly RestSkillLoad[],
|
|
222
|
+
summary: PackSummary,
|
|
223
|
+
): SkillResult | undefined {
|
|
224
|
+
return joinSkillPages(
|
|
225
|
+
pages.flatMap((page) =>
|
|
226
|
+
page.read?.status === "ready" ? (skillResultOf(page.read, summary) ?? []) : [],
|
|
227
|
+
),
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** The skill whole, as the REST API answers one, for `--json`. */
|
|
232
|
+
export function bodyOfSkill(skill: SkillResult): RestSkill {
|
|
233
|
+
const outcome = skillOutcome({ status: "ready", lookup: { kind: "found", skill } });
|
|
234
|
+
if ("failure" in outcome) {
|
|
235
|
+
throw new Error("a skill that was found has a body");
|
|
236
|
+
}
|
|
237
|
+
return outcome.body;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** One skill as `load_skill` lays it out for a model: the rules, the body, the included files, its files and references. */
|
|
241
|
+
export function formatSkillLoad(loaded: RestSkillLoad, whole: SkillResult | undefined): string {
|
|
242
|
+
if (loaded.status === "none" || loaded.address === null) {
|
|
243
|
+
return NO_ADDRESS;
|
|
244
|
+
}
|
|
245
|
+
if (loaded.status !== "ready") {
|
|
246
|
+
return refused("skill", loaded);
|
|
247
|
+
}
|
|
248
|
+
if (whole === undefined) {
|
|
249
|
+
return `${head("skill", loaded)}\nNothing was read.`;
|
|
250
|
+
}
|
|
251
|
+
return renderSkillResult(whole, CONSOLE_WORDS);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** The pages of a folder, as the console answered them, as the one listing they were cut from. */
|
|
255
|
+
export function joinFolder(
|
|
256
|
+
pages: readonly RestSkillBrowse[],
|
|
257
|
+
summary: PackSummary,
|
|
258
|
+
): BrowseResult | undefined {
|
|
259
|
+
const results = pages.flatMap((page) =>
|
|
260
|
+
page.read?.status === "ready" ? (browseResultOf(page.read, summary) ?? []) : [],
|
|
261
|
+
);
|
|
262
|
+
const first = results[0];
|
|
263
|
+
const last = results[results.length - 1];
|
|
264
|
+
if (first === undefined || last === undefined) {
|
|
265
|
+
return undefined;
|
|
266
|
+
}
|
|
267
|
+
return {
|
|
268
|
+
...first,
|
|
269
|
+
mount: last.mount,
|
|
270
|
+
entries: results.flatMap((result) => result.entries),
|
|
271
|
+
nextCursor: last.nextCursor,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** A folder whole, as the REST API answers one page, for `--json`. */
|
|
276
|
+
export const bodyOfFolder = (result: BrowseResult): RestBrowse =>
|
|
277
|
+
browseBody({ status: "ready", result });
|
|
278
|
+
|
|
279
|
+
/** What a folder holds, as `browse_repo` lays it out for a model. */
|
|
280
|
+
export function formatSkillBrowse(
|
|
281
|
+
browsed: RestSkillBrowse,
|
|
282
|
+
whole: BrowseResult | undefined,
|
|
283
|
+
): string {
|
|
284
|
+
if (browsed.status === "none" || browsed.address === null) {
|
|
285
|
+
return NO_ADDRESS;
|
|
286
|
+
}
|
|
287
|
+
if (browsed.status !== "ready") {
|
|
288
|
+
return refused("folder", browsed);
|
|
289
|
+
}
|
|
290
|
+
if (whole === undefined) {
|
|
291
|
+
return `${head("folder", browsed)}\nNothing was read.`;
|
|
292
|
+
}
|
|
293
|
+
return renderBrowseResult(whole, CONSOLE_WORDS);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** What words found, as `search_repo` lays it out for a model, one page of it. */
|
|
297
|
+
export function formatSkillFind(found: RestSkillFind, summary: PackSummary | undefined): string {
|
|
298
|
+
if (found.status === "none" || found.address === null) {
|
|
299
|
+
return NO_ADDRESS;
|
|
300
|
+
}
|
|
301
|
+
if (found.status !== "ready" || found.read?.status !== "ready" || summary === undefined) {
|
|
302
|
+
return refused("search", found);
|
|
303
|
+
}
|
|
304
|
+
return renderFindResult(findResultOf(found.read, summary), CONSOLE_WORDS);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** The pages of a file, as the console answered them, as the file whole; or the folder the path named. */
|
|
308
|
+
export function joinFile(
|
|
309
|
+
pages: readonly RestSkillFile[],
|
|
310
|
+
summary: PackSummary,
|
|
311
|
+
): FileResult | DirectoryResult | undefined {
|
|
312
|
+
const reads = pages.flatMap((page) => (page.read === null ? [] : [page.read]));
|
|
313
|
+
const first = reads[0];
|
|
314
|
+
if (first === undefined) {
|
|
315
|
+
return undefined;
|
|
316
|
+
}
|
|
317
|
+
if (first.kind === "directory") {
|
|
318
|
+
return directoryResultOf(first, summary);
|
|
319
|
+
}
|
|
320
|
+
const files = reads.flatMap((read) =>
|
|
321
|
+
read.kind === "file" ? (fileResultOf(read, summary) ?? []) : [],
|
|
322
|
+
);
|
|
323
|
+
const opening = files[0];
|
|
324
|
+
const closing = files[files.length - 1];
|
|
325
|
+
if (opening === undefined || closing === undefined) {
|
|
326
|
+
return undefined;
|
|
327
|
+
}
|
|
328
|
+
return {
|
|
329
|
+
...opening,
|
|
330
|
+
mount: closing.mount,
|
|
331
|
+
content: files.map((file) => file.content).join(""),
|
|
332
|
+
nextOffset: closing.nextOffset,
|
|
333
|
+
totalLength: closing.totalLength,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/** A file whole, or a folder, as the REST API answers it, for `--json`. */
|
|
338
|
+
export const bodyOfFile = (result: FileResult | DirectoryResult): RestFile =>
|
|
339
|
+
"entries" in result ? directoryBody(result) : fileBody(result);
|
|
340
|
+
|
|
341
|
+
/** A file as `read_repo_file` lays it out for a model, whole; or what the folder holds. */
|
|
342
|
+
export function formatSkillFile(
|
|
343
|
+
read: RestSkillFile,
|
|
344
|
+
whole: FileResult | DirectoryResult | undefined,
|
|
345
|
+
): string {
|
|
346
|
+
if (read.status === "none" || read.address === null) {
|
|
347
|
+
return NO_ADDRESS;
|
|
348
|
+
}
|
|
349
|
+
if (read.status !== "ready") {
|
|
350
|
+
return refused("file", read);
|
|
351
|
+
}
|
|
352
|
+
if (whole === undefined) {
|
|
353
|
+
return `${head("file", read)}\nNothing was read.`;
|
|
354
|
+
}
|
|
355
|
+
return "entries" in whole ? renderDirectoryResult(whole) : renderFileResult(whole, CONSOLE_WORDS);
|
|
356
|
+
}
|