@meistrari/tela-cli 1.10.1 → 1.12.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 CHANGED
@@ -1,7 +1,8 @@
1
1
  # Tela CLI
2
2
 
3
3
  `@meistrari/tela-cli` owns the local `tela` binary for scriptable access to
4
- Tela Local state and agent workflows.
4
+ Tela Local state, Pantry services and skills, agent repositories, and Workflow
5
+ Code projects.
5
6
 
6
7
  The CLI reads and writes the same `~/.tela/config.json` state as Tela Dock
7
8
  through `@meistrari/tela-local-config`. It should not parse local config files
@@ -9,7 +10,10 @@ directly, and normal output must not print token material.
9
10
 
10
11
  ## Commands
11
12
 
12
- Canonical commands:
13
+ Run `tela help`, `tela agent --help`, `tela skills --help`, or
14
+ `tela workflow --help` for the authoritative flag list.
15
+
16
+ Local state, auth, and Pantry services:
13
17
 
14
18
  ```bash
15
19
  tela auth login --no-interactive
@@ -32,14 +36,51 @@ tela service docs guide <slug> [path]
32
36
  tela service env list <slug> --pretty
33
37
  tela service env use <slug> <environment>
34
38
  tela doctor auth --json
39
+ ```
40
+
41
+ Managed Pantry skills:
42
+
43
+ ```bash
44
+ tela skills list --json
45
+ tela skills search <query> --json
46
+ tela skills files <slug> --json
47
+ tela skills cat <slug> [path]
48
+ tela skills install <slug...> --force --no-symlink --ignore-scripts --json
49
+ tela skills upgrade [slug...] --ignore-scripts --json
50
+ tela skills uninstall <slug...> --ignore-scripts --json
51
+ ```
52
+
53
+ Agent repository workflows:
54
+
55
+ ```bash
56
+ tela agent setup --list-options --context <context-id> --organization <id-or-name> --clone-root <path> --dry-run --json
35
57
  tela agent sync [repo] --current --all --dry-run --json --verbose
36
- tela agent run --input <name=path>... --plain --json --verbose
58
+ tela agent run --input <name=path>... --mode <one-shot|chat> --input-conflict <fail|overwrite|rename> --plain --json --verbose
37
59
  tela agent push --message <text> --claude --manual --dry-run --json --verbose
38
- tela agent feedback --list --apply <all|select> --json --verbose
60
+ tela agent merge [branch] --base <branch> --strategy <squash|merge> --keep-branch --yes --dry-run --json --verbose
61
+ tela agent feedback --list --apply <all|select> --feedback-id <id>... --json --verbose
39
62
  ```
40
63
 
64
+ Workflow Code:
65
+
66
+ ```bash
67
+ tela workflow init --directory <path> --workflow <key>... --package-manager <bun|pnpm|npm|yarn> --yes --json
68
+ tela workflow create <target> --project <project-id> --json
69
+ tela workflow compile [target] --config <path> --env <path> --json
70
+ tela workflow sync [target] --yes --json
71
+ tela workflow publish [target] --environment <name> --create-environment-if-missing --yes --json
72
+ tela workflow watch [target] --json
73
+ tela workflow codegen --json
74
+ tela workflow docs [page]
75
+ ```
76
+
77
+ `tela workflow` is routed into `@meistrari/workflow-code`; see
78
+ [`docs/workflow-code/`](../../docs/workflow-code/README.md) for its authoring
79
+ API, compiler, and state model. `tela workflow docs [page]` prints the
80
+ user-facing Workflow Code Page documentation bundled with the CLI.
81
+
41
82
  `auth login` uses device flow and creates or updates a context backed by the
42
- same Keychain credential adapter as Dock. `auth token` may refresh that session
83
+ same OS keychain credential adapter as Dock. `auth token` may refresh that session
43
84
  before returning metadata or printing a raw token. On macOS, renewing a Keychain
44
85
  session is delegated to Tela Dock, so sandboxed agents only read the saved
45
86
  credential. The CLI opens the installed Dock in the background if needed;
@@ -48,10 +48,11 @@ async function authLogin(args, options, io) {
48
48
  const environment = config.environments[environmentId];
49
49
  if (!environment)
50
50
  throw new Error(`Unknown Tela environment: ${environmentId}`);
51
+ // The CLI signs in to its own application; a configured
52
+ // targetApplicationId (older configs) is ignored.
51
53
  const requesterApplicationId = environment.authApplication?.requesterApplicationId;
52
- const targetApplicationId = environment.authApplication?.targetApplicationId;
53
- if (!requesterApplicationId || !targetApplicationId) {
54
- throw new Error(`Environment '${environmentId}' does not define auth application IDs. Configure them before logging in.`);
54
+ if (!requesterApplicationId) {
55
+ throw new Error(`Environment '${environmentId}' does not define an auth application ID. Configure it before logging in.`);
55
56
  }
56
57
  // auth-cli is ESM-only with no `require` export condition, so this
57
58
  // CJS bundle can only reach it through a dynamic import.
@@ -60,7 +61,6 @@ async function authLogin(args, options, io) {
60
61
  const flowOptions = {
61
62
  apiUrl: parsed.authUrl ?? environment.authApiUrl,
62
63
  requesterApplicationId,
63
- targetApplicationId,
64
64
  storage,
65
65
  };
66
66
  const loginContext = {
@@ -177,15 +177,21 @@ async function installSkills(args, options, io) {
177
177
  ignoreScripts: flags.ignoreScripts,
178
178
  ...(flags.symlink ? {} : { targets: [] }),
179
179
  });
180
+ // A failed install script rolls back only its own skill, so report the rest and still fail.
181
+ const installedSlugs = new Set(installed.map(entry => entry.slug));
182
+ const failed = flags.rest.filter(slug => !installedSlugs.has(slug));
180
183
  if (options.json) {
181
- (0, output_1.writeJson)(io, { installed });
182
- return 0;
184
+ (0, output_1.writeJson)(io, failed.length > 0 ? { installed, failed } : { installed });
185
+ return failed.length > 0 ? 1 : 0;
183
186
  }
184
187
  for (const entry of installed)
185
188
  (0, output_1.writeLine)(io, `Installed ${entry.slug}@${entry.sha.slice(0, 7)} to ${entry.dir}`);
186
- return 0;
189
+ return failed.length > 0 ? 1 : 0;
187
190
  }
188
191
  function writeSkillManagerDiagnostic(io, diagnostic) {
192
+ // A failed uninstall script throws, and the command prints that error.
193
+ if (diagnostic.type === 'lifecycle-failed' && diagnostic.phase === 'uninstall')
194
+ return;
189
195
  (0, output_1.writeError)(io, diagnostic.message);
190
196
  }
191
197
  async function upgradeSkills(args, options, io) {
package/dist/index.js CHANGED
@@ -1790,6 +1790,8 @@ var require_auth = __commonJS({
1790
1790
  exports2.refreshUserSessionCredential = refreshUserSessionCredential2;
1791
1791
  exports2.createStoredUserSession = createStoredUserSession;
1792
1792
  exports2.getJwtExpiresAt = getJwtExpiresAt;
1793
+ exports2.listApplicationOrganizations = listApplicationOrganizations;
1794
+ exports2.switchApplicationOrganization = switchApplicationOrganization;
1793
1795
  var node_buffer_1 = require("node:buffer");
1794
1796
  var credential_mutations_1 = require_credential_mutations();
1795
1797
  var credentials_1 = require_credentials();
@@ -1876,6 +1878,62 @@ var require_auth = __commonJS({
1876
1878
  return void 0;
1877
1879
  }
1878
1880
  }
1881
+ async function listApplicationOrganizations(environment, accessToken) {
1882
+ const applicationId = getJwtAudience(accessToken);
1883
+ if (!applicationId)
1884
+ throw new Error("The Tela session does not name its application. Sign in again.");
1885
+ const data = await authApiRequest(environment.authApiUrl, `/applications/candidate-organizations?applicationId=${encodeURIComponent(applicationId)}`, {
1886
+ headers: { authorization: `Bearer ${accessToken}` }
1887
+ });
1888
+ const organizations = Array.isArray(data.organizations) ? data.organizations : [];
1889
+ return organizations.flatMap((organization) => {
1890
+ if (!isPlainObject5(organization))
1891
+ return [];
1892
+ const id = stringField(organization.id);
1893
+ if (!id)
1894
+ return [];
1895
+ const slug = stringField(organization.slug);
1896
+ const logo = httpsUrlField(organization.logo);
1897
+ return [{
1898
+ id,
1899
+ name: stringField(organization.name) ?? stringField(organization.title) ?? slug ?? id,
1900
+ ...slug ? { slug } : {},
1901
+ ...logo ? { logo } : {}
1902
+ }];
1903
+ });
1904
+ }
1905
+ function httpsUrlField(value) {
1906
+ const text2 = stringField(value);
1907
+ if (!text2)
1908
+ return void 0;
1909
+ try {
1910
+ return new URL(text2).protocol === "https:" ? text2 : void 0;
1911
+ } catch {
1912
+ return void 0;
1913
+ }
1914
+ }
1915
+ async function switchApplicationOrganization(environment, accessToken, organizationId) {
1916
+ return await authApiRequest(environment.authApiUrl, "/applications/switch-organization", {
1917
+ method: "POST",
1918
+ headers: {
1919
+ "authorization": `Bearer ${accessToken}`,
1920
+ "x-tela-access-token": accessToken
1921
+ },
1922
+ body: { organizationId }
1923
+ });
1924
+ }
1925
+ function getJwtAudience(token) {
1926
+ try {
1927
+ const [, payload] = token.split(".");
1928
+ if (!payload)
1929
+ return void 0;
1930
+ const decoded = JSON.parse(node_buffer_1.Buffer.from(payload, "base64url").toString("utf8"));
1931
+ const audience = Array.isArray(decoded.aud) ? decoded.aud[0] : decoded.aud;
1932
+ return typeof audience === "string" && audience ? audience : void 0;
1933
+ } catch {
1934
+ return void 0;
1935
+ }
1936
+ }
1879
1937
  async function refreshApplicationToken(authApiUrl, refreshToken) {
1880
1938
  return await authApiRequest(authApiUrl, "/applications/token/refresh", {
1881
1939
  method: "POST",
@@ -1968,8 +2026,7 @@ var require_defaults = __commonJS({
1968
2026
  authApiUrl: "https://auth.tela.com",
1969
2027
  defaultApiUrl: "https://api.tela.com",
1970
2028
  authApplication: {
1971
- requesterApplicationId: "860d5506-cab2-4b8b-b08b-fb4222b65bb2",
1972
- targetApplicationId: "fe9fa028-4d34-407c-b27a-2ecc4779ae69"
2029
+ requesterApplicationId: "860d5506-cab2-4b8b-b08b-fb4222b65bb2"
1973
2030
  },
1974
2031
  services: {
1975
2032
  pantry: "https://pantry-api.tela.tools",
@@ -1984,8 +2041,7 @@ var require_defaults = __commonJS({
1984
2041
  authApiUrl: "https://auth-api.telastaging.com",
1985
2042
  defaultApiUrl: "https://api.telastaging.com",
1986
2043
  authApplication: {
1987
- requesterApplicationId: "2d2785a9-f79b-442c-a00c-5e41b25538ff",
1988
- targetApplicationId: "1c021ec1-b7d1-4d72-8733-ac69d930b2df"
2044
+ requesterApplicationId: "2d2785a9-f79b-442c-a00c-5e41b25538ff"
1989
2045
  },
1990
2046
  services: {
1991
2047
  agent: "https://agent-api.telastaging.com",
@@ -1998,8 +2054,7 @@ var require_defaults = __commonJS({
1998
2054
  authApiUrl: "http://127.0.0.1:3003",
1999
2055
  defaultApiUrl: "http://127.0.0.1:4000",
2000
2056
  authApplication: {
2001
- requesterApplicationId: "297ad7a7-d003-4097-b27a-f31498226f6e",
2002
- targetApplicationId: "4a727eb5-3933-4c4d-aa0e-620727c25181"
2057
+ requesterApplicationId: "297ad7a7-d003-4097-b27a-f31498226f6e"
2003
2058
  },
2004
2059
  services: {
2005
2060
  vault: "http://127.0.0.1:8443/_services/vault/"
@@ -18083,6 +18138,8 @@ var require_schema = __commonJS({
18083
18138
  }).strict();
18084
18139
  var authApplicationSchema = zod_1.z.object({
18085
18140
  requesterApplicationId: optionalNonEmptyString,
18141
+ // Written by older releases, which signed in to a second (target) app.
18142
+ // Logins now sign in to the requester alone, so it is read and ignored.
18086
18143
  targetApplicationId: optionalNonEmptyString
18087
18144
  }).strict();
18088
18145
  var environmentSchema = zod_1.z.object({
@@ -36886,7 +36943,7 @@ var init_zod = __esm({
36886
36943
  }
36887
36944
  });
36888
36945
 
36889
- // ../../node_modules/.pnpm/@meistrari+auth-core@1.39.3_@better-auth+core@1.6.26_@better-auth+utils@0.4.2_@better-f_5390ce4efcf7171453cb5c87c666430d/node_modules/@meistrari/auth-core/dist/index.mjs
36946
+ // ../../node_modules/.pnpm/@meistrari+auth-core@2.8.0_@better-auth+core@1.6.26_@better-auth+utils@0.4.2_@better-fe_c2707dc7f0525f2bbdb85ba87f9e4cbd/node_modules/@meistrari/auth-core/dist/index.mjs
36890
36947
  function parsedEnumValues(schema) {
36891
36948
  return Object.fromEntries(
36892
36949
  Object.entries(schema.enum).map(([key, value]) => [
@@ -36895,6 +36952,9 @@ function parsedEnumValues(schema) {
36895
36952
  ])
36896
36953
  );
36897
36954
  }
36955
+ function normalizeAmr(amr) {
36956
+ return amr === AMRs.Saml ? AMRs.Sso : amr;
36957
+ }
36898
36958
  function normalizeOrganizationsResponse(response) {
36899
36959
  if (Array.isArray(response)) {
36900
36960
  return {
@@ -37053,8 +37113,8 @@ function customEndpointsPluginClient() {
37053
37113
  getActions: ($fetch) => ({
37054
37114
  customEndpoints: {
37055
37115
  organizations: {
37056
- apiKeys: async () => {
37057
- return await $fetch("/custom-endpoints/organizations/api-keys");
37116
+ apiKeys: async (organizationId) => {
37117
+ return await $fetch("/custom-endpoints/organizations/api-keys", organizationId === void 0 ? void 0 : { query: { organizationId } });
37058
37118
  }
37059
37119
  },
37060
37120
  teams: {
@@ -37080,6 +37140,26 @@ function handshakePluginClient() {
37080
37140
  })
37081
37141
  };
37082
37142
  }
37143
+ function invitationSignupPluginClient() {
37144
+ return {
37145
+ id: "invitation-signup",
37146
+ getActions: ($fetch) => ({
37147
+ invitationSignup: {
37148
+ status: async (invitationId) => {
37149
+ return await $fetch("/invitation/signup-status", {
37150
+ query: { invitationId }
37151
+ });
37152
+ },
37153
+ setupPassword: async (invitationId, password2) => {
37154
+ return await $fetch("/invitation/setup-password", {
37155
+ method: "POST",
37156
+ body: { invitationId, password: password2 }
37157
+ });
37158
+ }
37159
+ }
37160
+ })
37161
+ };
37162
+ }
37083
37163
  function sessionAssurancePluginClient() {
37084
37164
  return {
37085
37165
  id: "session-assurance",
@@ -37107,6 +37187,20 @@ function sessionAssurancePluginClient() {
37107
37187
  })
37108
37188
  };
37109
37189
  }
37190
+ function loginMethodsPluginClient() {
37191
+ return {
37192
+ id: "login-methods",
37193
+ getActions: ($fetch) => {
37194
+ return {
37195
+ loginMethods: {
37196
+ get: async () => {
37197
+ return await $fetch("/login-methods", { method: "GET" });
37198
+ }
37199
+ }
37200
+ };
37201
+ }
37202
+ };
37203
+ }
37110
37204
  function createAPIClient(apiUrl, fetchOptions = {}) {
37111
37205
  const serviceName2 = typeof process !== "undefined" ? process.env.SERVICE_NAME : "";
37112
37206
  const userAgent2 = `auth-sdk:core:${version3}${serviceName2 ? `@${serviceName2}` : ""}`;
@@ -37123,6 +37217,7 @@ function createAPIClient(apiUrl, fetchOptions = {}) {
37123
37217
  }),
37124
37218
  customEndpointsPluginClient(),
37125
37219
  handshakePluginClient(),
37220
+ invitationSignupPluginClient(),
37126
37221
  ssoClient(),
37127
37222
  twoFactorClient(),
37128
37223
  jwtClient(),
@@ -37133,6 +37228,7 @@ function createAPIClient(apiUrl, fetchOptions = {}) {
37133
37228
  }),
37134
37229
  applicationsPluginClient(),
37135
37230
  sessionAssurancePluginClient(),
37231
+ loginMethodsPluginClient(),
37136
37232
  emailOTPClient()
37137
37233
  ],
37138
37234
  fetchOptions: {
@@ -37249,7 +37345,7 @@ function extractTokenPayload(token) {
37249
37345
  }
37250
37346
  var version3, AMRBrand, AALBrand, ACRBrand, AuthMethodBrand, AMR, AAL, ACR, AuthMethod, AMRs, AALs, ACRs, AuthMethods, statements, ac, Roles, rolesAccessControl, userAdditionalFields, JWTPayloadUser, JWTPayloadWorkspace, JWTPayloadAssurance, DEFAULT_JWT_ASSURANCE, JWTPayload, DEVICE_CODE_GRANT, CLIENT_CREDENTIALS_GRANT, ApplicationRouteError, AdminService, BaseError, ApplicationError, RefreshTokenExpiredError, ApplicationSessionAssuranceError, DeviceAuthorizationPendingError, DeviceAuthorizationSlowDownError, DeviceAccessDeniedError, DeviceCodeExpiredError, DeviceTransientServerError, OAuthStepUpProvider, StepUpOption, SessionAssurancePolicy, SENSITIVE_ACTION_ASSURANCE_POLICY, STEP_UP_MAX_AGE_SECONDS, SessionAssuranceRequiredCode, SessionAssurancePayloadAssurance, SessionAssuranceRequiredPayload, SessionAssuranceRequiredError, DEFAULT_SESSION_ASSURANCE_CONFIG, ApplicationSessionAssuranceConfig, ApplicationConfig, ApplicationService, OrganizationService, InvalidSocialProvider, InvalidCallbackURL, EmailRequired, SessionService, ApiKeyService, EmailOTPService, ClientJWTPayloadAssurance, SessionAssuranceResponse, OAuthBeginStepUpResponse, OAuthCompleteStepUpResponse, SendOtpResponse, SessionAssuranceService, UserService, ApiKeyMetadata, AuthClient;
37251
37347
  var init_dist2 = __esm({
37252
- "../../node_modules/.pnpm/@meistrari+auth-core@1.39.3_@better-auth+core@1.6.26_@better-auth+utils@0.4.2_@better-f_5390ce4efcf7171453cb5c87c666430d/node_modules/@meistrari/auth-core/dist/index.mjs"() {
37348
+ "../../node_modules/.pnpm/@meistrari+auth-core@2.8.0_@better-auth+core@1.6.26_@better-auth+utils@0.4.2_@better-fe_c2707dc7f0525f2bbdb85ba87f9e4cbd/node_modules/@meistrari/auth-core/dist/index.mjs"() {
37253
37349
  init_webapi();
37254
37350
  init_client();
37255
37351
  init_client2();
@@ -37258,7 +37354,7 @@ var init_dist2 = __esm({
37258
37354
  init_access2();
37259
37355
  init_access3();
37260
37356
  init_zod();
37261
- version3 = "1.39.3";
37357
+ version3 = "2.8.0";
37262
37358
  AMRBrand = /* @__PURE__ */ Symbol("amr");
37263
37359
  AALBrand = /* @__PURE__ */ Symbol("aal");
37264
37360
  ACRBrand = /* @__PURE__ */ Symbol("acr");
@@ -37268,6 +37364,11 @@ var init_dist2 = __esm({
37268
37364
  Otp: "otp",
37269
37365
  Oauth: "oauth",
37270
37366
  Sso: "sso",
37367
+ /**
37368
+ * @deprecated Issued to SSO sessions before SAML and OIDC were unified
37369
+ * under `sso`. Still parsed so existing sessions and application policies
37370
+ * remain valid, and read as `sso` (see `normalizeAmr`). Never issued.
37371
+ */
37271
37372
  Saml: "saml"
37272
37373
  }).brand(AMRBrand);
37273
37374
  AAL = zod_default.enum({
@@ -37282,6 +37383,11 @@ var init_dist2 = __esm({
37282
37383
  Password: "password",
37283
37384
  EmailOTP: "email_otp",
37284
37385
  OAuth: "oauth",
37386
+ SSO: "sso",
37387
+ /**
37388
+ * @deprecated Recorded on SSO sessions before SAML and OIDC were unified
37389
+ * under `sso`. Kept so existing sessions still parse; never recorded.
37390
+ */
37285
37391
  SAML: "saml",
37286
37392
  Unknown: "unknown"
37287
37393
  }).brand(AuthMethodBrand);
@@ -37402,6 +37508,8 @@ var init_dist2 = __esm({
37402
37508
  this.client = client;
37403
37509
  this.apiUrl = apiUrl;
37404
37510
  }
37511
+ client;
37512
+ apiUrl;
37405
37513
  /**
37406
37514
  * Requests an application management route.
37407
37515
  *
@@ -37683,6 +37791,47 @@ var init_dist2 = __esm({
37683
37791
  });
37684
37792
  return data;
37685
37793
  }
37794
+ /**
37795
+ * Lists applications with their entitlements as a global admin. Unlike
37796
+ * {@link listApplications}, which goes through the session-only application
37797
+ * routes, this uses the admin endpoint and so also works with a bearer token.
37798
+ *
37799
+ * @param options - Pagination, ordering, organization filter, search, and member preview options
37800
+ * @returns The application listing with entitlement details
37801
+ */
37802
+ async listAdminApplications(options2 = {}) {
37803
+ return await this.client.$fetch("/admin/applications", {
37804
+ method: "GET",
37805
+ query: {
37806
+ ...options2.limit !== void 0 ? { limit: options2.limit } : {},
37807
+ ...options2.offset !== void 0 ? { offset: options2.offset } : {},
37808
+ ...options2.search ? { search: options2.search } : {},
37809
+ ...options2.organizationId ? { organization_id: options2.organizationId } : {},
37810
+ ...options2.membersPreview !== void 0 ? { members_preview: options2.membersPreview } : {},
37811
+ ...options2.orderBy ? { sortBy: options2.orderBy } : {},
37812
+ ...options2.orderDirection ? { sortDirection: options2.orderDirection } : {}
37813
+ }
37814
+ });
37815
+ }
37816
+ /**
37817
+ * Lists the users with access to an application as a global admin, one row
37818
+ * per user with every organization that grants them access and the role it
37819
+ * grants.
37820
+ *
37821
+ * @param options - Application ID, organization filter, pagination, and search options
37822
+ * @returns The paginated application member listing
37823
+ */
37824
+ async listApplicationMembers(options2) {
37825
+ return await this.client.$fetch(`/admin/applications/${encodeURIComponent(options2.applicationId)}/members`, {
37826
+ method: "GET",
37827
+ query: {
37828
+ ...options2.limit !== void 0 ? { limit: options2.limit } : {},
37829
+ ...options2.offset !== void 0 ? { offset: options2.offset } : {},
37830
+ ...options2.query ? { query: options2.query } : {},
37831
+ ...options2.organizationId ? { organization_id: options2.organizationId } : {}
37832
+ }
37833
+ });
37834
+ }
37686
37835
  /**
37687
37836
  * Lists active members for one organization as a global admin.
37688
37837
  *
@@ -37740,6 +37889,36 @@ var init_dist2 = __esm({
37740
37889
  });
37741
37890
  return data;
37742
37891
  }
37892
+ /**
37893
+ * Updates a team for one organization. Global admins may update any
37894
+ * organization; an organization admin only their own.
37895
+ *
37896
+ * @param options - Organization ID, team ID, new name, and optional replacement member IDs
37897
+ * @returns The updated team
37898
+ */
37899
+ async updateOrganizationTeam(options2) {
37900
+ const { data } = await this.client.$fetch(`/admin/organizations/${encodeURIComponent(options2.organizationId)}/teams/update`, {
37901
+ method: "POST",
37902
+ body: {
37903
+ teamId: options2.teamId,
37904
+ name: options2.name,
37905
+ ...options2.memberIds !== void 0 ? { memberIds: options2.memberIds } : {}
37906
+ }
37907
+ });
37908
+ return data;
37909
+ }
37910
+ /**
37911
+ * Deletes a team for one organization, detaching its members with it.
37912
+ * Global admins may delete from any organization; an organization admin
37913
+ * only from their own.
37914
+ *
37915
+ * @param options - Organization ID and team ID
37916
+ */
37917
+ async deleteOrganizationTeam(options2) {
37918
+ await this.client.$fetch(`/admin/organizations/${encodeURIComponent(options2.organizationId)}/teams/${encodeURIComponent(options2.teamId)}`, {
37919
+ method: "DELETE"
37920
+ });
37921
+ }
37743
37922
  /**
37744
37923
  * Fetches a single entitlement together with its ordered access rules.
37745
37924
  *
@@ -37889,6 +38068,22 @@ var init_dist2 = __esm({
37889
38068
  async unbanUser(userId) {
37890
38069
  await this.client.$fetch("/admin/unban-user", { method: "POST", body: { userId } });
37891
38070
  }
38071
+ /**
38072
+ * Sets a user's password directly, without sending any email. Requires the
38073
+ * global admin role.
38074
+ * @param options - The user and their new password
38075
+ */
38076
+ async setUserPassword(options2) {
38077
+ await this.client.$fetch("/admin/set-user-password", { method: "POST", body: options2 });
38078
+ }
38079
+ /**
38080
+ * Signs a user out everywhere by revoking all of their sessions. Requires
38081
+ * the global admin role.
38082
+ * @param userId - The ID of the user whose sessions are revoked
38083
+ */
38084
+ async revokeUserSessions(userId) {
38085
+ await this.client.$fetch("/admin/revoke-user-sessions", { method: "POST", body: { userId } });
38086
+ }
37892
38087
  /**
37893
38088
  * Permanently removes a user from the auth system.
37894
38089
  * @param userId - The ID of the user to remove
@@ -38062,12 +38257,12 @@ var init_dist2 = __esm({
38062
38257
  acceptedAmr: [
38063
38258
  AMRs.Pwd,
38064
38259
  AMRs.Oauth,
38065
- AMRs.Saml
38260
+ AMRs.Sso
38066
38261
  ],
38067
38262
  minAal: AALs.Aal1
38068
38263
  };
38069
38264
  ApplicationSessionAssuranceConfig = zod_default.object({
38070
- acceptedAmr: AMR.array().optional().default(DEFAULT_SESSION_ASSURANCE_CONFIG.acceptedAmr),
38265
+ acceptedAmr: AMR.array().overwrite((amrs) => [...new Set(amrs.map(normalizeAmr))]).optional().default(DEFAULT_SESSION_ASSURANCE_CONFIG.acceptedAmr),
38071
38266
  minAal: AAL.refine(
38072
38267
  (aal) => aal !== AALs.Aal3,
38073
38268
  "Application session assurance currently supports aal1 and aal2 policies"
@@ -38086,6 +38281,7 @@ var init_dist2 = __esm({
38086
38281
  constructor(client) {
38087
38282
  this.client = client;
38088
38283
  }
38284
+ client;
38089
38285
  /**
38090
38286
  * Gets public display context for an application auth redirect.
38091
38287
  *
@@ -38103,7 +38299,9 @@ var init_dist2 = __esm({
38103
38299
  * Lists candidate organizations for a specific application.
38104
38300
  *
38105
38301
  * Returns organizations where the authenticated user is a member and
38106
- * the application has been enabled with appropriate entitlement rules.
38302
+ * the effective entitlement grants access through its rules. An enabled global
38303
+ * entitlement is selected first; organization entitlements are considered only
38304
+ * when no global entitlement is enabled.
38107
38305
  *
38108
38306
  * @param applicationId - The application ID to get candidate organizations for
38109
38307
  * @returns The list of candidate organizations
@@ -38152,7 +38350,7 @@ var init_dist2 = __esm({
38152
38350
  * on the verification URI to authorize the device.
38153
38351
  *
38154
38352
  * @param requesterApplicationId - The application requesting authorization
38155
- * @param targetApplicationId - The target application to gain access to
38353
+ * @param targetApplicationId - The target application to gain access to. Omit it to sign the user in to the requester application itself.
38156
38354
  * @returns Device authorization details including codes and verification URI
38157
38355
  */
38158
38356
  async startDeviceAuthorizationFlow(requesterApplicationId, targetApplicationId) {
@@ -38313,11 +38511,13 @@ var init_dist2 = __esm({
38313
38511
  * Switches the active organization for the authenticated user.
38314
38512
  *
38315
38513
  * Issues new access and refresh tokens scoped to the specified organization.
38316
- * The target organization must be entitled to the application and the entitlement's
38317
- * access rules must allow the current user.
38514
+ * Requires active membership in the target organization and an enabled entitlement
38515
+ * whose rules allow the user. The global entitlement is selected first, and its
38516
+ * denial never falls back to an organization entitlement. Organization entitlements
38517
+ * are considered only when no global entitlement is enabled.
38318
38518
  *
38319
38519
  * @param organizationId - The ID of the organization to switch to
38320
- * @param accessToken - Optional access token for authentication (uses cookie if not provided)
38520
+ * @param accessToken - Access token for authentication, sent as the `x-tela-access-token` header
38321
38521
  * @returns New authentication tokens and user/organization details
38322
38522
  */
38323
38523
  async switchOrganization(organizationId, accessToken) {
@@ -38348,6 +38548,7 @@ var init_dist2 = __esm({
38348
38548
  constructor(client) {
38349
38549
  this.client = client;
38350
38550
  }
38551
+ client;
38351
38552
  /**
38352
38553
  * Retrieves a single organization by ID with optional related data.
38353
38554
  *
@@ -38465,6 +38666,33 @@ var init_dist2 = __esm({
38465
38666
  member: response.member
38466
38667
  };
38467
38668
  }
38669
+ /**
38670
+ * Gets the pre-login signup status for an invitation.
38671
+ *
38672
+ * Public endpoint keyed by the invitation id from the emailed invite link.
38673
+ * While the invitation is pending, the result includes the invited email
38674
+ * and whether the invited user still needs to set up their account.
38675
+ *
38676
+ * @param id - The invitation ID from the invite link
38677
+ * @returns The invitation status with account-setup information
38678
+ */
38679
+ async getInvitationSignupStatus(id) {
38680
+ return await this.client.invitationSignup.status(id);
38681
+ }
38682
+ /**
38683
+ * Creates a password for an invited user who has no account yet.
38684
+ *
38685
+ * Only available once per invitation, while the invitation is pending and
38686
+ * the invited user has never signed in. The user is signed in as part of
38687
+ * the call; the session cookie is set on the response.
38688
+ *
38689
+ * @param id - The invitation ID from the invite link
38690
+ * @param password - The password to create (minimum 8 characters)
38691
+ * @returns The session token and signed-in user
38692
+ */
38693
+ async setupInvitationPassword(id, password2) {
38694
+ return await this.client.invitationSignup.setupPassword(id, password2);
38695
+ }
38468
38696
  /**
38469
38697
  * Removes a user from the active organization.
38470
38698
  *
@@ -38595,6 +38823,8 @@ var init_dist2 = __esm({
38595
38823
  this.client = client;
38596
38824
  this.apiUrl = apiUrl;
38597
38825
  }
38826
+ client;
38827
+ apiUrl;
38598
38828
  /**
38599
38829
  * Retrieves the current user session.
38600
38830
  *
@@ -38619,6 +38849,19 @@ var init_dist2 = __esm({
38619
38849
  async getToken() {
38620
38850
  return await this.client.token();
38621
38851
  }
38852
+ /**
38853
+ * Lists the login methods enabled in the environment. Public; safe to call
38854
+ * before authentication to decide which sign-in options to render.
38855
+ *
38856
+ * @returns The enabled login methods
38857
+ */
38858
+ async getLoginMethods() {
38859
+ const response = await this.client.loginMethods.get();
38860
+ if (!response.data) {
38861
+ throw new Error("No data returned from the API", { cause: response.error });
38862
+ }
38863
+ return response.data.loginMethods;
38864
+ }
38622
38865
  /**
38623
38866
  * Initiates social authentication flow with Google or Microsoft.
38624
38867
  *
@@ -38626,6 +38869,7 @@ var init_dist2 = __esm({
38626
38869
  * @param options.provider - The social provider
38627
38870
  * @param options.callbackURL - URL to redirect to after successful authentication
38628
38871
  * @param options.errorCallbackURL - URL to redirect to on error
38872
+ * @param options.loginHint - Email to preselect at the provider
38629
38873
  *
38630
38874
  * @throws {InvalidSocialProvider} When the provider is not 'google' or 'microsoft'
38631
38875
  * @throws {InvalidCallbackURL} When callback URLs are malformed
@@ -38633,7 +38877,8 @@ var init_dist2 = __esm({
38633
38877
  async signInWithSocialProvider({
38634
38878
  provider,
38635
38879
  callbackURL,
38636
- errorCallbackURL
38880
+ errorCallbackURL,
38881
+ loginHint
38637
38882
  }) {
38638
38883
  if (provider !== "google" && provider !== "microsoft") {
38639
38884
  throw new InvalidSocialProvider("Invalid social provider");
@@ -38648,21 +38893,23 @@ var init_dist2 = __esm({
38648
38893
  await this.client.signIn.social({
38649
38894
  provider,
38650
38895
  callbackURL,
38651
- errorCallbackURL: errorCallbackURL ?? `${this.apiUrl}/error?callbackURL=${appUrl}`
38896
+ errorCallbackURL: errorCallbackURL ?? `${this.apiUrl}/error?callbackURL=${appUrl}`,
38897
+ ...loginHint && { loginHint }
38652
38898
  });
38653
38899
  }
38654
38900
  /**
38655
- * Initiates SAML-based Single Sign-On authentication flow.
38901
+ * Initiates the Single Sign-On authentication flow. The provider resolved
38902
+ * from the email's domain determines whether SAML or OIDC is used.
38656
38903
  *
38657
- * @param options - SAML sign-in configuration
38658
- * @param options.email - User's email to determine the SAML provider
38904
+ * @param options - SSO sign-in configuration
38905
+ * @param options.email - User's email to determine the SSO provider
38659
38906
  * @param options.callbackURL - URL to redirect to after successful authentication
38660
38907
  * @param options.errorCallbackURL - URL to redirect to on error
38661
38908
  *
38662
38909
  * @throws {EmailRequired} When email is not provided
38663
38910
  * @throws {InvalidCallbackURL} When callback URLs are malformed
38664
38911
  */
38665
- async signInWithSaml({
38912
+ async signInWithSso({
38666
38913
  email: email3,
38667
38914
  callbackURL,
38668
38915
  errorCallbackURL
@@ -38680,8 +38927,7 @@ var init_dist2 = __esm({
38680
38927
  await this.client.signIn.sso({
38681
38928
  email: email3,
38682
38929
  callbackURL,
38683
- errorCallbackURL: errorCallbackURL ?? `${this.apiUrl}/error?callbackURL=${appUrl}`,
38684
- providerType: "saml"
38930
+ errorCallbackURL: errorCallbackURL ?? `${this.apiUrl}/error?callbackURL=${appUrl}`
38685
38931
  });
38686
38932
  }
38687
38933
  /**
@@ -38758,6 +39004,7 @@ var init_dist2 = __esm({
38758
39004
  constructor(client) {
38759
39005
  this.client = client;
38760
39006
  }
39007
+ client;
38761
39008
  /**
38762
39009
  * Creates a new API key.
38763
39010
  *
@@ -38797,12 +39044,13 @@ var init_dist2 = __esm({
38797
39044
  return await this.client.apiKey.list();
38798
39045
  }
38799
39046
  /**
38800
- * Lists all API keys belonging to the active organization.
39047
+ * Lists all API keys belonging to an organization.
38801
39048
  *
39049
+ * @param organizationId - Defaults to the active organization. Only global admins may select another organization.
38802
39050
  * @returns An array of API keys (without secret key values)
38803
39051
  */
38804
- async listOrganizationApiKeys() {
38805
- return await this.client.customEndpoints.organizations.apiKeys();
39052
+ async listOrganizationApiKeys(organizationId) {
39053
+ return await this.client.customEndpoints.organizations.apiKeys(organizationId);
38806
39054
  }
38807
39055
  /**
38808
39056
  * Updates an existing API key.
@@ -38836,6 +39084,7 @@ var init_dist2 = __esm({
38836
39084
  constructor(client) {
38837
39085
  this.client = client;
38838
39086
  }
39087
+ client;
38839
39088
  /**
38840
39089
  * Sends a sign-in verification OTP to an email address.
38841
39090
  *
@@ -38881,6 +39130,7 @@ var init_dist2 = __esm({
38881
39130
  constructor(client) {
38882
39131
  this.client = client;
38883
39132
  }
39133
+ client;
38884
39134
  async get() {
38885
39135
  return parseResponse(SessionAssuranceResponse, await this.client.sessionAssurance.getSessionAssurance());
38886
39136
  }
@@ -38904,6 +39154,7 @@ var init_dist2 = __esm({
38904
39154
  constructor(client) {
38905
39155
  this.client = client;
38906
39156
  }
39157
+ client;
38907
39158
  async changePassword(currentPassword, newPassword) {
38908
39159
  return await this.client.changePassword({ currentPassword, newPassword, revokeOtherSessions: true });
38909
39160
  }
@@ -39876,7 +40127,7 @@ var init_emittery = __esm({
39876
40127
  }
39877
40128
  });
39878
40129
 
39879
- // ../../node_modules/.pnpm/@meistrari+auth-cli@1.6.0_@better-auth+core@1.6.26_@better-auth+utils@0.4.2_@better-fet_0bf65f4b8ffb95a65428638518b6161c/node_modules/@meistrari/auth-cli/dist/index.mjs
40130
+ // ../../node_modules/.pnpm/@meistrari+auth-cli@1.7.0_@better-auth+core@1.6.26_@better-auth+utils@0.4.2_@better-fet_7a4cef6c29c6b591a268b55d655638b9/node_modules/@meistrari/auth-cli/dist/index.mjs
39880
40131
  var dist_exports = {};
39881
40132
  __export(dist_exports, {
39882
40133
  ApplicationSessionAssuranceError: () => ApplicationSessionAssuranceError,
@@ -39895,7 +40146,7 @@ function isNotFound(err) {
39895
40146
  }
39896
40147
  var import_promises2, import_promises3, import_node_path, DeviceFlowAbortedError, DEFAULT_POLL_INTERVAL, DeviceFlow, JsonFileStorage;
39897
40148
  var init_dist3 = __esm({
39898
- "../../node_modules/.pnpm/@meistrari+auth-cli@1.6.0_@better-auth+core@1.6.26_@better-auth+utils@0.4.2_@better-fet_0bf65f4b8ffb95a65428638518b6161c/node_modules/@meistrari/auth-cli/dist/index.mjs"() {
40149
+ "../../node_modules/.pnpm/@meistrari+auth-cli@1.7.0_@better-auth+core@1.6.26_@better-auth+utils@0.4.2_@better-fet_7a4cef6c29c6b591a268b55d655638b9/node_modules/@meistrari/auth-cli/dist/index.mjs"() {
39899
40150
  import_promises2 = require("node:timers/promises");
39900
40151
  init_dist2();
39901
40152
  init_dist2();
@@ -39911,6 +40162,7 @@ var init_dist3 = __esm({
39911
40162
  super("Device flow was aborted");
39912
40163
  this.reason = reason;
39913
40164
  }
40165
+ reason;
39914
40166
  name = "DeviceFlowAbortedError";
39915
40167
  };
39916
40168
  DEFAULT_POLL_INTERVAL = 5;
@@ -40231,6 +40483,7 @@ var init_dist3 = __esm({
40231
40483
  this.#indent = options2.indent ?? 2;
40232
40484
  this.#mode = options2.mode ?? 384;
40233
40485
  }
40486
+ path;
40234
40487
  #indent;
40235
40488
  #mode;
40236
40489
  /**
@@ -76983,9 +77236,7 @@ var require_skill_filesystem = __commonJS({
76983
77236
  var node_os_1 = require("node:os");
76984
77237
  var node_path_1 = __importDefault(require("node:path"));
76985
77238
  var node_process_1 = __importDefault(require("node:process"));
76986
- var node_util_1 = require("node:util");
76987
77239
  var archive_1 = require_archive();
76988
- var run = (0, node_util_1.promisify)(node_child_process_1.execFile);
76989
77240
  function resolveDefaultSkillManagerPaths(home = (0, node_os_1.homedir)()) {
76990
77241
  return {
76991
77242
  installRoot: node_path_1.default.join(home, ".agents", "skills"),
@@ -77172,14 +77423,72 @@ There is now nothing at ${target}. The previous installation is intact at ${back
77172
77423
  return { removed: false, reason: `Skipped ${linkPath}: ${describeError(error48)}` };
77173
77424
  }
77174
77425
  }
77175
- async function runSkillScript(script, cwd) {
77426
+ var SCRIPT_KILL_GRACE_MS = 3e3;
77427
+ var SCRIPT_GROUP_POLL_MS = 50;
77428
+ var MAX_SCRIPT_OUTPUT_LENGTH = 64 * 1024;
77429
+ async function runSkillScript(script, cwd, onOutput, signal) {
77430
+ if (signal?.aborted)
77431
+ return { ok: false, cancelled: true, output: "" };
77432
+ return await new Promise((resolve) => {
77433
+ const child = (0, node_child_process_1.spawn)("sh", ["-c", script], { cwd, detached: node_process_1.default.platform !== "win32" });
77434
+ let output = "";
77435
+ let settled = false;
77436
+ let cancelled = false;
77437
+ const append2 = (chunk) => {
77438
+ if (settled)
77439
+ return;
77440
+ const text2 = chunk.toString();
77441
+ output = `${output}${text2}`.slice(-MAX_SCRIPT_OUTPUT_LENGTH);
77442
+ onOutput?.(text2);
77443
+ };
77444
+ const finish = (ok) => {
77445
+ if (settled)
77446
+ return;
77447
+ settled = true;
77448
+ signal?.removeEventListener("abort", cancel2);
77449
+ resolve({ ok: ok && !cancelled, cancelled, output: output.trimEnd() });
77450
+ };
77451
+ function cancel2() {
77452
+ cancelled = true;
77453
+ killProcessTree(child, "SIGTERM");
77454
+ setTimeout(() => killProcessTree(child, "SIGKILL"), SCRIPT_KILL_GRACE_MS).unref();
77455
+ }
77456
+ signal?.addEventListener("abort", cancel2, { once: true });
77457
+ child.stdout.on("data", append2);
77458
+ child.stderr.on("data", append2);
77459
+ child.on("error", (error48) => {
77460
+ append2(`${describeError(error48)}
77461
+ `);
77462
+ finish(false);
77463
+ });
77464
+ child.on("exit", () => {
77465
+ if (cancelled)
77466
+ void waitForProcessGroupExit(child).then(() => finish(false));
77467
+ });
77468
+ child.on("close", (code) => finish(code === 0));
77469
+ });
77470
+ }
77471
+ async function waitForProcessGroupExit(child) {
77472
+ if (child.pid === void 0 || node_process_1.default.platform === "win32")
77473
+ return;
77474
+ while (true) {
77475
+ try {
77476
+ node_process_1.default.kill(-child.pid, 0);
77477
+ } catch {
77478
+ return;
77479
+ }
77480
+ await new Promise((resolve) => setTimeout(resolve, SCRIPT_GROUP_POLL_MS));
77481
+ }
77482
+ }
77483
+ function killProcessTree(child, signal) {
77484
+ if (child.pid === void 0)
77485
+ return;
77176
77486
  try {
77177
- const { stdout: stdout2, stderr } = await run("sh", ["-c", script], { cwd });
77178
- return { ok: true, output: [stdout2, stderr].map((text2) => text2.trimEnd()).filter(Boolean).join("\n") };
77179
- } catch (error48) {
77180
- const shell = error48;
77181
- const output = [shell.stdout ?? "", shell.stderr ?? "", describeError(error48)].map((text2) => text2.trimEnd()).filter(Boolean).join("\n");
77182
- return { ok: false, output };
77487
+ if (node_process_1.default.platform === "win32")
77488
+ (0, node_child_process_1.spawn)("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore" });
77489
+ else
77490
+ node_process_1.default.kill(-child.pid, signal);
77491
+ } catch {
77183
77492
  }
77184
77493
  }
77185
77494
  function describeError(error48) {
@@ -77236,8 +77545,8 @@ var require_skills = __commonJS({
77236
77545
  ...path2 ? { queries: { path: path2 } } : {}
77237
77546
  }));
77238
77547
  }
77239
- async function downloadSkillArchive(archiveUrl) {
77240
- const response = await fetch(archiveUrl);
77548
+ async function downloadSkillArchive(archiveUrl, signal) {
77549
+ const response = await fetch(archiveUrl, { signal });
77241
77550
  if (!response.ok) {
77242
77551
  (0, http_1.assertHttpOk)(response, await response.text().catch(() => ""), "Failed to download skill archive");
77243
77552
  }
@@ -77254,7 +77563,7 @@ var require_skill_manager = __commonJS({
77254
77563
  return mod && mod.__esModule ? mod : { "default": mod };
77255
77564
  };
77256
77565
  Object.defineProperty(exports2, "__esModule", { value: true });
77257
- exports2.SkillInstallDirectoryConflictError = exports2.SkillLifecycleError = exports2.resolveDefaultSkillManagerPaths = void 0;
77566
+ exports2.SkillInstallDirectoryConflictError = exports2.SkillInstallCancelledError = exports2.SkillLifecycleError = exports2.resolveDefaultSkillManagerPaths = void 0;
77258
77567
  exports2.createSkillManager = createSkillManager2;
77259
77568
  var promises_1 = require("node:fs/promises");
77260
77569
  var node_path_1 = __importDefault(require("node:path"));
@@ -77276,10 +77585,22 @@ var require_skill_manager = __commonJS({
77276
77585
  this.phase = phase;
77277
77586
  this.name = "SkillLifecycleError";
77278
77587
  this.slug = entry.slug;
77279
- this.retainedState = phase === "install" ? "installed" : "unchanged";
77588
+ this.retainedState = "unchanged";
77280
77589
  }
77281
77590
  };
77282
77591
  exports2.SkillLifecycleError = SkillLifecycleError;
77592
+ var SkillInstallCancelledError = class extends Error {
77593
+ slug;
77594
+ installed;
77595
+ code = "skill-install-cancelled";
77596
+ constructor(slug, installed) {
77597
+ super(`Installation was cancelled before ${slug} finished.`);
77598
+ this.slug = slug;
77599
+ this.installed = installed;
77600
+ this.name = "SkillInstallCancelledError";
77601
+ }
77602
+ };
77603
+ exports2.SkillInstallCancelledError = SkillInstallCancelledError;
77283
77604
  var SkillInstallDirectoryConflictError = class extends Error {
77284
77605
  code = "skill-install-directory-conflict";
77285
77606
  path;
@@ -77314,17 +77635,31 @@ Run \`tela skills install ${slug} --force\` to replace it, or remove it by hand.
77314
77635
  const diagnostics = [];
77315
77636
  const installed = [];
77316
77637
  const emit = createDiagnosticEmitter(options2, diagnostics);
77638
+ const signal = installOptions.signal;
77317
77639
  for (const slug of slugs) {
77318
- const entry = await installOne({
77319
- client,
77320
- diagnostics: emit,
77321
- force: installOptions.force ?? false,
77322
- ignoreScripts: installOptions.ignoreScripts ?? false,
77323
- inventoryFile,
77324
- paths,
77325
- slug,
77326
- targets
77327
- });
77640
+ if (signal?.aborted)
77641
+ throw new SkillInstallCancelledError(slug, installed);
77642
+ let entry;
77643
+ try {
77644
+ entry = await installOne({
77645
+ client,
77646
+ diagnostics: emit,
77647
+ force: installOptions.force ?? false,
77648
+ ignoreScripts: installOptions.ignoreScripts ?? false,
77649
+ inventoryFile,
77650
+ paths,
77651
+ progress: options2.onInstallProgress,
77652
+ signal,
77653
+ slug,
77654
+ targets
77655
+ });
77656
+ } catch (error48) {
77657
+ if (signal?.aborted)
77658
+ throw new SkillInstallCancelledError(slug, installed);
77659
+ if (error48 instanceof SkillLifecycleError && error48.phase === "install")
77660
+ continue;
77661
+ throw error48;
77662
+ }
77328
77663
  if (!entry)
77329
77664
  throw new Error(`Skill install for ${slug} did not produce an inventory entry.`);
77330
77665
  installed.push(entry);
@@ -77358,6 +77693,7 @@ Run \`tela skills install ${slug} --force\` to replace it, or remove it by hand.
77358
77693
  ignoreScripts: upgradeOptions.ignoreScripts ?? false,
77359
77694
  inventoryFile,
77360
77695
  paths,
77696
+ progress: options2.onInstallProgress,
77361
77697
  slug: replacementSlug,
77362
77698
  targets
77363
77699
  });
@@ -77388,6 +77724,7 @@ Run \`tela skills install ${slug} --force\` to replace it, or remove it by hand.
77388
77724
  inventoryFile,
77389
77725
  operation: "upgrade",
77390
77726
  paths,
77727
+ progress: options2.onInstallProgress,
77391
77728
  slug: current.slug,
77392
77729
  targets
77393
77730
  });
@@ -77486,30 +77823,68 @@ Run \`tela skills install ${slug} --force\` to replace it, or remove it by hand.
77486
77823
  };
77487
77824
  }
77488
77825
  async function installOne(options2) {
77826
+ options2.progress?.({
77827
+ type: "phase",
77828
+ slug: options2.slug,
77829
+ phase: "fetching-metadata",
77830
+ message: "Fetching skill metadata"
77831
+ });
77489
77832
  const { ingredient, snapshot } = await (0, skills_1.fetchSkillFile)(options2.client, options2.slug);
77490
- const archive = await (0, skills_1.downloadSkillArchive)(snapshot.archiveUrl);
77833
+ options2.progress?.({
77834
+ type: "phase",
77835
+ slug: options2.slug,
77836
+ phase: "downloading",
77837
+ message: "Downloading skill files"
77838
+ });
77839
+ const archive = await (0, skills_1.downloadSkillArchive)(snapshot.archiveUrl, options2.signal);
77840
+ options2.signal?.throwIfAborted();
77841
+ options2.progress?.({
77842
+ type: "phase",
77843
+ slug: options2.slug,
77844
+ phase: "extracting",
77845
+ message: "Verifying and extracting files"
77846
+ });
77491
77847
  const files = (0, archive_1.verifySnapshotFiles)((0, archive_1.parseTarGz)(archive), snapshot.manifest);
77492
77848
  const target = (0, skill_filesystem_1.resolveInstallDir)(options2.paths, options2.slug);
77493
- const stagedInstalls = [];
77849
+ const reportConcurrentChange = () => options2.diagnostics({
77850
+ type: "concurrent-change",
77851
+ slug: options2.slug,
77852
+ operation: options2.operation ?? "install",
77853
+ message: `Skipped ${options2.slug}: it was changed while this ${options2.operation ?? "install"} ran`
77854
+ });
77855
+ const before = (await (0, tela_local_config_1.listInstalledSkills)(options2.inventoryFile)).find((entry2) => entry2.slug === options2.slug) ?? null;
77856
+ if (options2.expectedCurrent && !isSameRegistration(before, options2.expectedCurrent)) {
77857
+ reportConcurrentChange();
77858
+ return null;
77859
+ }
77860
+ if ((0, skill_filesystem_1.isUnownedInstallDir)(target, before?.dir ?? null) && !options2.force)
77861
+ throw new SkillInstallDirectoryConflictError(options2.slug, target);
77862
+ const staged = await (0, skill_filesystem_1.stageSkillFiles)(target, files);
77494
77863
  let concurrentChange = false;
77495
77864
  let entry = null;
77496
77865
  try {
77866
+ const lifecycleEntry = {
77867
+ slug: options2.slug,
77868
+ dir: target,
77869
+ sha: snapshot.sha,
77870
+ bundle: ingredient.bundle,
77871
+ symlinks: [],
77872
+ scripts: ingredient.scripts,
77873
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
77874
+ };
77875
+ await runLifecycle(lifecycleEntry, "install", options2.ignoreScripts, options2.diagnostics, options2.progress, options2.signal);
77876
+ options2.signal?.throwIfAborted();
77877
+ options2.progress?.({
77878
+ type: "phase",
77879
+ slug: options2.slug,
77880
+ phase: "finalizing",
77881
+ message: "Linking and finalizing installation"
77882
+ });
77497
77883
  entry = await (0, tela_local_config_1.updateInstalledSkill)(options2.slug, async (registered) => {
77498
- if (options2.expectedCurrent && (registered === null || !isSameInstall(registered, options2.expectedCurrent))) {
77884
+ if (!isSameRegistration(registered, before)) {
77499
77885
  concurrentChange = true;
77500
- options2.diagnostics({
77501
- type: "concurrent-change",
77502
- slug: options2.slug,
77503
- operation: options2.operation ?? "install",
77504
- message: `Skipped ${options2.slug}: it was changed while this upgrade ran`
77505
- });
77506
77886
  return registered;
77507
77887
  }
77508
- if ((0, skill_filesystem_1.isUnownedInstallDir)(target, registered?.dir ?? null) && !options2.force) {
77509
- throw new SkillInstallDirectoryConflictError(options2.slug, target);
77510
- }
77511
- const staged = await (0, skill_filesystem_1.stageSkillFiles)(target, files);
77512
- stagedInstalls.push(staged);
77513
77888
  const desiredLinkPaths = new Set(options2.targets.map((linkTarget) => node_path_1.default.join(linkTarget.root, options2.slug)));
77514
77889
  for (const linkPath of registered?.symlinks ?? []) {
77515
77890
  if (desiredLinkPaths.has(linkPath))
@@ -77538,25 +77913,27 @@ Run \`tela skills install ${slug} --force\` to replace it, or remove it by hand.
77538
77913
  }
77539
77914
  }
77540
77915
  return {
77541
- slug: options2.slug,
77542
- dir: target,
77543
- sha: snapshot.sha,
77544
- bundle: ingredient.bundle,
77545
- symlinks,
77546
- scripts: ingredient.scripts,
77547
- installedAt: (/* @__PURE__ */ new Date()).toISOString()
77916
+ ...lifecycleEntry,
77917
+ symlinks
77548
77918
  };
77549
77919
  }, options2.inventoryFile);
77550
77920
  } catch (error48) {
77551
- await rollbackStagedInstall(stagedInstalls.at(-1) ?? null, error48);
77921
+ options2.progress?.({
77922
+ type: "phase",
77923
+ slug: options2.slug,
77924
+ phase: "rolling-back",
77925
+ message: "Rolling back failed installation"
77926
+ });
77927
+ await rollbackStagedInstall(staged, error48);
77552
77928
  }
77553
- if (concurrentChange)
77929
+ if (concurrentChange) {
77930
+ await staged.rollback();
77931
+ reportConcurrentChange();
77554
77932
  return null;
77555
- for (const staged of stagedInstalls)
77556
- await staged.commit();
77933
+ }
77934
+ await staged.commit();
77557
77935
  if (!entry)
77558
77936
  throw new Error(`Skill install for ${options2.slug} did not produce an inventory entry.`);
77559
- await runLifecycle(entry, "install", options2.ignoreScripts, options2.diagnostics);
77560
77937
  return entry;
77561
77938
  }
77562
77939
  function selectUpgradeTargets(installed, slugs) {
@@ -77575,6 +77952,11 @@ Run \`tela skills install ${slug} --force\` to replace it, or remove it by hand.
77575
77952
  throw new Error(`${missing.join(", ")} not installed by tela. Nothing to remove.`);
77576
77953
  return slugs.map((slug) => bySlug.get(slug));
77577
77954
  }
77955
+ function isSameRegistration(left, right) {
77956
+ if (left === null || right === null)
77957
+ return left === right;
77958
+ return isSameInstall(left, right);
77959
+ }
77578
77960
  function isSameInstall(left, right) {
77579
77961
  return left.installedAt === right.installedAt && left.sha === right.sha && left.dir === right.dir;
77580
77962
  }
@@ -77595,7 +77977,7 @@ Cleanup failed: ${describeError(cleanupError)}`);
77595
77977
  }
77596
77978
  throw error48;
77597
77979
  }
77598
- async function runLifecycle(entry, phase, ignoreScripts, emit) {
77980
+ async function runLifecycle(entry, phase, ignoreScripts, emit, progress2, signal) {
77599
77981
  const script = entry.scripts?.[phase];
77600
77982
  if (!script)
77601
77983
  return;
@@ -77616,7 +77998,17 @@ Cleanup failed: ${describeError(cleanupError)}`);
77616
77998
  script,
77617
77999
  message: `Running ${phase} script for ${entry.slug}: ${script}`
77618
78000
  });
77619
- const outcome = await (0, skill_filesystem_1.runSkillScript)(script, entry.dir);
78001
+ if (phase === "install") {
78002
+ progress2?.({
78003
+ type: "phase",
78004
+ slug: entry.slug,
78005
+ phase: "running-install-script",
78006
+ message: "Running install script"
78007
+ });
78008
+ }
78009
+ const outcome = await (0, skill_filesystem_1.runSkillScript)(script, entry.dir, phase === "install" ? (output) => progress2?.({ type: "script-output", slug: entry.slug, output }) : void 0, signal);
78010
+ if (outcome.cancelled)
78011
+ signal?.throwIfAborted();
77620
78012
  if (outcome.output) {
77621
78013
  emit({
77622
78014
  type: "lifecycle-output",
@@ -77627,7 +78019,14 @@ Cleanup failed: ${describeError(cleanupError)}`);
77627
78019
  });
77628
78020
  }
77629
78021
  if (!outcome.ok) {
77630
- throw new SkillLifecycleError(entry, phase);
78022
+ const error48 = new SkillLifecycleError(entry, phase);
78023
+ emit({
78024
+ type: "lifecycle-failed",
78025
+ slug: entry.slug,
78026
+ phase,
78027
+ message: error48.message
78028
+ });
78029
+ throw error48;
77631
78030
  }
77632
78031
  }
77633
78032
  function lifecycleFailureMessage(entry, phase) {
@@ -77636,8 +78035,8 @@ Cleanup failed: ${describeError(cleanupError)}`);
77636
78035
  return `${opener}
77637
78036
  ${entry.slug} was left in place. Re-run with --ignore-scripts to remove it anyway.`;
77638
78037
  return `${opener}
77639
- ${entry.slug} was installed to ${entry.dir} and recorded, but its install script did not finish, so the skill may not work.
77640
- Remove it with \`tela skills uninstall ${entry.slug}\`, or reinstall without the script: \`tela skills install ${entry.slug} --ignore-scripts\`.`;
78038
+ ${entry.slug} was not installed because its files and inventory entry were rolled back.
78039
+ Retry without the script with \`tela skills install ${entry.slug} --ignore-scripts\`.`;
77641
78040
  }
77642
78041
  function normalizePaths(paths) {
77643
78042
  const installRoot = normalizeAbsolutePath(paths.installRoot, "installRoot");
@@ -387685,10 +388084,9 @@ async function authLogin(args, options2, io) {
387685
388084
  if (!environment)
387686
388085
  throw new Error(`Unknown Tela environment: ${environmentId}`);
387687
388086
  const requesterApplicationId = environment.authApplication?.requesterApplicationId;
387688
- const targetApplicationId = environment.authApplication?.targetApplicationId;
387689
- if (!requesterApplicationId || !targetApplicationId) {
388087
+ if (!requesterApplicationId) {
387690
388088
  throw new Error(
387691
- `Environment '${environmentId}' does not define auth application IDs. Configure them before logging in.`
388089
+ `Environment '${environmentId}' does not define an auth application ID. Configure it before logging in.`
387692
388090
  );
387693
388091
  }
387694
388092
  const { DeviceFlow: DeviceFlow2 } = await Promise.resolve().then(() => (init_dist3(), dist_exports));
@@ -387696,7 +388094,6 @@ async function authLogin(args, options2, io) {
387696
388094
  const flowOptions = {
387697
388095
  apiUrl: parsed.authUrl ?? environment.authApiUrl,
387698
388096
  requesterApplicationId,
387699
- targetApplicationId,
387700
388097
  storage
387701
388098
  };
387702
388099
  const loginContext = {
@@ -389081,15 +389478,19 @@ async function installSkills(args, options2, io) {
389081
389478
  ignoreScripts: flags.ignoreScripts,
389082
389479
  ...flags.symlink ? {} : { targets: [] }
389083
389480
  });
389481
+ const installedSlugs = new Set(installed.map((entry) => entry.slug));
389482
+ const failed = flags.rest.filter((slug) => !installedSlugs.has(slug));
389084
389483
  if (options2.json) {
389085
- writeJson(io, { installed });
389086
- return 0;
389484
+ writeJson(io, failed.length > 0 ? { installed, failed } : { installed });
389485
+ return failed.length > 0 ? 1 : 0;
389087
389486
  }
389088
389487
  for (const entry of installed)
389089
389488
  writeLine(io, `Installed ${entry.slug}@${entry.sha.slice(0, 7)} to ${entry.dir}`);
389090
- return 0;
389489
+ return failed.length > 0 ? 1 : 0;
389091
389490
  }
389092
389491
  function writeSkillManagerDiagnostic(io, diagnostic) {
389492
+ if (diagnostic.type === "lifecycle-failed" && diagnostic.phase === "uninstall")
389493
+ return;
389093
389494
  writeError(io, diagnostic.message);
389094
389495
  }
389095
389496
  async function upgradeSkills(args, options2, io) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meistrari/tela-cli",
3
- "version": "1.10.1",
3
+ "version": "1.12.0",
4
4
  "description": "Tela command line tools for local state and agent workflows.",
5
5
  "engines": {
6
6
  "node": ">=22"
@@ -1,17 +1,18 @@
1
1
  ---
2
2
  name: tela-cli
3
- description: Use when a coding agent needs to operate Tela Agent repositories or Tela Pantry/service ingredients through the local `tela` CLI with non-interactive, flag-driven commands and JSON output.
3
+ description: Use when a coding agent needs to operate Tela Agent repositories, Tela Pantry/service ingredients, managed Pantry skills, or Workflow Code projects through the local `tela` CLI with non-interactive, flag-driven commands and JSON output.
4
4
  ---
5
5
 
6
6
  # Tela CLI
7
7
 
8
- Use the local `tela` binary for Tela Agent repository workflows and Pantry-backed
9
- service/library discovery. Prefer explicit flags and `--json` so commands are
8
+ Use the local `tela` binary for Tela Agent repository workflows, Pantry-backed
9
+ service/library discovery, managed skills, and Workflow Code projects. Prefer explicit flags and `--json` so commands are
10
10
  safe in non-interactive agent sessions.
11
11
 
12
12
  ## Subpaths
13
13
 
14
14
  - Agent workflows: read `references/agent.md` before running `tela agent ...`.
15
+ - Workflow Code: read `tela workflow docs` before authoring or changing workflows.
15
16
 
16
17
  ## Core Rules
17
18
 
@@ -21,6 +22,7 @@ safe in non-interactive agent sessions.
21
22
  - Read endpoint or library docs before calling a service: `tela service docs endpoints <slug>` then `tela service docs reference <slug> <METHOD> <PATH>`.
22
23
  - Prefer `--json` for machine-readable output and errors.
23
24
  - Do not rely on prompts in coding-agent sessions; pass every required value by flag.
25
+ - Pass `--yes` to `tela workflow init`, `sync`, and `publish` in agent sessions; without it they prompt in a terminal or stop with a diagnostic when confirmation is needed.
24
26
  - Do not print token material. Use `tela auth token --json` for metadata and `--raw` only when a shell command explicitly needs the token.
25
27
 
26
28
  ## Pantry and Service Commands
@@ -44,6 +46,43 @@ HTTPie-style args for `tela service api`:
44
46
  - `name==value` sends a query parameter.
45
47
  - `Header:value` sends a request header.
46
48
 
49
+ ## Managed Skills
50
+
51
+ ```bash
52
+ tela skills list --json
53
+ tela skills search <query> --json
54
+ tela skills files <slug> --json
55
+ tela skills cat <slug> [path]
56
+ tela skills install <slug...> --json
57
+ tela skills upgrade [slug...] --json
58
+ tela skills uninstall <slug...> --json
59
+ ```
60
+
61
+ Use `search`, `files`, and `cat` to inspect a skill before installing it.
62
+ `install` places the skill under `~/.agents/skills` and links it into the
63
+ configured provider directories; `--no-symlink` skips the links. `--force`
64
+ replaces a directory Tela did not install, so only use it when the error asks
65
+ for it. `--ignore-scripts` skips a skill's install/uninstall scripts; use it
66
+ only when a script failed and the error suggests it. `upgrade` without slugs
67
+ upgrades every installed skill.
68
+
69
+ ## Workflow Code
70
+
71
+ ```bash
72
+ tela workflow docs --json
73
+ tela workflow docs <page>
74
+ tela workflow init --directory <path> --workflow <key> --yes --json
75
+ tela workflow create <target> --project <project-id> --json
76
+ tela workflow compile [target] --json
77
+ tela workflow sync [target] --yes --json
78
+ tela workflow publish [target] --environment <name> --yes --json
79
+ ```
80
+
81
+ `tela workflow docs` lists the bundled documentation pages; read the relevant
82
+ page (for example `api-reference` or `cli`) before writing workflow code.
83
+ Compile locally before `sync`, and `sync` before `publish`. Run
84
+ `tela workflow --help` for every flag.
85
+
47
86
  ## Authentication
48
87
 
49
88
  Use `tela auth login --no-interactive` in an agent session. Open the URL it
@@ -81,6 +81,46 @@ tela agent push --message "feat: update agent behavior" --json
81
81
  Use `--message` for agent sessions. `--claude` asks the local Claude CLI to
82
82
  write a message, and `--manual` relies on fallback behavior.
83
83
 
84
+ ## Merge
85
+
86
+ Preview merging an agent branch into the repository's default branch:
87
+
88
+ ```bash
89
+ tela agent merge --dry-run --json
90
+ tela agent merge feature/my-branch --dry-run --json
91
+ ```
92
+
93
+ Merge after checking the dry run. JSON or non-interactive sessions must pass
94
+ `--yes`:
95
+
96
+ ```bash
97
+ tela agent merge feature/my-branch --yes --json
98
+ ```
99
+
100
+ The branch defaults to the current checkout's branch, and the base defaults to
101
+ the repository's default branch; override it with `--base <branch>`. Passing a
102
+ different branch checks it out, even with `--dry-run`. `--strategy squash`
103
+ (default) or `--strategy merge` selects how the Agent API merges, and the
104
+ remote branch is deleted unless `--keep-branch` is passed.
105
+
106
+ Merge stops before any remote merge call when the worktree is dirty
107
+ (`merge_dirty_worktree`), a merge is already in progress (`merge_in_progress`),
108
+ the branch is not on origin (`merge_branch_not_pushed`), or it has unpushed
109
+ commits (`merge_unpushed_commits`). Follow the error's `fixes`; most of these
110
+ are cleared by `tela agent push`.
111
+
112
+ When the Agent API reports conflicts, the command merges `origin/<base>` into
113
+ the branch locally and returns `status: "conflicts"`. Read `localMergeClean`:
114
+
115
+ - `true`: the local merge applied cleanly. Run `tela agent push`, then rerun
116
+ `tela agent merge <branch>`.
117
+ - `false`: conflict markers are left in the checkout. Resolve them, run
118
+ `tela agent push` to commit and upload the resolution, then rerun
119
+ `tela agent merge <branch>`.
120
+
121
+ Use `tela agent push` rather than raw git to commit the resolution, so the
122
+ branch is uploaded in the same step.
123
+
84
124
  ## Feedback
85
125
 
86
126
  List feedback for the current commit: