@terros-inc/cli 1.0.5 → 1.0.7

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/cli.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import './dist/index.js'
2
+ import './dist/index.js'
@@ -0,0 +1,12 @@
1
+ import { join } from 'node:path';
2
+ import { platform, release } from 'node:os';
3
+ import { readFileSync } from 'node:fs';
4
+ export function getAnalyticsHeaders() {
5
+ const packageJson = JSON.parse(readFileSync(join(import.meta.url, 'package.json'), 'utf8'));
6
+ return {
7
+ 'Terros-Platform': platform(),
8
+ 'Terros-Platform-Version': release(),
9
+ 'Terros-Bundle-Identifier': 'com.terros.cli',
10
+ 'Terros-App-Version': packageJson.version,
11
+ };
12
+ }
package/dist/api/query.js CHANGED
@@ -1,13 +1,32 @@
1
+ import * as process from 'node:process';
1
2
  import { getAuthorizationHeader } from "./auth.js";
3
+ import { getAnalyticsHeaders } from "./analytics.js";
2
4
  export async function queryTerrosAPI(path, input) {
5
+ const endpoint = getApiEndpoint();
6
+ const impersonation = getImpersonationHeaders();
7
+ const analytics = getAnalyticsHeaders();
3
8
  const authorization = await getAuthorizationHeader();
4
- const res = await fetch(`https://api.terros.com${path}`, {
9
+ const res = await fetch(`${endpoint}${path}`, {
5
10
  method: 'POST',
6
11
  headers: {
12
+ ...analytics,
13
+ ...impersonation,
7
14
  Authorization: authorization,
8
15
  'Content-Type': 'application/json',
9
16
  },
10
- body: JSON.stringify(input)
17
+ body: JSON.stringify(input),
11
18
  });
12
- return await res.json();
19
+ return (await res.json());
20
+ }
21
+ function getApiEndpoint() {
22
+ const envEndpoint = process.env.TERROS_API_ENDPOINT;
23
+ if (envEndpoint)
24
+ return envEndpoint;
25
+ return 'https://api.terros.com';
26
+ }
27
+ function getImpersonationHeaders() {
28
+ const userId = process.env.TERROS_IMPERSONATE;
29
+ if (userId)
30
+ return { impersonate_user_id: userId };
31
+ return {};
13
32
  }
@@ -1,20 +1,20 @@
1
+ import open from 'open';
2
+ import { DateTime } from 'luxon';
1
3
  import { AUTH0_CLIENT_ID, AUTH0_DOMAIN } from "./constants.js";
2
- import open from "open";
3
- import { DateTime } from "luxon";
4
4
  export async function signInToAuth0() {
5
5
  const res = await fetch(`${AUTH0_DOMAIN}/oauth/device/code`, {
6
6
  method: 'POST',
7
7
  headers: {
8
- "Content-Type": 'application/json'
8
+ 'Content-Type': 'application/json',
9
9
  },
10
10
  body: JSON.stringify({
11
11
  client_id: AUTH0_CLIENT_ID,
12
- scope: 'offline_access'
13
- })
12
+ scope: 'offline_access',
13
+ }),
14
14
  });
15
15
  if (!res.ok)
16
16
  throw new Error('Failed to get OAuth device code');
17
- const body = await res.json();
17
+ const body = (await res.json());
18
18
  const deadline = DateTime.now().plus({ seconds: body.expires_in });
19
19
  await open(body.verification_uri_complete);
20
20
  console.log(`Confirm that the code in your browser matches: ${body.user_code}`);
@@ -33,36 +33,36 @@ async function pollForToken(deadline, interval, deviceCode) {
33
33
  const res = await fetch(`${AUTH0_DOMAIN}/oauth/token`, {
34
34
  method: 'POST',
35
35
  headers: {
36
- "Content-Type": 'application/json',
36
+ 'Content-Type': 'application/json',
37
37
  },
38
38
  body: JSON.stringify({
39
39
  client_id: AUTH0_CLIENT_ID,
40
40
  grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
41
- device_code: deviceCode
42
- })
41
+ device_code: deviceCode,
42
+ }),
43
43
  });
44
44
  if (!res.ok) {
45
- const body = await res.json();
45
+ const body = (await res.json());
46
46
  if (body.error === 'access_denied')
47
47
  throw new Error('Authorization request denied');
48
48
  return pollForToken(deadline, interval, deviceCode);
49
49
  }
50
- return await res.json();
50
+ return (await res.json());
51
51
  }
52
52
  export async function refreshTokens(refreshToken) {
53
53
  const res = await fetch(`${AUTH0_DOMAIN}/oauth/token`, {
54
54
  method: 'POST',
55
55
  headers: {
56
- 'Content-Type': 'application/json'
56
+ 'Content-Type': 'application/json',
57
57
  },
58
58
  body: JSON.stringify({
59
59
  client_id: AUTH0_CLIENT_ID,
60
60
  grant_type: 'refresh_token',
61
- refresh_token: refreshToken
62
- })
61
+ refresh_token: refreshToken,
62
+ }),
63
63
  });
64
64
  if (res.ok) {
65
- return await res.json();
65
+ return (await res.json());
66
66
  }
67
67
  throw new Error('Unable to refresh token, it may be expired. Run `terros auth login` to sign in again');
68
68
  }
@@ -1,8 +1,8 @@
1
- import { DateTime } from 'luxon';
2
- import { homedir } from 'node:os';
3
1
  import { join } from 'node:path';
4
- import { existsSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
5
3
  import { mkdir, writeFile, readFile } from 'node:fs/promises';
4
+ import { existsSync } from 'node:fs';
5
+ import { DateTime } from 'luxon';
6
6
  import { refreshTokens } from "./auth0.js";
7
7
  export async function getTokens() {
8
8
  const tokens = await readTokens();
@@ -1,5 +1,5 @@
1
- import { signInToAuth0 } from "../auth/auth0.js";
2
1
  import { getTokens, saveTokens } from "../auth/tokens.js";
2
+ import { signInToAuth0 } from "../auth/auth0.js";
3
3
  export const authCommands = {
4
4
  login: {
5
5
  description: 'Sign in to Terros',
@@ -18,6 +18,6 @@ export const authCommands = {
18
18
  return;
19
19
  }
20
20
  console.log(tokens.access_token);
21
- }
22
- }
21
+ },
22
+ },
23
23
  };
@@ -1,6 +1,6 @@
1
- import { readFileSync } from 'node:fs';
2
- import { dirname, resolve } from 'node:path';
3
1
  import { fileURLToPath } from 'node:url';
2
+ import { dirname, resolve } from 'node:path';
3
+ import { readFileSync } from 'node:fs';
4
4
  import { parse } from 'yaml';
5
5
  import { getPathParts } from "./util.js";
6
6
  import { getEndpointParameters } from "./parameters.js";
@@ -6,7 +6,7 @@ export function buildEndpointInput(endpoint, params) {
6
6
  if (unknownParameterNames.length > 0) {
7
7
  throw new Error(`Unknown parameter(s): ${unknownParameterNames.map((name) => `--${name}`).join(', ')}`);
8
8
  }
9
- const missingParameters = endpoint.parameters.filter((parameter) => (parameter.required && !Object.hasOwn(parsedParams, parameter.name)));
9
+ const missingParameters = endpoint.parameters.filter((parameter) => parameter.required && !Object.hasOwn(parsedParams, parameter.name));
10
10
  if (missingParameters.length > 0) {
11
11
  throw new Error(`Missing required parameter(s): ${missingParameters.map((parameter) => `--${parameter.name}`).join(', ')}`);
12
12
  }
@@ -47,7 +47,12 @@ function getHiddenWrapperPrefix(endpoint) {
47
47
  if (propertyNames.length !== 1)
48
48
  return [];
49
49
  const wrapperName = propertyNames[0];
50
- return wrapperName ? [wrapperName] : [];
50
+ if (!wrapperName)
51
+ return [];
52
+ const wrapperSchema = schema.properties[wrapperName];
53
+ if (!wrapperSchema || !('type' in wrapperSchema) || wrapperSchema.type !== 'object')
54
+ return [];
55
+ return [wrapperName];
51
56
  }
52
57
  function setNestedValue(input, path, value) {
53
58
  if (path.length === 0) {
@@ -0,0 +1,69 @@
1
+ import { expect } from 'vitest';
2
+ import { buildEndpointInput } from "./input.js";
3
+ describe('buildEndpointInput', () => {
4
+ it('should not add a hidden wrapper prefix for a single non-object property', () => {
5
+ const endpoint = {
6
+ path: '/user/profile',
7
+ parameters: [
8
+ {
9
+ name: 'userId',
10
+ type: 'string',
11
+ required: false,
12
+ },
13
+ ],
14
+ properties: {
15
+ type: 'object',
16
+ properties: {
17
+ userId: {
18
+ type: 'string',
19
+ },
20
+ },
21
+ },
22
+ };
23
+ const params = {
24
+ _: ['user', 'profile'],
25
+ userId: 'U:dj',
26
+ };
27
+ const expected = {
28
+ userId: 'U:dj',
29
+ };
30
+ const result = buildEndpointInput(endpoint, params);
31
+ expect(result).toEqual(expected);
32
+ });
33
+ it('should add a hidden wrapper prefix for a single object property', () => {
34
+ const endpoint = {
35
+ path: '/user/profile',
36
+ parameters: [
37
+ {
38
+ name: 'userId',
39
+ type: 'string',
40
+ required: false,
41
+ },
42
+ ],
43
+ properties: {
44
+ type: 'object',
45
+ properties: {
46
+ profile: {
47
+ type: 'object',
48
+ properties: {
49
+ userId: {
50
+ type: 'string',
51
+ },
52
+ },
53
+ },
54
+ },
55
+ },
56
+ };
57
+ const params = {
58
+ _: ['user', 'profile'],
59
+ userId: 'U:dj',
60
+ };
61
+ const expected = {
62
+ profile: {
63
+ userId: 'U:dj',
64
+ },
65
+ };
66
+ const result = buildEndpointInput(endpoint, params);
67
+ expect(result).toEqual(expected);
68
+ });
69
+ });
@@ -37,9 +37,7 @@ function resolveSchema(schema, components, seen = new Set()) {
37
37
  }
38
38
  function flattenSchema(schema, context) {
39
39
  const resolved = resolveSchema(schema, context.components);
40
- if ('type' in resolved
41
- && resolved.type === 'object'
42
- && resolved.properties) {
40
+ if ('type' in resolved && resolved.type === 'object' && resolved.properties) {
43
41
  return Object.entries(resolved.properties).flatMap(([name, childSchema]) => {
44
42
  const required = resolved.required?.includes(name) ?? false;
45
43
  return flattenSchema(childSchema, {
@@ -54,7 +52,7 @@ function flattenSchema(schema, context) {
54
52
  name: context.path.join('.'),
55
53
  type: getSchemaType(resolved, context.components),
56
54
  required: context.required,
57
- ...(schema.description ?? resolved.description
55
+ ...((schema.description ?? resolved.description)
58
56
  ? { description: schema.description ?? resolved.description }
59
57
  : {}),
60
58
  },
@@ -69,9 +67,7 @@ function hideSingleObjectWrapper(parameters) {
69
67
  return parameters;
70
68
  return parameters.map((parameter) => ({
71
69
  ...parameter,
72
- name: parameter.name.startsWith(`${wrapperName}.`)
73
- ? parameter.name.slice(wrapperName.length + 1)
74
- : parameter.name,
70
+ name: parameter.name.startsWith(`${wrapperName}.`) ? parameter.name.slice(wrapperName.length + 1) : parameter.name,
75
71
  }));
76
72
  }
77
73
  export function getEndpointParameters(schema, components) {
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import minimist from 'minimist';
2
2
  import { formatCommandsHelp, formatSubcommandParametersHelp, formatSubcommandsHelp, HELP_PARENT_MESSAGE, } from "./messages.js";
3
- import { loadEndpoints } from "./crud/index.js";
4
3
  import { buildEndpointInput } from "./crud/input.js";
4
+ import { loadEndpoints } from "./crud/index.js";
5
+ import { getCommandGroup, getCommandNames, getSubcommand, getSubcommandNames } from "./commands/index.js";
5
6
  import { queryTerrosAPI } from "./api/query.js";
6
- import { getCommandGroup, getCommandNames, getSubcommand, getSubcommandNames, } from "./commands/index.js";
7
7
  async function main() {
8
8
  const params = minimist(process.argv.slice(2));
9
9
  const commands = params._;
package/dist/messages.js CHANGED
@@ -29,11 +29,7 @@ export function formatSubcommandsHelp(command, subcommands) {
29
29
  return lines.join('\n');
30
30
  }
31
31
  export function formatSubcommandParametersHelp(command, subcommand, parameters) {
32
- const lines = [
33
- `usage: terros ${command} ${subcommand} [parameters]`,
34
- '',
35
- 'Parameters:',
36
- ];
32
+ const lines = [`usage: terros ${command} ${subcommand} [parameters]`, '', 'Parameters:'];
37
33
  if (parameters.length === 0) {
38
34
  lines.push(' none');
39
35
  return lines.join('\n');
package/package.json CHANGED
@@ -14,10 +14,13 @@
14
14
  },
15
15
  "description": "Command-line interface for Terros Sales platform",
16
16
  "devDependencies": {
17
+ "@types/luxon": "^3.7.1",
17
18
  "@types/minimist": "^1.2.5",
18
19
  "@types/node": "^24.13.1",
20
+ "oxfmt": "^0.54.0",
21
+ "oxlint": "^1.69.0",
19
22
  "typescript": "^6.0.3",
20
- "@types/luxon": "^3.7.1"
23
+ "vitest": "^4.1.8"
21
24
  },
22
25
  "engines": {
23
26
  "node": ">=24"
@@ -44,10 +47,12 @@
44
47
  "url": "git+https://github.com/terros-inc/cli.git"
45
48
  },
46
49
  "type": "module",
47
- "version": "1.0.5",
50
+ "version": "1.0.7",
48
51
  "scripts": {
49
52
  "fetch-openapi": "wget https://docs.terros.com/openapi/terros.yml",
50
53
  "build": "tsc",
54
+ "lint": "oxlint",
55
+ "test": "vitest run",
51
56
  "start": "node src/index.ts"
52
57
  }
53
58
  }
package/terros.yml CHANGED
@@ -2922,9 +2922,9 @@ paths:
2922
2922
  evalDate: 9007199254740991
2923
2923
  teamId: Team.123
2924
2924
  categories: []
2925
- createdAt: 1781654399759
2925
+ createdAt: 1781726665720
2926
2926
  createdBy: U:123
2927
- updatedAt: 1781654399760
2927
+ updatedAt: 1781726665720
2928
2928
  updatedBy: U:123
2929
2929
  user:
2930
2930
  userId: U:123
@@ -2990,9 +2990,9 @@ paths:
2990
2990
  evalDate: 9007199254740991
2991
2991
  teamId: Team.123
2992
2992
  categories: []
2993
- createdAt: 1781654399759
2993
+ createdAt: 1781726665720
2994
2994
  createdBy: U:123
2995
- updatedAt: 1781654399760
2995
+ updatedAt: 1781726665720
2996
2996
  updatedBy: U:123
2997
2997
  user:
2998
2998
  userId: U:123
@@ -3199,9 +3199,9 @@ paths:
3199
3199
  evalDate: 9007199254740991
3200
3200
  teamId: Team.123
3201
3201
  categories: []
3202
- createdAt: 1781654399759
3202
+ createdAt: 1781726665720
3203
3203
  createdBy: U:123
3204
- updatedAt: 1781654399760
3204
+ updatedAt: 1781726665720
3205
3205
  updatedBy: U:123
3206
3206
  user:
3207
3207
  userId: U:123
@@ -4699,7 +4699,7 @@ paths:
4699
4699
  task:
4700
4700
  taskId: Task.123
4701
4701
  status: Completed
4702
- completedAt: 1781654399759
4702
+ completedAt: 1781726665719
4703
4703
  schema:
4704
4704
  type: object
4705
4705
  properties:
@@ -6842,8 +6842,8 @@ webhooks:
6842
6842
  email: jdoe@example.com
6843
6843
  evalState: Pending
6844
6844
  possible: 10
6845
- createdAt: "1781654399760"
6846
- updatedAt: "1781654399760"
6845
+ createdAt: "1781726665720"
6846
+ updatedAt: "1781726665720"
6847
6847
  Evaluation Updated:
6848
6848
  value:
6849
6849
  entity: Evaluation
@@ -6858,8 +6858,8 @@ webhooks:
6858
6858
  email: jdoe@example.com
6859
6859
  evalState: Pending
6860
6860
  possible: 10
6861
- createdAt: "1781654399760"
6862
- updatedAt: "1781654399760"
6861
+ createdAt: "1781726665720"
6862
+ updatedAt: "1781726665720"
6863
6863
  Evaluation Removed:
6864
6864
  value:
6865
6865
  entity: Evaluation
@@ -6974,8 +6974,8 @@ webhooks:
6974
6974
  longitude: -104.9942
6975
6975
  parentId: Team.AFCWest
6976
6976
  description: Professional American football team based in Denver, Colorado.
6977
- createdAt: 2026-06-16T23:59:59.756Z
6978
- updatedAt: 2026-06-16T23:59:59.756Z
6977
+ createdAt: 2026-06-17T20:04:25.717Z
6978
+ updatedAt: 2026-06-17T20:04:25.717Z
6979
6979
  members: []
6980
6980
  Team Updated:
6981
6981
  value:
@@ -6991,8 +6991,8 @@ webhooks:
6991
6991
  longitude: -104.9942
6992
6992
  parentId: Team.AFCWest
6993
6993
  description: Professional American football team based in Denver, Colorado.
6994
- createdAt: 2026-06-16T23:59:59.756Z
6995
- updatedAt: 2026-06-16T23:59:59.756Z
6994
+ createdAt: 2026-06-17T20:04:25.717Z
6995
+ updatedAt: 2026-06-17T20:04:25.717Z
6996
6996
  members:
6997
6997
  - userId: U:user123
6998
6998
  lastName: Smith
@@ -7102,8 +7102,8 @@ webhooks:
7102
7102
  email: john.smith@company.com
7103
7103
  firstName: John
7104
7104
  lastName: Smith
7105
- createdAt: "1781654399756"
7106
- updatedAt: "1781654399756"
7105
+ createdAt: "1781726665716"
7106
+ updatedAt: "1781726665716"
7107
7107
  User Updated:
7108
7108
  value:
7109
7109
  entity: User
@@ -7113,8 +7113,8 @@ webhooks:
7113
7113
  email: john.smith@company.com
7114
7114
  firstName: John
7115
7115
  lastName: Smith
7116
- createdAt: "1781654399756"
7117
- updatedAt: "1781654399756"
7116
+ createdAt: "1781726665716"
7117
+ updatedAt: "1781726665716"
7118
7118
  User Removed:
7119
7119
  value:
7120
7120
  entity: User
@@ -7187,7 +7187,7 @@ components:
7187
7187
  maximum: 8640000000000000
7188
7188
  title: Timestamp
7189
7189
  description: A timestamp in UTC milliseconds
7190
- example: 1781654399510
7190
+ example: 1781726665493
7191
7191
  latlng:
7192
7192
  type: object
7193
7193
  properties: