@elevasis/sdk 0.5.14 → 0.5.16

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/dist/cli.cjs CHANGED
@@ -40164,6 +40164,12 @@ var DOMAIN_MAP = {
40164
40164
  [DOMAINS.DIAGNOSTIC]: DIAGNOSTIC_DOMAIN
40165
40165
  };
40166
40166
 
40167
+ // ../core/src/platform/registry/reserved.ts
40168
+ var RESERVED_RESOURCE_IDS = /* @__PURE__ */ new Set(["command-center-assistant"]);
40169
+ function isReservedResourceId(resourceId) {
40170
+ return RESERVED_RESOURCE_IDS.has(resourceId);
40171
+ }
40172
+
40167
40173
  // ../core/src/execution/engine/base/errors.ts
40168
40174
  var ExecutionError = class extends Error {
40169
40175
  /**
@@ -43363,6 +43369,13 @@ var ResourceRegistry = class {
43363
43369
  }
43364
43370
  seen.add(id);
43365
43371
  }
43372
+ for (const id of incomingIds) {
43373
+ if (isReservedResourceId(id)) {
43374
+ throw new Error(
43375
+ `Resource ID '${id}' is reserved for platform use. External deployments cannot use reserved resource IDs.`
43376
+ );
43377
+ }
43378
+ }
43366
43379
  if (this.isRemote(orgName)) {
43367
43380
  this.unregisterOrganization(orgName);
43368
43381
  }
@@ -43395,6 +43408,46 @@ var ResourceRegistry = class {
43395
43408
  }
43396
43409
  this.serializedCache.set(orgName, serializeOrganization(this.registry[orgName]));
43397
43410
  }
43411
+ /**
43412
+ * Register built-in platform resources (static, local execution)
43413
+ *
43414
+ * Unlike registerOrganization(), these resources:
43415
+ * - Do NOT have remote config (execute in-process, not in worker threads)
43416
+ * - Are NOT removed by unregisterOrganization() (persist across redeployments)
43417
+ * - Use reserved resource IDs that external deployments cannot claim
43418
+ *
43419
+ * @param orgName - Organization name
43420
+ * @param org - Resource definitions with real handlers (not stubs)
43421
+ */
43422
+ registerStaticResources(orgName, org) {
43423
+ const incomingWorkflowIds = (org.workflows ?? []).map((w) => w.config.resourceId);
43424
+ const incomingAgentIds = (org.agents ?? []).map((a) => a.config.resourceId);
43425
+ const incomingIds = [...incomingWorkflowIds, ...incomingAgentIds];
43426
+ const seen = /* @__PURE__ */ new Set();
43427
+ for (const id of incomingIds) {
43428
+ if (seen.has(id)) {
43429
+ throw new Error(`Duplicate resource ID '${id}' in static resources.`);
43430
+ }
43431
+ seen.add(id);
43432
+ }
43433
+ const existingOrg = this.registry[orgName];
43434
+ if (existingOrg) {
43435
+ const existingWorkflowIds = new Set((existingOrg.workflows ?? []).map((w) => w.config.resourceId));
43436
+ const existingAgentIds = new Set((existingOrg.agents ?? []).map((a) => a.config.resourceId));
43437
+ for (const id of incomingIds) {
43438
+ if (existingWorkflowIds.has(id) || existingAgentIds.has(id)) {
43439
+ throw new Error(`Static resource '${id}' conflicts with existing resource in '${orgName}'.`);
43440
+ }
43441
+ }
43442
+ }
43443
+ if (existingOrg) {
43444
+ existingOrg.workflows = [...existingOrg.workflows ?? [], ...org.workflows ?? []];
43445
+ existingOrg.agents = [...existingOrg.agents ?? [], ...org.agents ?? []];
43446
+ } else {
43447
+ this.registry[orgName] = org;
43448
+ }
43449
+ this.serializedCache.set(orgName, serializeOrganization(this.registry[orgName]));
43450
+ }
43398
43451
  /**
43399
43452
  * Unregister runtime-registered resources for an organization
43400
43453
  *
@@ -43798,7 +43851,7 @@ async function apiDelete(endpoint, apiUrl = resolveApiUrl()) {
43798
43851
  // package.json
43799
43852
  var package_default = {
43800
43853
  name: "@elevasis/sdk",
43801
- version: "0.5.14",
43854
+ version: "0.5.16",
43802
43855
  description: "SDK for building Elevasis organization resources",
43803
43856
  type: "module",
43804
43857
  bin: {
@@ -43884,9 +43937,7 @@ async function scanDocumentation() {
43884
43937
  const raw = await (0, import_promises.readFile)(fullPath, "utf-8");
43885
43938
  const fileSize = Buffer.byteLength(raw, "utf-8");
43886
43939
  if (fileSize > 100 * 1024) {
43887
- throw new Error(
43888
- `Documentation file exceeds 100KB: docs/${relPath} (${Math.round(fileSize / 1024)}KB)`
43889
- );
43940
+ throw new Error(`Documentation file exceeds 100KB: docs/${relPath} (${Math.round(fileSize / 1024)}KB)`);
43890
43941
  }
43891
43942
  totalSize += fileSize;
43892
43943
  if (totalSize > 1024 * 1024) {
@@ -43939,7 +43990,9 @@ async function generateResourceMap(org) {
43939
43990
  );
43940
43991
  for (const w of workflows) {
43941
43992
  const desc = escapeMdx(w.config.description);
43942
- lines.push(`| \`${w.config.resourceId}\` | ${escapeMdx(w.config.name)} | ${w.config.version} | ${w.config.status} | ${desc} |`);
43993
+ lines.push(
43994
+ `| \`${w.config.resourceId}\` | ${escapeMdx(w.config.name)} | ${w.config.version} | ${w.config.status} | ${desc} |`
43995
+ );
43943
43996
  }
43944
43997
  lines.push("");
43945
43998
  }
@@ -43952,11 +44005,16 @@ async function generateResourceMap(org) {
43952
44005
  );
43953
44006
  for (const a of agents) {
43954
44007
  const desc = escapeMdx(a.config.description);
43955
- lines.push(`| \`${a.config.resourceId}\` | ${escapeMdx(a.config.name)} | ${a.config.version} | ${a.config.status} | ${desc} |`);
44008
+ lines.push(
44009
+ `| \`${a.config.resourceId}\` | ${escapeMdx(a.config.name)} | ${a.config.version} | ${a.config.status} | ${desc} |`
44010
+ );
43956
44011
  }
43957
44012
  lines.push("");
43958
44013
  }
43959
- lines.push(`**Total:** ${workflows.length + agents.length} resources (${workflows.length} workflows, ${agents.length} agents)`, "");
44014
+ lines.push(
44015
+ `**Total:** ${workflows.length + agents.length} resources (${workflows.length} workflows, ${agents.length} agents)`,
44016
+ ""
44017
+ );
43960
44018
  await (0, import_promises.mkdir)((0, import_path.resolve)("docs"), { recursive: true });
43961
44019
  await (0, import_promises.writeFile)((0, import_path.resolve)("docs/resource-map.mdx"), lines.join("\n"), "utf-8");
43962
44020
  }
@@ -44020,8 +44078,12 @@ async function generateProjectMap(org) {
44020
44078
  if (resourceCount === 0) {
44021
44079
  types = "(utilities)";
44022
44080
  } else {
44023
- const wCount = workflows.filter((w) => Array.isArray(w.config.domains) && w.config.domains.includes(domainName)).length;
44024
- const aCount = agents.filter((a) => Array.isArray(a.config.domains) && a.config.domains.includes(domainName)).length;
44081
+ const wCount = workflows.filter(
44082
+ (w) => Array.isArray(w.config.domains) && w.config.domains.includes(domainName)
44083
+ ).length;
44084
+ const aCount = agents.filter(
44085
+ (a) => Array.isArray(a.config.domains) && a.config.domains.includes(domainName)
44086
+ ).length;
44025
44087
  const parts = [];
44026
44088
  if (wCount > 0) parts.push(`${wCount} workflow${wCount !== 1 ? "s" : ""}`);
44027
44089
  if (aCount > 0) parts.push(`${aCount} agent${aCount !== 1 ? "s" : ""}`);
@@ -44133,10 +44195,7 @@ async function generateProjectMap(org) {
44133
44195
  if (categories.length === 0) {
44134
44196
  lines.push("SDK reference found but no categories parsed.", "");
44135
44197
  } else {
44136
- lines.push(
44137
- "| Category | Files | Covers |",
44138
- "| --- | --- | --- |"
44139
- );
44198
+ lines.push("| Category | Files | Covers |", "| --- | --- | --- |");
44140
44199
  for (const cat of categories) {
44141
44200
  const covers = cat.titles.slice(0, 5).join(", ") + (cat.titles.length > 5 ? ", ..." : "");
44142
44201
  lines.push(`| ${escapeMdx(cat.name)} | ${cat.count} | ${escapeMdx(covers)} |`);
@@ -44154,10 +44213,7 @@ async function generateProjectMap(org) {
44154
44213
  if (cmdFiles.length === 0) {
44155
44214
  lines.push("No commands found.", "");
44156
44215
  } else {
44157
- lines.push(
44158
- "| Command | File | Purpose |",
44159
- "| --- | --- | --- |"
44160
- );
44216
+ lines.push("| Command | File | Purpose |", "| --- | --- | --- |");
44161
44217
  for (const f of cmdFiles) {
44162
44218
  const cmdName = f.name.replace(/\.md$/, "");
44163
44219
  let purpose = "";
@@ -44186,10 +44242,7 @@ async function generateProjectMap(org) {
44186
44242
  if (ruleFiles.length === 0) {
44187
44243
  lines.push("No rules found.", "");
44188
44244
  } else {
44189
- lines.push(
44190
- "| Rule | File | Scope |",
44191
- "| --- | --- | --- |"
44192
- );
44245
+ lines.push("| Rule | File | Scope |", "| --- | --- | --- |");
44193
44246
  for (const f of ruleFiles) {
44194
44247
  const ruleName = f.name.replace(/\.md$/, "").replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
44195
44248
  let scope = "";
@@ -44220,10 +44273,7 @@ async function generateProjectMap(org) {
44220
44273
  if (skillDirs.length === 0) {
44221
44274
  lines.push("No skills found.", "");
44222
44275
  } else {
44223
- lines.push(
44224
- "| Skill | File | Trigger |",
44225
- "| --- | --- | --- |"
44226
- );
44276
+ lines.push("| Skill | File | Trigger |", "| --- | --- | --- |");
44227
44277
  for (const d of skillDirs) {
44228
44278
  const skillFile = (0, import_path.resolve)(".claude/skills", d.name, "SKILL.md");
44229
44279
  let trigger = "";
@@ -44279,10 +44329,7 @@ async function generateProjectMap(org) {
44279
44329
  if (memoryFiles.length === 0) {
44280
44330
  lines.push("No memory files found.", "");
44281
44331
  } else {
44282
- lines.push(
44283
- "| File | Purpose | Last Modified |",
44284
- "| --- | --- | --- |"
44285
- );
44332
+ lines.push("| File | Purpose | Last Modified |", "| --- | --- | --- |");
44286
44333
  for (const m of memoryFiles) {
44287
44334
  lines.push(`| .claude/memory/${m.rel} | ${escapeMdx(m.purpose)} | ${m.mtime} |`);
44288
44335
  }
@@ -44314,250 +44361,293 @@ async function generateProjectMap(org) {
44314
44361
  await (0, import_promises.mkdir)((0, import_path.resolve)("docs"), { recursive: true });
44315
44362
  await (0, import_promises.writeFile)((0, import_path.resolve)("docs/project-map.mdx"), lines.join("\n"), "utf-8");
44316
44363
  }
44364
+ async function generateNavigationMap(docs) {
44365
+ const excludedFiles = /* @__PURE__ */ new Set(["docs/navigation-map.mdx", "docs/project-map.mdx", "docs/resource-map.mdx"]);
44366
+ const filtered = docs.filter((doc) => !excludedFiles.has(doc.path)).sort((a, b) => {
44367
+ const orderA = a.frontmatter.order ?? 9999;
44368
+ const orderB = b.frontmatter.order ?? 9999;
44369
+ if (orderA !== orderB) return orderA - orderB;
44370
+ return a.frontmatter.title.localeCompare(b.frontmatter.title);
44371
+ });
44372
+ const lines = [
44373
+ "---",
44374
+ "title: Navigation Map",
44375
+ "description: Auto-generated table of contents for all documentation (updated on each deploy)",
44376
+ "order: 997",
44377
+ "---",
44378
+ "",
44379
+ "# Navigation Map",
44380
+ "",
44381
+ "> Auto-generated by `elevasis-sdk deploy`. Do not edit manually.",
44382
+ "",
44383
+ "| Title | Description | Path |",
44384
+ "| --- | --- | --- |"
44385
+ ];
44386
+ for (const doc of filtered) {
44387
+ lines.push(`| ${escapeMdx(doc.frontmatter.title)} | ${escapeMdx(doc.frontmatter.description)} | ${doc.path} |`);
44388
+ }
44389
+ lines.push("");
44390
+ await (0, import_promises.mkdir)((0, import_path.resolve)("docs"), { recursive: true });
44391
+ await (0, import_promises.writeFile)((0, import_path.resolve)("docs/navigation-map.mdx"), lines.join("\n"), "utf-8");
44392
+ }
44317
44393
  function registerDeployCommand(program3) {
44318
- program3.command("deploy").description("Validate, bundle, upload, and deploy project resources\n Example: elevasis-sdk deploy --api-url http://localhost:5170").option("--api-url <url>", "API URL").option("--entry <path>", "Path to entry file (default: ./src/index.ts)").option("--prod", "Deploy to production (overrides NODE_ENV=development)").action(wrapAction("deploy", async (options2) => {
44319
- const startTime = Date.now();
44320
- const apiUrl = resolveApiUrl(options2.apiUrl, options2.prod);
44321
- const env2 = resolveEnvironment(options2.prod);
44322
- const entryPath = options2.entry ?? "./src/index.ts";
44323
- const authSpinner = ora("Authenticating...").start();
44324
- let orgName;
44325
- try {
44326
- const me = await apiGet("/api/external/me", apiUrl);
44327
- orgName = me.organizationName;
44328
- authSpinner.succeed(
44329
- source_default.green("Authenticating...") + source_default.white(" done") + source_default.gray(` (${orgName})`)
44330
- );
44331
- } catch (error46) {
44332
- authSpinner.fail(source_default.red("Authentication failed"));
44333
- const errMsg = error46 instanceof Error ? error46.message : String(error46);
44334
- if (errMsg.includes("401") || errMsg.toLowerCase().includes("unauthorized")) {
44335
- const keyVar = !options2.prod && process.env.NODE_ENV === "development" && process.env.ELEVASIS_PLATFORM_KEY_DEV ? "ELEVASIS_PLATFORM_KEY_DEV" : "ELEVASIS_PLATFORM_KEY";
44336
- console.error(source_default.red(" Invalid API key."));
44337
- console.error(source_default.gray(` Your ${keyVar} was rejected by the server.`));
44338
- console.error(source_default.gray(" Check your .env file and verify the key in the Elevasis dashboard."));
44339
- } else {
44340
- console.error(source_default.gray(" Check your API key and API URL."));
44394
+ program3.command("deploy").description(
44395
+ "Validate, bundle, upload, and deploy project resources\n Example: elevasis-sdk deploy --api-url http://localhost:5170"
44396
+ ).option("--api-url <url>", "API URL").option("--entry <path>", "Path to entry file (default: ./src/index.ts)").option("--prod", "Deploy to production (overrides NODE_ENV=development)").action(
44397
+ wrapAction("deploy", async (options2) => {
44398
+ const startTime = Date.now();
44399
+ const apiUrl = resolveApiUrl(options2.apiUrl, options2.prod);
44400
+ const env2 = resolveEnvironment(options2.prod);
44401
+ const entryPath = options2.entry ?? "./src/index.ts";
44402
+ const authSpinner = ora("Authenticating...").start();
44403
+ let orgName;
44404
+ try {
44405
+ const me = await apiGet("/api/external/me", apiUrl);
44406
+ orgName = me.organizationName;
44407
+ authSpinner.succeed(
44408
+ source_default.green("Authenticating...") + source_default.white(" done") + source_default.gray(` (${orgName})`)
44409
+ );
44410
+ } catch (error46) {
44411
+ authSpinner.fail(source_default.red("Authentication failed"));
44412
+ const errMsg = error46 instanceof Error ? error46.message : String(error46);
44413
+ if (errMsg.includes("401") || errMsg.toLowerCase().includes("unauthorized")) {
44414
+ const keyVar = !options2.prod && process.env.NODE_ENV === "development" && process.env.ELEVASIS_PLATFORM_KEY_DEV ? "ELEVASIS_PLATFORM_KEY_DEV" : "ELEVASIS_PLATFORM_KEY";
44415
+ console.error(source_default.red(" Invalid API key."));
44416
+ console.error(source_default.gray(` Your ${keyVar} was rejected by the server.`));
44417
+ console.error(source_default.gray(" Check your .env file and verify the key in the Elevasis dashboard."));
44418
+ } else {
44419
+ console.error(source_default.gray(" Check your API key and API URL."));
44420
+ }
44421
+ throw error46;
44341
44422
  }
44342
- throw error46;
44343
- }
44344
- const validateSpinner = ora("Validating...").start();
44345
- let org;
44346
- try {
44347
- const jiti = (0, import_jiti.createJiti)(import_meta.url);
44348
- const entryModule = await jiti.import((0, import_path.resolve)(entryPath));
44349
- org = entryModule.default;
44350
- if (!org) {
44351
- validateSpinner.fail("Invalid entry: no default export found");
44352
- console.error(source_default.gray(` Entry file: ${(0, import_path.resolve)(entryPath)}`));
44353
- throw new Error("Invalid entry: no default export found");
44423
+ const validateSpinner = ora("Validating...").start();
44424
+ let org;
44425
+ try {
44426
+ const jiti = (0, import_jiti.createJiti)(import_meta.url);
44427
+ const entryModule = await jiti.import((0, import_path.resolve)(entryPath));
44428
+ org = entryModule.default;
44429
+ if (!org) {
44430
+ validateSpinner.fail("Invalid entry: no default export found");
44431
+ console.error(source_default.gray(` Entry file: ${(0, import_path.resolve)(entryPath)}`));
44432
+ throw new Error("Invalid entry: no default export found");
44433
+ }
44434
+ new ResourceRegistry({ [orgName]: org });
44435
+ const workflowCount = org.workflows?.length ?? 0;
44436
+ const agentCount = org.agents?.length ?? 0;
44437
+ const totalCount = workflowCount + agentCount;
44438
+ validateSpinner.succeed(
44439
+ source_default.green("Validating...") + source_default.white(" done") + source_default.gray(` (${totalCount} resource${totalCount !== 1 ? "s" : ""}, 0 errors)`)
44440
+ );
44441
+ console.log("");
44442
+ console.log(source_default.gray(` Org: ${orgName}`));
44443
+ console.log(source_default.gray(` Target: ${apiUrl} (${env2})`));
44444
+ console.log("");
44445
+ for (const w of org.workflows ?? []) {
44446
+ console.log(source_default.gray(` workflow ${source_default.white(w.config.resourceId)} v${w.config.version}`));
44447
+ }
44448
+ for (const a of org.agents ?? []) {
44449
+ console.log(source_default.gray(` agent ${source_default.white(a.config.resourceId)} v${a.config.version}`));
44450
+ }
44451
+ console.log("");
44452
+ } catch (error46) {
44453
+ if (error46 instanceof RegistryValidationError) {
44454
+ validateSpinner.fail(source_default.red("Validation failed"));
44455
+ console.error("");
44456
+ console.error(source_default.red(` ERROR ${error46.message}`));
44457
+ if (error46.resourceId) {
44458
+ console.error(source_default.gray(` Resource: ${error46.resourceId}`));
44459
+ }
44460
+ console.error("");
44461
+ console.error(source_default.gray(" Deploy aborted."));
44462
+ }
44463
+ throw error46;
44354
44464
  }
44355
- new ResourceRegistry({ [orgName]: org });
44356
- const workflowCount = org.workflows?.length ?? 0;
44357
- const agentCount = org.agents?.length ?? 0;
44358
- const totalCount = workflowCount + agentCount;
44359
- validateSpinner.succeed(
44360
- source_default.green("Validating...") + source_default.white(" done") + source_default.gray(` (${totalCount} resource${totalCount !== 1 ? "s" : ""}, 0 errors)`)
44361
- );
44362
- console.log("");
44363
- console.log(source_default.gray(` Org: ${orgName}`));
44364
- console.log(source_default.gray(` Target: ${apiUrl} (${env2})`));
44365
- console.log("");
44366
- for (const w of org.workflows ?? []) {
44367
- console.log(source_default.gray(` workflow ${source_default.white(w.config.resourceId)} v${w.config.version}`));
44465
+ await generateResourceMap(org);
44466
+ await generateProjectMap(org);
44467
+ let documentation = await scanDocumentation();
44468
+ if (documentation) {
44469
+ await generateNavigationMap(documentation);
44470
+ documentation = await scanDocumentation();
44471
+ console.log(
44472
+ source_default.gray(
44473
+ ` docs ${source_default.white(String(documentation.length))} file${documentation.length !== 1 ? "s" : ""}`
44474
+ )
44475
+ );
44368
44476
  }
44369
- for (const a of org.agents ?? []) {
44370
- console.log(source_default.gray(` agent ${source_default.white(a.config.resourceId)} v${a.config.version}`));
44477
+ const triggerCount = org.triggers?.length ?? 0;
44478
+ const integrationCount = org.integrations?.length ?? 0;
44479
+ const checkpointCount = org.humanCheckpoints?.length ?? 0;
44480
+ if (triggerCount > 0) console.log(source_default.gray(` triggers ${source_default.white(String(triggerCount))}`));
44481
+ if (integrationCount > 0) console.log(source_default.gray(` integrations ${source_default.white(String(integrationCount))}`));
44482
+ if (checkpointCount > 0) console.log(source_default.gray(` checkpoints ${source_default.white(String(checkpointCount))}`));
44483
+ const relationshipCount = org.relationships ? Object.keys(org.relationships).length : 0;
44484
+ if (relationshipCount > 0) {
44485
+ console.log(
44486
+ source_default.gray(
44487
+ ` rels ${source_default.white(String(relationshipCount))} resource${relationshipCount !== 1 ? "s" : ""}`
44488
+ )
44489
+ );
44371
44490
  }
44372
- console.log("");
44373
- } catch (error46) {
44374
- if (error46 instanceof RegistryValidationError) {
44375
- validateSpinner.fail(source_default.red("Validation failed"));
44376
- console.error("");
44377
- console.error(source_default.red(` ERROR ${error46.message}`));
44378
- if (error46.resourceId) {
44379
- console.error(source_default.gray(` Resource: ${error46.resourceId}`));
44491
+ const schemaWarnings = [];
44492
+ const workflows = (org.workflows ?? []).map((w) => {
44493
+ const meta = {
44494
+ resourceId: w.config.resourceId,
44495
+ name: w.config.name,
44496
+ version: w.config.version,
44497
+ status: w.config.status,
44498
+ description: w.config.description,
44499
+ domains: w.config.domains
44500
+ };
44501
+ if (w.contract.inputSchema) {
44502
+ try {
44503
+ meta.inputSchema = external_exports.toJSONSchema(w.contract.inputSchema);
44504
+ } catch {
44505
+ schemaWarnings.push(`${w.config.resourceId}: inputSchema could not be serialized`);
44506
+ }
44380
44507
  }
44381
- console.error("");
44382
- console.error(source_default.gray(" Deploy aborted."));
44383
- }
44384
- throw error46;
44385
- }
44386
- await generateResourceMap(org);
44387
- await generateProjectMap(org);
44388
- const documentation = await scanDocumentation();
44389
- if (documentation) {
44390
- console.log(source_default.gray(` docs ${source_default.white(String(documentation.length))} file${documentation.length !== 1 ? "s" : ""}`));
44391
- }
44392
- const triggerCount = org.triggers?.length ?? 0;
44393
- const integrationCount = org.integrations?.length ?? 0;
44394
- const checkpointCount = org.humanCheckpoints?.length ?? 0;
44395
- if (triggerCount > 0) console.log(source_default.gray(` triggers ${source_default.white(String(triggerCount))}`));
44396
- if (integrationCount > 0) console.log(source_default.gray(` integrations ${source_default.white(String(integrationCount))}`));
44397
- if (checkpointCount > 0) console.log(source_default.gray(` checkpoints ${source_default.white(String(checkpointCount))}`));
44398
- const relationshipCount = org.relationships ? Object.keys(org.relationships).length : 0;
44399
- if (relationshipCount > 0) {
44400
- console.log(source_default.gray(` rels ${source_default.white(String(relationshipCount))} resource${relationshipCount !== 1 ? "s" : ""}`));
44401
- }
44402
- const schemaWarnings = [];
44403
- const workflows = (org.workflows ?? []).map((w) => {
44404
- const meta = {
44405
- resourceId: w.config.resourceId,
44406
- name: w.config.name,
44407
- version: w.config.version,
44408
- status: w.config.status,
44409
- description: w.config.description,
44410
- domains: w.config.domains
44411
- };
44412
- if (w.contract.inputSchema) {
44413
- try {
44414
- meta.inputSchema = external_exports.toJSONSchema(w.contract.inputSchema);
44415
- } catch {
44416
- schemaWarnings.push(`${w.config.resourceId}: inputSchema could not be serialized`);
44508
+ if (w.contract.outputSchema) {
44509
+ try {
44510
+ meta.outputSchema = external_exports.toJSONSchema(w.contract.outputSchema);
44511
+ } catch {
44512
+ schemaWarnings.push(`${w.config.resourceId}: outputSchema could not be serialized`);
44513
+ }
44417
44514
  }
44418
- }
44419
- if (w.contract.outputSchema) {
44420
- try {
44421
- meta.outputSchema = external_exports.toJSONSchema(w.contract.outputSchema);
44422
- } catch {
44423
- schemaWarnings.push(`${w.config.resourceId}: outputSchema could not be serialized`);
44515
+ return meta;
44516
+ });
44517
+ const agents = (org.agents ?? []).map((a) => {
44518
+ const meta = {
44519
+ resourceId: a.config.resourceId,
44520
+ name: a.config.name,
44521
+ version: a.config.version,
44522
+ status: a.config.status,
44523
+ description: a.config.description,
44524
+ domains: a.config.domains
44525
+ };
44526
+ if (a.contract.inputSchema) {
44527
+ try {
44528
+ meta.inputSchema = external_exports.toJSONSchema(a.contract.inputSchema);
44529
+ } catch {
44530
+ schemaWarnings.push(`${a.config.resourceId}: inputSchema could not be serialized`);
44531
+ }
44424
44532
  }
44425
- }
44426
- return meta;
44427
- });
44428
- const agents = (org.agents ?? []).map((a) => {
44429
- const meta = {
44430
- resourceId: a.config.resourceId,
44431
- name: a.config.name,
44432
- version: a.config.version,
44433
- status: a.config.status,
44434
- description: a.config.description,
44435
- domains: a.config.domains
44436
- };
44437
- if (a.contract.inputSchema) {
44438
- try {
44439
- meta.inputSchema = external_exports.toJSONSchema(a.contract.inputSchema);
44440
- } catch {
44441
- schemaWarnings.push(`${a.config.resourceId}: inputSchema could not be serialized`);
44533
+ if (a.contract.outputSchema) {
44534
+ try {
44535
+ meta.outputSchema = external_exports.toJSONSchema(a.contract.outputSchema);
44536
+ } catch {
44537
+ schemaWarnings.push(`${a.config.resourceId}: outputSchema could not be serialized`);
44538
+ }
44442
44539
  }
44443
- }
44444
- if (a.contract.outputSchema) {
44445
- try {
44446
- meta.outputSchema = external_exports.toJSONSchema(a.contract.outputSchema);
44447
- } catch {
44448
- schemaWarnings.push(`${a.config.resourceId}: outputSchema could not be serialized`);
44540
+ return meta;
44541
+ });
44542
+ if (schemaWarnings.length > 0) {
44543
+ for (const warning of schemaWarnings) {
44544
+ console.log(source_default.yellow(` warn ${warning}`));
44449
44545
  }
44546
+ console.log(source_default.gray(" Schemas will be unavailable on the platform for these resources."));
44547
+ console.log("");
44450
44548
  }
44451
- return meta;
44452
- });
44453
- if (schemaWarnings.length > 0) {
44454
- for (const warning of schemaWarnings) {
44455
- console.log(source_default.yellow(` warn ${warning}`));
44456
- }
44457
- console.log(source_default.gray(" Schemas will be unavailable on the platform for these resources."));
44458
- console.log("");
44459
- }
44460
- const bundleSpinner = ora("Bundling...").start();
44461
- const wrapperPath = (0, import_path.resolve)("__elevasis_worker.ts");
44462
- const bundleOutfile = (0, import_path.resolve)("dist/bundle.js");
44463
- try {
44464
- const entryImport = entryPath.replace(/\.ts$/, ".js");
44465
- const wrapperContent = `import org from ${JSON.stringify(entryImport)}
44549
+ const bundleSpinner = ora("Bundling...").start();
44550
+ const wrapperPath = (0, import_path.resolve)("__elevasis_worker.ts");
44551
+ const bundleOutfile = (0, import_path.resolve)("dist/bundle.js");
44552
+ try {
44553
+ const entryImport = entryPath.replace(/\.ts$/, ".js");
44554
+ const wrapperContent = `import org from ${JSON.stringify(entryImport)}
44466
44555
  import { startWorker } from '@elevasis/sdk/worker'
44467
44556
  startWorker(org)
44468
44557
  `;
44469
- await (0, import_promises.writeFile)(wrapperPath, wrapperContent, "utf-8");
44470
- await (0, import_promises.mkdir)((0, import_path.resolve)("dist"), { recursive: true });
44471
- await esbuild.build({
44472
- entryPoints: [wrapperPath],
44473
- bundle: true,
44474
- platform: "node",
44475
- format: "cjs",
44476
- outfile: bundleOutfile
44477
- });
44478
- await (0, import_promises.unlink)(wrapperPath);
44479
- const bundleBuffer = await (0, import_promises.readFile)(bundleOutfile);
44480
- const bundleSizeKB = Math.round(bundleBuffer.length / 1024);
44481
- bundleSpinner.succeed(
44482
- source_default.green("Bundling...") + source_default.white(" done") + source_default.gray(` (${bundleSizeKB} KB)`)
44483
- );
44484
- } catch (error46) {
44485
- try {
44558
+ await (0, import_promises.writeFile)(wrapperPath, wrapperContent, "utf-8");
44559
+ await (0, import_promises.mkdir)((0, import_path.resolve)("dist"), { recursive: true });
44560
+ await esbuild.build({
44561
+ entryPoints: [wrapperPath],
44562
+ bundle: true,
44563
+ platform: "node",
44564
+ format: "cjs",
44565
+ outfile: bundleOutfile
44566
+ });
44486
44567
  await (0, import_promises.unlink)(wrapperPath);
44487
- } catch {
44568
+ const bundleBuffer = await (0, import_promises.readFile)(bundleOutfile);
44569
+ const bundleSizeKB = Math.round(bundleBuffer.length / 1024);
44570
+ bundleSpinner.succeed(
44571
+ source_default.green("Bundling...") + source_default.white(" done") + source_default.gray(` (${bundleSizeKB} KB)`)
44572
+ );
44573
+ } catch (error46) {
44574
+ try {
44575
+ await (0, import_promises.unlink)(wrapperPath);
44576
+ } catch {
44577
+ }
44578
+ bundleSpinner.fail(source_default.red("Bundling failed"));
44579
+ throw error46;
44488
44580
  }
44489
- bundleSpinner.fail(source_default.red("Bundling failed"));
44490
- throw error46;
44491
- }
44492
- const uploadSpinner = ora("Uploading...").start();
44493
- try {
44494
- const bundleBuffer = await (0, import_promises.readFile)(bundleOutfile);
44495
- const apiKey = resolveApiKey(options2.prod);
44496
- if (!apiKey) {
44497
- uploadSpinner.fail(source_default.red("Missing API key environment variable"));
44498
- console.error(source_default.gray(" Set it in your .env file or shell environment:"));
44499
- if (!options2.prod && process.env.NODE_ENV === "development") {
44500
- console.error(source_default.gray(" ELEVASIS_PLATFORM_KEY_DEV=sk_... (or ELEVASIS_PLATFORM_KEY as fallback)"));
44501
- } else {
44502
- console.error(source_default.gray(" ELEVASIS_PLATFORM_KEY=sk_..."));
44503
- }
44504
- throw new Error("Missing API key environment variable");
44505
- }
44506
- const metadata = {
44507
- sdkVersion: SDK_VERSION,
44508
- mode: env2,
44509
- resources: { workflows, agents },
44510
- ...documentation ? { documentation } : {},
44511
- ...org.relationships ? { relationships: org.relationships } : {}
44512
- };
44513
- const form = new FormData();
44514
- form.append("bundle", new Blob([bundleBuffer]), "bundle.js");
44515
- form.append("metadata", JSON.stringify(metadata));
44516
- const response = await fetch(`${apiUrl}/api/external/deploy`, {
44517
- method: "POST",
44518
- headers: { Authorization: `Bearer ${apiKey}` },
44519
- body: form
44520
- });
44521
- if (!response.ok) {
44522
- const errorText = await response.text();
44523
- uploadSpinner.fail(source_default.red("Upload failed"));
44524
- console.error(source_default.red(` ${response.status}: ${errorText}`));
44525
- throw new Error(`Deploy upload failed (${response.status}): ${errorText}`);
44526
- }
44527
- const result = await response.json();
44528
- uploadSpinner.succeed(
44529
- source_default.green("Uploading...") + source_default.white(" done")
44530
- );
44531
- const totalResources = (org.workflows?.length ?? 0) + (org.agents?.length ?? 0);
44532
- const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
44533
- if (result.status === "active") {
44534
- console.log("");
44535
- console.log(source_default.green.bold(` Deployed! ${totalResources} resource${totalResources !== 1 ? "s" : ""} live.`));
44536
- if (result.deployId) {
44537
- console.log(source_default.gray(` Version: ${result.deployId}`));
44581
+ const uploadSpinner = ora("Uploading...").start();
44582
+ try {
44583
+ const bundleBuffer = await (0, import_promises.readFile)(bundleOutfile);
44584
+ const apiKey = resolveApiKey(options2.prod);
44585
+ if (!apiKey) {
44586
+ uploadSpinner.fail(source_default.red("Missing API key environment variable"));
44587
+ console.error(source_default.gray(" Set it in your .env file or shell environment:"));
44588
+ if (!options2.prod && process.env.NODE_ENV === "development") {
44589
+ console.error(source_default.gray(" ELEVASIS_PLATFORM_KEY_DEV=sk_... (or ELEVASIS_PLATFORM_KEY as fallback)"));
44590
+ } else {
44591
+ console.error(source_default.gray(" ELEVASIS_PLATFORM_KEY=sk_..."));
44592
+ }
44593
+ throw new Error("Missing API key environment variable");
44538
44594
  }
44539
- console.log(source_default.gray(` Duration: ${elapsed}s`));
44540
- } else if (result.status === "failed") {
44541
- console.log("");
44542
- console.log(source_default.red.bold(" Deploy failed."));
44543
- if (result.error) {
44544
- console.error(source_default.red(` ${result.error}`));
44595
+ const metadata = {
44596
+ sdkVersion: SDK_VERSION,
44597
+ mode: env2,
44598
+ resources: { workflows, agents },
44599
+ ...documentation ? { documentation } : {},
44600
+ ...org.relationships ? { relationships: org.relationships } : {}
44601
+ };
44602
+ const form = new FormData();
44603
+ form.append("bundle", new Blob([bundleBuffer]), "bundle.js");
44604
+ form.append("metadata", JSON.stringify(metadata));
44605
+ const response = await fetch(`${apiUrl}/api/external/deploy`, {
44606
+ method: "POST",
44607
+ headers: { Authorization: `Bearer ${apiKey}` },
44608
+ body: form
44609
+ });
44610
+ if (!response.ok) {
44611
+ const errorText = await response.text();
44612
+ uploadSpinner.fail(source_default.red("Upload failed"));
44613
+ console.error(source_default.red(` ${response.status}: ${errorText}`));
44614
+ throw new Error(`Deploy upload failed (${response.status}): ${errorText}`);
44615
+ }
44616
+ const result = await response.json();
44617
+ uploadSpinner.succeed(source_default.green("Uploading...") + source_default.white(" done"));
44618
+ const totalResources = (org.workflows?.length ?? 0) + (org.agents?.length ?? 0);
44619
+ const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
44620
+ if (result.status === "active") {
44621
+ console.log("");
44622
+ console.log(
44623
+ source_default.green.bold(` Deployed! ${totalResources} resource${totalResources !== 1 ? "s" : ""} live.`)
44624
+ );
44625
+ if (result.deployId) {
44626
+ console.log(source_default.gray(` Version: ${result.deployId}`));
44627
+ }
44628
+ console.log(source_default.gray(` Duration: ${elapsed}s`));
44629
+ } else if (result.status === "failed") {
44630
+ console.log("");
44631
+ console.log(source_default.red.bold(" Deploy failed."));
44632
+ if (result.error) {
44633
+ console.error(source_default.red(` ${result.error}`));
44634
+ }
44635
+ throw new Error(`Deploy failed: ${result.error ?? "unknown error"}`);
44636
+ } else {
44637
+ console.log("");
44638
+ console.log(source_default.yellow(` Deploy status: ${result.status ?? "unknown"}`));
44639
+ if (result.deployId) {
44640
+ console.log(source_default.gray(` Version: ${result.deployId}`));
44641
+ }
44545
44642
  }
44546
- throw new Error(`Deploy failed: ${result.error ?? "unknown error"}`);
44547
- } else {
44548
- console.log("");
44549
- console.log(source_default.yellow(` Deploy status: ${result.status ?? "unknown"}`));
44550
- if (result.deployId) {
44551
- console.log(source_default.gray(` Version: ${result.deployId}`));
44643
+ } catch (error46) {
44644
+ if (uploadSpinner.isSpinning) {
44645
+ uploadSpinner.fail(source_default.red("Deploy failed"));
44552
44646
  }
44647
+ throw error46;
44553
44648
  }
44554
- } catch (error46) {
44555
- if (uploadSpinner.isSpinning) {
44556
- uploadSpinner.fail(source_default.red("Deploy failed"));
44557
- }
44558
- throw error46;
44559
- }
44560
- }));
44649
+ })
44650
+ );
44561
44651
  }
44562
44652
 
44563
44653
  // src/cli/commands/check.ts
@@ -44663,6 +44753,7 @@ function registerCheckCommand(program3) {
44663
44753
  }
44664
44754
 
44665
44755
  // src/cli/commands/exec.ts
44756
+ var import_node_fs = require("node:fs");
44666
44757
  var POLL_INTERVAL_MS = 3e3;
44667
44758
  async function pollForCompletion(resourceId, executionId, apiUrl) {
44668
44759
  const pollSpinner = ora("Waiting for completion...").start();
@@ -44681,10 +44772,7 @@ async function pollForCompletion(resourceId, executionId, apiUrl) {
44681
44772
  const elapsed = Math.round((Date.now() - startTime) / 1e3);
44682
44773
  pollSpinner.text = `Waiting for completion... (${elapsed}s)`;
44683
44774
  try {
44684
- const detail = await apiGet(
44685
- `/api/external/executions/${resourceId}/${executionId}`,
44686
- apiUrl
44687
- );
44775
+ const detail = await apiGet(`/api/external/executions/${resourceId}/${executionId}`, apiUrl);
44688
44776
  if (detail.status === "completed" || detail.status === "failed") {
44689
44777
  process.removeListener("SIGINT", cleanup);
44690
44778
  if (detail.status === "completed") {
@@ -44717,70 +44805,71 @@ async function pollForCompletion(resourceId, executionId, apiUrl) {
44717
44805
  }
44718
44806
  function registerExecCommand(program3) {
44719
44807
  program3.command("exec <resourceId>").description(`Execute a deployed resource
44720
- Example: elevasis-sdk exec my-workflow -i '{"key":"value"}'`).option("-i, --input <json>", "Input data as JSON string").option("--async", "Execute asynchronously with polling").option("--api-url <url>", "API URL").action(wrapAction("exec", async (resourceId, options2) => {
44721
- const input = options2.input ? JSON.parse(options2.input) : {};
44722
- const apiUrl = resolveApiUrl(options2.apiUrl);
44723
- if (options2.async) {
44724
- const asyncSpinner = ora(`Starting async execution of ${resourceId}...`).start();
44725
- const asyncResult = await apiPost(
44726
- "/api/external/execute-async",
44727
- { resourceId, input },
44728
- apiUrl
44729
- );
44730
- asyncSpinner.succeed(source_default.green("Execution started"));
44731
- console.log(source_default.gray(" Execution ID:"), source_default.cyan(asyncResult.executionId));
44732
- console.log("");
44733
- await pollForCompletion(resourceId, asyncResult.executionId, apiUrl);
44734
- return;
44735
- }
44736
- const spinner = ora(`Executing ${resourceId}...`).start();
44737
- try {
44738
- const result = await apiPost(
44739
- "/api/external/execute",
44740
- { resourceId, input },
44741
- apiUrl
44742
- );
44743
- if (result.success) {
44744
- spinner.succeed(source_default.green("Execution complete"));
44745
- console.log(source_default.gray(" Execution ID:"), source_default.cyan(result.executionId));
44746
- console.log("");
44747
- console.log(source_default.green(" Output:"));
44748
- console.log(JSON.stringify(result.data, null, 2));
44749
- } else {
44750
- spinner.fail(source_default.red("Execution failed"));
44751
- console.log(source_default.gray(" Execution ID:"), source_default.cyan(result.executionId));
44752
- }
44753
- } catch (error46) {
44754
- const message = error46 instanceof Error ? error46.message : String(error46);
44755
- const isConnectionFailure = message.includes("fetch failed") || message.includes("ECONNRESET") || message.includes("socket hang up") || message.includes("network");
44756
- if (isConnectionFailure) {
44757
- spinner.warn("Connection lost -- execution may still be running");
44758
- console.log();
44759
- try {
44760
- const recoverSpinner = ora("Recovering execution...").start();
44761
- const listResult = await apiGet(
44762
- `/api/external/executions/${resourceId}?limit=1`,
44808
+ Example: elevasis-sdk exec my-workflow -i '{"key":"value"}'`).option("-i, --input <json>", "Input data as JSON string").option("-f, --input-file <path>", "Read input from a JSON file (avoids shell escaping issues)").option("--async", "Execute asynchronously with polling").option("--api-url <url>", "API URL").action(
44809
+ wrapAction(
44810
+ "exec",
44811
+ async (resourceId, options2) => {
44812
+ const input = options2.inputFile ? JSON.parse((0, import_node_fs.readFileSync)(options2.inputFile, "utf8")) : options2.input ? JSON.parse(options2.input) : {};
44813
+ const apiUrl = resolveApiUrl(options2.apiUrl);
44814
+ if (options2.async) {
44815
+ const asyncSpinner = ora(`Starting async execution of ${resourceId}...`).start();
44816
+ const asyncResult = await apiPost(
44817
+ "/api/external/execute-async",
44818
+ { resourceId, input },
44763
44819
  apiUrl
44764
44820
  );
44765
- const running = listResult.executions.find((e) => e.status === "running");
44766
- if (running) {
44767
- recoverSpinner.succeed(`Found running execution: ${running.id}`);
44768
- console.log();
44769
- await pollForCompletion(resourceId, running.id, apiUrl);
44821
+ asyncSpinner.succeed(source_default.green("Execution started"));
44822
+ console.log(source_default.gray(" Execution ID:"), source_default.cyan(asyncResult.executionId));
44823
+ console.log("");
44824
+ await pollForCompletion(resourceId, asyncResult.executionId, apiUrl);
44825
+ return;
44826
+ }
44827
+ const spinner = ora(`Executing ${resourceId}...`).start();
44828
+ try {
44829
+ const result = await apiPost("/api/external/execute", { resourceId, input }, apiUrl);
44830
+ if (result.success) {
44831
+ spinner.succeed(source_default.green("Execution complete"));
44832
+ console.log(source_default.gray(" Execution ID:"), source_default.cyan(result.executionId));
44833
+ console.log("");
44834
+ console.log(source_default.green(" Output:"));
44835
+ console.log(JSON.stringify(result.data, null, 2));
44770
44836
  } else {
44771
- recoverSpinner.info("No running execution found -- it may have already completed");
44772
- console.log(source_default.dim(" Check status manually or re-run with --async"));
44837
+ spinner.fail(source_default.red("Execution failed"));
44838
+ console.log(source_default.gray(" Execution ID:"), source_default.cyan(result.executionId));
44773
44839
  }
44774
- } catch {
44775
- console.log(source_default.yellow("Could not recover. The execution may still be running on the server."));
44776
- console.log(source_default.dim(" Check status manually or re-run with --async"));
44840
+ } catch (error46) {
44841
+ const message = error46 instanceof Error ? error46.message : String(error46);
44842
+ const isConnectionFailure = message.includes("fetch failed") || message.includes("ECONNRESET") || message.includes("socket hang up") || message.includes("network");
44843
+ if (isConnectionFailure) {
44844
+ spinner.warn("Connection lost -- execution may still be running");
44845
+ console.log();
44846
+ try {
44847
+ const recoverSpinner = ora("Recovering execution...").start();
44848
+ const listResult = await apiGet(
44849
+ `/api/external/executions/${resourceId}?limit=1`,
44850
+ apiUrl
44851
+ );
44852
+ const running = listResult.executions.find((e) => e.status === "running");
44853
+ if (running) {
44854
+ recoverSpinner.succeed(`Found running execution: ${running.id}`);
44855
+ console.log();
44856
+ await pollForCompletion(resourceId, running.id, apiUrl);
44857
+ } else {
44858
+ recoverSpinner.info("No running execution found -- it may have already completed");
44859
+ console.log(source_default.dim(" Check status manually or re-run with --async"));
44860
+ }
44861
+ } catch {
44862
+ console.log(source_default.yellow("Could not recover. The execution may still be running on the server."));
44863
+ console.log(source_default.dim(" Check status manually or re-run with --async"));
44864
+ }
44865
+ process.exit(0);
44866
+ }
44867
+ spinner.fail(source_default.red("Execution failed"));
44868
+ throw error46;
44777
44869
  }
44778
- process.exit(0);
44779
44870
  }
44780
- spinner.fail(source_default.red("Execution failed"));
44781
- throw error46;
44782
- }
44783
- }));
44871
+ )
44872
+ );
44784
44873
  }
44785
44874
 
44786
44875
  // src/cli/commands/resources.ts
@@ -45090,7 +45179,7 @@ var import_path3 = require("path");
45090
45179
  var import_promises2 = require("fs/promises");
45091
45180
 
45092
45181
  // src/cli/commands/templates/core/workspace.ts
45093
- var TEMPLATE_VERSION = 27;
45182
+ var TEMPLATE_VERSION = 29;
45094
45183
  function configTemplate() {
45095
45184
  return `import type { ElevasConfig } from '@elevasis/sdk'
45096
45185
 
@@ -45102,44 +45191,52 @@ export default {
45102
45191
  `;
45103
45192
  }
45104
45193
  function packageJsonTemplate(organization) {
45105
- return JSON.stringify({
45106
- name: organization,
45107
- private: true,
45108
- type: "module",
45109
- scripts: {
45110
- "check-types": "tsc --noEmit",
45111
- check: "elevasis-sdk check",
45112
- deploy: "elevasis-sdk deploy"
45113
- },
45114
- dependencies: {
45115
- "@elevasis/sdk": `^${SDK_VERSION}`
45194
+ return JSON.stringify(
45195
+ {
45196
+ name: organization,
45197
+ private: true,
45198
+ type: "module",
45199
+ scripts: {
45200
+ "check-types": "tsc --noEmit",
45201
+ check: "elevasis-sdk check",
45202
+ deploy: "elevasis-sdk deploy"
45203
+ },
45204
+ dependencies: {
45205
+ "@elevasis/sdk": `^${SDK_VERSION}`
45206
+ },
45207
+ devDependencies: {
45208
+ typescript: "5.9.2",
45209
+ zod: "4.1.12"
45210
+ }
45116
45211
  },
45117
- devDependencies: {
45118
- typescript: "5.9.2",
45119
- zod: "4.1.12"
45120
- }
45121
- }, null, 2) + "\n";
45212
+ null,
45213
+ 2
45214
+ ) + "\n";
45122
45215
  }
45123
45216
  function pnpmWorkspaceTemplate() {
45124
45217
  return `packages: []
45125
45218
  `;
45126
45219
  }
45127
45220
  function tsconfigTemplate() {
45128
- return JSON.stringify({
45129
- compilerOptions: {
45130
- target: "ES2022",
45131
- module: "ESNext",
45132
- moduleResolution: "Bundler",
45133
- strict: true,
45134
- esModuleInterop: true,
45135
- skipLibCheck: true,
45136
- forceConsistentCasingInFileNames: true,
45137
- isolatedModules: true,
45138
- outDir: "./dist"
45221
+ return JSON.stringify(
45222
+ {
45223
+ compilerOptions: {
45224
+ target: "ES2022",
45225
+ module: "ESNext",
45226
+ moduleResolution: "Bundler",
45227
+ strict: true,
45228
+ esModuleInterop: true,
45229
+ skipLibCheck: true,
45230
+ forceConsistentCasingInFileNames: true,
45231
+ isolatedModules: true,
45232
+ outDir: "./dist"
45233
+ },
45234
+ include: ["src"],
45235
+ exclude: ["node_modules", "dist"]
45139
45236
  },
45140
- include: ["src"],
45141
- exclude: ["node_modules", "dist"]
45142
- }, null, 2) + "\n";
45237
+ null,
45238
+ 2
45239
+ ) + "\n";
45143
45240
  }
45144
45241
  function envTemplate() {
45145
45242
  return `ELEVASIS_PLATFORM_KEY=
@@ -45257,6 +45354,28 @@ function claudeSettingsTemplate() {
45257
45354
  }
45258
45355
  ]
45259
45356
  }
45357
+ ],
45358
+ PostToolUse: [
45359
+ {
45360
+ matcher: "Write|Edit|MultiEdit",
45361
+ hooks: [
45362
+ {
45363
+ type: "command",
45364
+ command: "node .claude/hooks/post-edit-validate.mjs"
45365
+ }
45366
+ ]
45367
+ }
45368
+ ],
45369
+ PostToolUseFailure: [
45370
+ {
45371
+ matcher: "Bash",
45372
+ hooks: [
45373
+ {
45374
+ type: "command",
45375
+ command: "node .claude/hooks/tool-failure-recovery.mjs"
45376
+ }
45377
+ ]
45378
+ }
45260
45379
  ]
45261
45380
  }
45262
45381
  },
@@ -45290,8 +45409,8 @@ process.stdin.on('end', () => {
45290
45409
  function claudeSdkBoundaryHookTemplate() {
45291
45410
  return String.raw`#!/usr/bin/env node
45292
45411
  // enforce-sdk-boundary.mjs
45293
- // Blocks gh CLI, destructive git operations, and file writes outside the project root.
45294
- // Allows git add, commit, push, pull, fetch -- used by /meta deploy.
45412
+ // Blocks file modifications (Write, Edit, MultiEdit, destructive Bash) outside the project root.
45413
+ // Git, gh, and other CLI tools are NOT blocked -- the agent can use them freely.
45295
45414
 
45296
45415
  import { resolve, normalize } from "node:path";
45297
45416
  import { appendFileSync, mkdirSync } from "node:fs";
@@ -45336,44 +45455,6 @@ try {
45336
45455
  if (input.tool_name === "Bash") {
45337
45456
  const cmd = input.tool_input?.command ?? "";
45338
45457
 
45339
- // GitHub CLI -- blocked (affects shared remote state, user-initiated only)
45340
- if (/\bgh\b/.test(cmd)) {
45341
- deny(
45342
- "BLOCKED: GitHub CLI (gh) command detected.\n" +
45343
- "WHY: GitHub CLI operations affect shared remote state (PRs, issues, releases). These must be user-initiated.\n" +
45344
- "INSTEAD: Ask the user to run this gh command manually."
45345
- );
45346
- process.exit(0);
45347
- }
45348
-
45349
- // Destructive git -- blocked
45350
- if (/(?<!-)\bgit\s+reset\b/.test(cmd) && /--hard/.test(cmd)) {
45351
- deny(
45352
- "BLOCKED: git reset --hard detected.\n" +
45353
- "WHY: Hard resets destroy uncommitted work and cannot be undone. This must be user-initiated.\n" +
45354
- "INSTEAD: Ask the user to run this git command manually."
45355
- );
45356
- process.exit(0);
45357
- }
45358
-
45359
- if (/(?<!-)\bgit\s+clean\b/.test(cmd) && /-[a-zA-Z]*f/.test(cmd)) {
45360
- deny(
45361
- "BLOCKED: git clean -f detected.\n" +
45362
- "WHY: Force-cleaning the working tree permanently removes untracked files. This must be user-initiated.\n" +
45363
- "INSTEAD: Ask the user to run this git command manually."
45364
- );
45365
- process.exit(0);
45366
- }
45367
-
45368
- if (/(?<!-)\bgit\s+(rebase|merge)\b/.test(cmd)) {
45369
- deny(
45370
- "BLOCKED: git rebase/merge detected.\n" +
45371
- "WHY: Rebase and merge rewrite history or combine branches in ways that require user judgment.\n" +
45372
- "INSTEAD: Ask the user to run this git command manually."
45373
- );
45374
- process.exit(0);
45375
- }
45376
-
45377
45458
  // Path-scoped blocks -- destructive commands or redirects outside project root
45378
45459
  const winPaths = cmd.match(/(?<![A-Za-z])[A-Za-z]:[/\\][^\s"'|;&)]+/g) || [];
45379
45460
  const unixPaths = cmd.match(/(?<=\s|^|"|')\/[^\s"'|;&)]+/g) || [];
@@ -45556,7 +45637,7 @@ For detailed per-dimension adaptation rules, read
45556
45637
  | --- | --- |
45557
45638
  | \`/meta\` | Project lifecycle: init, status, fix, deploy, health |
45558
45639
  | \`/docs\` | Browse, create, and verify permanent documentation |
45559
- | \`/work\` | Task tracking: create, save, resume, complete |
45640
+ | \`/work\` | Task tracking: auto-detects intent (create, save, resume); suggests complete |
45560
45641
  | \`/tutorial\` | Progressive learning path (21 items across 4 sections) |
45561
45642
 
45562
45643
  ## Skills
@@ -45788,43 +45869,46 @@ Observation focus: full lifecycle coverage, pipeline internals.
45788
45869
  **Item 4: /work and /docs**
45789
45870
 
45790
45871
  When automation is none:
45791
- "You can ask the assistant to track work across conversations. When you start something
45792
- complex, use /work create to save your place. Next session, /work resume picks up where
45793
- you left off." Walk through the concept without deep command details. Then introduce /docs:
45794
- "When a task is finished, /work complete moves it to docs/ permanently. After that, /docs
45795
- helps you find and read what's there -- like a notebook for your project." Run /docs (no
45796
- args) to show the docs/ overview together.
45797
- Verify: Create a task with \`/work create "practice task"\`, then run /work to see it listed.
45872
+ "You can ask the assistant to track work across conversations. Just say /work and tell it
45873
+ what you're working on -- it figures out the rest. It'll save your progress automatically,
45874
+ and next session you just say /work to pick up where you left off." Walk through the
45875
+ concept without deep command details. Then introduce /docs:
45876
+ "When a task is done, the assistant will ask if you want to finalize it -- that moves it
45877
+ to docs/ permanently. /docs helps you find and read what's there -- like a notebook for
45878
+ your project." Run /docs (no args) to show the docs/ overview together.
45879
+ Verify: Run \`/work\` and describe "practice task", see it created automatically.
45798
45880
  Then run /docs to see the docs/ structure.
45799
45881
  Observation focus: persistence concept, cross-session continuity, docs as permanent notes.
45800
45882
 
45801
45883
  When automation is low-code:
45802
- Show /work operations: create (task doc with frontmatter + sections), save (updates
45803
- Progress + Resume Context), resume (loads context for next session), complete (moves
45804
- to permanent docs/).
45884
+ Show /work as intent-driven: the agent detects whether to create, resume, or save based
45885
+ on context. Create happens when you describe new work, resume when you pick an existing
45886
+ task, save happens automatically when progress is made. Complete is the only action that
45887
+ asks permission first.
45805
45888
  Then introduce /docs: "/docs is for permanent knowledge -- things that don't expire. Use
45806
45889
  /docs create to document a workflow or integration. Use /docs verify to check if your
45807
45890
  docs match the current code." Explain the boundary: /work manages in-progress/, /docs
45808
45891
  manages permanent docs/.
45809
- Verify: Create a task with \`/work create "practice task"\`, run /work save, inspect the
45810
- file. Then run /docs to browse docs/.
45811
- Observation focus: task tracking workflow, /work vs /docs separation.
45892
+ Verify: Run \`/work\` and describe "practice task", see auto-create. Make some changes,
45893
+ then notice auto-save. Then run /docs to browse docs/.
45894
+ Observation focus: intent-driven task tracking, /work vs /docs separation.
45812
45895
 
45813
45896
  When automation is custom:
45814
45897
  Read \`.claude/commands/work.md\`. Full /work coverage:
45815
- /work create (kebab-case filename, frontmatter with status, Objective/Plan/Progress/
45816
- Resume Context sections), /work save (Progress + Resume Context update), /work resume
45817
- (multiple-task disambiguation), /work complete (moves to final location).
45898
+ Intent detection table (list, create, resume, save auto-invoked; complete always suggests).
45899
+ Task doc anatomy: kebab-case filename, frontmatter with status, Objective/Plan/Progress/
45900
+ Resume Context sections. Auto-save behavior: triggers on heavy context, wrap-up signals,
45901
+ 2+ steps completed. Resolution order for resume: number, keyword match, single in-progress.
45818
45902
  Then read \`.claude/commands/docs.md\`. Cover /docs operations:
45819
45903
  /docs (default): browse permanent docs/, categorized with read-only auto-generated files
45820
45904
  separate; /docs create: interview-driven, resource-aware doc creation from src/ code
45821
45905
  analysis; /docs verify: cross-references resource IDs, schema fields, platform tools in
45822
45906
  docs against src/ -- standalone analog of /meta fix step 5.
45823
- Explain the relationship: /work owns docs/in-progress/ (task lifecycle), /work complete
45907
+ Explain the relationship: /work owns docs/in-progress/ (task lifecycle), completing a task
45824
45908
  moves docs to permanent location, /docs browses and verifies what's there.
45825
- Verify: Create a task doc, save progress, run /work complete to move it. Then run /docs
45826
- to see it in the permanent docs/ listing.
45827
- Observation focus: task doc anatomy, /work-to-/docs handoff, docs verification as standalone tool.
45909
+ Verify: Create a task via /work, make progress, observe auto-save, then run /work complete
45910
+ to move it. Run /docs to see it in the permanent docs/ listing.
45911
+ Observation focus: intent detection, auto-save behavior, /work-to-/docs handoff, docs verification.
45828
45912
 
45829
45913
  **Item 5: Your First Custom Workflow**
45830
45914
 
@@ -46358,6 +46442,8 @@ function claudeWorkCommandTemplate() {
46358
46442
 
46359
46443
  You are a task tracking assistant for this Elevasis workspace. \`/work\` is the primary command for managing all work and projects.
46360
46444
 
46445
+ Your job is to **intelligently manage tasks without requiring the user to know subcommands**. Detect what the user needs from context and act accordingly.
46446
+
46361
46447
  ## Context
46362
46448
 
46363
46449
  Read \`docs/priorities.mdx\` if it exists for current priorities.
@@ -46377,9 +46463,25 @@ When scanning, treat \`index.mdx\` as the primary task doc for a directory.
46377
46463
 
46378
46464
  Enforce exactly three values in frontmatter: \`planned\`, \`in-progress\`, \`complete\`.
46379
46465
 
46380
- ## Operations
46466
+ ## Intent Detection
46467
+
46468
+ When \`/work\` is invoked (with or without arguments), detect intent from context:
46469
+
46470
+ | Signal | Action |
46471
+ |--------|--------|
46472
+ | No arguments, no active conversation context | **List** tasks, then let user pick |
46473
+ | User picks a number or names a task | **Resume** that task automatically |
46474
+ | User describes new work (not matching existing tasks) | **Create** a task doc automatically |
46475
+ | \`/work\` with a description that matches no existing task | **Create** automatically |
46476
+ | \`/work\` with a keyword/name matching an existing task | **Resume** automatically |
46477
+ | Conversation is getting heavy, user wrapping up, or 2+ steps completed | **Save** automatically |
46478
+ | All plan steps are COMPLETE | **Suggest** \`/work complete\` (never auto-invoke) |
46479
+
46480
+ **Key principle:** Create, save, and resume are auto-invoked. Complete always asks permission first.
46481
+
46482
+ ## Behaviors
46381
46483
 
46382
- ### No arguments (default) -- List and Pick
46484
+ ### List and Pick (default when no context)
46383
46485
 
46384
46486
  1. Scan \`docs/in-progress/\` recursively for \`.mdx\` files with \`status\` frontmatter
46385
46487
  2. For directories, read \`index.mdx\` as the primary task doc
@@ -46398,30 +46500,25 @@ Active Tasks
46398
46500
  3. [complete] CRM Integration
46399
46501
  Completed: 2026-03-03
46400
46502
 
46401
- Pick a task by number or name to resume, or say:
46402
- - "create" to start new work
46403
- - "complete <name>" to finish a task
46503
+ Pick a task by number or name, or describe new work to start.
46404
46504
  \`\`\`
46405
46505
 
46406
46506
  4. Cross-reference with \`docs/priorities.mdx\` if it exists
46407
- 5. When the user picks a number or describes a task in natural language, auto-invoke the **resume** flow on that task
46408
- 6. If no tasks found, suggest \`/work create\`
46507
+ 5. When the user picks a number or describes a task, auto-invoke the appropriate flow (resume or create)
46508
+ 6. If no tasks found, ask: "What are you trying to accomplish?"
46409
46509
 
46410
- ### \`create [description]\`
46510
+ ### Create (auto-invoked when new work detected)
46411
46511
 
46412
- Guided task creation with interview:
46413
-
46414
- 1. If no description given, ask: "What are you trying to accomplish?"
46415
- 2. Ask 2-3 focused questions:
46512
+ 1. If the user's intent is clear, skip the interview and create directly
46513
+ 2. If ambiguous, ask 1-2 focused questions:
46416
46514
  - "What does success look like?" (acceptance criteria)
46417
- - "Is this related to any existing work?" (scan \`docs/in-progress/\` and \`docs/\` for related topics)
46418
- - "Do we need to investigate anything first, or is the path clear?" (suggest investigation if needed)
46419
- 3. Determine directory placement:
46420
- - Scan \`docs/in-progress/\` for existing directories related to the topic
46515
+ - "Do we need to investigate anything first, or is the path clear?"
46516
+ 3. Scan \`docs/in-progress/\` for existing directories related to the topic
46517
+ 4. Determine directory placement:
46421
46518
  - If related to existing directory, create as a file within it
46422
46519
  - If new concept that may grow, create \`docs/in-progress/<slug>/index.mdx\`
46423
46520
  - If small/standalone, create \`docs/in-progress/<slug>.mdx\`
46424
- 4. Create the doc with \`status: planned\` and structured sections:
46521
+ 5. Create the doc with \`status: planned\` and structured sections:
46425
46522
 
46426
46523
  \`\`\`yaml
46427
46524
  ---
@@ -46433,15 +46530,20 @@ status: planned
46433
46530
 
46434
46531
  Sections: Objective (what and why), Plan (numbered steps), Progress (per-step tracking with PENDING markers), Resume Context (current state, key docs, "To continue" prompt).
46435
46532
 
46436
- 5. Update \`docs/priorities.mdx\` if it exists
46437
- 6. Report what was created with location and step count
46533
+ 6. Update \`docs/priorities.mdx\` if it exists
46534
+ 7. Report what was created with location and step count
46438
46535
 
46439
- ### \`save\`
46536
+ ### Save (auto-invoked when progress detected)
46440
46537
 
46441
- Update the current task doc's Progress and Resume Context:
46538
+ Auto-save triggers (do NOT ask, just save):
46539
+ - The conversation context is getting heavy (many tool calls, large file reads)
46540
+ - The user appears to be wrapping up (thanks, goodbye, switching topics)
46541
+ - Significant progress has been made (2+ steps completed without saving)
46542
+ - Before a context reset
46442
46543
 
46443
- 1. Identify the current task from conversation context (which in-progress doc was loaded/discussed)
46444
- 2. If not working on a tracked task, list available docs and ask which to save to, or offer to create a new one
46544
+ Save flow:
46545
+ 1. Identify the current task from conversation context
46546
+ 2. If not working on a tracked task, offer to create a new one
46445
46547
  3. Update Progress section:
46446
46548
  - Mark completed steps as \`COMPLETE\` with: what was done, key decisions, files changed
46447
46549
  - Mark current step as \`IN PROGRESS\` with current state
@@ -46451,15 +46553,13 @@ Update the current task doc's Progress and Resume Context:
46451
46553
  - Files Modified: table of file paths and descriptions of changes
46452
46554
  - Key docs to read on resume: file paths with why they matter
46453
46555
  - To continue: copy-pasteable prompt for the next session
46454
- 5. Set \`status\` appropriately (\`in-progress\` if ongoing, \`complete\` if all steps done)
46455
- 6. Report: task title, status, completed steps count, last step, and the resume command
46456
-
46457
- ### \`resume [name-or-number]\`
46556
+ 5. Set \`status\` appropriately (\`in-progress\` if ongoing)
46557
+ 6. Briefly confirm: "Progress saved to {path}."
46458
46558
 
46459
- Resume in-progress work. Designed for non-technical users who may not know file paths.
46559
+ ### Resume (auto-invoked when existing task detected)
46460
46560
 
46461
46561
  **Resolution order:**
46462
- 1. If a number is given (e.g., \`/work resume 2\`), map to the numbered list from the default list operation
46562
+ 1. If a number is given, map to the numbered list from the task list
46463
46563
  2. If a name/keyword is given, substring match against task titles and filenames in \`docs/in-progress/\`
46464
46564
  3. If no argument, scan for \`status: in-progress\` docs -- if one found, use it; if multiple, list and ask
46465
46565
  4. If multiple matches, list them and ask which to resume
@@ -46485,35 +46585,32 @@ Key context loaded:
46485
46585
  Ready to continue. {Copy of "To continue" prompt}
46486
46586
  \`\`\`
46487
46587
 
46488
- ### \`complete [name]\`
46588
+ ### Complete (NEVER auto-invoked -- always suggest)
46589
+
46590
+ When all plan steps are COMPLETE, suggest: "All steps for '{task}' look done. Want me to finalize it?"
46489
46591
 
46490
- Mark task complete, clean up, and move to permanent docs:
46592
+ Only proceed after explicit user confirmation. Then:
46491
46593
 
46492
- 1. Resolve target (same as resume: number, name, or scan for \`status: complete\`)
46493
- 2. **Validate readiness:** Check that all plan steps are marked COMPLETE. If not, warn and ask for confirmation.
46494
- 3. **Clean up the doc:**
46594
+ 1. **Validate readiness:** Check that all plan steps are marked COMPLETE. If not, warn and ask for confirmation.
46595
+ 2. **Clean up the doc:**
46495
46596
  - Strip \`## Resume Context\` section entirely (header through next \`##\` or EOF)
46496
46597
  - Strip progress markers: \`COMPLETE\`, \`IN PROGRESS\`, \`PENDING\` from step headings
46497
46598
  - Remove steps still marked PENDING entirely (header + body) -- they were never done
46498
46599
  - Remove \`status\` from frontmatter (keep \`title\` and \`description\`)
46499
46600
  - Target 200-400 lines; flag if result exceeds 500 lines
46500
- 4. **Determine destination:**
46601
+ 3. **Determine destination:**
46501
46602
  - Scan \`docs/\` (excluding \`docs/in-progress/\`) for existing directories related to this work
46502
46603
  - If a related directory exists, propose merging into it
46503
46604
  - If no related directory, propose \`docs/<slug>/\` or \`docs/<slug>.mdx\`
46504
46605
  - Present the proposed destination and ask user to confirm
46505
- 5. **Move the doc(s):**
46606
+ 4. **Move the doc(s):**
46506
46607
  - Single file: move from \`docs/in-progress/\` to destination
46507
46608
  - Directory: move entire directory
46508
- 6. **Verify and report:**
46609
+ 5. **Verify and report:**
46509
46610
  - Confirm destination exists, source removed
46510
46611
  - Check no leftover \`status:\` or \`## Resume Context\` in moved files
46511
46612
  - Update \`docs/priorities.mdx\` if it exists
46512
46613
  - Report: task title, cleanup stats (lines before/after), destination path
46513
-
46514
- ## Completion Suggestions
46515
-
46516
- When the agent observes that all plan steps for a task are marked COMPLETE and the user seems to be moving on, proactively suggest: "All steps for '{task}' are complete. Run \`/work complete\` to finalize it."
46517
46614
  `;
46518
46615
  }
46519
46616
  function claudeDocsCommandTemplate() {
@@ -46579,7 +46676,7 @@ Steps:
46579
46676
 
46580
46677
  ### \`create [description]\` -- Reference Doc Creation
46581
46678
 
46582
- Interview-driven creation for permanent documentation. Task docs go through \`/work create\`.
46679
+ Interview-driven creation for permanent documentation. Task docs go through \`/work\`.
46583
46680
 
46584
46681
  1. If no description given, ask: "What do you want to document?"
46585
46682
  2. Determine doc type (infer or ask):
@@ -46949,17 +47046,19 @@ Exactly three values for frontmatter \`status\`: \`planned\`, \`in-progress\`, \
46949
47046
  - IN PROGRESS -> COMPLETE (finishing a step)
46950
47047
  - Do NOT update on every action -- only on step transitions
46951
47048
 
46952
- ## Save Suggestions
47049
+ ## Auto-Save Behavior
46953
47050
 
46954
- Proactively suggest \`/work save\` when:
47051
+ The agent auto-saves progress (no user action needed) when:
46955
47052
  - The conversation context is getting heavy (many tool calls, large file reads)
46956
47053
  - The user appears to be wrapping up (thanks, goodbye, switching topics)
46957
47054
  - Significant progress has been made (2+ steps completed without saving)
46958
- - Before a \`/clear\` or context reset
47055
+ - Before a context reset
47056
+
47057
+ Auto-save updates the task doc's Progress and Resume Context sections silently, then briefly confirms.
46959
47058
 
46960
47059
  ## Completion Suggestions
46961
47060
 
46962
- When all plan steps are marked COMPLETE, suggest \`/work complete\` to finalize the task.
47061
+ When all plan steps are marked COMPLETE, **suggest** completing the task -- never auto-invoke. Ask: "All steps for '{task}' look done. Want me to finalize it?"
46963
47062
 
46964
47063
  ## Directory Conventions
46965
47064
 
@@ -46969,6 +47068,199 @@ When all plan steps are marked COMPLETE, suggest \`/work complete\` to finalize
46969
47068
  - Completed tasks move OUT of \`docs/in-progress/\` to \`docs/<relevant-dir>/\`
46970
47069
  `;
46971
47070
  }
47071
+ function claudePostEditValidateHookTemplate() {
47072
+ return `#!/usr/bin/env node
47073
+ // post-edit-validate.mjs
47074
+ // PostToolUse hook \u2014 auto-formats with prettier, type-checks .ts/.tsx files.
47075
+ // Fires after Edit|Write|MultiEdit succeeds.
47076
+
47077
+ import { existsSync } from 'node:fs'
47078
+ import { resolve, normalize, extname, join, dirname } from 'node:path'
47079
+ import { execSync } from 'node:child_process'
47080
+
47081
+ const ROOT = process.env.CLAUDE_PROJECT_DIR ?? process.cwd()
47082
+
47083
+ // Extensions prettier should format
47084
+ const PRETTIER_EXTENSIONS = new Set([
47085
+ '.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.json', '.css', '.md', '.mdx'
47086
+ ])
47087
+
47088
+ // Extensions that trigger type-checking
47089
+ const TS_EXTENSIONS = new Set(['.ts', '.tsx'])
47090
+
47091
+ function findNearestTsconfig(startDir) {
47092
+ let dir = startDir
47093
+ const root = normalize(ROOT)
47094
+ while (dir.length >= root.length) {
47095
+ const candidate = join(dir, 'tsconfig.json')
47096
+ if (existsSync(candidate)) return candidate
47097
+ const parent = dirname(dir)
47098
+ if (parent === dir) break
47099
+ dir = parent
47100
+ }
47101
+ return null
47102
+ }
47103
+
47104
+ try {
47105
+ const chunks = []
47106
+ for await (const chunk of process.stdin) chunks.push(chunk)
47107
+ const input = JSON.parse(Buffer.concat(chunks).toString())
47108
+
47109
+ const filePath = input.tool_input?.file_path
47110
+ if (!filePath) process.exit(0)
47111
+
47112
+ const ext = extname(filePath).toLowerCase()
47113
+ const absPath = normalize(resolve(filePath))
47114
+ if (!existsSync(absPath)) process.exit(0)
47115
+
47116
+ const results = []
47117
+
47118
+ // 1. Prettier
47119
+ if (PRETTIER_EXTENSIONS.has(ext)) {
47120
+ try {
47121
+ execSync('pnpm exec prettier --write "' + absPath + '"', {
47122
+ cwd: ROOT,
47123
+ stdio: ['pipe', 'pipe', 'pipe'],
47124
+ timeout: 10_000
47125
+ })
47126
+ } catch (err) {
47127
+ const stderr = err.stderr?.toString().trim() || ''
47128
+ if (stderr && !/ignored/i.test(stderr)) {
47129
+ results.push('Prettier error: ' + stderr.slice(0, 300))
47130
+ }
47131
+ }
47132
+ }
47133
+
47134
+ // 2. Type-check for .ts/.tsx
47135
+ if (TS_EXTENSIONS.has(ext)) {
47136
+ const tsconfig = findNearestTsconfig(dirname(absPath))
47137
+ if (tsconfig) {
47138
+ try {
47139
+ execSync('pnpm exec tsc --noEmit -p "' + tsconfig + '"', {
47140
+ cwd: ROOT,
47141
+ stdio: ['pipe', 'pipe', 'pipe'],
47142
+ timeout: 30_000,
47143
+ env: { ...process.env, NODE_OPTIONS: '--max-old-space-size=4096' }
47144
+ })
47145
+ } catch (err) {
47146
+ if (err.killed) process.exit(0) // Don't block on timeout
47147
+ const stdout = err.stdout?.toString() || ''
47148
+ if (stdout.includes('error TS')) {
47149
+ const errorLines = stdout
47150
+ .split('\\n')
47151
+ .filter(l => l.includes('error TS'))
47152
+ .slice(0, 10)
47153
+ results.push('Type errors after editing ' + filePath + ':\\n' + errorLines.join('\\n'))
47154
+ }
47155
+ }
47156
+ }
47157
+ }
47158
+
47159
+ // Output errors to Claude's context (silence = success)
47160
+ if (results.length > 0) {
47161
+ process.stderr.write(results.join('\\n\\n'))
47162
+ process.exit(2) // Exit 2 = send stderr as feedback to Claude
47163
+ }
47164
+ } catch {}
47165
+
47166
+ process.exit(0)
47167
+ `;
47168
+ }
47169
+ function claudeToolFailureRecoveryHookTemplate() {
47170
+ return `#!/usr/bin/env node
47171
+ // tool-failure-recovery.mjs
47172
+ // PostToolUseFailure hook \u2014 pattern-matches known Bash errors and returns
47173
+ // recovery advice via stderr + exit 2 (feedback to Claude).
47174
+
47175
+ const RECOVERY_TABLE = [
47176
+ {
47177
+ test: r => /JavaScript heap out of memory/i.test(r),
47178
+ advice: 'Out of memory.',
47179
+ fix: 'Run the command with NODE_OPTIONS="--max-old-space-size=4096".',
47180
+ why: 'Large TypeScript projects can exceed Node default heap limit.',
47181
+ },
47182
+ {
47183
+ test: r => /boundary hook/i.test(r) && /block|denied/i.test(r),
47184
+ advice: 'Command blocked by SDK boundary hook.',
47185
+ fix: 'Ask the user to run this command manually.',
47186
+ why: 'The boundary hook blocks file modifications (Write, Edit, destructive Bash) outside the project boundary.',
47187
+ see: 'CLAUDE.md',
47188
+ },
47189
+ {
47190
+ test: r => /ENOENT/.test(r) && /node_modules/.test(r),
47191
+ advice: 'Missing node_modules dependency.',
47192
+ fix: 'Run: pnpm install',
47193
+ why: 'Dependencies are not installed or were cleared.',
47194
+ },
47195
+ {
47196
+ test: r => /ERR_MODULE_NOT_FOUND/.test(r) && /@elevasis\\/sdk/.test(r),
47197
+ advice: '@elevasis/sdk module not found.',
47198
+ fix: 'Run: pnpm install \u2014 then verify @elevasis/sdk is in package.json dependencies.',
47199
+ why: 'The SDK package is not installed or the version is mismatched.',
47200
+ },
47201
+ {
47202
+ test: r => /ERR_MODULE_NOT_FOUND/.test(r),
47203
+ advice: 'Module not found at import path.',
47204
+ fix: 'Check the import path and verify the package is installed (pnpm install).',
47205
+ why: 'The import path does not match any installed package or local file.',
47206
+ },
47207
+ {
47208
+ test: r => /TS2307/.test(r) || (/cannot find/i.test(r) && /declaration/i.test(r)),
47209
+ advice: 'TypeScript cannot resolve module or declaration file.',
47210
+ fix: 'Check that the package is installed and tsconfig paths are correct.',
47211
+ why: 'Missing dependency or incorrect TypeScript configuration.',
47212
+ },
47213
+ {
47214
+ test: r => /EPERM/.test(r) || /permission denied/i.test(r),
47215
+ advice: 'Permission denied (EPERM).',
47216
+ fix: 'Close the file in any other process (editor, terminal, or dev server) and retry.',
47217
+ why: 'On Windows, files locked by another process cannot be written to.',
47218
+ },
47219
+ {
47220
+ test: r => /lockfile/i.test(r) && /conflict|outdated|ERR_PNPM/i.test(r),
47221
+ advice: 'pnpm lockfile conflict or outdated.',
47222
+ fix: 'Run: pnpm install to regenerate the lockfile.',
47223
+ why: 'The lockfile is out of sync with package.json changes.',
47224
+ },
47225
+ {
47226
+ test: r => /elevasis-sdk check/.test(r) || /elevasis-sdk deploy/.test(r),
47227
+ advice: 'elevasis-sdk CLI command failed.',
47228
+ fix: 'Check the error output above. Common causes: missing .env ELEVASIS_API_KEY, invalid resource schemas, or network issues.',
47229
+ why: 'The SDK CLI validates resources and communicates with the platform API.',
47230
+ },
47231
+ {
47232
+ test: r => /EADDRINUSE/.test(r),
47233
+ advice: 'Port already in use.',
47234
+ fix: 'Find and kill the process using the port, or use a different port.',
47235
+ why: 'A previous dev server or process is still holding the port.',
47236
+ },
47237
+ ]
47238
+
47239
+ function formatRecovery(entry) {
47240
+ let msg = 'FAILED: ' + entry.advice + '\\nFIX: ' + entry.fix
47241
+ if (entry.why) msg += '\\nWHY: ' + entry.why
47242
+ if (entry.see) msg += '\\nSEE: ' + entry.see
47243
+ return msg
47244
+ }
47245
+
47246
+ try {
47247
+ const chunks = []
47248
+ for await (const chunk of process.stdin) chunks.push(chunk)
47249
+ const input = JSON.parse(Buffer.concat(chunks).toString())
47250
+
47251
+ const response = input.tool_response ?? ''
47252
+
47253
+ for (const entry of RECOVERY_TABLE) {
47254
+ if (entry.test(response)) {
47255
+ process.stderr.write(formatRecovery(entry))
47256
+ process.exit(2)
47257
+ }
47258
+ }
47259
+ } catch {}
47260
+
47261
+ process.exit(0)
47262
+ `;
47263
+ }
46972
47264
 
46973
47265
  // src/cli/commands/templates/core/resources.ts
46974
47266
  function starterTemplate() {
@@ -47144,6 +47436,8 @@ function getManagedTemplates(ctx = {}) {
47144
47436
  ".claude/settings.json": claudeSettingsTemplate,
47145
47437
  ".claude/scripts/statusline-command.js": claudeStatuslineScriptTemplate,
47146
47438
  ".claude/hooks/enforce-sdk-boundary.mjs": claudeSdkBoundaryHookTemplate,
47439
+ ".claude/hooks/post-edit-validate.mjs": claudePostEditValidateHookTemplate,
47440
+ ".claude/hooks/tool-failure-recovery.mjs": claudeToolFailureRecoveryHookTemplate,
47147
47441
  ".claude/commands/tutorial.md": claudeTutorialCommandTemplate,
47148
47442
  ".claude/commands/meta.md": claudeMetaCommandTemplate,
47149
47443
  ".claude/commands/work.md": claudeWorkCommandTemplate,
@@ -47356,6 +47650,8 @@ var MANAGED_FILES = [
47356
47650
  "CLAUDE.md",
47357
47651
  ".claude/settings.json",
47358
47652
  ".claude/hooks/enforce-sdk-boundary.mjs",
47653
+ ".claude/hooks/post-edit-validate.mjs",
47654
+ ".claude/hooks/tool-failure-recovery.mjs",
47359
47655
  ".claude/commands/tutorial.md",
47360
47656
  ".claude/commands/meta.md",
47361
47657
  ".claude/commands/work.md",
@@ -47370,96 +47666,100 @@ var MANAGED_FILES = [
47370
47666
  ];
47371
47667
  var SCAFFOLD_FILES = [...INIT_ONLY_FILES, ...MANAGED_FILES];
47372
47668
  function registerInitCommand(program3) {
47373
- program3.command("init [directory]").description("Scaffold a new Elevasis workspace\n Example: elevasis-sdk init my-workspace").option("--force", "Overwrite existing files").option("--ui", "Include a Vite + React UI app in ui/").action(wrapAction("init", async (directory, options2) => {
47374
- const targetDir = directory ? (0, import_path3.resolve)(directory) : process.cwd();
47375
- const orgSlug = toSlug((0, import_path3.basename)(targetDir));
47376
- if (!options2.force) {
47377
- const filesToCheck = options2.ui ? [...SCAFFOLD_FILES, ...UI_INIT_FILES] : SCAFFOLD_FILES;
47378
- const conflicts = [];
47379
- for (const file2 of filesToCheck) {
47380
- try {
47381
- await (0, import_promises2.access)((0, import_path3.resolve)(targetDir, file2));
47382
- conflicts.push(file2);
47383
- } catch {
47384
- }
47385
- }
47386
- if (conflicts.length > 0) {
47387
- console.error(source_default.red("Files already exist:"));
47388
- for (const f of conflicts) {
47389
- console.error(source_default.gray(` ${f}`));
47669
+ program3.command("init [directory]").description("Scaffold a new Elevasis workspace\n Example: elevasis-sdk init my-workspace").option("--force", "Overwrite existing files").option("--ui", "Include a Vite + React UI app in ui/").action(
47670
+ wrapAction("init", async (directory, options2) => {
47671
+ const targetDir = directory ? (0, import_path3.resolve)(directory) : process.cwd();
47672
+ const orgSlug = toSlug((0, import_path3.basename)(targetDir));
47673
+ if (!options2.force) {
47674
+ const filesToCheck = options2.ui ? [...SCAFFOLD_FILES, ...UI_INIT_FILES] : SCAFFOLD_FILES;
47675
+ const conflicts = [];
47676
+ for (const file2 of filesToCheck) {
47677
+ try {
47678
+ await (0, import_promises2.access)((0, import_path3.resolve)(targetDir, file2));
47679
+ conflicts.push(file2);
47680
+ } catch {
47681
+ }
47390
47682
  }
47391
- console.error(source_default.gray("\n Use --force to overwrite."));
47392
- throw new Error("Scaffold conflict");
47393
- }
47394
- }
47395
- await (0, import_promises2.mkdir)((0, import_path3.resolve)(targetDir, "src/operations"), { recursive: true });
47396
- await (0, import_promises2.mkdir)((0, import_path3.resolve)(targetDir, "src/example"), { recursive: true });
47397
- await (0, import_promises2.mkdir)((0, import_path3.resolve)(targetDir, "src/shared"), { recursive: true });
47398
- await (0, import_promises2.mkdir)((0, import_path3.resolve)(targetDir, "docs/in-progress"), { recursive: true });
47399
- await (0, import_promises2.mkdir)((0, import_path3.resolve)(targetDir, ".claude/hooks"), { recursive: true });
47400
- await (0, import_promises2.mkdir)((0, import_path3.resolve)(targetDir, ".claude/scripts"), { recursive: true });
47401
- await (0, import_promises2.mkdir)((0, import_path3.resolve)(targetDir, ".claude/commands"), { recursive: true });
47402
- await (0, import_promises2.mkdir)((0, import_path3.resolve)(targetDir, ".claude/skills/creds"), { recursive: true });
47403
- await (0, import_promises2.mkdir)((0, import_path3.resolve)(targetDir, ".claude/rules"), { recursive: true });
47404
- if (options2.ui) {
47405
- await (0, import_promises2.mkdir)((0, import_path3.resolve)(targetDir, "ui/src"), { recursive: true });
47406
- }
47407
- const files = {
47408
- "elevasis.config.ts": configTemplate(),
47409
- "package.json": packageJsonTemplate(orgSlug),
47410
- "pnpm-workspace.yaml": pnpmWorkspaceTemplate(),
47411
- "tsconfig.json": tsconfigTemplate(),
47412
- ".env": envTemplate(),
47413
- ".npmrc": npmrcTemplate(),
47414
- ".gitignore": gitignoreTemplate({ hasUI: options2.ui }),
47415
- "src/index.ts": starterTemplate(),
47416
- "src/operations/platform-status.ts": platformStatusTemplate(),
47417
- "src/operations/index.ts": operationsBarrelTemplate(),
47418
- "src/example/echo.ts": starterWorkflowTemplate(),
47419
- "src/example/index.ts": exampleBarrelTemplate(),
47420
- "src/shared/.gitkeep": "",
47421
- "docs/index.mdx": docsIndexTemplate(orgSlug),
47422
- "docs/in-progress/.gitkeep": "",
47423
- "CLAUDE.md": claudeMdTemplate({ hasUI: options2.ui }),
47424
- ".claude/settings.json": claudeSettingsTemplate(),
47425
- ".claude/hooks/enforce-sdk-boundary.mjs": claudeSdkBoundaryHookTemplate(),
47426
- ".claude/commands/tutorial.md": claudeTutorialCommandTemplate(),
47427
- ".claude/commands/meta.md": claudeMetaCommandTemplate(),
47428
- ".claude/commands/work.md": claudeWorkCommandTemplate(),
47429
- ".claude/commands/docs.md": claudeDocsCommandTemplate(),
47430
- ".claude/skills/creds/SKILL.md": claudeCredsSkillTemplate(),
47431
- ".claude/rules/sdk-patterns.md": claudeSdkPatternsRuleTemplate(),
47432
- ".claude/rules/workspace-patterns.md": claudeWorkspaceRulesTemplate(),
47433
- ".claude/rules/docs-authoring.md": claudeDocsAuthoringRuleTemplate(),
47434
- ".claude/rules/memory-conventions.md": claudeMemoryConventionsRuleTemplate(),
47435
- ".claude/rules/project-map.md": claudeProjectMapRuleTemplate(),
47436
- ".claude/rules/task-tracking.md": claudeTaskTrackingRuleTemplate(),
47437
- ".claude/scripts/statusline-command.js": claudeStatuslineScriptTemplate()
47438
- };
47439
- if (options2.ui) {
47440
- Object.assign(files, getUIFiles(orgSlug));
47441
- }
47442
- for (const [filePath, content] of Object.entries(files)) {
47443
- await (0, import_promises2.writeFile)((0, import_path3.resolve)(targetDir, filePath), content, "utf-8");
47444
- }
47445
- console.log(source_default.green.bold(" Workspace created!"));
47446
- console.log("");
47447
- console.log(source_default.gray(" Next steps:"));
47448
- if (directory) {
47449
- console.log(source_default.gray(` cd ${directory}`));
47450
- }
47451
- console.log(source_default.gray(" pnpm install"));
47452
- console.log(source_default.gray(" # Add your API key to .env"));
47453
- console.log(source_default.gray(" elevasis-sdk check"));
47454
- console.log(source_default.gray(" elevasis-sdk deploy"));
47455
- if (options2.ui) {
47683
+ if (conflicts.length > 0) {
47684
+ console.error(source_default.red("Files already exist:"));
47685
+ for (const f of conflicts) {
47686
+ console.error(source_default.gray(` ${f}`));
47687
+ }
47688
+ console.error(source_default.gray("\n Use --force to overwrite."));
47689
+ throw new Error("Scaffold conflict");
47690
+ }
47691
+ }
47692
+ await (0, import_promises2.mkdir)((0, import_path3.resolve)(targetDir, "src/operations"), { recursive: true });
47693
+ await (0, import_promises2.mkdir)((0, import_path3.resolve)(targetDir, "src/example"), { recursive: true });
47694
+ await (0, import_promises2.mkdir)((0, import_path3.resolve)(targetDir, "src/shared"), { recursive: true });
47695
+ await (0, import_promises2.mkdir)((0, import_path3.resolve)(targetDir, "docs/in-progress"), { recursive: true });
47696
+ await (0, import_promises2.mkdir)((0, import_path3.resolve)(targetDir, ".claude/hooks"), { recursive: true });
47697
+ await (0, import_promises2.mkdir)((0, import_path3.resolve)(targetDir, ".claude/scripts"), { recursive: true });
47698
+ await (0, import_promises2.mkdir)((0, import_path3.resolve)(targetDir, ".claude/commands"), { recursive: true });
47699
+ await (0, import_promises2.mkdir)((0, import_path3.resolve)(targetDir, ".claude/skills/creds"), { recursive: true });
47700
+ await (0, import_promises2.mkdir)((0, import_path3.resolve)(targetDir, ".claude/rules"), { recursive: true });
47701
+ if (options2.ui) {
47702
+ await (0, import_promises2.mkdir)((0, import_path3.resolve)(targetDir, "ui/src"), { recursive: true });
47703
+ }
47704
+ const files = {
47705
+ "elevasis.config.ts": configTemplate(),
47706
+ "package.json": packageJsonTemplate(orgSlug),
47707
+ "pnpm-workspace.yaml": pnpmWorkspaceTemplate(),
47708
+ "tsconfig.json": tsconfigTemplate(),
47709
+ ".env": envTemplate(),
47710
+ ".npmrc": npmrcTemplate(),
47711
+ ".gitignore": gitignoreTemplate({ hasUI: options2.ui }),
47712
+ "src/index.ts": starterTemplate(),
47713
+ "src/operations/platform-status.ts": platformStatusTemplate(),
47714
+ "src/operations/index.ts": operationsBarrelTemplate(),
47715
+ "src/example/echo.ts": starterWorkflowTemplate(),
47716
+ "src/example/index.ts": exampleBarrelTemplate(),
47717
+ "src/shared/.gitkeep": "",
47718
+ "docs/index.mdx": docsIndexTemplate(orgSlug),
47719
+ "docs/in-progress/.gitkeep": "",
47720
+ "CLAUDE.md": claudeMdTemplate({ hasUI: options2.ui }),
47721
+ ".claude/settings.json": claudeSettingsTemplate(),
47722
+ ".claude/hooks/enforce-sdk-boundary.mjs": claudeSdkBoundaryHookTemplate(),
47723
+ ".claude/hooks/post-edit-validate.mjs": claudePostEditValidateHookTemplate(),
47724
+ ".claude/hooks/tool-failure-recovery.mjs": claudeToolFailureRecoveryHookTemplate(),
47725
+ ".claude/commands/tutorial.md": claudeTutorialCommandTemplate(),
47726
+ ".claude/commands/meta.md": claudeMetaCommandTemplate(),
47727
+ ".claude/commands/work.md": claudeWorkCommandTemplate(),
47728
+ ".claude/commands/docs.md": claudeDocsCommandTemplate(),
47729
+ ".claude/skills/creds/SKILL.md": claudeCredsSkillTemplate(),
47730
+ ".claude/rules/sdk-patterns.md": claudeSdkPatternsRuleTemplate(),
47731
+ ".claude/rules/workspace-patterns.md": claudeWorkspaceRulesTemplate(),
47732
+ ".claude/rules/docs-authoring.md": claudeDocsAuthoringRuleTemplate(),
47733
+ ".claude/rules/memory-conventions.md": claudeMemoryConventionsRuleTemplate(),
47734
+ ".claude/rules/project-map.md": claudeProjectMapRuleTemplate(),
47735
+ ".claude/rules/task-tracking.md": claudeTaskTrackingRuleTemplate(),
47736
+ ".claude/scripts/statusline-command.js": claudeStatuslineScriptTemplate()
47737
+ };
47738
+ if (options2.ui) {
47739
+ Object.assign(files, getUIFiles(orgSlug));
47740
+ }
47741
+ for (const [filePath, content] of Object.entries(files)) {
47742
+ await (0, import_promises2.writeFile)((0, import_path3.resolve)(targetDir, filePath), content, "utf-8");
47743
+ }
47744
+ console.log(source_default.green.bold(" Workspace created!"));
47456
47745
  console.log("");
47457
- console.log(source_default.gray(" UI app:"));
47458
- console.log(source_default.gray(" cd ui && pnpm install"));
47459
- console.log(source_default.gray(" # Set VITE_WORKOS_CLIENT_ID in ui/.env"));
47460
- console.log(source_default.gray(" pnpm dev"));
47461
- }
47462
- }));
47746
+ console.log(source_default.gray(" Next steps:"));
47747
+ if (directory) {
47748
+ console.log(source_default.gray(` cd ${directory}`));
47749
+ }
47750
+ console.log(source_default.gray(" pnpm install"));
47751
+ console.log(source_default.gray(" # Add your API key to .env"));
47752
+ console.log(source_default.gray(" elevasis-sdk check"));
47753
+ console.log(source_default.gray(" elevasis-sdk deploy"));
47754
+ if (options2.ui) {
47755
+ console.log("");
47756
+ console.log(source_default.gray(" UI app:"));
47757
+ console.log(source_default.gray(" cd ui && pnpm install"));
47758
+ console.log(source_default.gray(" # Set VITE_WORKOS_CLIENT_ID in ui/.env"));
47759
+ console.log(source_default.gray(" pnpm dev"));
47760
+ }
47761
+ })
47762
+ );
47463
47763
  }
47464
47764
  function toSlug(name) {
47465
47765
  const slug = name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/^[^a-z]+/, "").replace(/-+/g, "-").replace(/-$/, "");