pairit 0.2.0 → 0.3.0

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/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
@@ -9805,7 +9805,7 @@ async function attachHtmlToCompiledConfig(configPath, compiled) {
9805
9805
 
9806
9806
  // src/index.ts
9807
9807
  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) => {
9808
+ program2.name("pairit").description("CLI for Pairit experiment configs").version("0.3.0").option("--env <name>", "Target environment: staging or production", (value) => {
9809
9809
  try {
9810
9810
  return parseEnvName(value);
9811
9811
  } catch (error) {
@@ -9853,7 +9853,7 @@ configCommand.command("compile").argument("<config>", "Path to YAML config").des
9853
9853
  process11.exitCode = 1;
9854
9854
  }
9855
9855
  });
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) => {
9856
+ 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
9857
  try {
9858
9858
  const { payload, checksum, attachments } = await buildUploadPayload(configPath, options);
9859
9859
  const response = await callFunctions("/configs/upload", {
@@ -9864,10 +9864,23 @@ configCommand.command("upload").argument("<config>", "Path to YAML config").opti
9864
9864
  for (const file of attachments) {
9865
9865
  console.log(`✓ Attached ${path3.basename(file.src)} (${formatBytes(file.bytes)})`);
9866
9866
  }
9867
- console.log(`✓ Uploaded ${payload.configId} (${checksum})`);
9867
+ const result = response;
9868
+ const label = result.name ? `${result.name} (${result.configId})` : result.configId;
9869
+ if (result.created) {
9870
+ console.log(`✓ Created ${label}, revision ${result.revision}`);
9871
+ } else if (result.previousRevision != null) {
9872
+ console.log(`✓ Uploaded ${label}, revision ${result.previousRevision} → ${result.revision}`);
9873
+ } else {
9874
+ console.log(`✓ Uploaded ${label}, revision ${result.revision} (config unchanged)`);
9875
+ }
9876
+ console.log(` checksum ${checksum}`);
9877
+ if (result.previousRevision != null && result.previousRevisionSessionCount > 0) {
9878
+ const n = result.previousRevisionSessionCount;
9879
+ console.warn(`⚠ ${n} participant${n === 1 ? "" : "s"} ran revision ${result.previousRevision}. New participants will get revision ${result.revision}.`);
9880
+ console.warn(" Exports record each session's configRevision. For a different study, upload under a new --name instead.");
9881
+ }
9868
9882
  const labUrl = getLabUrl();
9869
- console.log(`Survey Link: ${labUrl}/${payload.configId}`);
9870
- console.log(JSON.stringify(response, null, 2));
9883
+ console.log(`Survey Link: ${labUrl}/${result.configId}`);
9871
9884
  } catch (error) {
9872
9885
  reportCliError("Upload failed", error);
9873
9886
  }
@@ -9883,12 +9896,41 @@ configCommand.command("list").description("List configs from Pairit Functions").
9883
9896
  configs.forEach((config) => {
9884
9897
  const metadata = config.metadata;
9885
9898
  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"}`);
9899
+ const name = config.name ? `name=${config.name} | ` : "";
9900
+ const revision = config.revision != null ? ` | revision=${config.revision}` : "";
9901
+ console.log(`${name}configId=${config.configId}${revision} | owner=${config.owner} | checksum=${config.checksum}${filename} | updated=${config.updatedAt ?? "n/a"}`);
9887
9902
  });
9888
9903
  } catch (error) {
9889
9904
  reportCliError("List failed", error);
9890
9905
  }
9891
9906
  });
9907
+ 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) => {
9908
+ try {
9909
+ const base = `/configs/${encodeURIComponent(configId)}/revisions`;
9910
+ if (options.revision) {
9911
+ const revision = await callFunctions(`${base}/${encodeURIComponent(options.revision)}`, { method: "GET" });
9912
+ const json = JSON.stringify(revision.config, null, 2);
9913
+ if (options.out) {
9914
+ await writeFile2(options.out, json, "utf8");
9915
+ console.log(`✓ Wrote revision ${options.revision} to ${options.out}`);
9916
+ } else {
9917
+ console.log(json);
9918
+ }
9919
+ return;
9920
+ }
9921
+ const history = await callFunctions(base, { method: "GET" });
9922
+ if (!history.revisions.length) {
9923
+ console.log("No revisions recorded yet. They start with the next upload.");
9924
+ return;
9925
+ }
9926
+ for (const r of history.revisions) {
9927
+ const current = r.revision === history.currentRevision ? " (current)" : "";
9928
+ console.log(`revision=${r.revision}${current} | sessions=${r.sessionCount} | uploaded=${r.createdAt ?? "n/a"} | checksum=${r.checksum ?? "n/a"}`);
9929
+ }
9930
+ } catch (error) {
9931
+ reportCliError("History failed", error);
9932
+ }
9933
+ });
9892
9934
  configCommand.command("delete").argument("<configId>", "Config id to delete").option("--force", "Skip confirmation").description("Delete config via Pairit Functions").action(async (configId, options) => {
9893
9935
  try {
9894
9936
  if (!options.force) {
@@ -10033,6 +10075,9 @@ async function lintConfig(configPath) {
10033
10075
  if (!config.schema_version) {
10034
10076
  errors.push("missing schema_version");
10035
10077
  }
10078
+ if (config.name !== undefined && (typeof config.name !== "string" || !config.name.trim())) {
10079
+ errors.push("name must be a non-empty string");
10080
+ }
10036
10081
  if (!config.initialPageId) {
10037
10082
  errors.push("missing initialPageId");
10038
10083
  }
@@ -10115,7 +10160,8 @@ async function buildUploadPayload(configPath, options) {
10115
10160
  const attachments = await attachHtmlToCompiledConfig(configPath, parsed);
10116
10161
  const hashBuffer = createHash("sha256").update(JSON.stringify(parsed)).digest();
10117
10162
  const checksum = hashBuffer.toString("hex");
10118
- const configId = options.configId ?? toBase64Url(hashBuffer.subarray(0, 12));
10163
+ const explicitName = options.name ?? sourceConfig.name;
10164
+ const name = options.configId ? explicitName : explicitName ?? path3.basename(configPath, path3.extname(configPath));
10119
10165
  const metadata = options.metadata ? JSON.parse(options.metadata) : {};
10120
10166
  if (!metadata.originalFilename) {
10121
10167
  metadata.originalFilename = path3.basename(configPath);
@@ -10128,7 +10174,8 @@ async function buildUploadPayload(configPath, options) {
10128
10174
  };
10129
10175
  return {
10130
10176
  payload: {
10131
- configId,
10177
+ ...name && { name },
10178
+ ...options.configId && { configId: options.configId },
10132
10179
  checksum,
10133
10180
  metadata: metadata ?? null,
10134
10181
  config: parsed,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pairit",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "CLI for the Pairit behavioral science experiment platform",
5
5
  "type": "module",
6
6
  "bin": {