aipctl 2026.10.2 → 2026.10.3

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
@@ -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.10.2".length > 0 ? "2026.10.2" : "0.0.0";
89673
+ const VERSION = "2026.10.3".length > 0 ? "2026.10.3" : "0.0.0";
89674
89674
  const BUILD_ENV = injectedBuildEnv;
89675
89675
  const INCLUDE_NATIVE_LOGS = BUILD_ENV !== "prod";
89676
89676
 
@@ -89732,6 +89732,27 @@ const isNativeCommandEnabled = (command, includeLogs = INCLUDE_NATIVE_LOGS) => {
89732
89732
  const FORCED_ARGS_SEPARATOR = "--";
89733
89733
  const NATIVE_ROOT_COMMANDS = new Set(nativeCommandNames);
89734
89734
  const trailingOptionSpecs = [
89735
+ ...["list", "get", "create", "update", "rename", "delete", "set-default", "unset-default"].map((command) => ({
89736
+ command: ["admin", "preset", command],
89737
+ booleanOptions: new Set(command === "delete" ? ["--force", "-f"] : ["list", "get"].includes(command) ? ["--json"] : []),
89738
+ valueOptions: /* @__PURE__ */ new Set(["--org", ...command === "list" ? ["--page", "--pageSize"] : []]),
89739
+ explicitBooleanValues: true,
89740
+ booleanAliases: { "-f": "--force" }
89741
+ })),
89742
+ ...["invite", "remove-member", "update-member-role"].map((command) => ({
89743
+ command: ["admin", "org", command],
89744
+ booleanOptions: new Set(command === "remove-member" ? ["--force", "-f"] : []),
89745
+ valueOptions: /* @__PURE__ */ new Set(["--org", ...command !== "remove-member" ? ["--role"] : []]),
89746
+ explicitBooleanValues: true,
89747
+ booleanAliases: { "-f": "--force" }
89748
+ })),
89749
+ {
89750
+ command: ["org", "exit"],
89751
+ booleanOptions: /* @__PURE__ */ new Set(["--force", "-f"]),
89752
+ valueOptions: /* @__PURE__ */ new Set(["--org"]),
89753
+ explicitBooleanValues: true,
89754
+ booleanAliases: { "-f": "--force" }
89755
+ },
89735
89756
  ...["list", "get"].map((command) => ({
89736
89757
  command: ["automation", command],
89737
89758
  booleanOptions: /* @__PURE__ */ new Set(["--json"]),
@@ -89885,7 +89906,20 @@ const implementedCommandOperationIds = /* @__PURE__ */ new Set([
89885
89906
  "listCliDailyUsers",
89886
89907
  "listCliTopMcpTools",
89887
89908
  "listCliAuditLogs",
89888
- "listCliAuditEventTypes"
89909
+ "listCliAuditEventTypes",
89910
+ "listCliPresets",
89911
+ "getCliPreset",
89912
+ "createCliPreset",
89913
+ "updateCliPreset",
89914
+ "updateCliPresetName",
89915
+ "deleteCliPreset",
89916
+ "updateCliDefaultPreset",
89917
+ "deleteCliDefaultPreset",
89918
+ "createCliOrganizationMemberInvitations",
89919
+ "deleteCliOrganizationMembers",
89920
+ "updateCliOrganizationMemberRoles",
89921
+ "deleteCliOrganizationMembership",
89922
+ "getCliTunnelStatus"
89889
89923
  ]);
89890
89924
  const releaseEnabledCommandOperationIds = /* @__PURE__ */ new Set([
89891
89925
  "listCliAgents",
@@ -89942,7 +89976,20 @@ const releaseEnabledCommandOperationIds = /* @__PURE__ */ new Set([
89942
89976
  "listCliDailyUsers",
89943
89977
  "listCliTopMcpTools",
89944
89978
  "listCliAuditLogs",
89945
- "listCliAuditEventTypes"
89979
+ "listCliAuditEventTypes",
89980
+ "listCliPresets",
89981
+ "getCliPreset",
89982
+ "createCliPreset",
89983
+ "updateCliPreset",
89984
+ "updateCliPresetName",
89985
+ "deleteCliPreset",
89986
+ "updateCliDefaultPreset",
89987
+ "deleteCliDefaultPreset",
89988
+ "createCliOrganizationMemberInvitations",
89989
+ "deleteCliOrganizationMembers",
89990
+ "updateCliOrganizationMemberRoles",
89991
+ "deleteCliOrganizationMembership",
89992
+ "getCliTunnelStatus"
89946
89993
  ]);
89947
89994
  const direct = (commands, operation) => commands.map((command) => ({
89948
89995
  command,
@@ -94948,6 +94995,14 @@ const makeApiServiceLive = (buildEnv) => effect$1(
94948
94995
  });
94949
94996
  const requireApiBaseUrlEffect = () => requireConfiguredBaseUrlEffect(loadConfiguredApiBaseUrl);
94950
94997
  const requireChatBaseUrlEffect = () => requireConfiguredBaseUrlEffect(loadConfiguredChatBaseUrl);
94998
+ const presetPath = (presetId, orgSlug) => try_({
94999
+ try: () => {
95000
+ const slug = orgSlug ?? configService.load().currentOrganization?.slug;
95001
+ if (!slug) throw new Error("No organization selected. Use --org or select an organization.");
95002
+ return `/api/cli/v1/organizations/${encodeURIComponent(slug)}/admin/presets/${encodeURIComponent(presetId)}`;
95003
+ },
95004
+ catch: configuredBaseUrlError
95005
+ });
94951
95006
  const toRefreshFailure = (error) => {
94952
95007
  if (error instanceof ApiError || error instanceof NotLoggedInError) {
94953
95008
  return error;
@@ -95908,7 +95963,8 @@ const makeApiServiceLive = (buildEnv) => effect$1(
95908
95963
  // Tunnel management
95909
95964
  getTunnelStatus: (orgSlug) => gen(function* () {
95910
95965
  const token = yield* getAccessToken();
95911
- const path = `/api/web/edge-tunnel/v2/organizations/slug/${encodeURIComponent(orgSlug)}/status`;
95966
+ yield* requireReleaseEnabledOperation("getCliTunnelStatus");
95967
+ const path = `/api/cli/v1/organizations/${encodeURIComponent(orgSlug)}/admin/tunnel/status`;
95912
95968
  return yield* makeRequest(baseUrl, path, token);
95913
95969
  }),
95914
95970
  getTunnelConfig: (orgSlug) => gen(function* () {
@@ -95922,49 +95978,60 @@ const makeApiServiceLive = (buildEnv) => effect$1(
95922
95978
  return yield* makeRequest(baseUrl, path, token, { method: "POST" });
95923
95979
  }),
95924
95980
  // Preset management
95925
- listPresets: (orgSlug) => gen(function* () {
95981
+ listPresets: (orgSlug, page = 1, pageSize = 100) => gen(function* () {
95926
95982
  const token = yield* getAccessToken();
95927
- const path = `/api/web/presets/v2?organizationSlug=${encodeURIComponent(orgSlug)}`;
95928
- return yield* makeRequest(baseUrl, path, token);
95983
+ yield* requireReleaseEnabledOperation("listCliPresets");
95984
+ const path = `/api/cli/v1/organizations/${encodeURIComponent(orgSlug)}/admin/presets?page=${page}&pageSize=${pageSize}`;
95985
+ const response = yield* makeRequest(baseUrl, path, token);
95986
+ return response.list;
95929
95987
  }),
95930
- getPreset: (presetId) => gen(function* () {
95988
+ getPreset: (presetId, orgSlug) => gen(function* () {
95931
95989
  const token = yield* getAccessToken();
95932
- const path = `/api/web/presets/${encodeURIComponent(presetId)}`;
95990
+ yield* requireReleaseEnabledOperation("getCliPreset");
95991
+ const path = yield* presetPath(presetId, orgSlug);
95933
95992
  return yield* makeRequest(baseUrl, path, token);
95934
95993
  }),
95935
95994
  createPreset: (body) => gen(function* () {
95936
95995
  const token = yield* getAccessToken();
95937
- return yield* makeRequest(baseUrl, "/api/web/presets/v2", token, {
95996
+ yield* requireReleaseEnabledOperation("createCliPreset");
95997
+ const { organizationSlug, ...request } = body;
95998
+ const path = `/api/cli/v1/organizations/${encodeURIComponent(organizationSlug)}/admin/presets`;
95999
+ return yield* makeRequest(baseUrl, path, token, {
95938
96000
  method: "POST",
95939
- body
96001
+ body: request
95940
96002
  });
95941
96003
  }),
95942
- updatePreset: (presetId, body) => gen(function* () {
96004
+ updatePreset: (presetId, body, orgSlug) => gen(function* () {
95943
96005
  const token = yield* getAccessToken();
95944
- const path = `/api/web/presets/${encodeURIComponent(presetId)}`;
96006
+ yield* requireReleaseEnabledOperation("updateCliPreset");
96007
+ const path = yield* presetPath(presetId, orgSlug);
95945
96008
  return yield* makeRequest(baseUrl, path, token, { method: "PUT", body });
95946
96009
  }),
95947
- renamePreset: (presetId, name) => gen(function* () {
96010
+ renamePreset: (presetId, name, orgSlug) => gen(function* () {
95948
96011
  const token = yield* getAccessToken();
95949
- const path = `/api/web/presets/${encodeURIComponent(presetId)}`;
96012
+ yield* requireReleaseEnabledOperation("updateCliPresetName");
96013
+ const path = yield* presetPath(presetId, orgSlug);
95950
96014
  return yield* makeRequest(baseUrl, path, token, {
95951
96015
  method: "PATCH",
95952
96016
  body: { name }
95953
96017
  });
95954
96018
  }),
95955
- deletePreset: (presetId) => gen(function* () {
96019
+ deletePreset: (presetId, orgSlug) => gen(function* () {
95956
96020
  const token = yield* getAccessToken();
95957
- const path = `/api/web/presets/${encodeURIComponent(presetId)}`;
96021
+ yield* requireReleaseEnabledOperation("deleteCliPreset");
96022
+ const path = yield* presetPath(presetId, orgSlug);
95958
96023
  return yield* makeRequest(baseUrl, path, token, { method: "DELETE", skipJsonParse: true });
95959
96024
  }),
95960
- setPresetAsDefault: (presetId) => gen(function* () {
96025
+ setPresetAsDefault: (presetId, orgSlug) => gen(function* () {
95961
96026
  const token = yield* getAccessToken();
95962
- const path = `/api/web/presets/${encodeURIComponent(presetId)}/default`;
95963
- return yield* makeRequest(baseUrl, path, token, { method: "POST" });
96027
+ yield* requireReleaseEnabledOperation("updateCliDefaultPreset");
96028
+ const path = `${yield* presetPath(presetId, orgSlug)}/default`;
96029
+ return yield* makeRequest(baseUrl, path, token, { method: "PUT" });
95964
96030
  }),
95965
- unsetPresetAsDefault: (presetId) => gen(function* () {
96031
+ unsetPresetAsDefault: (presetId, orgSlug) => gen(function* () {
95966
96032
  const token = yield* getAccessToken();
95967
- const path = `/api/web/presets/${encodeURIComponent(presetId)}/default`;
96033
+ yield* requireReleaseEnabledOperation("deleteCliDefaultPreset");
96034
+ const path = `${yield* presetPath(presetId, orgSlug)}/default`;
95968
96035
  return yield* makeRequest(baseUrl, path, token, { method: "DELETE" });
95969
96036
  }),
95970
96037
  regenPresetKey: (presetId) => gen(function* () {
@@ -95980,29 +96047,33 @@ const makeApiServiceLive = (buildEnv) => effect$1(
95980
96047
  // Org member management
95981
96048
  inviteOrgMembers: (req) => gen(function* () {
95982
96049
  const token = yield* getAccessToken();
95983
- return yield* makeRequest(baseUrl, `/api/web/organizations/slug/${encodeURIComponent(req.organizationSlug)}/invite`, token, {
96050
+ yield* requireReleaseEnabledOperation("createCliOrganizationMemberInvitations");
96051
+ return yield* makeRequest(baseUrl, `/api/cli/v1/organizations/${encodeURIComponent(req.organizationSlug)}/members/invitations`, token, {
95984
96052
  method: "POST",
95985
96053
  body: { emails: req.emails, role: req.role }
95986
96054
  });
95987
96055
  }),
95988
96056
  removeOrgMembers: (req) => gen(function* () {
95989
96057
  const token = yield* getAccessToken();
95990
- yield* makeRequest(baseUrl, `/api/web/organizations/slug/${encodeURIComponent(req.organizationSlug)}/members`, token, {
96058
+ yield* requireReleaseEnabledOperation("deleteCliOrganizationMembers");
96059
+ yield* makeRequest(baseUrl, `/api/cli/v1/organizations/${encodeURIComponent(req.organizationSlug)}/members`, token, {
95991
96060
  method: "DELETE",
95992
96061
  body: { members: req.members }
95993
96062
  });
95994
96063
  }),
95995
96064
  updateOrgMemberRoles: (req) => gen(function* () {
95996
96065
  const token = yield* getAccessToken();
95997
- yield* makeRequest(baseUrl, `/api/web/organizations/slug/${encodeURIComponent(req.organizationSlug)}/members`, token, {
96066
+ yield* requireReleaseEnabledOperation("updateCliOrganizationMemberRoles");
96067
+ yield* makeRequest(baseUrl, `/api/cli/v1/organizations/${encodeURIComponent(req.organizationSlug)}/members/role`, token, {
95998
96068
  method: "PUT",
95999
96069
  body: { members: req.members, role: req.role }
96000
96070
  });
96001
96071
  }),
96002
96072
  exitOrg: (slug) => gen(function* () {
96003
96073
  const token = yield* getAccessToken();
96004
- yield* makeRequest(baseUrl, `/api/web/organizations/slug/${encodeURIComponent(slug)}/exit`, token, {
96005
- method: "POST"
96074
+ yield* requireReleaseEnabledOperation("deleteCliOrganizationMembership");
96075
+ yield* makeRequest(baseUrl, `/api/cli/v1/organizations/${encodeURIComponent(slug)}/membership`, token, {
96076
+ method: "DELETE"
96006
96077
  });
96007
96078
  }),
96008
96079
  // Agent headless chat
@@ -96959,13 +97030,13 @@ const deleteCommand$4 = make$4(
96959
97030
  { id: idArg$7, org: orgOption, force: forceOption$5 },
96960
97031
  ({ id, org, force }) => gen(function* () {
96961
97032
  const api = yield* ApiService;
96962
- yield* resolveOrgSlug(org, api);
97033
+ const orgSlug = yield* resolveOrgSlug(org, api);
96963
97034
  if (!force) {
96964
97035
  yield* log(`Warning: This will permanently delete preset "${id}".`);
96965
97036
  yield* log("Use --force (-f) to skip this confirmation.");
96966
97037
  return;
96967
97038
  }
96968
- yield* api.deletePreset(id).pipe(
97039
+ yield* api.deletePreset(id, orgSlug).pipe(
96969
97040
  catchTag(
96970
97041
  "NotLoggedInError",
96971
97042
  (e) => gen(function* () {
@@ -96998,8 +97069,8 @@ const getCommand$6 = make$4(
96998
97069
  { id: idArg$6, org: orgOption, json: jsonOption$h },
96999
97070
  ({ id, org, json }) => gen(function* () {
97000
97071
  const api = yield* ApiService;
97001
- yield* resolveOrgSlug(org, api);
97002
- const preset = yield* api.getPreset(id).pipe(
97072
+ const orgSlug = yield* resolveOrgSlug(org, api);
97073
+ const preset = yield* api.getPreset(id, orgSlug).pipe(
97003
97074
  catchTag(
97004
97075
  "NotLoggedInError",
97005
97076
  (e) => gen(function* () {
@@ -97054,11 +97125,19 @@ function formatPresetListPrompt(prompt) {
97054
97125
  }
97055
97126
  const listCommand$9 = make$4(
97056
97127
  "list",
97057
- { org: orgOption, json: jsonOption$g },
97058
- ({ org, json }) => gen(function* () {
97128
+ {
97129
+ org: orgOption,
97130
+ json: jsonOption$g,
97131
+ page: integer("page").pipe(withDescription$3("Page number, starting at 1"), withDefault(1)),
97132
+ pageSize: integer("pageSize").pipe(withDescription$3("Presets per page (1\u2013100)"), withDefault(100))
97133
+ },
97134
+ ({ org, json, page, pageSize }) => gen(function* () {
97135
+ if (page < 1 || pageSize < 1 || pageSize > 100) {
97136
+ return yield* fail$3(new ApiError({ message: "Use --page 1 or higher and --pageSize between 1 and 100." }));
97137
+ }
97059
97138
  const api = yield* ApiService;
97060
97139
  const orgSlug = yield* resolveOrgSlug(org, api);
97061
- const result = yield* api.listPresets(orgSlug).pipe(
97140
+ const result = yield* api.listPresets(orgSlug, page, pageSize).pipe(
97062
97141
  catchTag(
97063
97142
  "NotLoggedInError",
97064
97143
  (e) => gen(function* () {
@@ -97079,10 +97158,10 @@ const listCommand$9 = make$4(
97079
97158
  return;
97080
97159
  }
97081
97160
  if (result.length === 0) {
97082
- yield* log("No presets found.");
97161
+ yield* log(`No presets found on page ${page}.`);
97083
97162
  return;
97084
97163
  }
97085
- yield* log(`Presets in organization "${orgSlug}":
97164
+ yield* log(`Presets in organization "${orgSlug}" (page ${page}):
97086
97165
  `);
97087
97166
  for (const preset of result) {
97088
97167
  const defaultMark = preset.isDefault ? " (default)" : "";
@@ -97158,8 +97237,8 @@ const renameCommand$1 = make$4(
97158
97237
  { id: idArg$3, name: nameArg$1, org: orgOption },
97159
97238
  ({ id, name, org }) => gen(function* () {
97160
97239
  const api = yield* ApiService;
97161
- yield* resolveOrgSlug(org, api);
97162
- yield* api.renamePreset(id, name).pipe(
97240
+ const orgSlug = yield* resolveOrgSlug(org, api);
97241
+ yield* api.renamePreset(id, name, orgSlug).pipe(
97163
97242
  catchTag(
97164
97243
  "NotLoggedInError",
97165
97244
  (e) => gen(function* () {
@@ -97185,8 +97264,8 @@ const setDefaultCommand = make$4(
97185
97264
  { id: idArg$2, org: orgOption },
97186
97265
  ({ id, org }) => gen(function* () {
97187
97266
  const api = yield* ApiService;
97188
- yield* resolveOrgSlug(org, api);
97189
- yield* api.setPresetAsDefault(id).pipe(
97267
+ const orgSlug = yield* resolveOrgSlug(org, api);
97268
+ yield* api.setPresetAsDefault(id, orgSlug).pipe(
97190
97269
  catchTag(
97191
97270
  "NotLoggedInError",
97192
97271
  (e) => gen(function* () {
@@ -97212,8 +97291,8 @@ const unsetDefaultCommand = make$4(
97212
97291
  { id: idArg$1, org: orgOption },
97213
97292
  ({ id, org }) => gen(function* () {
97214
97293
  const api = yield* ApiService;
97215
- yield* resolveOrgSlug(org, api);
97216
- yield* api.unsetPresetAsDefault(id).pipe(
97294
+ const orgSlug = yield* resolveOrgSlug(org, api);
97295
+ yield* api.unsetPresetAsDefault(id, orgSlug).pipe(
97217
97296
  catchTag(
97218
97297
  "NotLoggedInError",
97219
97298
  (e) => gen(function* () {
@@ -97242,7 +97321,7 @@ const updateCommand$1 = make$4(
97242
97321
  { id: idArg, jsonFile: jsonFileArg$2, org: orgOption },
97243
97322
  ({ id, jsonFile, org }) => gen(function* () {
97244
97323
  const api = yield* ApiService;
97245
- yield* resolveOrgSlug(org, api);
97324
+ const orgSlug = yield* resolveOrgSlug(org, api);
97246
97325
  const body = yield* try_({
97247
97326
  try: () => {
97248
97327
  const content = fs__namespace.readFileSync(jsonFile, "utf-8");
@@ -97257,7 +97336,7 @@ const updateCommand$1 = make$4(
97257
97336
  })
97258
97337
  )
97259
97338
  );
97260
- yield* api.updatePreset(id, body).pipe(
97339
+ yield* api.updatePreset(id, body, orgSlug).pipe(
97261
97340
  catchTag(
97262
97341
  "NotLoggedInError",
97263
97342
  (e) => gen(function* () {
@@ -40,8 +40,8 @@
40
40
  | AUTH-02 | auth | `aipctl auth refresh` | `ApiService.refreshToken` → `refreshAccessToken` → `src/core/oauth.ts#refreshOAuthToken` | api | `POST` | `/oauth2/token` | refresh token form body | refreshed local `Credentials` | not found in current `resource/oas` for `/oauth2/token` | none | `oas-missing` | N/A | `legacy-exception` | owner=`backend/api OAuth server`; rationale=`token refresh is required for session continuity but token endpoint is outside current OAS`; removal condition=`official OAuth token contract exists in resource/oas and independent generated contract`; incorporation plan=`same OAuth server contract as AUTH-01, plus refresh fixture` | `AIP-PUB-05` | existing `src/core/oauth.test.ts`, `src/core/api.test.ts`; needed token refresh path snapshot |
41
41
  | AUTH-03 | auth | `aipctl auth whoami` | `ApiService.getMe` | api | `GET` | `/api/cli/v1/me` | none | local `UserProfile` | `resource/oas/modules/backend/cli/openapi.yaml` | `backend/api/.../cli/generated/api/CliV1Api.kt#getCliMe` | `generated-ready` | N/A | `migrate` | none | N/A | `src/core/api.test.ts` verifies the dedicated path and bearer request; `commands/auth/auth.test.ts` covers command behavior |
42
42
  | ORG-01 | org | `aipctl org list`, `aipctl org use`, `aipctl org select` | `listOrganizations`, `getOrganization` | api | `GET` | `/api/cli/v1/organizations`; `use/select` resolve locally from the returned list | none | local `Organization[]` / `Organization` | `resource/oas/modules/backend/cli/openapi.yaml` | `backend/api/.../cli/generated/api/CliV1Api.kt#listCliOrganizations` | `generated-ready` | N/A | `migrate` | none | N/A | `src/core/api.test.ts`, `commands/org/org.test.ts`; covers dedicated path, local slug selection, default/IP-blocked fields |
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
- | 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 |
43
+ | ORG-02 | org | `aipctl org capabilities`, `aipctl org settings`, `aipctl org exit` | `getOrgCapabilities`, `getUserSettings`, `exitOrg` | api | `GET`, `GET`, `DELETE` | organization v1 `/api/cli/v1/organizations/{organizationSlug}/capabilities`; user-global v1 `/api/cli/v1/me/settings`; exit v1 `/api/cli/v1/organizations/{organizationSlug}/membership` | none | local `OrgCapabilities`, `UserSettings`, `void` | `resource/oas/modules/backend/cli/openapi.yaml` | `CliOrganizationApi`/`CliV1Api`; exit `deleteCliOrganizationMembership` | `generated-ready` | N/A | `migrate` | none; exit delegates existing member authorization, owner protection and audit | `AI-6344`; common error/deadline gate `AI-6378` | `src/core/api.test.ts`, `src/core/remaining-direct-contract.test.ts`, org tests, Backend controller test; actual CLI member exit/owner rejection |
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` | prefix `/api/cli/v1/organizations/{organizationSlug}`; `/members`; `/members/invitations`; `/members`; `/members/role` | `{ emails, role }` or `{ members, role? }`; 1..100 unique targets, email/userId/status checked against current membership | local `ListMembersResponse`, `InviteOrgMembersResponse`, `void` | `resource/oas/modules/backend/cli/openapi.yaml` | `backend/api/.../cli/generated/api/CliOrganizationApi.kt` | `generated-ready` | `P0` | `migrate` | none; preserves existing admin authorization, audit, pending/active/inactive semantics, skipped owner/self removal and invite partial failure; stale/missing target returns 409 before mutation | `AI-6344`; operational gates remain separate | API/command and Backend controller tests; actual CLI invite with LocalStack SES, role/remove for three statuses, non-admin 403, stale 409, owner protection, persisted audit |
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
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 |
@@ -64,8 +64,8 @@
64
64
  | P1-MODEL-EXEC-02 | model-exec-image | `aipctl model-exec image generate` | `executeModelExecImageGeneration`; output uses `downloadFromUrl` | chat + external artifact URL | `POST`, external `GET` | legacy `/api/{orgSlug}/cli/model-exec/images/generations`; response `downloadUrl` | local `ModelExecImageGenerationRequest`; Chat DTO source `chat/packages/chat/src/modules/cli-model-exec/interface/dto/cli-model-exec.dto.ts` | local `ModelExecImageGenerationResponse`; image bytes from external URL | no GA OAS; legacy controller only | none | `oas-missing` | deferred | `hide` | owner=`chat CLI Model Exec API`; rationale=`synchronous image execution, external artifact lifetime, and cost recovery require the deferred async design`; removal condition=`image job/artifact/TTL/My Drive/cost gates pass`; incorporation plan=`publish a separate v1 image job contract only after the deferred design is implemented` | follow-up image hardening | existing image request/path test remains on the legacy route; customer capability must stay disabled |
65
65
  | USAGE-01 | usage | `aipctl admin usage credit`, `top-users`, `daily-graph`, `daily-users`, `mcp-top` | `getCreditStatistics`, `getCreditTopUsers`, `getCreditDailyGraph`, `getCreditDailyUsers`, `getMcpTopTools` | api | `GET` | `/api/cli/v1/organizations/{organizationSlug}/admin/usage/credit`; `/admin/usage/top-users`; `/admin/usage/daily-graph`; `/admin/usage/daily-users`; `/admin/usage/mcp-top` | query `{ startDt,endDt,limit,pageNumber,pageSize,userId }`; daily-users uses one-based page/pageSize 1..100 | existing usage DTOs; `DailyUsageDetailsResponse.pagination { page,pageSize,total }` normalized to CLI output | `resource/oas/modules/backend/cli/openapi.yaml` → `paths/v1_admin_usage_*.yaml`; response aliases reuse `resource/oas/modules/backend/usage/components/schemas/*` | `backend/api/.../cli/generated/api/CliUsageApi.kt`; domain DTOs remain `com.querypie.duplo.api.usage.generated.model.*` | `generated-ready` | `P0` | `migrate` | none; dedicated adapter preserves organization admin authorization, three-month/future-date validation, and existing aggregation service | `AI-6344`; query/page workload bounds remain `AI-6413` | `src/core/usage-audit-contract.test.ts`, usage command tests, `CliUsageAuditApiControllerTest`, Gateway registry/contract parity; real production CLI verifies all five paths, 401/403/404/400 and future-date clamp |
66
66
  | AUDIT-01 | audit | `aipctl admin audit list`, `types` | `listAuditLogs`, `listAuditEventTypes` | api | `GET` | `/api/cli/v1/organizations/{organizationSlug}/admin/audit`; `/api/cli/v1/organizations/{organizationSlug}/admin/audit/types` | query `{ eventType,page,pageSize }`; page is one-based and pageSize is 1..100 | existing `ListAuditResponse`/`AuditEventType[]`; page/total is normalized for CLI pagination output | `resource/oas/modules/backend/cli/openapi.yaml` → `paths/v1_admin_audit*.yaml`; response aliases reuse `resource/oas/modules/backend/audit/components/schemas/*` | `backend/api/.../cli/generated/api/CliAuditApi.kt`; domain DTOs remain `com.querypie.duplo.api.generated.models.*` | `generated-ready` | `P0` | `migrate` | none; dedicated adapter preserves AUDIT_LOGS capability, plan/retention/viewable-period and organization filters | `AI-6344`; audit volume/query workload bounds remain `AI-6413` | `src/core/usage-audit-contract.test.ts`, `commands/admin/audit/*.test.ts`, `CliUsageAuditApiControllerTest`, Gateway registry/contract parity; real production CLI verifies page 2/3, types, 401/403/404/400 |
67
- | TUNNEL-01 | tunnel | `aipctl admin tunnel status`, `config`, `token` | `getTunnelStatus`, `getTunnelConfig`, `createTunnelToken` | api | `GET`, `GET`, `POST` | status target: `/api/cli/v1/organizations/{organizationSlug}/admin/tunnel/status`; current legacy `/api/web/edge-tunnel/v2/organizations/slug/{organizationSlug}/status`; `/config`; `/token` | none | local `TunnelStatus`, `TunnelConfig`, `void` | current: `resource/oas/modules/backend/tunnel/openapi.yaml`; target status: Backend CLI OAS | current tunnel generated reference; target CLI generated interface pending | `legacy-generated` | N/A | `migrate` | owner=`backend tunnel domain`; rationale=`status is safe GA-DIRECT but config/token expose sensitive material`; removal condition=`status receives its dedicated v1 contract`; incorporation plan=`migrate status only and keep config/token capability-disabled until --show-sensitive, TTL, redaction, and audit gates pass` | `AI-6344` task 3.3d; sensitive gate `AI-6380` | existing tunnel command tests; needed v1 status path/registry/permission/redaction and real CLI verification |
68
- | P1-PRESET-01 | preset | `aipctl admin preset list`, `get`, `create`, `update`, `rename`, `delete`, `set-default`, `unset-default`; key rotation commands remain candidates | `listPresets`, `getPreset`, `createPreset`, `updatePreset`, `renamePreset`, `deletePreset`, `setPresetAsDefault`, `unsetPresetAsDefault`; `regenPresetKey`, `regenPresetStaticKey` | api | `GET`, `POST`, `PUT`, `PATCH`, `DELETE` mix | target `/api/cli/v1/organizations/{organizationSlug}/admin/presets/**`; current legacy `/api/web/presets/v2`; `/api/web/presets/{presetId}`; `/key`; `/static-key`; `/default` | dedicated v1 basic-management contract pending; current delete is contract-aligned to 204 and CLI tolerates deployed 200 empty only for that legacy void endpoint | local preset DTOs with installation/json command fields; delete returns `void`; key values must not enter the GA-DIRECT response | current `resource/oas/duplo-web.yaml`; target Backend CLI OAS | current legacy generated reference; target CLI generated interface pending | `legacy-generated` | `P1` | `migrate` | owner=`backend app PresetsApiController`/preset domain; rationale=`basic management is GA-DIRECT while key/static-key rotation requires a separate secret/rollback gate`; removal condition=`eight basic operations receive dedicated v1 contracts and actual CLI verification`; incorporation plan=`reuse domain authorization/audit, preserve endpoint-specific void handling, and keep key rotation gated` | `AI-6344` task 3.3b; key gate `AI-6380` | existing legacy tests plus needed v1 OAS/adapter/registry/manifest, permission/audit/idempotency/destructive UX and real mise CLI lifecycle |
67
+ | TUNNEL-01 | tunnel | `aipctl admin tunnel status`; `config`/`token` remain gated | `getTunnelStatus`; candidate `getTunnelConfig`, `createTunnelToken` | api | `GET`; candidate `GET`/`POST` | status `/api/cli/v1/organizations/{organizationSlug}/admin/tunnel/status`; candidates retain Web `/config`/`/token` | none | status `{ connected: boolean }` only | status `resource/oas/modules/backend/cli/openapi.yaml`; candidates Tunnel OAS | `backend/api/.../cli/generated/api/CliTunnelApi.kt#getCliTunnelStatus` | `generated-ready` | N/A | `migrate` | none for status; existing personal Tunnel service checks current user/org; token/config secret gates remain | `AI-6344`; sensitive gate `AI-6380` | status contract/adapter/registry tests; actual CLI + Go Tunnel server disconnected/Redis session connected/user isolation, token count unchanged; actual edge handshake not verified |
68
+ | P1-PRESET-01 | preset | `aipctl admin preset list`, `get`, `create`, `update`, `rename`, `delete`, `set-default`, `unset-default`; key rotation commands remain gated | `listPresets`, `getPreset`, `createPreset`, `updatePreset`, `renamePreset`, `deletePreset`, `setPresetAsDefault`, `unsetPresetAsDefault` | api | list/get `GET`; create `POST`; update/default set `PUT`; rename `PATCH`; delete/default unset `DELETE` | `/api/cli/v1/organizations/{organizationSlug}/admin/presets`; `/{presetId}`; `/{presetId}/default` | bounded one-based page/pageSize 1..100; default pageSize 100; existing basic mutation bodies, explicit org in path | dedicated safe `CliPreset` metadata; no installation/json commands, auth, keys or MCP URL; list CLI JSON remains a page array; delete `void` | `resource/oas/modules/backend/cli/openapi.yaml` | `backend/api/.../cli/generated/api/CliPresetApi.kt`; `CliPreset` DTOs | `generated-ready` | `P1` | `migrate` | none for basic operations; user-owned resources require member+owner+org checks despite admin namespace; read service does not mint keys/enrich remotely; domain mutation/audit reused | `AI-6344`; key gate `AI-6380`; larger workload gate `AI-6413` | `src/core/remaining-direct-contract.test.ts`, Backend safe-read/controller tests, registry/manifest/help, trailing argv tests; actual production CLI eight-operation lifecycle, ownership/accessible integration/pagination/quota/key-count/DB/audit checks |
69
69
 
70
70
  ## 공통 CLI request quota 오류 계약
71
71
 
@@ -86,7 +86,7 @@ CLI `ApiError`는 위 여섯 code에서 allowlist된 quota detail만 보존하
86
86
 
87
87
  ### P0 queue
88
88
 
89
- - `P0-ORG-01` org member list/mutation legacy-exception aligned (done in `AIP-PUB-04`, independent contract follow-up remains)
89
+ - `P0-ORG-01` member list and invite/remove/role mutations use dedicated CLI v1 contracts (`AI-6344`); `ORG-02` exit uses DELETE membership
90
90
  - `P0-ORG-02` org invite-link trio hidden from public beta (done in `AIP-PUB-04`)
91
91
  - `P0-AGENT-01` / `P0-AGENT-02` moved to dedicated CLI lifecycle endpoints with metadata quota and capability checks (`AI-6344`)
92
92
  - `P0-AGENT-03` export remains a gated transfer candidate (`AI-6380`)
@@ -98,7 +98,7 @@ CLI `ApiError`는 위 여섯 code에서 allowlist된 quota detail만 보존하
98
98
 
99
99
  - `P1-INT-01` integration legacy-exception documented; current/legacy path+DTO tests added
100
100
  - `P1-AUTO-01` Automation management uses eight dedicated v1 operations; Schedule/Webhook/My Drive lifecycle and stored safe reads are implemented. External provider metadata/mutations and `P1-AUTO-02` remain gated.
101
- - `P1-PRESET-01` preset admin namespace/docs + legacy path tests done
101
+ - `P1-PRESET-01` eight basic operations use dedicated CLI v1 contracts and safe metadata DTOs (`AI-6344`); key rotation stays gated
102
102
  - `P1-SKILL-01` skill JSON/help/docs field sync done
103
103
  - `P1-CHAT-01` chat SSE fixture tests added for final/fallback/error cases
104
104
  - `P1-MODEL-EXEC-01` catalog/text moved to the dedicated v1 code-first OAS and CLI path; `P1-MODEL-EXEC-02` image remains deferred on the legacy route
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aipctl",
3
- "version": "2026.10.2",
3
+ "version": "2026.10.3",
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": "866cf35ffc9e44ebc83327f46bc0f77d229aec2f7b688bbdf0074ae937d41e7e",
26
- "wrapperHash": "2081868ac4d9ac9204f9f168fc0f33afb220ce17fdaeceb1060e9376ed3b138f",
25
+ "contentHash": "cd3e1d96a6c80cca1c9594573468ecac8876ea644f388d0d53950cc063451a72",
26
+ "wrapperHash": "1f9932e5f826496deb9203873e0e543319d84afda6610a78305ffa034fd665c0",
27
27
  "nativeHash": "006b08c3e391f4571d9b7c0f12d684fe2448106259e7e0581b10f9be362452e9"
28
28
  },
29
29
  "optionalDependencies": {