@meistrari/tela-cli 0.7.22 → 0.7.24

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
@@ -19,6 +19,14 @@ tela context list --json
19
19
  tela context show <context-id> --json
20
20
  tela context use <context-id>
21
21
  tela env list --json
22
+ tela update --json
23
+ tela api <slug> <METHOD> <PATH> [args...] --env <slug> --body <json> --pretty
24
+ tela docs endpoints <slug> --pretty
25
+ tela docs reference <slug> [METHOD PATH|file-path] --pretty
26
+ tela docs guide <slug> [path]
27
+ tela env list <ingredient-slug> --pretty
28
+ tela env set <ingredient-slug> <environment>
29
+ tela whoami --pretty
22
30
  tela doctor auth --json
23
31
  tela agent sync [repo] --current --all --dry-run --json --verbose
24
32
  tela agent run --input <name=path>... --plain --json --verbose
@@ -31,6 +39,12 @@ may refresh a user-session credential in Keychain before returning metadata or
31
39
  printing a raw token. Creating, removing, editing, and repairing contexts are
32
40
  Dock-first workflows.
33
41
 
42
+ The legacy `telactl` Pantry/service workflows are ported into this binary as
43
+ top-level `update`, `api`, `docs`, `whoami`, and `token` commands. Ingredient
44
+ cache and per-ingredient environment defaults live under `TELA_HOME` next to
45
+ the Dock config, while credential material still comes from the selected Tela
46
+ context through `@meistrari/tela-local-config`.
47
+
34
48
  ## Development
35
49
 
36
50
  From the repository root:
@@ -0,0 +1,38 @@
1
+ import type { CliIo, GlobalOptions } from '../output';
2
+ export type CachedEnvironment = {
3
+ slug: string;
4
+ url: string;
5
+ };
6
+ export type CachedIngredient = {
7
+ slug: string;
8
+ name: string;
9
+ description: string;
10
+ type: 'api' | 'library' | 'skill';
11
+ status: string;
12
+ environments: CachedEnvironment[];
13
+ };
14
+ type ParsedArg = {
15
+ kind: 'query';
16
+ key: string;
17
+ value: string;
18
+ } | {
19
+ kind: 'header';
20
+ key: string;
21
+ value: string;
22
+ } | {
23
+ kind: 'field';
24
+ key: string;
25
+ value: string;
26
+ } | {
27
+ kind: 'rawField';
28
+ key: string;
29
+ value: string;
30
+ };
31
+ export declare function runTelactlCommand(command: string, args: string[], options: GlobalOptions, io: CliIo): Promise<number>;
32
+ export declare function runIngredientEnvCommand(args: string[], options: GlobalOptions, io: CliIo): Promise<number | null>;
33
+ export declare function fetchIngredientCatalog(headers: Record<string, string>, fetchImpl?: typeof fetch): Promise<CachedIngredient[]>;
34
+ export declare function joinUrl(base: string, routePath: string): string;
35
+ export declare function parseHttpieArg(arg: string): ParsedArg | null;
36
+ export declare function extractGitHubFilePath(url: string): string | null;
37
+ export declare function resolveGuidePath(rootFilePath: string, providedPath?: string): string;
38
+ export {};
package/dist/index.js CHANGED
@@ -133,14 +133,14 @@ var require_auth = __commonJS({
133
133
  "use strict";
134
134
  Object.defineProperty(exports2, "__esModule", { value: true });
135
135
  exports2.shouldRefreshUserSession = shouldRefreshUserSession;
136
- exports2.refreshUserSessionCredential = refreshUserSessionCredential2;
136
+ exports2.refreshUserSessionCredential = refreshUserSessionCredential3;
137
137
  exports2.createStoredUserSession = createStoredUserSession;
138
138
  exports2.getJwtExpiresAt = getJwtExpiresAt;
139
139
  var credentials_1 = require_credentials();
140
140
  function shouldRefreshUserSession(credential, now = refreshSkewDate()) {
141
141
  return credential.kind === "user-session" && Boolean(credential.refreshToken) && (0, credentials_1.isExpired)(credential, now);
142
142
  }
143
- async function refreshUserSessionCredential2(environment, credentialRef, credential, credentials, options = {}) {
143
+ async function refreshUserSessionCredential3(environment, credentialRef, credential, credentials, options = {}) {
144
144
  if (!shouldRefreshUserSession(credential, options.now ?? refreshSkewDate())) {
145
145
  return credential;
146
146
  }
@@ -315,7 +315,7 @@ var require_paths = __commonJS({
315
315
  "use strict";
316
316
  Object.defineProperty(exports2, "__esModule", { value: true });
317
317
  exports2.expandHome = expandHome;
318
- exports2.getTelaLocalPaths = getTelaLocalPaths;
318
+ exports2.getTelaLocalPaths = getTelaLocalPaths2;
319
319
  exports2.configBackupPath = configBackupPath;
320
320
  var node_os_1 = require("node:os");
321
321
  var node_path_1 = require("node:path");
@@ -326,7 +326,7 @@ var require_paths = __commonJS({
326
326
  return (0, node_path_1.join)((0, node_os_1.homedir)(), path.slice(2));
327
327
  return path;
328
328
  }
329
- function getTelaLocalPaths(rootDir = process.env.TELA_HOME ?? "~/.tela") {
329
+ function getTelaLocalPaths2(rootDir = process.env.TELA_HOME ?? "~/.tela") {
330
330
  const root = (0, node_path_1.resolve)(expandHome(rootDir));
331
331
  return {
332
332
  rootDir: root,
@@ -346,7 +346,7 @@ var require_config = __commonJS({
346
346
  "../../packages/local-config/dist/config.js"(exports2) {
347
347
  "use strict";
348
348
  Object.defineProperty(exports2, "__esModule", { value: true });
349
- exports2.loadConfig = loadConfig5;
349
+ exports2.loadConfig = loadConfig6;
350
350
  exports2.saveConfig = saveConfig;
351
351
  exports2.updateConfig = updateConfig2;
352
352
  exports2.upsertEnvironment = upsertEnvironment;
@@ -367,7 +367,7 @@ var require_config = __commonJS({
367
367
  "Authorization",
368
368
  "x-data-token"
369
369
  ]);
370
- async function loadConfig5(configFile = (0, paths_1.getTelaLocalPaths)().configFile) {
370
+ async function loadConfig6(configFile = (0, paths_1.getTelaLocalPaths)().configFile) {
371
371
  let raw;
372
372
  try {
373
373
  raw = await (0, promises_1.readFile)(configFile, "utf8");
@@ -388,7 +388,7 @@ var require_config = __commonJS({
388
388
  await (0, promises_1.rename)(tempFile, configFile);
389
389
  }
390
390
  async function updateConfig2(mutator, configFile = (0, paths_1.getTelaLocalPaths)().configFile) {
391
- const current = await loadConfig5(configFile);
391
+ const current = await loadConfig6(configFile);
392
392
  const next = mutator(current) ?? current;
393
393
  await saveConfig(next, configFile);
394
394
  return next;
@@ -558,8 +558,8 @@ var require_context = __commonJS({
558
558
  "../../packages/local-config/dist/context.js"(exports2) {
559
559
  "use strict";
560
560
  Object.defineProperty(exports2, "__esModule", { value: true });
561
- exports2.resolveContext = resolveContext2;
562
- function resolveContext2(config, input = {}) {
561
+ exports2.resolveContext = resolveContext3;
562
+ function resolveContext3(config, input = {}) {
563
563
  const env = input.env ?? process.env;
564
564
  const contextId = input.contextId ?? env.TELA_CONTEXT ?? resolveFromEnvironmentAndWorkspace(config, input.environmentId, input.workspace) ?? config.activeContextId;
565
565
  if (!contextId) {
@@ -1518,7 +1518,7 @@ var require_agent_api = __commonJS({
1518
1518
  var tela_local_config_1 = require_dist();
1519
1519
  var auth_1 = require_auth2();
1520
1520
  var DEFAULT_TIMEOUT_MS = 1e4;
1521
- function joinUrl(baseUrl, path) {
1521
+ function joinUrl2(baseUrl, path) {
1522
1522
  return new URL(path, baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`).toString();
1523
1523
  }
1524
1524
  function parseJsonBody(responseText) {
@@ -1589,7 +1589,7 @@ var require_agent_api = __commonJS({
1589
1589
  exports2.AgentApiError = AgentApiError;
1590
1590
  async function listOrganizations(config, fetchFn = fetch) {
1591
1591
  const headers = await (0, auth_1.buildAgentApiHeaders)(config, { fetchFn });
1592
- const response = await fetchFn(joinUrl(config.agentApiUrl, "/v3/organizations"), {
1592
+ const response = await fetchFn(joinUrl2(config.agentApiUrl, "/v3/organizations"), {
1593
1593
  method: "GET",
1594
1594
  headers,
1595
1595
  signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS)
@@ -1606,7 +1606,7 @@ var require_agent_api = __commonJS({
1606
1606
  }
1607
1607
  async function provisionForgejoUser(input, config, fetchFn = fetch) {
1608
1608
  const headers = await (0, auth_1.buildAgentApiHeaders)(config, { fetchFn });
1609
- const response = await fetchFn(joinUrl(config.agentApiUrl, "/v3/forgejo/users"), {
1609
+ const response = await fetchFn(joinUrl2(config.agentApiUrl, "/v3/forgejo/users"), {
1610
1610
  method: "POST",
1611
1611
  headers: {
1612
1612
  ...headers,
@@ -1630,7 +1630,7 @@ var require_agent_api = __commonJS({
1630
1630
  }
1631
1631
  async function fetchRepositoryLookup(repository, config, fetchFn = fetch) {
1632
1632
  const headers = await (0, auth_1.buildAgentApiHeaders)(config, { fetchFn });
1633
- const response = await fetchFn(joinUrl(config.agentApiUrl, `/v3/agents/repositories/${encodeURIComponent(repository)}`), {
1633
+ const response = await fetchFn(joinUrl2(config.agentApiUrl, `/v3/agents/repositories/${encodeURIComponent(repository)}`), {
1634
1634
  method: "GET",
1635
1635
  headers,
1636
1636
  signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS)
@@ -1652,7 +1652,7 @@ var require_agent_api = __commonJS({
1652
1652
  }
1653
1653
  async function fetchCommitFeedback(organizationName, repository, commitHash, config, fetchFn = fetch) {
1654
1654
  const headers = await (0, auth_1.buildAgentApiHeaders)(config, { fetchFn });
1655
- const response = await fetchFn(joinUrl(config.agentApiUrl, `/v3/agents/${encodeURIComponent(organizationName)}/${encodeURIComponent(repository)}/feedback?commitHash=${encodeURIComponent(commitHash)}`), {
1655
+ const response = await fetchFn(joinUrl2(config.agentApiUrl, `/v3/agents/${encodeURIComponent(organizationName)}/${encodeURIComponent(repository)}/feedback?commitHash=${encodeURIComponent(commitHash)}`), {
1656
1656
  method: "GET",
1657
1657
  headers,
1658
1658
  signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS)
@@ -1674,7 +1674,7 @@ var require_agent_api = __commonJS({
1674
1674
  if (!config.credential) {
1675
1675
  throw new AgentApiError("Tela API agent listing requires a resolved credential", 0, "");
1676
1676
  }
1677
- const url = new URL(joinUrl(config.telaApiUrl, "/agent"));
1677
+ const url = new URL(joinUrl2(config.telaApiUrl, "/agent"));
1678
1678
  url.searchParams.set("includeProjects", "true");
1679
1679
  const response = await fetchFn(url.toString(), {
1680
1680
  method: "GET",
@@ -1696,7 +1696,7 @@ var require_agent_api = __commonJS({
1696
1696
  }
1697
1697
  async function listAvailableRepositories(config, fetchFn = fetch) {
1698
1698
  const headers = await (0, auth_1.buildAgentApiHeaders)(config, { fetchFn });
1699
- const response = await fetchFn(joinUrl(config.agentApiUrl, "/v3/agents/repositories"), {
1699
+ const response = await fetchFn(joinUrl2(config.agentApiUrl, "/v3/agents/repositories"), {
1700
1700
  method: "GET",
1701
1701
  headers,
1702
1702
  signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS)
@@ -1803,13 +1803,13 @@ var require_claude = __commonJS({
1803
1803
  minor: 1,
1804
1804
  patch: 108
1805
1805
  };
1806
- function isRecord(value) {
1806
+ function isRecord2(value) {
1807
1807
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
1808
1808
  }
1809
1809
  function parseClaudeSettings(path, content) {
1810
1810
  try {
1811
1811
  const parsed = JSON.parse(content);
1812
- if (isRecord(parsed)) {
1812
+ if (isRecord2(parsed)) {
1813
1813
  return parsed;
1814
1814
  }
1815
1815
  } catch (error) {
@@ -1831,7 +1831,7 @@ var require_claude = __commonJS({
1831
1831
  }
1832
1832
  function readAllowedPermissionRules(settings, settingsPath) {
1833
1833
  const permissions = settings.permissions;
1834
- if (!isRecord(permissions) || !Array.isArray(permissions.allow) || permissions.allow.length === 0) {
1834
+ if (!isRecord2(permissions) || !Array.isArray(permissions.allow) || permissions.allow.length === 0) {
1835
1835
  throw (0, errors_1.createUserFacingError)({
1836
1836
  code: "claude_project_permissions_missing",
1837
1837
  summary: "Agent repository is missing Claude project tool permissions.",
@@ -3300,7 +3300,7 @@ var require_run = __commonJS({
3300
3300
  function safeNumber(value) {
3301
3301
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
3302
3302
  }
3303
- function isRecord(value) {
3303
+ function isRecord2(value) {
3304
3304
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
3305
3305
  }
3306
3306
  function formatDurationMs(durationMs) {
@@ -3327,11 +3327,11 @@ var require_run = __commonJS({
3327
3327
  return `${minutes}m ${remainder}s`;
3328
3328
  }
3329
3329
  function summarizePermissionDenial(value) {
3330
- if (!isRecord(value)) {
3330
+ if (!isRecord2(value)) {
3331
3331
  return "Permission denied for a tool call.";
3332
3332
  }
3333
3333
  const toolName = safeString(value.tool_name) ?? "tool";
3334
- if (!isRecord(value.tool_input)) {
3334
+ if (!isRecord2(value.tool_input)) {
3335
3335
  return `Permission denied: ${toolName}`;
3336
3336
  }
3337
3337
  const inputSummary = summarizeToolInput(toolName, value.tool_input);
@@ -3361,7 +3361,7 @@ var require_run = __commonJS({
3361
3361
  return value !== void 0;
3362
3362
  }
3363
3363
  function summarizeToolInput(toolName, input) {
3364
- if (!isRecord(input)) {
3364
+ if (!isRecord2(input)) {
3365
3365
  return void 0;
3366
3366
  }
3367
3367
  const pathValue = [
@@ -3443,7 +3443,7 @@ var require_run = __commonJS({
3443
3443
  return "";
3444
3444
  }
3445
3445
  return content.flatMap((block) => {
3446
- if (!isRecord(block) || block.type !== "text") {
3446
+ if (!isRecord2(block) || block.type !== "text") {
3447
3447
  return [];
3448
3448
  }
3449
3449
  return typeof block.text === "string" ? [block.text] : [];
@@ -3458,7 +3458,7 @@ var require_run = __commonJS({
3458
3458
  return [];
3459
3459
  }
3460
3460
  return content.flatMap((block, index) => {
3461
- if (!isRecord(block) || block.type !== "tool_use") {
3461
+ if (!isRecord2(block) || block.type !== "tool_use") {
3462
3462
  return [];
3463
3463
  }
3464
3464
  const name = safeString(block.name);
@@ -3613,7 +3613,7 @@ var require_run = __commonJS({
3613
3613
  }];
3614
3614
  }
3615
3615
  case "files_persisted": {
3616
- const failed = Array.isArray(event.failed) ? event.failed.filter(isRecord) : [];
3616
+ const failed = Array.isArray(event.failed) ? event.failed.filter(isRecord2) : [];
3617
3617
  if (failed.length === 0) {
3618
3618
  const persistedCount = Array.isArray(event.files) ? event.files.length : 0;
3619
3619
  return [{
@@ -3673,7 +3673,7 @@ var require_run = __commonJS({
3673
3673
  return [];
3674
3674
  const events = [];
3675
3675
  for (const block of content) {
3676
- if (!isRecord(block) || block.type !== "tool_result")
3676
+ if (!isRecord2(block) || block.type !== "tool_result")
3677
3677
  continue;
3678
3678
  const toolUseId = safeString(block.tool_use_id);
3679
3679
  if (!toolUseId)
@@ -3684,7 +3684,7 @@ var require_run = __commonJS({
3684
3684
  output = blockContent;
3685
3685
  } else if (Array.isArray(blockContent)) {
3686
3686
  output = blockContent.flatMap((part) => {
3687
- if (!isRecord(part))
3687
+ if (!isRecord2(part))
3688
3688
  return [];
3689
3689
  if (typeof part.text === "string")
3690
3690
  return [part.text];
@@ -3777,7 +3777,7 @@ var require_run = __commonJS({
3777
3777
  return `<step${elapsed}>${escapeReasoningStepContent(content)}</step>`;
3778
3778
  }
3779
3779
  function normalizeToolInput(input) {
3780
- if (isRecord(input)) {
3780
+ if (isRecord2(input)) {
3781
3781
  return input;
3782
3782
  }
3783
3783
  if (input === void 0) {
@@ -3870,7 +3870,7 @@ var require_run = __commonJS({
3870
3870
  }
3871
3871
  }
3872
3872
  registerSubagentBucket(tool) {
3873
- const input = isRecord(tool.input) ? tool.input : {};
3873
+ const input = isRecord2(tool.input) ? tool.input : {};
3874
3874
  const agentType = safeString(input.subagent_type) ?? safeString(input.agent_type) ?? "subagent";
3875
3875
  const description = safeString(input.description) ?? "";
3876
3876
  const prompt = safeString(input.prompt);
@@ -3889,7 +3889,7 @@ var require_run = __commonJS({
3889
3889
  isSubagentTool(name, input) {
3890
3890
  if (name === "Task" || name === "Agent")
3891
3891
  return true;
3892
- if (isRecord(input) && typeof input.subagent_type === "string")
3892
+ if (isRecord2(input) && typeof input.subagent_type === "string")
3893
3893
  return true;
3894
3894
  return false;
3895
3895
  }
@@ -5472,7 +5472,7 @@ var require_setup = __commonJS({
5472
5472
  function toObject(value) {
5473
5473
  return value && typeof value === "object" ? value : null;
5474
5474
  }
5475
- function stringValue(value) {
5475
+ function stringValue2(value) {
5476
5476
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
5477
5477
  }
5478
5478
  function loadSshPublicKey(filePath) {
@@ -5555,9 +5555,9 @@ var require_setup = __commonJS({
5555
5555
  const raw = toObject(credential?.raw);
5556
5556
  const rawUser = toObject(raw?.user);
5557
5557
  const user = {
5558
- id: stringValue(rawUser?.id),
5559
- email: stringValue(rawUser?.email) ?? fallbackEmail,
5560
- name: stringValue(rawUser?.name) ?? stringValue(rawUser?.title) ?? stringValue(rawUser?.email) ?? fallbackEmail
5558
+ id: stringValue2(rawUser?.id),
5559
+ email: stringValue2(rawUser?.email) ?? fallbackEmail,
5560
+ name: stringValue2(rawUser?.name) ?? stringValue2(rawUser?.title) ?? stringValue2(rawUser?.email) ?? fallbackEmail
5561
5561
  };
5562
5562
  return user.id || user.email || user.name ? user : void 0;
5563
5563
  }
@@ -6640,8 +6640,8 @@ var require_dist2 = __commonJS({
6640
6640
  });
6641
6641
 
6642
6642
  // src/program.ts
6643
- var import_node_fs = require("node:fs");
6644
- var import_node_path = require("node:path");
6643
+ var import_node_fs2 = require("node:fs");
6644
+ var import_node_path2 = require("node:path");
6645
6645
  var import_tela_agent = __toESM(require_dist2());
6646
6646
 
6647
6647
  // src/output.ts
@@ -6930,20 +6930,745 @@ function writeDoctorHelp(io) {
6930
6930
  }
6931
6931
 
6932
6932
  // src/commands/env.ts
6933
+ var import_tela_local_config6 = __toESM(require_dist());
6934
+
6935
+ // src/commands/ingredients.ts
6936
+ var import_node_fs = require("node:fs");
6937
+ var import_promises = require("node:fs/promises");
6938
+ var import_node_path = require("node:path");
6933
6939
  var import_tela_local_config5 = __toESM(require_dist());
6940
+ var PANTRY_BASE_URL = "https://pantry-api.tela.tools";
6941
+ var INGREDIENT_PAGE_LIMIT = 100;
6942
+ var HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE"]);
6943
+ var FALLBACK_ENVIRONMENT_ORDER = ["tooling", "production", "staging", "development"];
6944
+ var PANTRY_INGREDIENT = {
6945
+ slug: "pantry",
6946
+ name: "Pantry",
6947
+ description: "Ingredient registry and API catalog",
6948
+ type: "api",
6949
+ status: "active",
6950
+ environments: [{ slug: "production", url: PANTRY_BASE_URL }]
6951
+ };
6952
+ async function runTelactlCommand(command, args, options, io) {
6953
+ try {
6954
+ if (command === "update") return updateIngredients(args, options, io);
6955
+ if (command === "api") return callIngredientApi(args, options, io);
6956
+ if (command === "docs") return runDocsCommand(args, options, io);
6957
+ if (command === "whoami") return whoami(args, options, io);
6958
+ if (command === "token") return tokenAlias(args, options, io);
6959
+ writeError(io, `Unknown telactl command: ${command}`);
6960
+ return 1;
6961
+ } catch (error) {
6962
+ writeError(io, error instanceof Error ? error.message : String(error));
6963
+ return 1;
6964
+ }
6965
+ }
6966
+ async function runIngredientEnvCommand(args, options, io) {
6967
+ const [action, ...rest] = args;
6968
+ if (action === "set") return setIngredientEnvironment(rest, io);
6969
+ if (action === "list" && rest.some((arg) => !arg.startsWith("-"))) return listIngredientEnvironments(rest, options, io);
6970
+ return null;
6971
+ }
6972
+ async function updateIngredients(args, options, io) {
6973
+ const flags = parseCommonFlags(args);
6974
+ if ("error" in flags) return usageError(flags.error, io);
6975
+ if (flags.rest.length > 0) return usageError(`Unknown update argument: ${flags.rest[0]}`, io);
6976
+ const session = await loadSession(flags.contextId);
6977
+ const cache = {
6978
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
6979
+ ingredients: await fetchIngredientCatalog(requestHeaders(session.credential))
6980
+ };
6981
+ await writeCache(cache);
6982
+ if (options.json) {
6983
+ writeJson(io, {
6984
+ cache: {
6985
+ updatedAt: cache.updatedAt,
6986
+ ingredientCount: cache.ingredients.length,
6987
+ contextId: session.resolved.id,
6988
+ refreshed: session.refreshed
6989
+ }
6990
+ });
6991
+ return 0;
6992
+ }
6993
+ writeLine(io, `Cached ${cache.ingredients.length} ingredients`);
6994
+ return 0;
6995
+ }
6996
+ async function callIngredientApi(args, _options, io) {
6997
+ const flags = parseApiFlags(args);
6998
+ if ("error" in flags) return usageError(flags.error, io);
6999
+ const [slug, method, routePath, ...httpieArgs] = flags.rest;
7000
+ if (!slug || !method || !routePath) {
7001
+ return usageError("Usage: tela api <slug> <METHOD> <PATH> [args...] [--env <slug>] [--body <json>] [-p|--pretty]", io);
7002
+ }
7003
+ if (!HTTP_METHODS.has(method.toUpperCase())) return usageError(`Unsupported HTTP method: ${method}`, io);
7004
+ const ingredient = requireIngredient(slug, "api");
7005
+ const environment = resolveIngredientEnvironment(ingredient, flags.env);
7006
+ if (!environment) {
7007
+ return usageError(`Ingredient '${slug}' has no environments configured. Run \`tela update\` after Pantry is updated.`, io);
7008
+ }
7009
+ const headers = new Headers();
7010
+ const queryParams = [];
7011
+ const fields = {};
7012
+ let hasFields = false;
7013
+ for (const arg of httpieArgs) {
7014
+ const parsed = parseHttpieArg(arg);
7015
+ if (!parsed) return usageError(`Invalid argument '${arg}'. Use Key:Value, name=value, name==value, or name:=rawJson.`, io);
7016
+ if (parsed.kind === "query") {
7017
+ queryParams.push([parsed.key, parsed.value]);
7018
+ continue;
7019
+ }
7020
+ if (parsed.kind === "header") {
7021
+ headers.set(parsed.key.trim(), parsed.value.trim());
7022
+ continue;
7023
+ }
7024
+ if (parsed.kind === "field") {
7025
+ fields[parsed.key] = parsed.value;
7026
+ hasFields = true;
7027
+ continue;
7028
+ }
7029
+ try {
7030
+ fields[parsed.key] = JSON.parse(parsed.value);
7031
+ } catch {
7032
+ return usageError(`Invalid JSON for raw field '${parsed.key}': ${parsed.value}`, io);
7033
+ }
7034
+ hasFields = true;
7035
+ }
7036
+ const url = new URL(joinUrl(environment.url, routePath));
7037
+ for (const [key, value] of queryParams) url.searchParams.append(key, value);
7038
+ const session = await loadSession(flags.contextId);
7039
+ for (const [key, value] of Object.entries(requestHeaders(session.credential))) {
7040
+ if (!headers.has(key)) headers.set(key, value);
7041
+ }
7042
+ if (!headers.has("Accept")) headers.set("Accept", "application/json");
7043
+ let requestBody;
7044
+ if (flags.body !== void 0) {
7045
+ requestBody = flags.body;
7046
+ } else if (hasFields) {
7047
+ requestBody = JSON.stringify(fields);
7048
+ }
7049
+ if (requestBody !== void 0 && !headers.has("Content-Type")) headers.set("Content-Type", "application/json");
7050
+ const response = await fetch(url, {
7051
+ method: method.toUpperCase(),
7052
+ headers,
7053
+ body: requestBody
7054
+ });
7055
+ const text = await response.text();
7056
+ assertHttpOk(response, text);
7057
+ if (flags.pretty) {
7058
+ const parsed = tryParseJson(text);
7059
+ if (parsed !== null) {
7060
+ writeJson(io, parsed);
7061
+ return 0;
7062
+ }
7063
+ }
7064
+ writeLine(io, text);
7065
+ return 0;
7066
+ }
7067
+ async function runDocsCommand(args, options, io) {
7068
+ const [action, ...rest] = args;
7069
+ if (!action || action === "help" || action === "--help" || action === "-h") {
7070
+ writeDocsHelp(io);
7071
+ return 0;
7072
+ }
7073
+ if (action === "endpoints") return docsEndpoints(rest, options, io);
7074
+ if (action === "reference") return docsReference(rest, options, io);
7075
+ if (action === "guide") return docsGuide(rest, options, io);
7076
+ return usageError(`Unknown docs command: ${action}`, io);
7077
+ }
7078
+ async function docsEndpoints(args, options, io) {
7079
+ const flags = parseCommonFlags(args);
7080
+ if ("error" in flags) return usageError(flags.error, io);
7081
+ const [slug] = flags.rest;
7082
+ if (!slug) return usageError("Usage: tela docs endpoints <slug> [-p|--pretty]", io);
7083
+ requireIngredient(slug, "api");
7084
+ const spec = await fetchOpenApiSpec(slug, flags.contextId);
7085
+ const paths = isRecord(spec.paths) ? spec.paths : {};
7086
+ if (flags.pretty && !options.json) {
7087
+ writeLine(io, formatEndpointsTable(paths));
7088
+ return 0;
7089
+ }
7090
+ writeJson(io, paths);
7091
+ return 0;
7092
+ }
7093
+ async function docsReference(args, options, io) {
7094
+ const flags = parseCommonFlags(args);
7095
+ if ("error" in flags) return usageError(flags.error, io);
7096
+ const [slug, first, second] = flags.rest;
7097
+ if (!slug) return usageError("Usage: tela docs reference <slug> [METHOD PATH|file-path] [-p|--pretty]", io);
7098
+ const ingredient = requireIngredient(slug);
7099
+ if (ingredient.type === "api") {
7100
+ if (first && !second && HTTP_METHODS.has(first.toUpperCase())) {
7101
+ return usageError("Both METHOD and PATH are required to show a specific endpoint", io);
7102
+ }
7103
+ const spec = await fetchOpenApiSpec(slug, flags.contextId);
7104
+ if (first && second) {
7105
+ if (!HTTP_METHODS.has(first.toUpperCase())) return usageError(`Unsupported HTTP method: ${first}`, io);
7106
+ const pathEntry = isRecord(spec.paths) && isRecord(spec.paths[second]) ? spec.paths[second] : null;
7107
+ if (!pathEntry) return usageError(`Path not found: ${second}`, io);
7108
+ const op = pathEntry[first.toLowerCase()];
7109
+ if (!op) return usageError(`Method ${first.toUpperCase()} not found for ${second}`, io);
7110
+ if (flags.pretty && !options.json) {
7111
+ writeLine(io, formatEndpoint(spec, op, first, second));
7112
+ return 0;
7113
+ }
7114
+ writeJson(io, op);
7115
+ return 0;
7116
+ }
7117
+ if (flags.pretty && !options.json) {
7118
+ writeJson(io, spec);
7119
+ return 0;
7120
+ }
7121
+ writeLine(io, JSON.stringify(spec));
7122
+ return 0;
7123
+ }
7124
+ if (ingredient.type !== "library") return usageError(`Ingredient '${slug}' is not an API or library ingredient.`, io);
7125
+ const ingredientBody = await fetchIngredient(slug, flags.contextId);
7126
+ const apiRef = isRecord(ingredientBody.docs) ? stringValue(ingredientBody.docs.api) : "";
7127
+ if (!apiRef) return usageError(`Library ${slug} has no API reference`, io);
7128
+ const filePath = extractGitHubFilePath(apiRef);
7129
+ if (!filePath) return usageError(`Could not extract file path from API reference URL: ${apiRef}`, io);
7130
+ const slashIndex = filePath.lastIndexOf("/");
7131
+ const dir = slashIndex === -1 ? "" : filePath.slice(0, slashIndex + 1);
7132
+ const relativePath = first ? first.replace(/^\/+/, "") : "globals.md";
7133
+ const guide = await fetchGuide(slug, `${dir}${relativePath}`, flags.contextId);
7134
+ writeLine(io, stringValue(guide.content));
7135
+ return 0;
7136
+ }
7137
+ async function docsGuide(args, _options, io) {
7138
+ const flags = parseCommonFlags(args);
7139
+ if ("error" in flags) return usageError(flags.error, io);
7140
+ const [slug, providedPath] = flags.rest;
7141
+ if (!slug) return usageError("Usage: tela docs guide <slug> [path] [-p|--pretty]", io);
7142
+ const ingredient = requireIngredient(slug);
7143
+ if (ingredient.type !== "api" && ingredient.type !== "library") {
7144
+ return usageError(`Ingredient '${slug}' is not an API or library ingredient.`, io);
7145
+ }
7146
+ const ingredientBody = await fetchIngredient(slug, flags.contextId);
7147
+ const guidesRef = isRecord(ingredientBody.docs) ? stringValue(ingredientBody.docs.guides) : "";
7148
+ if (!guidesRef) return usageError(`Ingredient ${slug} has no guide`, io);
7149
+ const filePath = extractGitHubFilePath(guidesRef);
7150
+ if (!filePath) return usageError(`Could not extract file path from guide URL: ${guidesRef}`, io);
7151
+ const guide = await fetchGuide(slug, resolveGuidePath(filePath, providedPath), flags.contextId);
7152
+ writeLine(io, stringValue(guide.content));
7153
+ return 0;
7154
+ }
7155
+ async function listIngredientEnvironments(args, options, io) {
7156
+ const flags = parseCommonFlags(args);
7157
+ if ("error" in flags) return usageError(flags.error, io);
7158
+ const [slug] = flags.rest;
7159
+ if (!slug) return usageError("Usage: tela env list <ingredient-slug> [-p|--pretty|--json]", io);
7160
+ const ingredient = requireIngredient(slug);
7161
+ const environments = ingredient.environments ?? [];
7162
+ if (options.json || !flags.pretty) {
7163
+ writeJson(io, { environments });
7164
+ return 0;
7165
+ }
7166
+ if (environments.length === 0) {
7167
+ writeLine(io, "(no environments configured)");
7168
+ return 0;
7169
+ }
7170
+ const slugWidth = Math.max(...environments.map((environment) => environment.slug.length));
7171
+ for (const environment of environments) {
7172
+ writeLine(io, `${environment.slug.padEnd(slugWidth)} ${environment.url}`);
7173
+ }
7174
+ return 0;
7175
+ }
7176
+ async function setIngredientEnvironment(args, io) {
7177
+ const [slug, environment] = args;
7178
+ if (!slug || !environment) return usageError("Usage: tela env set <ingredient-slug> <environment>", io);
7179
+ const ingredient = requireIngredient(slug);
7180
+ if (!ingredient.environments.some((candidate) => candidate.slug === environment)) {
7181
+ const available = ingredient.environments.map((candidate) => candidate.slug).join(", ") || "(none)";
7182
+ return usageError(`Environment '${environment}' not found for '${slug}'. Available: ${available}`, io);
7183
+ }
7184
+ const defaults = await readEnvironmentDefaults();
7185
+ defaults[slug] = environment;
7186
+ await writeEnvironmentDefaults(defaults);
7187
+ writeLine(io, `Set default environment for '${slug}' to '${environment}'`);
7188
+ return 0;
7189
+ }
7190
+ async function whoami(args, _options, io) {
7191
+ const flags = parseCommonFlags(args);
7192
+ if ("error" in flags) return usageError(flags.error, io);
7193
+ if (flags.rest.length > 0) return usageError(`Unknown whoami argument: ${flags.rest[0]}`, io);
7194
+ const session = await loadSession(flags.contextId);
7195
+ if (!session.credential.accessToken) {
7196
+ return usageError(`Context ${session.resolved.id} does not have an access token credential.`, io);
7197
+ }
7198
+ const response = await fetch(new URL("/api/auth/applications/whoami", session.resolved.environment.authApiUrl), {
7199
+ headers: {
7200
+ Accept: "application/json",
7201
+ "x-tela-access-token": session.credential.accessToken
7202
+ }
7203
+ });
7204
+ const text = await response.text();
7205
+ assertHttpOk(response, text);
7206
+ if (flags.pretty) {
7207
+ const parsed = tryParseJson(text);
7208
+ if (parsed !== null) {
7209
+ writeJson(io, parsed);
7210
+ return 0;
7211
+ }
7212
+ }
7213
+ writeLine(io, text);
7214
+ return 0;
7215
+ }
7216
+ async function tokenAlias(args, _options, io) {
7217
+ const flags = parseCommonFlags(args);
7218
+ if ("error" in flags) return usageError(flags.error, io);
7219
+ if (flags.rest.length > 0) return usageError(`Unknown token argument: ${flags.rest[0]}`, io);
7220
+ const session = await loadSession(flags.contextId);
7221
+ if (!session.credential.accessToken) return usageError(`Context ${session.resolved.id} does not have an access token credential.`, io);
7222
+ writeLine(io, session.credential.accessToken);
7223
+ return 0;
7224
+ }
7225
+ async function fetchOpenApiSpec(slug, contextId) {
7226
+ const session = await loadSession(contextId);
7227
+ const response = await fetch(getPantryOpenapiUrl(slug), {
7228
+ headers: requestHeaders(session.credential)
7229
+ });
7230
+ const text = await response.text();
7231
+ assertHttpOk(response, text, "Failed to fetch OpenAPI spec");
7232
+ const parsed = JSON.parse(text);
7233
+ if (!isRecord(parsed)) throw new Error("OpenAPI response was not a JSON object");
7234
+ return parsed;
7235
+ }
7236
+ async function fetchIngredient(slug, contextId) {
7237
+ const session = await loadSession(contextId);
7238
+ const response = await fetch(getPantryIngredientUrl(slug), {
7239
+ headers: requestHeaders(session.credential)
7240
+ });
7241
+ const text = await response.text();
7242
+ assertHttpOk(response, text, "Failed to fetch ingredient");
7243
+ const parsed = JSON.parse(text);
7244
+ if (!isRecord(parsed)) throw new Error("Ingredient response was not a JSON object");
7245
+ return parsed;
7246
+ }
7247
+ async function fetchGuide(slug, filePath, contextId) {
7248
+ const session = await loadSession(contextId);
7249
+ const response = await fetch(getPantryGuideUrl(slug, filePath), {
7250
+ headers: requestHeaders(session.credential)
7251
+ });
7252
+ const text = await response.text();
7253
+ assertHttpOk(response, text, "Failed to fetch guide");
7254
+ const parsed = JSON.parse(text);
7255
+ if (!isRecord(parsed)) throw new Error("Guide response was not a JSON object");
7256
+ return parsed;
7257
+ }
7258
+ async function loadSession(contextId) {
7259
+ const config = await (0, import_tela_local_config5.loadConfig)();
7260
+ const resolved = (0, import_tela_local_config5.resolveContext)(config, { contextId });
7261
+ const credentials = createCliCredentialAdapter();
7262
+ const credentialRef = resolved.context.credentialRef;
7263
+ const originalCredential = await credentials.get(credentialRef);
7264
+ if (!originalCredential) throw new Error(`Missing credential for Tela context ${resolved.id}`);
7265
+ const credential = originalCredential.kind === "user-session" ? await (0, import_tela_local_config5.refreshUserSessionCredential)(resolved.environment, credentialRef, originalCredential, credentials) : originalCredential;
7266
+ return {
7267
+ resolved,
7268
+ credentialRef,
7269
+ credential,
7270
+ refreshed: credential !== originalCredential
7271
+ };
7272
+ }
7273
+ function requestHeaders(credential) {
7274
+ if (credential.kind === "data-token" && credential.dataToken) {
7275
+ return {
7276
+ Accept: "application/json",
7277
+ "x-data-token": credential.dataToken
7278
+ };
7279
+ }
7280
+ if (credential.accessToken) {
7281
+ return {
7282
+ Accept: "application/json",
7283
+ Authorization: `Bearer ${credential.accessToken}`
7284
+ };
7285
+ }
7286
+ throw new Error(`Credential ${credential.kind} does not contain usable token material`);
7287
+ }
7288
+ function requireIngredient(slug, type) {
7289
+ const ingredient = readModules().find((candidate) => candidate.slug === slug);
7290
+ if (!ingredient) throw new Error(`Ingredient '${slug}' not found. Run \`tela update\` first.`);
7291
+ if (type && ingredient.type !== type) throw new Error(`Ingredient '${slug}' is not a ${type} ingredient.`);
7292
+ return ingredient;
7293
+ }
7294
+ async function fetchIngredientCatalog(headers, fetchImpl = fetch) {
7295
+ const ingredients = [];
7296
+ let total = null;
7297
+ for (let offset = 0; ; offset += INGREDIENT_PAGE_LIMIT) {
7298
+ const url = new URL("/ingredients", PANTRY_BASE_URL);
7299
+ url.searchParams.set("limit", String(INGREDIENT_PAGE_LIMIT));
7300
+ url.searchParams.set("offset", String(offset));
7301
+ const response = await fetchImpl(url.toString(), { headers });
7302
+ const text = await response.text();
7303
+ assertHttpOk(response, text, "Failed to fetch ingredients");
7304
+ const data = JSON.parse(text);
7305
+ const rawPageIngredients = data.ingredients ?? [];
7306
+ const pageIngredients = rawPageIngredients.map(normalizeCachedIngredient).filter((ingredient) => ingredient.slug);
7307
+ ingredients.push(...pageIngredients);
7308
+ total = typeof data.total === "number" ? data.total : total;
7309
+ if (total !== null && ingredients.length >= total) break;
7310
+ if (rawPageIngredients.length < INGREDIENT_PAGE_LIMIT) break;
7311
+ }
7312
+ return ingredients;
7313
+ }
7314
+ function normalizeCachedIngredient(ingredient) {
7315
+ return {
7316
+ slug: stringValue(ingredient.slug),
7317
+ name: stringValue(ingredient.name),
7318
+ description: stringValue(ingredient.description),
7319
+ type: normalizeIngredientType(ingredient.type),
7320
+ status: stringValue(ingredient.status),
7321
+ environments: (ingredient.environments ?? []).map((environment) => ({
7322
+ slug: environment.slug,
7323
+ url: environment.url
7324
+ })).filter((environment) => environment.slug && environment.url)
7325
+ };
7326
+ }
7327
+ function readModules() {
7328
+ const cache = readCache();
7329
+ const ingredients = cache?.ingredients ?? [];
7330
+ return ingredients.some((ingredient) => ingredient.slug === PANTRY_INGREDIENT.slug) ? ingredients : [...ingredients, PANTRY_INGREDIENT];
7331
+ }
7332
+ function readCache() {
7333
+ try {
7334
+ const text = (0, import_node_fs.readFileSync)(cachePath(), "utf8");
7335
+ const parsed = JSON.parse(text);
7336
+ if (!Array.isArray(parsed.ingredients)) return null;
7337
+ return parsed;
7338
+ } catch {
7339
+ return null;
7340
+ }
7341
+ }
7342
+ async function writeCache(cache) {
7343
+ const filePath = cachePath();
7344
+ await (0, import_promises.mkdir)((0, import_node_path.dirname)(filePath), { recursive: true });
7345
+ await (0, import_promises.writeFile)(filePath, `${JSON.stringify(cache, null, 2)}
7346
+ `, "utf8");
7347
+ }
7348
+ function cachePath() {
7349
+ return (0, import_node_path.join)((0, import_tela_local_config5.getTelaLocalPaths)().rootDir, "ingredients.json");
7350
+ }
7351
+ async function readEnvironmentDefaults() {
7352
+ try {
7353
+ return JSON.parse(await (0, import_promises.readFile)(environmentDefaultsPath(), "utf8"));
7354
+ } catch {
7355
+ return {};
7356
+ }
7357
+ }
7358
+ async function writeEnvironmentDefaults(defaults) {
7359
+ const filePath = environmentDefaultsPath();
7360
+ await (0, import_promises.mkdir)((0, import_node_path.dirname)(filePath), { recursive: true });
7361
+ await (0, import_promises.writeFile)(filePath, `${JSON.stringify(defaults, null, 2)}
7362
+ `, "utf8");
7363
+ }
7364
+ function environmentDefaultsPath() {
7365
+ return (0, import_node_path.join)((0, import_tela_local_config5.getTelaLocalPaths)().rootDir, "ingredient-environments.json");
7366
+ }
7367
+ function resolveIngredientEnvironment(ingredient, override) {
7368
+ const environments = ingredient.environments ?? [];
7369
+ if (override) {
7370
+ const found = environments.find((environment) => environment.slug === override);
7371
+ if (!found) {
7372
+ const available2 = environments.map((environment) => environment.slug).join(", ") || "(none)";
7373
+ throw new Error(`Environment '${override}' not found for ingredient '${ingredient.slug}'. Available: ${available2}`);
7374
+ }
7375
+ return found;
7376
+ }
7377
+ if (environments.length === 0) return null;
7378
+ if (environments.length === 1) return environments[0] ?? null;
7379
+ const defaults = readEnvironmentDefaultsSync();
7380
+ const saved = defaults[ingredient.slug];
7381
+ if (saved) {
7382
+ const found = environments.find((environment) => environment.slug === saved);
7383
+ if (found) return found;
7384
+ }
7385
+ for (const preferred of FALLBACK_ENVIRONMENT_ORDER) {
7386
+ const found = environments.find((environment) => environment.slug === preferred);
7387
+ if (found) return found;
7388
+ }
7389
+ const available = environments.map((environment) => environment.slug).join(", ");
7390
+ throw new Error(
7391
+ `Multiple environments configured for '${ingredient.slug}' and no default matches.
7392
+ Available: ${available}
7393
+ Use --env <slug> to pick one, or run \`tela env set ${ingredient.slug} <environment>\` to save a default.`
7394
+ );
7395
+ }
7396
+ function readEnvironmentDefaultsSync() {
7397
+ try {
7398
+ return JSON.parse((0, import_node_fs.readFileSync)(environmentDefaultsPath(), "utf8"));
7399
+ } catch {
7400
+ return {};
7401
+ }
7402
+ }
7403
+ function parseCommonFlags(args) {
7404
+ const flags = {
7405
+ pretty: false,
7406
+ rest: []
7407
+ };
7408
+ for (let index = 0; index < args.length; index += 1) {
7409
+ const arg = args[index];
7410
+ if (arg === "--context") {
7411
+ const value = args[index + 1];
7412
+ if (!value) return { error: "Expected value after --context" };
7413
+ flags.contextId = value;
7414
+ index += 1;
7415
+ continue;
7416
+ }
7417
+ if (arg === "--pretty" || arg === "-p") {
7418
+ flags.pretty = true;
7419
+ continue;
7420
+ }
7421
+ flags.rest.push(arg);
7422
+ }
7423
+ return flags;
7424
+ }
7425
+ function parseApiFlags(args) {
7426
+ const flags = {
7427
+ pretty: false,
7428
+ rest: []
7429
+ };
7430
+ for (let index = 0; index < args.length; index += 1) {
7431
+ const arg = args[index];
7432
+ if (arg === "--context") {
7433
+ const value = args[index + 1];
7434
+ if (!value) return { error: "Expected value after --context" };
7435
+ flags.contextId = value;
7436
+ index += 1;
7437
+ continue;
7438
+ }
7439
+ if (arg === "--env") {
7440
+ const value = args[index + 1];
7441
+ if (!value) return { error: "Expected value after --env" };
7442
+ flags.env = value;
7443
+ index += 1;
7444
+ continue;
7445
+ }
7446
+ if (arg === "--body") {
7447
+ const value = args[index + 1];
7448
+ if (value === void 0) return { error: "Expected value after --body" };
7449
+ flags.body = value;
7450
+ index += 1;
7451
+ continue;
7452
+ }
7453
+ if (arg === "--pretty" || arg === "-p") {
7454
+ flags.pretty = true;
7455
+ continue;
7456
+ }
7457
+ flags.rest.push(arg);
7458
+ }
7459
+ return flags;
7460
+ }
7461
+ function joinUrl(base, routePath) {
7462
+ const trimmedBase = base.replace(/\/$/, "");
7463
+ const prefixedPath = routePath.startsWith("/") ? routePath : `/${routePath}`;
7464
+ return `${trimmedBase}${prefixedPath}`;
7465
+ }
7466
+ function parseHttpieArg(arg) {
7467
+ for (let index = 0; index < arg.length; index += 1) {
7468
+ const two = arg.slice(index, index + 2);
7469
+ if (two === "==") return { kind: "query", key: arg.slice(0, index), value: arg.slice(index + 2) };
7470
+ if (two === ":=") return { kind: "rawField", key: arg.slice(0, index), value: arg.slice(index + 2) };
7471
+ if (arg[index] === ":") return { kind: "header", key: arg.slice(0, index), value: arg.slice(index + 1) };
7472
+ if (arg[index] === "=") return { kind: "field", key: arg.slice(0, index), value: arg.slice(index + 1) };
7473
+ }
7474
+ return null;
7475
+ }
7476
+ function getPantryOpenapiUrl(slug) {
7477
+ return slug === "pantry" ? `${PANTRY_BASE_URL}/openapi` : `${PANTRY_BASE_URL}/ingredients/${slug}/openapi`;
7478
+ }
7479
+ function getPantryIngredientUrl(slug) {
7480
+ return `${PANTRY_BASE_URL}/ingredients/${slug}`;
7481
+ }
7482
+ function getPantryGuideUrl(slug, filePath) {
7483
+ const base = `${PANTRY_BASE_URL}/ingredients/${slug}/guide`;
7484
+ if (!filePath) return base;
7485
+ const params = new URLSearchParams({ path: filePath });
7486
+ return `${base}?${params.toString()}`;
7487
+ }
7488
+ function formatEndpointsTable(paths) {
7489
+ const lines = [];
7490
+ for (const [routePath, methods] of Object.entries(paths)) {
7491
+ for (const [method, op] of Object.entries(methods)) {
7492
+ if (method.startsWith("x-")) continue;
7493
+ const summary = op.summary ?? op.operationId ?? "";
7494
+ lines.push(`${method.toUpperCase().padEnd(7)} ${routePath} ${summary}`);
7495
+ }
7496
+ }
7497
+ return lines.join("\n");
7498
+ }
7499
+ function formatEndpoint(spec, op, method, routePath) {
7500
+ if (!isRecord(op)) return "";
7501
+ const lines = [`${method.toUpperCase()} ${routePath}`];
7502
+ if (typeof op.summary === "string") lines.push(`
7503
+ ${op.summary}`);
7504
+ if (typeof op.description === "string" && op.description !== op.summary) lines.push(op.description);
7505
+ const parameters = Array.isArray(op.parameters) ? op.parameters : [];
7506
+ if (parameters.length > 0) {
7507
+ lines.push("\n## Parameters\n");
7508
+ for (const parameter of parameters) {
7509
+ const resolved = isRecord(parameter) && typeof parameter.$ref === "string" ? resolveRef(spec, parameter.$ref) : parameter;
7510
+ if (!isRecord(resolved)) continue;
7511
+ const required = resolved.required ? " (required)" : "";
7512
+ const description = typeof resolved.description === "string" ? ` - ${resolved.description}` : "";
7513
+ const schema = isRecord(resolved.schema) ? resolved.schema : {};
7514
+ const type = typeof schema.type === "string" ? schema.type : "";
7515
+ lines.push(` ${stringValue(resolved.in)} ${stringValue(resolved.name)}: ${type}${required}${description}`);
7516
+ }
7517
+ }
7518
+ if (isRecord(op.requestBody)) {
7519
+ lines.push("\n## Request Body\n");
7520
+ const content = isRecord(op.requestBody.content) ? op.requestBody.content : {};
7521
+ for (const [contentType, media] of Object.entries(content)) {
7522
+ if (!isRecord(media)) continue;
7523
+ lines.push(` Content-Type: ${contentType}`);
7524
+ const schema = resolveSchema(spec, media.schema);
7525
+ if (schema) lines.push(formatSchema(schema, 1));
7526
+ }
7527
+ }
7528
+ if (isRecord(op.responses)) {
7529
+ lines.push("\n## Responses\n");
7530
+ for (const [status, response] of Object.entries(op.responses)) {
7531
+ const resolved = isRecord(response) && typeof response.$ref === "string" ? resolveRef(spec, response.$ref) : response;
7532
+ if (!isRecord(resolved)) continue;
7533
+ lines.push(` ${status}: ${stringValue(resolved.description)}`);
7534
+ const content = isRecord(resolved.content) ? resolved.content : {};
7535
+ for (const media of Object.values(content)) {
7536
+ if (!isRecord(media)) continue;
7537
+ const schema = resolveSchema(spec, media.schema);
7538
+ if (schema) lines.push(formatSchema(schema, 2));
7539
+ }
7540
+ }
7541
+ }
7542
+ return lines.join("\n");
7543
+ }
7544
+ function resolveRef(spec, ref) {
7545
+ const parts = ref.replace("#/", "").split("/");
7546
+ let current = spec;
7547
+ for (const part of parts) {
7548
+ current = isRecord(current) ? current[part] : void 0;
7549
+ }
7550
+ return current;
7551
+ }
7552
+ function resolveSchema(spec, schema) {
7553
+ if (!isRecord(schema)) return null;
7554
+ if (typeof schema.$ref === "string") return resolveSchema(spec, resolveRef(spec, schema.$ref));
7555
+ if (schema.items) return { ...schema, items: resolveSchema(spec, schema.items) };
7556
+ if (isRecord(schema.properties)) {
7557
+ const resolved = {};
7558
+ for (const [key, value] of Object.entries(schema.properties)) {
7559
+ resolved[key] = resolveSchema(spec, value) ?? value;
7560
+ }
7561
+ return { ...schema, properties: resolved };
7562
+ }
7563
+ return schema;
7564
+ }
7565
+ function formatSchema(schema, indent = 0) {
7566
+ if (!schema) return "";
7567
+ const pad = " ".repeat(indent);
7568
+ if (schema.type === "object" && isRecord(schema.properties)) {
7569
+ const requiredFields = Array.isArray(schema.required) ? schema.required.filter((item) => typeof item === "string") : [];
7570
+ return Object.entries(schema.properties).map(([key, prop]) => {
7571
+ if (!isRecord(prop)) return `${pad}${key}: unknown`;
7572
+ const required = requiredFields.includes(key) ? "" : "?";
7573
+ const type = typeof prop.type === "string" ? prop.type : "object";
7574
+ const description = typeof prop.description === "string" ? ` - ${prop.description}` : "";
7575
+ if (prop.type === "object" && isRecord(prop.properties)) {
7576
+ return `${pad}${key}${required}: {${description}
7577
+ ${formatSchema(prop, indent + 1)}
7578
+ ${pad}}`;
7579
+ }
7580
+ if (prop.type === "array" && isRecord(prop.items)) {
7581
+ const itemType = typeof prop.items.type === "string" ? prop.items.type : "object";
7582
+ return `${pad}${key}${required}: ${itemType}[]${description}`;
7583
+ }
7584
+ return `${pad}${key}${required}: ${type}${description}`;
7585
+ }).join("\n");
7586
+ }
7587
+ return `${pad}${typeof schema.type === "string" ? schema.type : "unknown"}`;
7588
+ }
7589
+ function extractGitHubFilePath(url) {
7590
+ let parsed;
7591
+ try {
7592
+ parsed = new URL(url);
7593
+ } catch {
7594
+ return null;
7595
+ }
7596
+ const parts = parsed.pathname.replace(/^\/+|\/+$/g, "").split("/").filter(Boolean).map((part) => decodeURIComponent(part));
7597
+ if (parsed.hostname === "github.com") {
7598
+ const [owner, repo, scope, ref, ...filePathParts] = parts;
7599
+ if (!owner || !repo || scope !== "blob" || !ref || filePathParts.length === 0) return null;
7600
+ return filePathParts.join("/");
7601
+ }
7602
+ if (parsed.hostname === "raw.githubusercontent.com") {
7603
+ const [owner, repo, firstRefPart, ...rest] = parts;
7604
+ if (!owner || !repo || !firstRefPart) return null;
7605
+ if (firstRefPart === "refs") {
7606
+ const [, , ...filePathParts] = rest;
7607
+ return filePathParts.length > 0 ? filePathParts.join("/") : null;
7608
+ }
7609
+ return rest.length > 0 ? rest.join("/") : null;
7610
+ }
7611
+ return null;
7612
+ }
7613
+ function resolveGuidePath(rootFilePath, providedPath) {
7614
+ const slashIndex = rootFilePath.lastIndexOf("/");
7615
+ const dir = slashIndex === -1 ? "" : rootFilePath.slice(0, slashIndex + 1);
7616
+ const basename = slashIndex === -1 ? rootFilePath : rootFilePath.slice(slashIndex + 1);
7617
+ const relativePath = providedPath ? providedPath.replace(/^\/+/, "") : basename;
7618
+ return `${dir}${relativePath}`;
7619
+ }
7620
+ function assertHttpOk(response, bodyText, context) {
7621
+ if (response.ok) return;
7622
+ const prefix = context ? `${context}: ` : "";
7623
+ throw new Error(`${prefix}${response.status} ${response.statusText}${bodyText ? `
7624
+ ${bodyText}` : ""}`);
7625
+ }
7626
+ function usageError(message, io) {
7627
+ writeError(io, message);
7628
+ return 1;
7629
+ }
7630
+ function writeDocsHelp(io) {
7631
+ writeLine(io, "Usage: tela docs <command> [args]");
7632
+ writeLine(io);
7633
+ writeLine(io, "Commands:");
7634
+ writeLine(io, " docs endpoints <slug> [-p|--pretty] [--json]");
7635
+ writeLine(io, " docs reference <slug> [METHOD PATH|file-path] [-p|--pretty] [--json]");
7636
+ writeLine(io, " docs guide <slug> [path] [-p|--pretty]");
7637
+ }
7638
+ function tryParseJson(text) {
7639
+ try {
7640
+ return JSON.parse(text);
7641
+ } catch {
7642
+ return null;
7643
+ }
7644
+ }
7645
+ function isRecord(value) {
7646
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
7647
+ }
7648
+ function stringValue(value) {
7649
+ return typeof value === "string" ? value : "";
7650
+ }
7651
+ function normalizeIngredientType(value) {
7652
+ if (value === "library" || value === "skill") return value;
7653
+ return "api";
7654
+ }
7655
+
7656
+ // src/commands/env.ts
6934
7657
  async function runEnvCommand(args, options, io) {
6935
7658
  const [action] = args;
6936
7659
  if (!action || action === "help" || action === "--help" || action === "-h") {
6937
7660
  writeEnvHelp(io);
6938
7661
  return 0;
6939
7662
  }
7663
+ const ingredientResult = await runIngredientEnvCommand(args, options, io);
7664
+ if (ingredientResult !== null) return ingredientResult;
6940
7665
  if (action === "list") return listEnvironments(options, io);
6941
7666
  writeError(io, `Unknown env command: ${action}`);
6942
7667
  return 1;
6943
7668
  }
6944
7669
  async function listEnvironments(options, io) {
6945
- const config = await (0, import_tela_local_config5.loadConfig)();
6946
- const publicConfig = await (0, import_tela_local_config5.toPublicConfig)(config, createCliCredentialAdapter());
7670
+ const config = await (0, import_tela_local_config6.loadConfig)();
7671
+ const publicConfig = await (0, import_tela_local_config6.toPublicConfig)(config, createCliCredentialAdapter());
6947
7672
  const environments = Object.values(publicConfig.environments);
6948
7673
  if (options.json) {
6949
7674
  writeJson(io, { environments });
@@ -6957,6 +7682,8 @@ async function listEnvironments(options, io) {
6957
7682
  }
6958
7683
  function writeEnvHelp(io) {
6959
7684
  writeLine(io, "Usage: tela env list [--json]");
7685
+ writeLine(io, "Usage: tela env list <ingredient-slug> [-p|--pretty|--json]");
7686
+ writeLine(io, "Usage: tela env set <ingredient-slug> <environment>");
6960
7687
  }
6961
7688
 
6962
7689
  // src/program.ts
@@ -6976,6 +7703,9 @@ async function runCli(argv, io) {
6976
7703
  if (command === "env") return runEnvCommand(rest, options, io);
6977
7704
  if (command === "doctor") return runDoctorCommand(rest, options, io);
6978
7705
  if (command === "agent") return (0, import_tela_agent.runAgentCli)(rest, io, options);
7706
+ if (command === "update" || command === "api" || command === "docs" || command === "whoami" || command === "token") {
7707
+ return runTelactlCommand(command, rest, options, io);
7708
+ }
6979
7709
  writeError(io, `Unknown command: ${command}`);
6980
7710
  return 1;
6981
7711
  }
@@ -7005,7 +7735,16 @@ function writeHelp(io) {
7005
7735
  writeLine(io, " context show <context-id> [--json]");
7006
7736
  writeLine(io, " context use <context-id> [--json]");
7007
7737
  writeLine(io, " env list [--json]");
7738
+ writeLine(io, " env list <ingredient-slug> [-p|--pretty|--json]");
7739
+ writeLine(io, " env set <ingredient-slug> <environment>");
7008
7740
  writeLine(io, " doctor auth [--json]");
7741
+ writeLine(io, " update [--context <context-id>] [--json]");
7742
+ writeLine(io, " api <slug> <METHOD> <PATH> [args...] [--env <slug>] [--body <json>] [-p|--pretty]");
7743
+ writeLine(io, " docs endpoints <slug> [-p|--pretty] [--json]");
7744
+ writeLine(io, " docs reference <slug> [METHOD PATH|file-path] [-p|--pretty] [--json]");
7745
+ writeLine(io, " docs guide <slug> [path] [-p|--pretty]");
7746
+ writeLine(io, " whoami [--context <context-id>] [-p|--pretty]");
7747
+ writeLine(io, " token [--context <context-id>]");
7009
7748
  writeLine(io, " version");
7010
7749
  writeLine(io, " agent sync [repo] [--current] [--all] [--dry-run] [--json] [--verbose]");
7011
7750
  writeLine(io, " agent run [--input <name=path>...] [--mode one-shot|chat] [--input-conflict fail|overwrite|rename] [--plain] [--json] [--verbose]");
@@ -7014,7 +7753,7 @@ function writeHelp(io) {
7014
7753
  writeLine(io, " agent setup [--list-options] [--context <context-id>] [--organization <id-or-name>] [--clone-root <path>] [--ssh-public-key <path>] [--ssh-private-key <path>] [--dry-run] [--json]");
7015
7754
  }
7016
7755
  function getPackageVersion() {
7017
- const metadata = JSON.parse((0, import_node_fs.readFileSync)((0, import_node_path.join)(__dirname, "..", "package.json"), "utf8"));
7756
+ const metadata = JSON.parse((0, import_node_fs2.readFileSync)((0, import_node_path2.join)(__dirname, "..", "package.json"), "utf8"));
7018
7757
  return typeof metadata.version === "string" ? metadata.version : "0.0.0";
7019
7758
  }
7020
7759
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meistrari/tela-cli",
3
- "version": "0.7.22",
3
+ "version": "0.7.24",
4
4
  "description": "Tela command line tools for local state and agent workflows.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -1,12 +1,13 @@
1
1
  ---
2
2
  name: tela-cli
3
- description: Use when a coding agent needs to operate Tela Agent repositories 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 or Tela Pantry/service ingredients 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. Prefer explicit
9
- flags and `--json` so commands are safe in non-interactive agent sessions.
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
10
+ safe in non-interactive agent sessions.
10
11
 
11
12
  ## Subpaths
12
13
 
@@ -15,6 +16,28 @@ flags and `--json` so commands are safe in non-interactive agent sessions.
15
16
  ## Core Rules
16
17
 
17
18
  - Use `tela agent ...` commands, not legacy top-level `sync`, `run`, `push`, or `feedback`.
19
+ - Run `tela update` before relying on ingredient slugs; it refreshes the local Pantry cache under `TELA_HOME`.
20
+ - Query Pantry before building a service integration from scratch: `tela api pantry GET /ingredients query==<term>`.
21
+ - Read endpoint or library docs before calling a service: `tela docs endpoints <slug>` then `tela docs reference <slug> <METHOD> <PATH>`.
18
22
  - Prefer `--json` for machine-readable output and errors.
19
23
  - Do not rely on prompts in coding-agent sessions; pass every required value by flag.
20
24
  - Do not print token material. Use `tela auth token --json` for metadata and `--raw` only when a shell command explicitly needs the token.
25
+
26
+ ## Pantry and Service Commands
27
+
28
+ ```bash
29
+ tela update --json
30
+ tela api <slug> <METHOD> <PATH> [args...] --env <slug> --body <json> --pretty
31
+ tela docs endpoints <slug> --pretty
32
+ tela docs reference <slug> [METHOD PATH|file-path] --pretty
33
+ tela docs guide <slug> [path]
34
+ tela env list <ingredient-slug> --pretty
35
+ tela env set <ingredient-slug> <environment>
36
+ ```
37
+
38
+ HTTPie-style args for `tela api`:
39
+
40
+ - `name=value` sends a JSON string field.
41
+ - `name:=value` sends a raw JSON field.
42
+ - `name==value` sends a query parameter.
43
+ - `Header:value` sends a request header.