aipctl 2026.9.4 → 2026.9.6
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 +154 -19
- package/docs/api-contract-inventory.md +12 -0
- package/package.json +3 -3
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$
|
|
781
|
+
const isRecord$3 = 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$
|
|
25161
|
+
if (!isRecord$3(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.
|
|
89673
|
+
const VERSION = "2026.9.6".length > 0 ? "2026.9.6" : "0.0.0";
|
|
89674
89674
|
const BUILD_ENV = injectedBuildEnv;
|
|
89675
89675
|
const INCLUDE_NATIVE_LOGS = BUILD_ENV !== "prod";
|
|
89676
89676
|
|
|
@@ -89812,10 +89812,18 @@ const implementedCommandOperationIds = /* @__PURE__ */ new Set([
|
|
|
89812
89812
|
"listCliModelExecModels",
|
|
89813
89813
|
"createCliModelChatExecution"
|
|
89814
89814
|
]);
|
|
89815
|
+
const releaseEnabledCommandOperationIds = /* @__PURE__ */ new Set([
|
|
89816
|
+
"getCliMe",
|
|
89817
|
+
"getCliUserSettings",
|
|
89818
|
+
"listCliOrganizations",
|
|
89819
|
+
"getCliOrganizationCapabilities",
|
|
89820
|
+
"listCliOrganizationMembers",
|
|
89821
|
+
"listCliModelExecModels"
|
|
89822
|
+
]);
|
|
89815
89823
|
const direct = (commands, operation) => commands.map((command) => ({
|
|
89816
89824
|
command,
|
|
89817
89825
|
tier: "GA_DIRECT",
|
|
89818
|
-
availability: implementedCommandOperationIds.has(operation.operationId ?? "") ? "IMPLEMENTED" : "
|
|
89826
|
+
availability: !implementedCommandOperationIds.has(operation.operationId ?? "") ? "PLANNED" : releaseEnabledCommandOperationIds.has(operation.operationId ?? "") ? "IMPLEMENTED" : "GATED",
|
|
89819
89827
|
...operation
|
|
89820
89828
|
}));
|
|
89821
89829
|
const candidate = (commands, owner, authBoundary, requiredGates) => commands.map((command) => ({
|
|
@@ -90554,6 +90562,7 @@ const CUSTOMER_AVAILABILITIES = /* @__PURE__ */ new Set([
|
|
|
90554
90562
|
"AVAILABLE"
|
|
90555
90563
|
]);
|
|
90556
90564
|
const isCustomerAvailable = (entry) => CUSTOMER_AVAILABILITIES.has(entry.availability);
|
|
90565
|
+
const normalizeCliArgCase = (arg) => arg.toLowerCase();
|
|
90557
90566
|
const commandSegments = (command) => command.split(" ");
|
|
90558
90567
|
const PROD_DISABLED_BUILT_INS = /* @__PURE__ */ new Set(["--completions", "--wizard"]);
|
|
90559
90568
|
const isCommandPrefix = (entry, args) => {
|
|
@@ -90579,11 +90588,12 @@ const getUnavailableCommandMessage = (args, buildEnv) => {
|
|
|
90579
90588
|
if (buildEnv !== "prod" || args.length === 0) {
|
|
90580
90589
|
return null;
|
|
90581
90590
|
}
|
|
90582
|
-
const
|
|
90591
|
+
const normalizedArgs = args.map(normalizeCliArgCase);
|
|
90592
|
+
const disabledBuiltIn = normalizedArgs.find((arg) => PROD_DISABLED_BUILT_INS.has(arg));
|
|
90583
90593
|
if (disabledBuiltIn) {
|
|
90584
90594
|
return formatUnavailableOptionMessage(disabledBuiltIn);
|
|
90585
90595
|
}
|
|
90586
|
-
const commandArgs = commandArgsForAvailability(
|
|
90596
|
+
const commandArgs = commandArgsForAvailability(normalizedArgs);
|
|
90587
90597
|
const entry = commandManifest.filter((candidate) => isCommandPrefix(candidate, commandArgs)).sort((left, right) => commandSegments(right.command).length - commandSegments(left.command).length).at(0);
|
|
90588
90598
|
if (entry) {
|
|
90589
90599
|
return isCustomerAvailable(entry) ? null : formatUnavailableMessage(entry.command);
|
|
@@ -90622,14 +90632,14 @@ class InvalidAppUrlError extends Error {
|
|
|
90622
90632
|
this.rawValue = rawValue;
|
|
90623
90633
|
}
|
|
90624
90634
|
}
|
|
90625
|
-
function isRecord$
|
|
90635
|
+
function isRecord$2(value) {
|
|
90626
90636
|
return typeof value === "object" && value !== null;
|
|
90627
90637
|
}
|
|
90628
90638
|
function optionalString$1(value) {
|
|
90629
90639
|
return typeof value === "string" ? value : void 0;
|
|
90630
90640
|
}
|
|
90631
90641
|
function parseCurrentOrganization(value) {
|
|
90632
|
-
if (!isRecord$
|
|
90642
|
+
if (!isRecord$2(value)) {
|
|
90633
90643
|
return void 0;
|
|
90634
90644
|
}
|
|
90635
90645
|
const id = optionalString$1(value["id"]);
|
|
@@ -90641,7 +90651,7 @@ function parseCurrentOrganization(value) {
|
|
|
90641
90651
|
return { id, slug, name };
|
|
90642
90652
|
}
|
|
90643
90653
|
function normalizeLoadedConfig(value) {
|
|
90644
|
-
if (!isRecord$
|
|
90654
|
+
if (!isRecord$2(value)) {
|
|
90645
90655
|
return {};
|
|
90646
90656
|
}
|
|
90647
90657
|
return {
|
|
@@ -92875,11 +92885,11 @@ const CREDENTIALS_FILE = path__namespace.join(CONFIG_DIR$1, "credentials.json");
|
|
|
92875
92885
|
const CREDENTIALS_LOCK_FILE = path__namespace.join(CONFIG_DIR$1, "credentials.lock");
|
|
92876
92886
|
class CredentialServiceTag extends Tag$1("CredentialService")() {
|
|
92877
92887
|
}
|
|
92878
|
-
function isNonEmptyString(value) {
|
|
92888
|
+
function isNonEmptyString$1(value) {
|
|
92879
92889
|
return typeof value === "string" && value.length > 0;
|
|
92880
92890
|
}
|
|
92881
92891
|
function hasOAuthClient(credentials) {
|
|
92882
|
-
return isNonEmptyString(credentials?.clientId);
|
|
92892
|
+
return isNonEmptyString$1(credentials?.clientId);
|
|
92883
92893
|
}
|
|
92884
92894
|
function isAccessTokenExpired(credentials, bufferMs = 0, now = Date.now()) {
|
|
92885
92895
|
if (!credentials?.expiresAt) {
|
|
@@ -92888,13 +92898,13 @@ function isAccessTokenExpired(credentials, bufferMs = 0, now = Date.now()) {
|
|
|
92888
92898
|
return credentials.expiresAt - bufferMs <= now;
|
|
92889
92899
|
}
|
|
92890
92900
|
function hasValidRefreshToken(credentials, now) {
|
|
92891
|
-
return isNonEmptyString(credentials.refreshToken) && (credentials.refreshExpiresAt === void 0 || credentials.refreshExpiresAt > now);
|
|
92901
|
+
return isNonEmptyString$1(credentials.refreshToken) && (credentials.refreshExpiresAt === void 0 || credentials.refreshExpiresAt > now);
|
|
92892
92902
|
}
|
|
92893
92903
|
function getCredentialAuthStatus(credentials, now = Date.now(), options = {}) {
|
|
92894
92904
|
if (!hasOAuthClient(credentials)) {
|
|
92895
92905
|
return "missing";
|
|
92896
92906
|
}
|
|
92897
|
-
const needsAccessRefresh = options.forceRefresh || !isNonEmptyString(credentials.accessToken) || isAccessTokenExpired(credentials, options.accessExpiryBufferMs ?? 0, now);
|
|
92907
|
+
const needsAccessRefresh = options.forceRefresh || !isNonEmptyString$1(credentials.accessToken) || isAccessTokenExpired(credentials, options.accessExpiryBufferMs ?? 0, now);
|
|
92898
92908
|
if (!needsAccessRefresh) {
|
|
92899
92909
|
return "authenticated";
|
|
92900
92910
|
}
|
|
@@ -93890,7 +93900,7 @@ function trimTrailingSlash(value) {
|
|
|
93890
93900
|
function toError$1(error, fallbackMessage) {
|
|
93891
93901
|
return error instanceof Error ? new Error(maskSensitiveText(error.message)) : new Error(maskSensitiveText(fallbackMessage));
|
|
93892
93902
|
}
|
|
93893
|
-
function isRecord(value) {
|
|
93903
|
+
function isRecord$1(value) {
|
|
93894
93904
|
return typeof value === "object" && value !== null;
|
|
93895
93905
|
}
|
|
93896
93906
|
function optionalString(value) {
|
|
@@ -93900,7 +93910,7 @@ function optionalNumber(value) {
|
|
|
93900
93910
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
93901
93911
|
}
|
|
93902
93912
|
function parseOAuthTokenResponse(value) {
|
|
93903
|
-
if (!isRecord(value)) {
|
|
93913
|
+
if (!isRecord$1(value)) {
|
|
93904
93914
|
throw new OAuthRequestError("OAuth token response was not a JSON object", void 0, "invalid_token_response");
|
|
93905
93915
|
}
|
|
93906
93916
|
const accessToken = optionalString(value["access_token"]);
|
|
@@ -94429,6 +94439,55 @@ async function acquire() {
|
|
|
94429
94439
|
}
|
|
94430
94440
|
}
|
|
94431
94441
|
|
|
94442
|
+
const SERVER_CAPABILITY_SCHEMA_VERSION = 1;
|
|
94443
|
+
class ServerCapabilityError extends Error {
|
|
94444
|
+
code;
|
|
94445
|
+
constructor(code, message) {
|
|
94446
|
+
super(message);
|
|
94447
|
+
this.name = "ServerCapabilityError";
|
|
94448
|
+
this.code = code;
|
|
94449
|
+
}
|
|
94450
|
+
}
|
|
94451
|
+
function parseEnabledServerOperations(value) {
|
|
94452
|
+
if (!isRecord(value) || value["schemaVersion"] !== SERVER_CAPABILITY_SCHEMA_VERSION) {
|
|
94453
|
+
throw new ServerCapabilityError(
|
|
94454
|
+
"CLI_CAPABILITIES_INCOMPATIBLE",
|
|
94455
|
+
"The server returned an unsupported CLI capability schema."
|
|
94456
|
+
);
|
|
94457
|
+
}
|
|
94458
|
+
const enabledOperations = value["enabledOperations"];
|
|
94459
|
+
if (!Array.isArray(enabledOperations)) {
|
|
94460
|
+
throw new ServerCapabilityError(
|
|
94461
|
+
"CLI_CAPABILITIES_INCOMPATIBLE",
|
|
94462
|
+
"The server returned an invalid CLI capability operation list."
|
|
94463
|
+
);
|
|
94464
|
+
}
|
|
94465
|
+
const operationIds = /* @__PURE__ */ new Set();
|
|
94466
|
+
for (const operation of enabledOperations) {
|
|
94467
|
+
if (!isRecord(operation) || !isNonEmptyString(operation["operationId"]) || !isNonEmptyString(operation["capability"]) || operationIds.has(operation["operationId"])) {
|
|
94468
|
+
throw new ServerCapabilityError(
|
|
94469
|
+
"CLI_CAPABILITIES_INCOMPATIBLE",
|
|
94470
|
+
"The server returned an invalid CLI capability operation."
|
|
94471
|
+
);
|
|
94472
|
+
}
|
|
94473
|
+
operationIds.add(operation["operationId"]);
|
|
94474
|
+
}
|
|
94475
|
+
return operationIds;
|
|
94476
|
+
}
|
|
94477
|
+
function requireEnabledServerOperation(enabledOperations, operationId) {
|
|
94478
|
+
if (enabledOperations.has(operationId)) return;
|
|
94479
|
+
throw new ServerCapabilityError(
|
|
94480
|
+
"CLI_OPERATION_NOT_ENABLED",
|
|
94481
|
+
`CLI operation "${operationId}" is not enabled by the server.`
|
|
94482
|
+
);
|
|
94483
|
+
}
|
|
94484
|
+
function isRecord(value) {
|
|
94485
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
94486
|
+
}
|
|
94487
|
+
function isNonEmptyString(value) {
|
|
94488
|
+
return typeof value === "string" && value.length > 0;
|
|
94489
|
+
}
|
|
94490
|
+
|
|
94432
94491
|
class ApiError extends TaggedError$1("ApiError") {
|
|
94433
94492
|
}
|
|
94434
94493
|
class NotLoggedInError extends TaggedError$1("NotLoggedInError") {
|
|
@@ -94593,12 +94652,51 @@ async function toApiError(response) {
|
|
|
94593
94652
|
errorData = await response.json();
|
|
94594
94653
|
} catch {
|
|
94595
94654
|
}
|
|
94655
|
+
const details = isCliQuotaErrorCode(errorData.code) ? parseCliQuotaErrorDetails(errorData.details) : void 0;
|
|
94656
|
+
const retryAfterSeconds = details ? parseRetryAfterSeconds(response.headers.get("retry-after")) : void 0;
|
|
94596
94657
|
return createApiError({
|
|
94597
94658
|
message: errorData.message || `HTTP ${response.status}: ${response.statusText}`,
|
|
94598
94659
|
statusCode: response.status,
|
|
94599
|
-
code: errorData.code
|
|
94660
|
+
code: errorData.code,
|
|
94661
|
+
...details ? { details } : {},
|
|
94662
|
+
...retryAfterSeconds === void 0 ? {} : { retryAfterSeconds }
|
|
94600
94663
|
});
|
|
94601
94664
|
}
|
|
94665
|
+
function isCliQuotaErrorCode(code) {
|
|
94666
|
+
return code === "CLI_QUOTA_EXCEEDED" || code === "CLI_QUOTA_POLICY_UNAVAILABLE" || code === "CLI_QUOTA_LIMITER_UNAVAILABLE";
|
|
94667
|
+
}
|
|
94668
|
+
function parseCliQuotaErrorDetails(value) {
|
|
94669
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
94670
|
+
const details = value;
|
|
94671
|
+
if (details["limitKind"] !== "request_rate") return void 0;
|
|
94672
|
+
if (typeof details["quotaClass"] !== "string" || !["bootstrap", "metadata", "text-execution", "control-read"].includes(details["quotaClass"])) {
|
|
94673
|
+
return void 0;
|
|
94674
|
+
}
|
|
94675
|
+
if (typeof details["requestId"] !== "string" || details["requestId"].length === 0) return void 0;
|
|
94676
|
+
const scope = details["scope"];
|
|
94677
|
+
if (scope !== void 0 && (typeof scope !== "string" || !["user", "organization", "bootstrap", "control-read"].includes(scope))) {
|
|
94678
|
+
return void 0;
|
|
94679
|
+
}
|
|
94680
|
+
const resetAt = details["resetAt"];
|
|
94681
|
+
if (resetAt !== void 0 && (typeof resetAt !== "string" || !Number.isFinite(Date.parse(resetAt)))) {
|
|
94682
|
+
return void 0;
|
|
94683
|
+
}
|
|
94684
|
+
const dependency = details["dependency"];
|
|
94685
|
+
if (dependency !== void 0 && dependency !== "policy" && dependency !== "limiter") return void 0;
|
|
94686
|
+
return {
|
|
94687
|
+
limitKind: "request_rate",
|
|
94688
|
+
quotaClass: details["quotaClass"],
|
|
94689
|
+
requestId: details["requestId"],
|
|
94690
|
+
...scope === void 0 ? {} : { scope },
|
|
94691
|
+
...resetAt === void 0 ? {} : { resetAt },
|
|
94692
|
+
...dependency === void 0 ? {} : { dependency }
|
|
94693
|
+
};
|
|
94694
|
+
}
|
|
94695
|
+
function parseRetryAfterSeconds(value) {
|
|
94696
|
+
if (!value || !/^\d+$/.test(value)) return void 0;
|
|
94697
|
+
const parsed = Number(value);
|
|
94698
|
+
return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : void 0;
|
|
94699
|
+
}
|
|
94602
94700
|
const makeRequest = (baseUrl, path, accessToken, options) => tryPromise({
|
|
94603
94701
|
try: async () => {
|
|
94604
94702
|
const resolvedBaseUrl = typeof baseUrl === "function" ? baseUrl() : baseUrl;
|
|
@@ -94735,7 +94833,7 @@ const parseSseStream = async (body) => {
|
|
|
94735
94833
|
}
|
|
94736
94834
|
throw new AgentChatStreamIncompleteError(hasDataEvent && sawOnlyTerminalEnvelope);
|
|
94737
94835
|
};
|
|
94738
|
-
const
|
|
94836
|
+
const makeApiServiceLive = (buildEnv) => effect$1(
|
|
94739
94837
|
ApiServiceTag,
|
|
94740
94838
|
gen(function* () {
|
|
94741
94839
|
const configService = yield* ConfigService;
|
|
@@ -94852,6 +94950,34 @@ const ApiServiceLive = effect$1(
|
|
|
94852
94950
|
});
|
|
94853
94951
|
const baseUrl = () => loadConfiguredApiBaseUrl();
|
|
94854
94952
|
const chatBaseUrl = () => loadConfiguredChatBaseUrl();
|
|
94953
|
+
const requireServerOperation = (operationId, token) => {
|
|
94954
|
+
if (buildEnv !== "prod") return _void;
|
|
94955
|
+
if (!releaseEnabledCommandOperationIds.has(operationId)) {
|
|
94956
|
+
return fail$3(createApiError({
|
|
94957
|
+
message: `CLI operation "${operationId}" is not enabled in this aipctl release.`,
|
|
94958
|
+
statusCode: 404,
|
|
94959
|
+
code: "CLI_OPERATION_NOT_ENABLED"
|
|
94960
|
+
}));
|
|
94961
|
+
}
|
|
94962
|
+
return gen(function* () {
|
|
94963
|
+
const response = yield* makeRequest(
|
|
94964
|
+
baseUrl,
|
|
94965
|
+
"/api/cli/v1/capabilities",
|
|
94966
|
+
token
|
|
94967
|
+
);
|
|
94968
|
+
yield* try_({
|
|
94969
|
+
try: () => {
|
|
94970
|
+
const enabledOperations = parseEnabledServerOperations(response);
|
|
94971
|
+
requireEnabledServerOperation(enabledOperations, operationId);
|
|
94972
|
+
},
|
|
94973
|
+
catch: (error) => createApiError({
|
|
94974
|
+
message: error instanceof Error ? error.message : "CLI server capability validation failed",
|
|
94975
|
+
statusCode: error instanceof ServerCapabilityError && error.code === "CLI_OPERATION_NOT_ENABLED" ? 404 : 503,
|
|
94976
|
+
code: error instanceof ServerCapabilityError ? error.code : "CLI_CAPABILITIES_INCOMPATIBLE"
|
|
94977
|
+
})
|
|
94978
|
+
});
|
|
94979
|
+
});
|
|
94980
|
+
};
|
|
94855
94981
|
const subscribeAgentChatStream = (orgSlug, conversationId, token) => gen(function* () {
|
|
94856
94982
|
const resolvedChatBaseUrl = yield* requireChatBaseUrlEffect();
|
|
94857
94983
|
const path = `/api/${encodeURIComponent(orgSlug)}/conversations/${encodeURIComponent(conversationId)}/stream`;
|
|
@@ -94918,10 +95044,12 @@ const ApiServiceLive = effect$1(
|
|
|
94918
95044
|
return {
|
|
94919
95045
|
listOrganizations: () => gen(function* () {
|
|
94920
95046
|
const token = yield* getAccessToken();
|
|
95047
|
+
yield* requireServerOperation("listCliOrganizations", token);
|
|
94921
95048
|
return yield* makeRequest(baseUrl, "/api/cli/v1/organizations", token);
|
|
94922
95049
|
}),
|
|
94923
95050
|
getOrganization: (slug) => gen(function* () {
|
|
94924
95051
|
const token = yield* getAccessToken();
|
|
95052
|
+
yield* requireServerOperation("listCliOrganizations", token);
|
|
94925
95053
|
const orgs = yield* makeRequest(baseUrl, "/api/cli/v1/organizations", token);
|
|
94926
95054
|
const org = orgs.find((o) => o.slug === slug);
|
|
94927
95055
|
if (!org) {
|
|
@@ -95261,6 +95389,7 @@ const ApiServiceLive = effect$1(
|
|
|
95261
95389
|
// Phase 4: Monitoring
|
|
95262
95390
|
listOrgMembers: (orgSlug) => gen(function* () {
|
|
95263
95391
|
const token = yield* getAccessToken();
|
|
95392
|
+
yield* requireServerOperation("listCliOrganizationMembers", token);
|
|
95264
95393
|
const path = `/api/cli/v1/organizations/${encodeURIComponent(orgSlug)}/members`;
|
|
95265
95394
|
const raw = yield* makeRequest(baseUrl, path, token);
|
|
95266
95395
|
return {
|
|
@@ -95286,12 +95415,14 @@ const ApiServiceLive = effect$1(
|
|
|
95286
95415
|
listModelExecModels: (orgSlug) => gen(function* () {
|
|
95287
95416
|
yield* requireChatBaseUrlEffect();
|
|
95288
95417
|
const token = yield* getAccessToken();
|
|
95418
|
+
yield* requireServerOperation("listCliModelExecModels", token);
|
|
95289
95419
|
const path = `/api/cli/v1/organizations/${encodeURIComponent(orgSlug)}/model-exec/models`;
|
|
95290
95420
|
return yield* makeRequest(chatBaseUrl, path, token);
|
|
95291
95421
|
}),
|
|
95292
95422
|
executeModelExecChat: (orgSlug, body) => gen(function* () {
|
|
95293
95423
|
yield* requireChatBaseUrlEffect();
|
|
95294
95424
|
const token = yield* getAccessToken();
|
|
95425
|
+
yield* requireServerOperation("createCliModelChatExecution", token);
|
|
95295
95426
|
const path = `/api/cli/v1/organizations/${encodeURIComponent(orgSlug)}/model-exec/chat`;
|
|
95296
95427
|
return yield* makeRequest(chatBaseUrl, path, token, {
|
|
95297
95428
|
method: "POST",
|
|
@@ -95433,6 +95564,7 @@ const ApiServiceLive = effect$1(
|
|
|
95433
95564
|
// Phase 6: Auth enhancements
|
|
95434
95565
|
getMe: () => gen(function* () {
|
|
95435
95566
|
const token = yield* getAccessToken();
|
|
95567
|
+
yield* requireServerOperation("getCliMe", token);
|
|
95436
95568
|
return yield* makeRequest(baseUrl, "/api/cli/v1/me", token);
|
|
95437
95569
|
}),
|
|
95438
95570
|
refreshToken: () => refreshAccessToken({ force: true }),
|
|
@@ -95614,11 +95746,13 @@ const ApiServiceLive = effect$1(
|
|
|
95614
95746
|
// Org capabilities & user settings
|
|
95615
95747
|
getOrgCapabilities: (orgSlug) => gen(function* () {
|
|
95616
95748
|
const token = yield* getAccessToken();
|
|
95749
|
+
yield* requireServerOperation("getCliOrganizationCapabilities", token);
|
|
95617
95750
|
const path = `/api/cli/v1/organizations/${encodeURIComponent(orgSlug)}/capabilities`;
|
|
95618
95751
|
return yield* makeRequest(baseUrl, path, token);
|
|
95619
95752
|
}),
|
|
95620
95753
|
getUserSettings: () => gen(function* () {
|
|
95621
95754
|
const token = yield* getAccessToken();
|
|
95755
|
+
yield* requireServerOperation("getCliUserSettings", token);
|
|
95622
95756
|
return yield* makeRequest(baseUrl, "/api/cli/v1/me/settings", token);
|
|
95623
95757
|
}),
|
|
95624
95758
|
// Tunnel management
|
|
@@ -95736,6 +95870,7 @@ const ApiServiceLive = effect$1(
|
|
|
95736
95870
|
};
|
|
95737
95871
|
})
|
|
95738
95872
|
);
|
|
95873
|
+
const ApiServiceLive = makeApiServiceLive(BUILD_ENV);
|
|
95739
95874
|
const ApiService = ApiServiceTag;
|
|
95740
95875
|
|
|
95741
95876
|
const orgOption = text("org").pipe(
|
|
@@ -101402,10 +101537,10 @@ const hasCustomerAvailableCommand = (path) => {
|
|
|
101402
101537
|
const formatCommandList = (path, subcommands, options) => subcommands.map((command) => [...path, getCommandName(command)]).filter((commandPath) => !options.customerOnly || hasCustomerAvailableCommand(commandPath)).map((commandPath) => ` - ${formatCanonicalPath(commandPath)}`).join("\n");
|
|
101403
101538
|
const getHelpPath = (args) => {
|
|
101404
101539
|
const lastArg = args.at(-1);
|
|
101405
|
-
if (!lastArg || !HELP_FLAGS$1.has(lastArg)) {
|
|
101540
|
+
if (!lastArg || !HELP_FLAGS$1.has(normalizeCliArgCase(lastArg))) {
|
|
101406
101541
|
return null;
|
|
101407
101542
|
}
|
|
101408
|
-
const path = args.slice(0, -1);
|
|
101543
|
+
const path = args.slice(0, -1).map(normalizeCliArgCase);
|
|
101409
101544
|
return path.every((segment) => !segment.startsWith("-")) ? path : null;
|
|
101410
101545
|
};
|
|
101411
101546
|
const renderGroupHelp = ({ path, description, subcommands }, options) => {
|
|
@@ -65,6 +65,18 @@
|
|
|
65
65
|
| TUNNEL-01 | tunnel | `aipctl admin tunnel status`, `config`, `token` | `getTunnelStatus`, `getTunnelConfig`, `createTunnelToken` | api | `GET`, `GET`, `POST` | `/api/web/edge-tunnel/v2/organizations/slug/{organizationSlug}/status`; `/config`; `/token` | none | local `TunnelStatus`, `TunnelConfig`, `void` | `resource/oas/modules/backend/tunnel/openapi.yaml` | `frontend/packages/api/src/out/modules/tunnel-types.ts#paths[...]` | `generated-ready` | N/A | `keep` | none | N/A | existing `commands/tunnel/tunnel.test.ts`; needed v2 path snapshot |
|
|
66
66
|
| P1-PRESET-01 | preset | `aipctl admin preset list`, `get`, `create`, `update`, `rename`, `delete`, `set-default`, `unset-default`, `regen-key`, `regen-static-key` | `listPresets`, `getPreset`, `createPreset`, `updatePreset`, `renamePreset`, `deletePreset`, `setPresetAsDefault`, `unsetPresetAsDefault`, `regenPresetKey`, `regenPresetStaticKey` | api | `GET`, `POST`, `PUT`, `PATCH`, `DELETE` mix | `/api/web/presets/v2`; `/api/web/presets/{presetId}`; `/key`; `/static-key`; `/default` | legacy preset API kept; delete response is contract-aligned to 204 No Content and CLI treats delete as endpoint-specific void response to tolerate deployed 200 empty drift | local preset DTOs with installation/json command fields; delete returns `void` | `resource/oas/duplo-web.yaml` | `frontend/packages/api/src/out/user-types.ts#paths["/api/web/presets/v2"]`, `#paths["/api/web/presets/{presetId}"]`, `#paths["/api/web/presets/{presetId}/default"]` | `legacy-generated` | `P1` | `keep` | owner=`backend app PresetsApiController`; rationale=`runtime returned 200 empty while generated/OAS says 204, causing JSON parse false failure`; removal condition=`204 is deployed everywhere and CLI no longer needs 200-empty tolerance`; incorporation plan=`keep endpoint-specific void mode, do not add global 200-empty fallback` | N/A | updated `src/core/api.test.ts`, `commands/admin/preset/preset.test.ts`, `../docs/how-to/aip-cli-usage.md`, `backend/api/src/test/kotlin/.../PresetsApiControllerTest.kt`; covers admin namespace docs, `/v2`, `/{id}`, `/default`, `/key`, `/static-key`, delete 200-empty tolerance, and JSON endpoint 200-empty failure |
|
|
67
67
|
|
|
68
|
+
## 공통 CLI request quota 오류 계약
|
|
69
|
+
|
|
70
|
+
`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
|
+
|
|
72
|
+
| HTTP | code | CLI 처리 |
|
|
73
|
+
| --- | --- | --- |
|
|
74
|
+
| 429 | `CLI_QUOTA_EXCEEDED` | `limitKind=request_rate`, quota class, denying scope, request ID를 보존한다. 양수 refill로 계산 가능한 경우에만 `Retry-After`, `resetAt`을 보존한다. 자동 retry는 하지 않는다. |
|
|
75
|
+
| 503 | `CLI_QUOTA_POLICY_UNAVAILABLE` | Backend effective-policy auth/timeout/schema/freshness 실패. stale/default policy로 대체하거나 자동 retry하지 않는다. |
|
|
76
|
+
| 503 | `CLI_QUOTA_LIMITER_UNAVAILABLE` | Redis timeout/script/result 실패. 유료·mutation 요청을 자동 retry하지 않는다. |
|
|
77
|
+
|
|
78
|
+
CLI `ApiError`는 위 세 code에서 allowlist된 quota detail만 보존하며 서버가 추가로 보낸 조직/사용자 ID, raw policy, Redis key는 노출하지 않는다. 회귀 테스트는 `src/core/api.test.ts`와 Gateway `test/cli/quota/**`에 있다.
|
|
79
|
+
|
|
68
80
|
## Queue IDs for follow-up
|
|
69
81
|
|
|
70
82
|
### P0 queue
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aipctl",
|
|
3
|
-
"version": "2026.9.
|
|
3
|
+
"version": "2026.9.6",
|
|
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": "
|
|
26
|
-
"wrapperHash": "
|
|
25
|
+
"contentHash": "cbdcceeb10bb96331c9d30645354766c9172ca096bca981dd1130547ffa81d06",
|
|
26
|
+
"wrapperHash": "1f3314676a7f45c0f4672c43eb2e22c1cce93846e6215e31bb9af756c2cd89db",
|
|
27
27
|
"nativeHash": "006b08c3e391f4571d9b7c0f12d684fe2448106259e7e0581b10f9be362452e9"
|
|
28
28
|
},
|
|
29
29
|
"optionalDependencies": {
|