@granular-software/sdk 0.4.21 → 0.4.22

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 +241 -151
  2. package/dist/cli/index.js +452 -167
  3. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -5496,13 +5496,19 @@ var LIBRARY_TEMPLATE = {
5496
5496
  operations: [
5497
5497
  createClassOperation("author", {
5498
5498
  name: { type: "string", description: "Author full name" },
5499
- nationality: { type: "string", description: "Primary nationality or home region" },
5499
+ nationality: {
5500
+ type: "string",
5501
+ description: "Primary nationality or home region"
5502
+ },
5500
5503
  birth_year: { type: "number", description: "Year of birth" }
5501
5504
  }),
5502
5505
  createClassOperation("book", {
5503
5506
  title: { type: "string", description: "Book title" },
5504
5507
  isbn: { type: "string", description: "ISBN identifier" },
5505
- publication_year: { type: "number", description: "Year the book was first published" },
5508
+ publication_year: {
5509
+ type: "number",
5510
+ description: "Year the book was first published"
5511
+ },
5506
5512
  price_eur: { type: "number", description: "Current list price in euros" }
5507
5513
  }),
5508
5514
  createClassOperation("publisher", {
@@ -5512,7 +5518,10 @@ var LIBRARY_TEMPLATE = {
5512
5518
  }),
5513
5519
  createClassOperation("editor", {
5514
5520
  name: { type: "string", description: "Editor full name" },
5515
- specialty: { type: "string", description: "Editorial focus or genre specialty" },
5521
+ specialty: {
5522
+ type: "string",
5523
+ description: "Editorial focus or genre specialty"
5524
+ },
5516
5525
  email: { type: "email", description: "Work email address" }
5517
5526
  }),
5518
5527
  createRelationshipOperation({
@@ -5710,8 +5719,14 @@ var LIBRARY_TEMPLATE = {
5710
5719
  inputSchema: {
5711
5720
  type: "object",
5712
5721
  properties: {
5713
- collection: { type: "string", description: "Destination collection name" },
5714
- note: { type: "string", description: "Optional note attached to the bookmark" }
5722
+ collection: {
5723
+ type: "string",
5724
+ description: "Destination collection name"
5725
+ },
5726
+ note: {
5727
+ type: "string",
5728
+ description: "Optional note attached to the bookmark"
5729
+ }
5715
5730
  }
5716
5731
  },
5717
5732
  outputSchema: {
@@ -5745,7 +5760,10 @@ var LIBRARY_TEMPLATE = {
5745
5760
  inputSchema: {
5746
5761
  type: "object",
5747
5762
  properties: {
5748
- format: { type: "string", description: "Requested format such as paperback, ebook, or audio" },
5763
+ format: {
5764
+ type: "string",
5765
+ description: "Requested format such as paperback, ebook, or audio"
5766
+ },
5749
5767
  quantity: { type: "number", description: "How many copies to order" }
5750
5768
  },
5751
5769
  required: ["format"]
@@ -5782,7 +5800,10 @@ var LIBRARY_TEMPLATE = {
5782
5800
  inputSchema: {
5783
5801
  type: "object",
5784
5802
  properties: {
5785
- channel: { type: "string", description: "Notification channel such as email or push" }
5803
+ channel: {
5804
+ type: "string",
5805
+ description: "Notification channel such as email or push"
5806
+ }
5786
5807
  },
5787
5808
  required: ["channel"]
5788
5809
  },
@@ -5813,8 +5834,15 @@ var LIBRARY_TEMPLATE = {
5813
5834
  type: "object",
5814
5835
  properties: {
5815
5836
  email: { type: "string", description: "Destination email address" },
5816
- bookIds: { type: "array", items: { type: "string" }, description: "Books to include in the list" },
5817
- message: { type: "string", description: "Optional note for the recipient" }
5837
+ bookIds: {
5838
+ type: "array",
5839
+ items: { type: "string" },
5840
+ description: "Books to include in the list"
5841
+ },
5842
+ message: {
5843
+ type: "string",
5844
+ description: "Optional note for the recipient"
5845
+ }
5818
5846
  },
5819
5847
  required: ["email", "bookIds"]
5820
5848
  },
@@ -5849,10 +5877,16 @@ var SUPPORT_TEMPLATE = {
5849
5877
  createClassOperation("customer", {
5850
5878
  name: { type: "string", description: "Customer display name" },
5851
5879
  email: { type: "email", description: "Customer email address" },
5852
- segment: { type: "string", description: "Customer segment such as self-serve or enterprise" }
5880
+ segment: {
5881
+ type: "string",
5882
+ description: "Customer segment such as self-serve or enterprise"
5883
+ }
5853
5884
  }),
5854
5885
  createClassOperation("order", {
5855
- number: { type: "string", description: "Order number shown to the customer" },
5886
+ number: {
5887
+ type: "string",
5888
+ description: "Order number shown to the customer"
5889
+ },
5856
5890
  total_eur: { type: "number", description: "Order total in euros" },
5857
5891
  status: { type: "string", description: "Current fulfillment status" }
5858
5892
  }),
@@ -6017,7 +6051,10 @@ var SUPPORT_TEMPLATE = {
6017
6051
  inputSchema: {
6018
6052
  type: "object",
6019
6053
  properties: {
6020
- message: { type: "string", description: "Reply body sent to the customer" }
6054
+ message: {
6055
+ type: "string",
6056
+ description: "Reply body sent to the customer"
6057
+ }
6021
6058
  },
6022
6059
  required: ["message"]
6023
6060
  },
@@ -6049,7 +6086,10 @@ var SUPPORT_TEMPLATE = {
6049
6086
  type: "object",
6050
6087
  properties: {
6051
6088
  amount: { type: "number", description: "Refund amount in euros" },
6052
- reason: { type: "string", description: "Reason shown in the payment platform" }
6089
+ reason: {
6090
+ type: "string",
6091
+ description: "Reason shown in the payment platform"
6092
+ }
6053
6093
  },
6054
6094
  required: ["amount"]
6055
6095
  },
@@ -6153,7 +6193,10 @@ var DELIVERY_TEMPLATE = {
6153
6193
  createClassOperation("project", {
6154
6194
  name: { type: "string", description: "Project name" },
6155
6195
  status: { type: "string", description: "Current delivery status" },
6156
- budget_k_eur: { type: "number", description: "Budget in thousands of euros" }
6196
+ budget_k_eur: {
6197
+ type: "number",
6198
+ description: "Budget in thousands of euros"
6199
+ }
6157
6200
  }),
6158
6201
  createClassOperation("milestone", {
6159
6202
  name: { type: "string", description: "Milestone title" },
@@ -6163,7 +6206,10 @@ var DELIVERY_TEMPLATE = {
6163
6206
  createClassOperation("task", {
6164
6207
  title: { type: "string", description: "Task title" },
6165
6208
  status: { type: "string", description: "Task execution status" },
6166
- estimate_days: { type: "number", description: "Estimated effort in days" }
6209
+ estimate_days: {
6210
+ type: "number",
6211
+ description: "Estimated effort in days"
6212
+ }
6167
6213
  }),
6168
6214
  createClassOperation("owner", {
6169
6215
  name: { type: "string", description: "Owner full name" },
@@ -6354,7 +6400,10 @@ var DELIVERY_TEMPLATE = {
6354
6400
  inputSchema: {
6355
6401
  type: "object",
6356
6402
  properties: {
6357
- environment: { type: "string", description: "Target environment such as staging or production" }
6403
+ environment: {
6404
+ type: "string",
6405
+ description: "Target environment such as staging or production"
6406
+ }
6358
6407
  },
6359
6408
  required: ["environment"]
6360
6409
  },
@@ -6385,7 +6434,10 @@ var DELIVERY_TEMPLATE = {
6385
6434
  inputSchema: {
6386
6435
  type: "object",
6387
6436
  properties: {
6388
- channel: { type: "string", description: "Destination channel such as slack or email" },
6437
+ channel: {
6438
+ type: "string",
6439
+ description: "Destination channel such as slack or email"
6440
+ },
6389
6441
  message: { type: "string", description: "Message body" }
6390
6442
  },
6391
6443
  required: ["channel", "message"]
@@ -6417,7 +6469,10 @@ var DELIVERY_TEMPLATE = {
6417
6469
  type: "object",
6418
6470
  properties: {
6419
6471
  email: { type: "string", description: "Destination email address" },
6420
- projectId: { type: "string", description: "Project referenced by the report" },
6472
+ projectId: {
6473
+ type: "string",
6474
+ description: "Project referenced by the report"
6475
+ },
6421
6476
  summary: { type: "string", description: "Status report summary" }
6422
6477
  },
6423
6478
  required: ["email", "projectId", "summary"]
@@ -6491,17 +6546,17 @@ function createStarterManifest(name, templateId) {
6491
6546
  schemaVersion: 2,
6492
6547
  name,
6493
6548
  description: `${name} \u2014 ${template.manifestDescription}`,
6494
- volumes: [{
6495
- name: "schema",
6496
- scope: "sandbox",
6497
- imports: [
6498
- { alias: "@std", name: "standard_modules", label: "prod" }
6499
- ],
6500
- operations: [
6501
- ...template.operations,
6502
- ...template.effects.map(createEffectOperation)
6503
- ]
6504
- }]
6549
+ volumes: [
6550
+ {
6551
+ name: "schema",
6552
+ scope: "sandbox",
6553
+ imports: [{ alias: "@std", name: "standard_modules", label: "prod" }],
6554
+ operations: [
6555
+ ...template.operations,
6556
+ ...template.effects.map(createEffectOperation)
6557
+ ]
6558
+ }
6559
+ ]
6505
6560
  };
6506
6561
  }
6507
6562
  function generateSeedScript(sandboxId, apiUrl, templateId) {
@@ -6512,8 +6567,12 @@ function generateSeedScript(sandboxId, apiUrl, templateId) {
6512
6567
  * Seed script for the "${template.label}" starter template.
6513
6568
  *
6514
6569
  * Safe to re-run:
6515
- * - \`recordObject()\` upserts by \`{ className, id }\`
6570
+ * - \`recordObject()\` / \`recordObjects()\` upsert by \`{ className, id }\`
6516
6571
  * - relationships are re-applied on each run
6572
+ *
6573
+ * Guidance:
6574
+ * - Use \`recordObject()\` for one record or one targeted update
6575
+ * - Use \`recordObjects()\` for starter datasets like this one so you get chunked writes + progress
6517
6576
  */
6518
6577
 
6519
6578
  import { Granular } from '@granular-software/sdk';
@@ -6548,10 +6607,19 @@ async function main() {
6548
6607
  });
6549
6608
 
6550
6609
  try {
6551
- for (const record of records) {
6552
- await env.recordObject(record);
6553
- console.log(\`Upserted \${record.className}:\${record.id}\`);
6554
- }
6610
+ let completed = 0;
6611
+ const results = await env.recordObjects(records, {
6612
+ onChunkComplete(info) {
6613
+ completed += info.recordCount;
6614
+ console.log(
6615
+ \`[Seed] Chunk \${info.chunkIndex + 1}/\${info.totalChunks}: committed \${completed}/\${records.length} records (\${info.durationMs}ms)\`,
6616
+ );
6617
+ },
6618
+ });
6619
+
6620
+ const createdCount = results.filter((result) => result.created).length;
6621
+ const updatedCount = results.length - createdCount;
6622
+ console.log(\`[Seed] Created \${createdCount} records, updated \${updatedCount} records.\`);
6555
6623
  } finally {
6556
6624
  await env.disconnect();
6557
6625
  }
@@ -8922,7 +8990,7 @@ The rest of this guide assumes these terms.
8922
8990
  | **Job** | Code string passed to \`environment.submitJob(code)\`. Runs in Granular\u2019s **sandbox runtime** with access to \`./sandbox-tools\` (generated classes + effect entrypoints). |
8923
8991
  | **Effect** | Declared with \`withEffect\`; **handler** registered with \`registerEffects\` in **your** process. Jobs invoke effects; handlers do IO outside Granular. |
8924
8992
  | **Class** | Entity **kind** in the domain (e.g. \`book\`) with \`has\` fields in the manifest. |
8925
- | **Record / object** | One **instance**: \`className\`, \`id\`, \`fields\`, \`relationships\`. Upsert with \`recordObject\`. |
8993
+ | **Record / object** | One **instance**: \`className\`, \`id\`, \`fields\`, \`relationships\`. Use \`recordObject\` for one upsert, \`recordObjects\` for several immediate upserts, and record-import APIs for async bulk loads. |
8926
8994
  | **Relationship** | Declared link between two classes; defines **property names** and cardinality. Keys appear in \`recordObject({ relationships })\`. |
8927
8995
 
8928
8996
  ### Data vs actions (short)
@@ -8941,7 +9009,7 @@ function manifestGuideEndToEndSection() {
8941
9009
  | 2 | \`granular build\` | Manifest **uploaded**; ontology version created or reused; build run compiles it; errors surface here |
8942
9010
  | 3 | (Optional) Run your **effects host** \u2014 e.g. \`npx tsx granular-effects.ts\` \u2014 calling \`registerEffects(sandboxId, \u2026)\` | Effect **handlers** attached in **your** process |
8943
9011
  | 4 | \`new Granular({ apiKey })\` then \`connect({ ontology, environment, userId, permissions })\` | **\`Environment\`** for that ontology environment slot |
8944
- | 5 | \`recordObject\` / \`recordObjects\` | **Records** stored with correct fields and relationship keys. \`recordObjects\` chunks to the control plane (default 100 rows/request); pass \`onChunkComplete\` / \`concurrency\` for UI progress. For async bulk loads use \`enqueueRecordImport\` + import status APIs. |
9012
+ | 5 | \`recordObject\` / \`recordObjects\` | **Records** stored with correct fields and relationship keys. Use \`recordObject\` for one targeted write. Use \`recordObjects\` for one synchronous multi-record write with chunking (default 100 rows/request), retries, and optional \`onChunkComplete\` / \`concurrency\` progress. For async bulk loads use \`enqueueRecordImport\` + import status APIs. |
8945
9013
  | 6 | \`submitJob(\`\u2026\`)\` with imports from \`./sandbox-tools\` | **Jobs** run; may call **effects** \u2192 your handlers execute and return results |
8946
9014
 
8947
9015
  **Accuracy tip:** After changing the manifest, always **re-build** before assuming generated types, \`./sandbox-tools\` names, or effect signatures match the file on disk.`;
@@ -14629,9 +14697,15 @@ function renderMetamodelDocTable(rows) {
14629
14697
  // src/cli/agent-docs/manifest-guide.ts
14630
14698
  function generateManifestAgentGuide(options) {
14631
14699
  const { projectName } = options;
14632
- const fieldMetamodelTable = renderMetamodelDocTable(getFieldMetamodelDocRows());
14633
- const modelMetamodelTable = renderMetamodelDocTable(getModelMetamodelDocRows());
14634
- const effectMetamodelTable = renderMetamodelDocTable(getEffectMetamodelDocRows());
14700
+ const fieldMetamodelTable = renderMetamodelDocTable(
14701
+ getFieldMetamodelDocRows()
14702
+ );
14703
+ const modelMetamodelTable = renderMetamodelDocTable(
14704
+ getModelMetamodelDocRows()
14705
+ );
14706
+ const effectMetamodelTable = renderMetamodelDocTable(
14707
+ getEffectMetamodelDocRows()
14708
+ );
14635
14709
  return `# Granular manifest guide (for coding agents)
14636
14710
 
14637
14711
  This guide is for **coding agents** who may have **no prior context on Granular**. It explains the product, core terms, how to **author** \`granular.json\`, **build** the domain, and use the SDK (\`connect\` \u2192 **environment**, \`recordObject\`, \`submitJob\`, **effects**). For **this repository\u2019s** exact class names, relationship keys, and effect payloads only, see [GRANULAR_SANDBOX.md](../GRANULAR_SANDBOX.md).
@@ -14961,8 +15035,8 @@ ${effectMetamodelTable}
14961
15035
  |-----|---------|
14962
15036
  | \`environmentId\`, \`sandboxId\`, \`apiEndpoint\`, \u2026 | Session context. |
14963
15037
  | \`applyManifest(manifest)\` | Apply manifest operations at runtime (alternative to CLI build for dynamic ontologies). |
14964
- | \`recordObject\` / \`recordObjects\` | Upsert instances and relationships. \`recordObjects\` uses chunked HTTP batch writes (default 100 rows/chunk); optional \`onChunkComplete\`, \`batchSize\`, \`concurrency\`. |
14965
- | \`enqueueRecordImport\`, \`listRecordImports\`, \`getRecordImportSummary\`, \u2026 | **Async** bulk import (worker queue + aggregate progress). Prefer when loads are huge and fire-and-forget is OK. |
15038
+ | \`recordObject\` / \`recordObjects\` | Upsert instances and relationships. Use \`recordObject\` for one object or a targeted patch. Use \`recordObjects\` for synchronous multi-record ingestion with chunked HTTP writes (default 100 rows/chunk), retries, and optional \`onChunkComplete\`, \`batchSize\`, \`concurrency\`. |
15039
+ | \`enqueueRecordImport\`, \`listRecordImports\`, \`getRecordImport\`, \`getRecordImportSummary\`, \u2026 | **Async** bulk import (worker queue + aggregate progress). Prefer when loads are huge, returning an \`importId\` is enough up front, and background processing is acceptable. |
14966
15040
  | \`graphql(query, variables?)\` | **GraphQL** \u2014 see dedicated subsection below. |
14967
15041
  | \`defineRelationship\`, \`getRelationships\`, \`attach\`, \`detach\`, \`listRelated\` | Imperative relationship operations (same ideas as manifest \`defineRelationship\`). |
14968
15042
  | \`submitJob(code)\` | Run code in the sandbox; import from \`./sandbox-tools\`. |
@@ -15099,13 +15173,18 @@ function generateSandboxAgentDoc(manifest, meta) {
15099
15173
  lines.push("|-----|-------|");
15100
15174
  lines.push(`| Sandbox id | \`${meta.sandboxId}\` |`);
15101
15175
  if (meta.apiUrl) lines.push(`| API / WS base | \`${meta.apiUrl}\` |`);
15102
- if (meta.manifestId) lines.push(`| Last uploaded manifest id | \`${meta.manifestId}\` |`);
15176
+ if (meta.manifestId)
15177
+ lines.push(`| Last uploaded manifest id | \`${meta.manifestId}\` |`);
15103
15178
  if (meta.buildId) lines.push(`| Last version id | \`${meta.buildId}\` |`);
15104
15179
  if (meta.buildPending) {
15105
- lines.push("| Version status | *No successful version/build recorded in this doc run \u2014 run `granular build`.* |");
15180
+ lines.push(
15181
+ "| Version status | *No successful version/build recorded in this doc run \u2014 run `granular build`.* |"
15182
+ );
15106
15183
  }
15107
15184
  lines.push("");
15108
- lines.push("Set `GRANULAR_API_KEY` (e.g. in `.env.local`). Optional: `GRANULAR_API_URL` overrides the WebSocket URL.");
15185
+ lines.push(
15186
+ "Set `GRANULAR_API_KEY` (e.g. in `.env.local`). Optional: `GRANULAR_API_URL` overrides the WebSocket URL."
15187
+ );
15109
15188
  lines.push("");
15110
15189
  for (const line of sandboxDocMainConceptsSection().split("\n")) {
15111
15190
  lines.push(line);
@@ -15119,18 +15198,40 @@ function generateSandboxAgentDoc(manifest, meta) {
15119
15198
  lines.push("");
15120
15199
  lines.push("| Command | Purpose |");
15121
15200
  lines.push("|---------|---------|");
15122
- lines.push("| `granular build` | Create or rebuild the current ontology version |");
15123
- lines.push("| `granular version current --json` | Resolve the latest/current ontology version |");
15124
- lines.push("| `granular version diff <version> [base]` | Show the semantic diff between versions |");
15125
- lines.push("| `granular tag move <tag> <version>` | Move a release tag such as `prod` |");
15126
- lines.push("| `granular document` | Refresh this file from `granular.json` |");
15127
- lines.push("| `granular connect test --json` | Verify auth and environment connectivity |");
15128
- lines.push("| `granular session heap --json` | Inspect the normalized session heap |");
15129
- lines.push("| `granular graphql --query '...' --json` | Query the live graph |");
15130
- lines.push("| `granular effects list --json` | Inspect declared and live effects |");
15131
- lines.push("| `granular job run --file ./job.ts --json` | Execute a real runtime job |");
15201
+ lines.push(
15202
+ "| `granular build` | Create or rebuild the current ontology version |"
15203
+ );
15204
+ lines.push(
15205
+ "| `granular version current --json` | Resolve the latest/current ontology version |"
15206
+ );
15207
+ lines.push(
15208
+ "| `granular version diff <version> [base]` | Show the semantic diff between versions |"
15209
+ );
15210
+ lines.push(
15211
+ "| `granular tag move <tag> <version>` | Move a release tag such as `prod` |"
15212
+ );
15213
+ lines.push(
15214
+ "| `granular document` | Refresh this file from `granular.json` |"
15215
+ );
15216
+ lines.push(
15217
+ "| `granular connect test --json` | Verify auth and environment connectivity |"
15218
+ );
15219
+ lines.push(
15220
+ "| `granular session heap --json` | Inspect the normalized session heap |"
15221
+ );
15222
+ lines.push(
15223
+ "| `granular graphql --query '...' --json` | Query the live graph |"
15224
+ );
15225
+ lines.push(
15226
+ "| `granular effects list --json` | Inspect declared and live effects |"
15227
+ );
15228
+ lines.push(
15229
+ "| `granular job run --file ./job.ts --json` | Execute a real runtime job |"
15230
+ );
15132
15231
  lines.push("| `npx tsx " + seed + "` | Seed sample records (if present) |");
15133
- lines.push("| `npx tsx " + eff + "` | Run effect handler host (if present) |");
15232
+ lines.push(
15233
+ "| `npx tsx " + eff + "` | Run effect handler host (if present) |"
15234
+ );
15134
15235
  lines.push("");
15135
15236
  lines.push("## Connect");
15136
15237
  lines.push("");
@@ -15153,7 +15254,9 @@ function generateSandboxAgentDoc(manifest, meta) {
15153
15254
  lines.push("## Classes and fields");
15154
15255
  lines.push("");
15155
15256
  if (classes.length === 0) {
15156
- lines.push("*No `create` + `@std/class` operations found in the manifest.*");
15257
+ lines.push(
15258
+ "*No `create` + `@std/class` operations found in the manifest.*"
15259
+ );
15157
15260
  lines.push("");
15158
15261
  } else {
15159
15262
  for (const cls of classes) {
@@ -15179,14 +15282,20 @@ function generateSandboxAgentDoc(manifest, meta) {
15179
15282
  }
15180
15283
  lines.push("## Relationships");
15181
15284
  lines.push("");
15182
- lines.push("Declared with `defineRelationship`. Submodel names are the **keys** on each class for `recordObject({ relationships: { ... } })`. Values are **foreign class ids** (single string or `string[]` when \u201Cmany\u201D).");
15285
+ lines.push(
15286
+ "Declared with `defineRelationship`. Submodel names are the **keys** on each class for `recordObject({ relationships: { ... } })`. Values are **foreign class ids** (single string or `string[]` when \u201Cmany\u201D)."
15287
+ );
15183
15288
  lines.push("");
15184
15289
  if (relationships.length === 0) {
15185
15290
  lines.push("*No relationships defined.*");
15186
15291
  lines.push("");
15187
15292
  } else {
15188
- lines.push("| Left | Right | Cardinality | Left key (`leftSubmodel`) | Right key (`rightSubmodel`) |");
15189
- lines.push("|------|-------|-------------|---------------------------|----------------------------|");
15293
+ lines.push(
15294
+ "| Left | Right | Cardinality | Left key (`leftSubmodel`) | Right key (`rightSubmodel`) |"
15295
+ );
15296
+ lines.push(
15297
+ "|------|-------|-------------|---------------------------|----------------------------|"
15298
+ );
15190
15299
  for (const r of relationships) {
15191
15300
  lines.push(
15192
15301
  `| \`${r.left}\` | \`${r.right}\` | ${r.cardinalityLabel} | \`${r.leftSubmodel}\` | \`${r.rightSubmodel}\` |`
@@ -15196,7 +15305,9 @@ function generateSandboxAgentDoc(manifest, meta) {
15196
15305
  }
15197
15306
  lines.push("## `recordObject` \u2014 examples per class");
15198
15307
  lines.push("");
15199
- lines.push("`recordObject` upserts by `{ className, id }`. In `relationships`, use **only the target object\u2019s id** for its class (see keys below).");
15308
+ lines.push(
15309
+ "`recordObject` upserts by `{ className, id }`. In `relationships`, use **only the target object\u2019s id** for its class (see keys below)."
15310
+ );
15200
15311
  lines.push("");
15201
15312
  for (const cls of classes) {
15202
15313
  const edges = relationshipEdgesForClass(cls.name, relationships);
@@ -15222,9 +15333,13 @@ function generateSandboxAgentDoc(manifest, meta) {
15222
15333
  lines.push(` relationships: {`);
15223
15334
  for (const e of edges) {
15224
15335
  if (e.isMany) {
15225
- lines.push(` ${e.submodelKey}: ['${e.foreignClass}_id_1'], // ${e.foreignClass} ids`);
15336
+ lines.push(
15337
+ ` ${e.submodelKey}: ['${e.foreignClass}_id_1'], // ${e.foreignClass} ids`
15338
+ );
15226
15339
  } else {
15227
- lines.push(` ${e.submodelKey}: '${e.foreignClass}_id', // one ${e.foreignClass} id`);
15340
+ lines.push(
15341
+ ` ${e.submodelKey}: '${e.foreignClass}_id', // one ${e.foreignClass} id`
15342
+ );
15228
15343
  }
15229
15344
  }
15230
15345
  lines.push(` },`);
@@ -15237,15 +15352,27 @@ function generateSandboxAgentDoc(manifest, meta) {
15237
15352
  lines.push("*No classes \u2014 no examples.*");
15238
15353
  lines.push("");
15239
15354
  }
15240
- lines.push("## Effects declared in this manifest (`withEffect` + live handlers)");
15355
+ lines.push(
15356
+ "## Effects declared in this manifest (`withEffect` + live handlers)"
15357
+ );
15241
15358
  lines.push("");
15242
- lines.push("Each entry below matches one `withEffect` operation. For every declaration you must:");
15359
+ lines.push(
15360
+ "Each entry below matches one `withEffect` operation. For every declaration you must:"
15361
+ );
15243
15362
  lines.push("");
15244
- lines.push("1. **Build** the manifest so the domain package includes the effect (`granular build`).");
15245
- lines.push("2. **Register** a handler whose `name` (and `className` / `static` when applicable) matches \u2014 use `granular.registerEffects('" + meta.sandboxId + "', [...])` in your host process (see `granular-effects.ts` in starter projects).");
15246
- lines.push("3. **Invoke** from sandbox code via `./sandbox-tools` inside `submitJob`; the handler you registered runs in your process and the result returns to the job.");
15363
+ lines.push(
15364
+ "1. **Build** the manifest so the domain package includes the effect (`granular build`)."
15365
+ );
15366
+ lines.push(
15367
+ "2. **Register** a handler whose `name` (and `className` / `static` when applicable) matches \u2014 use `granular.registerEffects('" + meta.sandboxId + "', [...])` in your host process (see `granular-effects.ts` in starter projects)."
15368
+ );
15369
+ lines.push(
15370
+ "3. **Invoke** from sandbox code via `./sandbox-tools` inside `submitJob`; the handler you registered runs in your process and the result returns to the job."
15371
+ );
15247
15372
  lines.push("");
15248
- lines.push("**Handler shapes:** global \u2192 `(params, ctx) => \u2026`; static class effect \u2192 `(params, ctx) => \u2026`; instance effect \u2192 `(objectId, params, ctx) => \u2026` where `objectId` is the real-world id for `attachedClass`.");
15373
+ lines.push(
15374
+ "**Handler shapes:** global \u2192 `(params, ctx) => \u2026`; static class effect \u2192 `(params, ctx) => \u2026`; instance effect \u2192 `(objectId, params, ctx) => \u2026` where `objectId` is the real-world id for `attachedClass`."
15375
+ );
15249
15376
  lines.push("");
15250
15377
  if (effects2.length === 0) {
15251
15378
  lines.push("*No `withEffect` operations in this manifest.*");
@@ -15286,7 +15413,9 @@ function generateSandboxAgentDoc(manifest, meta) {
15286
15413
  } else {
15287
15414
  lines.push("```typescript");
15288
15415
  lines.push(`// Instance: (objectId, params, ctx) => Promise<output>`);
15289
- lines.push(`handler: async (objectId, params, ctx) => { /* objectId is the ${ef.attachedClass} id */ }`);
15416
+ lines.push(
15417
+ `handler: async (objectId, params, ctx) => { /* objectId is the ${ef.attachedClass} id */ }`
15418
+ );
15290
15419
  lines.push("```");
15291
15420
  }
15292
15421
  lines.push("");
@@ -15294,7 +15423,9 @@ function generateSandboxAgentDoc(manifest, meta) {
15294
15423
  }
15295
15424
  lines.push("## `submitJob` / sandbox code");
15296
15425
  lines.push("");
15297
- lines.push("Jobs run in the sandbox with generated classes from `./sandbox-tools`. Class names are PascalCase.");
15426
+ lines.push(
15427
+ "Jobs run in the sandbox with generated classes from `./sandbox-tools`. Class names are PascalCase."
15428
+ );
15298
15429
  lines.push("");
15299
15430
  const pascalImports = classes.map((c) => toPascalCase(c.name));
15300
15431
  const effectNames = effects2.map((e) => e.name).filter(Boolean);
@@ -15302,7 +15433,9 @@ function generateSandboxAgentDoc(manifest, meta) {
15302
15433
  const firstGlobal = globalEffects[0]?.name;
15303
15434
  lines.push("```typescript");
15304
15435
  lines.push(`const job = await env.submitJob(\``);
15305
- const importList = [...pascalImports, ...effectNames].filter((v, i, a) => a.indexOf(v) === i);
15436
+ const importList = [...pascalImports, ...effectNames].filter(
15437
+ (v, i, a) => a.indexOf(v) === i
15438
+ );
15306
15439
  if (importList.length > 0) {
15307
15440
  lines.push(` import { ${importList.join(", ")} } from './sandbox-tools';`);
15308
15441
  } else {
@@ -15316,10 +15449,14 @@ function generateSandboxAgentDoc(manifest, meta) {
15316
15449
  const target = classWithInstanceEffect ?? classes[0];
15317
15450
  const C = toPascalCase(target.name);
15318
15451
  const c = target.name;
15319
- lines.push(` const rows = await ${C}.list({ limit: 10, saveAs: '${c}_rows' });`);
15452
+ lines.push(
15453
+ ` const rows = await ${C}.list({ limit: 10, saveAs: '${c}_rows' });`
15454
+ );
15320
15455
  lines.push(` const row = rows[0] ?? null;`);
15321
15456
  lines.push("");
15322
- const instEffects = effects2.filter((e) => e.attachedClass === c && !e.isStatic);
15457
+ const instEffects = effects2.filter(
15458
+ (e) => e.attachedClass === c && !e.isStatic
15459
+ );
15323
15460
  if (instEffects.length > 0) {
15324
15461
  const n = instEffects[0].name;
15325
15462
  lines.push(` if (row) {`);
@@ -15343,28 +15480,50 @@ function generateSandboxAgentDoc(manifest, meta) {
15343
15480
  lines.push("");
15344
15481
  lines.push("## Companion scripts");
15345
15482
  lines.push("");
15346
- lines.push(`- **\`${seed}\`** \u2014 example \`recordObject\` batch for starter data.`);
15347
- lines.push(`- **\`${eff}\`** \u2014 long-running process that registers effect handlers for this sandbox.`);
15483
+ lines.push(
15484
+ `- **\`${seed}\`** \u2014 example starter-data script using \`recordObjects(...)\` with progress updates.`
15485
+ );
15486
+ lines.push(
15487
+ `- **\`${eff}\`** \u2014 long-running process that registers effect handlers for this sandbox.`
15488
+ );
15348
15489
  lines.push("");
15349
15490
  lines.push("## SDK surface (reminder)");
15350
15491
  lines.push("");
15351
15492
  lines.push("| Area | APIs |");
15352
15493
  lines.push("|------|------|");
15353
- lines.push("| Auth / session | `Granular`, `connect`, `disconnect`, `recordUser` |");
15354
- lines.push("| Data | `recordObject`, `recordObjects`, record import queue APIs |");
15355
- lines.push("| GraphQL API | `graphql` \u2014 query/mutate the underlying graph when you need it |");
15356
- lines.push("| Relationships (API) | `defineRelationship`, `getRelationships`, `attach`, `detach`, `listRelated` |");
15357
- lines.push("| Ontology | `applyManifest` (runtime), or CLI `granular build` |");
15494
+ lines.push(
15495
+ "| Auth / session | `Granular`, `connect`, `disconnect`, `recordUser` |"
15496
+ );
15497
+ lines.push(
15498
+ "| Data | `recordObject` (one write), `recordObjects` (immediate multi-write), record import queue APIs (async bulk load) |"
15499
+ );
15500
+ lines.push(
15501
+ "| GraphQL API | `graphql` \u2014 query/mutate the underlying graph when you need it |"
15502
+ );
15503
+ lines.push(
15504
+ "| Relationships (API) | `defineRelationship`, `getRelationships`, `attach`, `detach`, `listRelated` |"
15505
+ );
15506
+ lines.push(
15507
+ "| Ontology | `applyManifest` (runtime), or CLI `granular build` |"
15508
+ );
15358
15509
  lines.push("| Jobs | `submitJob`, `answerPrompt` |");
15359
- lines.push("| Domain | `getDomain`, `getDomainTypes`, `getDomainDocumentation` |");
15360
- lines.push("| Effects | `registerEffects`, `getEffects`, `onEffectsChanged` |");
15510
+ lines.push(
15511
+ "| Domain | `getDomain`, `getDomainTypes`, `getDomainDocumentation` |"
15512
+ );
15513
+ lines.push(
15514
+ "| Effects | `registerEffects`, `getEffects`, `onEffectsChanged` |"
15515
+ );
15361
15516
  lines.push("| Ops | `checkReadiness`, `getHeap`, `rpc` |");
15362
15517
  lines.push("");
15363
- lines.push("Full list: [docs/granular-manifest.md](docs/granular-manifest.md).");
15518
+ lines.push(
15519
+ "Full list: [docs/granular-manifest.md](docs/granular-manifest.md)."
15520
+ );
15364
15521
  lines.push("");
15365
15522
  lines.push("---");
15366
15523
  lines.push("");
15367
- lines.push(`*Generated at ${(/* @__PURE__ */ new Date()).toISOString()} by @granular-software/sdk*`);
15524
+ lines.push(
15525
+ `*Generated at ${(/* @__PURE__ */ new Date()).toISOString()} by @granular-software/sdk*`
15526
+ );
15368
15527
  lines.push("");
15369
15528
  return lines.join("\n");
15370
15529
  }
@@ -15501,8 +15660,10 @@ edit granular.json -> granular build -> granular document -> run tests -> option
15501
15660
  # Quick reminders
15502
15661
 
15503
15662
  - Default auth story: \`granular login\`. Manual \`.env.local\` editing is fallback guidance only.
15504
- - Read \`GRANULAR_SANDBOX.md\` before writing \`recordObject\` payloads or relationship keys.
15505
- - Large ingests: use \`recordObjects(rows, { onChunkComplete, concurrency })\` for synchronous chunked writes with progress, or \`enqueueRecordImport\` + \`getRecordImport\` for async queue-based bulk loads.
15663
+ - Read \`GRANULAR_SANDBOX.md\` before writing \`recordObject\` or \`recordObjects\` payloads, especially relationship keys.
15664
+ - Use \`recordObject(...)\` for one object or one targeted update.
15665
+ - Use one \`recordObjects(rows, { onChunkComplete, concurrency })\` call when you need to write several records now and want chunk-level progress.
15666
+ - Use \`enqueueRecordImport(...)\` with \`getRecordImport(...)\` / \`getRecordImportSummary()\` only for large async imports where returning an \`importId\` is better than waiting for every row to finish.
15506
15667
  - Build failures should be mapped to the exact class, relationship, or effect declaration that caused them.
15507
15668
  - If the user starts from the control plane UI, converge back to the same repo state and the same CLI commands.
15508
15669
  `
@@ -15815,8 +15976,12 @@ function renderControlPlaneSection(controlPlaneSessionId) {
15815
15976
  `;
15816
15977
  }
15817
15978
  function renderOnboardingStateTemplate(options = {}) {
15818
- const controlPlaneSection = renderControlPlaneSection(options.controlPlaneSessionId);
15819
- const controlPlaneSetupSection = renderControlPlaneSetupSection(options.onboardingContext);
15979
+ const controlPlaneSection = renderControlPlaneSection(
15980
+ options.controlPlaneSessionId
15981
+ );
15982
+ const controlPlaneSetupSection = renderControlPlaneSetupSection(
15983
+ options.onboardingContext
15984
+ );
15820
15985
  return `# Granular onboarding state
15821
15986
 
15822
15987
  Use this file to track the first-run flow. After the first successful loop, switch to the short daily loop:
@@ -15902,8 +16067,12 @@ ${authHint}
15902
16067
  `;
15903
16068
  }
15904
16069
  function renderStartHereTemplate(options = {}) {
15905
- const controlPlaneSection = renderControlPlaneSection(options.controlPlaneSessionId);
15906
- const controlPlaneSetupSection = renderControlPlaneSetupSection(options.onboardingContext);
16070
+ const controlPlaneSection = renderControlPlaneSection(
16071
+ options.controlPlaneSessionId
16072
+ );
16073
+ const controlPlaneSetupSection = renderControlPlaneSetupSection(
16074
+ options.onboardingContext
16075
+ );
15907
16076
  return `# Granular start here
15908
16077
 
15909
16078
  This file is generated by \`granular init\`. Use it as the shared kickoff file for Codex, Cursor, Claude Code, and humans.
@@ -15976,7 +16145,10 @@ function mergeReadmeSnippet(existingContent) {
15976
16145
  `;
15977
16146
  }
15978
16147
  if (existingContent.includes(README_BEGIN) && existingContent.includes(README_END)) {
15979
- const re = new RegExp(`${escapeRegex2(README_BEGIN)}[\\s\\S]*?${escapeRegex2(README_END)}`, "m");
16148
+ const re = new RegExp(
16149
+ `${escapeRegex2(README_BEGIN)}[\\s\\S]*?${escapeRegex2(README_END)}`,
16150
+ "m"
16151
+ );
15980
16152
  return existingContent.replace(re, block.trim());
15981
16153
  }
15982
16154
  return `${existingContent.trimEnd()}
@@ -16009,27 +16181,52 @@ ${endMarker}`;
16009
16181
  return;
16010
16182
  }
16011
16183
  if (previous.includes(beginMarker) && previous.includes(endMarker)) {
16012
- const re = new RegExp(`${escapeRegex2(beginMarker)}[\\s\\S]*?${escapeRegex2(endMarker)}`, "m");
16013
- fs__namespace.writeFileSync(filePath, previous.replace(re, nextContent.trim()), "utf-8");
16184
+ const re = new RegExp(
16185
+ `${escapeRegex2(beginMarker)}[\\s\\S]*?${escapeRegex2(endMarker)}`,
16186
+ "m"
16187
+ );
16188
+ fs__namespace.writeFileSync(
16189
+ filePath,
16190
+ previous.replace(re, nextContent.trim()),
16191
+ "utf-8"
16192
+ );
16014
16193
  return;
16015
16194
  }
16016
- fs__namespace.writeFileSync(filePath, `${previous.trimEnd()}
16195
+ fs__namespace.writeFileSync(
16196
+ filePath,
16197
+ `${previous.trimEnd()}
16017
16198
 
16018
16199
  ${nextContent}
16019
- `, "utf-8");
16200
+ `,
16201
+ "utf-8"
16202
+ );
16020
16203
  }
16021
16204
  function writeCursorHooks(projectRoot) {
16022
16205
  const hooksConfigPath = path3__namespace.join(projectRoot, ".cursor", "hooks.json");
16023
- const afterShellPath = path3__namespace.join(projectRoot, ".cursor", "hooks", "after-shell.mjs");
16024
- const afterFileEditPath = path3__namespace.join(projectRoot, ".cursor", "hooks", "after-file-edit.mjs");
16206
+ const afterShellPath = path3__namespace.join(
16207
+ projectRoot,
16208
+ ".cursor",
16209
+ "hooks",
16210
+ "after-shell.mjs"
16211
+ );
16212
+ const afterFileEditPath = path3__namespace.join(
16213
+ projectRoot,
16214
+ ".cursor",
16215
+ "hooks",
16216
+ "after-file-edit.mjs"
16217
+ );
16025
16218
  upsertFile(
16026
16219
  hooksConfigPath,
16027
16220
  JSON.stringify(
16028
16221
  {
16029
16222
  version: 1,
16030
16223
  hooks: {
16031
- afterShellExecution: [{ command: "node .cursor/hooks/after-shell.mjs" }],
16032
- afterFileEdit: [{ command: "node .cursor/hooks/after-file-edit.mjs" }]
16224
+ afterShellExecution: [
16225
+ { command: "node .cursor/hooks/after-shell.mjs" }
16226
+ ],
16227
+ afterFileEdit: [
16228
+ { command: "node .cursor/hooks/after-file-edit.mjs" }
16229
+ ]
16033
16230
  }
16034
16231
  },
16035
16232
  null,
@@ -16140,7 +16337,33 @@ policy:
16140
16337
  `;
16141
16338
  }
16142
16339
  function writeGranularAgentSkills(options) {
16143
- const { projectRoot, includeCursorHooks, includeCursorSubagents, controlPlaneSessionId, onboardingContext } = options;
16340
+ const {
16341
+ projectRoot,
16342
+ includeCursorHooks,
16343
+ includeCursorSubagents,
16344
+ controlPlaneSessionId,
16345
+ onboardingContext
16346
+ } = options;
16347
+ const readmePath = path3__namespace.join(projectRoot, "README.md");
16348
+ const readmeContent = fs__namespace.existsSync(readmePath) ? fs__namespace.readFileSync(readmePath, "utf-8") : "";
16349
+ fs__namespace.writeFileSync(readmePath, mergeReadmeSnippet(readmeContent), "utf-8");
16350
+ maybeWriteFile(
16351
+ path3__namespace.join(projectRoot, ONTOLOGY_BRIEF_NAME),
16352
+ renderOntologyBriefTemplate(onboardingContext)
16353
+ );
16354
+ upsertFile(
16355
+ path3__namespace.join(projectRoot, GRANULAR_START_HERE_NAME),
16356
+ renderStartHereTemplate({ controlPlaneSessionId, onboardingContext })
16357
+ );
16358
+ maybeWriteFile(
16359
+ path3__namespace.join(projectRoot, "ONBOARDING_STATE.md"),
16360
+ renderOnboardingStateTemplate({ controlPlaneSessionId, onboardingContext })
16361
+ );
16362
+ maybeWriteFile(
16363
+ path3__namespace.join(projectRoot, ".agents", "openai.yaml"),
16364
+ renderOpenAiAgentMetadata()
16365
+ );
16366
+ writeClaudeIndex(projectRoot);
16144
16367
  for (const definition of GRANULAR_SKILLS) {
16145
16368
  upsertFile(
16146
16369
  path3__namespace.join(projectRoot, ".agents", "skills", definition.name, "SKILL.md"),
@@ -16164,25 +16387,14 @@ function writeGranularAgentSkills(options) {
16164
16387
  if (includeCursorHooks) {
16165
16388
  writeCursorHooks(projectRoot);
16166
16389
  }
16167
- maybeWriteFile(path3__namespace.join(projectRoot, ONTOLOGY_BRIEF_NAME), renderOntologyBriefTemplate(onboardingContext));
16168
- upsertFile(
16169
- path3__namespace.join(projectRoot, GRANULAR_START_HERE_NAME),
16170
- renderStartHereTemplate({ controlPlaneSessionId, onboardingContext })
16171
- );
16172
- maybeWriteFile(
16173
- path3__namespace.join(projectRoot, "ONBOARDING_STATE.md"),
16174
- renderOnboardingStateTemplate({ controlPlaneSessionId, onboardingContext })
16175
- );
16176
- maybeWriteFile(path3__namespace.join(projectRoot, ".agents", "openai.yaml"), renderOpenAiAgentMetadata());
16177
- writeClaudeIndex(projectRoot);
16178
- const readmePath = path3__namespace.join(projectRoot, "README.md");
16179
- const readmeContent = fs__namespace.existsSync(readmePath) ? fs__namespace.readFileSync(readmePath, "utf-8") : "";
16180
- fs__namespace.writeFileSync(readmePath, mergeReadmeSnippet(readmeContent), "utf-8");
16181
16390
  }
16182
16391
 
16183
16392
  // src/cli/commands/init.ts
16184
16393
  function prompt(question, defaultValue) {
16185
- const rl = readline__namespace.createInterface({ input: process.stdin, output: process.stdout });
16394
+ const rl = readline__namespace.createInterface({
16395
+ input: process.stdin,
16396
+ output: process.stdout
16397
+ });
16186
16398
  const suffix = defaultValue ? ` ${brand.muted(`(${defaultValue})`)}` : "";
16187
16399
  return new Promise((resolve2) => {
16188
16400
  rl.question(` ${question}${suffix}: `, (answer) => {
@@ -16195,7 +16407,10 @@ function waitForEnter(message) {
16195
16407
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
16196
16408
  return Promise.resolve();
16197
16409
  }
16198
- const rl = readline__namespace.createInterface({ input: process.stdin, output: process.stdout });
16410
+ const rl = readline__namespace.createInterface({
16411
+ input: process.stdin,
16412
+ output: process.stdout
16413
+ });
16199
16414
  return new Promise((resolve2) => {
16200
16415
  rl.question(` ${message}`, () => {
16201
16416
  rl.close();
@@ -16223,12 +16438,16 @@ async function selectStarterTemplate(requestedTemplate) {
16223
16438
  return DEFAULT_STARTER_TEMPLATE_ID;
16224
16439
  }
16225
16440
  const templates = listStarterTemplates();
16226
- const defaultIndex = templates.findIndex((template) => template.id === DEFAULT_STARTER_TEMPLATE_ID);
16441
+ const defaultIndex = templates.findIndex(
16442
+ (template) => template.id === DEFAULT_STARTER_TEMPLATE_ID
16443
+ );
16227
16444
  step("Choose a starter ontology template");
16228
16445
  for (const [index, template] of templates.entries()) {
16229
16446
  const isDefault = template.id === DEFAULT_STARTER_TEMPLATE_ID;
16230
16447
  const defaultLabel = isDefault ? ` ${brand.muted("(default)")}` : "";
16231
- console.log(` ${index + 1}. ${brand.bold(template.label)}${defaultLabel}`);
16448
+ console.log(
16449
+ ` ${index + 1}. ${brand.bold(template.label)}${defaultLabel}`
16450
+ );
16232
16451
  dim(` ${template.id} \u2014 ${template.summary}`);
16233
16452
  }
16234
16453
  console.log();
@@ -16241,7 +16460,9 @@ async function selectStarterTemplate(requestedTemplate) {
16241
16460
  if (isStarterTemplateId(normalizedChoice)) {
16242
16461
  return normalizedChoice;
16243
16462
  }
16244
- warn(`Unknown template "${rawChoice}". Falling back to ${DEFAULT_STARTER_TEMPLATE_ID}.`);
16463
+ warn(
16464
+ `Unknown template "${rawChoice}". Falling back to ${DEFAULT_STARTER_TEMPLATE_ID}.`
16465
+ );
16245
16466
  return DEFAULT_STARTER_TEMPLATE_ID;
16246
16467
  }
16247
16468
  async function initCommand(projectName, options) {
@@ -16263,7 +16484,9 @@ async function initCommand(projectName, options) {
16263
16484
  info("No API key found. Starting browser login...");
16264
16485
  await waitForEnter("Press Enter to open the login page in your browser...");
16265
16486
  console.log();
16266
- const waiting = spinner("Opening browser and waiting for authentication...");
16487
+ const waiting = spinner(
16488
+ "Opening browser and waiting for authentication..."
16489
+ );
16267
16490
  try {
16268
16491
  apiKey = await loginWithBrowser({
16269
16492
  authBaseUrl: loadAuthUrl(),
@@ -16310,7 +16533,9 @@ async function initCommand(projectName, options) {
16310
16533
  try {
16311
16534
  sandbox = await api.findOrCreateSandbox(name);
16312
16535
  if (sandbox.name === name) {
16313
- creating.succeed(` Sandbox ready: ${brand.secondary(sandbox.sandboxId)}`);
16536
+ creating.succeed(
16537
+ ` Sandbox ready: ${brand.secondary(sandbox.sandboxId)}`
16538
+ );
16314
16539
  }
16315
16540
  } catch (err) {
16316
16541
  creating.fail(` Failed to create sandbox: ${err.message}`);
@@ -16318,7 +16543,9 @@ async function initCommand(projectName, options) {
16318
16543
  }
16319
16544
  const project = createDefaultManifest(name, templateId);
16320
16545
  writeManifestFile(project);
16321
- success(`Created ${brand.bold("granular.json")} with the ${template.label} starter ontology`);
16546
+ success(
16547
+ `Created ${brand.bold("granular.json")} with the ${template.label} starter ontology`
16548
+ );
16322
16549
  writeRcFile({
16323
16550
  sandboxId: sandbox.sandboxId,
16324
16551
  sandboxName: sandbox.name,
@@ -16354,6 +16581,37 @@ async function initCommand(projectName, options) {
16354
16581
  writeAgentsMdFile(getProjectRoot());
16355
16582
  success(`Updated ${brand.bold("AGENTS.md")}`);
16356
16583
  }
16584
+ const effectsPath = path3__namespace.join(getProjectRoot(), EFFECTS_SCRIPT_NAME);
16585
+ const seedPath = path3__namespace.join(getProjectRoot(), SEED_SCRIPT_NAME);
16586
+ const integrationTestPath = path3__namespace.join(
16587
+ getProjectRoot(),
16588
+ INTEGRATION_TEST_NAME
16589
+ );
16590
+ fs__namespace.writeFileSync(
16591
+ effectsPath,
16592
+ generateEffectsScript(sandbox.sandboxId, apiUrl, templateId),
16593
+ "utf-8"
16594
+ );
16595
+ success(
16596
+ `Created ${brand.bold(EFFECTS_SCRIPT_NAME)} (external effect host)`
16597
+ );
16598
+ fs__namespace.writeFileSync(
16599
+ seedPath,
16600
+ generateSeedScript(sandbox.sandboxId, apiUrl, templateId),
16601
+ "utf-8"
16602
+ );
16603
+ success(
16604
+ `Created ${brand.bold(SEED_SCRIPT_NAME)} (sample record seeding script)`
16605
+ );
16606
+ fs__namespace.mkdirSync(path3__namespace.dirname(integrationTestPath), { recursive: true });
16607
+ fs__namespace.writeFileSync(
16608
+ integrationTestPath,
16609
+ generateIntegrationTest(sandbox.sandboxId, apiUrl, templateId),
16610
+ "utf-8"
16611
+ );
16612
+ success(
16613
+ `Created ${brand.bold(INTEGRATION_TEST_NAME)} (real integration smoke test)`
16614
+ );
16357
16615
  writeGranularAgentSkills({
16358
16616
  projectRoot: getProjectRoot(),
16359
16617
  includeCursorHooks: options?.cursorHooks,
@@ -16368,7 +16626,9 @@ async function initCommand(projectName, options) {
16368
16626
  rolesSummary: options?.rolesSummary
16369
16627
  }
16370
16628
  });
16371
- success("Generated Granular agent skills for Codex, Cursor, and Claude Code");
16629
+ success(
16630
+ "Generated Granular agent skills for Codex, Cursor, and Claude Code"
16631
+ );
16372
16632
  success(
16373
16633
  `Created ${brand.bold(ONTOLOGY_BRIEF_NAME)}, ${brand.bold(GRANULAR_START_HERE_NAME)}, and ${brand.bold("ONBOARDING_STATE.md")}; updated ${brand.bold("README.md")}`
16374
16634
  );
@@ -16388,41 +16648,24 @@ async function initCommand(projectName, options) {
16388
16648
  );
16389
16649
  lastManifestId = manifest.manifestId;
16390
16650
  building.text = " Triggering build...";
16391
- const build = await api.triggerBuild(sandbox.sandboxId, manifest.manifestId);
16651
+ const build = await api.triggerBuild(
16652
+ sandbox.sandboxId,
16653
+ manifest.manifestId
16654
+ );
16392
16655
  building.text = " Building...";
16393
16656
  const completed = await api.waitForBuild(build.buildId, (status) => {
16394
16657
  building.text = ` Building... ${brand.muted(status)}`;
16395
16658
  });
16396
16659
  lastBuildId = completed.buildId;
16397
16660
  initialBuildSucceeded = true;
16398
- building.succeed(` Build completed: ${brand.secondary(completed.buildId)}`);
16661
+ building.succeed(
16662
+ ` Build completed: ${brand.secondary(completed.buildId)}`
16663
+ );
16399
16664
  } catch (err) {
16400
16665
  building.fail(` Build failed: ${err.message}`);
16401
16666
  dim("You can retry with `granular build`");
16402
16667
  }
16403
16668
  }
16404
- const effectsPath = path3__namespace.join(getProjectRoot(), EFFECTS_SCRIPT_NAME);
16405
- const seedPath = path3__namespace.join(getProjectRoot(), SEED_SCRIPT_NAME);
16406
- const integrationTestPath = path3__namespace.join(getProjectRoot(), INTEGRATION_TEST_NAME);
16407
- fs__namespace.writeFileSync(
16408
- effectsPath,
16409
- generateEffectsScript(sandbox.sandboxId, apiUrl, templateId),
16410
- "utf-8"
16411
- );
16412
- success(`Created ${brand.bold(EFFECTS_SCRIPT_NAME)} (external effect host)`);
16413
- fs__namespace.writeFileSync(
16414
- seedPath,
16415
- generateSeedScript(sandbox.sandboxId, apiUrl, templateId),
16416
- "utf-8"
16417
- );
16418
- success(`Created ${brand.bold(SEED_SCRIPT_NAME)} (sample record seeding script)`);
16419
- fs__namespace.mkdirSync(path3__namespace.dirname(integrationTestPath), { recursive: true });
16420
- fs__namespace.writeFileSync(
16421
- integrationTestPath,
16422
- generateIntegrationTest(sandbox.sandboxId, apiUrl, templateId),
16423
- "utf-8"
16424
- );
16425
- success(`Created ${brand.bold(INTEGRATION_TEST_NAME)} (real integration smoke test)`);
16426
16669
  writeSandboxAgentDocFile(getProjectRoot(), project.manifest, {
16427
16670
  sandboxId: sandbox.sandboxId,
16428
16671
  apiUrl,
@@ -16430,18 +16673,20 @@ async function initCommand(projectName, options) {
16430
16673
  buildId: lastBuildId,
16431
16674
  buildPending: !initialBuildSucceeded
16432
16675
  });
16433
- success(`Wrote ${brand.bold("GRANULAR_SANDBOX.md")} (agent reference for this sandbox)`);
16676
+ success(
16677
+ `Wrote ${brand.bold("GRANULAR_SANDBOX.md")} (agent reference for this sandbox)`
16678
+ );
16434
16679
  console.log();
16435
16680
  divider();
16436
16681
  console.log();
16437
16682
  success(`Project ${brand.bold(name)} initialized!`);
16438
16683
  console.log();
16439
16684
  const kv = {
16440
- "Ontology": sandbox.sandboxId,
16441
- "Template": template.label,
16442
- "Manifest": "granular.json",
16443
- "Config": ".granularrc",
16444
- "Auth": "granular login / .env.local",
16685
+ Ontology: sandbox.sandboxId,
16686
+ Template: template.label,
16687
+ Manifest: "granular.json",
16688
+ Config: ".granularrc",
16689
+ Auth: "granular login / .env.local",
16445
16690
  "Start here": GRANULAR_START_HERE_NAME,
16446
16691
  "Ontology brief": ONTOLOGY_BRIEF_NAME,
16447
16692
  "Seed script": SEED_SCRIPT_NAME,
@@ -16477,25 +16722,65 @@ async function initCommand(projectName, options) {
16477
16722
  }
16478
16723
  keyValue(kv);
16479
16724
  const nextSteps2 = options?.skipBuild ? [
16480
- { command: "granular login", description: "Ensure local auth is ready before building or testing" },
16481
- { command: `open ${GRANULAR_START_HERE_NAME}`, description: "Read the happy path and file map shared by humans and agents" },
16482
- { command: `open ${ONTOLOGY_BRIEF_NAME}`, description: "Fill the product brief before editing granular.json" },
16483
- { command: "granular build", description: "Build the starter ontology before you seed or simulate" },
16484
- { command: `node --test ${INTEGRATION_TEST_NAME}`, description: "Run the generated real Granular smoke test against your dev ontology" },
16485
- { command: "granular simulate", description: "Open the simulator for this sandbox" }
16725
+ {
16726
+ command: "granular login",
16727
+ description: "Ensure local auth is ready before building or testing"
16728
+ },
16729
+ {
16730
+ command: `open ${GRANULAR_START_HERE_NAME}`,
16731
+ description: "Read the happy path and file map shared by humans and agents"
16732
+ },
16733
+ {
16734
+ command: `open ${ONTOLOGY_BRIEF_NAME}`,
16735
+ description: "Fill the product brief before editing granular.json"
16736
+ },
16737
+ {
16738
+ command: "granular build",
16739
+ description: "Build the starter ontology before you seed or simulate"
16740
+ },
16741
+ {
16742
+ command: `node --test ${INTEGRATION_TEST_NAME}`,
16743
+ description: "Run the generated real Granular smoke test against your dev ontology"
16744
+ },
16745
+ {
16746
+ command: "granular simulate",
16747
+ description: "Open the simulator for this sandbox"
16748
+ }
16486
16749
  ] : [
16487
- { command: `open ${GRANULAR_START_HERE_NAME}`, description: "Read the happy path and file map shared by humans and agents" },
16488
- { command: `open ${ONTOLOGY_BRIEF_NAME}`, description: "Fill the brief, then use it to guide your first manifest edits" },
16489
- { command: "granular build", description: "Compile the ontology after updating granular.json" },
16490
- { command: "granular document", description: "Refresh GRANULAR_SANDBOX.md so agents and tests use exact keys" },
16491
- { command: `node --test ${INTEGRATION_TEST_NAME}`, description: "Run the generated real Granular smoke test against your dev ontology" },
16492
- { command: "granular simulate", description: "Open the simulator for this sandbox" }
16750
+ {
16751
+ command: `open ${GRANULAR_START_HERE_NAME}`,
16752
+ description: "Read the happy path and file map shared by humans and agents"
16753
+ },
16754
+ {
16755
+ command: `open ${ONTOLOGY_BRIEF_NAME}`,
16756
+ description: "Fill the brief, then use it to guide your first manifest edits"
16757
+ },
16758
+ {
16759
+ command: "granular build",
16760
+ description: "Compile the ontology after updating granular.json"
16761
+ },
16762
+ {
16763
+ command: "granular document",
16764
+ description: "Refresh GRANULAR_SANDBOX.md so agents and tests use exact keys"
16765
+ },
16766
+ {
16767
+ command: `node --test ${INTEGRATION_TEST_NAME}`,
16768
+ description: "Run the generated real Granular smoke test against your dev ontology"
16769
+ },
16770
+ {
16771
+ command: "granular simulate",
16772
+ description: "Open the simulator for this sandbox"
16773
+ }
16493
16774
  ];
16494
16775
  nextSteps(nextSteps2);
16495
16776
  console.log();
16496
16777
  dim("Mental model:");
16497
- dim(" Query and navigate data through the ontology. Use effects only for actions that leave the sandbox.");
16498
- dim(" The seed script shows how records enter the graph. The effects script shows how external systems plug in.");
16778
+ dim(
16779
+ " Query and navigate data through the ontology. Use effects only for actions that leave the sandbox."
16780
+ );
16781
+ dim(
16782
+ " The seed script shows synchronous batch recording with progress. The effects script shows how external systems plug in."
16783
+ );
16499
16784
  console.log();
16500
16785
  }
16501
16786
  var DEFAULT_LOCAL_API_KEY2 = "gn_sk_tenant_default_principal_local_e2e_00000000";