pairit 0.1.8 → 0.1.9

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.
Files changed (3) hide show
  1. package/README.md +19 -0
  2. package/dist/index.js +52 -1
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -12,6 +12,7 @@ A lightweight command line utility for working with Pairit experiment configurat
12
12
  - `media list` — list media objects in Cloud Storage
13
13
  - `media delete` — delete a media object from Cloud Storage
14
14
  - `data export` — export sessions, events, and chat messages for a config as CSV/JSON/JSONL
15
+ - `admin add-user` / `admin remove-user` / `admin list-users` — manage the manager allowlist (admin role required)
15
16
 
16
17
  ## Install
17
18
 
@@ -77,6 +78,24 @@ bun run apps/manager/cli/src/index.ts media delete onboarding/logo.png --force
77
78
 
78
79
  `config compile` writes `configs/simple-survey-basic.json` next to the source YAML. `config upload` defaults the config id to a 16-character base64url string derived from the SHA-256 hash of the compiled JSON (unless `--config-id` overrides it).
79
80
 
81
+ ## Manager allowlist (admin)
82
+
83
+ Manager sign-ins are gated on a Mongo allowlist. Anyone who isn't on it lands on `/access-denied` after Google sign-in. Use these commands to manage who can sign in. The calling CLI user must be an admin.
84
+
85
+ ```bash
86
+ pairit admin list-users # Show everyone on the allowlist
87
+ pairit admin add-user alice@example.com # Add as researcher
88
+ pairit admin add-user bob@example.com --admin # Add as admin (can manage the allowlist)
89
+ pairit admin remove-user alice@example.com # Remove and revoke their sessions
90
+ pairit admin remove-user alice@example.com --force # Skip confirmation prompt
91
+ ```
92
+
93
+ Notes:
94
+ - Bootstrap: `MANAGER_BOOTSTRAP_ADMIN_EMAIL` is seeded as admin on every manager boot. Set it before the first deploy or you'll have no admins.
95
+ - Day-1 migration: on first boot, existing config owners are backfilled as researchers (not the entire user collection, which is shared with lab participants).
96
+ - Self-removal is blocked.
97
+ - The rejection page contact is set by `MANAGER_ADMIN_CONTACT_EMAIL` (defaults to `harang@pairium.ai`).
98
+
80
99
  ## Per-experiment LLM credentials
81
100
 
82
101
  If your config uses AI agents, attach provider credentials when uploading:
package/dist/index.js CHANGED
@@ -9666,7 +9666,7 @@ async function loadKeytar() {
9666
9666
 
9667
9667
  // src/index.ts
9668
9668
  var program2 = new Command;
9669
- program2.name("pairit").description("CLI for Pairit experiment configs").version("0.1.8");
9669
+ program2.name("pairit").description("CLI for Pairit experiment configs").version("0.1.9");
9670
9670
  program2.command("login").description("Login to Pairit Manager").action(async () => {
9671
9671
  try {
9672
9672
  await login();
@@ -9817,6 +9817,57 @@ dataCommand.command("export").argument("<configId>", "Config ID to export data f
9817
9817
  reportCliError("Export failed", error);
9818
9818
  }
9819
9819
  });
9820
+ var adminCommand = program2.command("admin").description("Manage manager allowlist (admin only)");
9821
+ adminCommand.command("add-user").argument("<email>", "Email to allow").option("--admin", "Grant admin privileges").description("Add a user to the manager allowlist").action(async (email, options) => {
9822
+ try {
9823
+ const response = await callFunctions("/admin/users", {
9824
+ method: "POST",
9825
+ headers: { "Content-Type": "application/json" },
9826
+ body: JSON.stringify({ email, isAdmin: Boolean(options.admin) })
9827
+ });
9828
+ const entry = response?.user;
9829
+ if (!entry)
9830
+ throw new Error("No user returned");
9831
+ console.log(`✓ Added ${entry.email} (${entry.isAdmin ? "admin" : "researcher"})`);
9832
+ } catch (error) {
9833
+ reportCliError("Add user failed", error);
9834
+ }
9835
+ });
9836
+ adminCommand.command("remove-user").argument("<email>", "Email to remove").option("--force", "Skip confirmation").description("Remove a user from the manager allowlist and revoke sessions").action(async (email, options) => {
9837
+ try {
9838
+ if (!options.force) {
9839
+ const confirmed = await promptConfirm(`Remove ${email} from allowlist? (y/N)`);
9840
+ if (!confirmed) {
9841
+ console.log("Removal aborted");
9842
+ return;
9843
+ }
9844
+ }
9845
+ await callFunctions(`/admin/users/${encodeURIComponent(email)}`, {
9846
+ method: "DELETE"
9847
+ });
9848
+ console.log(`✓ Removed ${email}`);
9849
+ } catch (error) {
9850
+ reportCliError("Remove user failed", error);
9851
+ }
9852
+ });
9853
+ adminCommand.command("list-users").description("List users on the manager allowlist").action(async () => {
9854
+ try {
9855
+ const response = await callFunctions("/admin/users", {
9856
+ method: "GET"
9857
+ });
9858
+ const users = response?.users ?? [];
9859
+ if (!users.length) {
9860
+ console.log("No users on the allowlist");
9861
+ return;
9862
+ }
9863
+ users.forEach((u) => {
9864
+ const role = u.isAdmin ? "admin" : "researcher";
9865
+ console.log(`${u.email} | ${role} | addedBy=${u.addedBy} | addedAt=${u.addedAt}`);
9866
+ });
9867
+ } catch (error) {
9868
+ reportCliError("List users failed", error);
9869
+ }
9870
+ });
9820
9871
  program2.parseAsync(process9.argv).catch((err) => {
9821
9872
  console.error(err);
9822
9873
  process9.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pairit",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "CLI for the Pairit behavioral science experiment platform",
5
5
  "type": "module",
6
6
  "bin": {