@seliseblocks/cli-os 0.2.10 → 0.2.12

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 (53) hide show
  1. package/AI_USAGE_GUIDE.md +11 -20
  2. package/LICENSE +21 -21
  3. package/README.md +3 -5
  4. package/bin/run.js +2 -2
  5. package/dist/commands/auth/oidc-clients/save.js +4 -1
  6. package/dist/commands/auth/refresh.js +21 -2
  7. package/dist/commands/mfa/generate.js +7 -4
  8. package/dist/commands/mfa/method-set.js +13 -4
  9. package/dist/commands/mfa/totp-enable.d.ts +3 -3
  10. package/dist/commands/mfa/totp-enable.js +4 -4
  11. package/dist/commands/mfa/verify.js +4 -2
  12. package/dist/commands/new/web.js +3 -2
  13. package/dist/commands/projects/create.js +110 -16
  14. package/dist/index.js +34 -40
  15. package/dist/lib/oidc-discovery.d.ts +2 -0
  16. package/dist/lib/oidc-discovery.js +12 -0
  17. package/package.json +47 -47
  18. package/dist/commands/sdk/client.d.ts +0 -1
  19. package/dist/commands/sdk/client.js +0 -99
  20. package/dist/commands/skill/add.d.ts +0 -1
  21. package/dist/commands/skill/add.js +0 -19
  22. package/dist/commands/skill/list.d.ts +0 -1
  23. package/dist/commands/skill/list.js +0 -15
  24. package/dist/commands/skill/show.d.ts +0 -1
  25. package/dist/commands/skill/show.js +0 -15
  26. package/dist/lib/skills.d.ts +0 -17
  27. package/dist/lib/skills.js +0 -69
  28. package/dist/skills/blocks-data-gateway-configuration/SKILL.md +0 -204
  29. package/dist/skills/blocks-data-gateway-crud/SKILL.md +0 -223
  30. package/dist/skills/blocks-data-storage/SKILL.md +0 -253
  31. package/dist/skills/blocks-data-storage/flows/object-management.md +0 -124
  32. package/dist/skills/blocks-frontend-local-https/SKILL.md +0 -100
  33. package/dist/skills/blocks-iam-access-control/SKILL.md +0 -49
  34. package/dist/skills/blocks-iam-access-control/flows/feature-gating.md +0 -38
  35. package/dist/skills/blocks-iam-access-control/flows/manage-roles-permissions.md +0 -110
  36. package/dist/skills/blocks-iam-account/SKILL.md +0 -169
  37. package/dist/skills/blocks-iam-mfa/SKILL.md +0 -124
  38. package/dist/skills/blocks-iam-organizations/SKILL.md +0 -43
  39. package/dist/skills/blocks-iam-organizations/flows/admin-mutations.md +0 -89
  40. package/dist/skills/blocks-iam-organizations/flows/read-and-switch.md +0 -57
  41. package/dist/skills/blocks-iam-sso-oidc-configuration/SKILL.md +0 -105
  42. package/dist/skills/blocks-iam-sso-oidc-implementation/SKILL.md +0 -80
  43. package/dist/skills/blocks-iam-users/SKILL.md +0 -131
  44. package/dist/skills/blocks-localization-configuration/SKILL.md +0 -149
  45. package/dist/skills/blocks-localization-implementation/SKILL.md +0 -63
  46. package/dist/skills/blocks-mail/SKILL.md +0 -95
  47. package/dist/skills/blocks-notification/SKILL.md +0 -69
  48. package/dist/skills/blocks-notifier/SKILL.md +0 -107
  49. package/dist/skills/blocks-onboarding/SKILL.md +0 -77
  50. package/dist/skills/blocks-release-deployment/SKILL.md +0 -81
  51. package/dist/skills/blocks-secrets/SKILL.md +0 -81
  52. package/dist/skills/blocks-storage-configuration/SKILL.md +0 -93
  53. package/dist/skills/lint.mjs +0 -168
package/dist/index.js CHANGED
@@ -86,17 +86,13 @@ import { localizationValidate } from "./commands/localization/validate.js";
86
86
  import { login } from "./commands/login.js";
87
87
  import { logout } from "./commands/logout.js";
88
88
  import { newWeb } from "./commands/new/web.js";
89
- // import { createProject } from "./commands/projects/create.js"; // disabled for now
89
+ import { createProject } from "./commands/projects/create.js";
90
90
  import { getProject } from "./commands/projects/get.js";
91
91
  import { listProjects } from "./commands/projects/list.js";
92
92
  import { releaseBuildsGet } from "./commands/release/builds/get.js";
93
93
  import { releaseBuildsList } from "./commands/release/builds/list.js";
94
94
  import { releaseDeploy } from "./commands/release/deploy.js";
95
95
  import { releaseStatus } from "./commands/release/status.js";
96
- import { sdkClient } from "./commands/sdk/client.js";
97
- import { skillAdd } from "./commands/skill/add.js";
98
- import { skillList } from "./commands/skill/list.js";
99
- import { skillShow } from "./commands/skill/show.js";
100
96
  import { useProject } from "./commands/use.js";
101
97
  import { authClientCredentialsDelete } from "./commands/auth/client-credentials/delete.js";
102
98
  import { authClientCredentialsList } from "./commands/auth/client-credentials/list.js";
@@ -200,6 +196,7 @@ const commands = {
200
196
  "init": () => init(),
201
197
  "login": login,
202
198
  "logout": logout,
199
+ "projects:create": createProject,
203
200
  "projects:list": listProjects,
204
201
  "use": useProject,
205
202
  "deselect": deselectProject,
@@ -401,10 +398,6 @@ const commands = {
401
398
  "iam:me": iamMe,
402
399
  "projects:get": getProject,
403
400
  "new:web": newWeb,
404
- "skill:list": skillList,
405
- "skill:show": skillShow,
406
- "skill:add": skillAdd,
407
- "sdk:client": sdkClient,
408
401
  };
409
402
  const MAX_COMMAND_WORDS = 4;
410
403
  function resolveCommand(argv) {
@@ -535,6 +528,18 @@ Auth:
535
528
  Revoke the current refresh token when possible and remove local session data.
536
529
 
537
530
  Projects:
531
+ blocks projects create <name> [--allow-duplicate-name] [--yes] [--dry-run] [--json]
532
+ Create a new Blocks project via /os/v4/Project/Create using the account
533
+ token (no project needs to be selected yet). Creates exactly one
534
+ application, always in the 'dev' environment -- environment, domain,
535
+ cookie domain, and production flag are fixed and not configurable here;
536
+ the domain sent is a placeholder the platform discards and replaces with
537
+ the one it assigns. Confirms first because it accepts the Blocks terms on
538
+ your behalf. Refuses a name already used by another project unless
539
+ --allow-duplicate-name is passed. Verifies the result against
540
+ Project/Gets and prints the new tenantId, tenantGroupId, and assigned
541
+ domain. Does not select the project -- run 'blocks use <tenantId>' next.
542
+
538
543
  blocks projects list [--json]
539
544
  List accessible Blocks projects via /os/v4/Project/Gets. Uses the
540
545
  impersonated project session when a project is selected, otherwise the
@@ -616,6 +621,9 @@ IAM:
616
621
  blocks iam permissions update <id> [same flags as create, plus --is-archived]
617
622
  [--dry-run] [--yes] [--json]
618
623
  blocks iam permissions by-severity [--json]
624
+ --type is IAM's ResourceType: 0 None, 1 Endpoint, 2 FrontendAction, 3 DataProtection.
625
+ --severity is PermissionSeverity, ordered most-severe-first, not least: 0 None,
626
+ 1 Critical, 2 High, 3 Medium, 4 Low.
619
627
 
620
628
  Resources (/iam/v4/iam/resource*):
621
629
  blocks iam resources groups [--json]
@@ -647,11 +655,15 @@ IAM:
647
655
  [--body '<json>'|--file <path>] [--dry-run] [--yes] [--json]
648
656
 
649
657
  MFA (/iam/v4/mfa*, project-scoped: requires a selected project, impersonated project token only):
658
+ Every mfa-type/auth-type/user-mfa-type value is IAM's UserMfaType enum: 0 None, 1 TOTP,
659
+ 2 Email, 3 Sms, 4 WhatsApp. Only 1 and 2 have a working provider; 3 and 4 are declared
660
+ but unimplemented. Leaving --user-mfa-type empty means no method is allowed, so MFA is
661
+ never actually required no matter what else the policy says.
650
662
  blocks mfa config get [--json]
651
663
  Read the tenant's MFA policy.
652
664
  blocks mfa config save [--enable] [--require-for-all-users] [--allow-user-opt-out]
653
665
  [--allow-backup-codes] [--backup-codes-count <n>]
654
- [--user-mfa-type 0,1] [--required-roles a,b] [--exempt-roles a,b]
666
+ [--user-mfa-type 1,2] [--required-roles a,b] [--exempt-roles a,b]
655
667
  [--body '<json>'|--file <path>] [--dry-run] [--yes] [--json]
656
668
  Save the tenant's MFA policy.
657
669
  blocks mfa totp setup [--json]
@@ -670,9 +682,12 @@ MFA (/iam/v4/mfa*, project-scoped: requires a selected project, impersonated pro
670
682
  blocks mfa resend <mfaId> [--send-phone-number-as-email-domain <domain>] [--json]
671
683
  blocks mfa verify <mfaId> <code> --auth-type <n> [--from-token-call] [--json]
672
684
  blocks mfa method set --mfa-type <n> [--json]
673
- Switch the impersonated user's active MFA method.
685
+ Switch the impersonated user's active MFA method. IAM only branches on 1 (TOTP) and
686
+ 2 (Email) here -- any other value falls through to its disable path and turns the
687
+ user's MFA off. Use 'blocks mfa disable' when that is what you mean.
674
688
  blocks mfa disable [--dry-run] [--yes] [--json]
675
689
  blocks mfa backup-codes list [--json]
690
+ Returns { remaining: <count> } only -- the codes themselves are shown once, at generate.
676
691
  blocks mfa backup-codes generate [--dry-run] [--yes] [--json]
677
692
  blocks mfa backup-codes use <userId> <code> [--json]
678
693
 
@@ -815,11 +830,11 @@ Auth Admin (/iam/v4/auth/identity-providers*, /config, /client-credentials, /oid
815
830
  [--redirect-uris a,b] [--post-logout-redirect-uris a,b]
816
831
  [--scope] [--allowed-scopes a,b] [--allowed-response-types a,b]
817
832
  [--require-pkce] [--require-consent] [--require-mfa]
818
- [--allowed-mfa-methods 0,1] [--front-channel-logout-uri]
833
+ [--allowed-mfa-methods 1,2] [--front-channel-logout-uri]
819
834
  [--back-channel-logout-uri] [--auto-redirect]
820
835
  [--external-discovery-endpoint] [--active] [--login-mode]
821
836
  [--client-logo-url] [--client-brand-color] [--use-tokens-cookie]
822
- [--register-as-identity-provider] [--device-flow-client]
837
+ [--register-as-identity-provider] [--oidc-url] [--device-flow-client]
823
838
  [--body '<json>'|--file <path>] [--dry-run] [--yes] [--json]
824
839
  Upsert: omit --item-id to register a new client, pass it to update an existing one.
825
840
  The response's client_secret is shown once and is not retrievable again afterward.
@@ -828,9 +843,10 @@ Auth Admin (/iam/v4/auth/identity-providers*, /config, /client-credentials, /oid
828
843
  lets it request the client_credentials grant. Pass --client-type public for any
829
844
  PKCE/browser client.
830
845
  --register-as-identity-provider creates the linked identity provider in the same call.
831
- Its authorize/token/userinfo/jwks/issuer values are filled from the discovery document
832
- at --external-discovery-endpoint; with no discovery endpoint they are left null and the
833
- provider's scope is replaced with "openid profile email". Verify with 'auth idp list'.
846
+ Its authorize/token/userinfo/jwks/issuer values are filled from the discovery document.
847
+ When omitted, --external-discovery-endpoint defaults to
848
+ <oidc-url>/<project>/.well-known/openid-configuration; pass it explicitly for an
849
+ external provider or non-standard IAM base URL.
834
850
  blocks auth oidc-clients delete <clientId> [--dry-run] [--yes] [--json]
835
851
  Irreversible; revokes all tokens issued to the client.
836
852
  blocks auth oidc-clients rotate-secret <clientId> [--dry-run] [--yes] [--json]
@@ -906,6 +922,7 @@ Data:
906
922
  Irreversible.
907
923
  blocks data schema info list [--json]
908
924
  Entity-type schema collections with basic info.
925
+ --schema-type: 1 Entity, 2 Dto. There is no 0.
909
926
  blocks data schema info save --schema-name <n> [--collection-name] [--schema-type <1|2>]
910
927
  [--body '<json>'|--file <path>] [--dry-run] [--yes] [--json]
911
928
  Create schema metadata only (no fields yet) - pair with data schema fields.
@@ -1090,6 +1107,7 @@ Localization:
1090
1107
  blocks localization key uilm-import <fileId> [--message-co-relation-id]
1091
1108
  [--dry-run] [--yes] [--json]
1092
1109
  blocks localization key uilm-export [--output-type <0-5>] [--app-ids a,b] [--languages a,b]
1110
+ --output-type: 0 Json (default), 1 Xml, 2 Text, 3 Xlsx, 4 Csv, 5 Xlf.
1093
1111
  [--reference-file-id] [--caller-tenant-id] [--start-date]
1094
1112
  [--end-date] [--message-co-relation-id] [--dry-run] [--yes] [--json]
1095
1113
  blocks localization key get-uilm-exported-files [--search] [--page-number] [--page-size]
@@ -1163,29 +1181,5 @@ Scaffold:
1163
1181
  different Data/IAM/Localization/OS gateway URL explicitly only if your
1164
1182
  project uses a non-default one.
1165
1183
  --oidc-url defaults to https://iam.seliseblocks.com.
1166
-
1167
- Skills:
1168
- blocks skill list [--json]
1169
- List bundled blocks-skills/*/SKILL.md agent context docs (name +
1170
- description). Local-only, no cloud calls.
1171
- blocks skill show <name> [--json]
1172
- Print one skill's full SKILL.md content.
1173
- blocks skill add <name> [--dir <path>]
1174
- Copy a bundled skill's SKILL.md into <path>/<name>/SKILL.md in the
1175
- current directory (default --dir is 'blocks-skills'), for use in a
1176
- project outside this monorepo. Overwrites silently, same as
1177
- 'data schema pull'.
1178
-
1179
- SDK:
1180
- blocks sdk client [--app-domain <domain>] [--client-id <oidcClientId>]
1181
- [--x-blocks-key <tenantId>] [--blocks-api-url <url>] [--oidc-url <url>] [--json]
1182
- Read-only: "I want to use the Blocks SDK -- show me the client." Resolves this
1183
- project's @seliseblocks/client config using the selected project unless
1184
- --x-blocks-key overrides it, and the project's registered domain/OIDC client
1185
- when --app-domain/--client-id are omitted. Its API URL defaults to
1186
- https://api.seliseblocks.com unless --blocks-api-url is passed.
1187
- Prints a ready-to-paste createBlocksClient(...) snippet.
1188
- Passing both --app-domain and --client-id skips the project lookup entirely
1189
- (no login required). Never writes a file; to scaffold a new app use 'new web'.
1190
1184
  `);
1191
1185
  }
@@ -0,0 +1,2 @@
1
+ export declare function blocksOidcWellKnownUrl(oidcUrl: string, tenantId: string): string;
2
+ export declare function withBlocksIdentityProviderDiscovery<T extends Record<string, unknown>>(body: T, oidcUrl: string, tenantId: string): T;
@@ -0,0 +1,12 @@
1
+ export function blocksOidcWellKnownUrl(oidcUrl, tenantId) {
2
+ return `${oidcUrl.replace(/\/$/, "")}/${tenantId}/.well-known/openid-configuration`;
3
+ }
4
+ export function withBlocksIdentityProviderDiscovery(body, oidcUrl, tenantId) {
5
+ if (body.registerAsIdentityProvider !== true || (typeof body.externalDiscoveryEndpoint === "string" && body.externalDiscoveryEndpoint)) {
6
+ return body;
7
+ }
8
+ return {
9
+ ...body,
10
+ externalDiscoveryEndpoint: blocksOidcWellKnownUrl(oidcUrl, tenantId)
11
+ };
12
+ }
package/package.json CHANGED
@@ -1,47 +1,47 @@
1
- {
2
- "name": "@seliseblocks/cli-os",
3
- "version": "0.2.10",
4
- "description": "CLI for SELISE Blocks project setup and configuration.",
5
- "license": "MIT",
6
- "type": "module",
7
- "bin": {
8
- "blocks": "bin/run.js"
9
- },
10
- "main": "./dist/index.js",
11
- "types": "./dist/index.d.ts",
12
- "exports": {
13
- ".": {
14
- "types": "./dist/index.d.ts",
15
- "import": "./dist/index.js"
16
- },
17
- "./package.json": "./package.json"
18
- },
19
- "publishConfig": {
20
- "access": "public"
21
- },
22
- "sideEffects": false,
23
- "files": [
24
- "bin",
25
- "dist",
26
- "README.md",
27
- "AI_USAGE_GUIDE.md",
28
- "LICENSE"
29
- ],
30
- "scripts": {
31
- "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
32
- "build": "npm run clean && tsc -p tsconfig.json && node scripts/copy-skills.mjs",
33
- "dev": "tsx src/index.ts",
34
- "lint": "tsc -p tsconfig.json --noEmit",
35
- "test": "npm run build && node --test test/*.test.mjs",
36
- "prepack": "npm run build",
37
- "prepublishOnly": "npm test"
38
- },
39
- "devDependencies": {
40
- "@types/node": "^22.0.0",
41
- "tsx": "^4.16.0",
42
- "typescript": "^5.5.0"
43
- },
44
- "engines": {
45
- "node": ">=20"
46
- }
47
- }
1
+ {
2
+ "name": "@seliseblocks/cli-os",
3
+ "version": "0.2.12",
4
+ "description": "CLI for SELISE Blocks project setup and configuration.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "blocks": "bin/run.js"
9
+ },
10
+ "main": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js"
16
+ },
17
+ "./package.json": "./package.json"
18
+ },
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "sideEffects": false,
23
+ "files": [
24
+ "bin",
25
+ "dist",
26
+ "README.md",
27
+ "AI_USAGE_GUIDE.md",
28
+ "LICENSE"
29
+ ],
30
+ "scripts": {
31
+ "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
32
+ "build": "npm run clean && tsc -p tsconfig.json",
33
+ "dev": "tsx src/index.ts",
34
+ "lint": "tsc -p tsconfig.json --noEmit",
35
+ "test": "npm run build && node --test test/*.test.mjs",
36
+ "prepack": "npm run build",
37
+ "prepublishOnly": "npm test"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^22.0.0",
41
+ "tsx": "^4.16.0",
42
+ "typescript": "^5.5.0"
43
+ },
44
+ "engines": {
45
+ "node": ">=20"
46
+ }
47
+ }
@@ -1 +0,0 @@
1
- export declare function sdkClient(argv: string[]): Promise<void>;
@@ -1,99 +0,0 @@
1
- import { blocksRequest } from "../../lib/api.js";
2
- import { stringFlag } from "../../lib/args.js";
3
- import { defaults } from "../../lib/config.js";
4
- import { findProjectByTenantId } from "../../lib/project-info.js";
5
- import { writeOutput } from "../../lib/output.js";
6
- import { requestContext } from "../../lib/request-context.js";
7
- import { parseCommand, selectedProject } from "../../lib/workspace.js";
8
- // Read-only: "I want to use the Blocks SDK in my app -- show me the client."
9
- // Resolves this project's createBlocksClient() config and prints it, same
10
- // values 'new web' scaffolds with. Never writes a file and never mutates
11
- // anything -- if you want the SDK wired into a new app, use 'new web'.
12
- export async function sdkClient(argv) {
13
- const { flags } = parseCommand(argv);
14
- const tenantId = stringFlag(flags, "x-blocks-key") || (await selectedProject(flags));
15
- const apiUrl = stringFlag(flags, "blocks-api-url", { defaultValue: defaults().apiUrl });
16
- const oidcUrl = stringFlag(flags, "oidc-url", { defaultValue: defaults().oidcUrl });
17
- let appDomain = stringFlag(flags, "app-domain");
18
- let oidcClientId = stringFlag(flags, "client-id");
19
- const notes = [];
20
- if (!appDomain || !oidcClientId) {
21
- const { project } = await findProjectByTenantId(tenantId, flags);
22
- if (!appDomain) {
23
- const domains = (project.applications ?? [])
24
- .map((application) => application.domain)
25
- .filter((domain) => Boolean(domain));
26
- if (domains.length === 1) {
27
- appDomain = domains[0];
28
- }
29
- else if (domains.length > 1) {
30
- notes.push(`Multiple domains registered for this project (${domains.join(", ")}) -- pass --app-domain to pick one.`);
31
- }
32
- else {
33
- notes.push("This project has no domains registered in Blocks -- pass --app-domain explicitly.");
34
- }
35
- }
36
- if (!oidcClientId) {
37
- const clients = await listOidcClients(tenantId, flags);
38
- if (clients.length === 1) {
39
- oidcClientId = clients[0].id;
40
- }
41
- else if (clients.length > 1) {
42
- notes.push(`Multiple OIDC clients found (${clients.map((client) => `${client.label} [${client.id}]`).join(", ")}) -- pass --client-id to pick one.`);
43
- }
44
- else {
45
- notes.push("No OIDC client registered for this project -- create one ('auth:oidc-clients:save') and pass --client-id.");
46
- }
47
- }
48
- }
49
- if (flags.json) {
50
- writeOutput({ apiUrl, appDomain: appDomain || undefined, notes, oidcClientId: oidcClientId || undefined, oidcUrl, xBlocksKey: tenantId }, flags);
51
- return;
52
- }
53
- console.log("import { createBlocksClient } from \"@seliseblocks/client\";");
54
- console.log("");
55
- console.log("export const blocksClient = createBlocksClient({");
56
- console.log(` apiUrl: "${apiUrl}",`);
57
- if (appDomain)
58
- console.log(` appDomain: "${appDomain}",`);
59
- console.log(" oidc: {");
60
- console.log(` clientId: "${oidcClientId || "<register a public OIDC client, see auth:oidc-clients:save>"}",`);
61
- console.log(" scope: \"openid profile\",");
62
- console.log(` url: "${oidcUrl}"`);
63
- console.log(" },");
64
- console.log(` xBlocksKey: "${tenantId}"`);
65
- console.log("});");
66
- for (const note of notes)
67
- console.log(`\n${note}`);
68
- }
69
- async function listOidcClients(tenantId, flags) {
70
- const raw = await blocksRequest("/iam/v4/oidc-clients", {
71
- impersonatedProjectAuth: true,
72
- projectTenantId: tenantId,
73
- ...requestContext(flags)
74
- });
75
- const clients = [];
76
- for (const item of normalizeList(raw)) {
77
- if (typeof item !== "object" || item === null)
78
- continue;
79
- const record = item;
80
- const id = record.itemId ?? record.clientId ?? record.id;
81
- if (typeof id !== "string" || !id)
82
- continue;
83
- const label = typeof record.clientDisplayName === "string" && record.clientDisplayName ? record.clientDisplayName : id;
84
- clients.push({ id, label });
85
- }
86
- return clients;
87
- }
88
- function normalizeList(raw) {
89
- if (Array.isArray(raw))
90
- return raw;
91
- if (raw && typeof raw === "object") {
92
- for (const key of ["data", "items", "results"]) {
93
- const value = raw[key];
94
- if (Array.isArray(value))
95
- return value;
96
- }
97
- }
98
- return [];
99
- }
@@ -1 +0,0 @@
1
- export declare function skillAdd(argv: string[]): Promise<void>;
@@ -1,19 +0,0 @@
1
- import { cp } from "node:fs/promises";
2
- import { dirname, join } from "node:path";
3
- import { stringFlag } from "../../lib/args.js";
4
- import { readSkill } from "../../lib/skills.js";
5
- import { parseCommand } from "../../lib/workspace.js";
6
- export async function skillAdd(argv) {
7
- const { args, flags } = parseCommand(argv);
8
- const name = args[0];
9
- if (!name)
10
- throw new Error("Missing skill name. Run 'blocks skill list' to see available skills.");
11
- const skill = await readSkill(name);
12
- const sourceDir = dirname(skill.path);
13
- const targetDir = stringFlag(flags, "dir", { defaultValue: "blocks-skills" });
14
- const targetPath = join(process.cwd(), targetDir, name);
15
- // Copy the whole skill directory, not just SKILL.md -- some skills also ship
16
- // supporting files (e.g. flows/*.md) that SKILL.md links to.
17
- await cp(sourceDir, targetPath, { recursive: true });
18
- console.log(`Added ${targetPath}`);
19
- }
@@ -1 +0,0 @@
1
- export declare function skillList(argv: string[]): Promise<void>;
@@ -1,15 +0,0 @@
1
- import { listSkills, SKILLS_REPO_URL } from "../../lib/skills.js";
2
- import { writeOutput } from "../../lib/output.js";
3
- import { parseCommand } from "../../lib/workspace.js";
4
- export async function skillList(argv) {
5
- const { flags } = parseCommand(argv);
6
- const skills = await listSkills();
7
- if (flags.json) {
8
- writeOutput(skills, flags);
9
- return;
10
- }
11
- for (const skill of skills) {
12
- console.log(`${skill.name} ${skill.description}`);
13
- }
14
- console.log(`\nFull catalog (may be ahead of this bundled list): ${SKILLS_REPO_URL}`);
15
- }
@@ -1 +0,0 @@
1
- export declare function skillShow(argv: string[]): Promise<void>;
@@ -1,15 +0,0 @@
1
- import { readSkill } from "../../lib/skills.js";
2
- import { writeOutput } from "../../lib/output.js";
3
- import { parseCommand } from "../../lib/workspace.js";
4
- export async function skillShow(argv) {
5
- const { args, flags } = parseCommand(argv);
6
- const name = args[0];
7
- if (!name)
8
- throw new Error("Missing skill name. Run 'blocks skill list' to see available skills.");
9
- const skill = await readSkill(name);
10
- if (flags.json) {
11
- writeOutput(skill, flags);
12
- return;
13
- }
14
- console.log(skill.content);
15
- }
@@ -1,17 +0,0 @@
1
- export type SkillSummary = {
2
- description: string;
3
- name: string;
4
- path: string;
5
- };
6
- export type SkillDetail = SkillSummary & {
7
- content: string;
8
- };
9
- export declare const SKILLS_REPO_URL = "https://github.com/SELISEdigitalplatforms/blocks-cli/tree/main/blocks-skills";
10
- export declare function resolveSkillsDir(): string;
11
- export declare function parseFrontmatter(raw: string): {
12
- body: string;
13
- description?: string;
14
- name?: string;
15
- };
16
- export declare function listSkills(): Promise<SkillSummary[]>;
17
- export declare function readSkill(name: string): Promise<SkillDetail>;
@@ -1,69 +0,0 @@
1
- import { existsSync } from "node:fs";
2
- import { readdir, readFile } from "node:fs/promises";
3
- import { dirname, join } from "node:path";
4
- import { fileURLToPath } from "node:url";
5
- // Canonical public catalog -- the source of truth when the locally bundled
6
- // skills are missing a name, or out of date relative to what's published.
7
- export const SKILLS_REPO_URL = "https://github.com/SELISEdigitalplatforms/blocks-cli/tree/main/blocks-skills";
8
- // Resolves where blocks-skills/*/SKILL.md content lives, in priority order:
9
- // 1. Bundled into this package at build time (see scripts/copy-skills.mjs) --
10
- // what a published npm install actually ships.
11
- // 2. The monorepo root's blocks-skills/ folder -- covers 'npm run dev' (tsx,
12
- // no build step) and running straight from a source checkout.
13
- export function resolveSkillsDir() {
14
- const moduleDir = dirname(fileURLToPath(import.meta.url));
15
- const candidates = [join(moduleDir, "..", "skills"), join(moduleDir, "..", "..", "..", "blocks-skills")];
16
- for (const candidate of candidates) {
17
- if (existsSync(candidate))
18
- return candidate;
19
- }
20
- throw new Error("No blocks-skills content found. Expected a bundled 'skills' folder next to this package, or a 'blocks-skills' folder at the monorepo root.");
21
- }
22
- // Hand-rolled parser for this repo's flat, single-line SKILL.md frontmatter
23
- // (`name: ...` / `description: "..."`) -- no YAML dependency exists in this
24
- // package and none of these fields span multiple lines.
25
- export function parseFrontmatter(raw) {
26
- const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
27
- if (!match)
28
- return { body: raw };
29
- const [, frontmatter, body] = match;
30
- const fields = {};
31
- for (const line of frontmatter.split(/\r?\n/)) {
32
- const separator = line.indexOf(":");
33
- if (separator === -1)
34
- continue;
35
- const key = line.slice(0, separator).trim();
36
- let value = line.slice(separator + 1).trim();
37
- if (value.length >= 2 && value.startsWith("\"") && value.endsWith("\"")) {
38
- value = value.slice(1, -1);
39
- }
40
- fields[key] = value;
41
- }
42
- return { body, description: fields.description, name: fields.name };
43
- }
44
- export async function listSkills() {
45
- const dir = resolveSkillsDir();
46
- const entries = await readdir(dir, { withFileTypes: true });
47
- const summaries = [];
48
- for (const entry of entries) {
49
- if (!entry.isDirectory())
50
- continue;
51
- const skillPath = join(dir, entry.name, "SKILL.md");
52
- if (!existsSync(skillPath))
53
- continue;
54
- const raw = await readFile(skillPath, "utf8");
55
- const { description, name } = parseFrontmatter(raw);
56
- summaries.push({ description: description ?? "", name: name ?? entry.name, path: skillPath });
57
- }
58
- return summaries.sort((a, b) => a.name.localeCompare(b.name));
59
- }
60
- export async function readSkill(name) {
61
- const skills = await listSkills();
62
- const match = skills.find((skill) => skill.name === name);
63
- if (!match) {
64
- const available = skills.map((skill) => skill.name).join(", ") || "(none found)";
65
- throw new Error(`Unknown skill '${name}'. Available skills: ${available}. If the skill you're looking for isn't listed (the bundled set may be out of date), check the full catalog at ${SKILLS_REPO_URL}.`);
66
- }
67
- const content = await readFile(match.path, "utf8");
68
- return { ...match, content };
69
- }