@seliseblocks/cli-os 0.1.1 → 0.1.2

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/index.js CHANGED
@@ -192,152 +192,152 @@ function toCliError(error) {
192
192
  return { code: "command_failed", message };
193
193
  }
194
194
  function printHelp() {
195
- console.log(`Blocks OS CLI
196
-
197
- Usage:
198
- blocks-os <command> [options]
199
-
200
- Global options:
201
- --version Print CLI version.
202
- --json Print machine-readable JSON where supported.
203
- --api-url <url> Override Blocks API URL for this command.
204
- --account <name> Use a named account profile; default is implicit.
205
- --project <tenantId> Use a project tenant for project-scoped commands.
206
- --dry-run Show planned mutation without calling the API.
207
- --yes Skip mutation confirmation after explicit approval.
208
-
209
- Setup and health:
210
- blocks-os init
211
- Create local Blocks workspace files: blocks.json, data schema/rules folders,
212
- release deploy config, and .env.example.
213
-
214
- blocks-os doctor [--json]
215
- Check local Node.js, OIDC config, token cache, selected project, and config
216
- file locations. Does not mutate cloud resources.
217
-
218
- Auth:
219
- blocks-os auth:add [account] --client-id <id> --client-secret <secret>
220
- Save OIDC client settings for the CLI. Defaults target production Blocks
221
- URLs, root tenant, scope, and localhost callback. Secret is stored separately.
222
-
223
- blocks-os login
224
- Browser Authorization Code + PKCE login. Opens IAM, captures the localhost
225
- callback, stores account access and refresh tokens, and auto-refreshes later.
226
-
227
- blocks-os login:device
228
- Device-code login for terminals or agents where a browser callback is not
229
- convenient. Uses the same saved account profile.
230
-
231
- blocks-os auth:status [--json]
232
- Show configured account, token freshness, refresh-token availability, and
233
- selected project without printing secrets.
234
-
235
- blocks-os auth:refresh [--project] [--json]
236
- Force account token refresh, or project token refresh with --project.
237
-
238
- blocks-os auth:repair [account] [--dry-run] [--yes] [--json]
239
- Clear broken local secure-store auth state while keeping account profile
240
- config. Use after OS credential migration, stale DPAPI, or corrupted tokens.
241
-
242
- blocks-os auth:list [--json]
243
- List saved account profiles without showing secrets.
244
-
245
- blocks-os auth:show [account]
246
- Print one account profile with secret redacted as configured/missing.
247
-
248
- blocks-os auth:use <account>
249
- Switch the active named account profile.
250
-
251
- blocks-os auth:remove <account>
252
- Remove account config, cached tokens, and stored client secret for that
253
- account.
254
-
255
- blocks-os logout
256
- Revoke the current refresh token when possible and remove local session data.
257
-
258
- Projects:
259
- blocks-os projects:list [--json]
260
- List accessible Blocks projects via /os/v4/Project/Gets using the account
261
- token. Read-only.
262
-
263
- blocks-os projects:get [tenantId] [--json]
264
- Read one project from Project/Gets. Uses selected project when tenantId is
265
- omitted. Read-only.
266
-
267
- blocks-os projects:create <name> [--env dev] [--yes] [--dry-run] [--json]
268
- Create a Blocks project/environment via /os/v4/Project/Create using the
269
- account token. Mutating; supports dry-run and confirmation.
270
-
271
- blocks-os use <project-tenant-id>
272
- Save the selected project tenant globally and in blocks.json when present.
273
- Does not call cloud APIs.
274
-
275
- IAM:
276
- blocks-os iam:me [--json]
277
- Read the current user from IAM using the account token. This is the only IAM
278
- admin surface exposed in the MVP.
279
-
280
- Data:
281
- blocks-os data:validate [--json]
282
- Validate local blocks/data/schemas/*.json and blocks/data/rules.json before
283
- pushing. Local-only.
284
-
285
- blocks-os data:schema:list [--json]
286
- List project schemas via /data/v4/schemas using an impersonated project
287
- token. Read-only.
288
-
289
- blocks-os data:schema:pull [--json]
290
- Download project schemas into blocks/data/schemas/*.json. Writes local files
291
- only.
292
-
293
- blocks-os data:schema:push [--dry-run] [--yes] [--json]
294
- Create or update project schemas via /data/v4/schemas/define. Mutating;
295
- uses POST for create and PUT for update.
296
-
297
- blocks-os data:rules:pull [--json]
298
- Download data-access policies into blocks/data/rules.json. Writes local
299
- files only.
300
-
301
- blocks-os data:rules:deploy [--dry-run] [--yes] [--json]
302
- Apply schema security and data-access policies. Mutating; supports dry-run
303
- and confirmation.
304
-
305
- blocks-os data:reload [--dry-run] [--yes] [--json]
306
- Reload Data schema configuration so staged schema/rule changes become live.
307
- Mutating; calls POST /data/v4/schema-configurations/reload.
308
-
309
- Localization:
310
- blocks-os localization:validate --module <name> --language <culture> [--file <path>] [--json]
311
- Validate a local i18n JSON dictionary. Supports nested JSON input and
312
- validates the flattened key/value set locally.
313
-
314
- blocks-os localization:push --module <name> --language <culture> [--file <path>] [--route <route>] [--context <text>] [--dry-run] [--yes] [--json]
315
- Create or update Localization keys from a local i18n JSON dictionary via
316
- /localization/v4/Key/SaveKeys. Creates the module first when it is missing.
317
-
318
- blocks-os localization:pull --module <name> --language <culture> [--out <path>] [--json]
319
- Download published cloud localization via
320
- /localization/v4/Key/GetCloudUilmFile and write a local JSON dictionary.
321
-
322
- Release:
323
- blocks-os release:deploy --repo-id <repoId> [--dry-run] [--yes] [--json]
324
- Trigger a manual Release build/deploy for a configured repository. Mutating;
325
- no artifact upload is performed by this CLI.
326
-
327
- blocks-os release:status <buildId> [--json]
328
- Read Release build status by build id. Read-only.
329
-
330
- blocks-os release:builds:list --repo-id <repoId> [--json]
331
- List Release build details for a repository. Read-only.
332
-
333
- blocks-os release:builds:get <buildId> [--json]
334
- Alias for release:status. Read-only.
335
-
336
- Scaffold:
337
- blocks-os new web <name> --x-blocks-key <tenantId> --app-domain <domain> --blocks-api-url <url> [--client-id <oidcClientId>] [--oidc-url <url>]
338
- Create a Vite React starter app with a real browser Authorization Code +
339
- PKCE login flow against Blocks IAM (login page, /login/callback handler,
340
- route guards), plus Data schema listing, Release build lookup, environment
341
- config, and safe .gitignore defaults.
195
+ console.log(`Blocks OS CLI
196
+
197
+ Usage:
198
+ blocks-os <command> [options]
199
+
200
+ Global options:
201
+ --version Print CLI version.
202
+ --json Print machine-readable JSON where supported.
203
+ --api-url <url> Override Blocks API URL for this command.
204
+ --account <name> Use a named account profile; default is implicit.
205
+ --project <tenantId> Use a project tenant for project-scoped commands.
206
+ --dry-run Show planned mutation without calling the API.
207
+ --yes Skip mutation confirmation after explicit approval.
208
+
209
+ Setup and health:
210
+ blocks-os init
211
+ Create local Blocks workspace files: blocks.json, data schema/rules folders,
212
+ release deploy config, and .env.example.
213
+
214
+ blocks-os doctor [--json]
215
+ Check local Node.js, OIDC config, token cache, selected project, and config
216
+ file locations. Does not mutate cloud resources.
217
+
218
+ Auth:
219
+ blocks-os auth:add [account] --client-id <id> --client-secret <secret>
220
+ Save OIDC client settings for the CLI. Defaults target production Blocks
221
+ URLs, root tenant, scope, and localhost callback. Secret is stored separately.
222
+
223
+ blocks-os login
224
+ Browser Authorization Code + PKCE login. Opens IAM, captures the localhost
225
+ callback, stores account access and refresh tokens, and auto-refreshes later.
226
+
227
+ blocks-os login:device
228
+ Device-code login for terminals or agents where a browser callback is not
229
+ convenient. Uses the same saved account profile.
230
+
231
+ blocks-os auth:status [--json]
232
+ Show configured account, token freshness, refresh-token availability, and
233
+ selected project without printing secrets.
234
+
235
+ blocks-os auth:refresh [--project] [--json]
236
+ Force account token refresh, or project token refresh with --project.
237
+
238
+ blocks-os auth:repair [account] [--dry-run] [--yes] [--json]
239
+ Clear broken local secure-store auth state while keeping account profile
240
+ config. Use after OS credential migration, stale DPAPI, or corrupted tokens.
241
+
242
+ blocks-os auth:list [--json]
243
+ List saved account profiles without showing secrets.
244
+
245
+ blocks-os auth:show [account]
246
+ Print one account profile with secret redacted as configured/missing.
247
+
248
+ blocks-os auth:use <account>
249
+ Switch the active named account profile.
250
+
251
+ blocks-os auth:remove <account>
252
+ Remove account config, cached tokens, and stored client secret for that
253
+ account.
254
+
255
+ blocks-os logout
256
+ Revoke the current refresh token when possible and remove local session data.
257
+
258
+ Projects:
259
+ blocks-os projects:list [--json]
260
+ List accessible Blocks projects via /os/v4/Project/Gets using the account
261
+ token. Read-only.
262
+
263
+ blocks-os projects:get [tenantId] [--json]
264
+ Read one project from Project/Gets. Uses selected project when tenantId is
265
+ omitted. Read-only.
266
+
267
+ blocks-os projects:create <name> [--env dev] [--yes] [--dry-run] [--json]
268
+ Create a Blocks project/environment via /os/v4/Project/Create using the
269
+ account token. Mutating; supports dry-run and confirmation.
270
+
271
+ blocks-os use <project-tenant-id>
272
+ Save the selected project tenant globally and in blocks.json when present.
273
+ Does not call cloud APIs.
274
+
275
+ IAM:
276
+ blocks-os iam:me [--json]
277
+ Read the current user from IAM using the account token. This is the only IAM
278
+ admin surface exposed in the MVP.
279
+
280
+ Data:
281
+ blocks-os data:validate [--json]
282
+ Validate local blocks/data/schemas/*.json and blocks/data/rules.json before
283
+ pushing. Local-only.
284
+
285
+ blocks-os data:schema:list [--json]
286
+ List project schemas via /data/v4/schemas using an impersonated project
287
+ token. Read-only.
288
+
289
+ blocks-os data:schema:pull [--json]
290
+ Download project schemas into blocks/data/schemas/*.json. Writes local files
291
+ only.
292
+
293
+ blocks-os data:schema:push [--dry-run] [--yes] [--json]
294
+ Create or update project schemas via /data/v4/schemas/define. Mutating;
295
+ uses POST for create and PUT for update.
296
+
297
+ blocks-os data:rules:pull [--json]
298
+ Download data-access policies into blocks/data/rules.json. Writes local
299
+ files only.
300
+
301
+ blocks-os data:rules:deploy [--dry-run] [--yes] [--json]
302
+ Apply schema security and data-access policies. Mutating; supports dry-run
303
+ and confirmation.
304
+
305
+ blocks-os data:reload [--dry-run] [--yes] [--json]
306
+ Reload Data schema configuration so staged schema/rule changes become live.
307
+ Mutating; calls POST /data/v4/schema-configurations/reload.
308
+
309
+ Localization:
310
+ blocks-os localization:validate --module <name> --language <culture> [--file <path>] [--json]
311
+ Validate a local i18n JSON dictionary. Supports nested JSON input and
312
+ validates the flattened key/value set locally.
313
+
314
+ blocks-os localization:push --module <name> --language <culture> [--file <path>] [--route <route>] [--context <text>] [--dry-run] [--yes] [--json]
315
+ Create or update Localization keys from a local i18n JSON dictionary via
316
+ /localization/v4/Key/SaveKeys. Creates the module first when it is missing.
317
+
318
+ blocks-os localization:pull --module <name> --language <culture> [--out <path>] [--json]
319
+ Download published cloud localization via
320
+ /localization/v4/Key/GetCloudUilmFile and write a local JSON dictionary.
321
+
322
+ Release:
323
+ blocks-os release:deploy --repo-id <repoId> [--dry-run] [--yes] [--json]
324
+ Trigger a manual Release build/deploy for a configured repository. Mutating;
325
+ no artifact upload is performed by this CLI.
326
+
327
+ blocks-os release:status <buildId> [--json]
328
+ Read Release build status by build id. Read-only.
329
+
330
+ blocks-os release:builds:list --repo-id <repoId> [--json]
331
+ List Release build details for a repository. Read-only.
332
+
333
+ blocks-os release:builds:get <buildId> [--json]
334
+ Alias for release:status. Read-only.
335
+
336
+ Scaffold:
337
+ blocks-os new web <name> --x-blocks-key <tenantId> --app-domain <domain> --blocks-api-url <url> [--client-id <oidcClientId>] [--oidc-url <url>]
338
+ Create a Vite React starter app with a real browser Authorization Code +
339
+ PKCE login flow against Blocks IAM (login page, /login/callback handler,
340
+ route guards), plus Data schema listing, Release build lookup, environment
341
+ config, and safe .gitignore defaults.
342
342
  `);
343
343
  }
@@ -15,6 +15,7 @@ export type TokenSet = {
15
15
  expiresAt?: string;
16
16
  idToken?: string;
17
17
  refreshToken?: string;
18
+ refreshTokenExpiresAt?: string;
18
19
  scope?: string;
19
20
  tokenType?: string;
20
21
  };
@@ -8,12 +8,15 @@ export type TokenResponse = {
8
8
  expires_in?: number;
9
9
  id_token?: string;
10
10
  refresh_token?: string;
11
+ refresh_expires_in?: number;
12
+ refresh_token_expires_in?: number;
11
13
  scope?: string;
12
14
  token_type?: string;
13
15
  };
14
16
  export declare function randomState(): string;
15
17
  export declare function expiresAtFromToken(response: TokenResponse, accessToken: string): string | undefined;
16
18
  export declare function expiresAtFromJwtFirst(response: TokenResponse, accessToken: string): string | undefined;
19
+ export declare function refreshExpiresAtFromToken(response: TokenResponse, refreshToken?: string): string | undefined;
17
20
  export declare function isExpiring(expiresAt?: string): boolean;
18
21
  export declare function applyAccountToken(config: BlocksCliConfig, store: BlocksTokenStore, account: string, clientId: string, response: TokenResponse): {
19
22
  config: BlocksCliConfig;
package/dist/lib/token.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { randomBytes } from "node:crypto";
2
2
  import { decodeJwtPayload, tenantFromToken } from "./jwt.js";
3
3
  const EXPIRY_SKEW_MS = 60_000;
4
+ const DEFAULT_REFRESH_TOKEN_LIFETIME_SECONDS = 30 * 60;
4
5
  export function randomState() {
5
6
  return randomBytes(24).toString("base64url");
6
7
  }
@@ -21,6 +22,26 @@ export function expiresAtFromJwtFirst(response, accessToken) {
21
22
  }
22
23
  return expiresAtFromToken(response, accessToken);
23
24
  }
25
+ export function refreshExpiresAtFromToken(response, refreshToken) {
26
+ if (refreshToken) {
27
+ try {
28
+ const payload = decodeJwtPayload(refreshToken);
29
+ if (typeof payload.exp === "number") {
30
+ return new Date(payload.exp * 1000).toISOString();
31
+ }
32
+ }
33
+ catch {
34
+ // Opaque refresh tokens have no JWT payload; use server metadata or the configured lifetime.
35
+ }
36
+ }
37
+ const expiresIn = response.refresh_expires_in ?? response.refresh_token_expires_in;
38
+ if (typeof expiresIn === "number" && expiresIn > 0) {
39
+ return new Date(Date.now() + expiresIn * 1000).toISOString();
40
+ }
41
+ return refreshToken
42
+ ? new Date(Date.now() + DEFAULT_REFRESH_TOKEN_LIFETIME_SECONDS * 1000).toISOString()
43
+ : undefined;
44
+ }
24
45
  export function isExpiring(expiresAt) {
25
46
  if (!expiresAt)
26
47
  return true;
@@ -34,12 +55,17 @@ export function applyAccountToken(config, store, account, clientId, response) {
34
55
  // rotate it (still valid, just not reissued) -- fall back to the previous
35
56
  // one instead of overwriting a working refresh token with undefined.
36
57
  const previousRefreshToken = store.accounts[account]?.account?.refreshToken;
58
+ const previousRefreshTokenExpiresAt = store.accounts[account]?.account?.refreshTokenExpiresAt;
59
+ const refreshToken = response.refresh_token ?? previousRefreshToken;
37
60
  const tokenSet = {
38
61
  accessToken: response.access_token,
39
62
  accountTenant: tenantFromToken(response.access_token),
40
- expiresAt: expiresAtFromToken(response, response.access_token),
63
+ expiresAt: expiresAtFromJwtFirst(response, response.access_token),
41
64
  idToken: response.id_token,
42
- refreshToken: response.refresh_token ?? previousRefreshToken,
65
+ refreshToken,
66
+ refreshTokenExpiresAt: response.refresh_token
67
+ ? refreshExpiresAtFromToken(response, response.refresh_token)
68
+ : previousRefreshTokenExpiresAt,
43
69
  scope: response.scope,
44
70
  tokenType: response.token_type ?? "Bearer"
45
71
  };
@@ -72,10 +98,15 @@ export function applyProjectToken(config, store, account, tenantId, response) {
72
98
  throw new Error(response.error_description ?? response.error ?? "Project token response did not include an access token");
73
99
  }
74
100
  const previousRefreshToken = store.accounts[account]?.projects?.[tenantId]?.refreshToken;
101
+ const previousRefreshTokenExpiresAt = store.accounts[account]?.projects?.[tenantId]?.refreshTokenExpiresAt;
102
+ const refreshToken = response.refresh_token ?? previousRefreshToken;
75
103
  const tokenSet = {
76
104
  accessToken: response.access_token,
77
105
  expiresAt: expiresAtFromJwtFirst(response, response.access_token),
78
- refreshToken: response.refresh_token ?? previousRefreshToken,
106
+ refreshToken,
107
+ refreshTokenExpiresAt: response.refresh_token
108
+ ? refreshExpiresAtFromToken(response, response.refresh_token)
109
+ : previousRefreshTokenExpiresAt,
79
110
  scope: response.scope,
80
111
  tokenType: response.token_type ?? "Bearer"
81
112
  };
package/package.json CHANGED
@@ -1,47 +1,47 @@
1
- {
2
- "name": "@seliseblocks/cli-os",
3
- "version": "0.1.1",
4
- "description": "CLI for SELISE Blocks OS project setup and configuration.",
5
- "license": "MIT",
6
- "type": "module",
7
- "bin": {
8
- "blocks-os": "./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
+ {
2
+ "name": "@seliseblocks/cli-os",
3
+ "version": "0.1.2",
4
+ "description": "CLI for SELISE Blocks OS project setup and configuration.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "blocks-os": "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
+ }