sim 2.1.0-dev.28.1 → 2.1.0-dev.32.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.
Files changed (3) hide show
  1. package/README.md +16 -7
  2. package/dist/index.js +501 -32
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -12,9 +12,9 @@ Full documentation: **https://docs.sim.ai/cli**
12
12
 
13
13
  ## Profiles
14
14
 
15
- Profiles work like the AWS CLI: one identity and one set of defaults per named
16
- profile, selected with `-P`, `--profile`, or `SIM_PROFILE`. This is what lets you keep
17
- production and a local dev stack side by side without re-authenticating.
15
+ Profiles work like the AWS CLI and are selected with `-P`, `--profile`, or
16
+ `SIM_PROFILE`. A profile normally owns one identity and one set of defaults; a
17
+ workspace profile can instead share a stored identity through `auth_profile`.
18
18
 
19
19
  Non-secret settings live in `~/.sim/config`:
20
20
 
@@ -27,6 +27,10 @@ output = table
27
27
  [profile dev]
28
28
  endpoint = http://localhost:3000
29
29
  workspace = ws_local
30
+
31
+ [profile acme]
32
+ auth_profile = default
33
+ workspace = ws_acme
30
34
  ```
31
35
 
32
36
  Keys live in `~/.sim/credentials`, written `0600`:
@@ -45,7 +49,8 @@ The section-naming asymmetry — `[profile dev]` in config, `[dev]` in credentia
45
49
  ```bash
46
50
  sim configure --set-endpoint http://localhost:3000 --profile dev
47
51
  sim configure --set-workspace ws_local --profile dev
48
- sim profiles # list them; * marks the active one
52
+ sim profiles # list them; * marks the active one
53
+ sim profile add acme --workspace ws_acme # share the active stored login
49
54
  sim whoami # resolved values, where each came from, and whether they work
50
55
  ```
51
56
 
@@ -62,7 +67,7 @@ Each setting resolves independently, first match wins:
62
67
  indefinitely) and `SIM_DEBUG=1` traces requests to stderr. Node ignores
63
68
  `HTTPS_PROXY` unless `NODE_USE_ENV_PROXY=1` is also set, on Node 22.21+ or
64
69
  24.5+; the CLI warns when a proxy is configured but will not be used.
65
- | 3 | `~/.sim/config` / `~/.sim/credentials` for the selected profile |
70
+ | 3 | `~/.sim/config` for the selected profile and credentials for its `auth_profile`, when set |
66
71
  | 4 | Built-in default (`https://www.sim.ai`, `table`) |
67
72
 
68
73
  Formats are listed under [Output formats](#output-formats).
@@ -112,8 +117,11 @@ the key can access.
112
117
  `sim login --workspace <id>` preselects a workspace in the picker, and an
113
118
  existing profile's workspace preselects itself on re-login.
114
119
 
115
- `sim logout` removes the stored key. It does not revoke it — do that in
116
- Settings API keys.
120
+ `sim logout` removes the stored key. A shared workspace profile cannot remove
121
+ its authentication profile's key; use `sim logout --all --profile <name>` to
122
+ remove only the workspace profile. An authentication profile cannot be removed
123
+ entirely while workspace profiles reference it. Logging out does not revoke a
124
+ key — do that in Settings → API keys.
117
125
 
118
126
  ## Commands
119
127
 
@@ -147,6 +155,7 @@ sim logs get <runId>
147
155
  sim audit-logs list --organization <organizationId> [--all-workspaces]
148
156
  sim audit-logs get <id> --organization <organizationId>
149
157
 
158
+ sim workspaces list
150
159
  sim workspaces get
151
160
  sim workspaces members
152
161
 
package/dist/index.js CHANGED
@@ -2432,6 +2432,33 @@ function readConfigProfile(profile) {
2432
2432
  function readCredentialsProfile(profile) {
2433
2433
  return getSection(readIni(credentialsPath()), profile) ?? {};
2434
2434
  }
2435
+ function resolveAuthenticationProfileName(profile) {
2436
+ const config = readConfigProfile(profile);
2437
+ if (!Object.hasOwn(config, "auth_profile"))
2438
+ return profile;
2439
+ const authProfile = config.auth_profile.trim();
2440
+ if (!authProfile) {
2441
+ throw new ProfileConfigError(`Profile "${profile}" has an empty auth_profile.`);
2442
+ }
2443
+ if (authProfile === profile) {
2444
+ throw new ProfileConfigError(`Profile "${profile}" cannot use itself as auth_profile. Remove the auth_profile setting instead.`);
2445
+ }
2446
+ if (Object.hasOwn(config, "endpoint")) {
2447
+ throw new ProfileConfigError(`Profile "${profile}" cannot set both auth_profile and endpoint. Set the endpoint on authentication profile "${authProfile}".`);
2448
+ }
2449
+ if (readCredentialsProfile(profile).api_key) {
2450
+ throw new ProfileConfigError(`Profile "${profile}" cannot set both auth_profile and its own API key. Remove one of them.`);
2451
+ }
2452
+ const authConfig = readConfigProfile(authProfile);
2453
+ const credentials = readCredentialsProfile(authProfile);
2454
+ if (Object.keys(authConfig).length === 0 && Object.keys(credentials).length === 0) {
2455
+ throw new ProfileConfigError(`Profile "${profile}" references missing auth_profile "${authProfile}".`);
2456
+ }
2457
+ if (Object.hasOwn(authConfig, "auth_profile")) {
2458
+ throw new ProfileConfigError(`Profile "${profile}" references auth_profile "${authProfile}", which also has auth_profile set. Authentication profile references cannot be chained.`);
2459
+ }
2460
+ return authProfile;
2461
+ }
2435
2462
  function listProfiles() {
2436
2463
  const names = new Set;
2437
2464
  for (const section of listSections(readIni(configPath()))) {
@@ -2445,6 +2472,9 @@ function listProfiles() {
2445
2472
  }
2446
2473
  return [...names].sort();
2447
2474
  }
2475
+ function listAuthenticationDependents(authProfile) {
2476
+ return listProfiles().filter((profile) => profile !== authProfile && readConfigProfile(profile).auth_profile?.trim() === authProfile);
2477
+ }
2448
2478
  function writeConfigProfile(profile, values) {
2449
2479
  const doc = readIni(configPath());
2450
2480
  setSectionValues(doc, configSectionName(profile), values);
@@ -2489,11 +2519,13 @@ function resolve(candidates, fallback, fallbackSource) {
2489
2519
  function resolveProfile(overrides = {}) {
2490
2520
  const name = overrides.profile || process.env.SIM_PROFILE || DEFAULT_PROFILE;
2491
2521
  const config = readConfigProfile(name);
2492
- const credentials = readCredentialsProfile(name);
2522
+ const authProfile = resolveAuthenticationProfileName(name);
2523
+ const authConfig = authProfile === name ? config : readConfigProfile(authProfile);
2524
+ const credentials = readCredentialsProfile(authProfile);
2493
2525
  const endpoint = resolve([
2494
2526
  ["flag", overrides.endpoint],
2495
2527
  ["env", process.env.SIM_ENDPOINT],
2496
- ["config", config.endpoint]
2528
+ ["config", authConfig.endpoint]
2497
2529
  ], DEFAULT_ENDPOINT, "default");
2498
2530
  const apiKey = resolve([
2499
2531
  ["flag", overrides.apiKey],
@@ -6759,6 +6791,40 @@ var V2_OPERATIONS = {
6759
6791
  }
6760
6792
  }
6761
6793
  },
6794
+ createKnowledgeConnector: {
6795
+ method: "POST",
6796
+ path: "/api/v2/knowledge/[id]/connectors",
6797
+ pathParams: ["id"],
6798
+ pathParamDocs: { id: "Unique knowledge base identifier." },
6799
+ responseMode: "json",
6800
+ summary: "Create Knowledge Connector",
6801
+ body: {
6802
+ workspaceId: {
6803
+ kind: "string",
6804
+ required: true,
6805
+ describe: "Workspace that owns the knowledge base."
6806
+ },
6807
+ connectorType: { kind: "string", required: true, describe: "Registered connector type." },
6808
+ credentialId: {
6809
+ kind: "string",
6810
+ describe: "OAuth credential identifier for connectors that require OAuth."
6811
+ },
6812
+ apiKey: {
6813
+ kind: "string",
6814
+ describe: "Write-only API key for connectors that use API-key authentication."
6815
+ },
6816
+ sourceConfig: {
6817
+ kind: "object",
6818
+ required: true,
6819
+ describe: "Connector-specific source selection and filtering configuration."
6820
+ },
6821
+ syncIntervalMinutes: {
6822
+ kind: "integer",
6823
+ default: 1440,
6824
+ describe: "Scheduled synchronization interval in minutes; zero disables scheduling."
6825
+ }
6826
+ }
6827
+ },
6762
6828
  createKnowledgeDocumentUpload: {
6763
6829
  method: "POST",
6764
6830
  path: "/api/v2/knowledge/[id]/documents/uploads",
@@ -6923,22 +6989,11 @@ var V2_OPERATIONS = {
6923
6989
  kind: "string",
6924
6990
  describe: "Required only when provider discovery requests a client-generated ID."
6925
6991
  },
6926
- serviceAccountJson: {
6992
+ credentials: {
6927
6993
  kind: "string",
6928
- describe: "Write-only Google service-account JSON key."
6929
- },
6930
- apiToken: { kind: "string", describe: "Write-only provider API token." },
6931
- domain: { kind: "string", describe: "Provider account domain." },
6932
- signingSecret: { kind: "string", describe: "Write-only webhook signing secret." },
6933
- botToken: { kind: "string", describe: "Write-only bot token." },
6934
- clientId: { kind: "string", describe: "OAuth client identifier." },
6935
- clientSecret: { kind: "string", describe: "Write-only OAuth client secret." },
6936
- certificateId: { kind: "string", describe: "Provider certificate mapping identifier." },
6937
- orgId: { kind: "string", describe: "Provider organization ID." },
6938
- dataCenter: { kind: "string", describe: "Provider data center." },
6939
- authMethod: { kind: "string", describe: "Provider authentication method." },
6940
- privateKey: { kind: "string", describe: "Write-only PEM private key." },
6941
- username: { kind: "string", describe: "Provider run-as username." }
6994
+ required: true,
6995
+ describe: "Write-only JSON object string containing the fields declared by credential-provider discovery."
6996
+ }
6942
6997
  }
6943
6998
  },
6944
6999
  createSkill: {
@@ -7206,6 +7261,28 @@ var V2_OPERATIONS = {
7206
7261
  }
7207
7262
  }
7208
7263
  },
7264
+ deleteKnowledgeConnector: {
7265
+ method: "DELETE",
7266
+ path: "/api/v2/knowledge/[id]/connectors/[connectorId]",
7267
+ pathParams: ["id", "connectorId"],
7268
+ pathParamDocs: {
7269
+ id: "Knowledge base that owns the connector.",
7270
+ connectorId: "Connector selected for the operation."
7271
+ },
7272
+ responseMode: "json",
7273
+ summary: "Delete Knowledge Connector",
7274
+ query: {
7275
+ workspaceId: {
7276
+ kind: "string",
7277
+ required: true,
7278
+ describe: "Workspace that owns the knowledge base."
7279
+ },
7280
+ deleteDocuments: {
7281
+ kind: "boolean",
7282
+ describe: "Also permanently delete documents produced by this connector."
7283
+ }
7284
+ }
7285
+ },
7209
7286
  deleteKnowledgeDocument: {
7210
7287
  method: "DELETE",
7211
7288
  path: "/api/v2/knowledge/[id]/documents/[documentId]",
@@ -7632,6 +7709,24 @@ var V2_OPERATIONS = {
7632
7709
  }
7633
7710
  }
7634
7711
  },
7712
+ getKnowledgeConnector: {
7713
+ method: "GET",
7714
+ path: "/api/v2/knowledge/[id]/connectors/[connectorId]",
7715
+ pathParams: ["id", "connectorId"],
7716
+ pathParamDocs: {
7717
+ id: "Knowledge base that owns the connector.",
7718
+ connectorId: "Connector selected for the operation."
7719
+ },
7720
+ responseMode: "json",
7721
+ summary: "Get Knowledge Connector",
7722
+ query: {
7723
+ workspaceId: {
7724
+ kind: "string",
7725
+ required: true,
7726
+ describe: "Workspace that owns the knowledge base."
7727
+ }
7728
+ }
7729
+ },
7635
7730
  getKnowledgeDocument: {
7636
7731
  method: "GET",
7637
7732
  path: "/api/v2/knowledge/[id]/documents/[documentId]",
@@ -7799,6 +7894,24 @@ var V2_OPERATIONS = {
7799
7894
  responseMode: "json",
7800
7895
  summary: "Get Workspace"
7801
7896
  },
7897
+ grantSkillEditor: {
7898
+ method: "POST",
7899
+ path: "/api/v2/skills/[id]/editors",
7900
+ pathParams: ["id"],
7901
+ pathParamDocs: {
7902
+ id: "Unique skill identifier. A built-in skill is `builtin-` followed by its name, for example `builtin-research`."
7903
+ },
7904
+ responseMode: "json",
7905
+ summary: "Grant Skill Editor",
7906
+ body: {
7907
+ workspaceId: { kind: "string", required: true, describe: "Workspace that owns the skill." },
7908
+ email: {
7909
+ kind: "string",
7910
+ required: true,
7911
+ describe: "Email address of a current workspace member."
7912
+ }
7913
+ }
7914
+ },
7802
7915
  importWorkflow: {
7803
7916
  method: "POST",
7804
7917
  path: "/api/v2/workflows/import",
@@ -8167,6 +8280,73 @@ var V2_OPERATIONS = {
8167
8280
  }
8168
8281
  }
8169
8282
  },
8283
+ listKnowledgeConnectorDocuments: {
8284
+ method: "GET",
8285
+ path: "/api/v2/knowledge/[id]/connectors/[connectorId]/documents",
8286
+ pathParams: ["id", "connectorId"],
8287
+ pathParamDocs: {
8288
+ id: "Knowledge base that owns the connector.",
8289
+ connectorId: "Connector selected for the operation."
8290
+ },
8291
+ responseMode: "json",
8292
+ summary: "List Knowledge Connector Documents",
8293
+ query: {
8294
+ workspaceId: {
8295
+ kind: "string",
8296
+ required: true,
8297
+ describe: "Workspace that owns the knowledge base."
8298
+ },
8299
+ includeExcluded: {
8300
+ kind: "boolean",
8301
+ describe: "Include documents explicitly excluded by a user."
8302
+ },
8303
+ limit: {
8304
+ kind: "integer",
8305
+ default: 50,
8306
+ describe: "Maximum connector documents to return per page. Must be a whole number from 1 to 100. Defaults to 50."
8307
+ },
8308
+ cursor: {
8309
+ kind: "string",
8310
+ describe: "Opaque cursor from the previous page. Send it back with the same sort and filters; only `limit` may change. Change anything else and pagination must restart without a cursor."
8311
+ }
8312
+ }
8313
+ },
8314
+ listKnowledgeConnectors: {
8315
+ method: "GET",
8316
+ path: "/api/v2/knowledge/[id]/connectors",
8317
+ pathParams: ["id"],
8318
+ pathParamDocs: { id: "Unique knowledge base identifier." },
8319
+ responseMode: "json",
8320
+ summary: "List Knowledge Connectors",
8321
+ query: {
8322
+ workspaceId: {
8323
+ kind: "string",
8324
+ required: true,
8325
+ describe: "Workspace that owns the knowledge base."
8326
+ },
8327
+ sortBy: {
8328
+ kind: "enum",
8329
+ values: ["connectorType", "createdAt", "updatedAt"],
8330
+ default: "createdAt",
8331
+ describe: "Field used to sort the result."
8332
+ },
8333
+ sortOrder: {
8334
+ kind: "enum",
8335
+ values: ["asc", "desc"],
8336
+ default: "desc",
8337
+ describe: "Sort direction."
8338
+ },
8339
+ limit: {
8340
+ kind: "integer",
8341
+ default: 50,
8342
+ describe: "Maximum connectors to return per page. Must be a whole number from 1 to 100. Defaults to 50."
8343
+ },
8344
+ cursor: {
8345
+ kind: "string",
8346
+ describe: "Opaque cursor from the previous page. Send it back with the same sort and filters; only `limit` may change. Change anything else and pagination must restart without a cursor."
8347
+ }
8348
+ }
8349
+ },
8170
8350
  listKnowledgeDocuments: {
8171
8351
  method: "GET",
8172
8352
  path: "/api/v2/knowledge/[id]/documents",
@@ -8462,6 +8642,40 @@ var V2_OPERATIONS = {
8462
8642
  }
8463
8643
  }
8464
8644
  },
8645
+ listSkillEditors: {
8646
+ method: "GET",
8647
+ path: "/api/v2/skills/[id]/editors",
8648
+ pathParams: ["id"],
8649
+ pathParamDocs: {
8650
+ id: "Unique skill identifier. A built-in skill is `builtin-` followed by its name, for example `builtin-research`."
8651
+ },
8652
+ responseMode: "json",
8653
+ summary: "List Skill Editors",
8654
+ query: {
8655
+ workspaceId: { kind: "string", required: true, describe: "Workspace that owns the skill." },
8656
+ sortBy: {
8657
+ kind: "enum",
8658
+ values: ["email", "name"],
8659
+ default: "email",
8660
+ describe: "Field used to sort the result. Sorting by `name` is case-sensitive and follows the storage collation, so do not rely on a case-insensitive order."
8661
+ },
8662
+ sortOrder: {
8663
+ kind: "enum",
8664
+ values: ["asc", "desc"],
8665
+ default: "asc",
8666
+ describe: "Sort direction."
8667
+ },
8668
+ limit: {
8669
+ kind: "integer",
8670
+ default: 50,
8671
+ describe: "Maximum skill editors to return per page. Must be a whole number from 1 to 100. Defaults to 50."
8672
+ },
8673
+ cursor: {
8674
+ kind: "string",
8675
+ describe: "Opaque cursor from the previous page. Send it back with the same sort and filters; only `limit` may change. Change anything else and pagination must restart without a cursor."
8676
+ }
8677
+ }
8678
+ },
8465
8679
  listSkills: {
8466
8680
  method: "GET",
8467
8681
  path: "/api/v2/skills",
@@ -8774,6 +8988,36 @@ var V2_OPERATIONS = {
8774
8988
  }
8775
8989
  }
8776
8990
  },
8991
+ listWorkspaces: {
8992
+ method: "GET",
8993
+ path: "/api/v2/workspaces",
8994
+ pathParams: [],
8995
+ responseMode: "json",
8996
+ summary: "List Workspaces",
8997
+ query: {
8998
+ sortBy: {
8999
+ kind: "enum",
9000
+ values: ["name", "createdAt", "updatedAt"],
9001
+ default: "createdAt",
9002
+ describe: "Field used to sort the result. Sorting by `name` is case-sensitive and follows the storage collation, so do not rely on a case-insensitive order."
9003
+ },
9004
+ sortOrder: {
9005
+ kind: "enum",
9006
+ values: ["asc", "desc"],
9007
+ default: "desc",
9008
+ describe: "Sort direction."
9009
+ },
9010
+ limit: {
9011
+ kind: "integer",
9012
+ default: 50,
9013
+ describe: "Maximum workspaces to return per page. Must be a whole number from 1 to 100. Defaults to 50."
9014
+ },
9015
+ cursor: {
9016
+ kind: "string",
9017
+ describe: "Opaque cursor from the previous page. Send it back with the same sort and filters; only `limit` may change. Change anything else and pagination must restart without a cursor."
9018
+ }
9019
+ }
9020
+ },
8777
9021
  moveFileItems: {
8778
9022
  method: "POST",
8779
9023
  path: "/api/v2/files/move",
@@ -8800,7 +9044,7 @@ var V2_OPERATIONS = {
8800
9044
  workspaceId: { kind: "string", required: true, describe: "Unique workspace identifier." },
8801
9045
  predicate: {
8802
9046
  kind: "unknown",
8803
- describe: 'Recursive predicate tree. Each group node is exactly one non-empty `all` or `any` array whose members are further groups or `{ field, op, value }` conditions; the root must be a group, not a bare condition. At most 100 members per group, 10 levels of nesting, and 500 nodes in total. The negating operators include nulls: `ne`, `nin`, `ncontains`, `nlike`, and `nilike` match rows whose column is null or absent, so "not X" is not the complement of "X" over a nullable column. That holds for every column type, multi-select included. To exclude nulls, `all`-combine the negation with `isNotEmpty` (multi-select) or `isNotNull`. Comparison: `eq`, `ne`, `gt`, `gte`, `lt`, `lte`. Membership: `in`, `nin` (array operand). Emptiness: `isEmpty`, `isNotEmpty`, `isNull`, `isNotNull` (no operand). Substring, always case-insensitive, operand matched literally: `contains`, `ncontains`, `startsWith`, `endsWith`. Pattern: `like`/`nlike` (case-sensitive), `ilike`/`nilike` (case-insensitive). **`*` is the only wildcard** and stands for any run of characters; `%`, `_`, and backslash match themselves. Use `like: "Hi*"`, not `like: "Hi%"`. A `select` column compares by option id and restricts its operators: single-select accepts `eq`, `ne`, `in`, `nin`; multi-select accepts `contains`, `ncontains`. Option names are accepted as operands and resolved to ids.'
9047
+ describe: 'A single `{ field, op, value }` condition or a recursive `all`/`any` group; either form is normalized to a grouped predicate after validation. At most 100 members per group, 10 levels of nesting, and 500 nodes in total. The negating operators include nulls: `ne`, `nin`, `ncontains`, `nlike`, and `nilike` match rows whose column is null or absent, so "not X" is not the complement of "X" over a nullable column. That holds for every column type, multi-select included. To exclude nulls, `all`-combine the negation with `isNotEmpty` (multi-select) or `isNotNull`. Comparison: `eq`, `ne`, `gt`, `gte`, `lt`, `lte`. Membership: `in`, `nin` (array operand). Emptiness: `isEmpty`, `isNotEmpty`, `isNull`, `isNotNull` (no operand). Substring, always case-insensitive, operand matched literally: `contains`, `ncontains`, `startsWith`, `endsWith`. Pattern: `like`/`nlike` (case-sensitive), `ilike`/`nilike` (case-insensitive). **`*` is the only wildcard** and stands for any run of characters; `%`, `_`, and backslash match themselves. Use `like: "Hi*"`, not `like: "Hi%"`. A `select` column compares by option id and restricts its operators: single-select accepts `eq`, `ne`, `in`, `nin`; multi-select accepts `contains`, `ncontains`. Option names are accepted as operands and resolved to ids.'
8804
9048
  },
8805
9049
  sort: { kind: "array", describe: "Ordered table-row sort specification." },
8806
9050
  limit: {
@@ -8821,7 +9065,7 @@ var V2_OPERATIONS = {
8821
9065
  workspaceId: { kind: "string", required: true, describe: "Unique workspace identifier." },
8822
9066
  predicate: {
8823
9067
  kind: "unknown",
8824
- describe: 'Recursive predicate tree. Each group node is exactly one non-empty `all` or `any` array whose members are further groups or `{ field, op, value }` conditions; the root must be a group, not a bare condition. At most 100 members per group, 10 levels of nesting, and 500 nodes in total. The negating operators include nulls: `ne`, `nin`, `ncontains`, `nlike`, and `nilike` match rows whose column is null or absent, so "not X" is not the complement of "X" over a nullable column. That holds for every column type, multi-select included. To exclude nulls, `all`-combine the negation with `isNotEmpty` (multi-select) or `isNotNull`. Comparison: `eq`, `ne`, `gt`, `gte`, `lt`, `lte`. Membership: `in`, `nin` (array operand). Emptiness: `isEmpty`, `isNotEmpty`, `isNull`, `isNotNull` (no operand). Substring, always case-insensitive, operand matched literally: `contains`, `ncontains`, `startsWith`, `endsWith`. Pattern: `like`/`nlike` (case-sensitive), `ilike`/`nilike` (case-insensitive). **`*` is the only wildcard** and stands for any run of characters; `%`, `_`, and backslash match themselves. Use `like: "Hi*"`, not `like: "Hi%"`. A `select` column compares by option id and restricts its operators: single-select accepts `eq`, `ne`, `in`, `nin`; multi-select accepts `contains`, `ncontains`. Option names are accepted as operands and resolved to ids.'
9068
+ describe: 'A single `{ field, op, value }` condition or a recursive `all`/`any` group; either form is normalized to a grouped predicate after validation. At most 100 members per group, 10 levels of nesting, and 500 nodes in total. The negating operators include nulls: `ne`, `nin`, `ncontains`, `nlike`, and `nilike` match rows whose column is null or absent, so "not X" is not the complement of "X" over a nullable column. That holds for every column type, multi-select included. To exclude nulls, `all`-combine the negation with `isNotEmpty` (multi-select) or `isNotNull`. Comparison: `eq`, `ne`, `gt`, `gte`, `lt`, `lte`. Membership: `in`, `nin` (array operand). Emptiness: `isEmpty`, `isNotEmpty`, `isNull`, `isNotNull` (no operand). Substring, always case-insensitive, operand matched literally: `contains`, `ncontains`, `startsWith`, `endsWith`. Pattern: `like`/`nlike` (case-sensitive), `ilike`/`nilike` (case-insensitive). **`*` is the only wildcard** and stands for any run of characters; `%`, `_`, and backslash match themselves. Use `like: "Hi*"`, not `like: "Hi%"`. A `select` column compares by option id and restricts its operators: single-select accepts `eq`, `ne`, `in`, `nin`; multi-select accepts `contains`, `ncontains`. Option names are accepted as operands and resolved to ids.'
8825
9069
  }
8826
9070
  }
8827
9071
  },
@@ -8932,6 +9176,24 @@ var V2_OPERATIONS = {
8932
9176
  input: { kind: "unknown", describe: "Input supplied to the paused workflow block." }
8933
9177
  }
8934
9178
  },
9179
+ revokeSkillEditor: {
9180
+ method: "DELETE",
9181
+ path: "/api/v2/skills/[id]/editors",
9182
+ pathParams: ["id"],
9183
+ pathParamDocs: {
9184
+ id: "Unique skill identifier. A built-in skill is `builtin-` followed by its name, for example `builtin-research`."
9185
+ },
9186
+ responseMode: "json",
9187
+ summary: "Revoke Skill Editor",
9188
+ query: {
9189
+ workspaceId: { kind: "string", required: true, describe: "Workspace that owns the skill." },
9190
+ email: {
9191
+ kind: "string",
9192
+ required: true,
9193
+ describe: "Email address of a current workspace member."
9194
+ }
9195
+ }
9196
+ },
8935
9197
  rollbackWorkflow: {
8936
9198
  method: "POST",
8937
9199
  path: "/api/v2/workflows/[id]/rollback",
@@ -9071,6 +9333,29 @@ var V2_OPERATIONS = {
9071
9333
  }
9072
9334
  }
9073
9335
  },
9336
+ syncKnowledgeConnector: {
9337
+ method: "POST",
9338
+ path: "/api/v2/knowledge/[id]/connectors/[connectorId]/sync",
9339
+ pathParams: ["id", "connectorId"],
9340
+ pathParamDocs: {
9341
+ id: "Knowledge base that owns the connector.",
9342
+ connectorId: "Connector selected for the operation."
9343
+ },
9344
+ responseMode: "json",
9345
+ summary: "Sync Knowledge Connector",
9346
+ body: {
9347
+ workspaceId: {
9348
+ kind: "string",
9349
+ required: true,
9350
+ describe: "Workspace that owns the knowledge base."
9351
+ },
9352
+ rehydrate: {
9353
+ kind: "boolean",
9354
+ default: false,
9355
+ describe: "Re-fetch and re-index every existing connector document."
9356
+ }
9357
+ }
9358
+ },
9074
9359
  tableExportDownload: {
9075
9360
  method: "GET",
9076
9361
  path: "/api/v2/tables/exports/[exportId]/download",
@@ -9153,6 +9438,66 @@ var V2_OPERATIONS = {
9153
9438
  folderPath: { kind: "string", describe: "New containing-folder path." }
9154
9439
  }
9155
9440
  },
9441
+ updateKnowledgeConnector: {
9442
+ method: "PATCH",
9443
+ path: "/api/v2/knowledge/[id]/connectors/[connectorId]",
9444
+ pathParams: ["id", "connectorId"],
9445
+ pathParamDocs: {
9446
+ id: "Knowledge base that owns the connector.",
9447
+ connectorId: "Connector selected for the operation."
9448
+ },
9449
+ responseMode: "json",
9450
+ summary: "Update Knowledge Connector",
9451
+ body: {
9452
+ workspaceId: {
9453
+ kind: "string",
9454
+ required: true,
9455
+ describe: "Workspace that owns the knowledge base."
9456
+ },
9457
+ sourceConfig: {
9458
+ kind: "object",
9459
+ describe: "Replacement source selection and filtering configuration."
9460
+ },
9461
+ syncIntervalMinutes: {
9462
+ kind: "integer",
9463
+ describe: "New scheduled synchronization interval in minutes."
9464
+ },
9465
+ status: {
9466
+ kind: "enum",
9467
+ values: ["active", "paused"],
9468
+ describe: "New connector state."
9469
+ }
9470
+ }
9471
+ },
9472
+ updateKnowledgeConnectorDocuments: {
9473
+ method: "PATCH",
9474
+ path: "/api/v2/knowledge/[id]/connectors/[connectorId]/documents",
9475
+ pathParams: ["id", "connectorId"],
9476
+ pathParamDocs: {
9477
+ id: "Knowledge base that owns the connector.",
9478
+ connectorId: "Connector selected for the operation."
9479
+ },
9480
+ responseMode: "json",
9481
+ summary: "Update Knowledge Connector Documents",
9482
+ body: {
9483
+ workspaceId: {
9484
+ kind: "string",
9485
+ required: true,
9486
+ describe: "Workspace that owns the knowledge base."
9487
+ },
9488
+ operation: {
9489
+ kind: "enum",
9490
+ required: true,
9491
+ values: ["restore", "exclude"],
9492
+ describe: "Whether to restore or exclude the selected documents."
9493
+ },
9494
+ documentIds: {
9495
+ kind: "array",
9496
+ required: true,
9497
+ describe: "Connector document identifiers to update."
9498
+ }
9499
+ }
9500
+ },
9156
9501
  updateKnowledgeDocument: {
9157
9502
  method: "PATCH",
9158
9503
  path: "/api/v2/knowledge/[id]/documents/[documentId]",
@@ -9481,6 +9826,8 @@ var V2_OPERATIONS = {
9481
9826
  };
9482
9827
 
9483
9828
  // src/commands/auth.ts
9829
+ var PROFILE_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
9830
+ var MAX_INTERACTIVE_WORKSPACES = 1000;
9484
9831
  function openBrowser(url) {
9485
9832
  const [command, args] = process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : [process.platform === "darwin" ? "open" : "xdg-open", [url]];
9486
9833
  try {
@@ -9516,9 +9863,91 @@ async function confirmProfileOverwrite(profileName) {
9516
9863
  prompt.close();
9517
9864
  }
9518
9865
  }
9866
+ function selectedProfileName(command) {
9867
+ return globalsOf(command).profile || process.env.SIM_PROFILE || DEFAULT_PROFILE;
9868
+ }
9869
+ function validateNewProfileName(profileName) {
9870
+ if (!PROFILE_NAME_PATTERN.test(profileName)) {
9871
+ throw new SimApiError(`Invalid profile name "${profileName}". Use letters, numbers, dots, underscores, or hyphens, starting with a letter or number.`, 0);
9872
+ }
9873
+ if (listProfiles().includes(profileName)) {
9874
+ throw new SimApiError(`Profile "${profileName}" already exists. Remove it first with: sim logout --all --profile ${profileName}`, 0);
9875
+ }
9876
+ }
9877
+ function requireStoredAuthentication(profile) {
9878
+ const authProfile = resolveAuthenticationProfileName(profile.name);
9879
+ const storedKey = readCredentialsProfile(authProfile).api_key;
9880
+ if (profile.sources.apiKey !== "credentials" || !storedKey) {
9881
+ throw new SimApiError(`Cannot create a shared profile from "${profile.name}": the active API key is not stored. Run: sim login --profile ${authProfile}`, 0);
9882
+ }
9883
+ if (profile.sources.endpoint === "flag" || profile.sources.endpoint === "env") {
9884
+ throw new SimApiError(`Cannot create a shared profile from "${profile.name}": the active endpoint comes from ${profile.sources.endpoint}. Save it with: sim configure --profile ${authProfile} --set-endpoint ${profile.endpoint}`, 0);
9885
+ }
9886
+ return authProfile;
9887
+ }
9888
+ async function getWorkspaceById(client, workspaceId) {
9889
+ const operation = V2_OPERATIONS.getWorkspace;
9890
+ const response = await client.request(resolvePath(operation.path, { workspaceId }), { method: operation.method });
9891
+ return response.data;
9892
+ }
9893
+ async function chooseWorkspace(client) {
9894
+ if (!process.stdin.isTTY) {
9895
+ throw new SimApiError("No workspace provided. Pass --workspace <id> when creating a profile non-interactively.", 0);
9896
+ }
9897
+ const operation = V2_OPERATIONS.listWorkspaces;
9898
+ const workspaces = await requestAllPages(client, operation.path, {
9899
+ method: operation.method,
9900
+ query: { sortBy: "name", sortOrder: "asc" },
9901
+ pageSize: 100,
9902
+ limit: MAX_INTERACTIVE_WORKSPACES + 1
9903
+ });
9904
+ if (workspaces.length === 0) {
9905
+ throw new SimApiError("The active API key cannot access any workspaces.", 0);
9906
+ }
9907
+ if (workspaces.length > MAX_INTERACTIVE_WORKSPACES) {
9908
+ throw new SimApiError(`The active API key can access more than ${MAX_INTERACTIVE_WORKSPACES} workspaces, which is too many to show interactively. Pass --workspace <id> instead.`, 0);
9909
+ }
9910
+ console.log(`
9911
+ Available workspaces:`);
9912
+ for (const [index, workspace] of workspaces.entries()) {
9913
+ console.log(` ${index + 1}) ${safeOneLine(workspace.name)} (${workspace.id})`);
9914
+ }
9915
+ const prompt = createInterface({ input: process.stdin, output: process.stderr });
9916
+ try {
9917
+ const answer = await prompt.question(`Choose a workspace [1-${workspaces.length}]: `);
9918
+ const selected = Number(answer.trim());
9919
+ if (!Number.isInteger(selected) || selected < 1 || selected > workspaces.length) {
9920
+ throw new SimApiError(`Invalid workspace selection "${safeOneLine(answer)}". Choose a number from 1 to ${workspaces.length}.`, 0);
9921
+ }
9922
+ return workspaces[selected - 1];
9923
+ } finally {
9924
+ prompt.close();
9925
+ }
9926
+ }
9927
+ function addProfileCommand() {
9928
+ return new Command("add").description("Add a workspace profile that shares the active stored login").argument("<name>", "Name for the new profile").option("-w, --workspace <id>", "Existing workspace to use; omit for an interactive picker").action(async (profileName, _options, command) => {
9929
+ validateNewProfileName(profileName);
9930
+ const { client, profile } = clientFrom(command);
9931
+ const authProfile = requireStoredAuthentication(profile);
9932
+ const workspaceId = globalsOf(command).workspace;
9933
+ const workspace = workspaceId ? await getWorkspaceById(client, workspaceId) : await chooseWorkspace(client);
9934
+ writeConfigProfile(profileName, {
9935
+ auth_profile: authProfile,
9936
+ workspace: workspace.id
9937
+ });
9938
+ console.log(source_default.green(`✓ Added profile "${profileName}" in ${configPath()}`));
9939
+ console.log(` Workspace: ${safeOneLine(workspace.name)} (${workspace.id})`);
9940
+ console.log(` Authentication: ${authProfile}`);
9941
+ console.log(source_default.dim(` Try: sim --profile ${profileName} whoami`));
9942
+ });
9943
+ }
9519
9944
  function loginCommand() {
9520
9945
  return new Command("login").description("Authorize this terminal and store an API key for the profile").option("--scope <scope>", "Key space to mint from: platform or copilot", "platform").option("--no-browser", "Print the URL instead of opening a browser").option("-y, --yes", "Overwrite an existing profile without prompting").action(async (options, command) => {
9521
9946
  const profile = profileFrom(command);
9947
+ const authProfile = resolveAuthenticationProfileName(profile.name);
9948
+ if (authProfile !== profile.name) {
9949
+ throw new SimApiError(`Profile "${profile.name}" shares authentication with "${authProfile}". Run: sim login --profile ${authProfile}`, 0);
9950
+ }
9522
9951
  if (options.scope !== "platform" && options.scope !== "copilot") {
9523
9952
  throw new SimApiError(`Unknown scope "${options.scope}". Use platform or copilot.`, 0);
9524
9953
  }
@@ -9565,16 +9994,25 @@ Waiting for approval…`));
9565
9994
  }
9566
9995
  function logoutCommand() {
9567
9996
  return new Command("logout").description("Remove the profile's stored API key").option("--all", "Remove the profile entirely, including its settings").action((options, command) => {
9568
- const profile = profileFrom(command);
9569
9997
  if (options.all) {
9570
- const removed = deleteProfile(profile.name);
9998
+ const profileName = selectedProfileName(command);
9999
+ const dependents = listAuthenticationDependents(profileName);
10000
+ if (dependents.length > 0) {
10001
+ throw new SimApiError(`Cannot remove authentication profile "${profileName}" because it is used by: ${dependents.join(", ")}. Remove those profiles first.`, 0);
10002
+ }
10003
+ const removed = deleteProfile(profileName);
9571
10004
  if (!removed.config && !removed.credentials) {
9572
- console.log(source_default.dim(`Nothing stored for profile "${profile.name}".`));
10005
+ console.log(source_default.dim(`Nothing stored for profile "${profileName}".`));
9573
10006
  return;
9574
10007
  }
9575
- console.log(source_default.green(`✓ Removed profile "${profile.name}".`));
10008
+ console.log(source_default.green(`✓ Removed profile "${profileName}".`));
9576
10009
  return;
9577
10010
  }
10011
+ const profile = profileFrom(command);
10012
+ const authProfile = resolveAuthenticationProfileName(profile.name);
10013
+ if (authProfile !== profile.name) {
10014
+ throw new SimApiError(`Profile "${profile.name}" shares authentication with "${authProfile}". Log out of the authentication profile instead: sim logout --profile ${authProfile}`, 0);
10015
+ }
9578
10016
  if (!readCredentialsProfile(profile.name).api_key) {
9579
10017
  console.log(source_default.dim(`No stored key for profile "${profile.name}".`));
9580
10018
  return;
@@ -9682,27 +10120,38 @@ function whoamiCommand() {
9682
10120
  });
9683
10121
  }
9684
10122
  function profilesCommand() {
9685
- return new Command("profiles").alias("profile").description("List the profiles defined in the config and credentials files").action((_options, command) => {
10123
+ const command = new Command("profiles").alias("profile").description("List profiles or add a workspace profile that shares a stored login");
10124
+ const printProfiles = (_options, actionCommand) => {
9686
10125
  const profiles = listProfiles();
9687
10126
  if (profiles.length === 0) {
9688
10127
  console.log(source_default.dim("No profiles yet. Run: sim login"));
9689
10128
  return;
9690
10129
  }
9691
- const active = profileFrom(command).name;
10130
+ const active = selectedProfileName(actionCommand);
9692
10131
  for (const name of profiles) {
9693
10132
  const marker = name === active ? source_default.green("*") : " ";
9694
- const hasKey = Boolean(readCredentialsProfile(name).api_key);
9695
- console.log(`${marker} ${name}${hasKey ? "" : source_default.dim(" (no key)")}`);
10133
+ const authProfile = resolveAuthenticationProfileName(name);
10134
+ const hasKey = Boolean(readCredentialsProfile(authProfile).api_key);
10135
+ const authentication = authProfile === name ? "" : source_default.dim(` (auth: ${authProfile})`);
10136
+ console.log(`${marker} ${name}${hasKey ? "" : source_default.dim(" (no key)")}${authentication}`);
9696
10137
  }
9697
- });
10138
+ };
10139
+ command.action(printProfiles);
10140
+ command.addCommand(new Command("list").description("List configured profiles").action(printProfiles));
10141
+ command.addCommand(addProfileCommand());
10142
+ return command;
9698
10143
  }
9699
10144
 
9700
10145
  // src/commands/configure.ts
9701
10146
  function configureCommand() {
9702
10147
  return new Command("configure").description("Set a profile's endpoint, default workspace, or output format").option("--set-endpoint <url>", "Sim deployment to talk to").option("--set-workspace <id>", "Default workspace for workspace-scoped commands").option("--set-output <format>", `Default output format (${OUTPUT_FORMATS.join(" | ")})`).option("--unset <key...>", "Remove settings (endpoint, workspace, output)").action((options, command) => {
9703
10148
  const profile = profileFrom(command);
10149
+ const authProfile = resolveAuthenticationProfileName(profile.name);
9704
10150
  const updates = {};
9705
10151
  if (options.setEndpoint) {
10152
+ if (authProfile !== profile.name) {
10153
+ throw new SimApiError(`Profile "${profile.name}" shares its endpoint with authentication profile "${authProfile}". Run: sim configure --profile ${authProfile} --set-endpoint ${options.setEndpoint}`, 0);
10154
+ }
9706
10155
  updates.endpoint = normalizeEndpoint(options.setEndpoint, "--set-endpoint");
9707
10156
  }
9708
10157
  if (options.setWorkspace)
@@ -9717,6 +10166,9 @@ function configureCommand() {
9717
10166
  if (!["endpoint", "workspace", "output"].includes(key)) {
9718
10167
  throw new SimApiError(`Cannot unset "${key}". Use endpoint, workspace, or output.`, 0);
9719
10168
  }
10169
+ if (key === "endpoint" && authProfile !== profile.name) {
10170
+ throw new SimApiError(`Profile "${profile.name}" shares its endpoint with authentication profile "${authProfile}". Run: sim configure --profile ${authProfile} --unset endpoint`, 0);
10171
+ }
9720
10172
  updates[key] = null;
9721
10173
  }
9722
10174
  if (Object.keys(updates).length === 0) {
@@ -9741,6 +10193,7 @@ import { existsSync as existsSync2, readFileSync as readFileSync3, readSync } fr
9741
10193
  // src/contract/commands.ts
9742
10194
  var TABLE_NAME_HELP = "Identifier: letters, numbers, and underscores; cannot start with a number";
9743
10195
  var TABLE_FILTER_HELP = 'Predicate: {"all":[{"field":"status","op":"eq","value":"active"}]}; groups use all/any. Operators: eq, ne, gt, gte, lt, lte, in, nin, contains, ncontains, startsWith, endsWith, like, ilike, nlike, nilike, isEmpty, isNotEmpty, isNull, isNotNull';
10196
+ var TABLE_READ_FILTER_HELP = 'Condition: {"field":"status","op":"eq","value":"active"}. Groups: {"all":[{"field":"status","op":"eq","value":"active"}]} or {"any":[{"field":"status","op":"eq","value":"active"}]}; group entries may also be nested groups. Operators: eq, ne, gt, gte, lt, lte, in, nin, contains, ncontains, startsWith, endsWith, like, ilike, nlike, nilike, isEmpty, isNotEmpty, isNull, isNotNull';
9744
10197
  var TABLE_SORT_HELP = 'Ordered sort keys: [{"field":"createdAt","direction":"desc"}] (direction: asc or desc)';
9745
10198
  var CUSTOM_TOOL_SCHEMA_HELP = 'OpenAI function schema: {"type":"function","function":{"name":"...","parameters":{"type":"object","properties":{}}}}';
9746
10199
  var FOLDER_PATH_INPUT = {
@@ -9843,6 +10296,15 @@ var CLI_CONTRACT = {
9843
10296
  selectAll: { boolean: true, describe: "Apply to every document in the knowledge base" }
9844
10297
  }
9845
10298
  },
10299
+ listKnowledgeConnectorDocuments: {
10300
+ command: "knowledge connectors documents list"
10301
+ },
10302
+ updateKnowledgeConnectorDocuments: {
10303
+ command: "knowledge connectors documents update",
10304
+ flags: {
10305
+ documentIds: { name: "document", list: true }
10306
+ }
10307
+ },
9846
10308
  undeployWorkflow: {
9847
10309
  command: "workflows undeploy",
9848
10310
  describe: "Take a workflow out of deployment"
@@ -9864,11 +10326,17 @@ var CLI_CONTRACT = {
9864
10326
  pathArgumentNames: KNOWLEDGE_BASE_PATH_ARGUMENT,
9865
10327
  confirm: "This deletes the document and its embeddings."
9866
10328
  },
10329
+ deleteKnowledgeConnector: {
10330
+ confirm: "This deletes the connector; --delete-documents also deletes its synchronized documents."
10331
+ },
9867
10332
  deleteFile: { confirm: "This archives the file." },
9868
10333
  deleteCredential: {
9869
10334
  confirm: "This disconnects the credential and removes its stored authentication."
9870
10335
  },
9871
10336
  deleteSkill: { confirm: "This deletes the skill." },
10337
+ revokeSkillEditor: {
10338
+ confirm: "This revokes the explicit skill editor grant for the selected email."
10339
+ },
9872
10340
  deleteCustomTool: { confirm: "This deletes the custom tool." },
9873
10341
  deleteMcpServer: {
9874
10342
  confirm: "This removes the MCP server and the tools it provides."
@@ -9964,7 +10432,7 @@ var CLI_CONTRACT = {
9964
10432
  queryRows: {
9965
10433
  command: "tables rows query",
9966
10434
  flags: {
9967
- predicate: { name: "filter", json: true, describe: TABLE_FILTER_HELP },
10435
+ predicate: { name: "filter", json: true, describe: TABLE_READ_FILTER_HELP },
9968
10436
  sort: { json: true, describe: TABLE_SORT_HELP }
9969
10437
  },
9970
10438
  expand: "data"
@@ -10392,7 +10860,7 @@ var CLI_CONTRACT = {
10392
10860
  name: "filter",
10393
10861
  renamedFrom: ["predicate"],
10394
10862
  json: true,
10395
- describe: TABLE_FILTER_HELP
10863
+ describe: TABLE_READ_FILTER_HELP
10396
10864
  }
10397
10865
  }
10398
10866
  },
@@ -11151,7 +11619,7 @@ async function createServiceAccount(command, providerId, options) {
11151
11619
  if (provider.requiresClientGeneratedCredentialId && !options.id) {
11152
11620
  throw new SimApiError(`--id is required for ${providerId}.`, 0);
11153
11621
  }
11154
- const credentials = credentialValues(provider, options.credentials);
11622
+ const credentialFields = credentialValues(provider, options.credentials);
11155
11623
  const operation = V2_OPERATIONS.createServiceAccountCredential;
11156
11624
  const response = await client.request(operation.path, {
11157
11625
  method: operation.method,
@@ -11162,7 +11630,7 @@ async function createServiceAccount(command, providerId, options) {
11162
11630
  displayName: options.name,
11163
11631
  ...options.description ? { description: options.description } : {},
11164
11632
  ...options.id ? { id: options.id } : {},
11165
- ...credentials
11633
+ credentials: JSON.stringify(credentialFields)
11166
11634
  }
11167
11635
  });
11168
11636
  renderResult("createServiceAccountCredential", profile.output, response.data, SERVICE_ACCOUNT_RESULT);
@@ -12926,6 +13394,7 @@ with -P, --profile, or SIM_PROFILE.
12926
13394
 
12927
13395
  Examples:
12928
13396
  $ sim login Authorize the default profile
13397
+ $ sim profile add acme --workspace ws_123 Reuse that login for a workspace
12929
13398
  $ sim login --profile dev --endpoint http://localhost:3000
12930
13399
  $ sim workflows list
12931
13400
  $ sim logs list --level error --limit 20
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sim",
3
- "version": "2.1.0-dev.28.1",
3
+ "version": "2.1.0-dev.32.1",
4
4
  "description": "Sim CLI - talk to the Sim API from your terminal",
5
5
  "type": "module",
6
6
  "bin": {