aipctl 2026.9.8 → 2026.9.10

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/build/index.cjs CHANGED
@@ -778,7 +778,7 @@ const isIterable = input => typeof input === "string" || hasProperty(input, Symb
778
778
  * @since 2.0.0
779
779
  * @see isObject
780
780
  */
781
- const isRecord$3 = input => isRecordOrArray(input) && !Array.isArray(input);
781
+ const isRecord$2 = input => isRecordOrArray(input) && !Array.isArray(input);
782
782
  /**
783
783
  * A refinement that checks if a value is `PromiseLike`. It performs a duck-typing
784
784
  * check for a `.then` method.
@@ -25158,7 +25158,7 @@ const go = (ast, isDecoding) => {
25158
25158
  const concurrency = getConcurrency(ast);
25159
25159
  const batching = getBatching(ast);
25160
25160
  return (input, options) => {
25161
- if (!isRecord$3(input)) {
25161
+ if (!isRecord$2(input)) {
25162
25162
  return left(new Type(ast, input));
25163
25163
  }
25164
25164
  const allErrors = options?.errors === "all";
@@ -89670,7 +89670,7 @@ const injectedBuildEnv = "dev".length > 0 ? "dev" : "local";
89670
89670
  if (!isBuildEnv(injectedBuildEnv)) {
89671
89671
  throw new Error(`Invalid AIPCTL_BUILD_ENV: ${injectedBuildEnv}`);
89672
89672
  }
89673
- const VERSION = "2026.9.8".length > 0 ? "2026.9.8" : "0.0.0";
89673
+ const VERSION = "2026.9.10".length > 0 ? "2026.9.10" : "0.0.0";
89674
89674
  const BUILD_ENV = injectedBuildEnv;
89675
89675
  const INCLUDE_NATIVE_LOGS = BUILD_ENV !== "prod";
89676
89676
 
@@ -89819,7 +89819,25 @@ const implementedCommandOperationIds = /* @__PURE__ */ new Set([
89819
89819
  "listCliOrganizationMembers",
89820
89820
  "listCliModels",
89821
89821
  "listCliModelExecModels",
89822
- "createCliModelChatExecution"
89822
+ "createCliModelChatExecution",
89823
+ "listCliSkills",
89824
+ "getCliSkill",
89825
+ "deleteCliSkill",
89826
+ "listCliSkillLibrary",
89827
+ "createCliSkillClones",
89828
+ "listCliBuiltInSkills",
89829
+ "createCliBuiltInSkillClones",
89830
+ "listCliAdminSkills",
89831
+ "getCliAdminSkill",
89832
+ "deleteCliAdminSkill",
89833
+ "updateCliAdminSkill",
89834
+ "listCliAdminBuiltInSkills",
89835
+ "createCliAdminBuiltInSkillClones",
89836
+ "listCliDriveEntries",
89837
+ "getCliDriveUsage",
89838
+ "createCliDriveFolder",
89839
+ "updateCliDriveEntryName",
89840
+ "deleteCliDriveEntries"
89823
89841
  ]);
89824
89842
  const releaseEnabledCommandOperationIds = /* @__PURE__ */ new Set([
89825
89843
  "listCliAgents",
@@ -89836,7 +89854,25 @@ const releaseEnabledCommandOperationIds = /* @__PURE__ */ new Set([
89836
89854
  "getCliOrganizationCapabilities",
89837
89855
  "listCliOrganizationMembers",
89838
89856
  "listCliModels",
89839
- "listCliModelExecModels"
89857
+ "listCliModelExecModels",
89858
+ "listCliSkills",
89859
+ "getCliSkill",
89860
+ "deleteCliSkill",
89861
+ "listCliSkillLibrary",
89862
+ "createCliSkillClones",
89863
+ "listCliBuiltInSkills",
89864
+ "createCliBuiltInSkillClones",
89865
+ "listCliAdminSkills",
89866
+ "getCliAdminSkill",
89867
+ "deleteCliAdminSkill",
89868
+ "updateCliAdminSkill",
89869
+ "listCliAdminBuiltInSkills",
89870
+ "createCliAdminBuiltInSkillClones",
89871
+ "listCliDriveEntries",
89872
+ "getCliDriveUsage",
89873
+ "createCliDriveFolder",
89874
+ "updateCliDriveEntryName",
89875
+ "deleteCliDriveEntries"
89840
89876
  ]);
89841
89877
  const direct = (commands, operation) => commands.map((command) => ({
89842
89878
  command,
@@ -90650,14 +90686,14 @@ class InvalidAppUrlError extends Error {
90650
90686
  this.rawValue = rawValue;
90651
90687
  }
90652
90688
  }
90653
- function isRecord$2(value) {
90689
+ function isRecord$1(value) {
90654
90690
  return typeof value === "object" && value !== null;
90655
90691
  }
90656
90692
  function optionalString$1(value) {
90657
90693
  return typeof value === "string" ? value : void 0;
90658
90694
  }
90659
90695
  function parseCurrentOrganization(value) {
90660
- if (!isRecord$2(value)) {
90696
+ if (!isRecord$1(value)) {
90661
90697
  return void 0;
90662
90698
  }
90663
90699
  const id = optionalString$1(value["id"]);
@@ -90669,7 +90705,7 @@ function parseCurrentOrganization(value) {
90669
90705
  return { id, slug, name };
90670
90706
  }
90671
90707
  function normalizeLoadedConfig(value) {
90672
- if (!isRecord$2(value)) {
90708
+ if (!isRecord$1(value)) {
90673
90709
  return {};
90674
90710
  }
90675
90711
  return {
@@ -92903,11 +92939,11 @@ const CREDENTIALS_FILE = path__namespace.join(CONFIG_DIR$1, "credentials.json");
92903
92939
  const CREDENTIALS_LOCK_FILE = path__namespace.join(CONFIG_DIR$1, "credentials.lock");
92904
92940
  class CredentialServiceTag extends Tag$1("CredentialService")() {
92905
92941
  }
92906
- function isNonEmptyString$1(value) {
92942
+ function isNonEmptyString(value) {
92907
92943
  return typeof value === "string" && value.length > 0;
92908
92944
  }
92909
92945
  function hasOAuthClient(credentials) {
92910
- return isNonEmptyString$1(credentials?.clientId);
92946
+ return isNonEmptyString(credentials?.clientId);
92911
92947
  }
92912
92948
  function isAccessTokenExpired(credentials, bufferMs = 0, now = Date.now()) {
92913
92949
  if (!credentials?.expiresAt) {
@@ -92916,13 +92952,13 @@ function isAccessTokenExpired(credentials, bufferMs = 0, now = Date.now()) {
92916
92952
  return credentials.expiresAt - bufferMs <= now;
92917
92953
  }
92918
92954
  function hasValidRefreshToken(credentials, now) {
92919
- return isNonEmptyString$1(credentials.refreshToken) && (credentials.refreshExpiresAt === void 0 || credentials.refreshExpiresAt > now);
92955
+ return isNonEmptyString(credentials.refreshToken) && (credentials.refreshExpiresAt === void 0 || credentials.refreshExpiresAt > now);
92920
92956
  }
92921
92957
  function getCredentialAuthStatus(credentials, now = Date.now(), options = {}) {
92922
92958
  if (!hasOAuthClient(credentials)) {
92923
92959
  return "missing";
92924
92960
  }
92925
- const needsAccessRefresh = options.forceRefresh || !isNonEmptyString$1(credentials.accessToken) || isAccessTokenExpired(credentials, options.accessExpiryBufferMs ?? 0, now);
92961
+ const needsAccessRefresh = options.forceRefresh || !isNonEmptyString(credentials.accessToken) || isAccessTokenExpired(credentials, options.accessExpiryBufferMs ?? 0, now);
92926
92962
  if (!needsAccessRefresh) {
92927
92963
  return "authenticated";
92928
92964
  }
@@ -93918,7 +93954,7 @@ function trimTrailingSlash(value) {
93918
93954
  function toError$1(error, fallbackMessage) {
93919
93955
  return error instanceof Error ? new Error(maskSensitiveText(error.message)) : new Error(maskSensitiveText(fallbackMessage));
93920
93956
  }
93921
- function isRecord$1(value) {
93957
+ function isRecord(value) {
93922
93958
  return typeof value === "object" && value !== null;
93923
93959
  }
93924
93960
  function optionalString(value) {
@@ -93928,7 +93964,7 @@ function optionalNumber(value) {
93928
93964
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
93929
93965
  }
93930
93966
  function parseOAuthTokenResponse(value) {
93931
- if (!isRecord$1(value)) {
93967
+ if (!isRecord(value)) {
93932
93968
  throw new OAuthRequestError("OAuth token response was not a JSON object", void 0, "invalid_token_response");
93933
93969
  }
93934
93970
  const accessToken = optionalString(value["access_token"]);
@@ -94457,55 +94493,6 @@ async function acquire() {
94457
94493
  }
94458
94494
  }
94459
94495
 
94460
- const SERVER_CAPABILITY_SCHEMA_VERSION = 1;
94461
- class ServerCapabilityError extends Error {
94462
- code;
94463
- constructor(code, message) {
94464
- super(message);
94465
- this.name = "ServerCapabilityError";
94466
- this.code = code;
94467
- }
94468
- }
94469
- function parseEnabledServerOperations(value) {
94470
- if (!isRecord(value) || value["schemaVersion"] !== SERVER_CAPABILITY_SCHEMA_VERSION) {
94471
- throw new ServerCapabilityError(
94472
- "CLI_CAPABILITIES_INCOMPATIBLE",
94473
- "The server returned an unsupported CLI capability schema."
94474
- );
94475
- }
94476
- const enabledOperations = value["enabledOperations"];
94477
- if (!Array.isArray(enabledOperations)) {
94478
- throw new ServerCapabilityError(
94479
- "CLI_CAPABILITIES_INCOMPATIBLE",
94480
- "The server returned an invalid CLI capability operation list."
94481
- );
94482
- }
94483
- const operationIds = /* @__PURE__ */ new Set();
94484
- for (const operation of enabledOperations) {
94485
- if (!isRecord(operation) || !isNonEmptyString(operation["operationId"]) || !isNonEmptyString(operation["capability"]) || operationIds.has(operation["operationId"])) {
94486
- throw new ServerCapabilityError(
94487
- "CLI_CAPABILITIES_INCOMPATIBLE",
94488
- "The server returned an invalid CLI capability operation."
94489
- );
94490
- }
94491
- operationIds.add(operation["operationId"]);
94492
- }
94493
- return operationIds;
94494
- }
94495
- function requireEnabledServerOperation(enabledOperations, operationId) {
94496
- if (enabledOperations.has(operationId)) return;
94497
- throw new ServerCapabilityError(
94498
- "CLI_OPERATION_NOT_ENABLED",
94499
- `CLI operation "${operationId}" is not enabled by the server.`
94500
- );
94501
- }
94502
- function isRecord(value) {
94503
- return typeof value === "object" && value !== null && !Array.isArray(value);
94504
- }
94505
- function isNonEmptyString(value) {
94506
- return typeof value === "string" && value.length > 0;
94507
- }
94508
-
94509
94496
  class ApiError extends TaggedError$1("ApiError") {
94510
94497
  }
94511
94498
  class NotLoggedInError extends TaggedError$1("NotLoggedInError") {
@@ -94670,26 +94657,31 @@ async function toApiError(response) {
94670
94657
  errorData = await response.json();
94671
94658
  } catch {
94672
94659
  }
94673
- const details = isCliQuotaErrorCode(errorData.code) ? parseCliQuotaErrorDetails(errorData.details) : void 0;
94674
- const retryAfterSeconds = details ? parseRetryAfterSeconds(response.headers.get("retry-after")) : void 0;
94660
+ const code = errorData.code ?? errorData.errorCode;
94661
+ const details = isCliQuotaErrorCode(code) ? parseCliQuotaErrorDetails(errorData.details, code) : void 0;
94662
+ const retryAfterSeconds = details ? details.limitKind === "concurrent" ? void 0 : details.retryAfterSeconds ?? parseRetryAfterSeconds(response.headers.get("retry-after")) : void 0;
94675
94663
  return createApiError({
94676
94664
  message: errorData.message || `HTTP ${response.status}: ${response.statusText}`,
94677
94665
  statusCode: response.status,
94678
- code: errorData.code,
94666
+ code,
94679
94667
  ...details ? { details } : {},
94680
94668
  ...retryAfterSeconds === void 0 ? {} : { retryAfterSeconds }
94681
94669
  });
94682
94670
  }
94683
94671
  function isCliQuotaErrorCode(code) {
94684
- return code === "CLI_QUOTA_EXCEEDED" || code === "CLI_QUOTA_POLICY_UNAVAILABLE" || code === "CLI_QUOTA_LIMITER_UNAVAILABLE";
94672
+ return code === "CLI_QUOTA_EXCEEDED" || code === "CLI_QUOTA_POLICY_UNAVAILABLE" || code === "CLI_QUOTA_LIMITER_UNAVAILABLE" || code === "CLI_TEXT_DAILY_LIMIT" || code === "CLI_TEXT_USER_CONCURRENCY_LIMIT" || code === "CLI_TEXT_ORGANIZATION_CONCURRENCY_LIMIT";
94673
+ }
94674
+ function isCliTextQuotaErrorCode(code) {
94675
+ return code === "CLI_TEXT_DAILY_LIMIT" || code === "CLI_TEXT_USER_CONCURRENCY_LIMIT" || code === "CLI_TEXT_ORGANIZATION_CONCURRENCY_LIMIT";
94685
94676
  }
94686
- function parseCliQuotaErrorDetails(value) {
94677
+ function parseCliQuotaErrorDetails(value, code) {
94687
94678
  if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
94688
94679
  const details = value;
94689
- if (details["limitKind"] !== "request_rate") return void 0;
94690
- if (typeof details["quotaClass"] !== "string" || !["bootstrap", "metadata", "text-execution", "control-read"].includes(details["quotaClass"])) {
94691
- return void 0;
94692
- }
94680
+ const sourceLimitKind = details["limitKind"];
94681
+ const limitKind = sourceLimitKind === "request_rate" ? "request_rate" : sourceLimitKind === "dailyTextAdmissions" ? "daily" : sourceLimitKind === "concurrentTextExecutions" ? "concurrent" : void 0;
94682
+ if (!limitKind) return void 0;
94683
+ const quotaClass = details["quotaClass"] ?? (isCliTextQuotaErrorCode(code) ? "text-execution" : void 0);
94684
+ if (typeof quotaClass !== "string" || !["bootstrap", "metadata", "text-execution", "control-read"].includes(quotaClass)) return void 0;
94693
94685
  if (typeof details["requestId"] !== "string" || details["requestId"].length === 0) return void 0;
94694
94686
  const scope = details["scope"];
94695
94687
  if (scope !== void 0 && (typeof scope !== "string" || !["user", "organization", "bootstrap", "control-read"].includes(scope))) {
@@ -94699,17 +94691,42 @@ function parseCliQuotaErrorDetails(value) {
94699
94691
  if (resetAt !== void 0 && (typeof resetAt !== "string" || !Number.isFinite(Date.parse(resetAt)))) {
94700
94692
  return void 0;
94701
94693
  }
94694
+ const retryAfterSeconds = details["retryAfterSeconds"];
94695
+ if (retryAfterSeconds !== void 0 && (typeof retryAfterSeconds !== "number" || !Number.isSafeInteger(retryAfterSeconds) || retryAfterSeconds < 0)) {
94696
+ return void 0;
94697
+ }
94702
94698
  const dependency = details["dependency"];
94703
94699
  if (dependency !== void 0 && dependency !== "policy" && dependency !== "limiter") return void 0;
94700
+ if (!isCliQuotaCombinationValid(code, limitKind, quotaClass, scope, dependency)) return void 0;
94704
94701
  return {
94705
- limitKind: "request_rate",
94706
- quotaClass: details["quotaClass"],
94702
+ limitKind,
94703
+ quotaClass,
94707
94704
  requestId: details["requestId"],
94708
94705
  ...scope === void 0 ? {} : { scope },
94709
- ...resetAt === void 0 ? {} : { resetAt },
94706
+ ...resetAt === void 0 || limitKind === "concurrent" ? {} : { resetAt },
94707
+ ...retryAfterSeconds === void 0 || limitKind === "concurrent" ? {} : { retryAfterSeconds },
94710
94708
  ...dependency === void 0 ? {} : { dependency }
94711
94709
  };
94712
94710
  }
94711
+ function isCliQuotaCombinationValid(code, limitKind, quotaClass, scope, dependency) {
94712
+ if (isCliTextQuotaErrorCode(code)) {
94713
+ const expectedLimitKind = code === "CLI_TEXT_DAILY_LIMIT" ? "daily" : "concurrent";
94714
+ const expectedScope = code === "CLI_TEXT_ORGANIZATION_CONCURRENCY_LIMIT" ? "organization" : "user";
94715
+ return limitKind === expectedLimitKind && quotaClass === "text-execution" && scope === expectedScope && dependency === void 0;
94716
+ }
94717
+ if (limitKind !== "request_rate") return false;
94718
+ const validScope = scope === void 0 || (quotaClass === "bootstrap" ? scope === "bootstrap" : quotaClass === "control-read" ? scope === "control-read" : scope === "user" || scope === "organization");
94719
+ if (!validScope) return false;
94720
+ switch (code) {
94721
+ case "CLI_QUOTA_EXCEEDED":
94722
+ return dependency === void 0;
94723
+ case "CLI_QUOTA_POLICY_UNAVAILABLE":
94724
+ return dependency === void 0 || dependency === "policy";
94725
+ case "CLI_QUOTA_LIMITER_UNAVAILABLE":
94726
+ return dependency === void 0 || dependency === "limiter";
94727
+ }
94728
+ return false;
94729
+ }
94713
94730
  function parseRetryAfterSeconds(value) {
94714
94731
  if (!value || !/^\d+$/.test(value)) return void 0;
94715
94732
  const parsed = Number(value);
@@ -94968,7 +94985,7 @@ const makeApiServiceLive = (buildEnv) => effect$1(
94968
94985
  });
94969
94986
  const baseUrl = () => loadConfiguredApiBaseUrl();
94970
94987
  const chatBaseUrl = () => loadConfiguredChatBaseUrl();
94971
- const requireServerOperation = (operationId, token) => {
94988
+ const requireReleaseEnabledOperation = (operationId) => {
94972
94989
  if (buildEnv !== "prod") return _void;
94973
94990
  if (!releaseEnabledCommandOperationIds.has(operationId)) {
94974
94991
  return fail$3(createApiError({
@@ -94977,24 +94994,7 @@ const makeApiServiceLive = (buildEnv) => effect$1(
94977
94994
  code: "CLI_OPERATION_NOT_ENABLED"
94978
94995
  }));
94979
94996
  }
94980
- return gen(function* () {
94981
- const response = yield* makeRequest(
94982
- baseUrl,
94983
- "/api/cli/v1/capabilities",
94984
- token
94985
- );
94986
- yield* try_({
94987
- try: () => {
94988
- const enabledOperations = parseEnabledServerOperations(response);
94989
- requireEnabledServerOperation(enabledOperations, operationId);
94990
- },
94991
- catch: (error) => createApiError({
94992
- message: error instanceof Error ? error.message : "CLI server capability validation failed",
94993
- statusCode: error instanceof ServerCapabilityError && error.code === "CLI_OPERATION_NOT_ENABLED" ? 404 : 503,
94994
- code: error instanceof ServerCapabilityError ? error.code : "CLI_CAPABILITIES_INCOMPATIBLE"
94995
- })
94996
- });
94997
- });
94997
+ return _void;
94998
94998
  };
94999
94999
  const subscribeAgentChatStream = (orgSlug, conversationId, token) => gen(function* () {
95000
95000
  const resolvedChatBaseUrl = yield* requireChatBaseUrlEffect();
@@ -95062,12 +95062,12 @@ const makeApiServiceLive = (buildEnv) => effect$1(
95062
95062
  return {
95063
95063
  listOrganizations: () => gen(function* () {
95064
95064
  const token = yield* getAccessToken();
95065
- yield* requireServerOperation("listCliOrganizations", token);
95065
+ yield* requireReleaseEnabledOperation("listCliOrganizations");
95066
95066
  return yield* makeRequest(baseUrl, "/api/cli/v1/organizations", token);
95067
95067
  }),
95068
95068
  getOrganization: (slug) => gen(function* () {
95069
95069
  const token = yield* getAccessToken();
95070
- yield* requireServerOperation("listCliOrganizations", token);
95070
+ yield* requireReleaseEnabledOperation("listCliOrganizations");
95071
95071
  const orgs = yield* makeRequest(baseUrl, "/api/cli/v1/organizations", token);
95072
95072
  const org = orgs.find((o) => o.slug === slug);
95073
95073
  if (!org) {
@@ -95082,11 +95082,12 @@ const makeApiServiceLive = (buildEnv) => effect$1(
95082
95082
  }),
95083
95083
  listSkills: (orgSlug, options) => gen(function* () {
95084
95084
  const token = yield* getAccessToken();
95085
+ yield* requireReleaseEnabledOperation("listCliSkills");
95085
95086
  const params = new URLSearchParams();
95086
95087
  params.set("count", String(options?.count ?? 50));
95087
95088
  if (options?.cursor) params.set("cursor", options.cursor);
95088
95089
  if (options?.search) params.set("search", options.search);
95089
- const path = `/api/web/organizations/slug/${encodeURIComponent(orgSlug)}/skills?${params.toString()}`;
95090
+ const path = `/api/cli/v1/organizations/${encodeURIComponent(orgSlug)}/skills?${params.toString()}`;
95090
95091
  const response = yield* makeRequest(baseUrl, path, token);
95091
95092
  return {
95092
95093
  ...response,
@@ -95103,11 +95104,12 @@ const makeApiServiceLive = (buildEnv) => effect$1(
95103
95104
  }),
95104
95105
  listAdminSkills: (orgSlug, options) => gen(function* () {
95105
95106
  const token = yield* getAccessToken();
95107
+ yield* requireReleaseEnabledOperation("listCliAdminSkills");
95106
95108
  const params = new URLSearchParams();
95107
95109
  params.set("pageNumber", String(options?.pageNumber ?? 1));
95108
95110
  params.set("pageSize", String(options?.pageSize ?? 20));
95109
95111
  if (options?.search) params.set("search", options.search);
95110
- const path = `/api/web/admin/organizations/slug/${encodeURIComponent(orgSlug)}/skills?${params.toString()}`;
95112
+ const path = `/api/cli/v1/organizations/${encodeURIComponent(orgSlug)}/admin/skills?${params.toString()}`;
95111
95113
  const response = yield* makeRequest(baseUrl, path, token);
95112
95114
  return {
95113
95115
  ...response,
@@ -95177,12 +95179,14 @@ const makeApiServiceLive = (buildEnv) => effect$1(
95177
95179
  }),
95178
95180
  deleteSkill: (orgSlug, skillId) => gen(function* () {
95179
95181
  const token = yield* getAccessToken();
95180
- const path = `/api/web/organizations/slug/${encodeURIComponent(orgSlug)}/skills/${encodeURIComponent(skillId)}`;
95182
+ yield* requireReleaseEnabledOperation("deleteCliSkill");
95183
+ const path = `/api/cli/v1/organizations/${encodeURIComponent(orgSlug)}/skills/${encodeURIComponent(skillId)}`;
95181
95184
  return yield* makeRequest(baseUrl, path, token, { method: "DELETE" });
95182
95185
  }),
95183
95186
  deleteAdminSkill: (orgSlug, skillId) => gen(function* () {
95184
95187
  const token = yield* getAccessToken();
95185
- const path = `/api/web/admin/organizations/slug/${encodeURIComponent(orgSlug)}/skills/${encodeURIComponent(skillId)}`;
95188
+ yield* requireReleaseEnabledOperation("deleteCliAdminSkill");
95189
+ const path = `/api/cli/v1/organizations/${encodeURIComponent(orgSlug)}/admin/skills/${encodeURIComponent(skillId)}`;
95186
95190
  return yield* makeRequest(baseUrl, path, token, { method: "DELETE" });
95187
95191
  }),
95188
95192
  getCurrentOrganization: () => gen(function* () {
@@ -95199,10 +95203,11 @@ const makeApiServiceLive = (buildEnv) => effect$1(
95199
95203
  // Phase 1: Skill detail/download/library/clone
95200
95204
  getSkill: (orgSlug, skillId, options) => gen(function* () {
95201
95205
  const token = yield* getAccessToken();
95206
+ yield* requireReleaseEnabledOperation("getCliSkill");
95202
95207
  const params = new URLSearchParams();
95203
95208
  if (options?.maxFiles) params.set("maxFiles", String(options.maxFiles));
95204
95209
  const qs = params.toString();
95205
- const path = `/api/web/organizations/slug/${encodeURIComponent(orgSlug)}/skills/${encodeURIComponent(skillId)}${qs ? `?${qs}` : ""}`;
95210
+ const path = `/api/cli/v1/organizations/${encodeURIComponent(orgSlug)}/skills/${encodeURIComponent(skillId)}${qs ? `?${qs}` : ""}`;
95206
95211
  const response = yield* makeRequest(baseUrl, path, token);
95207
95212
  return {
95208
95213
  ...response,
@@ -95214,10 +95219,11 @@ const makeApiServiceLive = (buildEnv) => effect$1(
95214
95219
  }),
95215
95220
  getAdminSkill: (orgSlug, skillId, options) => gen(function* () {
95216
95221
  const token = yield* getAccessToken();
95222
+ yield* requireReleaseEnabledOperation("getCliAdminSkill");
95217
95223
  const params = new URLSearchParams();
95218
95224
  if (options?.maxFiles) params.set("maxFiles", String(options.maxFiles));
95219
95225
  const qs = params.toString();
95220
- const path = `/api/web/admin/organizations/slug/${encodeURIComponent(orgSlug)}/skills/${encodeURIComponent(skillId)}${qs ? `?${qs}` : ""}`;
95226
+ const path = `/api/cli/v1/organizations/${encodeURIComponent(orgSlug)}/admin/skills/${encodeURIComponent(skillId)}${qs ? `?${qs}` : ""}`;
95221
95227
  const response = yield* makeRequest(baseUrl, path, token);
95222
95228
  return {
95223
95229
  ...response,
@@ -95229,7 +95235,8 @@ const makeApiServiceLive = (buildEnv) => effect$1(
95229
95235
  }),
95230
95236
  updateAdminSkill: (orgSlug, skillId, body) => gen(function* () {
95231
95237
  const token = yield* getAccessToken();
95232
- const path = `/api/web/admin/organizations/slug/${encodeURIComponent(orgSlug)}/skills/${encodeURIComponent(skillId)}`;
95238
+ yield* requireReleaseEnabledOperation("updateCliAdminSkill");
95239
+ const path = `/api/cli/v1/organizations/${encodeURIComponent(orgSlug)}/admin/skills/${encodeURIComponent(skillId)}`;
95233
95240
  return yield* makeRequest(baseUrl, path, token, {
95234
95241
  method: "PATCH",
95235
95242
  body
@@ -95289,16 +95296,18 @@ const makeApiServiceLive = (buildEnv) => effect$1(
95289
95296
  }),
95290
95297
  listLibrarySkills: (orgSlug, options) => gen(function* () {
95291
95298
  const token = yield* getAccessToken();
95299
+ yield* requireReleaseEnabledOperation("listCliSkillLibrary");
95292
95300
  const params = new URLSearchParams();
95293
95301
  params.set("count", String(options?.count ?? 50));
95294
95302
  if (options?.cursor) params.set("cursor", options.cursor);
95295
95303
  if (options?.search) params.set("search", options.search);
95296
- const path = `/api/web/organizations/slug/${encodeURIComponent(orgSlug)}/skills/library?${params.toString()}`;
95304
+ const path = `/api/cli/v1/organizations/${encodeURIComponent(orgSlug)}/skills/library?${params.toString()}`;
95297
95305
  return yield* makeRequest(baseUrl, path, token);
95298
95306
  }),
95299
95307
  cloneSkills: (orgSlug, sourceSkillIds) => gen(function* () {
95300
95308
  const token = yield* getAccessToken();
95301
- const path = `/api/web/organizations/slug/${encodeURIComponent(orgSlug)}/skills/library/clone`;
95309
+ yield* requireReleaseEnabledOperation("createCliSkillClones");
95310
+ const path = `/api/cli/v1/organizations/${encodeURIComponent(orgSlug)}/skills/library/clone`;
95302
95311
  return yield* makeRequest(baseUrl, path, token, {
95303
95312
  method: "POST",
95304
95313
  body: { sourceSkillIds }
@@ -95307,7 +95316,7 @@ const makeApiServiceLive = (buildEnv) => effect$1(
95307
95316
  // Phase 2: Agent management
95308
95317
  listInstalledAgents: (orgSlug) => gen(function* () {
95309
95318
  const token = yield* getAccessToken();
95310
- yield* requireServerOperation("listCliAgents", token);
95319
+ yield* requireReleaseEnabledOperation("listCliAgents");
95311
95320
  const path = `/api/cli/v1/organizations/${encodeURIComponent(orgSlug)}/agents`;
95312
95321
  const raw = yield* makeRequest(baseUrl, path, token);
95313
95322
  return {
@@ -95317,7 +95326,7 @@ const makeApiServiceLive = (buildEnv) => effect$1(
95317
95326
  }),
95318
95327
  getInstalledAgent: (orgSlug, agentId) => gen(function* () {
95319
95328
  const token = yield* getAccessToken();
95320
- yield* requireServerOperation("getCliAgent", token);
95329
+ yield* requireReleaseEnabledOperation("getCliAgent");
95321
95330
  const path = `/api/cli/v1/organizations/${encodeURIComponent(orgSlug)}/agents/${encodeURIComponent(agentId)}`;
95322
95331
  const raw = yield* makeRequest(baseUrl, path, token);
95323
95332
  return {
@@ -95330,7 +95339,7 @@ const makeApiServiceLive = (buildEnv) => effect$1(
95330
95339
  }),
95331
95340
  updateInstalledAgentStatus: (orgSlug, agentId, status) => gen(function* () {
95332
95341
  const token = yield* getAccessToken();
95333
- yield* requireServerOperation("updateCliAgentStatus", token);
95342
+ yield* requireReleaseEnabledOperation("updateCliAgentStatus");
95334
95343
  const path = `/api/cli/v1/organizations/${encodeURIComponent(orgSlug)}/agents/${encodeURIComponent(agentId)}/status`;
95335
95344
  return yield* makeRequest(baseUrl, path, token, {
95336
95345
  method: "PATCH",
@@ -95410,7 +95419,7 @@ const makeApiServiceLive = (buildEnv) => effect$1(
95410
95419
  // Phase 4: Monitoring
95411
95420
  listOrgMembers: (orgSlug) => gen(function* () {
95412
95421
  const token = yield* getAccessToken();
95413
- yield* requireServerOperation("listCliOrganizationMembers", token);
95422
+ yield* requireReleaseEnabledOperation("listCliOrganizationMembers");
95414
95423
  const path = `/api/cli/v1/organizations/${encodeURIComponent(orgSlug)}/members`;
95415
95424
  const raw = yield* makeRequest(baseUrl, path, token);
95416
95425
  return {
@@ -95426,7 +95435,7 @@ const makeApiServiceLive = (buildEnv) => effect$1(
95426
95435
  listModels: (orgSlug) => gen(function* () {
95427
95436
  yield* requireChatBaseUrlEffect();
95428
95437
  const token = yield* getAccessToken();
95429
- yield* requireServerOperation("listCliModels", token);
95438
+ yield* requireReleaseEnabledOperation("listCliModels");
95430
95439
  const path = `/api/cli/v1/organizations/${encodeURIComponent(orgSlug)}/models`;
95431
95440
  const response = yield* makeRequest(chatBaseUrl, path, token);
95432
95441
  return response.models.map((id) => ({
@@ -95437,14 +95446,14 @@ const makeApiServiceLive = (buildEnv) => effect$1(
95437
95446
  listModelExecModels: (orgSlug) => gen(function* () {
95438
95447
  yield* requireChatBaseUrlEffect();
95439
95448
  const token = yield* getAccessToken();
95440
- yield* requireServerOperation("listCliModelExecModels", token);
95449
+ yield* requireReleaseEnabledOperation("listCliModelExecModels");
95441
95450
  const path = `/api/cli/v1/organizations/${encodeURIComponent(orgSlug)}/model-exec/models`;
95442
95451
  return yield* makeRequest(chatBaseUrl, path, token);
95443
95452
  }),
95444
95453
  executeModelExecChat: (orgSlug, body) => gen(function* () {
95445
95454
  yield* requireChatBaseUrlEffect();
95446
95455
  const token = yield* getAccessToken();
95447
- yield* requireServerOperation("createCliModelChatExecution", token);
95456
+ yield* requireReleaseEnabledOperation("createCliModelChatExecution");
95448
95457
  const path = `/api/cli/v1/organizations/${encodeURIComponent(orgSlug)}/model-exec/chat`;
95449
95458
  return yield* makeRequest(chatBaseUrl, path, token, {
95450
95459
  method: "POST",
@@ -95517,8 +95526,9 @@ const makeApiServiceLive = (buildEnv) => effect$1(
95517
95526
  // Phase 5: Drive/Integration
95518
95527
  listDriveEntries: (orgSlug) => gen(function* () {
95519
95528
  const token = yield* getAccessToken();
95529
+ yield* requireReleaseEnabledOperation("listCliDriveEntries");
95520
95530
  const params = new URLSearchParams({ count: "200" });
95521
- const path = `/api/web/organizations/slug/${encodeURIComponent(orgSlug)}/drive/entries?${params.toString()}`;
95531
+ const path = `/api/cli/v1/organizations/${encodeURIComponent(orgSlug)}/drive/entries?${params.toString()}`;
95522
95532
  return yield* makeRequest(baseUrl, path, token);
95523
95533
  }),
95524
95534
  getDriveDownloadUrl: (orgSlug, entryId) => gen(function* () {
@@ -95552,22 +95562,26 @@ const makeApiServiceLive = (buildEnv) => effect$1(
95552
95562
  }),
95553
95563
  deleteDriveEntries: (orgSlug, entryIds) => gen(function* () {
95554
95564
  const token = yield* getAccessToken();
95555
- const path = `/api/web/organizations/slug/${encodeURIComponent(orgSlug)}/drive/entries/delete`;
95565
+ yield* requireReleaseEnabledOperation("deleteCliDriveEntries");
95566
+ const path = `/api/cli/v1/organizations/${encodeURIComponent(orgSlug)}/drive/entries/delete`;
95556
95567
  return yield* makeRequest(baseUrl, path, token, { method: "POST", body: { entryIds } });
95557
95568
  }),
95558
95569
  createDriveFolder: (orgSlug, name) => gen(function* () {
95559
95570
  const token = yield* getAccessToken();
95560
- const path = `/api/web/organizations/slug/${encodeURIComponent(orgSlug)}/drive/entries`;
95571
+ yield* requireReleaseEnabledOperation("createCliDriveFolder");
95572
+ const path = `/api/cli/v1/organizations/${encodeURIComponent(orgSlug)}/drive/folders`;
95561
95573
  return yield* makeRequest(baseUrl, path, token, { method: "POST", body: { name } });
95562
95574
  }),
95563
95575
  renameDriveEntry: (orgSlug, entryId, newName) => gen(function* () {
95564
95576
  const token = yield* getAccessToken();
95565
- const path = `/api/web/organizations/slug/${encodeURIComponent(orgSlug)}/drive/entries/${encodeURIComponent(entryId)}`;
95577
+ yield* requireReleaseEnabledOperation("updateCliDriveEntryName");
95578
+ const path = `/api/cli/v1/organizations/${encodeURIComponent(orgSlug)}/drive/entries/${encodeURIComponent(entryId)}`;
95566
95579
  return yield* makeRequest(baseUrl, path, token, { method: "PATCH", body: { newName } });
95567
95580
  }),
95568
95581
  getDriveUsage: (orgSlug) => gen(function* () {
95569
95582
  const token = yield* getAccessToken();
95570
- const path = `/api/web/organizations/slug/${encodeURIComponent(orgSlug)}/drive/usage`;
95583
+ yield* requireReleaseEnabledOperation("getCliDriveUsage");
95584
+ const path = `/api/cli/v1/organizations/${encodeURIComponent(orgSlug)}/drive/usage`;
95571
95585
  return yield* makeRequest(baseUrl, path, token);
95572
95586
  }),
95573
95587
  listIntegrations: (orgSlug) => gen(function* () {
@@ -95586,7 +95600,7 @@ const makeApiServiceLive = (buildEnv) => effect$1(
95586
95600
  // Phase 6: Auth enhancements
95587
95601
  getMe: () => gen(function* () {
95588
95602
  const token = yield* getAccessToken();
95589
- yield* requireServerOperation("getCliMe", token);
95603
+ yield* requireReleaseEnabledOperation("getCliMe");
95590
95604
  return yield* makeRequest(baseUrl, "/api/cli/v1/me", token);
95591
95605
  }),
95592
95606
  refreshToken: () => refreshAccessToken({ force: true }),
@@ -95624,7 +95638,7 @@ const makeApiServiceLive = (buildEnv) => effect$1(
95624
95638
  // Phase 8: Agent admin CRUD
95625
95639
  createInstalledAgent: (orgSlug, body) => gen(function* () {
95626
95640
  const token = yield* getAccessToken();
95627
- yield* requireServerOperation("createCliAgent", token);
95641
+ yield* requireReleaseEnabledOperation("createCliAgent");
95628
95642
  const path = `/api/cli/v1/organizations/${encodeURIComponent(orgSlug)}/agents`;
95629
95643
  return yield* makeRequest(baseUrl, path, token, {
95630
95644
  method: "POST",
@@ -95633,7 +95647,7 @@ const makeApiServiceLive = (buildEnv) => effect$1(
95633
95647
  }),
95634
95648
  updateInstalledAgent: (orgSlug, agentId, body) => gen(function* () {
95635
95649
  const token = yield* getAccessToken();
95636
- yield* requireServerOperation("updateCliAgent", token);
95650
+ yield* requireReleaseEnabledOperation("updateCliAgent");
95637
95651
  const path = `/api/cli/v1/organizations/${encodeURIComponent(orgSlug)}/agents/${encodeURIComponent(agentId)}`;
95638
95652
  return yield* makeRequest(baseUrl, path, token, {
95639
95653
  method: "PATCH",
@@ -95642,7 +95656,7 @@ const makeApiServiceLive = (buildEnv) => effect$1(
95642
95656
  }),
95643
95657
  deleteInstalledAgent: (orgSlug, agentId) => gen(function* () {
95644
95658
  const token = yield* getAccessToken();
95645
- yield* requireServerOperation("deleteCliAgent", token);
95659
+ yield* requireReleaseEnabledOperation("deleteCliAgent");
95646
95660
  const path = `/api/cli/v1/organizations/${encodeURIComponent(orgSlug)}/agents/${encodeURIComponent(agentId)}`;
95647
95661
  return yield* makeRequest(baseUrl, path, token, { method: "DELETE" });
95648
95662
  }),
@@ -95669,7 +95683,7 @@ const makeApiServiceLive = (buildEnv) => effect$1(
95669
95683
  }),
95670
95684
  getInstalledAgentShare: (orgSlug, agentId) => gen(function* () {
95671
95685
  const token = yield* getAccessToken();
95672
- yield* requireServerOperation("getCliAgentShare", token);
95686
+ yield* requireReleaseEnabledOperation("getCliAgentShare");
95673
95687
  const path = `/api/cli/v1/organizations/${encodeURIComponent(orgSlug)}/agents/${encodeURIComponent(agentId)}/share`;
95674
95688
  const raw = yield* makeRequest(baseUrl, path, token);
95675
95689
  const sharedUserList = yield* try_({
@@ -95683,7 +95697,7 @@ const makeApiServiceLive = (buildEnv) => effect$1(
95683
95697
  }),
95684
95698
  listAgentTemplates: (orgSlug) => gen(function* () {
95685
95699
  const token = yield* getAccessToken();
95686
- yield* requireServerOperation("listCliAgentTemplates", token);
95700
+ yield* requireReleaseEnabledOperation("listCliAgentTemplates");
95687
95701
  const path = `/api/cli/v1/organizations/${encodeURIComponent(orgSlug)}/agents/templates`;
95688
95702
  const templates = [];
95689
95703
  let page = 1;
@@ -95707,16 +95721,37 @@ const makeApiServiceLive = (buildEnv) => effect$1(
95707
95721
  // Built-in skill library
95708
95722
  listBuiltInSkills: (orgSlug, options) => gen(function* () {
95709
95723
  const token = yield* getAccessToken();
95724
+ yield* requireReleaseEnabledOperation("listCliBuiltInSkills");
95710
95725
  const params = new URLSearchParams();
95711
95726
  params.set("limit", String(options?.limit ?? 50));
95712
95727
  if (options?.cursor) params.set("cursor", options.cursor);
95713
95728
  if (options?.search) params.set("search", options.search);
95714
- const path = `/api/web/admin/organizations/slug/${encodeURIComponent(orgSlug)}/skills/built-in-library?${params.toString()}`;
95729
+ const path = `/api/cli/v1/organizations/${encodeURIComponent(orgSlug)}/skills/built-in?${params.toString()}`;
95715
95730
  return yield* makeRequest(baseUrl, path, token);
95716
95731
  }),
95717
95732
  cloneBuiltInSkills: (orgSlug, sourceSkillIds) => gen(function* () {
95718
95733
  const token = yield* getAccessToken();
95719
- const path = `/api/web/admin/organizations/slug/${encodeURIComponent(orgSlug)}/skills/built-in-library/clone`;
95734
+ yield* requireReleaseEnabledOperation("createCliBuiltInSkillClones");
95735
+ const path = `/api/cli/v1/organizations/${encodeURIComponent(orgSlug)}/skills/built-in/clone`;
95736
+ return yield* makeRequest(baseUrl, path, token, {
95737
+ method: "POST",
95738
+ body: { sourceSkillIds }
95739
+ });
95740
+ }),
95741
+ listAdminBuiltInSkills: (orgSlug, options) => gen(function* () {
95742
+ const token = yield* getAccessToken();
95743
+ yield* requireReleaseEnabledOperation("listCliAdminBuiltInSkills");
95744
+ const params = new URLSearchParams();
95745
+ params.set("limit", String(options?.limit ?? 50));
95746
+ if (options?.cursor) params.set("cursor", options.cursor);
95747
+ if (options?.search) params.set("search", options.search);
95748
+ const path = `/api/cli/v1/organizations/${encodeURIComponent(orgSlug)}/admin/skills/built-in?${params.toString()}`;
95749
+ return yield* makeRequest(baseUrl, path, token);
95750
+ }),
95751
+ cloneAdminBuiltInSkills: (orgSlug, sourceSkillIds) => gen(function* () {
95752
+ const token = yield* getAccessToken();
95753
+ yield* requireReleaseEnabledOperation("createCliAdminBuiltInSkillClones");
95754
+ const path = `/api/cli/v1/organizations/${encodeURIComponent(orgSlug)}/admin/skills/built-in/clone`;
95720
95755
  return yield* makeRequest(baseUrl, path, token, {
95721
95756
  method: "POST",
95722
95757
  body: { sourceSkillIds }
@@ -95780,13 +95815,13 @@ const makeApiServiceLive = (buildEnv) => effect$1(
95780
95815
  // Org capabilities & user settings
95781
95816
  getOrgCapabilities: (orgSlug) => gen(function* () {
95782
95817
  const token = yield* getAccessToken();
95783
- yield* requireServerOperation("getCliOrganizationCapabilities", token);
95818
+ yield* requireReleaseEnabledOperation("getCliOrganizationCapabilities");
95784
95819
  const path = `/api/cli/v1/organizations/${encodeURIComponent(orgSlug)}/capabilities`;
95785
95820
  return yield* makeRequest(baseUrl, path, token);
95786
95821
  }),
95787
95822
  getUserSettings: () => gen(function* () {
95788
95823
  const token = yield* getAccessToken();
95789
- yield* requireServerOperation("getCliUserSettings", token);
95824
+ yield* requireReleaseEnabledOperation("getCliUserSettings");
95790
95825
  return yield* makeRequest(baseUrl, "/api/cli/v1/me/settings", token);
95791
95826
  }),
95792
95827
  // Tunnel management
@@ -97182,14 +97217,14 @@ const searchOption$3 = text("search").pipe(
97182
97217
  withDescription$3("Search built-in skills by name"),
97183
97218
  optional
97184
97219
  );
97185
- const builtInCommand = make$4(
97220
+ const makeBuiltInCommand = (listSkills = (api, orgSlug, options) => api.listBuiltInSkills(orgSlug, options), description = "List built-in skills from the library") => make$4(
97186
97221
  "built-in",
97187
97222
  { org: orgOption, search: searchOption$3 },
97188
97223
  ({ org, search }) => gen(function* () {
97189
97224
  const api = yield* ApiService;
97190
97225
  const orgSlug = yield* resolveOrgSlug(org, api);
97191
97226
  const searchValue = search._tag === "Some" ? search.value : void 0;
97192
- const result = yield* api.listBuiltInSkills(orgSlug, { search: searchValue }).pipe(
97227
+ const result = yield* listSkills(api, orgSlug, { search: searchValue }).pipe(
97193
97228
  catchTag(
97194
97229
  "NotLoggedInError",
97195
97230
  (e) => gen(function* () {
@@ -97227,7 +97262,8 @@ const builtInCommand = make$4(
97227
97262
  yield* log("(More built-in skills available.)");
97228
97263
  }
97229
97264
  })
97230
- ).pipe(withDescription("List built-in skills from the library"));
97265
+ ).pipe(withDescription(description));
97266
+ const builtInCommand = makeBuiltInCommand();
97231
97267
 
97232
97268
  const MAX_CLONE_SKILLS$1 = 5;
97233
97269
  const formatCloneBuiltInFailureMessage = maskSensitiveText;
@@ -97235,7 +97271,7 @@ const skillIdsArg$1 = text$2({ name: "id" }).pipe(
97235
97271
  withDescription$5(`Built-in skill ID(s) to clone (max ${MAX_CLONE_SKILLS$1})`),
97236
97272
  repeated
97237
97273
  );
97238
- const cloneBuiltInCommand = make$4(
97274
+ const makeCloneBuiltInCommand = (cloneSkills = (api, orgSlug, sourceSkillIds) => api.cloneBuiltInSkills(orgSlug, sourceSkillIds), description = "Clone built-in skills from the library to your organization") => make$4(
97239
97275
  "clone-built-in",
97240
97276
  { skillIds: skillIdsArg$1, org: orgOption },
97241
97277
  ({ skillIds, org }) => gen(function* () {
@@ -97251,7 +97287,7 @@ const cloneBuiltInCommand = make$4(
97251
97287
  return;
97252
97288
  }
97253
97289
  yield* log(`Cloning ${ids.length} built-in skill(s) to organization "${orgSlug}"...`);
97254
- const result = yield* api.cloneBuiltInSkills(orgSlug, ids).pipe(
97290
+ const result = yield* cloneSkills(api, orgSlug, ids).pipe(
97255
97291
  catchTag(
97256
97292
  "NotLoggedInError",
97257
97293
  (e) => gen(function* () {
@@ -97281,7 +97317,8 @@ const cloneBuiltInCommand = make$4(
97281
97317
  }
97282
97318
  }
97283
97319
  })
97284
- ).pipe(withDescription("Clone built-in skills from the library to your organization"));
97320
+ ).pipe(withDescription(description));
97321
+ const cloneBuiltInCommand = makeCloneBuiltInCommand();
97285
97322
 
97286
97323
  const skillIdArg$4 = text$2({ name: "id" }).pipe(withDescription$5("Skill ID to download"));
97287
97324
  const outputOption$2 = text("output").pipe(
@@ -97858,6 +97895,14 @@ const unpublishCommand = make$4(
97858
97895
  yield* log(`Organization skill "${skillId}" was removed from the shared library.`);
97859
97896
  })
97860
97897
  ).pipe(withDescription("Remove an organization skill from the shared library"));
97898
+ const adminBuiltInCommand = makeBuiltInCommand(
97899
+ (api, orgSlug, options) => api.listAdminBuiltInSkills(orgSlug, options),
97900
+ "List built-in skills available to an AI operator"
97901
+ );
97902
+ const adminCloneBuiltInCommand = makeCloneBuiltInCommand(
97903
+ (api, orgSlug, sourceSkillIds) => api.cloneAdminBuiltInSkills(orgSlug, sourceSkillIds),
97904
+ "Clone built-in skills as an AI operator"
97905
+ );
97861
97906
  const adminSkillCommandDescription = "Admin skill management (list, get, upload, download, delete, publish, unpublish, built-in, clone-built-in, github)";
97862
97907
  const adminSkillSubcommands = [
97863
97908
  listCommand$8,
@@ -97867,8 +97912,8 @@ const adminSkillSubcommands = [
97867
97912
  deleteCommand$3,
97868
97913
  publishCommand,
97869
97914
  unpublishCommand,
97870
- builtInCommand,
97871
- cloneBuiltInCommand,
97915
+ adminBuiltInCommand,
97916
+ adminCloneBuiltInCommand,
97872
97917
  githubSkillCommand
97873
97918
  ];
97874
97919
  const adminSkillCommand = make$4("skill", {}).pipe(
@@ -43,10 +43,10 @@
43
43
  | ORG-02 | org | `aipctl org capabilities`, `aipctl org settings`, `aipctl org exit` | `getOrgCapabilities`, `getUserSettings`, `exitOrg` | api | `GET`, `GET`, `POST` | organization v1 `/api/cli/v1/organizations/{organizationSlug}/capabilities`; user-global v1 `/api/cli/v1/me/settings`; legacy `/api/web/organizations/slug/{organizationSlug}/exit` | none | local `OrgCapabilities`, `UserSettings`, `void` | read paths: `resource/oas/modules/backend/cli/openapi.yaml`; exit: `resource/oas/duplo-web.yaml` | read generated `CliOrganizationApi`/`CliV1Api`; exit legacy generated | `legacy-generated` | N/A | `migrate` | owner=`backend`; rationale=`capabilities is organization-owned, settings remains user-global, and exit remains a Wave B mutation`; removal condition=`exit receives its dedicated v1 mutation contract`; incorporation plan=`preserve current membership authorization and non-replay behavior` | task 3.3 | `src/core/api.test.ts` verifies v1 read paths and settings ownership; org tests cover response/error behavior |
44
44
  | P0-ORG-01 | org-member | `aipctl org members`; `aipctl admin org invite`, `aipctl admin org remove-member`, `aipctl admin org update-member-role` | `listOrgMembers`, `inviteOrgMembers`, `removeOrgMembers`, `updateOrgMemberRoles` | api | `GET`, `POST`, `DELETE`, `PUT` | list v1 `/api/cli/v1/organizations/{organizationSlug}/members`; mutations remain legacy `/invite`; `/members` | list uses stable CLI member response; mutation bodies preserve `{ emails, role }` and `{ members, role? }` | local `ListMembersResponse`, `InviteOrgMembersResponse`, `void` | list: `resource/oas/modules/backend/cli/openapi.yaml`; mutation: `resource/oas/duplo-web.yaml` | list generated `CliOrganizationApi`; mutation legacy generated | `legacy-generated` | `P0` | `legacy-exception` | owner=`backend`; rationale=`member read is migrated while admin mutations remain Wave B`; removal condition=`invite/remove/role receive dedicated v1 contracts`; incorporation plan=`reuse current admin authorization, audit, stale target validation, and non-replay semantics` | task 3.3 | `src/core/api.test.ts`, `commands/org/members.test.ts`, `commands/org/org-member.test.ts`; covers v1 member read and legacy mutation bodies |
45
45
  | P0-ORG-02 | org-admin | hidden from public beta (`aipctl admin org invite-link`, `gen-invite-link`, `revoke-invite-link` removed from command tree) | removed from public surface | api | `GET`, `POST`, `DELETE` | previously `/api/web/organizations/slug/{organizationSlug}/invite/link` | none | hidden | latest: `resource/oas/modules/backend/organization/openapi.yaml` (`/invite/link/v2`); legacy: `resource/oas/duplo-web.yaml` (`/invite/link`) | latest `frontend/packages/api/src/out/modules/organization-types.ts#paths["/api/web/organizations/slug/{organizationSlug}/invite/link/v2"]`; legacy `frontend/packages/api/src/out/user-types.ts#paths["/api/web/organizations/slug/{organizationSlug}/invite/link"]` | `oas-source-only` | `P0` | `hide` | owner=`backend organization module`; rationale=`v2 exists but current CLI UX contract for GET/DELETE parity is not approved for public beta`; removal condition=`public beta continues hiding until a reviewed v2 CLI UX contract exists`; incorporation plan=`reintroduce only after independent organization contract/build and public UX review` | N/A | updated admin org help smoke + command-tree regression to ensure trio is absent |
46
- | SKILL-01 | skill | `aipctl skill list`, `aipctl skill get`, `aipctl skill delete` | `listSkills`, `getSkill`, `deleteSkill` | api | `GET`, `GET`, `DELETE` | `/api/web/organizations/slug/{organizationSlug}/skills`; `/skills/{skillId}` | query `{ count,cursor,search }`; detail query `{ maxFiles }` | local `ListSkillsResponse`, `SkillDetail`, `void` | `resource/oas/modules/backend/organization/skills/openapi.yaml` | `frontend/packages/api/src/out/modules/organization-skills-types.ts#paths["/api/web/organizations/slug/{organizationSlug}/skills"]`, `#paths["/api/web/organizations/slug/{organizationSlug}/skills/{skillId}"]` | `generated-ready` | N/A | `keep` | none | N/A | existing `commands/skill/*.test.ts`; needed path/type snapshot once independent generation exists |
46
+ | SKILL-01 | skill | `aipctl skill list`, `aipctl skill get`, `aipctl skill delete` | `listSkills`, `getSkill`, `deleteSkill` | api | `GET`, `GET`, `DELETE` | `/api/cli/v1/organizations/{organizationSlug}/skills`; `/api/cli/v1/organizations/{organizationSlug}/skills/{skillId}` | query `{ count,cursor,search }`; detail query `{ maxFiles }` | local `ListSkillsResponse`, `SkillDetail`, `void` | `resource/oas/modules/backend/cli/openapi.yaml` → `v1_skills*.yaml` (DTO aliases to organization Skills OAS) | `backend/api/.../cli/generated/api/CliSkillApi.kt`; domain DTOs remain `com.querypie.duplo.api.skills.generated.model.*` | `generated-ready` | `P0` | `migrate` | none; `CliSkillApiController` delegates to the existing member authorization/audit controller | `AI-6344` | `src/core/api.test.ts`, `CliSkillDriveApiControllerTest`, Gateway registry/contract parity; production CLI and local 403/404 checks (401/429 remain common gates) |
47
47
  | SKILL-02 | skill | `aipctl skill upload` | `createUploadUrl` → `uploadToS3` → `completeUpload` (`abortUpload` on failure) | api + external | `POST` → `PUT` → `POST` | `/skills/upload-url`; external presigned `uploadUrl`; `/skills/{skillId}/complete` | local upload request `{ filename,fileSize,mimeType }`, file buffer, complete body `{ overwrite }` | `CreateSkillUploadUrlResponse`, external `void`, `CompleteSkillUploadResponse` | `resource/oas/modules/backend/organization/skills/openapi.yaml` | `frontend/packages/api/src/out/modules/organization-skills-types.ts#paths["/api/web/organizations/slug/{organizationSlug}/skills/upload-url"]`, `#paths["/api/web/organizations/slug/{organizationSlug}/skills/{skillId}/complete"]` | `external-presigned` | N/A | `keep` | none | N/A | existing skill upload tests; needed presigned URL + complete path snapshot |
48
- | SKILL-03 | skill | `aipctl skill library`, `aipctl skill clone`, `aipctl skill built-in`, `aipctl skill clone-built-in`; `aipctl admin skill built-in`, `aipctl admin skill clone-built-in` | `listLibrarySkills`, `cloneSkills`, `listBuiltInSkills`, `cloneBuiltInSkills` | api | `GET`, `POST`, `GET`, `POST` | `/skills/library`; `/skills/library/clone`; `/admin/.../skills/built-in-library`; `/built-in-library/clone` | list query `{ count,cursor,search/limit }`; clone body `{ sourceSkillIds[] }` | local list/clone DTOs | `resource/oas/modules/backend/organization/skills/openapi.yaml` | `frontend/packages/api/src/out/modules/organization-skills-types.ts#paths["/api/web/organizations/slug/{organizationSlug}/skills/library"]`, `#paths["/api/web/organizations/slug/{organizationSlug}/skills/library/clone"]`, `#paths["/api/web/admin/organizations/slug/{organizationSlug}/skills/built-in-library"]`, `#paths["/api/web/admin/organizations/slug/{organizationSlug}/skills/built-in-library/clone"]` | `generated-ready` | N/A | `keep` | none | N/A | existing skill extended tests; needed built-in/library path snapshot |
49
- | SKILL-04 | skill-admin | `aipctl admin skill list`, `get`, `delete`, `publish`, `unpublish` | `listAdminSkills`, `getAdminSkill`, `deleteAdminSkill`, `updateAdminSkill` | api | `GET`, `GET`, `DELETE`, `PATCH` | `/api/web/admin/organizations/slug/{organizationSlug}/skills`; `/skills/{skillId}` | list query `{ pageNumber,pageSize,search }`; publish toggle body `{ availableInLibrary: boolean }` | local `ListAdminSkillsResponse`, `SkillDetail`, `void` | `resource/oas/modules/backend/organization/skills/openapi.yaml` | `frontend/packages/api/src/out/modules/organization-skills-types.ts#paths["/api/web/admin/organizations/slug/{organizationSlug}/skills"]`, `#paths["/api/web/admin/organizations/slug/{organizationSlug}/skills/{skillId}"]` | `generated-ready` | N/A | `keep` | none | N/A | existing `commands/admin/skill.test.ts`; needed admin list/detail/update path snapshot |
48
+ | SKILL-03 | skill | `aipctl skill library`, `aipctl skill clone`, `aipctl skill built-in`, `aipctl skill clone-built-in`; `aipctl admin skill built-in`, `aipctl admin skill clone-built-in` | `listLibrarySkills`, `cloneSkills`, `listBuiltInSkills`, `cloneBuiltInSkills`, `listAdminBuiltInSkills`, `cloneAdminBuiltInSkills` | api | `GET`, `POST`, `GET`, `POST`, `GET`, `POST` | `/api/cli/v1/organizations/{organizationSlug}/skills/library`; `/skills/library/clone`; `/skills/built-in`; `/skills/built-in/clone`; `/admin/skills/built-in`; `/admin/skills/built-in/clone` | list query `{ count,cursor,search/limit }`; clone body `{ sourceSkillIds[] }` | local list/clone DTOs | `resource/oas/modules/backend/cli/openapi.yaml` → `v1_skills_library*`, `v1_skills_built-in*`, `v1_admin_skills_built-in*` (aliases to organization Skills OAS) | `backend/api/.../cli/generated/api/CliSkillApi.kt`; domain DTOs remain `com.querypie.duplo.api.skills.generated.model.*` | `generated-ready` | `P0` | `migrate` | none; built-in execution reuses the existing AI-operator authorization owner while CLI API methods preserve general/admin operation identity | `AI-6344` | existing skill extended/admin tests, API path tests, adapter test, Gateway registry/contract parity and production CLI clone/list checks |
49
+ | SKILL-04 | skill-admin | `aipctl admin skill list`, `get`, `delete`, `publish`, `unpublish` | `listAdminSkills`, `getAdminSkill`, `deleteAdminSkill`, `updateAdminSkill` | api | `GET`, `GET`, `DELETE`, `PATCH` | `/api/cli/v1/organizations/{organizationSlug}/admin/skills`; `/admin/skills/{skillId}` | list query `{ pageNumber,pageSize,search }`; publish toggle body `{ availableInLibrary: boolean }` | local `ListAdminSkillsResponse`, `SkillDetail`, `void` | `resource/oas/modules/backend/cli/openapi.yaml` → `v1_admin_skills*.yaml` (DTO aliases to organization Skills OAS) | `backend/api/.../cli/generated/api/CliSkillApi.kt`; domain DTOs remain `com.querypie.duplo.api.skills.generated.model.*` | `generated-ready` | `P0` | `migrate` | none; `CliSkillApiController` delegates AI-operator authorization, workflow and audit owner | `AI-6344` | existing `commands/admin/skill.test.ts`, adapter test, Gateway parity; production admin list/publish/delete and 403/429 checks |
50
50
  | SKILL-05 | skill-admin | `aipctl skill download`; `aipctl admin skill upload`, `aipctl admin skill download`, `aipctl admin skill github inspect/import/sync/credentials set/clear` | `createAdminUploadUrl` → `uploadToS3` → `completeAdminUpload` (`abortAdminUpload` on failure); `getSkillDownloadUrl` → `downloadFromUrl`; `inspectGitHubSkillSource`, `prepareGitHubSkill`, `syncPrepareGitHubSkill`, `updateGitHubSkillCredentials` | api + external | `POST`/`PUT`/`POST`; `POST`/`GET`; `POST`, `POST`, `POST`, `PATCH` | `/admin/.../skills/upload-url`; `/admin/.../skills/download-url`; `/admin/.../skills/github-repo-inspect`; `/github-prepare`; `/{skillId}/sync-prepare`; `/{skillId}/github-credentials` | upload/download local bodies; GitHub inspect/prepare `{ url, pat? }`; credentials `{ pat or null }` | upload/download/GitHub admin skill DTOs | `resource/oas/modules/backend/organization/skills/openapi.yaml` | `frontend/packages/api/src/out/modules/organization-skills-types.ts#paths["/api/web/admin/organizations/slug/{organizationSlug}/skills/upload-url"]`, `#paths["/api/web/admin/organizations/slug/{organizationSlug}/skills/download-url"]`, `#paths["/api/web/admin/organizations/slug/{organizationSlug}/skills/github-repo-inspect"]`, `#paths["/api/web/admin/organizations/slug/{organizationSlug}/skills/github-prepare"]`, `#paths["/api/web/admin/organizations/slug/{organizationSlug}/skills/{skillId}/sync-prepare"]`, `#paths["/api/web/admin/organizations/slug/{organizationSlug}/skills/{skillId}/github-credentials"]` | `external-presigned` | N/A | `keep` | none | N/A | existing `commands/admin/skill.test.ts`, `commands/skill/skill-extended.test.ts`; needed GitHub admin skill contract fixtures |
51
51
  | P1-SKILL-01 | skill-docs | `aipctl skill get --json`, `aipctl admin skill get --json`, `aipctl admin skill github import/sync --json` | same owners as `SKILL-01`/`SKILL-05` | api | same as upstream rows | same as upstream rows | runtime/JSON/help/docs now align on `source`, `origin`, `updateAvailable`, `secretsAvailable`, GitHub metadata, `secretKeys`; stale `version` and file `size` removed | JSON/help/docs contract for skill objects | `resource/oas/modules/backend/organization/skills/openapi.yaml` | `frontend/packages/api/src/out/modules/organization-skills-types.ts#components[...]` | `generated-ready` | `P1` | `keep` | none | N/A | updated `src/core/api.test.ts`, `commands/skill/skill.test.ts`, `commands/skill/skill-extended.test.ts`, `commands/admin/skill.test.ts`, `commands/json-format.test.ts`, `../docs/how-to/aip-cli-usage.md`; covers JSON/help/docs field sync |
52
52
  | P0-AGENT-01 | agent-user | `aipctl agent list`, `aipctl agent get` | `listInstalledAgents`, `getInstalledAgent` | api | `GET`, `GET` | `/api/cli/v1/organizations/{organizationSlug}/agents`; `/{installedAgentId}` | preserves user read semantics and maps `list`/`installedAgentId` to local `data`/`id` | local `ListInstalledAgentsResponse`, `{ data: InstalledAgentDetail }` | `resource/oas/modules/backend/cli/openapi.yaml`; shared DTO refs from `resource/oas/duplo.yaml` | `backend/api/.../cli/generated/api/CliAgentApi.kt` | `generated-ready` | `P0` | `migrate` | none; dedicated adapter delegates to the working `AgentsApiController` bean | `AI-6344` | `src/core/api.test.ts`, `CliAgentApiControllerTest`, Gateway registry/admission parity; real local production CLI list/get JSON |
@@ -55,7 +55,7 @@
55
55
  | P1-CHAT-01 | agent-chat | `aipctl agent chat` | `agentNewChat`, `agentChat`, local `parseSseStream` | chat | `POST` + SSE | `/librechat/api/{orgSlug}/agent/new-chat`; `/librechat/api/{orgSlug}/agent/chat` | request body `{ agentInstallationId or conversationId, text, files: [] }` | local `AgentChatResult { text, conversationId }` parsed from SSE event fields `final`, `conversation.conversationId`, `responseMessage` | path source: `resource/oas/chat.yaml` | `frontend/packages/api/src/out/chat-types.ts#paths["/librechat/api/{orgSlug}/agent/new-chat"]`, `#paths["/librechat/api/{orgSlug}/agent/chat"]` | `stream-exception` | `P1` | `legacy-exception` | owner=`chat backend agent SSE stream contract`; rationale=`path exists in OpenAPI but final SSE event payload is still out-of-band`; removal condition=`official SSE event schema or versioned fixture is published with the chat contract`; incorporation plan=`bind CLI parser/tests to the published SSE fixture and drop local-only stream assumptions` | N/A | updated `src/core/api.test.ts`; covers content[] text, fallback text, malformed event skip, and no-final-event failure |
56
56
  | P1-AUTO-01 | automation | `aipctl automation list`, `get`, `create`, `update`, `delete`, `run`, `enable`, `disable`, `history`, `cancel`, `webhook-regen`, `triggers` | `listAutomations`, `getAutomation`, `createAutomation`, `updateAutomation`, `deleteAutomation`, `runAutomation`, `updateAutomationStatus`, `listAutomationHistories`, `cancelAutomationHistory`, `regenerateWebhookUrl`, `getTriggerProviderMetadata` | api | `GET`, `POST`, `PUT`, `DELETE` mix | `/api/web/organizations/slug/{organizationSlug}/automations...`; `/automation-trigger-providers/.../metadata` | automation pagination normalized to `{ page, pageSize, total }`; metadata calls now use provider-specific OAS paths and required args (`accountId`, `folderId`, `serviceType`) | local automation DTOs + webhook URL DTO | `resource/oas/modules/backend/automation/user/openapi.yaml` | `frontend/packages/api/src/out/modules/automation-user-types.ts#paths[...]` | `generated-ready` | `P1` | `keep` | none | N/A | updated `src/core/api.test.ts`, `commands/automation/automation.test.ts`, `commands/json-format.test.ts`; covers official pagination fields, provider-specific metadata routes, and unsupported-provider preflight failure |
57
57
  | CHAT-01 | chat-readonly | `aipctl chat list`, `aipctl chat get`, `aipctl chat messages` | `listConversations`, `getConversation`, `listMessages` | chat | `GET` | `/librechat/api/{orgSlug}/convos`; `/convos/{conversationId}`; `/messages/{conversationId}` | list sends no default query; optional cursor only (`?cursor=...`). `pageSize`/`sortBy`/`sortDirection` defaults removed due deployed Chat validation drift | local `ListConversationsResponse`, `ConversationDetail`, `ChatMessage[]` | `resource/oas/chat.yaml` | `frontend/packages/api/src/out/chat-types.ts#paths["/librechat/api/{orgSlug}/convos"]`, `#paths["/librechat/api/{orgSlug}/convos/{conversationId}"]`, `#paths["/librechat/api/{orgSlug}/messages/{conversationId}"]` | `legacy-generated` | N/A | `keep` | owner=`chat backend`; rationale=`PROD rejects CLI default pageSize/sort query while no-query and cursor-only read path are safe`; removal condition=`chat deployed contract publishes and accepts explicit pagination query for public CLI`; incorporation plan=`reintroduce explicit pagination only after OAS/source/deployed validation align` | N/A | updated `src/core/api.test.ts`, existing `commands/chat/chat.test.ts`; covers default no-query and cursor-only URL |
58
- | P0-DRIVE-01 | drive | `aipctl drive list`, `download`, `delete`, `mkdir`, `rename`, `usage` | `listDriveEntries`, `getDriveDownloadUrl`, `deleteDriveEntries`, `createDriveFolder`, `renameDriveEntry`, `getDriveUsage`, `downloadFromUrl` | api + external | `GET`, `POST`, `POST`, `POST`, `PATCH`, `GET`, external `GET` | `/drive/entries?count=200`; `/drive/entries/download-url`; `/drive/entries/delete`; `/drive/entries/{entryId}`; `/drive/usage` | official list `{ path, hasNext, nextCursor, items }`; delete body `{ entryIds[] }`; rename body `{ newName }`; download body `{ entryIds: [id] }` | official drive list/download DTOs + external file buffer | `resource/oas/modules/backend/organization/storage/openapi.yaml` | `frontend/packages/api/src/out/modules/organization-storage-types.ts#paths["/api/web/organizations/slug/{organizationSlug}/drive/entries"]`, `#paths["/api/web/organizations/slug/{organizationSlug}/drive/entries/download-url"]`, `#paths["/api/web/organizations/slug/{organizationSlug}/drive/entries/{entryId}"]`, `#paths["/api/web/organizations/slug/{organizationSlug}/drive/entries/delete"]`, `#paths["/api/web/organizations/slug/{organizationSlug}/drive/usage"]` | `generated-ready` | `P0` | `migrate` | none | `AIP-PUB-05` | updated `src/core/api.test.ts`, `commands/drive/drive.test.ts`; covers paged list shape, batch download-url request, DTO field rename (`sizeBytes`, `modifiedAt`) |
58
+ | P0-DRIVE-01 | drive | `aipctl drive list`, `download`, `delete`, `mkdir`, `rename`, `usage` | `listDriveEntries`, `getDriveDownloadUrl`, `deleteDriveEntries`, `createDriveFolder`, `renameDriveEntry`, `getDriveUsage`, `downloadFromUrl` | api + external | `GET`, `POST`, `POST`, `POST`, `PATCH`, `GET`, external `GET` | CLI-managed basic CRUD: `/api/cli/v1/organizations/{organizationSlug}/drive/entries?count=200`, `/drive/entries/delete`, `/drive/folders`, `/drive/entries/{entryId}`, `/drive/usage`; download/upload remain Web/presigned | official list `{ path, hasNext, nextCursor, items }`; delete body `{ entryIds[] }`; mkdir optional `parentId`; rename body `{ newName }`; download body `{ entryIds: [id] }` | official drive list/download DTOs + external file buffer | `resource/oas/modules/backend/cli/openapi.yaml` → `v1_drive_*.yaml` (DTO aliases to organization Storage OAS); download/upload remain `resource/oas/modules/backend/organization/storage/openapi.yaml` | `backend/api/.../cli/generated/api/CliDriveApi.kt`; domain DTOs remain `com.querypie.duplo.api.drive.controller.generated.model.*` | `generated-ready` | `P0` | `migrate` | none; `CliDriveApiController` delegates membership, capacity and audit owner | `AI-6344` | `src/core/api.test.ts`, `CliSkillDriveApiControllerTest`, Gateway registry/contract parity; production CRUD/usage and local 403/404 checks (401/429 remain common gates) |
59
59
  | P0-DRIVE-02 | drive | `aipctl drive upload` | `createDriveUploadUrl` → `uploadToS3` → `completeDriveUpload` | api + external | `POST` → `PUT` → `POST` | `/drive/entries/upload-url`; external presigned `uploadUrl`; `/drive/entries/{entryId}/complete` | official upload request `{ parentId: null, filename, fileSize, mimeType }` | official `CreateDriveUploadUrlResponse { id, filename, uploadUrl, headers }` then `void` | `resource/oas/modules/backend/organization/storage/openapi.yaml` | `frontend/packages/api/src/out/modules/organization-storage-types.ts#paths["/api/web/organizations/slug/{organizationSlug}/drive/entries/upload-url"]`, `#paths["/api/web/organizations/slug/{organizationSlug}/drive/entries/{entryId}/complete"]` | `generated-ready` | `P0` | `migrate` | none | `AIP-PUB-05` | updated `src/core/api.test.ts`, `commands/drive/drive.test.ts`; covers `parentId: null` request and response `id`/`filename` usage |
60
60
  | P1-INT-01 | integration | `aipctl integration list`, `test`; `aipctl admin integration get`, `update`, `delete`, `tools`, `available` | `listIntegrations`, `testIntegration`, `getIntegration`, `updateIntegration`, `deleteIntegration`, `listIntegrationTools`, `listConfigurableIntegrations` | api | `GET`, `POST`, `GET`, `PUT`, `DELETE`, `GET`, `GET` | `/api/web/integrations/v2`; `/api/web/integration/{integrationId}/test`; `/api/web/admin/organizations/slug/{organizationSlug}/integrations/{integrationId}`; `/{integrationId}/tools`; `/configurable-integrations` | top-level list/test and admin detail/available now follow current/legacy OAS path+body+DTO where available; admin update/delete still accept untyped legacy mutation body/path until admin mutation contract lands | local `Integration`, `IntegrationDetail`, `IntegrationToolsResponse`, `ConfigurableIntegration[]` | legacy admin paths: `resource/oas/duplo-web.yaml`; partial modern source: `resource/oas/modules/backend/integration/openapi.yaml` (does not cover these admin list/CRUD/tools paths) | legacy `frontend/packages/api/src/out/user-types.ts#paths["/api/web/admin/organizations/slug/{organizationSlug}/integrations"]`, `#paths["/api/web/admin/organizations/slug/{organizationSlug}/configurable-integrations"]`; partial current `frontend/packages/api/src/out/modules/integration-types.ts` lacks these admin list/CRUD/tools paths | `legacy-generated` | `P1` | `legacy-exception` | owner=`backend app Integrations / duplo-web legacy OAS`; rationale=`public beta keeps integration list/test/admin detail/tools/available, but admin update/delete still lack an approved current contract outside legacy duplo-web`; removal condition=`independent contract covers user list/test plus admin CRUD/tools/available`; incorporation plan=`extend integration module or independent contract for list/test/admin CRUD/tools/available, then retire legacy path/body mappings and delete/update exception` | N/A | updated `src/core/api.test.ts`, `commands/integration/integration.test.ts`; covers v2 list query, test body `{ organizationSlug }`, admin detail/tools DTO fields, configurable wrapper unwrap, and pinned update/delete legacy paths |
61
61
  | P0-MODEL-01 | model | `aipctl model list` | `listModels` | chat | `GET` | `/api/cli/v1/organizations/{orgSlug}/models` | none | `{ models: string[], defaultModel? }` is mapped into `Model[]` and preserves active standard, custom/BYOK, and `etc` catalog IDs | code-first OAS: `chat/packages/chat/src/modules/cli-model-exec/interface/cli-model-exec-v1.interface.controller.ts` | runtime Swagger contract; the existing Web model endpoint remains an independent compatibility reference | `oas-source-only` | `P0` | `migrate` | owner=`Chat CliModelExecV1InterfaceController/ModelInternalService`; rationale=`the dedicated route reuses the existing organization-authorized model domain catalog instead of the narrowed model-exec SharedModel catalog`; removal condition=`none for the CLI route; Web migration is a separate work item`; incorporation plan=`keep the CLI path and capability/registry parity tests aligned with the code-first Chat contract` | `AIP-PUB-05` | `src/core/api.test.ts` verifies the dedicated path, production capability gate, default mapping, and preservation of custom/BYOK and `etc` IDs; Chat service/controller tests verify domain delegation and Swagger extensions |
@@ -68,15 +68,18 @@
68
68
 
69
69
  ## 공통 CLI request quota 오류 계약
70
70
 
71
- `GA-DIRECT` 업무 호출과 `GET /api/cli/v1/capabilities`는 Gateway request-rate admission을 통과한다. Gateway-owned source 계약은 `resource/oas/modules/backend-gateway/me/components/schemas/CliQuotaErrorResponse.yaml`과 `CliQuotaErrorDetails.yaml`이다.
71
+ `GA-DIRECT` 업무 호출과 명시적 discovery/진단용 `GET /api/cli/v1/capabilities`는 각각 Gateway request-rate admission을 통과한다. production CLI는 업무마다 capability를 선조회하지 않는다. 로컬 release manifest는 artifact에 없는 명령을 차단하고, 실제 업무 endpoint의 Gateway registry가 최신 server release/rollout/quota를 authoritative하게 검사한다. Gateway-owned source 계약은 `resource/oas/modules/backend-gateway/me/components/schemas/CliQuotaErrorResponse.yaml`과 `CliQuotaErrorDetails.yaml`이다.
72
72
 
73
73
  | HTTP | code | CLI 처리 |
74
74
  | --- | --- | --- |
75
75
  | 429 | `CLI_QUOTA_EXCEEDED` | `limitKind=request_rate`, quota class, denying scope, request ID를 보존한다. 양수 refill로 계산 가능한 경우에만 `Retry-After`, `resetAt`을 보존한다. 자동 retry는 하지 않는다. |
76
76
  | 503 | `CLI_QUOTA_POLICY_UNAVAILABLE` | Backend effective-policy auth/timeout/schema/freshness 실패. stale/default policy로 대체하거나 자동 retry하지 않는다. |
77
77
  | 503 | `CLI_QUOTA_LIMITER_UNAVAILABLE` | Redis timeout/script/result 실패. 유료·mutation 요청을 자동 retry하지 않는다. |
78
+ | 429 | `CLI_TEXT_DAILY_LIMIT` | `limitKind=daily`, `quotaClass=text-execution`, 사용자 scope와 request ID를 보존한다. 유효한 UTC reset 시각과 그 시각에서 계산한 retry만 노출하며 자동 retry하지 않는다. |
79
+ | 429 | `CLI_TEXT_USER_CONCURRENCY_LIMIT` | `limitKind=concurrent`, `quotaClass=text-execution`, 사용자 scope와 request ID만 보존한다. permit 해제 시각을 알 수 없으므로 `Retry-After`와 `resetAt`을 만들거나 전달하지 않는다. |
80
+ | 429 | `CLI_TEXT_ORGANIZATION_CONCURRENCY_LIMIT` | `limitKind=concurrent`, `quotaClass=text-execution`, 조직 scope와 request ID만 보존한다. permit 해제 시각을 알 수 없으므로 `Retry-After`와 `resetAt`을 만들거나 전달하지 않는다. |
78
81
 
79
- CLI `ApiError`는 위 세 code에서 allowlist된 quota detail만 보존하며 서버가 추가로 보낸 조직/사용자 ID, raw policy, Redis key는 노출하지 않는다. 회귀 테스트는 `src/core/api.test.ts`와 Gateway `test/cli/quota/**`에 있다.
82
+ CLI `ApiError`는 위 여섯 code에서 allowlist된 quota detail만 보존하며 서버가 추가로 보낸 조직/사용자 ID, raw policy, Redis key는 노출하지 않는다. Gateway는 `code`, Chat은 `errorCode`를 사용하지만 CLI에서 같은 진단 구조로 정규화한다. 회귀 테스트는 `src/core/api.test.ts`, Chat `cli-text-execution.internal.service.spec.ts`, Gateway `test/cli/quota/**`에 있다.
80
83
 
81
84
  ## Queue IDs for follow-up
82
85
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aipctl",
3
- "version": "2026.9.8",
3
+ "version": "2026.9.10",
4
4
  "description": "QueryPie AIP and MCP CLI tool",
5
5
  "author": "QueryPie",
6
6
  "license": "UNLICENSED",
@@ -22,8 +22,8 @@
22
22
  "node": ">=24.18.0"
23
23
  },
24
24
  "metadata": {
25
- "contentHash": "fc535c50b1c26702237a8dbfd683f04ddaec6b84c369c308b9714d1e65c5fc69",
26
- "wrapperHash": "69f4f9d9e2ed5283318f3b770870f094ae315b9cc579ad426203224c66624461",
25
+ "contentHash": "347543a4b224c8b9c8ff935b76027e974cb8b4fe60194c8fa521914f08fd4547",
26
+ "wrapperHash": "78654f7e6577f5ff63efac889f31483bdd5bcd6f34f474784fd356a89dc46bdf",
27
27
  "nativeHash": "006b08c3e391f4571d9b7c0f12d684fe2448106259e7e0581b10f9be362452e9"
28
28
  },
29
29
  "optionalDependencies": {