From 574060f46ab2c49dc47ef4f1e8de6dcfe5361c1f Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Thu, 21 May 2026 17:13:48 +0400 Subject: [PATCH] feat: support concurrent chunk uploads --- README.md | 6 +- cli.ts | 6 + docs/examples/advisor/delete-report.md | 4 + docs/examples/advisor/get-insight.md | 5 + docs/examples/advisor/get-report.md | 4 + docs/examples/advisor/list-insights.md | 5 + docs/examples/advisor/list-reports.md | 4 + docs/examples/presences/delete.md | 4 + docs/examples/presences/get-usage.md | 3 + docs/examples/presences/get.md | 4 + docs/examples/presences/list.md | 4 + docs/examples/presences/update-presence.md | 5 + docs/examples/presences/upsert.md | 6 + docs/examples/project/get.md | 3 + .../update-deny-aliased-email-policy.md | 4 + .../update-deny-canonical-email-policy.md | 4 - docs/examples/projects/get.md | 4 - docs/examples/usage/list-events.md | 4 + docs/examples/usage/list-gauges.md | 4 + install.ps1 | 4 +- install.sh | 2 +- lib/commands/services/advisor.ts | 129 ++++++++++++++++ lib/commands/services/presences.ts | 144 ++++++++++++++++++ lib/commands/services/project.ts | 38 +++-- lib/commands/services/projects.ts | 25 +-- lib/commands/services/usage.ts | 86 +++++++++++ lib/constants.ts | 2 +- package-lock.json | 4 +- package.json | 2 +- scoop/appwrite.config.json | 6 +- 30 files changed, 467 insertions(+), 58 deletions(-) create mode 100644 docs/examples/advisor/delete-report.md create mode 100644 docs/examples/advisor/get-insight.md create mode 100644 docs/examples/advisor/get-report.md create mode 100644 docs/examples/advisor/list-insights.md create mode 100644 docs/examples/advisor/list-reports.md create mode 100644 docs/examples/presences/delete.md create mode 100644 docs/examples/presences/get-usage.md create mode 100644 docs/examples/presences/get.md create mode 100644 docs/examples/presences/list.md create mode 100644 docs/examples/presences/update-presence.md create mode 100644 docs/examples/presences/upsert.md create mode 100644 docs/examples/project/get.md create mode 100644 docs/examples/project/update-deny-aliased-email-policy.md delete mode 100644 docs/examples/project/update-deny-canonical-email-policy.md delete mode 100644 docs/examples/projects/get.md create mode 100644 docs/examples/usage/list-events.md create mode 100644 docs/examples/usage/list-gauges.md create mode 100644 lib/commands/services/advisor.ts create mode 100644 lib/commands/services/presences.ts create mode 100644 lib/commands/services/usage.ts diff --git a/README.md b/README.md index f7ba8c51..aa6038e3 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Appwrite Command Line SDK ![License](https://img.shields.io/github/license/appwrite/sdk-for-cli.svg?style=flat-square) -![Version](https://img.shields.io/badge/api%20version-1.9.4-blue.svg?style=flat-square) +![Version](https://img.shields.io/badge/api%20version-1.9.5-blue.svg?style=flat-square) [![Build Status](https://img.shields.io/travis/com/appwrite/sdk-generator?style=flat-square)](https://travis-ci.com/appwrite/sdk-generator) [![Twitter Account](https://img.shields.io/twitter/follow/appwrite?color=00acee&label=twitter&style=flat-square)](https://twitter.com/appwrite) [![Discord](https://img.shields.io/discord/564160730845151244?label=discord&style=flat-square)](https://appwrite.io/discord) @@ -29,7 +29,7 @@ Once the installation is complete, you can verify the install using ```sh $ appwrite -v -21.0.0 +21.1.0 ``` ### Install using prebuilt binaries @@ -83,7 +83,7 @@ $ scoop install https://raw.githubusercontent.com/appwrite/sdk-for-cli/master/sc Once the installation completes, you can verify your install using ``` $ appwrite -v -21.0.0 +21.1.0 ``` ## Getting Started diff --git a/cli.ts b/cli.ts index 293f5336..494341c2 100644 --- a/cli.ts +++ b/cli.ts @@ -45,14 +45,17 @@ import { locale } from './lib/commands/services/locale.js'; import { messaging } from './lib/commands/services/messaging.js'; import { migrations } from './lib/commands/services/migrations.js'; import { organizations } from './lib/commands/services/organizations.js'; +import { presences } from './lib/commands/services/presences.js'; import { project } from './lib/commands/services/project.js'; import { projects } from './lib/commands/services/projects.js'; import { proxy } from './lib/commands/services/proxy.js'; +import { advisor } from './lib/commands/services/advisor.js'; import { sites } from './lib/commands/services/sites.js'; import { storage } from './lib/commands/services/storage.js'; import { tablesDB } from './lib/commands/services/tables-db.js'; import { teams } from './lib/commands/services/teams.js'; import { tokens } from './lib/commands/services/tokens.js'; +import { usage } from './lib/commands/services/usage.js'; import { users } from './lib/commands/services/users.js'; import { vcs } from './lib/commands/services/vcs.js'; import { webhooks } from './lib/commands/services/webhooks.js'; @@ -215,14 +218,17 @@ if (process.argv.includes('-v') || process.argv.includes('--version')) { .addCommand(messaging) .addCommand(migrations) .addCommand(organizations) + .addCommand(presences) .addCommand(project) .addCommand(projects) .addCommand(proxy) + .addCommand(advisor) .addCommand(sites) .addCommand(storage) .addCommand(tablesDB) .addCommand(teams) .addCommand(tokens) + .addCommand(usage) .addCommand(users) .addCommand(vcs) .addCommand(webhooks) diff --git a/docs/examples/advisor/delete-report.md b/docs/examples/advisor/delete-report.md new file mode 100644 index 00000000..03ad2aba --- /dev/null +++ b/docs/examples/advisor/delete-report.md @@ -0,0 +1,4 @@ +```bash +appwrite advisor delete-report \ + --report-id +``` diff --git a/docs/examples/advisor/get-insight.md b/docs/examples/advisor/get-insight.md new file mode 100644 index 00000000..d8c15a81 --- /dev/null +++ b/docs/examples/advisor/get-insight.md @@ -0,0 +1,5 @@ +```bash +appwrite advisor get-insight \ + --report-id \ + --insight-id +``` diff --git a/docs/examples/advisor/get-report.md b/docs/examples/advisor/get-report.md new file mode 100644 index 00000000..72ce6124 --- /dev/null +++ b/docs/examples/advisor/get-report.md @@ -0,0 +1,4 @@ +```bash +appwrite advisor get-report \ + --report-id +``` diff --git a/docs/examples/advisor/list-insights.md b/docs/examples/advisor/list-insights.md new file mode 100644 index 00000000..31b1a8dc --- /dev/null +++ b/docs/examples/advisor/list-insights.md @@ -0,0 +1,5 @@ +```bash +appwrite advisor list-insights \ + --report-id \ + --limit 25 +``` diff --git a/docs/examples/advisor/list-reports.md b/docs/examples/advisor/list-reports.md new file mode 100644 index 00000000..50abda66 --- /dev/null +++ b/docs/examples/advisor/list-reports.md @@ -0,0 +1,4 @@ +```bash +appwrite advisor list-reports \ + --limit 25 +``` diff --git a/docs/examples/presences/delete.md b/docs/examples/presences/delete.md new file mode 100644 index 00000000..c64c0006 --- /dev/null +++ b/docs/examples/presences/delete.md @@ -0,0 +1,4 @@ +```bash +appwrite presences delete \ + --presence-id +``` diff --git a/docs/examples/presences/get-usage.md b/docs/examples/presences/get-usage.md new file mode 100644 index 00000000..65daeca2 --- /dev/null +++ b/docs/examples/presences/get-usage.md @@ -0,0 +1,3 @@ +```bash +appwrite presences get-usage +``` diff --git a/docs/examples/presences/get.md b/docs/examples/presences/get.md new file mode 100644 index 00000000..86a1b5d7 --- /dev/null +++ b/docs/examples/presences/get.md @@ -0,0 +1,4 @@ +```bash +appwrite presences get \ + --presence-id +``` diff --git a/docs/examples/presences/list.md b/docs/examples/presences/list.md new file mode 100644 index 00000000..83c77666 --- /dev/null +++ b/docs/examples/presences/list.md @@ -0,0 +1,4 @@ +```bash +appwrite presences list \ + --limit 25 +``` diff --git a/docs/examples/presences/update-presence.md b/docs/examples/presences/update-presence.md new file mode 100644 index 00000000..800fd721 --- /dev/null +++ b/docs/examples/presences/update-presence.md @@ -0,0 +1,5 @@ +```bash +appwrite presences update-presence \ + --presence-id \ + --user-id +``` diff --git a/docs/examples/presences/upsert.md b/docs/examples/presences/upsert.md new file mode 100644 index 00000000..4261cc0c --- /dev/null +++ b/docs/examples/presences/upsert.md @@ -0,0 +1,6 @@ +```bash +appwrite presences upsert \ + --presence-id \ + --user-id \ + --status +``` diff --git a/docs/examples/project/get.md b/docs/examples/project/get.md new file mode 100644 index 00000000..a1a1b4b5 --- /dev/null +++ b/docs/examples/project/get.md @@ -0,0 +1,3 @@ +```bash +appwrite project get +``` diff --git a/docs/examples/project/update-deny-aliased-email-policy.md b/docs/examples/project/update-deny-aliased-email-policy.md new file mode 100644 index 00000000..dfd87334 --- /dev/null +++ b/docs/examples/project/update-deny-aliased-email-policy.md @@ -0,0 +1,4 @@ +```bash +appwrite project update-deny-aliased-email-policy \ + --enabled false +``` diff --git a/docs/examples/project/update-deny-canonical-email-policy.md b/docs/examples/project/update-deny-canonical-email-policy.md deleted file mode 100644 index a70582e4..00000000 --- a/docs/examples/project/update-deny-canonical-email-policy.md +++ /dev/null @@ -1,4 +0,0 @@ -```bash -appwrite project update-deny-canonical-email-policy \ - --enabled false -``` diff --git a/docs/examples/projects/get.md b/docs/examples/projects/get.md deleted file mode 100644 index 553fa866..00000000 --- a/docs/examples/projects/get.md +++ /dev/null @@ -1,4 +0,0 @@ -```bash -appwrite projects get \ - --project-id -``` diff --git a/docs/examples/usage/list-events.md b/docs/examples/usage/list-events.md new file mode 100644 index 00000000..32cb9dc1 --- /dev/null +++ b/docs/examples/usage/list-events.md @@ -0,0 +1,4 @@ +```bash +appwrite usage list-events \ + --limit 25 +``` diff --git a/docs/examples/usage/list-gauges.md b/docs/examples/usage/list-gauges.md new file mode 100644 index 00000000..f4d9ddb4 --- /dev/null +++ b/docs/examples/usage/list-gauges.md @@ -0,0 +1,4 @@ +```bash +appwrite usage list-gauges \ + --limit 25 +``` diff --git a/install.ps1 b/install.ps1 index 748f0c1e..36884e7b 100644 --- a/install.ps1 +++ b/install.ps1 @@ -13,8 +13,8 @@ # You can use "View source" of this page to see the full script. # REPO -$GITHUB_x64_URL = "https://github.com/appwrite/sdk-for-cli/releases/download/21.0.0/appwrite-cli-win-x64.exe" -$GITHUB_arm64_URL = "https://github.com/appwrite/sdk-for-cli/releases/download/21.0.0/appwrite-cli-win-arm64.exe" +$GITHUB_x64_URL = "https://github.com/appwrite/sdk-for-cli/releases/download/21.1.0/appwrite-cli-win-x64.exe" +$GITHUB_arm64_URL = "https://github.com/appwrite/sdk-for-cli/releases/download/21.1.0/appwrite-cli-win-arm64.exe" $APPWRITE_BINARY_NAME = "appwrite.exe" diff --git a/install.sh b/install.sh index ce2b248c..0b17321f 100644 --- a/install.sh +++ b/install.sh @@ -120,7 +120,7 @@ verifyMacOSCodeSignature() { downloadBinary() { echo "[2/5] Downloading executable for $OS ($ARCH) ..." - GITHUB_LATEST_VERSION="21.0.0" + GITHUB_LATEST_VERSION="21.1.0" GITHUB_FILE="appwrite-cli-${OS}-${ARCH}" GITHUB_URL="https://github.com/$GITHUB_REPOSITORY_NAME/releases/download/$GITHUB_LATEST_VERSION/$GITHUB_FILE" diff --git a/lib/commands/services/advisor.ts b/lib/commands/services/advisor.ts new file mode 100644 index 00000000..617cdf52 --- /dev/null +++ b/lib/commands/services/advisor.ts @@ -0,0 +1,129 @@ +import { Command } from "commander"; +import { + buildQueries, + collectQueryValue, + parseDeprecatedWhereQuery, + parseFilterQuery, +} from "../utils/query.js"; +import { sdkForProject } from "../../sdks.js"; +import { + actionRunner, + commandDescriptions, + success, + parse, + parseBool, + parseInteger, +} from "../../parser.js"; +import { Advisor } from "@appwrite.io/console"; + +let advisorClient: Advisor | null = null; + +const getAdvisorClient = async (): Promise => { + if (!advisorClient) { + const sdkClient = await sdkForProject(); + advisorClient = new Advisor(sdkClient); + } + return advisorClient; +}; + +export const advisor = new Command("advisor") + .description(commandDescriptions["advisor"] ?? "") + .configureHelp({ + helpWidth: process.stdout.columns || 80, + }); + +const advisorListReportsCommand = advisor + .command(`list-reports`) + .description(`Get a list of all the project's analyzer reports. You can use the query params to filter your results. +`) + .option(`--queries [queries...]`, `Raw Appwrite JSON query strings (legacy). Use this for advanced queries or automation; for common filtering, sorting, and pagination prefer --filter, --sort-asc, --sort-desc, --limit, and --offset. When mixed, raw --queries are sent before generated flag queries. Array of query strings generated using the Query class provided by the SDK. Learn more about queries (https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: appId, type, targetType, target, analyzedAt`) + .option( + `--total [value]`, + `When set to false, the total count returned will be 0 and will not be calculated.`, + (value: string | undefined) => + value === undefined ? true : parseBool(value), + ) + .option(`--filter `, `Filter using a simple comparison expression. Repeat for multiple filters. Supports field=value, field!=value, field>value, field>=value, field collectQueryValue(parseFilterQuery(value), previous)) + .option(`--where `, `Deprecated. Use --filter instead. Filter using a simple comparison expression. Repeat for multiple filters.`, (value: string, previous: string[] | undefined) => collectQueryValue(parseDeprecatedWhereQuery(value), previous)) + .option(`--sort-asc `, `Sort results by an attribute in ascending order. Repeat for multiple sort fields.`, (value: string, previous: string[] | undefined) => collectQueryValue(value, previous)) + .option(`--sort-desc `, `Sort results by an attribute in descending order. Repeat for multiple sort fields.`, (value: string, previous: string[] | undefined) => collectQueryValue(value, previous)) + .option(`--limit `, `Maximum number of results to return.`, parseInteger) + .option(`--offset `, `Number of results to skip.`, parseInteger) + .option(`--cursor-after `, `Return results after this cursor ID.`) + .option(`--cursor-before `, `Return results before this cursor ID.`) + .action( + actionRunner( + async ({ queries, total, filter, where, sortAsc, sortDesc, cursorAfter, cursorBefore, limit, offset }) => + parse(await (await getAdvisorClient()).listReports(buildQueries({ queries, filter, where, sortAsc, sortDesc, cursorAfter, cursorBefore, limit, offset }), total)), + ), + ); + + +const advisorGetReportCommand = advisor + .command(`get-report`) + .description(`Get an analyzer report by its unique ID. The response includes the report's metadata and the nested insights it produced. +`) + .requiredOption(`--report-id `, `Report ID.`) + .action( + actionRunner( + async ({ reportId }) => + parse(await (await getAdvisorClient()).getReport(reportId)), + ), + ); + + +const advisorDeleteReportCommand = advisor + .command(`delete-report`) + .description(`Delete an analyzer report by its unique ID. Nested insights and CTA metadata are removed asynchronously by the deletes worker. +`) + .requiredOption(`--report-id `, `Report ID.`) + .action( + actionRunner( + async ({ reportId }) => + parse(await (await getAdvisorClient()).deleteReport(reportId)), + ), + ); + + +const advisorListInsightsCommand = advisor + .command(`list-insights`) + .description(`List the insights produced under a single analyzer report. You can use the query params to filter your results further. +`) + .requiredOption(`--report-id `, `Parent report ID.`) + .option(`--queries [queries...]`, `Raw Appwrite JSON query strings (legacy). Use this for advanced queries or automation; for common filtering, sorting, and pagination prefer --filter, --sort-asc, --sort-desc, --limit, and --offset. When mixed, raw --queries are sent before generated flag queries. Array of query strings generated using the Query class provided by the SDK. Learn more about queries (https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: type, severity, status, resourceType, resourceId, parentResourceType, parentResourceId, analyzedAt, dismissedAt, dismissedBy`) + .option( + `--total [value]`, + `When set to false, the total count returned will be 0 and will not be calculated.`, + (value: string | undefined) => + value === undefined ? true : parseBool(value), + ) + .option(`--filter `, `Filter using a simple comparison expression. Repeat for multiple filters. Supports field=value, field!=value, field>value, field>=value, field collectQueryValue(parseFilterQuery(value), previous)) + .option(`--where `, `Deprecated. Use --filter instead. Filter using a simple comparison expression. Repeat for multiple filters.`, (value: string, previous: string[] | undefined) => collectQueryValue(parseDeprecatedWhereQuery(value), previous)) + .option(`--sort-asc `, `Sort results by an attribute in ascending order. Repeat for multiple sort fields.`, (value: string, previous: string[] | undefined) => collectQueryValue(value, previous)) + .option(`--sort-desc `, `Sort results by an attribute in descending order. Repeat for multiple sort fields.`, (value: string, previous: string[] | undefined) => collectQueryValue(value, previous)) + .option(`--limit `, `Maximum number of results to return.`, parseInteger) + .option(`--offset `, `Number of results to skip.`, parseInteger) + .option(`--cursor-after `, `Return results after this cursor ID.`) + .option(`--cursor-before `, `Return results before this cursor ID.`) + .action( + actionRunner( + async ({ reportId, queries, total, filter, where, sortAsc, sortDesc, cursorAfter, cursorBefore, limit, offset }) => + parse(await (await getAdvisorClient()).listInsights(reportId, buildQueries({ queries, filter, where, sortAsc, sortDesc, cursorAfter, cursorBefore, limit, offset }), total)), + ), + ); + + +const advisorGetInsightCommand = advisor + .command(`get-insight`) + .description(`Get an insight by its unique ID, scoped to its parent report. +`) + .requiredOption(`--report-id `, `Parent report ID.`) + .requiredOption(`--insight-id `, `Insight ID.`) + .action( + actionRunner( + async ({ reportId, insightId }) => + parse(await (await getAdvisorClient()).getInsight(reportId, insightId)), + ), + ); + + diff --git a/lib/commands/services/presences.ts b/lib/commands/services/presences.ts new file mode 100644 index 00000000..8f2c155b --- /dev/null +++ b/lib/commands/services/presences.ts @@ -0,0 +1,144 @@ +import { Command } from "commander"; +import { + buildQueries, + collectQueryValue, + parseDeprecatedWhereQuery, + parseFilterQuery, +} from "../utils/query.js"; +import { sdkForProject } from "../../sdks.js"; +import { + actionRunner, + commandDescriptions, + success, + parse, + parseBool, + parseInteger, + parseJsonObject, +} from "../../parser.js"; +import { Presences } from "@appwrite.io/console"; + +let presencesClient: Presences | null = null; + +const getPresencesClient = async (): Promise => { + if (!presencesClient) { + const sdkClient = await sdkForProject(); + presencesClient = new Presences(sdkClient); + } + return presencesClient; +}; + +export const presences = new Command("presences") + .description(commandDescriptions["presences"] ?? "") + .configureHelp({ + helpWidth: process.stdout.columns || 80, + }); + +const presencesListCommand = presences + .command(`list`) + .description(`List presence logs. Expired entries are filtered out automatically. +`) + .option(`--queries [queries...]`, `Raw Appwrite JSON query strings (legacy). Use this for advanced queries or automation; for common filtering, sorting, and pagination prefer --filter, --sort-asc, --sort-desc, --limit, and --offset. When mixed, raw --queries are sent before generated flag queries. Array of query strings generated using the Query class provided by the SDK.`) + .option( + `--total [value]`, + `When set to false, the total count returned will be 0 and will not be calculated.`, + (value: string | undefined) => + value === undefined ? true : parseBool(value), + ) + .option(`--ttl `, `TTL (seconds) for caching list responses. Responses are stored in an in-memory key-value cache, keyed per project, collection, schema version (attributes and indexes), caller authorization roles, and the exact query — so users with different permissions never share cached entries. Schema changes invalidate cached entries automatically; document writes do not, so choose a TTL you are comfortable serving as stale data. Set to 0 to disable caching. Must be between 0 and 86400 (24 hours).`, parseInteger) + .option(`--filter `, `Filter using a simple comparison expression. Repeat for multiple filters. Supports field=value, field!=value, field>value, field>=value, field collectQueryValue(parseFilterQuery(value), previous)) + .option(`--where `, `Deprecated. Use --filter instead. Filter using a simple comparison expression. Repeat for multiple filters.`, (value: string, previous: string[] | undefined) => collectQueryValue(parseDeprecatedWhereQuery(value), previous)) + .option(`--sort-asc `, `Sort results by an attribute in ascending order. Repeat for multiple sort fields.`, (value: string, previous: string[] | undefined) => collectQueryValue(value, previous)) + .option(`--sort-desc `, `Sort results by an attribute in descending order. Repeat for multiple sort fields.`, (value: string, previous: string[] | undefined) => collectQueryValue(value, previous)) + .option(`--limit `, `Maximum number of results to return.`, parseInteger) + .option(`--offset `, `Number of results to skip.`, parseInteger) + .option(`--cursor-after `, `Return results after this cursor ID.`) + .option(`--cursor-before `, `Return results before this cursor ID.`) + .action( + actionRunner( + async ({ queries, total, ttl, filter, where, sortAsc, sortDesc, cursorAfter, cursorBefore, limit, offset }) => + parse(await (await getPresencesClient()).list(buildQueries({ queries, filter, where, sortAsc, sortDesc, cursorAfter, cursorBefore, limit, offset }), total, ttl)), + ), + ); + + +const presencesGetUsageCommand = presences + .command(`get-usage`) + .description(`Get presence usage metrics, including the current total of online users and historical online user counts for the selected time range. +`) + .option(`--range `, `Date range.`) + .action( + actionRunner( + async ({ range }) => + parse(await (await getPresencesClient()).getUsage(range)), + ), + ); + + +const presencesGetCommand = presences + .command(`get`) + .description(`Get a presence log by its unique ID. Entries whose \`expiresAt\` is in the past are treated as not found. +`) + .requiredOption(`--presence-id `, `Presence unique ID.`) + .action( + actionRunner( + async ({ presenceId }) => + parse(await (await getPresencesClient()).get(presenceId)), + ), + ); + + +const presencesUpsertCommand = presences + .command(`upsert`) + .description(`Create or update a presence log by its user ID. +`) + .requiredOption(`--presence-id `, `Presence unique ID.`) + .requiredOption(`--user-id `, `User ID.`) + .requiredOption(`--status `, `Presence status.`) + .option(`--permissions [permissions...]`, `An array of permissions strings. By default, only the current user is granted all permissions. Learn more about permissions (https://appwrite.io/docs/permissions).`) + .option(`--expires-at `, `Presence expiry datetime.`) + .option(`--metadata `, `Presence metadata object.`) + .action( + actionRunner( + async ({ presenceId, userId, status, permissions, expiresAt, metadata }) => + parse(await (await getPresencesClient()).upsert(presenceId, userId, status, permissions, expiresAt, parseJsonObject(metadata, "--metadata"))), + ), + ); + + +const presencesUpdatePresenceCommand = presences + .command(`update-presence`) + .description(`Update a presence log by its unique ID. Using the patch method you can pass only specific fields that will get updated. +`) + .requiredOption(`--presence-id `, `Presence unique ID.`) + .requiredOption(`--user-id `, `User ID.`) + .option(`--status `, `Presence status.`) + .option(`--expires-at `, `Presence expiry datetime.`) + .option(`--metadata `, `Presence metadata object.`) + .option(`--permissions [permissions...]`, `An array of permissions strings. By default, only the current user is granted all permissions. Learn more about permissions (https://appwrite.io/docs/permissions).`) + .option( + `--purge [value]`, + `When true, purge cached responses used by list presences endpoint.`, + (value: string | undefined) => + value === undefined ? true : parseBool(value), + ) + .action( + actionRunner( + async ({ presenceId, userId, status, expiresAt, metadata, permissions, purge }) => + parse(await (await getPresencesClient()).updatePresence(presenceId, userId, status, expiresAt, parseJsonObject(metadata, "--metadata"), permissions, purge)), + ), + ); + + +const presencesDeleteCommand = presences + .command(`delete`) + .description(`Delete a presence log by its unique ID. +`) + .requiredOption(`--presence-id `, `Presence unique ID.`) + .action( + actionRunner( + async ({ presenceId }) => + parse(await (await getPresencesClient()).delete(presenceId)), + ), + ); + + diff --git a/lib/commands/services/project.ts b/lib/commands/services/project.ts index e400d9bc..59f0c45e 100644 --- a/lib/commands/services/project.ts +++ b/lib/commands/services/project.ts @@ -32,6 +32,16 @@ export const project = new Command("project") helpWidth: process.stdout.columns || 80, }); +const projectGetCommand = project + .command(`get`) + .description(`Get a project.`) + .action( + actionRunner( + async () => parse(await (await getProjectClient()).get()), + ), + ); + + const projectDeleteCommand = project .command(`delete`) .description(`Delete a project.`) @@ -1290,14 +1300,14 @@ const projectListPoliciesCommand = project ); -const projectUpdateDenyCanonicalEmailPolicyCommand = project - .command(`update-deny-canonical-email-policy`) - .description(`Configures if email aliases such as subaddresses and emails with suffixes are denied during new users sign-ups and email updates.`) - .requiredOption(`--enabled `, `Set whether or not to block email aliases during signup and email updates.`, parseBool) +const projectUpdateDenyAliasedEmailPolicyCommand = project + .command(`update-deny-aliased-email-policy`) + .description(`Configures if aliased emails such as subaddresses and emails with suffixes are denied during new users sign-ups and email updates.`) + .requiredOption(`--enabled `, `Set whether or not to block aliased emails during signup and email updates.`, parseBool) .action( actionRunner( async ({ enabled }) => - parse(await (await getProjectClient()).updateDenyCanonicalEmailPolicy(enabled)), + parse(await (await getProjectClient()).updateDenyAliasedEmailPolicy(enabled)), ), ); @@ -1493,7 +1503,7 @@ const projectUpdateProtocolCommand = project const projectUpdateServiceCommand = project .command(`update-service`) .description(`Update properties of a specific service. Use this endpoint to enable or disable a service in your project. `) - .requiredOption(`--service-id `, `Service name. Can be one of: account, avatars, databases, tablesdb, locale, health, project, storage, teams, users, vcs, sites, functions, proxy, graphql, migrations, messaging`) + .requiredOption(`--service-id `, `Service name. Can be one of: account, avatars, databases, tablesdb, locale, health, project, storage, teams, users, vcs, sites, functions, proxy, graphql, migrations, messaging, advisor`) .requiredOption(`--enabled `, `Service status.`, parseBool) .action( actionRunner( @@ -1508,12 +1518,12 @@ const projectUpdateSMTPCommand = project .description(`Update the SMTP configuration for your project. Use this endpoint to configure your project's SMTP provider with your custom settings for sending transactional emails.`) .option(`--host `, `SMTP server hostname (domain)`) .option(`--port `, `SMTP server port`, parseInteger) - .option(`--username `, `SMTP server username. Leave empty for no authorization.`) - .option(`--password `, `SMTP server password. Leave empty for no authorization. This property is stored securely and cannot be read in future (write-only).`) - .option(`--sender-email `, `Email address shown in inbox as the sender of the email.`) - .option(`--sender-name `, `Name shown in inbox as the sender of the email.`) - .option(`--reply-to-email `, `Email used when user replies to the email.`) - .option(`--reply-to-name `, `Name used when user replies to the email.`) + .option(`--username `, `SMTP server username. Pass an empty string to clear a previously set value.`) + .option(`--password `, `SMTP server password. Pass an empty string to clear a previously set value. This property is stored securely and cannot be read in future (write-only).`) + .option(`--sender-email `, `Email address shown in inbox as the sender of the email. Pass an empty string to clear a previously set value.`) + .option(`--sender-name `, `Name shown in inbox as the sender of the email. Pass an empty string to clear a previously set value.`) + .option(`--reply-to-email `, `Email used when user replies to the email. Pass an empty string to clear a previously set value.`) + .option(`--reply-to-name `, `Name used when user replies to the email. Pass an empty string to clear a previously set value.`) .option(`--secure `, `Configures if communication with SMTP server is encrypted. Allowed values are: tls, ssl. Leave empty for no encryption.`) .option( `--enabled [value]`, @@ -1569,8 +1579,8 @@ const projectUpdateEmailTemplateCommand = project .option(`--subject `, `Subject of the email template. Can be up to 255 characters.`) .option(`--message `, `Plain or HTML body of the email template message. Can be up to 10MB of content.`) .option(`--sender-name `, `Name of the email sender.`) - .option(`--sender-email `, `Email of the sender.`) - .option(`--reply-to-email `, `Reply to email.`) + .option(`--sender-email `, `Email of the sender. Pass an empty string to clear a previously set value.`) + .option(`--reply-to-email `, `Reply to email. Pass an empty string to clear a previously set value.`) .option(`--reply-to-name `, `Reply to name.`) .action( actionRunner( diff --git a/lib/commands/services/projects.ts b/lib/commands/services/projects.ts index 13921687..3219de24 100644 --- a/lib/commands/services/projects.ts +++ b/lib/commands/services/projects.ts @@ -68,31 +68,10 @@ const projectsCreateCommand = projects .requiredOption(`--name `, `Project name. Max length: 128 chars.`) .requiredOption(`--team-id `, `Team unique ID.`) .option(`--region `, `Project Region.`) - .option(`--description `, `Project description. Max length: 256 chars.`) - .option(`--logo `, `Project logo.`) - .option(`--url `, `Project URL.`) - .option(`--legal-name `, `Project legal Name. Max length: 256 chars.`) - .option(`--legal-country `, `Project legal Country. Max length: 256 chars.`) - .option(`--legal-state `, `Project legal State. Max length: 256 chars.`) - .option(`--legal-city `, `Project legal City. Max length: 256 chars.`) - .option(`--legal-address `, `Project legal Address. Max length: 256 chars.`) - .option(`--legal-tax-id `, `Project legal Tax ID. Max length: 256 chars.`) - .action( - actionRunner( - async ({ projectId, name, teamId, region, description, logo, url, legalName, legalCountry, legalState, legalCity, legalAddress, legalTaxId }) => - parse(await (await getProjectsClient()).create(projectId, name, teamId, region, description, logo, url, legalName, legalCountry, legalState, legalCity, legalAddress, legalTaxId)), - ), - ); - - -const projectsGetCommand = projects - .command(`get`) - .description(`Get a project by its unique ID. This endpoint allows you to retrieve the project's details, including its name, description, team, region, and other metadata. `) - .requiredOption(`--project-id `, `Project unique ID.`) .action( actionRunner( - async ({ projectId }) => - parse(await (await getProjectsClient()).get(projectId)), + async ({ projectId, name, teamId, region }) => + parse(await (await getProjectsClient()).create(projectId, name, teamId, region)), ), ); diff --git a/lib/commands/services/usage.ts b/lib/commands/services/usage.ts new file mode 100644 index 00000000..c1fea0dc --- /dev/null +++ b/lib/commands/services/usage.ts @@ -0,0 +1,86 @@ +import { Command } from "commander"; +import { + buildQueries, + collectQueryValue, + parseDeprecatedWhereQuery, + parseFilterQuery, +} from "../utils/query.js"; +import { sdkForProject } from "../../sdks.js"; +import { + actionRunner, + commandDescriptions, + success, + parse, + parseBool, + parseInteger, +} from "../../parser.js"; +import { Usage } from "@appwrite.io/console"; + +let usageClient: Usage | null = null; + +const getUsageClient = async (): Promise => { + if (!usageClient) { + const sdkClient = await sdkForProject(); + usageClient = new Usage(sdkClient); + } + return usageClient; +}; + +export const usage = new Command("usage") + .description(commandDescriptions["usage"] ?? "") + .configureHelp({ + helpWidth: process.stdout.columns || 80, + }); + +const usageListEventsCommand = usage + .command(`list-events`) + .description(`Query usage event metrics from the usage database. Returns individual event rows with full metadata. Pass Query objects as JSON strings to filter, paginate, and order results. Supported query methods: equal, greaterThanEqual, lessThanEqual, orderAsc, orderDesc, limit, offset. Supported filter attributes: metric, path, method, status, resource, resourceId, country, userAgent, time (these match the underlying column names — note that the response surfaces \`resource\` as \`resourceType\` and \`country\` as \`countryCode\`). When no time filter is supplied the endpoint defaults to the last 7 days. Default \`limit(100)\` is applied if none is given; user-supplied limits are capped at 500. The \`total\` field is capped at 5000 to keep counts predictable — pass \`total=false\` to skip the count entirely.`) + .option(`--queries [queries...]`, `Raw Appwrite JSON query strings (legacy). Use this for advanced queries or automation; for common filtering, sorting, and pagination prefer --filter, --sort-asc, --sort-desc, --limit, and --offset. When mixed, raw --queries are sent before generated flag queries. Array of query strings as JSON. Supported: equal("metric", [...]), equal("path", [...]), equal("method", [...]), equal("status", [...]), equal("resource", [...]), equal("resourceId", [...]), equal("country", [...]), equal("userAgent", [...]), greaterThanEqual("time", "..."), lessThanEqual("time", "..."), orderAsc("time"), orderDesc("time"), limit(N), offset(N).`) + .option( + `--total [value]`, + `When set to false, the total count returned will be 0 and will not be calculated.`, + (value: string | undefined) => + value === undefined ? true : parseBool(value), + ) + .option(`--filter `, `Filter using a simple comparison expression. Repeat for multiple filters. Supports field=value, field!=value, field>value, field>=value, field collectQueryValue(parseFilterQuery(value), previous)) + .option(`--where `, `Deprecated. Use --filter instead. Filter using a simple comparison expression. Repeat for multiple filters.`, (value: string, previous: string[] | undefined) => collectQueryValue(parseDeprecatedWhereQuery(value), previous)) + .option(`--sort-asc `, `Sort results by an attribute in ascending order. Repeat for multiple sort fields.`, (value: string, previous: string[] | undefined) => collectQueryValue(value, previous)) + .option(`--sort-desc `, `Sort results by an attribute in descending order. Repeat for multiple sort fields.`, (value: string, previous: string[] | undefined) => collectQueryValue(value, previous)) + .option(`--limit `, `Maximum number of results to return.`, parseInteger) + .option(`--offset `, `Number of results to skip.`, parseInteger) + .option(`--cursor-after `, `Return results after this cursor ID.`) + .option(`--cursor-before `, `Return results before this cursor ID.`) + .action( + actionRunner( + async ({ queries, total, filter, where, sortAsc, sortDesc, cursorAfter, cursorBefore, limit, offset }) => + parse(await (await getUsageClient()).listEvents(buildQueries({ queries, filter, where, sortAsc, sortDesc, cursorAfter, cursorBefore, limit, offset }), total)), + ), + ); + + +const usageListGaugesCommand = usage + .command(`list-gauges`) + .description(`Query usage gauge metrics (point-in-time resource snapshots) from the usage database. Returns individual gauge snapshots with metric, value, and timestamp. Pass Query objects as JSON strings to filter, paginate, and order results. Supported query methods: equal, greaterThanEqual, lessThanEqual, orderAsc, orderDesc, limit, offset. Supported filter attributes: metric, time. Use \`orderDesc("time"), limit(1)\` to fetch the most recent snapshot. When no time filter is supplied the endpoint defaults to the last 7 days. Default \`limit(100)\` is applied if none is given; user-supplied limits are capped at 500. The \`total\` field is capped at 5000 to keep counts predictable — pass \`total=false\` to skip the count entirely.`) + .option(`--queries [queries...]`, `Raw Appwrite JSON query strings (legacy). Use this for advanced queries or automation; for common filtering, sorting, and pagination prefer --filter, --sort-asc, --sort-desc, --limit, and --offset. When mixed, raw --queries are sent before generated flag queries. Array of query strings as JSON. Supported: equal("metric", [...]), greaterThanEqual("time", "..."), lessThanEqual("time", "..."), orderAsc("time"), orderDesc("time"), limit(N), offset(N).`) + .option( + `--total [value]`, + `When set to false, the total count returned will be 0 and will not be calculated.`, + (value: string | undefined) => + value === undefined ? true : parseBool(value), + ) + .option(`--filter `, `Filter using a simple comparison expression. Repeat for multiple filters. Supports field=value, field!=value, field>value, field>=value, field collectQueryValue(parseFilterQuery(value), previous)) + .option(`--where `, `Deprecated. Use --filter instead. Filter using a simple comparison expression. Repeat for multiple filters.`, (value: string, previous: string[] | undefined) => collectQueryValue(parseDeprecatedWhereQuery(value), previous)) + .option(`--sort-asc `, `Sort results by an attribute in ascending order. Repeat for multiple sort fields.`, (value: string, previous: string[] | undefined) => collectQueryValue(value, previous)) + .option(`--sort-desc `, `Sort results by an attribute in descending order. Repeat for multiple sort fields.`, (value: string, previous: string[] | undefined) => collectQueryValue(value, previous)) + .option(`--limit `, `Maximum number of results to return.`, parseInteger) + .option(`--offset `, `Number of results to skip.`, parseInteger) + .option(`--cursor-after `, `Return results after this cursor ID.`) + .option(`--cursor-before `, `Return results before this cursor ID.`) + .action( + actionRunner( + async ({ queries, total, filter, where, sortAsc, sortDesc, cursorAfter, cursorBefore, limit, offset }) => + parse(await (await getUsageClient()).listGauges(buildQueries({ queries, filter, where, sortAsc, sortDesc, cursorAfter, cursorBefore, limit, offset }), total)), + ), + ); + + diff --git a/lib/constants.ts b/lib/constants.ts index 6fa5730d..7a61ae0b 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -1,7 +1,7 @@ // SDK export const SDK_TITLE = 'Appwrite'; export const SDK_TITLE_LOWER = 'appwrite'; -export const SDK_VERSION = '21.0.0'; +export const SDK_VERSION = '21.1.0'; export const SDK_NAME = 'Command Line'; export const SDK_PLATFORM = 'console'; export const SDK_LANGUAGE = 'cli'; diff --git a/package-lock.json b/package-lock.json index 4f314e2b..7c213a53 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "appwrite-cli", - "version": "21.0.0", + "version": "21.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "appwrite-cli", - "version": "21.0.0", + "version": "21.1.0", "license": "BSD-3-Clause", "dependencies": { "@appwrite.io/console": "13.0.0", diff --git a/package.json b/package.json index 095df95d..1f70bfa4 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "type": "module", "homepage": "https://appwrite.io/support", "description": "Appwrite is an open-source self-hosted backend server that abstracts and simplifies complex and repetitive development tasks behind a very simple REST API", - "version": "21.0.0", + "version": "21.1.0", "license": "BSD-3-Clause", "main": "dist/index.cjs", "module": "dist/index.js", diff --git a/scoop/appwrite.config.json b/scoop/appwrite.config.json index 69b3cf94..5bbd88d2 100644 --- a/scoop/appwrite.config.json +++ b/scoop/appwrite.config.json @@ -1,12 +1,12 @@ { "$schema": "https://raw.githubusercontent.com/ScoopInstaller/Scoop/master/schema.json", - "version": "21.0.0", + "version": "21.1.0", "description": "The Appwrite CLI is a command-line application that allows you to interact with Appwrite and perform server-side tasks using your terminal.", "homepage": "https://github.com/appwrite/sdk-for-cli", "license": "BSD-3-Clause", "architecture": { "64bit": { - "url": "https://github.com/appwrite/sdk-for-cli/releases/download/21.0.0/appwrite-cli-win-x64.exe", + "url": "https://github.com/appwrite/sdk-for-cli/releases/download/21.1.0/appwrite-cli-win-x64.exe", "bin": [ [ "appwrite-cli-win-x64.exe", @@ -15,7 +15,7 @@ ] }, "arm64": { - "url": "https://github.com/appwrite/sdk-for-cli/releases/download/21.0.0/appwrite-cli-win-arm64.exe", + "url": "https://github.com/appwrite/sdk-for-cli/releases/download/21.1.0/appwrite-cli-win-arm64.exe", "bin": [ [ "appwrite-cli-win-arm64.exe",