Skip to content

Add pl-cli: CLI for Platforma server state manipulation#1516

Merged
dbolotin merged 6 commits intomainfrom
feature/pl-cli
Mar 18, 2026
Merged

Add pl-cli: CLI for Platforma server state manipulation#1516
dbolotin merged 6 commits intomainfrom
feature/pl-cli

Conversation

@dbolotin
Copy link
Copy Markdown
Member

Summary

  • New @platforma-sdk/pl-cli package in tools/pl-cli/
  • CLI for direct Platforma server state manipulation via PlClient + transactions
  • Uses oclif framework (matching existing block-tools and pl-bootstrap)

Commands

Project management (standard user auth):

  • pl-cli project list — list all projects (table or JSON)
  • pl-cli project info <id|label> — show project details (schema, blocks, timestamps)
  • pl-cli project duplicate <id|label> — duplicate with auto-rename on collision
  • pl-cli project rename <id|label> --name "X" — rename a project
  • pl-cli project delete <id|label> — delete with confirmation (or --force)

Admin operations (controller credentials via PL_ADMIN_USER/PL_ADMIN_PASSWORD):

  • pl-cli admin copy-project — copy project between users
  • pl-cli admin user-list — list user roots on server

Design decisions

  • Lightweight: uses PlClient + transactions directly, no full MiddleLayer initialization
  • Architecture supports adding MiddleLayer later for heavier operations (e.g. running blocks)
  • All commands support --format text (human-readable) and --format json (automation)
  • Project resolution by both field ID and label
  • Admin commands use separate credential env vars for controller-level access

Test plan

  • Tested project list against live server
  • Tested project duplicate with auto-rename deduplication
  • Tested admin copy-project (same user to same user)
  • Tested project info and project delete
  • Tested JSON output format
  • Type check passes
  • Linter passes (oxlint)

New @platforma-sdk/pl-cli package with commands:
- project list/info/duplicate/rename/delete
- admin copy-project (cross-user, controller auth)
- admin user-list

Supports --format text|json output for all commands.
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Mar 17, 2026

🦋 Changeset detected

Latest commit: 6dd1552

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@platforma-sdk/pl-cli Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request delivers a new command-line interface (CLI) tool, pl-cli, to streamline interactions with the Platforma server. It enables users to perform various project management tasks and provides administrators with tools for cross-user operations. The CLI is designed to be lightweight and efficient, directly utilizing the PlClient and transaction mechanisms, offering a powerful way to manage Platforma server state programmatically or interactively.

Highlights

  • New CLI Tool: Introduced @platforma-sdk/pl-cli, a new command-line interface for direct Platforma server state manipulation.
  • Project Management Commands: Added commands for standard user operations including project list, project info, project duplicate, project rename, and project delete.
  • Admin Operations: Included administrative commands such as admin copy-project (for copying projects between users) and admin user-list (to list user roots).
  • Framework and Output: The CLI is built using the oclif framework and all commands support both human-readable text and JSON output formats.
Changelog
  • .changeset/pl-cli.md
    • Added a new changeset entry detailing the pl-cli tool and its commands.
  • pnpm-lock.yaml
    • Updated to include dependencies for the new pl-cli package.
  • pnpm-workspace.yaml
    • Added tools/pl-cli to the workspace packages.
  • tools/pl-cli/.oxfmtrc.json
    • Added OxFmt configuration for the new CLI package.
  • tools/pl-cli/.oxlintrc.json
    • Added OxLint configuration for the new CLI package.
  • tools/pl-cli/bin/run.js
    • Added the main executable script for the pl-cli tool.
  • tools/pl-cli/package.json
    • Added package metadata, scripts, dependencies, and oclif configuration for pl-cli.
  • tools/pl-cli/src/admin_base_command.ts
    • Added a base class for admin-specific CLI commands, handling controller credentials.
  • tools/pl-cli/src/admin_connection.ts
    • Added utilities for establishing admin connections and navigating user roots.
  • tools/pl-cli/src/base_command.ts
    • Added a base class for general CLI commands, handling user authentication and common flags.
  • tools/pl-cli/src/cmd/admin/copy-project.ts
    • Added the admin copy-project command for copying projects between users.
  • tools/pl-cli/src/cmd/admin/user-list.ts
    • Added the admin user-list command to list user root hashes on the server.
  • tools/pl-cli/src/cmd/index.ts
    • Added the command index for oclif, registering all new commands.
  • tools/pl-cli/src/cmd/project/delete.ts
    • Added the project delete command for removing projects.
  • tools/pl-cli/src/cmd/project/duplicate.ts
    • Added the project duplicate command for copying projects within a user.
  • tools/pl-cli/src/cmd/project/info.ts
    • Added the project info command to display detailed project information.
  • tools/pl-cli/src/cmd/project/list.ts
    • Added the project list command to display all projects for a user.
  • tools/pl-cli/src/cmd/project/rename.ts
    • Added the project rename command to change a project's label.
  • tools/pl-cli/src/connection.ts
    • Added utilities for establishing standard user connections to Platforma.
  • tools/pl-cli/src/lib.ts
    • Added an export file for the pl-cli library.
  • tools/pl-cli/src/output.ts
    • Added utilities for formatting and outputting CLI results.
  • tools/pl-cli/src/project_ops.ts
    • Added core logic for project operations like listing, resolving, duplicating, renaming, and deleting.
  • tools/pl-cli/tsconfig.json
    • Added TypeScript configuration for the pl-cli package.
  • tools/pl-cli/vite.config.mts
    • Added Vite build configuration for the pl-cli package.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new and valuable CLI tool, pl-cli, for interacting with the Platforma server. The code is well-structured, leveraging oclif for the command framework and creating clear base classes for different command types (user vs. admin). The separation of concerns with project_ops.ts and connection helpers is also well done.

My review focuses on a few areas for improvement:

  • Performance: Some operations that iterate over projects can be optimized by parallelizing requests.
  • Code Duplication: There's an opportunity to refactor some repeated logic into a shared helper function.
  • Potential Bug: A helper function for output formatting has a minor bug that could cause issues if used in a certain way.

Overall, this is a solid addition to the toolset. The changes are well-thought-out and the CLI provides useful functionality as described.

Comment thread tools/pl-cli/src/cmd/project/duplicate.ts Outdated
Comment thread tools/pl-cli/src/output.ts Outdated
Comment thread tools/pl-cli/src/project_ops.ts Outdated
- Export project model constants, ProjectsField, and duplicateProject
  from pl-middle-layer public API
- Import them in pl-cli instead of duplicating (eliminates drift risk)
- Remove duplicateProjectInTx copy from CLI
- Extract shared CLI flags into cmd-opts.ts (matching monorepo pattern)
- Move navigateToUserRoot from admin_connection to project_ops
- Merge admin connection into connection.ts
- Remove admin user-list (reads wrong resource, no PL API to enumerate users)
- Fix dynamic import of isNullResourceId in duplicate command
- Normalize --auto-rename flag between duplicate and copy-project
- Remove dead output() function, replace with outputJson()
- Remove unused outputFormat accessor from PlCommand
- Expand lib.ts exports for programmatic use
- Add double-connect guard in base commands
- Switch to ESM-only build (type: module) for pl-middle-layer compat
vitest exits 1 when no test files exist. Add --passWithNoTests flag.
@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 17, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 54.27%. Comparing base (e2f2809) to head (6dd1552).
⚠️ Report is 3 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1516   +/-   ##
=======================================
  Coverage   54.27%   54.27%           
=======================================
  Files         242      242           
  Lines       13727    13727           
  Branches     2828     2828           
=======================================
  Hits         7451     7451           
  Misses       5341     5341           
  Partials      935      935           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

…target-user

Merge AdminCommand into PlCommand with dual-mode connect():
- connect() returns { pl, projectListRid } for single-user ops
- connectClient() returns raw PlClient for multi-user admin ops
- AdminTargetFlags (optional) on all project commands
- AdminAuthFlags (required) on purely admin commands like copy-project
- Delete separate admin_base_command.ts
@dbolotin dbolotin enabled auto-merge March 17, 2026 23:52
…jects

- Add getExistingLabelsInTx(tx, projectListRid) to deduplicate label
  reading logic shared between duplicate and admin copy-project
- Use Promise.all for parallel KV fetches in listProjects
Comment thread tools/pl-cli/package.json
"dependencies": {
"@milaboratories/pl-client": "workspace:*",
"@milaboratories/pl-middle-layer": "workspace:*",
"@oclif/core": "catalog:"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we use commander? me and Alexander agreed that we need to migrate block-tools to it, but had no time, and now this new package uses oclif again

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's separately migrate it.

);

const target =
targetUser === flags["source-user"] ? source : await navigateToUserRoot(pl, targetUser);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we not use await in ternary operator? that looks dangerous

tx.createField(field(target.projectListRid, newId), "Dynamic", newPrj);
await tx.commit();

return { rid: await toGlobalResourceId(newPrj), label: newLabel };
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again, please place statements with await on a separate line, not as a part of bigger expression. they should catch the eye of the reader instantly

targetUser,
});
} else {
console.log(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we are using outputJson for json, maybe we should define outputText instead of calling console.log directly?

Comment thread tools/pl-cli/src/cmd/project/delete.ts Outdated
const info = await getProjectInfo(pl, projectListRid, id);

if (!flags.force) {
const readline = await import("node:readline");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why inline import, not on top of the file?

Comment thread tools/pl-cli/src/cmd/project/delete.ts Outdated
const answer = await new Promise<string>((resolve) => {
rl.question(`Delete project "${info.label}" (${info.blockCount} blocks)? [y/N] `, resolve);
});
rl.close();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should it be try-finally to always close?


const newRid = await pl.withWriteTx("duplicateProject", async (tx) => {
const sourceMetaStr = await tx.getKValueString(sourceRid, ProjectMetaKey);
const sourceMeta = JSON.parse(sourceMetaStr);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we add zod validation here maybe?

"admin-user"?: string;
"admin-password"?: string;
}): Promise<PlClient> {
if (this._pl) throw new Error("connectClient() called twice");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it correct? maybe we should re-initialize connection instead?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bug guard - each command runs once and should only connect once. Re-initializing would leak the previous connection. If a command somehow calls this twice, that's a bug we want to catch immediately rather than silently reconnect.

return { pl, projectListRid };
}

protected async finally(_: Error | undefined): Promise<void> {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we can utilize AsyncDisposable somehow instead of manually calling close?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding AsyncDisposable to PlClient is a good idea but belongs in a separate pl-client PR. This finally() is the idiomatic oclif cleanup mechanism.

tx.getKValueStringIfExists(f.value, ProjectLastModifiedTimestamp),
]);

const meta: ProjectMeta = metaStr ? JSON.parse(metaStr) : { label: "(unknown)" };
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again, maybe we need zod? I looks dangerous to just trust the type of JSON.parse

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add it on the level of model in a separate PR.

@dbolotin dbolotin added this pull request to the merge queue Mar 18, 2026
@vadimpiven vadimpiven removed this pull request from the merge queue due to a manual request Mar 18, 2026
…y cleanup

- Add outputText() to output.ts for consistency with outputJson()
- Replace all console.log in commands with outputText()
- Move node:readline import to top of delete.ts
- Wrap readline in try-finally to ensure close on error
@dbolotin dbolotin enabled auto-merge March 18, 2026 14:35
@dbolotin dbolotin added this pull request to the merge queue Mar 18, 2026
Merged via the queue into main with commit 698fdbb Mar 18, 2026
9 checks passed
@dbolotin dbolotin deleted the feature/pl-cli branch March 18, 2026 14:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants