primitive-admin 1.0.59 → 1.0.61

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 (52) hide show
  1. package/dist/bin/primitive.js +6 -0
  2. package/dist/bin/primitive.js.map +1 -1
  3. package/dist/src/commands/admins.js +22 -8
  4. package/dist/src/commands/admins.js.map +1 -1
  5. package/dist/src/commands/analytics.js +60 -0
  6. package/dist/src/commands/analytics.js.map +1 -1
  7. package/dist/src/commands/blob-buckets.js +37 -2
  8. package/dist/src/commands/blob-buckets.js.map +1 -1
  9. package/dist/src/commands/connections.d.ts +2 -0
  10. package/dist/src/commands/connections.js +95 -0
  11. package/dist/src/commands/connections.js.map +1 -0
  12. package/dist/src/commands/guides.d.ts +4 -29
  13. package/dist/src/commands/guides.js +9 -59
  14. package/dist/src/commands/guides.js.map +1 -1
  15. package/dist/src/commands/init.js +12 -4
  16. package/dist/src/commands/init.js.map +1 -1
  17. package/dist/src/commands/locks.d.ts +8 -0
  18. package/dist/src/commands/locks.js +160 -0
  19. package/dist/src/commands/locks.js.map +1 -0
  20. package/dist/src/commands/sessions.d.ts +2 -0
  21. package/dist/src/commands/sessions.js +63 -0
  22. package/dist/src/commands/sessions.js.map +1 -0
  23. package/dist/src/commands/skill.js +2 -2
  24. package/dist/src/commands/skill.js.map +1 -1
  25. package/dist/src/commands/sync.js +120 -13
  26. package/dist/src/commands/sync.js.map +1 -1
  27. package/dist/src/commands/workflows.js +56 -13
  28. package/dist/src/commands/workflows.js.map +1 -1
  29. package/dist/src/lib/api-client.d.ts +95 -0
  30. package/dist/src/lib/api-client.js +137 -15
  31. package/dist/src/lib/api-client.js.map +1 -1
  32. package/dist/src/lib/channel.d.ts +30 -0
  33. package/dist/src/lib/channel.js +68 -0
  34. package/dist/src/lib/channel.js.map +1 -0
  35. package/dist/src/lib/generated-allowlist.js +11 -0
  36. package/dist/src/lib/generated-allowlist.js.map +1 -1
  37. package/dist/src/lib/skill-installer.d.ts +4 -2
  38. package/dist/src/lib/skill-installer.js +139 -11
  39. package/dist/src/lib/skill-installer.js.map +1 -1
  40. package/dist/src/lib/template.d.ts +16 -1
  41. package/dist/src/lib/template.js +37 -6
  42. package/dist/src/lib/template.js.map +1 -1
  43. package/dist/src/lib/workflow-fragments.d.ts +23 -0
  44. package/dist/src/lib/workflow-fragments.js +229 -8
  45. package/dist/src/lib/workflow-fragments.js.map +1 -1
  46. package/dist/src/lib/workflow-payload.d.ts +1 -1
  47. package/dist/src/lib/workflow-payload.js +17 -0
  48. package/dist/src/lib/workflow-payload.js.map +1 -1
  49. package/dist/src/lib/workflow-toml-validator.d.ts +57 -1
  50. package/dist/src/lib/workflow-toml-validator.js +104 -1
  51. package/dist/src/lib/workflow-toml-validator.js.map +1 -1
  52. package/package.json +1 -1
@@ -0,0 +1,63 @@
1
+ import { ApiClient } from "../lib/api-client.js";
2
+ import { resolveAppId } from "../lib/config.js";
3
+ import { error, info, formatTable, formatDate, json, } from "../lib/output.js";
4
+ export function registerSessionsCommands(program) {
5
+ const sessions = program
6
+ .command("sessions")
7
+ .description("Inspect a user's authentication sessions")
8
+ .addHelpText("after", `
9
+ A session is one sign-in for a user. The ACTIVE column shows whether it has
10
+ expired — an expired session that has not yet been swept still appears, with
11
+ ACTIVE = false. Authentication tokens are never returned.
12
+
13
+ Examples:
14
+ $ primitive sessions list --user 01HXY...
15
+ $ primitive sessions list --user 01HXY... --limit 50
16
+ $ primitive sessions list --user 01HXY... --json
17
+ `);
18
+ sessions
19
+ .command("list")
20
+ .description("List a user's sessions")
21
+ .option("--app <app-id>", "App ID (uses current app if not specified)")
22
+ .requiredOption("--user <user-id>", "List sessions for this user")
23
+ .option("--limit <n>", "Maximum rows to return (default 25, max 100)")
24
+ .option("--cursor <cursor>", "Pagination cursor from a previous page")
25
+ .option("--json", "Output as JSON")
26
+ .action(async (options) => {
27
+ const resolvedAppId = resolveAppId(undefined, options);
28
+ const client = new ApiClient();
29
+ const limit = options.limit ? Number(options.limit) : undefined;
30
+ if (options.limit !== undefined &&
31
+ (!Number.isInteger(limit) || limit <= 0)) {
32
+ error("--limit must be a positive integer.");
33
+ process.exit(1);
34
+ }
35
+ try {
36
+ const { items, nextCursor } = await client.listSessions(resolvedAppId, options.user, { limit, cursor: options.cursor });
37
+ if (options.json) {
38
+ json({ items, nextCursor });
39
+ return;
40
+ }
41
+ if (!items || items.length === 0) {
42
+ info("No sessions found.");
43
+ return;
44
+ }
45
+ console.log(formatTable(items, [
46
+ { header: "SESSION", key: "sessionId" },
47
+ { header: "USER", key: "userId" },
48
+ { header: "ACTIVE", key: "active" },
49
+ { header: "CREATED", key: "createdAt", format: formatDate },
50
+ { header: "LAST ACTIVITY", key: "lastActivity", format: formatDate },
51
+ { header: "EXPIRES", key: "expiresAt", format: formatDate },
52
+ ]));
53
+ if (nextCursor) {
54
+ info(`More results available. Re-run with --cursor ${nextCursor}`);
55
+ }
56
+ }
57
+ catch (err) {
58
+ error(err.message);
59
+ process.exit(1);
60
+ }
61
+ });
62
+ }
63
+ //# sourceMappingURL=sessions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessions.js","sourceRoot":"","sources":["../../../src/commands/sessions.ts"],"names":[],"mappings":"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"}
@@ -6,8 +6,8 @@ export function registerSkillCommands(program) {
6
6
  skill
7
7
  .command("install")
8
8
  .description("Install or update the Primitive platform skill for Claude Code")
9
- .action(() => {
10
- if (!installSkillExplicit()) {
9
+ .action(async () => {
10
+ if (!(await installSkillExplicit())) {
11
11
  process.exitCode = 1;
12
12
  }
13
13
  });
@@ -1 +1 @@
1
- {"version":3,"file":"skill.js","sourceRoot":"","sources":["../../../src/commands/skill.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE9F,MAAM,UAAU,qBAAqB,CAAC,OAAgB;IACpD,MAAM,KAAK,GAAG,OAAO;SAClB,OAAO,CAAC,OAAO,CAAC;SAChB,WAAW,CAAC,qDAAqD,CAAC,CAAC;IAEtE,KAAK;SACF,OAAO,CAAC,SAAS,CAAC;SAClB,WAAW,CAAC,gEAAgE,CAAC;SAC7E,MAAM,CAAC,GAAG,EAAE;QACX,IAAI,CAAC,oBAAoB,EAAE,EAAE,CAAC;YAC5B,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACvB,CAAC;IACH,CAAC,CAAC,CAAC;IAEL,KAAK;SACF,OAAO,CAAC,WAAW,CAAC;SACpB,WAAW,CAAC,sDAAsD,CAAC;SACnE,MAAM,CAAC,GAAG,EAAE;QACX,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC;YACtB,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACvB,CAAC;IACH,CAAC,CAAC,CAAC;IAEL,KAAK;SACF,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,wCAAwC,CAAC;SACrD,MAAM,CAAC,GAAG,EAAE;QACX,WAAW,EAAE,CAAC;IAChB,CAAC,CAAC,CAAC;AACP,CAAC"}
1
+ {"version":3,"file":"skill.js","sourceRoot":"","sources":["../../../src/commands/skill.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE9F,MAAM,UAAU,qBAAqB,CAAC,OAAgB;IACpD,MAAM,KAAK,GAAG,OAAO;SAClB,OAAO,CAAC,OAAO,CAAC;SAChB,WAAW,CAAC,qDAAqD,CAAC,CAAC;IAEtE,KAAK;SACF,OAAO,CAAC,SAAS,CAAC;SAClB,WAAW,CAAC,gEAAgE,CAAC;SAC7E,MAAM,CAAC,KAAK,IAAI,EAAE;QACjB,IAAI,CAAC,CAAC,MAAM,oBAAoB,EAAE,CAAC,EAAE,CAAC;YACpC,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACvB,CAAC;IACH,CAAC,CAAC,CAAC;IAEL,KAAK;SACF,OAAO,CAAC,WAAW,CAAC;SACpB,WAAW,CAAC,sDAAsD,CAAC;SACnE,MAAM,CAAC,GAAG,EAAE;QACX,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC;YACtB,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACvB,CAAC;IACH,CAAC,CAAC,CAAC;IAEL,KAAK;SACF,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,wCAAwC,CAAC;SACrD,MAAM,CAAC,GAAG,EAAE;QACX,WAAW,EAAE,CAAC;IAChB,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -105,6 +105,15 @@ function canonicalJsonStringify(value) {
105
105
  if (Array.isArray(value)) {
106
106
  return "[" + value.map((v) => canonicalJsonStringify(v)).join(",") + "]";
107
107
  }
108
+ // Honor `toJSON` before the key walk. A `Date` — or smol-toml's `TomlDate`,
109
+ // what a TOML datetime literal parses to — has no enumerable own keys, so
110
+ // the `Object.keys()` walk below would collapse it to "{}" and every
111
+ // datetime would hash identically (issue #1919). Both implement `toJSON`,
112
+ // returning the ISO string; recursing on it makes date-like values (nested
113
+ // ones too) participate in the hash. Matches JSON.stringify's own semantics.
114
+ if (typeof value.toJSON === "function") {
115
+ return canonicalJsonStringify(value.toJSON());
116
+ }
108
117
  const keys = Object.keys(value).sort();
109
118
  return ("{" +
110
119
  keys
@@ -913,6 +922,16 @@ export function serializeWorkflow(workflow, draft, configs, logger) {
913
922
  // as a data-loss fallback (issue #1446).
914
923
  inputSchema: serializeWorkflowSchemaField(workflow.inputSchema, "inputSchema", workflow.workflowKey, log),
915
924
  outputSchema: serializeWorkflowSchemaField(workflow.outputSchema, "outputSchema", workflow.workflowKey, log),
925
+ // #1972 — declarative run-scoped lock (`[workflow.lock]`, #1518 Phase 3).
926
+ // The GET response returns `lock` parsed (an object, null when unset —
927
+ // like `metadataManifest`). Emit the nested `[workflow.lock]` table only
928
+ // when present so a fresh pull → push round-trips it and an unset lock
929
+ // doesn't write a noisy empty table (nor a false `sync diff`). Before this
930
+ // pull dropped the lock entirely, so a pull → push cycle silently cleared
931
+ // a server-side lock.
932
+ lock: workflow.lock && typeof workflow.lock === "object"
933
+ ? workflow.lock
934
+ : undefined,
916
935
  },
917
936
  // Use active config steps, fall back to draft
918
937
  steps,
@@ -3024,7 +3043,8 @@ Directory Structure:
3024
3043
  // (no more `process.exit` bypass class). The per-loop validators below
3025
3044
  // remain as defense-in-depth but are converted to throws too.
3026
3045
  const preflightValidationErrors = [];
3027
- // Validate all workflow TOMLs (issue #685 misnested-header check).
3046
+ // Validate all workflow TOMLs (#685 misnested-header check and the
3047
+ // #1447 system + accessRule lint).
3028
3048
  const preflightWorkflowsDir = join(configDir, "workflows");
3029
3049
  if (existsSync(preflightWorkflowsDir)) {
3030
3050
  for (const file of readdirSync(preflightWorkflowsDir).filter((f) => f.endsWith(".toml"))) {
@@ -4445,10 +4465,12 @@ Directory Structure:
4445
4465
  info(` Skipped workflows/${file} — platform-owned workflow "${platformOwnedKey}" (managed by the platform)`);
4446
4466
  continue;
4447
4467
  }
4448
- // Issue #685: reject misnested headers (e.g.
4449
- // [steps.<id>.request]) before pushing. The runtime silently
4450
- // ignores fields outside the allowlist, so this is the only
4451
- // place to catch the footgun. Validation happens BEFORE the
4468
+ // Reject misnested headers (#685, e.g. [steps.<id>.request]) and
4469
+ // the #1447 runAs="system" + accessRule dead-config combo before
4470
+ // pushing. The runtime silently ignores fields outside the
4471
+ // allowlist, so this is the only place to catch the footgun, and
4472
+ // the server rejects a system accessRule (#1258) — flagging it
4473
+ // here keeps the push all-or-nothing. Validation happens BEFORE the
4452
4474
  // skip-if-unchanged check so a previously-pushed-but-broken
4453
4475
  // file gets a clear diagnostic on every push attempt.
4454
4476
  // Issue #976: validation is now front-loaded before any apply
@@ -4936,6 +4958,28 @@ Directory Structure:
4936
4958
  const validateExpectedModifiedAt = options.force
4937
4959
  ? undefined
4938
4960
  : existingEntry?.modifiedAt;
4961
+ // Issue #1336 (codex round-1 P1): the final post-push rule content
4962
+ // of every op this push lands. When the push ALSO changes the
4963
+ // manifest, the server re-lints child ops against the new manifest;
4964
+ // forwarding each op's proposed `access`/`params` lets that re-lint
4965
+ // evaluate the op's post-push form instead of the stale stored row,
4966
+ // so an atomic manifest-rename + op-rewrite push is not falsely
4967
+ // rejected. A field is sent as value-or-null (config-as-code: the
4968
+ // TOML op IS the final state).
4969
+ const pendingOpUpdates = operations.map((op) => ({
4970
+ name: op.name,
4971
+ access: typeof op.access === "string" ? op.access : null,
4972
+ params: op.params ?? null,
4973
+ }));
4974
+ // Issue #1336 (codex round-1 P1/P2): the declared-access manifest
4975
+ // this push proposes (the parsed object, or null when the type
4976
+ // declares none / is clearing it). Forwarded as
4977
+ // `metadataManifestOverride` on the op dry-runs so op-access lints
4978
+ // run against the manifest the same push is landing, not the stale
4979
+ // stored one (existing type: a newly-declared key is honored; fresh
4980
+ // type: an undeclared ref is caught instead of skipped).
4981
+ const proposedManifest = typeConfig.metadataManifest ??
4982
+ null;
4939
4983
  try {
4940
4984
  // 1. Schema-edit gate (type-config PATCH dry-run) — only when the
4941
4985
  // type already exists on the server and we'd actually PATCH a
@@ -4948,13 +4992,53 @@ Directory Structure:
4948
4992
  // type-config dry-run at all.
4949
4993
  if (existingEntry && typeLevelChange) {
4950
4994
  const validateUpdateData = computeTypeUpdateData();
4951
- {
4952
- await client.updateDatabaseTypeConfig(resolvedAppId, dbType, pendingOpDeletes.length > 0
4953
- ? { ...validateUpdateData, pendingOpDeletes, finalOpNames }
4954
- : validateUpdateData, validateExpectedModifiedAt, {
4955
- dryRun: true,
4956
- acceptWarnings: !!options.acceptWarnings,
4957
- });
4995
+ const baseValidate = pendingOpDeletes.length > 0
4996
+ ? { ...validateUpdateData, pendingOpDeletes, finalOpNames }
4997
+ : validateUpdateData;
4998
+ await client.updateDatabaseTypeConfig(resolvedAppId, dbType,
4999
+ // Issue #1336 (P1): forward the pending op rewrites so the
5000
+ // server's child-op re-lint uses each op's post-push content.
5001
+ { ...baseValidate, pendingOpUpdates }, validateExpectedModifiedAt, {
5002
+ dryRun: true,
5003
+ acceptWarnings: !!options.acceptWarnings,
5004
+ });
5005
+ }
5006
+ else if (!existingEntry) {
5007
+ // Fresh type: run the config CREATE dry-run (issue #1336, codex
5008
+ // round-1 P2). Before this, a brand-new type's inline config
5009
+ // rules (`defaultAccess`, `metadataAccess`, autoPopulatedFields,
5010
+ // triggers) and its manifest were never dry-run — a malformed
5011
+ // rule reported a clean `sync push --dry-run` then failed only on
5012
+ // the real POST. Mirror the apply-path create payload below.
5013
+ const createDryRunData = { databaseType: dbType };
5014
+ if (typeConfig.ruleSetId)
5015
+ createDryRunData.ruleSetId = typeConfig.ruleSetId;
5016
+ if (typeConfig.triggers)
5017
+ createDryRunData.triggers = typeConfig.triggers;
5018
+ if (typeConfig.metadataAccess)
5019
+ createDryRunData.metadataAccess = typeConfig.metadataAccess;
5020
+ if (typeConfig.defaultAccess)
5021
+ createDryRunData.defaultAccess = typeConfig.defaultAccess;
5022
+ if (typeConfig.autoPopulatedFields)
5023
+ createDryRunData.autoPopulatedFields =
5024
+ typeConfig.autoPopulatedFields;
5025
+ if (typeConfig.timestamps)
5026
+ createDryRunData.timestamps = typeConfig.timestamps;
5027
+ if (typeConfig.schema)
5028
+ createDryRunData.schema = typeConfig.schema;
5029
+ if (typeConfig.metadataManifest)
5030
+ createDryRunData.metadataManifest = typeConfig.metadataManifest;
5031
+ try {
5032
+ await client.createDatabaseTypeConfig(resolvedAppId, createDryRunData, { dryRun: true });
5033
+ }
5034
+ catch (createErr) {
5035
+ // A 409 means the type already exists server-side (local sync
5036
+ // state was lost); the fresh-type inline-rule validation does
5037
+ // not apply and the real push reconciles separately. Any other
5038
+ // error is a genuine create-time validation failure — re-throw
5039
+ // so the catch below reports it.
5040
+ if (createErr?.statusCode !== 409)
5041
+ throw createErr;
4958
5042
  }
4959
5043
  }
4960
5044
  // 2. Op-edit gate (op create/update dry-run) for every op in the
@@ -5012,7 +5096,14 @@ Directory Structure:
5012
5096
  access: op.access,
5013
5097
  definition: op.definition,
5014
5098
  params: op.params,
5015
- }, options.force ? undefined : existingOp.modifiedAt, { dryRun: true, schemaOverride: proposedSchema });
5099
+ }, options.force ? undefined : existingOp.modifiedAt, {
5100
+ dryRun: true,
5101
+ schemaOverride: proposedSchema,
5102
+ // Issue #1336 (P1/P2): gate the op-access lint against
5103
+ // the manifest THIS push is landing, not the stale
5104
+ // stored one.
5105
+ metadataManifestOverride: proposedManifest,
5106
+ });
5016
5107
  }
5017
5108
  else {
5018
5109
  await client.createDatabaseTypeOperation(resolvedAppId, dbType, {
@@ -5025,6 +5116,10 @@ Directory Structure:
5025
5116
  }, {
5026
5117
  dryRun: true,
5027
5118
  schemaOverride: proposedSchema,
5119
+ // Issue #1336 (P1/P2): as above — a fresh-type op dry-run
5120
+ // now lints against the proposed manifest instead of
5121
+ // skipping the lint entirely.
5122
+ metadataManifestOverride: proposedManifest,
5028
5123
  // Issue #915 (defect a, follow-up): for a fresh type the
5029
5124
  // server has no stored config to read `defaultAccess`
5030
5125
  // from, so an op that omits `access` to inherit the
@@ -5209,6 +5304,18 @@ Directory Structure:
5209
5304
  // exclusion (see derivation above).
5210
5305
  updateData.finalOpNames = finalOpNames;
5211
5306
  }
5307
+ // Issue #1336 (codex round-2 P1): forward the pending op
5308
+ // rewrites on the REAL mutating PATCH too, mirroring the
5309
+ // dry-run validate pass above. Without this, when the same
5310
+ // push replaces a manifest key AND rewrites an op that
5311
+ // references the old key, the server's child-op re-lint runs
5312
+ // against the stale stored op → INVALID_RULE → the config
5313
+ // PATCH aborts before the op PATCH lands. Sending the final
5314
+ // op forms lets the re-lint evaluate each op's post-push
5315
+ // content, so the atomic manifest-rename + op-rewrite push
5316
+ // converges. Sent unconditionally (as the dry-run does); a
5317
+ // field absent from a claim means "not changed" server-side.
5318
+ updateData.pendingOpUpdates = pendingOpUpdates;
5212
5319
  if (typeLevelChange) {
5213
5320
  changes.push({ type: "database-type", action: "update", key: dbType });
5214
5321
  const updated = await client.updateDatabaseTypeConfig(resolvedAppId, dbType, updateData, expectedModifiedAt, {