@shuffl/cli 0.3.0 → 0.4.0
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 +14 -0
- package/dist/commands.generated.js +341 -17
- package/dist/index.js +64 -35
- package/dist/types.d.ts +2 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -56,4 +56,18 @@ shuffl knowledge sources import-pdf --input-file pdf-command.json --json
|
|
|
56
56
|
|
|
57
57
|
Management writes require `requestKey` and return `{receipt, result}`. Sensitive writes also require `confirm` equal to the SDK operation name. Keep the same input/key on retry: the duplicate returns only the receipt and `result: null`, without repeating the write or disclosing an old result. Read current resources to reconcile an unknown outcome.
|
|
58
58
|
|
|
59
|
+
## Company settings
|
|
60
|
+
|
|
61
|
+
Commands under `shuffl company` take a company key (`shuffl_company_`) from **Company settings → API keys** instead of a workspace key:
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
credential-command | shuffl login --company --base-url https://YOUR-SHUFFL-HOST --token-stdin
|
|
65
|
+
shuffl company current --json
|
|
66
|
+
shuffl company members list --limit 100 --json
|
|
67
|
+
shuffl company members remove --user-id USER_ID --json
|
|
68
|
+
shuffl logout --company
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Or set `SHUFFL_COMPANY_KEY` and `SHUFFL_BASE_URL`. The company key is saved in `company.json` next to `config.json`, and only `shuffl company` commands send it; `shuffl logout --company` removes it. Each command's `--help` names its company permission. See [company keys](https://shuffl-rebuild.vercel.app/docs/api-permissions#company-keys).
|
|
72
|
+
|
|
59
73
|
Full guide: [Use the Shuffl CLI](https://shuffl-rebuild.vercel.app/docs/cli).
|
|
@@ -2595,6 +2595,10 @@ export const commands = [
|
|
|
2595
2595
|
const: 'onboarding.candidates',
|
|
2596
2596
|
type: 'string',
|
|
2597
2597
|
},
|
|
2598
|
+
{
|
|
2599
|
+
const: 'onboarding.completeTodo',
|
|
2600
|
+
type: 'string',
|
|
2601
|
+
},
|
|
2598
2602
|
{
|
|
2599
2603
|
const: 'onboarding.enroll',
|
|
2600
2604
|
type: 'string',
|
|
@@ -6436,6 +6440,37 @@ export const commands = [
|
|
|
6436
6440
|
scope: 'onboarding:read',
|
|
6437
6441
|
summary: 'List people who could be enrolled in onboarding',
|
|
6438
6442
|
},
|
|
6443
|
+
{
|
|
6444
|
+
access: 'write',
|
|
6445
|
+
command: ['onboarding', 'complete-todo'],
|
|
6446
|
+
input: {
|
|
6447
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
6448
|
+
additionalProperties: false,
|
|
6449
|
+
properties: {
|
|
6450
|
+
confirm: {
|
|
6451
|
+
const: 'onboarding.completeTodo',
|
|
6452
|
+
type: 'string',
|
|
6453
|
+
},
|
|
6454
|
+
done: {
|
|
6455
|
+
type: 'boolean',
|
|
6456
|
+
},
|
|
6457
|
+
requestKey: {
|
|
6458
|
+
maxLength: 128,
|
|
6459
|
+
minLength: 1,
|
|
6460
|
+
type: 'string',
|
|
6461
|
+
},
|
|
6462
|
+
todoId: {
|
|
6463
|
+
type: 'string',
|
|
6464
|
+
},
|
|
6465
|
+
},
|
|
6466
|
+
required: ['done', 'todoId', 'confirm', 'requestKey'],
|
|
6467
|
+
type: 'object',
|
|
6468
|
+
},
|
|
6469
|
+
path: ['onboarding', 'completeTodo'],
|
|
6470
|
+
permission: 'onboarding.completeTodo',
|
|
6471
|
+
scope: 'self:write',
|
|
6472
|
+
summary: 'Check off (or reopen) one of your own onboarding to-dos',
|
|
6473
|
+
},
|
|
6439
6474
|
{
|
|
6440
6475
|
access: 'write',
|
|
6441
6476
|
command: ['onboarding', 'enroll'],
|
|
@@ -6553,7 +6588,7 @@ export const commands = [
|
|
|
6553
6588
|
path: ['onboarding', 'journey'],
|
|
6554
6589
|
permission: 'onboarding.journey',
|
|
6555
6590
|
scope: 'self:read',
|
|
6556
|
-
summary: 'Your onboarding timeline: start date, buddy, manager
|
|
6591
|
+
summary: 'Your onboarding timeline: start date, buddy, manager, upcoming check-ins, your to-dos and resources',
|
|
6557
6592
|
},
|
|
6558
6593
|
{
|
|
6559
6594
|
access: 'read',
|
|
@@ -6807,6 +6842,171 @@ export const commands = [
|
|
|
6807
6842
|
sendTime: {
|
|
6808
6843
|
type: 'string',
|
|
6809
6844
|
},
|
|
6845
|
+
steps: {
|
|
6846
|
+
items: {
|
|
6847
|
+
additionalProperties: false,
|
|
6848
|
+
properties: {
|
|
6849
|
+
body: {
|
|
6850
|
+
type: 'string',
|
|
6851
|
+
},
|
|
6852
|
+
checkpoint: {
|
|
6853
|
+
anyOf: [
|
|
6854
|
+
{
|
|
6855
|
+
const: 'day_30',
|
|
6856
|
+
type: 'string',
|
|
6857
|
+
},
|
|
6858
|
+
{
|
|
6859
|
+
const: 'day_365',
|
|
6860
|
+
type: 'string',
|
|
6861
|
+
},
|
|
6862
|
+
{
|
|
6863
|
+
const: 'day_60',
|
|
6864
|
+
type: 'string',
|
|
6865
|
+
},
|
|
6866
|
+
{
|
|
6867
|
+
const: 'day_90',
|
|
6868
|
+
type: 'string',
|
|
6869
|
+
},
|
|
6870
|
+
],
|
|
6871
|
+
},
|
|
6872
|
+
id: {
|
|
6873
|
+
type: 'string',
|
|
6874
|
+
},
|
|
6875
|
+
kind: {
|
|
6876
|
+
anyOf: [
|
|
6877
|
+
{
|
|
6878
|
+
const: 'checkpoint',
|
|
6879
|
+
type: 'string',
|
|
6880
|
+
},
|
|
6881
|
+
{
|
|
6882
|
+
const: 'message',
|
|
6883
|
+
type: 'string',
|
|
6884
|
+
},
|
|
6885
|
+
{
|
|
6886
|
+
const: 'task',
|
|
6887
|
+
type: 'string',
|
|
6888
|
+
},
|
|
6889
|
+
],
|
|
6890
|
+
},
|
|
6891
|
+
offsetDays: {
|
|
6892
|
+
type: 'number',
|
|
6893
|
+
},
|
|
6894
|
+
personId: {
|
|
6895
|
+
type: ['null', 'string'],
|
|
6896
|
+
},
|
|
6897
|
+
recipient: {
|
|
6898
|
+
anyOf: [
|
|
6899
|
+
{
|
|
6900
|
+
const: 'buddy',
|
|
6901
|
+
type: 'string',
|
|
6902
|
+
},
|
|
6903
|
+
{
|
|
6904
|
+
const: 'hire',
|
|
6905
|
+
type: 'string',
|
|
6906
|
+
},
|
|
6907
|
+
{
|
|
6908
|
+
const: 'hr',
|
|
6909
|
+
type: 'string',
|
|
6910
|
+
},
|
|
6911
|
+
{
|
|
6912
|
+
const: 'manager',
|
|
6913
|
+
type: 'string',
|
|
6914
|
+
},
|
|
6915
|
+
{
|
|
6916
|
+
const: 'person',
|
|
6917
|
+
type: 'string',
|
|
6918
|
+
},
|
|
6919
|
+
],
|
|
6920
|
+
},
|
|
6921
|
+
resources: {
|
|
6922
|
+
items: {
|
|
6923
|
+
anyOf: [
|
|
6924
|
+
{
|
|
6925
|
+
additionalProperties: false,
|
|
6926
|
+
properties: {
|
|
6927
|
+
sourceId: {
|
|
6928
|
+
type: 'string',
|
|
6929
|
+
},
|
|
6930
|
+
title: {
|
|
6931
|
+
type: 'string',
|
|
6932
|
+
},
|
|
6933
|
+
type: {
|
|
6934
|
+
const: 'knowledge',
|
|
6935
|
+
type: 'string',
|
|
6936
|
+
},
|
|
6937
|
+
},
|
|
6938
|
+
required: ['sourceId', 'title', 'type'],
|
|
6939
|
+
type: 'object',
|
|
6940
|
+
},
|
|
6941
|
+
{
|
|
6942
|
+
additionalProperties: false,
|
|
6943
|
+
properties: {
|
|
6944
|
+
label: {
|
|
6945
|
+
type: 'string',
|
|
6946
|
+
},
|
|
6947
|
+
type: {
|
|
6948
|
+
const: 'link',
|
|
6949
|
+
type: 'string',
|
|
6950
|
+
},
|
|
6951
|
+
url: {
|
|
6952
|
+
type: 'string',
|
|
6953
|
+
},
|
|
6954
|
+
},
|
|
6955
|
+
required: ['label', 'type', 'url'],
|
|
6956
|
+
type: 'object',
|
|
6957
|
+
},
|
|
6958
|
+
{
|
|
6959
|
+
additionalProperties: false,
|
|
6960
|
+
properties: {
|
|
6961
|
+
communityId: {
|
|
6962
|
+
type: 'string',
|
|
6963
|
+
},
|
|
6964
|
+
name: {
|
|
6965
|
+
type: 'string',
|
|
6966
|
+
},
|
|
6967
|
+
type: {
|
|
6968
|
+
const: 'community',
|
|
6969
|
+
type: 'string',
|
|
6970
|
+
},
|
|
6971
|
+
},
|
|
6972
|
+
required: ['communityId', 'name', 'type'],
|
|
6973
|
+
type: 'object',
|
|
6974
|
+
},
|
|
6975
|
+
],
|
|
6976
|
+
},
|
|
6977
|
+
type: 'array',
|
|
6978
|
+
},
|
|
6979
|
+
title: {
|
|
6980
|
+
type: 'string',
|
|
6981
|
+
},
|
|
6982
|
+
todos: {
|
|
6983
|
+
items: {
|
|
6984
|
+
additionalProperties: false,
|
|
6985
|
+
properties: {
|
|
6986
|
+
id: {
|
|
6987
|
+
type: 'string',
|
|
6988
|
+
},
|
|
6989
|
+
label: {
|
|
6990
|
+
type: 'string',
|
|
6991
|
+
},
|
|
6992
|
+
},
|
|
6993
|
+
required: ['id', 'label'],
|
|
6994
|
+
type: 'object',
|
|
6995
|
+
},
|
|
6996
|
+
type: 'array',
|
|
6997
|
+
},
|
|
6998
|
+
},
|
|
6999
|
+
required: [
|
|
7000
|
+
'id',
|
|
7001
|
+
'kind',
|
|
7002
|
+
'offsetDays',
|
|
7003
|
+
'recipient',
|
|
7004
|
+
'title',
|
|
7005
|
+
],
|
|
7006
|
+
type: 'object',
|
|
7007
|
+
},
|
|
7008
|
+
type: 'array',
|
|
7009
|
+
},
|
|
6810
7010
|
timezone: {
|
|
6811
7011
|
type: 'string',
|
|
6812
7012
|
},
|
|
@@ -10422,14 +10622,6 @@ export const commands = [
|
|
|
10422
10622
|
},
|
|
10423
10623
|
kind: {
|
|
10424
10624
|
anyOf: [
|
|
10425
|
-
{
|
|
10426
|
-
const: 'manager_check_in',
|
|
10427
|
-
type: 'string',
|
|
10428
|
-
},
|
|
10429
|
-
{
|
|
10430
|
-
const: 'new_hire_check_in',
|
|
10431
|
-
type: 'string',
|
|
10432
|
-
},
|
|
10433
10625
|
{
|
|
10434
10626
|
const: 'praise',
|
|
10435
10627
|
type: 'string',
|
|
@@ -10476,14 +10668,6 @@ export const commands = [
|
|
|
10476
10668
|
},
|
|
10477
10669
|
kind: {
|
|
10478
10670
|
anyOf: [
|
|
10479
|
-
{
|
|
10480
|
-
const: 'manager_check_in',
|
|
10481
|
-
type: 'string',
|
|
10482
|
-
},
|
|
10483
|
-
{
|
|
10484
|
-
const: 'new_hire_check_in',
|
|
10485
|
-
type: 'string',
|
|
10486
|
-
},
|
|
10487
10671
|
{
|
|
10488
10672
|
const: 'praise',
|
|
10489
10673
|
type: 'string',
|
|
@@ -12832,4 +13016,144 @@ export const commands = [
|
|
|
12832
13016
|
scope: null,
|
|
12833
13017
|
summary: 'Discover endpoint permissions available to this token',
|
|
12834
13018
|
},
|
|
13019
|
+
{
|
|
13020
|
+
access: 'read',
|
|
13021
|
+
command: ['company', 'current'],
|
|
13022
|
+
credential: 'company',
|
|
13023
|
+
input: null,
|
|
13024
|
+
path: ['current'],
|
|
13025
|
+
permission: 'company.current',
|
|
13026
|
+
scope: null,
|
|
13027
|
+
summary: 'Read the company this key belongs to and the permissions it was granted',
|
|
13028
|
+
},
|
|
13029
|
+
{
|
|
13030
|
+
access: 'read',
|
|
13031
|
+
command: ['company', 'details'],
|
|
13032
|
+
credential: 'company',
|
|
13033
|
+
input: null,
|
|
13034
|
+
path: ['details'],
|
|
13035
|
+
permission: 'company.details',
|
|
13036
|
+
scope: null,
|
|
13037
|
+
summary: 'Read the company name, logo and default workspace',
|
|
13038
|
+
},
|
|
13039
|
+
{
|
|
13040
|
+
access: 'read',
|
|
13041
|
+
command: ['company', 'email-domains', 'list'],
|
|
13042
|
+
credential: 'company',
|
|
13043
|
+
input: null,
|
|
13044
|
+
path: ['emailDomains', 'list'],
|
|
13045
|
+
permission: 'company.emailDomains.list',
|
|
13046
|
+
scope: null,
|
|
13047
|
+
summary: "List the company's email domains and their verification",
|
|
13048
|
+
},
|
|
13049
|
+
{
|
|
13050
|
+
access: 'read',
|
|
13051
|
+
command: ['company', 'members', 'list'],
|
|
13052
|
+
credential: 'company',
|
|
13053
|
+
input: {
|
|
13054
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
13055
|
+
properties: {
|
|
13056
|
+
cursor: {
|
|
13057
|
+
description: 'nextCursor from the previous page.',
|
|
13058
|
+
maxLength: 1024,
|
|
13059
|
+
minLength: 1,
|
|
13060
|
+
type: 'string',
|
|
13061
|
+
},
|
|
13062
|
+
limit: {
|
|
13063
|
+
default: 25,
|
|
13064
|
+
maximum: 100,
|
|
13065
|
+
minimum: 1,
|
|
13066
|
+
type: 'integer',
|
|
13067
|
+
},
|
|
13068
|
+
},
|
|
13069
|
+
type: 'object',
|
|
13070
|
+
},
|
|
13071
|
+
path: ['members', 'list'],
|
|
13072
|
+
permission: 'company.members.list',
|
|
13073
|
+
scope: null,
|
|
13074
|
+
summary: 'List company members and their company roles',
|
|
13075
|
+
},
|
|
13076
|
+
{
|
|
13077
|
+
access: 'write',
|
|
13078
|
+
command: ['company', 'members', 'remove'],
|
|
13079
|
+
credential: 'company',
|
|
13080
|
+
input: {
|
|
13081
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
13082
|
+
properties: {
|
|
13083
|
+
userId: {
|
|
13084
|
+
maxLength: 128,
|
|
13085
|
+
minLength: 1,
|
|
13086
|
+
type: 'string',
|
|
13087
|
+
},
|
|
13088
|
+
},
|
|
13089
|
+
required: ['userId'],
|
|
13090
|
+
type: 'object',
|
|
13091
|
+
},
|
|
13092
|
+
path: ['members', 'remove'],
|
|
13093
|
+
permission: 'company.members.remove',
|
|
13094
|
+
scope: null,
|
|
13095
|
+
summary: 'Remove someone from the company (administrators only with an owner’s key)',
|
|
13096
|
+
},
|
|
13097
|
+
{
|
|
13098
|
+
access: 'write',
|
|
13099
|
+
command: ['company', 'rename'],
|
|
13100
|
+
credential: 'company',
|
|
13101
|
+
input: {
|
|
13102
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
13103
|
+
properties: {
|
|
13104
|
+
name: {
|
|
13105
|
+
maxLength: 100,
|
|
13106
|
+
minLength: 1,
|
|
13107
|
+
type: 'string',
|
|
13108
|
+
},
|
|
13109
|
+
},
|
|
13110
|
+
required: ['name'],
|
|
13111
|
+
type: 'object',
|
|
13112
|
+
},
|
|
13113
|
+
path: ['rename'],
|
|
13114
|
+
permission: 'company.rename',
|
|
13115
|
+
scope: null,
|
|
13116
|
+
summary: 'Rename the company',
|
|
13117
|
+
},
|
|
13118
|
+
{
|
|
13119
|
+
access: 'read',
|
|
13120
|
+
command: ['company', 'slack', 'list'],
|
|
13121
|
+
credential: 'company',
|
|
13122
|
+
input: null,
|
|
13123
|
+
path: ['slack', 'list'],
|
|
13124
|
+
permission: 'company.slack.list',
|
|
13125
|
+
scope: null,
|
|
13126
|
+
summary: 'List the Slack workspaces connected to the company',
|
|
13127
|
+
},
|
|
13128
|
+
{
|
|
13129
|
+
access: 'read',
|
|
13130
|
+
command: ['company', 'workspaces', 'list'],
|
|
13131
|
+
credential: 'company',
|
|
13132
|
+
input: null,
|
|
13133
|
+
path: ['workspaces', 'list'],
|
|
13134
|
+
permission: 'company.workspaces.list',
|
|
13135
|
+
scope: null,
|
|
13136
|
+
summary: "List the company's workspaces",
|
|
13137
|
+
},
|
|
13138
|
+
{
|
|
13139
|
+
access: 'write',
|
|
13140
|
+
command: ['company', 'workspaces', 'set-default'],
|
|
13141
|
+
credential: 'company',
|
|
13142
|
+
input: {
|
|
13143
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
13144
|
+
properties: {
|
|
13145
|
+
teamId: {
|
|
13146
|
+
maxLength: 128,
|
|
13147
|
+
minLength: 1,
|
|
13148
|
+
type: 'string',
|
|
13149
|
+
},
|
|
13150
|
+
},
|
|
13151
|
+
required: ['teamId'],
|
|
13152
|
+
type: 'object',
|
|
13153
|
+
},
|
|
13154
|
+
path: ['workspaces', 'setDefault'],
|
|
13155
|
+
permission: 'company.workspaces.setDefault',
|
|
13156
|
+
scope: null,
|
|
13157
|
+
summary: 'Choose the workspace new email domain and single sign-on members join',
|
|
13158
|
+
},
|
|
12835
13159
|
];
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { chmod, mkdir, readFile, rename, rm, writeFile, } from 'node:fs/promises
|
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
5
|
import { dirname, join } from 'node:path';
|
|
6
|
-
import { createShuffl, ORPCError } from '@shuffl/sdk';
|
|
6
|
+
import { createShuffl, createShufflCompany, ORPCError } from '@shuffl/sdk';
|
|
7
7
|
import { commands } from './commands.generated.js';
|
|
8
8
|
// package.json sits one level above both src/ and dist/.
|
|
9
9
|
const { version } = createRequire(import.meta.url)('../package.json');
|
|
@@ -43,7 +43,7 @@ function parse(args) {
|
|
|
43
43
|
const [name, ...value] = arg.slice(2).split('=');
|
|
44
44
|
if (!name || name in flags)
|
|
45
45
|
throw new Error(`Duplicate or invalid flag: ${arg}`);
|
|
46
|
-
if (['help', 'json', 'token-stdin', 'version'].includes(name)) {
|
|
46
|
+
if (['company', 'help', 'json', 'token-stdin', 'version'].includes(name)) {
|
|
47
47
|
if (value.length)
|
|
48
48
|
throw new Error(`--${name} takes no value`);
|
|
49
49
|
flags[name] = true;
|
|
@@ -72,6 +72,31 @@ function scalar(raw, schema) {
|
|
|
72
72
|
return JSON.parse(raw);
|
|
73
73
|
return raw;
|
|
74
74
|
}
|
|
75
|
+
async function save(file, credentials) {
|
|
76
|
+
await mkdir(dirname(file), { mode: 0o700, recursive: true });
|
|
77
|
+
const temporary = `${file}.${randomUUID()}.tmp`;
|
|
78
|
+
try {
|
|
79
|
+
await writeFile(temporary, JSON.stringify(credentials) + '\n', {
|
|
80
|
+
flag: 'wx',
|
|
81
|
+
mode: 0o600,
|
|
82
|
+
});
|
|
83
|
+
await rename(temporary, file);
|
|
84
|
+
await chmod(file, 0o600);
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
await rm(temporary, { force: true });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async function load(file) {
|
|
91
|
+
try {
|
|
92
|
+
return JSON.parse(await readFile(file, 'utf8'));
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
if (error.code !== 'ENOENT')
|
|
96
|
+
throw new Error('Cannot read CLI configuration. Run shuffl login again.');
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
75
100
|
export async function runCli(args, io = {
|
|
76
101
|
env: process.env,
|
|
77
102
|
stderr: (s) => process.stderr.write(s),
|
|
@@ -81,8 +106,12 @@ export async function runCli(args, io = {
|
|
|
81
106
|
const json = args.includes('--json');
|
|
82
107
|
try {
|
|
83
108
|
const { positional, flags } = parse(args);
|
|
84
|
-
const
|
|
85
|
-
join(io.env.XDG_CONFIG_HOME ?? join(homedir(), '.config'), 'shuffl')
|
|
109
|
+
const configDirectory = io.env.SHUFFL_CONFIG_DIR ??
|
|
110
|
+
join(io.env.XDG_CONFIG_HOME ?? join(homedir(), '.config'), 'shuffl');
|
|
111
|
+
const configFile = join(configDirectory, 'config.json');
|
|
112
|
+
// #1692: a company key is saved apart from the workspace key, and only
|
|
113
|
+
// `shuffl company` commands send it.
|
|
114
|
+
const companyFile = join(configDirectory, 'company.json');
|
|
86
115
|
if (flags.version && !positional.length) {
|
|
87
116
|
io.stdout(json ? `${JSON.stringify({ version })}\n` : `${version}\n`);
|
|
88
117
|
return 0;
|
|
@@ -90,27 +119,38 @@ export async function runCli(args, io = {
|
|
|
90
119
|
if (flags.help || !positional.length) {
|
|
91
120
|
const definition = commands.find((c) => c.command.join(' ') === positional.join(' '));
|
|
92
121
|
io.stdout(definition
|
|
93
|
-
? `${definition.summary}\nPermission: ${definition.permission} (${definition.access})\n\nshuffl ${definition.command.join(' ')} [--input JSON | --input-file FILE] [--json]\n${Object.entries(definition.input?.properties ?? {})
|
|
122
|
+
? `${definition.summary}\nPermission: ${definition.permission} (${definition.access})${definition.credential === 'company' ? '\nCredential: a company key (SHUFFL_COMPANY_KEY or shuffl login --company)' : ''}\n\nshuffl ${definition.command.join(' ')} [--input JSON | --input-file FILE] [--json]\n${Object.entries(definition.input?.properties ?? {})
|
|
94
123
|
.map(([key, value]) => ` --${key.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`)} <${value.type ?? 'json'}>`)
|
|
95
124
|
.join('\n')}\n`
|
|
96
|
-
: `Shuffl CLI ${version}\n\n shuffl login --base-url URL --token-stdin\n shuffl logout\n shuffl --version\n${commands.map((c) => ` shuffl ${c.command.join(' ')} — ${c.summary}`).join('\n')}\n\nUse --help on a command for inputs. Use --json for machine-readable output.\nCreate a key in Settings → API keys and choose the endpoint permissions this CLI needs.\n`);
|
|
125
|
+
: `Shuffl CLI ${version}\n\n shuffl login --base-url URL --token-stdin\n shuffl login --company --base-url URL --token-stdin\n shuffl logout [--company]\n shuffl --version\n${commands.map((c) => ` shuffl ${c.command.join(' ')} — ${c.summary}`).join('\n')}\n\nUse --help on a command for inputs. Use --json for machine-readable output.\nCreate a key in Settings → API keys and choose the endpoint permissions this CLI needs.\nshuffl company commands use a company key from Company settings → API keys instead.\n`);
|
|
97
126
|
return 0;
|
|
98
127
|
}
|
|
99
128
|
if (positional.length === 1 && positional[0] === 'logout') {
|
|
100
|
-
if (Object.keys(flags).some((k) =>
|
|
129
|
+
if (Object.keys(flags).some((k) => !['company', 'json'].includes(k)))
|
|
101
130
|
throw new Error('Unknown logout flag.');
|
|
102
|
-
await rm(configFile, { force: true });
|
|
131
|
+
await rm(flags.company ? companyFile : configFile, { force: true });
|
|
103
132
|
io.stdout(json
|
|
104
133
|
? '{}\n'
|
|
105
|
-
:
|
|
134
|
+
: `Signed out locally. Revoke the ${flags.company ? 'key in Company settings' : 'token in Settings'} to disable other copies.\n`);
|
|
106
135
|
return 0;
|
|
107
136
|
}
|
|
108
137
|
if (positional.length === 1 && positional[0] === 'login') {
|
|
109
|
-
if (Object.keys(flags).some((k) => !['base-url', 'token-stdin', 'json'].includes(k)))
|
|
138
|
+
if (Object.keys(flags).some((k) => !['base-url', 'company', 'token-stdin', 'json'].includes(k)))
|
|
110
139
|
throw new Error('Unknown login flag.');
|
|
111
140
|
if (!flags['token-stdin'] || typeof flags['base-url'] !== 'string')
|
|
112
141
|
throw new Error('Use login --base-url URL --token-stdin. The token is read from standard input.');
|
|
113
142
|
const token = (await io.stdin()).trim();
|
|
143
|
+
if (flags.company) {
|
|
144
|
+
if (!/^shuffl_company_[A-Za-z0-9_-]{43}$/.test(token))
|
|
145
|
+
throw new Error('Expected a Shuffl company key.');
|
|
146
|
+
const baseUrl = new URL(flags['base-url']).origin;
|
|
147
|
+
const company = await request(() => createShufflCompany({ baseUrl, token }).current());
|
|
148
|
+
await save(companyFile, { baseUrl, token });
|
|
149
|
+
io.stdout(json
|
|
150
|
+
? JSON.stringify({ company }) + '\n'
|
|
151
|
+
: `Signed in to company ${company.organizationId} with ${company.permissions.length} permission${company.permissions.length === 1 ? '' : 's'}.\n`);
|
|
152
|
+
return 0;
|
|
153
|
+
}
|
|
114
154
|
if (!/^shuffl_api_[A-Za-z0-9_-]{43}$/.test(token))
|
|
115
155
|
throw new Error('Expected a Shuffl API token.');
|
|
116
156
|
const baseUrl = new URL(flags['base-url']).origin;
|
|
@@ -119,19 +159,7 @@ export async function runCli(args, io = {
|
|
|
119
159
|
token,
|
|
120
160
|
});
|
|
121
161
|
const team = await request(() => client.teams.current());
|
|
122
|
-
await
|
|
123
|
-
const temporary = `${configFile}.${randomUUID()}.tmp`;
|
|
124
|
-
try {
|
|
125
|
-
await writeFile(temporary, JSON.stringify({ baseUrl, token }) + '\n', {
|
|
126
|
-
flag: 'wx',
|
|
127
|
-
mode: 0o600,
|
|
128
|
-
});
|
|
129
|
-
await rename(temporary, configFile);
|
|
130
|
-
await chmod(configFile, 0o600);
|
|
131
|
-
}
|
|
132
|
-
finally {
|
|
133
|
-
await rm(temporary, { force: true });
|
|
134
|
-
}
|
|
162
|
+
await save(configFile, { baseUrl, token });
|
|
135
163
|
io.stdout(json ? JSON.stringify({ team }) + '\n' : `Signed in to ${team.name}.\n`);
|
|
136
164
|
return 0;
|
|
137
165
|
}
|
|
@@ -165,25 +193,26 @@ export async function runCli(args, io = {
|
|
|
165
193
|
throw new Error(`Input ${key} was provided more than once.`);
|
|
166
194
|
input[key] = scalar(flags[flag], fields[key]);
|
|
167
195
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
throw new Error('Cannot read CLI configuration. Run shuffl login again.');
|
|
175
|
-
}
|
|
176
|
-
const token = io.env.SHUFFL_TOKEN ?? saved?.token;
|
|
196
|
+
const company = command.credential === 'company';
|
|
197
|
+
const environmentToken = company
|
|
198
|
+
? io.env.SHUFFL_COMPANY_KEY
|
|
199
|
+
: io.env.SHUFFL_TOKEN;
|
|
200
|
+
const saved = await load(company ? companyFile : configFile);
|
|
201
|
+
const token = environmentToken ?? saved?.token;
|
|
177
202
|
const baseUrl = typeof flags['base-url'] === 'string'
|
|
178
203
|
? flags['base-url']
|
|
179
204
|
: (io.env.SHUFFL_BASE_URL ?? saved?.baseUrl);
|
|
180
205
|
if (!token || !baseUrl)
|
|
181
|
-
throw new Error(
|
|
182
|
-
|
|
206
|
+
throw new Error(company
|
|
207
|
+
? 'Run shuffl login --company, or set SHUFFL_COMPANY_KEY and SHUFFL_BASE_URL.'
|
|
208
|
+
: 'Run shuffl login, or set SHUFFL_TOKEN and SHUFFL_BASE_URL.');
|
|
209
|
+
if (!environmentToken &&
|
|
183
210
|
saved &&
|
|
184
211
|
new URL(baseUrl).origin !== new URL(saved.baseUrl).origin)
|
|
185
212
|
throw new Error('Saved credentials belong to a different server. Sign in to the target server first.');
|
|
186
|
-
let method =
|
|
213
|
+
let method = company
|
|
214
|
+
? createShufflCompany({ baseUrl, token })
|
|
215
|
+
: createShuffl({ baseUrl, token });
|
|
187
216
|
for (const name of command.path)
|
|
188
217
|
method = method[name];
|
|
189
218
|
const output = await request(() => method(input));
|
package/dist/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"dependencies": {
|
|
3
3
|
"@orpc/client": "1.15.3",
|
|
4
|
-
"@shuffl/api-contracts": "0.
|
|
5
|
-
"@shuffl/sdk": "0.
|
|
4
|
+
"@shuffl/api-contracts": "0.4.0",
|
|
5
|
+
"@shuffl/sdk": "0.4.0"
|
|
6
6
|
},
|
|
7
7
|
"description": "Shuffl command line: every public API operation as a scriptable command with JSON output.",
|
|
8
8
|
"engines": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"sideEffects": false,
|
|
34
34
|
"type": "module",
|
|
35
35
|
"types": "./dist/index.d.ts",
|
|
36
|
-
"version": "0.
|
|
36
|
+
"version": "0.4.0",
|
|
37
37
|
"bin": {
|
|
38
38
|
"shuffl": "./dist/bin.js"
|
|
39
39
|
}
|