pairit 0.2.0 → 0.3.1

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 +7 -7
  2. package/dist/index.js +65 -8
  3. package/package.json +40 -40
package/README.md CHANGED
@@ -43,8 +43,8 @@ pairit login # Authenticate (required firs
43
43
 
44
44
  pairit config lint path/to/config.yaml
45
45
  pairit config compile path/to/config.yaml
46
- pairit config upload configs/simple-survey-basic.yaml --config-id simple-survey-basic
47
- pairit config upload configs/agent-study.yaml --config-id agent-study --openai-api-key sk-...
46
+ pairit config upload configs/simple-survey-basic.yaml --name simple-survey-basic
47
+ pairit config upload configs/agent-study.yaml --name agent-study --openai-api-key sk-...
48
48
  pairit config list
49
49
  pairit config delete 2f3c4d5e...
50
50
 
@@ -66,8 +66,8 @@ Add `--private` if you need to keep an object private. Use `--bucket <name>` onl
66
66
  # Point at local manager (published CLI defaults to production)
67
67
  export PAIRIT_API_URL=http://localhost:3002
68
68
 
69
- bun run apps/manager/cli/src/index.ts config upload configs/simple-survey-basic.yaml --config-id simple-survey-basic
70
- bun run apps/manager/cli/src/index.ts config upload configs/agent-study.yaml --config-id agent-study --openai-api-key sk-...
69
+ bun run apps/manager/cli/src/index.ts config upload configs/simple-survey-basic.yaml --name simple-survey-basic
70
+ bun run apps/manager/cli/src/index.ts config upload configs/agent-study.yaml --name agent-study --openai-api-key sk-...
71
71
  bun run apps/manager/cli/src/index.ts config list
72
72
  bun run apps/manager/cli/src/index.ts config delete 2f3c4d5e... --force
73
73
 
@@ -76,7 +76,7 @@ bun run apps/manager/cli/src/index.ts media list
76
76
  bun run apps/manager/cli/src/index.ts media delete onboarding/logo.png --force
77
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
+ `config compile` writes `configs/simple-survey-basic.json` next to the source YAML. `config upload` creates or updates a config by name (`--name`, else the YAML `name:` field, else the file name). The server assigns the config id (`<name>-<10 random chars>`) on the first upload, and later uploads under the same name add revisions. `--config-id` updates an existing config by id. See `config history` for past revisions.
80
80
 
81
81
  ## Manager allowlist (admin)
82
82
 
@@ -101,8 +101,8 @@ Notes:
101
101
  If your config uses AI agents, attach provider credentials when uploading:
102
102
 
103
103
  ```bash
104
- pairit config upload path/to/config.yaml --config-id my-exp --openai-api-key sk-...
105
- pairit config upload path/to/config.yaml --config-id my-exp --anthropic-api-key sk-ant-...
104
+ pairit config upload path/to/config.yaml --name my-exp --openai-api-key sk-...
105
+ pairit config upload path/to/config.yaml --name my-exp --anthropic-api-key sk-ant-...
106
106
  ```
107
107
 
108
108
  Notes:
package/dist/index.js CHANGED
@@ -9513,9 +9513,13 @@ function getEnvName() {
9513
9513
  return fromEnv ? parseEnvName(fromEnv) : "production";
9514
9514
  }
9515
9515
  function getApiUrl() {
9516
+ if (selected)
9517
+ return ENVIRONMENTS[selected].api;
9516
9518
  return process2.env.PAIRIT_API_URL || ENVIRONMENTS[getEnvName()].api;
9517
9519
  }
9518
9520
  function getLabUrl() {
9521
+ if (selected)
9522
+ return ENVIRONMENTS[selected].lab;
9519
9523
  return process2.env.PAIRIT_LAB_URL || ENVIRONMENTS[getEnvName()].lab;
9520
9524
  }
9521
9525
  function getCredentialKey() {
@@ -9805,7 +9809,7 @@ async function attachHtmlToCompiledConfig(configPath, compiled) {
9805
9809
 
9806
9810
  // src/index.ts
9807
9811
  var program2 = new Command;
9808
- program2.name("pairit").description("CLI for Pairit experiment configs").version("0.2.0").option("--env <name>", "Target environment: staging or production", (value) => {
9812
+ program2.name("pairit").description("CLI for Pairit experiment configs").version("0.3.1").option("--env <name>", "Target environment: staging or production", (value) => {
9809
9813
  try {
9810
9814
  return parseEnvName(value);
9811
9815
  } catch (error) {
@@ -9853,7 +9857,7 @@ configCommand.command("compile").argument("<config>", "Path to YAML config").des
9853
9857
  process11.exitCode = 1;
9854
9858
  }
9855
9859
  });
9856
- configCommand.command("upload").argument("<config>", "Path to YAML config").option("--config-id <configId>", "Config id (defaults to hash)").option("--metadata <json>", "Optional metadata JSON string").option("--openai-api-key <key>", "OpenAI API key for this experiment").option("--anthropic-api-key <key>", "Anthropic API key for this experiment").description("Compile and upload config via Pairit Functions").action(async (configPath, options) => {
9860
+ configCommand.command("upload").argument("<config>", "Path to YAML config").option("--name <name>", "Config name (defaults to the YAML name field, then the file name)").option("--config-id <configId>", "Update an existing config by id (configs created before names)").option("--metadata <json>", "Optional metadata JSON string").option("--openai-api-key <key>", "OpenAI API key for this experiment").option("--anthropic-api-key <key>", "Anthropic API key for this experiment").description("Compile and upload config via Pairit Functions").action(async (configPath, options) => {
9857
9861
  try {
9858
9862
  const { payload, checksum, attachments } = await buildUploadPayload(configPath, options);
9859
9863
  const response = await callFunctions("/configs/upload", {
@@ -9864,10 +9868,23 @@ configCommand.command("upload").argument("<config>", "Path to YAML config").opti
9864
9868
  for (const file of attachments) {
9865
9869
  console.log(`✓ Attached ${path3.basename(file.src)} (${formatBytes(file.bytes)})`);
9866
9870
  }
9867
- console.log(`✓ Uploaded ${payload.configId} (${checksum})`);
9871
+ const result = response;
9872
+ const label = result.name ? `${result.name} (${result.configId})` : result.configId;
9873
+ if (result.created) {
9874
+ console.log(`✓ Created ${label}, revision ${result.revision}`);
9875
+ } else if (result.previousRevision != null) {
9876
+ console.log(`✓ Uploaded ${label}, revision ${result.previousRevision} → ${result.revision}`);
9877
+ } else {
9878
+ console.log(`✓ Uploaded ${label}, revision ${result.revision} (config unchanged)`);
9879
+ }
9880
+ console.log(` checksum ${checksum}`);
9881
+ if (result.previousRevision != null && result.previousRevisionSessionCount > 0) {
9882
+ const n = result.previousRevisionSessionCount;
9883
+ console.warn(`⚠ ${n} participant${n === 1 ? "" : "s"} ran revision ${result.previousRevision}. New participants will get revision ${result.revision}.`);
9884
+ console.warn(" Exports record each session's configRevision. For a different study, upload under a new --name instead.");
9885
+ }
9868
9886
  const labUrl = getLabUrl();
9869
- console.log(`Survey Link: ${labUrl}/${payload.configId}`);
9870
- console.log(JSON.stringify(response, null, 2));
9887
+ console.log(`Survey Link: ${labUrl}/${result.configId}`);
9871
9888
  } catch (error) {
9872
9889
  reportCliError("Upload failed", error);
9873
9890
  }
@@ -9883,12 +9900,41 @@ configCommand.command("list").description("List configs from Pairit Functions").
9883
9900
  configs.forEach((config) => {
9884
9901
  const metadata = config.metadata;
9885
9902
  const filename = metadata?.originalFilename ? ` | file=${metadata.originalFilename}` : "";
9886
- console.log(`configId=${config.configId} | owner=${config.owner} | checksum=${config.checksum}${filename} | updated=${config.updatedAt ?? "n/a"}`);
9903
+ const name = config.name ? `name=${config.name} | ` : "";
9904
+ const revision = config.revision != null ? ` | revision=${config.revision}` : "";
9905
+ console.log(`${name}configId=${config.configId}${revision} | owner=${config.owner} | checksum=${config.checksum}${filename} | updated=${config.updatedAt ?? "n/a"}`);
9887
9906
  });
9888
9907
  } catch (error) {
9889
9908
  reportCliError("List failed", error);
9890
9909
  }
9891
9910
  });
9911
+ configCommand.command("history").argument("<configId>", "Config id").option("--revision <n>", "Download one revision's compiled config").option("--out <file>", "With --revision, write the config to this file").description("List a config's revisions, or download one").action(async (configId, options) => {
9912
+ try {
9913
+ const base = `/configs/${encodeURIComponent(configId)}/revisions`;
9914
+ if (options.revision) {
9915
+ const revision = await callFunctions(`${base}/${encodeURIComponent(options.revision)}`, { method: "GET" });
9916
+ const json = JSON.stringify(revision.config, null, 2);
9917
+ if (options.out) {
9918
+ await writeFile2(options.out, json, "utf8");
9919
+ console.log(`✓ Wrote revision ${options.revision} to ${options.out}`);
9920
+ } else {
9921
+ console.log(json);
9922
+ }
9923
+ return;
9924
+ }
9925
+ const history = await callFunctions(base, { method: "GET" });
9926
+ if (!history.revisions.length) {
9927
+ console.log("No revisions recorded yet. They start with the next upload.");
9928
+ return;
9929
+ }
9930
+ for (const r of history.revisions) {
9931
+ const current = r.revision === history.currentRevision ? " (current)" : "";
9932
+ console.log(`revision=${r.revision}${current} | sessions=${r.sessionCount} | uploaded=${r.createdAt ?? "n/a"} | checksum=${r.checksum ?? "n/a"}`);
9933
+ }
9934
+ } catch (error) {
9935
+ reportCliError("History failed", error);
9936
+ }
9937
+ });
9892
9938
  configCommand.command("delete").argument("<configId>", "Config id to delete").option("--force", "Skip confirmation").description("Delete config via Pairit Functions").action(async (configId, options) => {
9893
9939
  try {
9894
9940
  if (!options.force) {
@@ -10033,6 +10079,9 @@ async function lintConfig(configPath) {
10033
10079
  if (!config.schema_version) {
10034
10080
  errors.push("missing schema_version");
10035
10081
  }
10082
+ if (config.name !== undefined && (typeof config.name !== "string" || !config.name.trim())) {
10083
+ errors.push("name must be a non-empty string");
10084
+ }
10036
10085
  if (!config.initialPageId) {
10037
10086
  errors.push("missing initialPageId");
10038
10087
  }
@@ -10044,6 +10093,12 @@ async function lintConfig(configPath) {
10044
10093
  if (!page.id) {
10045
10094
  errors.push(`pages[${index}] is missing id`);
10046
10095
  }
10096
+ page.components?.forEach((component, componentIndex) => {
10097
+ const column = component?.column;
10098
+ if (column !== undefined && column !== "left" && column !== "right") {
10099
+ errors.push(`pages[${index}].components[${componentIndex}].column must be "left" or "right"`);
10100
+ }
10101
+ });
10047
10102
  });
10048
10103
  }
10049
10104
  if (errors.length) {
@@ -10115,7 +10170,8 @@ async function buildUploadPayload(configPath, options) {
10115
10170
  const attachments = await attachHtmlToCompiledConfig(configPath, parsed);
10116
10171
  const hashBuffer = createHash("sha256").update(JSON.stringify(parsed)).digest();
10117
10172
  const checksum = hashBuffer.toString("hex");
10118
- const configId = options.configId ?? toBase64Url(hashBuffer.subarray(0, 12));
10173
+ const explicitName = options.name ?? sourceConfig.name;
10174
+ const name = options.configId ? explicitName : explicitName ?? path3.basename(configPath, path3.extname(configPath));
10119
10175
  const metadata = options.metadata ? JSON.parse(options.metadata) : {};
10120
10176
  if (!metadata.originalFilename) {
10121
10177
  metadata.originalFilename = path3.basename(configPath);
@@ -10128,7 +10184,8 @@ async function buildUploadPayload(configPath, options) {
10128
10184
  };
10129
10185
  return {
10130
10186
  payload: {
10131
- configId,
10187
+ ...name && { name },
10188
+ ...options.configId && { configId: options.configId },
10132
10189
  checksum,
10133
10190
  metadata: metadata ?? null,
10134
10191
  config: parsed,
package/package.json CHANGED
@@ -1,42 +1,42 @@
1
1
  {
2
- "name": "pairit",
3
- "version": "0.2.0",
4
- "description": "CLI for the Pairit behavioral science experiment platform",
5
- "type": "module",
6
- "bin": {
7
- "pairit": "dist/index.js"
8
- },
9
- "files": [
10
- "dist"
11
- ],
12
- "scripts": {
13
- "build": "bun build src/index.ts --outdir dist --target node",
14
- "lint": "tsc --noEmit",
15
- "prepublishOnly": "bun run build"
16
- },
17
- "keywords": [
18
- "experiment",
19
- "behavioral-science",
20
- "research",
21
- "cli"
22
- ],
23
- "repository": {
24
- "type": "git",
25
- "url": "https://github.com/pairium/pairit.git",
26
- "directory": "apps/manager/cli"
27
- },
28
- "homepage": "https://docs.pairium.ai/pairit/",
29
- "license": "MIT",
30
- "optionalDependencies": {
31
- "keytar": "^7.9.0"
32
- },
33
- "devDependencies": {
34
- "@types/bun": "^1.1.13",
35
- "@types/open": "^6.2.1",
36
- "better-auth": "^1.4.7",
37
- "commander": "^12.1.0",
38
- "open": "^11.0.0",
39
- "typescript": "^5.9.3",
40
- "yaml": "^2.6.0"
41
- }
2
+ "name": "pairit",
3
+ "version": "0.3.1",
4
+ "description": "CLI for the Pairit behavioral science experiment platform",
5
+ "type": "module",
6
+ "bin": {
7
+ "pairit": "dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "build": "bun build src/index.ts --outdir dist --target node",
14
+ "lint": "tsc --noEmit",
15
+ "prepublishOnly": "bun run build"
16
+ },
17
+ "keywords": [
18
+ "experiment",
19
+ "behavioral-science",
20
+ "research",
21
+ "cli"
22
+ ],
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/pairium/pairit.git",
26
+ "directory": "apps/manager/cli"
27
+ },
28
+ "homepage": "https://docs.pairium.ai/pairit/",
29
+ "license": "MIT",
30
+ "optionalDependencies": {
31
+ "keytar": "^7.9.0"
32
+ },
33
+ "devDependencies": {
34
+ "@types/bun": "^1.1.13",
35
+ "@types/open": "^6.2.1",
36
+ "better-auth": "^1.4.7",
37
+ "commander": "^12.1.0",
38
+ "open": "^11.0.0",
39
+ "typescript": "^5.9.3",
40
+ "yaml": "^2.6.0"
41
+ }
42
42
  }