@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.
- package/README.md +241 -151
- package/dist/cli/index.js +452 -167
- 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: {
|
|
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: {
|
|
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: {
|
|
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: {
|
|
5714
|
-
|
|
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: {
|
|
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: {
|
|
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: {
|
|
5817
|
-
|
|
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: {
|
|
5880
|
+
segment: {
|
|
5881
|
+
type: "string",
|
|
5882
|
+
description: "Customer segment such as self-serve or enterprise"
|
|
5883
|
+
}
|
|
5853
5884
|
}),
|
|
5854
5885
|
createClassOperation("order", {
|
|
5855
|
-
number: {
|
|
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: {
|
|
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: {
|
|
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: {
|
|
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: {
|
|
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: {
|
|
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: {
|
|
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: {
|
|
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
|
-
|
|
6496
|
-
|
|
6497
|
-
|
|
6498
|
-
{ alias: "@std", name: "standard_modules", label: "prod" }
|
|
6499
|
-
|
|
6500
|
-
|
|
6501
|
-
|
|
6502
|
-
|
|
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()\`
|
|
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
|
-
|
|
6552
|
-
|
|
6553
|
-
|
|
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\`.
|
|
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\`
|
|
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(
|
|
14633
|
-
|
|
14634
|
-
|
|
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\`
|
|
14965
|
-
| \`enqueueRecordImport\`, \`listRecordImports\`, \`getRecordImportSummary\`, \u2026 | **Async** bulk import (worker queue + aggregate progress). Prefer when loads are huge
|
|
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)
|
|
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(
|
|
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(
|
|
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(
|
|
15123
|
-
|
|
15124
|
-
|
|
15125
|
-
lines.push(
|
|
15126
|
-
|
|
15127
|
-
|
|
15128
|
-
lines.push(
|
|
15129
|
-
|
|
15130
|
-
|
|
15131
|
-
lines.push(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
15189
|
-
|
|
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(
|
|
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(
|
|
15336
|
+
lines.push(
|
|
15337
|
+
` ${e.submodelKey}: ['${e.foreignClass}_id_1'], // ${e.foreignClass} ids`
|
|
15338
|
+
);
|
|
15226
15339
|
} else {
|
|
15227
|
-
lines.push(
|
|
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(
|
|
15355
|
+
lines.push(
|
|
15356
|
+
"## Effects declared in this manifest (`withEffect` + live handlers)"
|
|
15357
|
+
);
|
|
15241
15358
|
lines.push("");
|
|
15242
|
-
lines.push(
|
|
15359
|
+
lines.push(
|
|
15360
|
+
"Each entry below matches one `withEffect` operation. For every declaration you must:"
|
|
15361
|
+
);
|
|
15243
15362
|
lines.push("");
|
|
15244
|
-
lines.push(
|
|
15245
|
-
|
|
15246
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
15347
|
-
|
|
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(
|
|
15354
|
-
|
|
15355
|
-
|
|
15356
|
-
lines.push(
|
|
15357
|
-
|
|
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(
|
|
15360
|
-
|
|
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(
|
|
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(
|
|
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
|
|
15505
|
-
-
|
|
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(
|
|
15819
|
-
|
|
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(
|
|
15906
|
-
|
|
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(
|
|
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(
|
|
16013
|
-
|
|
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(
|
|
16195
|
+
fs__namespace.writeFileSync(
|
|
16196
|
+
filePath,
|
|
16197
|
+
`${previous.trimEnd()}
|
|
16017
16198
|
|
|
16018
16199
|
${nextContent}
|
|
16019
|
-
`,
|
|
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(
|
|
16024
|
-
|
|
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: [
|
|
16032
|
-
|
|
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 {
|
|
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({
|
|
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({
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
16441
|
-
|
|
16442
|
-
|
|
16443
|
-
|
|
16444
|
-
|
|
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
|
-
{
|
|
16481
|
-
|
|
16482
|
-
|
|
16483
|
-
|
|
16484
|
-
{
|
|
16485
|
-
|
|
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
|
-
{
|
|
16488
|
-
|
|
16489
|
-
|
|
16490
|
-
|
|
16491
|
-
{
|
|
16492
|
-
|
|
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(
|
|
16498
|
-
|
|
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";
|