@databricks/appkit 0.66.1 → 0.67.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/appkit/package.js +1 -1
  2. package/dist/core/agent/frontmatter.js +23 -0
  3. package/dist/core/agent/frontmatter.js.map +1 -0
  4. package/dist/core/agent/load-agents.d.ts.map +1 -1
  5. package/dist/core/agent/load-agents.js +36 -6
  6. package/dist/core/agent/load-agents.js.map +1 -1
  7. package/dist/core/agent/skills/index.js +7 -0
  8. package/dist/core/agent/skills/load-skills.js +78 -0
  9. package/dist/core/agent/skills/load-skills.js.map +1 -0
  10. package/dist/core/agent/skills/parse-skill.js +69 -0
  11. package/dist/core/agent/skills/parse-skill.js.map +1 -0
  12. package/dist/core/agent/skills/read-resource.js +31 -0
  13. package/dist/core/agent/skills/read-resource.js.map +1 -0
  14. package/dist/core/agent/skills/render.js +33 -0
  15. package/dist/core/agent/skills/render.js.map +1 -0
  16. package/dist/core/agent/skills/resolve-catalog.js +78 -0
  17. package/dist/core/agent/skills/resolve-catalog.js.map +1 -0
  18. package/dist/core/agent/skills/types.d.ts +50 -0
  19. package/dist/core/agent/skills/types.d.ts.map +1 -0
  20. package/dist/core/agent/types.d.ts +47 -0
  21. package/dist/core/agent/types.d.ts.map +1 -1
  22. package/dist/core/agent/types.js.map +1 -1
  23. package/dist/plugins/agents/agents.d.ts +50 -0
  24. package/dist/plugins/agents/agents.d.ts.map +1 -1
  25. package/dist/plugins/agents/agents.js +225 -13
  26. package/dist/plugins/agents/agents.js.map +1 -1
  27. package/dist/plugins/agents/manifest.js +40 -21
  28. package/dist/plugins/agents/schemas.js +2 -1
  29. package/dist/plugins/agents/schemas.js.map +1 -1
  30. package/dist/plugins/server/index.js +2 -2
  31. package/dist/plugins/server/index.js.map +1 -1
  32. package/dist/plugins/server/remote-tunnel/remote-tunnel-manager.js +3 -3
  33. package/dist/plugins/server/remote-tunnel/remote-tunnel-manager.js.map +1 -1
  34. package/dist/plugins/server/static-server.js +3 -3
  35. package/dist/plugins/server/static-server.js.map +1 -1
  36. package/dist/plugins/server/utils.js +3 -3
  37. package/dist/plugins/server/utils.js.map +1 -1
  38. package/dist/plugins/server/vite-dev-server.js +4 -4
  39. package/dist/plugins/server/vite-dev-server.js.map +1 -1
  40. package/dist/shared/src/schemas/manifest.d.ts +2 -2
  41. package/dist/type-generator/database/generate.js +3 -3
  42. package/dist/type-generator/database/generate.js.map +1 -1
  43. package/dist/type-generator/migration.js +2 -2
  44. package/dist/type-generator/migration.js.map +1 -1
  45. package/dist/type-generator/serving/server-file-extractor.js +3 -3
  46. package/dist/type-generator/serving/server-file-extractor.js.map +1 -1
  47. package/docs/api/appkit/Interface.AgentDefinition.md +11 -0
  48. package/docs/api/appkit/Interface.AgentsPluginConfig.md +35 -0
  49. package/docs/api/appkit/Interface.RegisteredAgent.md +11 -0
  50. package/docs/api/appkit/TypeAlias.ResolvedToolEntry.md +46 -0
  51. package/docs/plugins/agents.md +68 -1
  52. package/package.json +1 -1
  53. package/sbom.cdx.json +1 -1
@@ -81,8 +81,8 @@ declare const pluginManifestSchema: z.ZodObject<{
81
81
  type: z.ZodLiteral<"secret">;
82
82
  }, z.core.$strict>, z.ZodObject<{
83
83
  permission: z.ZodEnum<{
84
- CAN_MANAGE_RUN: "CAN_MANAGE_RUN";
85
84
  CAN_VIEW: "CAN_VIEW";
85
+ CAN_MANAGE_RUN: "CAN_MANAGE_RUN";
86
86
  CAN_MANAGE: "CAN_MANAGE";
87
87
  }>;
88
88
  alias: z.ZodString;
@@ -602,8 +602,8 @@ declare const pluginManifestSchema: z.ZodObject<{
602
602
  type: z.ZodLiteral<"secret">;
603
603
  }, z.core.$strict>, z.ZodObject<{
604
604
  permission: z.ZodEnum<{
605
- CAN_MANAGE_RUN: "CAN_MANAGE_RUN";
606
605
  CAN_VIEW: "CAN_VIEW";
606
+ CAN_MANAGE_RUN: "CAN_MANAGE_RUN";
607
607
  CAN_MANAGE: "CAN_MANAGE";
608
608
  }>;
609
609
  alias: z.ZodString;
@@ -3,7 +3,7 @@ import { walkSchema } from "./walk-schema.js";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import fs from "node:fs/promises";
5
5
  import path from "node:path";
6
- import fs$1 from "node:fs";
6
+ import fsSync from "node:fs";
7
7
 
8
8
  //#region src/type-generator/database/generate.ts
9
9
  /** Safe diagnostic raised when database declarations cannot be generated. */
@@ -63,7 +63,7 @@ ${walkSchema(schema).map((entry) => ` ${JSON.stringify(entry.name)}: {
63
63
  }
64
64
  /** Atomically replace generated output only when its content changes. */
65
65
  async function writeIfChanged(outFile, content) {
66
- if (fs$1.existsSync(outFile) && await fs.readFile(outFile, "utf8") === content) return;
66
+ if (fsSync.existsSync(outFile) && await fs.readFile(outFile, "utf8") === content) return;
67
67
  await fs.mkdir(path.dirname(outFile), { recursive: true });
68
68
  const temporary = path.join(path.dirname(outFile), `.${path.basename(outFile)}.${randomUUID()}.tmp`);
69
69
  try {
@@ -80,7 +80,7 @@ async function importSchema(schemaFile) {
80
80
  }
81
81
  /** Generate current registry types or neutralize them when loading fails. */
82
82
  async function generateDatabaseTypes(options) {
83
- if (!fs$1.existsSync(options.schemaFile)) {
83
+ if (!fsSync.existsSync(options.schemaFile)) {
84
84
  await writeIfChanged(options.outFile, NEUTRAL_DATABASE_TYPES);
85
85
  return;
86
86
  }
@@ -1 +1 @@
1
- {"version":3,"file":"generate.js","names":["fsSync"],"sources":["../../../src/type-generator/database/generate.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport fsSync from \"node:fs\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport type { Schema } from \"../../database/schema-builder\";\nimport { assertFinalizedSchema } from \"../../database/schema-builder/define-schema\";\nimport { walkSchema } from \"./walk-schema\";\n\n/** Safe diagnostic raised when database declarations cannot be generated. */\nexport class DatabaseTypegenError extends Error {\n constructor(message = \"Database schema generation failed\") {\n super(message);\n this.name = \"DatabaseTypegenError\";\n }\n}\n\ninterface GenerateDatabaseTypesOptions {\n readonly schemaFile: string;\n readonly outFile: string;\n}\n\nexport const DATABASE_TYPES_FILE = \"database.d.ts\";\n\nfunction schemaLabel(schemaFile: string): string {\n return path.relative(process.cwd(), schemaFile) || schemaFile;\n}\n\nfunction importFailureReason(error: unknown): string {\n if (error instanceof SyntaxError) return \"could not be parsed\";\n let code: unknown;\n try {\n code =\n error && typeof error === \"object\"\n ? Reflect.get(error, \"code\")\n : undefined;\n } catch {\n code = undefined;\n }\n if (code === \"MODULE_NOT_FOUND\" || code === \"ERR_MODULE_NOT_FOUND\") {\n return \"contains an unresolved import\";\n }\n return \"threw while loading\";\n}\n\n/** Empty augmentation used when no valid database schema is available. */\nexport const NEUTRAL_DATABASE_TYPES = `// Auto-generated by AppKit - DO NOT EDIT\nimport \"@databricks/appkit\";\n\ndeclare module \"@databricks/appkit\" {\n interface DatabaseRegistry {}\n}\n`;\n\n/** Render one `DatabaseRegistry` augmentation from a finalized schema. */\nfunction render(schema: Schema): string {\n const entries = walkSchema(schema)\n .map(\n (entry) => ` ${JSON.stringify(entry.name)}: {\n row: ${entry.row};\n publicRow: ${entry.publicRow};\n insert: ${entry.insert};\n update: ${entry.update};\n filters: ${entry.filters};\n includes: ${entry.includes};\n hasPrimaryKey: ${entry.hasPrimaryKey};\n };`,\n )\n .join(\"\\n\");\n return `// Auto-generated by AppKit - DO NOT EDIT\nimport \"@databricks/appkit\";\n\ndeclare module \"@databricks/appkit\" {\n type DatabaseLogicalFilter<T> = T & {\n and?: readonly DatabaseLogicalFilter<T>[];\n or?: readonly DatabaseLogicalFilter<T>[];\n };\n\n interface DatabaseRegistry {\n${entries}\n }\n}\n`;\n}\n\n/** Atomically replace generated output only when its content changes. */\nasync function writeIfChanged(outFile: string, content: string): Promise<void> {\n if (\n fsSync.existsSync(outFile) &&\n (await fs.readFile(outFile, \"utf8\")) === content\n )\n return;\n await fs.mkdir(path.dirname(outFile), { recursive: true });\n const temporary = path.join(\n path.dirname(outFile),\n `.${path.basename(outFile)}.${randomUUID()}.tmp`,\n );\n try {\n await fs.writeFile(temporary, content, \"utf8\");\n await fs.rename(temporary, outFile);\n } finally {\n await fs.rm(temporary, { force: true });\n }\n}\n\n/** Disable Jiti's module cache so watch-mode runs observe schema edits. */\nasync function importSchema(schemaFile: string): Promise<unknown> {\n const { createJiti } = await import(\"jiti\");\n return createJiti(import.meta.url, { moduleCache: false }).import(schemaFile);\n}\n\n/** Generate current registry types or neutralize them when loading fails. */\nexport async function generateDatabaseTypes(\n options: GenerateDatabaseTypesOptions,\n): Promise<void> {\n if (!fsSync.existsSync(options.schemaFile)) {\n await writeIfChanged(options.outFile, NEUTRAL_DATABASE_TYPES);\n return;\n }\n try {\n const module = (await importSchema(options.schemaFile)) as {\n schema?: Schema;\n };\n if (!(\"schema\" in module)) {\n throw new DatabaseTypegenError(\n `Database schema module \"${schemaLabel(options.schemaFile)}\" must export a named \"schema\"`,\n );\n }\n try {\n assertFinalizedSchema(module.schema);\n } catch {\n throw new DatabaseTypegenError(\n `Database schema \"${schemaLabel(options.schemaFile)}\" is not a finalized AppKit schema`,\n );\n }\n await writeIfChanged(options.outFile, render(module.schema));\n } catch (error) {\n // A failed run must not leave stale entities visible to TypeScript.\n await writeIfChanged(options.outFile, NEUTRAL_DATABASE_TYPES);\n throw error instanceof DatabaseTypegenError\n ? error\n : new DatabaseTypegenError(\n `Database schema \"${schemaLabel(options.schemaFile)}\" ${importFailureReason(error)}`,\n );\n }\n}\n"],"mappings":";;;;;;;;;AAUA,IAAa,uBAAb,cAA0C,MAAM;CAC9C,YAAY,UAAU,qCAAqC;AACzD,QAAM,QAAQ;AACd,OAAK,OAAO;;;AAShB,MAAa,sBAAsB;AAEnC,SAAS,YAAY,YAA4B;AAC/C,QAAO,KAAK,SAAS,QAAQ,KAAK,EAAE,WAAW,IAAI;;AAGrD,SAAS,oBAAoB,OAAwB;AACnD,KAAI,iBAAiB,YAAa,QAAO;CACzC,IAAI;AACJ,KAAI;AACF,SACE,SAAS,OAAO,UAAU,WACtB,QAAQ,IAAI,OAAO,OAAO,GAC1B;SACA;AACN,SAAO;;AAET,KAAI,SAAS,sBAAsB,SAAS,uBAC1C,QAAO;AAET,QAAO;;;AAIT,MAAa,yBAAyB;;;;;;;;AAStC,SAAS,OAAO,QAAwB;AActC,QAAO;;;;;;;;;;EAbS,WAAW,OAAO,CAC/B,KACE,UAAU,OAAO,KAAK,UAAU,MAAM,KAAK,CAAC;aACtC,MAAM,IAAI;mBACJ,MAAM,UAAU;gBACnB,MAAM,OAAO;gBACb,MAAM,OAAO;iBACZ,MAAM,QAAQ;kBACb,MAAM,SAAS;uBACV,MAAM,cAAc;QAEtC,CACA,KAAK,KAAK,CAWL;;;;;;AAOV,eAAe,eAAe,SAAiB,SAAgC;AAC7E,KACEA,KAAO,WAAW,QAAQ,IACzB,MAAM,GAAG,SAAS,SAAS,OAAO,KAAM,QAEzC;AACF,OAAM,GAAG,MAAM,KAAK,QAAQ,QAAQ,EAAE,EAAE,WAAW,MAAM,CAAC;CAC1D,MAAM,YAAY,KAAK,KACrB,KAAK,QAAQ,QAAQ,EACrB,IAAI,KAAK,SAAS,QAAQ,CAAC,GAAG,YAAY,CAAC,MAC5C;AACD,KAAI;AACF,QAAM,GAAG,UAAU,WAAW,SAAS,OAAO;AAC9C,QAAM,GAAG,OAAO,WAAW,QAAQ;WAC3B;AACR,QAAM,GAAG,GAAG,WAAW,EAAE,OAAO,MAAM,CAAC;;;;AAK3C,eAAe,aAAa,YAAsC;CAChE,MAAM,EAAE,eAAe,MAAM,OAAO;AACpC,QAAO,WAAW,OAAO,KAAK,KAAK,EAAE,aAAa,OAAO,CAAC,CAAC,OAAO,WAAW;;;AAI/E,eAAsB,sBACpB,SACe;AACf,KAAI,CAACA,KAAO,WAAW,QAAQ,WAAW,EAAE;AAC1C,QAAM,eAAe,QAAQ,SAAS,uBAAuB;AAC7D;;AAEF,KAAI;EACF,MAAM,SAAU,MAAM,aAAa,QAAQ,WAAW;AAGtD,MAAI,EAAE,YAAY,QAChB,OAAM,IAAI,qBACR,2BAA2B,YAAY,QAAQ,WAAW,CAAC,gCAC5D;AAEH,MAAI;AACF,yBAAsB,OAAO,OAAO;UAC9B;AACN,SAAM,IAAI,qBACR,oBAAoB,YAAY,QAAQ,WAAW,CAAC,oCACrD;;AAEH,QAAM,eAAe,QAAQ,SAAS,OAAO,OAAO,OAAO,CAAC;UACrD,OAAO;AAEd,QAAM,eAAe,QAAQ,SAAS,uBAAuB;AAC7D,QAAM,iBAAiB,uBACnB,QACA,IAAI,qBACF,oBAAoB,YAAY,QAAQ,WAAW,CAAC,IAAI,oBAAoB,MAAM,GACnF"}
1
+ {"version":3,"file":"generate.js","names":[],"sources":["../../../src/type-generator/database/generate.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport fsSync from \"node:fs\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport type { Schema } from \"../../database/schema-builder\";\nimport { assertFinalizedSchema } from \"../../database/schema-builder/define-schema\";\nimport { walkSchema } from \"./walk-schema\";\n\n/** Safe diagnostic raised when database declarations cannot be generated. */\nexport class DatabaseTypegenError extends Error {\n constructor(message = \"Database schema generation failed\") {\n super(message);\n this.name = \"DatabaseTypegenError\";\n }\n}\n\ninterface GenerateDatabaseTypesOptions {\n readonly schemaFile: string;\n readonly outFile: string;\n}\n\nexport const DATABASE_TYPES_FILE = \"database.d.ts\";\n\nfunction schemaLabel(schemaFile: string): string {\n return path.relative(process.cwd(), schemaFile) || schemaFile;\n}\n\nfunction importFailureReason(error: unknown): string {\n if (error instanceof SyntaxError) return \"could not be parsed\";\n let code: unknown;\n try {\n code =\n error && typeof error === \"object\"\n ? Reflect.get(error, \"code\")\n : undefined;\n } catch {\n code = undefined;\n }\n if (code === \"MODULE_NOT_FOUND\" || code === \"ERR_MODULE_NOT_FOUND\") {\n return \"contains an unresolved import\";\n }\n return \"threw while loading\";\n}\n\n/** Empty augmentation used when no valid database schema is available. */\nexport const NEUTRAL_DATABASE_TYPES = `// Auto-generated by AppKit - DO NOT EDIT\nimport \"@databricks/appkit\";\n\ndeclare module \"@databricks/appkit\" {\n interface DatabaseRegistry {}\n}\n`;\n\n/** Render one `DatabaseRegistry` augmentation from a finalized schema. */\nfunction render(schema: Schema): string {\n const entries = walkSchema(schema)\n .map(\n (entry) => ` ${JSON.stringify(entry.name)}: {\n row: ${entry.row};\n publicRow: ${entry.publicRow};\n insert: ${entry.insert};\n update: ${entry.update};\n filters: ${entry.filters};\n includes: ${entry.includes};\n hasPrimaryKey: ${entry.hasPrimaryKey};\n };`,\n )\n .join(\"\\n\");\n return `// Auto-generated by AppKit - DO NOT EDIT\nimport \"@databricks/appkit\";\n\ndeclare module \"@databricks/appkit\" {\n type DatabaseLogicalFilter<T> = T & {\n and?: readonly DatabaseLogicalFilter<T>[];\n or?: readonly DatabaseLogicalFilter<T>[];\n };\n\n interface DatabaseRegistry {\n${entries}\n }\n}\n`;\n}\n\n/** Atomically replace generated output only when its content changes. */\nasync function writeIfChanged(outFile: string, content: string): Promise<void> {\n if (\n fsSync.existsSync(outFile) &&\n (await fs.readFile(outFile, \"utf8\")) === content\n )\n return;\n await fs.mkdir(path.dirname(outFile), { recursive: true });\n const temporary = path.join(\n path.dirname(outFile),\n `.${path.basename(outFile)}.${randomUUID()}.tmp`,\n );\n try {\n await fs.writeFile(temporary, content, \"utf8\");\n await fs.rename(temporary, outFile);\n } finally {\n await fs.rm(temporary, { force: true });\n }\n}\n\n/** Disable Jiti's module cache so watch-mode runs observe schema edits. */\nasync function importSchema(schemaFile: string): Promise<unknown> {\n const { createJiti } = await import(\"jiti\");\n return createJiti(import.meta.url, { moduleCache: false }).import(schemaFile);\n}\n\n/** Generate current registry types or neutralize them when loading fails. */\nexport async function generateDatabaseTypes(\n options: GenerateDatabaseTypesOptions,\n): Promise<void> {\n if (!fsSync.existsSync(options.schemaFile)) {\n await writeIfChanged(options.outFile, NEUTRAL_DATABASE_TYPES);\n return;\n }\n try {\n const module = (await importSchema(options.schemaFile)) as {\n schema?: Schema;\n };\n if (!(\"schema\" in module)) {\n throw new DatabaseTypegenError(\n `Database schema module \"${schemaLabel(options.schemaFile)}\" must export a named \"schema\"`,\n );\n }\n try {\n assertFinalizedSchema(module.schema);\n } catch {\n throw new DatabaseTypegenError(\n `Database schema \"${schemaLabel(options.schemaFile)}\" is not a finalized AppKit schema`,\n );\n }\n await writeIfChanged(options.outFile, render(module.schema));\n } catch (error) {\n // A failed run must not leave stale entities visible to TypeScript.\n await writeIfChanged(options.outFile, NEUTRAL_DATABASE_TYPES);\n throw error instanceof DatabaseTypegenError\n ? error\n : new DatabaseTypegenError(\n `Database schema \"${schemaLabel(options.schemaFile)}\" ${importFailureReason(error)}`,\n );\n }\n}\n"],"mappings":";;;;;;;;;AAUA,IAAa,uBAAb,cAA0C,MAAM;CAC9C,YAAY,UAAU,qCAAqC;AACzD,QAAM,QAAQ;AACd,OAAK,OAAO;;;AAShB,MAAa,sBAAsB;AAEnC,SAAS,YAAY,YAA4B;AAC/C,QAAO,KAAK,SAAS,QAAQ,KAAK,EAAE,WAAW,IAAI;;AAGrD,SAAS,oBAAoB,OAAwB;AACnD,KAAI,iBAAiB,YAAa,QAAO;CACzC,IAAI;AACJ,KAAI;AACF,SACE,SAAS,OAAO,UAAU,WACtB,QAAQ,IAAI,OAAO,OAAO,GAC1B;SACA;AACN,SAAO;;AAET,KAAI,SAAS,sBAAsB,SAAS,uBAC1C,QAAO;AAET,QAAO;;;AAIT,MAAa,yBAAyB;;;;;;;;AAStC,SAAS,OAAO,QAAwB;AActC,QAAO;;;;;;;;;;EAbS,WAAW,OAAO,CAC/B,KACE,UAAU,OAAO,KAAK,UAAU,MAAM,KAAK,CAAC;aACtC,MAAM,IAAI;mBACJ,MAAM,UAAU;gBACnB,MAAM,OAAO;gBACb,MAAM,OAAO;iBACZ,MAAM,QAAQ;kBACb,MAAM,SAAS;uBACV,MAAM,cAAc;QAEtC,CACA,KAAK,KAAK,CAWL;;;;;;AAOV,eAAe,eAAe,SAAiB,SAAgC;AAC7E,KACE,OAAO,WAAW,QAAQ,IACzB,MAAM,GAAG,SAAS,SAAS,OAAO,KAAM,QAEzC;AACF,OAAM,GAAG,MAAM,KAAK,QAAQ,QAAQ,EAAE,EAAE,WAAW,MAAM,CAAC;CAC1D,MAAM,YAAY,KAAK,KACrB,KAAK,QAAQ,QAAQ,EACrB,IAAI,KAAK,SAAS,QAAQ,CAAC,GAAG,YAAY,CAAC,MAC5C;AACD,KAAI;AACF,QAAM,GAAG,UAAU,WAAW,SAAS,OAAO;AAC9C,QAAM,GAAG,OAAO,WAAW,QAAQ;WAC3B;AACR,QAAM,GAAG,GAAG,WAAW,EAAE,OAAO,MAAM,CAAC;;;;AAK3C,eAAe,aAAa,YAAsC;CAChE,MAAM,EAAE,eAAe,MAAM,OAAO;AACpC,QAAO,WAAW,OAAO,KAAK,KAAK,EAAE,aAAa,OAAO,CAAC,CAAC,OAAO,WAAW;;;AAI/E,eAAsB,sBACpB,SACe;AACf,KAAI,CAAC,OAAO,WAAW,QAAQ,WAAW,EAAE;AAC1C,QAAM,eAAe,QAAQ,SAAS,uBAAuB;AAC7D;;AAEF,KAAI;EACF,MAAM,SAAU,MAAM,aAAa,QAAQ,WAAW;AAGtD,MAAI,EAAE,YAAY,QAChB,OAAM,IAAI,qBACR,2BAA2B,YAAY,QAAQ,WAAW,CAAC,gCAC5D;AAEH,MAAI;AACF,yBAAsB,OAAO,OAAO;UAC9B;AACN,SAAM,IAAI,qBACR,oBAAoB,YAAY,QAAQ,WAAW,CAAC,oCACrD;;AAEH,QAAM,eAAe,QAAQ,SAAS,OAAO,OAAO,OAAO,CAAC;UACrD,OAAO;AAEd,QAAM,eAAe,QAAQ,SAAS,uBAAuB;AAC7D,QAAM,iBAAiB,uBACnB,QACA,IAAI,qBACF,oBAAoB,YAAY,QAAQ,WAAW,CAAC,IAAI,oBAAoB,MAAM,GACnF"}
@@ -2,7 +2,7 @@ import { createLogger } from "../logging/logger.js";
2
2
  import pc from "picocolors";
3
3
  import fs from "node:fs/promises";
4
4
  import path from "node:path";
5
- import fs$1 from "node:fs";
5
+ import fsSync from "node:fs";
6
6
 
7
7
  //#region src/type-generator/migration.ts
8
8
  const logger = createLogger("type-generator:migration");
@@ -16,7 +16,7 @@ const logger = createLogger("type-generator:migration");
16
16
  */
17
17
  function resolveProjectRoot(outFile) {
18
18
  const root = path.resolve(path.dirname(outFile), "..", "..");
19
- if (!fs$1.existsSync(path.join(root, "package.json"))) logger.warn("Resolved project root %s has no package.json — migration may target the wrong directory. Check your outFile path: %s", root, outFile);
19
+ if (!fsSync.existsSync(path.join(root, "package.json"))) logger.warn("Resolved project root %s has no package.json — migration may target the wrong directory. Check your outFile path: %s", root, outFile);
20
20
  return root;
21
21
  }
22
22
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"migration.js","names":["fsSync"],"sources":["../../src/type-generator/migration.ts"],"sourcesContent":["import fsSync from \"node:fs\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport pc from \"picocolors\";\n\nimport { createLogger } from \"../logging/logger\";\n\nconst logger = createLogger(\"type-generator:migration\");\n\n/**\n * Derive project root from an outFile path.\n * outFile is always `<projectRoot>/shared/appkit-types/<file>` — both the Vite plugins\n * and the CLI construct it this way, so going up two levels is safe.\n *\n * Validates that the resolved root contains a package.json — if not, logs a warning\n * so custom outFile paths don't silently operate on the wrong directory.\n */\nexport function resolveProjectRoot(outFile: string): string {\n const root = path.resolve(path.dirname(outFile), \"..\", \"..\");\n if (!fsSync.existsSync(path.join(root, \"package.json\"))) {\n logger.warn(\n \"Resolved project root %s has no package.json — migration may target the wrong directory. \" +\n \"Check your outFile path: %s\",\n root,\n outFile,\n );\n }\n return root;\n}\n\n/**\n * Remove old generated types from client/src/ (pre-shared/ location).\n * Best-effort: silently ignores missing files.\n */\nexport async function removeOldGeneratedTypes(\n projectRoot: string,\n filename: string,\n): Promise<void> {\n const oldFile = path.join(projectRoot, \"client\", \"src\", filename);\n try {\n await fs.unlink(oldFile);\n logger.debug(\"Removed old types at %s\", oldFile);\n } catch {\n // File doesn't exist — nothing to clean up\n }\n}\n\n// ── Project config migration ────────────────────────────────────────────\n\nconst migratedProjects = new Set<string>();\n\n/**\n * One-time config migration: update tsconfig and package.json for shared/ types output.\n * Idempotent — each sub-migration checks current file state and skips if already migrated.\n * Deduplicates per project root so monorepo builds migrate each app independently.\n * Opt-out: set `\"appkit\": { \"autoMigrate\": false }` in package.json.\n */\nexport async function migrateProjectConfig(projectRoot: string): Promise<void> {\n const resolved = path.resolve(projectRoot);\n if (migratedProjects.has(resolved)) return;\n migratedProjects.add(resolved);\n\n if (await isAutoMigrateDisabled(projectRoot)) {\n logger.debug(\"Auto-migration disabled via package.json appkit.autoMigrate\");\n return;\n }\n\n const results: Array<{ file: string; action: string }> = [];\n\n results.push(...(await migrateTsconfigClient(projectRoot)));\n results.push(...(await migrateTsconfigServer(projectRoot)));\n results.push(...(await migratePackageJsonScripts(projectRoot)));\n\n if (results.length > 0) {\n printMigrationSummary(results);\n }\n}\n\n/** Exported for testing only. */\nexport function _resetMigrationState(): void {\n migratedProjects.clear();\n}\n\n// ── Helpers ─────────────────────────────────────────────────────────────\n\nasync function isAutoMigrateDisabled(projectRoot: string): Promise<boolean> {\n try {\n const raw = await fs.readFile(\n path.join(projectRoot, \"package.json\"),\n \"utf-8\",\n );\n const parsed = JSON.parse(raw);\n return parsed.appkit?.autoMigrate === false;\n } catch {\n return false;\n }\n}\n\n/** Strip JSONC comments (block and line) so JSON.parse can handle tsconfig files. */\nfunction stripJsonComments(text: string): string {\n // Match strings (to skip them) or comments (to remove them).\n // Strings must be matched first to avoid stripping comment-like patterns inside string values\n // (e.g. \"server/**/*\" contains /* which looks like a block comment start).\n return text.replace(/\"(?:[^\"\\\\]|\\\\.)*\"|\\/\\*[\\s\\S]*?\\*\\/|\\/\\/.*/g, (match) =>\n match.startsWith('\"') ? match : \"\",\n );\n}\n\ntype MigrationResult = Array<{ file: string; action: string }>;\n\n// ── tsconfig.client.json ────────────────────────────────────────────────\n\nasync function migrateTsconfigClient(\n projectRoot: string,\n): Promise<MigrationResult> {\n const results: MigrationResult = [];\n const filePath = path.join(projectRoot, \"tsconfig.client.json\");\n\n try {\n const raw = await fs.readFile(filePath, \"utf-8\");\n const parsed = JSON.parse(stripJsonComments(raw));\n\n if (!Array.isArray(parsed.include)) return results;\n if (parsed.include.includes(\"shared/appkit-types\")) return results;\n\n parsed.include.push(\"shared/appkit-types\");\n await fs.writeFile(\n filePath,\n `${JSON.stringify(parsed, null, 2)}\\n`,\n \"utf-8\",\n );\n results.push({\n file: \"tsconfig.client.json\",\n action: 'added \"shared/appkit-types\" to include',\n });\n } catch (err) {\n logger.warn(\n \"Failed to migrate tsconfig.client.json: %s\",\n (err as Error).message,\n );\n }\n\n return results;\n}\n\n// ── tsconfig.server.json ────────────────────────────────────────────────\n\nasync function migrateTsconfigServer(\n projectRoot: string,\n): Promise<MigrationResult> {\n const results: MigrationResult = [];\n const filePath = path.join(projectRoot, \"tsconfig.server.json\");\n\n try {\n const raw = await fs.readFile(filePath, \"utf-8\");\n const parsed = JSON.parse(stripJsonComments(raw));\n const opts = parsed.compilerOptions;\n\n if (!opts || !opts.outDir) return results; // already migrated or non-standard\n\n delete opts.outDir;\n delete opts.declaration;\n delete opts.declarationMap;\n delete opts.sourceMap;\n opts.noEmit = true;\n\n await fs.writeFile(\n filePath,\n `${JSON.stringify(parsed, null, 2)}\\n`,\n \"utf-8\",\n );\n results.push({\n file: \"tsconfig.server.json\",\n action: \"switched to noEmit mode\",\n });\n } catch (err) {\n logger.warn(\n \"Failed to migrate tsconfig.server.json: %s\",\n (err as Error).message,\n );\n }\n\n return results;\n}\n\n// ── package.json ────────────────────────────────────────────────────────\n\nconst SCRIPT_MIGRATIONS: Record<string, { old: string; new: string }> = {\n \"build:server\": {\n old: \"tsdown -c tsdown.server.config.ts\",\n new: \"tsc -b tsconfig.server.json && tsdown -c tsdown.server.config.ts\",\n },\n typecheck: {\n old: \"tsc -p ./tsconfig.server.json --noEmit && tsc -p ./tsconfig.client.json --noEmit\",\n new: \"tsc -b tsconfig.server.json && tsc -b tsconfig.client.json\",\n },\n};\n\nasync function migratePackageJsonScripts(\n projectRoot: string,\n): Promise<MigrationResult> {\n const results: MigrationResult = [];\n const filePath = path.join(projectRoot, \"package.json\");\n\n try {\n const raw = await fs.readFile(filePath, \"utf-8\");\n const parsed = JSON.parse(raw);\n const scripts = parsed.scripts;\n if (!scripts) return results;\n\n const updated: string[] = [];\n\n for (const [name, { old, new: replacement }] of Object.entries(\n SCRIPT_MIGRATIONS,\n )) {\n if (scripts[name] === old) {\n scripts[name] = replacement;\n updated.push(name);\n }\n }\n\n if (updated.length === 0) return results;\n\n const indent = raw.match(/^\\s+/m)?.[0]?.length === 4 ? 4 : 2;\n await fs.writeFile(\n filePath,\n `${JSON.stringify(parsed, null, indent)}\\n`,\n \"utf-8\",\n );\n results.push({\n file: \"package.json\",\n action: `updated ${updated.join(\" and \")} scripts`,\n });\n } catch (err) {\n logger.warn(\n \"Failed to migrate package.json scripts: %s\",\n (err as Error).message,\n );\n }\n\n return results;\n}\n\n// ── Summary ─────────────────────────────────────────────────────────────\n\nfunction printMigrationSummary(\n results: Array<{ file: string; action: string }>,\n) {\n const separator = pc.dim(\"─\".repeat(50));\n console.log(\"\");\n console.log(` ${pc.bold(\"Typegen Migration\")}`);\n console.log(` ${separator}`);\n for (const { file, action } of results) {\n console.log(` ${pc.green(\"✓\")} ${file.padEnd(24)} ${pc.dim(action)}`);\n }\n console.log(` ${separator}`);\n console.log(\"\");\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"migration.js","names":[],"sources":["../../src/type-generator/migration.ts"],"sourcesContent":["import fsSync from \"node:fs\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport pc from \"picocolors\";\n\nimport { createLogger } from \"../logging/logger\";\n\nconst logger = createLogger(\"type-generator:migration\");\n\n/**\n * Derive project root from an outFile path.\n * outFile is always `<projectRoot>/shared/appkit-types/<file>` — both the Vite plugins\n * and the CLI construct it this way, so going up two levels is safe.\n *\n * Validates that the resolved root contains a package.json — if not, logs a warning\n * so custom outFile paths don't silently operate on the wrong directory.\n */\nexport function resolveProjectRoot(outFile: string): string {\n const root = path.resolve(path.dirname(outFile), \"..\", \"..\");\n if (!fsSync.existsSync(path.join(root, \"package.json\"))) {\n logger.warn(\n \"Resolved project root %s has no package.json — migration may target the wrong directory. \" +\n \"Check your outFile path: %s\",\n root,\n outFile,\n );\n }\n return root;\n}\n\n/**\n * Remove old generated types from client/src/ (pre-shared/ location).\n * Best-effort: silently ignores missing files.\n */\nexport async function removeOldGeneratedTypes(\n projectRoot: string,\n filename: string,\n): Promise<void> {\n const oldFile = path.join(projectRoot, \"client\", \"src\", filename);\n try {\n await fs.unlink(oldFile);\n logger.debug(\"Removed old types at %s\", oldFile);\n } catch {\n // File doesn't exist — nothing to clean up\n }\n}\n\n// ── Project config migration ────────────────────────────────────────────\n\nconst migratedProjects = new Set<string>();\n\n/**\n * One-time config migration: update tsconfig and package.json for shared/ types output.\n * Idempotent — each sub-migration checks current file state and skips if already migrated.\n * Deduplicates per project root so monorepo builds migrate each app independently.\n * Opt-out: set `\"appkit\": { \"autoMigrate\": false }` in package.json.\n */\nexport async function migrateProjectConfig(projectRoot: string): Promise<void> {\n const resolved = path.resolve(projectRoot);\n if (migratedProjects.has(resolved)) return;\n migratedProjects.add(resolved);\n\n if (await isAutoMigrateDisabled(projectRoot)) {\n logger.debug(\"Auto-migration disabled via package.json appkit.autoMigrate\");\n return;\n }\n\n const results: Array<{ file: string; action: string }> = [];\n\n results.push(...(await migrateTsconfigClient(projectRoot)));\n results.push(...(await migrateTsconfigServer(projectRoot)));\n results.push(...(await migratePackageJsonScripts(projectRoot)));\n\n if (results.length > 0) {\n printMigrationSummary(results);\n }\n}\n\n/** Exported for testing only. */\nexport function _resetMigrationState(): void {\n migratedProjects.clear();\n}\n\n// ── Helpers ─────────────────────────────────────────────────────────────\n\nasync function isAutoMigrateDisabled(projectRoot: string): Promise<boolean> {\n try {\n const raw = await fs.readFile(\n path.join(projectRoot, \"package.json\"),\n \"utf-8\",\n );\n const parsed = JSON.parse(raw);\n return parsed.appkit?.autoMigrate === false;\n } catch {\n return false;\n }\n}\n\n/** Strip JSONC comments (block and line) so JSON.parse can handle tsconfig files. */\nfunction stripJsonComments(text: string): string {\n // Match strings (to skip them) or comments (to remove them).\n // Strings must be matched first to avoid stripping comment-like patterns inside string values\n // (e.g. \"server/**/*\" contains /* which looks like a block comment start).\n return text.replace(/\"(?:[^\"\\\\]|\\\\.)*\"|\\/\\*[\\s\\S]*?\\*\\/|\\/\\/.*/g, (match) =>\n match.startsWith('\"') ? match : \"\",\n );\n}\n\ntype MigrationResult = Array<{ file: string; action: string }>;\n\n// ── tsconfig.client.json ────────────────────────────────────────────────\n\nasync function migrateTsconfigClient(\n projectRoot: string,\n): Promise<MigrationResult> {\n const results: MigrationResult = [];\n const filePath = path.join(projectRoot, \"tsconfig.client.json\");\n\n try {\n const raw = await fs.readFile(filePath, \"utf-8\");\n const parsed = JSON.parse(stripJsonComments(raw));\n\n if (!Array.isArray(parsed.include)) return results;\n if (parsed.include.includes(\"shared/appkit-types\")) return results;\n\n parsed.include.push(\"shared/appkit-types\");\n await fs.writeFile(\n filePath,\n `${JSON.stringify(parsed, null, 2)}\\n`,\n \"utf-8\",\n );\n results.push({\n file: \"tsconfig.client.json\",\n action: 'added \"shared/appkit-types\" to include',\n });\n } catch (err) {\n logger.warn(\n \"Failed to migrate tsconfig.client.json: %s\",\n (err as Error).message,\n );\n }\n\n return results;\n}\n\n// ── tsconfig.server.json ────────────────────────────────────────────────\n\nasync function migrateTsconfigServer(\n projectRoot: string,\n): Promise<MigrationResult> {\n const results: MigrationResult = [];\n const filePath = path.join(projectRoot, \"tsconfig.server.json\");\n\n try {\n const raw = await fs.readFile(filePath, \"utf-8\");\n const parsed = JSON.parse(stripJsonComments(raw));\n const opts = parsed.compilerOptions;\n\n if (!opts || !opts.outDir) return results; // already migrated or non-standard\n\n delete opts.outDir;\n delete opts.declaration;\n delete opts.declarationMap;\n delete opts.sourceMap;\n opts.noEmit = true;\n\n await fs.writeFile(\n filePath,\n `${JSON.stringify(parsed, null, 2)}\\n`,\n \"utf-8\",\n );\n results.push({\n file: \"tsconfig.server.json\",\n action: \"switched to noEmit mode\",\n });\n } catch (err) {\n logger.warn(\n \"Failed to migrate tsconfig.server.json: %s\",\n (err as Error).message,\n );\n }\n\n return results;\n}\n\n// ── package.json ────────────────────────────────────────────────────────\n\nconst SCRIPT_MIGRATIONS: Record<string, { old: string; new: string }> = {\n \"build:server\": {\n old: \"tsdown -c tsdown.server.config.ts\",\n new: \"tsc -b tsconfig.server.json && tsdown -c tsdown.server.config.ts\",\n },\n typecheck: {\n old: \"tsc -p ./tsconfig.server.json --noEmit && tsc -p ./tsconfig.client.json --noEmit\",\n new: \"tsc -b tsconfig.server.json && tsc -b tsconfig.client.json\",\n },\n};\n\nasync function migratePackageJsonScripts(\n projectRoot: string,\n): Promise<MigrationResult> {\n const results: MigrationResult = [];\n const filePath = path.join(projectRoot, \"package.json\");\n\n try {\n const raw = await fs.readFile(filePath, \"utf-8\");\n const parsed = JSON.parse(raw);\n const scripts = parsed.scripts;\n if (!scripts) return results;\n\n const updated: string[] = [];\n\n for (const [name, { old, new: replacement }] of Object.entries(\n SCRIPT_MIGRATIONS,\n )) {\n if (scripts[name] === old) {\n scripts[name] = replacement;\n updated.push(name);\n }\n }\n\n if (updated.length === 0) return results;\n\n const indent = raw.match(/^\\s+/m)?.[0]?.length === 4 ? 4 : 2;\n await fs.writeFile(\n filePath,\n `${JSON.stringify(parsed, null, indent)}\\n`,\n \"utf-8\",\n );\n results.push({\n file: \"package.json\",\n action: `updated ${updated.join(\" and \")} scripts`,\n });\n } catch (err) {\n logger.warn(\n \"Failed to migrate package.json scripts: %s\",\n (err as Error).message,\n );\n }\n\n return results;\n}\n\n// ── Summary ─────────────────────────────────────────────────────────────\n\nfunction printMigrationSummary(\n results: Array<{ file: string; action: string }>,\n) {\n const separator = pc.dim(\"─\".repeat(50));\n console.log(\"\");\n console.log(` ${pc.bold(\"Typegen Migration\")}`);\n console.log(` ${separator}`);\n for (const { file, action } of results) {\n console.log(` ${pc.green(\"✓\")} ${file.padEnd(24)} ${pc.dim(action)}`);\n }\n console.log(` ${separator}`);\n console.log(\"\");\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { createLogger } from "../../logging/logger.js";
2
2
  import path from "node:path";
3
- import fs from "node:fs";
3
+ import fsSync from "node:fs";
4
4
  import { Lang, parse } from "@ast-grep/napi";
5
5
 
6
6
  //#region src/type-generator/serving/server-file-extractor.ts
@@ -20,7 +20,7 @@ const SERVER_FILE_CANDIDATES = ["server/index.ts", "server/server.ts"];
20
20
  function findServerFile(basePath) {
21
21
  for (const candidate of SERVER_FILE_CANDIDATES) {
22
22
  const fullPath = path.join(basePath, candidate);
23
- if (fs.existsSync(fullPath)) return fullPath;
23
+ if (fsSync.existsSync(fullPath)) return fullPath;
24
24
  }
25
25
  return null;
26
26
  }
@@ -35,7 +35,7 @@ function findServerFile(basePath) {
35
35
  function extractServingEndpoints(serverFilePath) {
36
36
  let content;
37
37
  try {
38
- content = fs.readFileSync(serverFilePath, "utf-8");
38
+ content = fsSync.readFileSync(serverFilePath, "utf-8");
39
39
  } catch {
40
40
  logger.debug("Could not read server file: %s", serverFilePath);
41
41
  return null;
@@ -1 +1 @@
1
- {"version":3,"file":"server-file-extractor.js","names":[],"sources":["../../../src/type-generator/serving/server-file-extractor.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { Lang, parse, type SgNode } from \"@ast-grep/napi\";\n\nimport { createLogger } from \"../../logging/logger\";\nimport type { EndpointConfig } from \"../../plugins/serving/types\";\n\nconst logger = createLogger(\"type-generator:serving:extractor\");\n\n/**\n * Candidate paths for the server entry file, relative to the project root.\n * Checked in order; the first that exists is used.\n * Same convention as plugin sync (sync.ts SERVER_FILE_CANDIDATES).\n */\nconst SERVER_FILE_CANDIDATES = [\"server/index.ts\", \"server/server.ts\"];\n\n/**\n * Find the server entry file by checking candidate paths in order.\n *\n * @param basePath - Project root directory to search from\n * @returns Absolute path to the server file, or null if none found\n */\nexport function findServerFile(basePath: string): string | null {\n for (const candidate of SERVER_FILE_CANDIDATES) {\n const fullPath = path.join(basePath, candidate);\n if (fs.existsSync(fullPath)) {\n return fullPath;\n }\n }\n return null;\n}\n\n/**\n * Extract serving endpoint config from a server file by AST-parsing it.\n * Looks for `serving({ endpoints: { alias: { env: \"...\" }, ... } })` calls\n * and extracts the endpoint alias names and their environment variable mappings.\n *\n * @param serverFilePath - Absolute path to the server entry file\n * @returns Extracted endpoint config, or null if not found or not extractable\n */\nexport function extractServingEndpoints(\n serverFilePath: string,\n): Record<string, EndpointConfig> | null {\n let content: string;\n try {\n content = fs.readFileSync(serverFilePath, \"utf-8\");\n } catch {\n logger.debug(\"Could not read server file: %s\", serverFilePath);\n return null;\n }\n\n const lang = serverFilePath.endsWith(\".tsx\") ? Lang.Tsx : Lang.TypeScript;\n const ast = parse(lang, content);\n const root = ast.root();\n\n // Find serving(...) call expressions\n const servingCall = findServingCall(root);\n if (!servingCall) {\n logger.debug(\"No serving() call found in %s\", serverFilePath);\n return null;\n }\n\n // Get the first argument (the config object)\n const args = servingCall.field(\"arguments\");\n if (!args) {\n return null;\n }\n\n const configArg = args.children().find((child) => child.kind() === \"object\");\n if (!configArg) {\n // serving() called with no args or non-object arg\n return null;\n }\n\n // Find the \"endpoints\" property in the config object\n const endpointsPair = findProperty(configArg, \"endpoints\");\n if (!endpointsPair) {\n // Config object has no \"endpoints\" property (e.g. serving({ timeout: 5000 }))\n return null;\n }\n\n // Get the value of the endpoints property\n const endpointsValue = getPropertyValue(endpointsPair);\n if (!endpointsValue || endpointsValue.kind() !== \"object\") {\n // endpoints is a variable reference, not an inline object\n logger.debug(\n \"serving() endpoints is not an inline object literal in %s. \" +\n \"Pass endpoints explicitly via appKitServingTypesPlugin({ endpoints }) in vite.config.ts.\",\n serverFilePath,\n );\n return null;\n }\n\n // Extract each endpoint entry\n const endpoints: Record<string, EndpointConfig> = {};\n const pairs = endpointsValue\n .children()\n .filter((child) => child.kind() === \"pair\");\n\n for (const pair of pairs) {\n const entry = extractEndpointEntry(pair);\n if (entry) {\n endpoints[entry.alias] = entry.config;\n }\n }\n\n if (Object.keys(endpoints).length === 0) {\n return null;\n }\n\n logger.debug(\n \"Extracted %d endpoint(s) from %s: %s\",\n Object.keys(endpoints).length,\n serverFilePath,\n Object.keys(endpoints).join(\", \"),\n );\n\n return endpoints;\n}\n\n/**\n * Find the serving() call expression in the AST.\n * Looks for call expressions where the callee identifier is \"serving\".\n */\nfunction findServingCall(root: SgNode): SgNode | null {\n const callExpressions = root.findAll({\n rule: { kind: \"call_expression\" },\n });\n\n for (const call of callExpressions) {\n const callee = call.children()[0];\n if (callee?.kind() === \"identifier\" && callee.text() === \"serving\") {\n return call;\n }\n }\n\n return null;\n}\n\n/**\n * Find a property (pair node) with the given key name in an object expression.\n */\nfunction findProperty(objectNode: SgNode, propertyName: string): SgNode | null {\n const pairs = objectNode\n .children()\n .filter((child) => child.kind() === \"pair\");\n\n for (const pair of pairs) {\n const key = pair.children()[0];\n if (!key) continue;\n\n const keyText =\n key.kind() === \"property_identifier\"\n ? key.text()\n : key.kind() === \"string\"\n ? key.text().replace(/^['\"]|['\"]$/g, \"\")\n : null;\n\n if (keyText === propertyName) {\n return pair;\n }\n }\n\n return null;\n}\n\n/**\n * Get the value node from a pair (property: value).\n * The value is typically the last meaningful child after the colon.\n */\nfunction getPropertyValue(pairNode: SgNode): SgNode | null {\n const children = pairNode.children();\n // pair children: [key, \":\", value]\n return children.length >= 3 ? children[children.length - 1] : null;\n}\n\n/**\n * Extract a single endpoint entry from a pair node like:\n * `demo: { env: \"DATABRICKS_SERVING_ENDPOINT_NAME\", servedModel: \"my-model\" }`\n */\nfunction extractEndpointEntry(\n pair: SgNode,\n): { alias: string; config: EndpointConfig } | null {\n const children = pair.children();\n if (children.length < 3) return null;\n\n // Get alias name (the key)\n const keyNode = children[0];\n const alias =\n keyNode.kind() === \"property_identifier\"\n ? keyNode.text()\n : keyNode.kind() === \"string\"\n ? keyNode.text().replace(/^['\"]|['\"]$/g, \"\")\n : null;\n\n if (!alias) return null;\n\n // Get the value (should be an object like { env: \"...\" })\n const valueNode = children[children.length - 1];\n if (valueNode.kind() !== \"object\") return null;\n\n // Extract env field\n const envPair = findProperty(valueNode, \"env\");\n if (!envPair) return null;\n\n const envValue = getPropertyValue(envPair);\n if (!envValue || envValue.kind() !== \"string\") return null;\n\n const env = envValue.text().replace(/^['\"]|['\"]$/g, \"\");\n\n // Extract optional servedModel field\n const config: EndpointConfig = { env };\n const servedModelPair = findProperty(valueNode, \"servedModel\");\n if (servedModelPair) {\n const servedModelValue = getPropertyValue(servedModelPair);\n if (servedModelValue?.kind() === \"string\") {\n config.servedModel = servedModelValue.text().replace(/^['\"]|['\"]$/g, \"\");\n }\n }\n\n return { alias, config };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"server-file-extractor.js","names":["fs"],"sources":["../../../src/type-generator/serving/server-file-extractor.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { Lang, parse, type SgNode } from \"@ast-grep/napi\";\n\nimport { createLogger } from \"../../logging/logger\";\nimport type { EndpointConfig } from \"../../plugins/serving/types\";\n\nconst logger = createLogger(\"type-generator:serving:extractor\");\n\n/**\n * Candidate paths for the server entry file, relative to the project root.\n * Checked in order; the first that exists is used.\n * Same convention as plugin sync (sync.ts SERVER_FILE_CANDIDATES).\n */\nconst SERVER_FILE_CANDIDATES = [\"server/index.ts\", \"server/server.ts\"];\n\n/**\n * Find the server entry file by checking candidate paths in order.\n *\n * @param basePath - Project root directory to search from\n * @returns Absolute path to the server file, or null if none found\n */\nexport function findServerFile(basePath: string): string | null {\n for (const candidate of SERVER_FILE_CANDIDATES) {\n const fullPath = path.join(basePath, candidate);\n if (fs.existsSync(fullPath)) {\n return fullPath;\n }\n }\n return null;\n}\n\n/**\n * Extract serving endpoint config from a server file by AST-parsing it.\n * Looks for `serving({ endpoints: { alias: { env: \"...\" }, ... } })` calls\n * and extracts the endpoint alias names and their environment variable mappings.\n *\n * @param serverFilePath - Absolute path to the server entry file\n * @returns Extracted endpoint config, or null if not found or not extractable\n */\nexport function extractServingEndpoints(\n serverFilePath: string,\n): Record<string, EndpointConfig> | null {\n let content: string;\n try {\n content = fs.readFileSync(serverFilePath, \"utf-8\");\n } catch {\n logger.debug(\"Could not read server file: %s\", serverFilePath);\n return null;\n }\n\n const lang = serverFilePath.endsWith(\".tsx\") ? Lang.Tsx : Lang.TypeScript;\n const ast = parse(lang, content);\n const root = ast.root();\n\n // Find serving(...) call expressions\n const servingCall = findServingCall(root);\n if (!servingCall) {\n logger.debug(\"No serving() call found in %s\", serverFilePath);\n return null;\n }\n\n // Get the first argument (the config object)\n const args = servingCall.field(\"arguments\");\n if (!args) {\n return null;\n }\n\n const configArg = args.children().find((child) => child.kind() === \"object\");\n if (!configArg) {\n // serving() called with no args or non-object arg\n return null;\n }\n\n // Find the \"endpoints\" property in the config object\n const endpointsPair = findProperty(configArg, \"endpoints\");\n if (!endpointsPair) {\n // Config object has no \"endpoints\" property (e.g. serving({ timeout: 5000 }))\n return null;\n }\n\n // Get the value of the endpoints property\n const endpointsValue = getPropertyValue(endpointsPair);\n if (!endpointsValue || endpointsValue.kind() !== \"object\") {\n // endpoints is a variable reference, not an inline object\n logger.debug(\n \"serving() endpoints is not an inline object literal in %s. \" +\n \"Pass endpoints explicitly via appKitServingTypesPlugin({ endpoints }) in vite.config.ts.\",\n serverFilePath,\n );\n return null;\n }\n\n // Extract each endpoint entry\n const endpoints: Record<string, EndpointConfig> = {};\n const pairs = endpointsValue\n .children()\n .filter((child) => child.kind() === \"pair\");\n\n for (const pair of pairs) {\n const entry = extractEndpointEntry(pair);\n if (entry) {\n endpoints[entry.alias] = entry.config;\n }\n }\n\n if (Object.keys(endpoints).length === 0) {\n return null;\n }\n\n logger.debug(\n \"Extracted %d endpoint(s) from %s: %s\",\n Object.keys(endpoints).length,\n serverFilePath,\n Object.keys(endpoints).join(\", \"),\n );\n\n return endpoints;\n}\n\n/**\n * Find the serving() call expression in the AST.\n * Looks for call expressions where the callee identifier is \"serving\".\n */\nfunction findServingCall(root: SgNode): SgNode | null {\n const callExpressions = root.findAll({\n rule: { kind: \"call_expression\" },\n });\n\n for (const call of callExpressions) {\n const callee = call.children()[0];\n if (callee?.kind() === \"identifier\" && callee.text() === \"serving\") {\n return call;\n }\n }\n\n return null;\n}\n\n/**\n * Find a property (pair node) with the given key name in an object expression.\n */\nfunction findProperty(objectNode: SgNode, propertyName: string): SgNode | null {\n const pairs = objectNode\n .children()\n .filter((child) => child.kind() === \"pair\");\n\n for (const pair of pairs) {\n const key = pair.children()[0];\n if (!key) continue;\n\n const keyText =\n key.kind() === \"property_identifier\"\n ? key.text()\n : key.kind() === \"string\"\n ? key.text().replace(/^['\"]|['\"]$/g, \"\")\n : null;\n\n if (keyText === propertyName) {\n return pair;\n }\n }\n\n return null;\n}\n\n/**\n * Get the value node from a pair (property: value).\n * The value is typically the last meaningful child after the colon.\n */\nfunction getPropertyValue(pairNode: SgNode): SgNode | null {\n const children = pairNode.children();\n // pair children: [key, \":\", value]\n return children.length >= 3 ? children[children.length - 1] : null;\n}\n\n/**\n * Extract a single endpoint entry from a pair node like:\n * `demo: { env: \"DATABRICKS_SERVING_ENDPOINT_NAME\", servedModel: \"my-model\" }`\n */\nfunction extractEndpointEntry(\n pair: SgNode,\n): { alias: string; config: EndpointConfig } | null {\n const children = pair.children();\n if (children.length < 3) return null;\n\n // Get alias name (the key)\n const keyNode = children[0];\n const alias =\n keyNode.kind() === \"property_identifier\"\n ? keyNode.text()\n : keyNode.kind() === \"string\"\n ? keyNode.text().replace(/^['\"]|['\"]$/g, \"\")\n : null;\n\n if (!alias) return null;\n\n // Get the value (should be an object like { env: \"...\" })\n const valueNode = children[children.length - 1];\n if (valueNode.kind() !== \"object\") return null;\n\n // Extract env field\n const envPair = findProperty(valueNode, \"env\");\n if (!envPair) return null;\n\n const envValue = getPropertyValue(envPair);\n if (!envValue || envValue.kind() !== \"string\") return null;\n\n const env = envValue.text().replace(/^['\"]|['\"]$/g, \"\");\n\n // Extract optional servedModel field\n const config: EndpointConfig = { env };\n const servedModelPair = findProperty(valueNode, \"servedModel\");\n if (servedModelPair) {\n const servedModelValue = getPropertyValue(servedModelPair);\n if (servedModelValue?.kind() === \"string\") {\n config.servedModel = servedModelValue.text().replace(/^['\"]|['\"]$/g, \"\");\n }\n }\n\n return { alias, config };\n}\n"],"mappings":";;;;;;AAQA,MAAM,SAAS,aAAa,mCAAmC;;;;;;AAO/D,MAAM,yBAAyB,CAAC,mBAAmB,mBAAmB;;;;;;;AAQtE,SAAgB,eAAe,UAAiC;AAC9D,MAAK,MAAM,aAAa,wBAAwB;EAC9C,MAAM,WAAW,KAAK,KAAK,UAAU,UAAU;AAC/C,MAAIA,OAAG,WAAW,SAAS,CACzB,QAAO;;AAGX,QAAO;;;;;;;;;;AAWT,SAAgB,wBACd,gBACuC;CACvC,IAAI;AACJ,KAAI;AACF,YAAUA,OAAG,aAAa,gBAAgB,QAAQ;SAC5C;AACN,SAAO,MAAM,kCAAkC,eAAe;AAC9D,SAAO;;CAQT,MAAM,cAAc,gBAJR,MADC,eAAe,SAAS,OAAO,GAAG,KAAK,MAAM,KAAK,YACvC,QAAQ,CACf,MAAM,CAGkB;AACzC,KAAI,CAAC,aAAa;AAChB,SAAO,MAAM,iCAAiC,eAAe;AAC7D,SAAO;;CAIT,MAAM,OAAO,YAAY,MAAM,YAAY;AAC3C,KAAI,CAAC,KACH,QAAO;CAGT,MAAM,YAAY,KAAK,UAAU,CAAC,MAAM,UAAU,MAAM,MAAM,KAAK,SAAS;AAC5E,KAAI,CAAC,UAEH,QAAO;CAIT,MAAM,gBAAgB,aAAa,WAAW,YAAY;AAC1D,KAAI,CAAC,cAEH,QAAO;CAIT,MAAM,iBAAiB,iBAAiB,cAAc;AACtD,KAAI,CAAC,kBAAkB,eAAe,MAAM,KAAK,UAAU;AAEzD,SAAO,MACL,uJAEA,eACD;AACD,SAAO;;CAIT,MAAM,YAA4C,EAAE;CACpD,MAAM,QAAQ,eACX,UAAU,CACV,QAAQ,UAAU,MAAM,MAAM,KAAK,OAAO;AAE7C,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,QAAQ,qBAAqB,KAAK;AACxC,MAAI,MACF,WAAU,MAAM,SAAS,MAAM;;AAInC,KAAI,OAAO,KAAK,UAAU,CAAC,WAAW,EACpC,QAAO;AAGT,QAAO,MACL,wCACA,OAAO,KAAK,UAAU,CAAC,QACvB,gBACA,OAAO,KAAK,UAAU,CAAC,KAAK,KAAK,CAClC;AAED,QAAO;;;;;;AAOT,SAAS,gBAAgB,MAA6B;CACpD,MAAM,kBAAkB,KAAK,QAAQ,EACnC,MAAM,EAAE,MAAM,mBAAmB,EAClC,CAAC;AAEF,MAAK,MAAM,QAAQ,iBAAiB;EAClC,MAAM,SAAS,KAAK,UAAU,CAAC;AAC/B,MAAI,QAAQ,MAAM,KAAK,gBAAgB,OAAO,MAAM,KAAK,UACvD,QAAO;;AAIX,QAAO;;;;;AAMT,SAAS,aAAa,YAAoB,cAAqC;CAC7E,MAAM,QAAQ,WACX,UAAU,CACV,QAAQ,UAAU,MAAM,MAAM,KAAK,OAAO;AAE7C,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,MAAM,KAAK,UAAU,CAAC;AAC5B,MAAI,CAAC,IAAK;AASV,OANE,IAAI,MAAM,KAAK,wBACX,IAAI,MAAM,GACV,IAAI,MAAM,KAAK,WACb,IAAI,MAAM,CAAC,QAAQ,gBAAgB,GAAG,GACtC,UAEQ,aACd,QAAO;;AAIX,QAAO;;;;;;AAOT,SAAS,iBAAiB,UAAiC;CACzD,MAAM,WAAW,SAAS,UAAU;AAEpC,QAAO,SAAS,UAAU,IAAI,SAAS,SAAS,SAAS,KAAK;;;;;;AAOhE,SAAS,qBACP,MACkD;CAClD,MAAM,WAAW,KAAK,UAAU;AAChC,KAAI,SAAS,SAAS,EAAG,QAAO;CAGhC,MAAM,UAAU,SAAS;CACzB,MAAM,QACJ,QAAQ,MAAM,KAAK,wBACf,QAAQ,MAAM,GACd,QAAQ,MAAM,KAAK,WACjB,QAAQ,MAAM,CAAC,QAAQ,gBAAgB,GAAG,GAC1C;AAER,KAAI,CAAC,MAAO,QAAO;CAGnB,MAAM,YAAY,SAAS,SAAS,SAAS;AAC7C,KAAI,UAAU,MAAM,KAAK,SAAU,QAAO;CAG1C,MAAM,UAAU,aAAa,WAAW,MAAM;AAC9C,KAAI,CAAC,QAAS,QAAO;CAErB,MAAM,WAAW,iBAAiB,QAAQ;AAC1C,KAAI,CAAC,YAAY,SAAS,MAAM,KAAK,SAAU,QAAO;CAKtD,MAAM,SAAyB,EAAE,KAHrB,SAAS,MAAM,CAAC,QAAQ,gBAAgB,GAAG,EAGjB;CACtC,MAAM,kBAAkB,aAAa,WAAW,cAAc;AAC9D,KAAI,iBAAiB;EACnB,MAAM,mBAAmB,iBAAiB,gBAAgB;AAC1D,MAAI,kBAAkB,MAAM,KAAK,SAC/B,QAAO,cAAc,iBAAiB,MAAM,CAAC,QAAQ,gBAAgB,GAAG;;AAI5E,QAAO;EAAE;EAAO;EAAQ"}
@@ -118,6 +118,17 @@ Setting `name` to a value that differs from the registry key is harmless but con
118
118
 
119
119
  ***
120
120
 
121
+ ### skills?[​](#skills "Direct link to skills?")
122
+
123
+ ```ts
124
+ optional skills: string[];
125
+
126
+ ```
127
+
128
+ Names of global skills (shared `skills/` pool or catalog volume) to make visible to this agent. Per-agent skills under `<id>/skills/` are always visible and need not be listed. Ignored when the plugin's `autoInheritSkills` makes every global skill visible.
129
+
130
+ ***
131
+
121
132
  ### tools?[​](#tools "Direct link to tools?")
122
133
 
123
134
  ```ts
@@ -60,6 +60,19 @@ Milliseconds to wait before auto-denying. Default: 60\_000.
60
60
 
61
61
  ***
62
62
 
63
+ ### autoInheritSkills?[​](#autoinheritskills "Direct link to autoInheritSkills?")
64
+
65
+ ```ts
66
+ optional autoInheritSkills:
67
+ | boolean
68
+ | AutoInheritToolsConfig;
69
+
70
+ ```
71
+
72
+ Whether every global skill (shared `skills/` pool or catalog volume) is visible to an agent without listing it in `skills:` frontmatter. Off by default so each agent's always-on skill catalog stays lean; accepts a boolean shorthand or a per-origin `{ file, code }` config, mirroring [autoInheritTools](#autoinherittools).
73
+
74
+ ***
75
+
63
76
  ### autoInheritTools?[​](#autoinherittools "Direct link to autoInheritTools?")
64
77
 
65
78
  ```ts
@@ -198,6 +211,28 @@ optional name: string;
198
211
 
199
212
  ***
200
213
 
214
+ ### skillCredentialMode?[​](#skillcredentialmode "Direct link to skillCredentialMode?")
215
+
216
+ ```ts
217
+ optional skillCredentialMode: "sp" | "obo";
218
+
219
+ ```
220
+
221
+ Identity used to read catalog (volume) skills. v1 supports `"sp"` (default — a shared, service-principal-readable curated pool). `"obo"` is the reserved switch point for per-user skill volumes and is not wired yet (falls back to `"sp"` with a warning).
222
+
223
+ ***
224
+
225
+ ### skillsVolume?[​](#skillsvolume "Direct link to skillsVolume?")
226
+
227
+ ```ts
228
+ optional skillsVolume: string;
229
+
230
+ ```
231
+
232
+ Unity Catalog Volume path for catalog-sourced skills (e.g. `/Volumes/<catalog>/<schema>/<volume>`). Falls back to the `DATABRICKS_VOLUME_AGENT_SKILLS` env var. Skills at `<volume>/<name>/SKILL.md` are discovered at boot and on `reload()` and read as the service principal.
233
+
234
+ ***
235
+
201
236
  ### telemetry?[​](#telemetry "Direct link to telemetry?")
202
237
 
203
238
  ```ts
@@ -78,6 +78,17 @@ name: string;
78
78
 
79
79
  ***
80
80
 
81
+ ### skills?[​](#skills "Direct link to skills?")
82
+
83
+ ```ts
84
+ optional skills: ResolvedSkillCatalog;
85
+
86
+ ```
87
+
88
+ Resolved per-agent skill catalog (visibility + collision rules applied). Present when any skill is visible to this agent; drives the always-on prompt catalog and `load_skill` dispatch.
89
+
90
+ ***
91
+
81
92
  ### toolIndex[​](#toolindex "Direct link to toolIndex")
82
93
 
83
94
  ```ts
@@ -27,6 +27,12 @@ type ResolvedToolEntry =
27
27
  def: AgentToolDefinition;
28
28
  source: "hosted-supervisor";
29
29
  spec: SupervisorTool;
30
+ }
31
+ | {
32
+ builtin: "load_skill" | "read_skill_file";
33
+ catalog: ResolvedSkillCatalog;
34
+ def: AgentToolDefinition;
35
+ source: "skill";
30
36
  };
31
37
 
32
38
  ```
@@ -194,3 +200,43 @@ Adapter-side hosted tool (executed by the model-host, not by the Node process).
194
200
  spec: SupervisorTool;
195
201
 
196
202
  ```
203
+
204
+ ```ts
205
+ {
206
+ builtin: "load_skill" | "read_skill_file";
207
+ catalog: ResolvedSkillCatalog;
208
+ def: AgentToolDefinition;
209
+ source: "skill";
210
+ }
211
+
212
+ ```
213
+
214
+ ### builtin[​](#builtin "Direct link to builtin")
215
+
216
+ ```ts
217
+ builtin: "load_skill" | "read_skill_file";
218
+
219
+ ```
220
+
221
+ ### catalog[​](#catalog "Direct link to catalog")
222
+
223
+ ```ts
224
+ catalog: ResolvedSkillCatalog;
225
+
226
+ ```
227
+
228
+ ### def[​](#def-5 "Direct link to def")
229
+
230
+ ```ts
231
+ def: AgentToolDefinition;
232
+
233
+ ```
234
+
235
+ ### source[​](#source-5 "Direct link to source")
236
+
237
+ ```ts
238
+ source: "skill";
239
+
240
+ ```
241
+
242
+ Built-in skill tools (`load_skill`, `read_skill_file`) injected into any agent that has a visible skill catalog. Executed in-process by the agents plugin against the agent's resolved catalog; read-only, so they bypass the approval gate.
@@ -36,7 +36,7 @@ That alone gives you a live HTTP server with `POST /invocations` (and its alias
36
36
 
37
37
  ## Level 1: drop a markdown agent package[​](#level-1-drop-a-markdown-agent-package "Direct link to Level 1: drop a markdown agent package")
38
38
 
39
- Each agent lives in its own folder under `server/agents/` with entry file `agent.md`. A folder is an agent only if it holds an entry file (`agent.md` or `agent.ts`); a folder without one is skipped, so per-agent asset folders like `skills/` sit beside the entry.
39
+ Each agent lives in its own folder under `server/agents/` with entry file `agent.md`. A folder is an agent only if it holds an entry file (`agent.md` or `agent.ts`); a folder without one is skipped, so per-agent asset folders sit beside the entry — notably a `skills/` folder holding [Skills](#skills) (on-demand instruction packs the agent loads by name). A shared `server/agents/skills/` folder holds skills available to any agent.
40
40
 
41
41
  ```text
42
42
  my-app/
@@ -211,6 +211,69 @@ await createApp({
211
211
 
212
212
  Put `supervisor`, `researcher`, and `writer` in their own `server/agents/<id>/agent.ts` folders (default export each) — a markdown parent can also delegate to a code child in a sibling folder via `agents: [helper]` frontmatter. Each key in `agents: {...}` on an `AgentDefinition` becomes an `agent-<key>` tool on the parent. When invoked, the agents plugin runs the child's adapter with a fresh message list (no shared thread state) and returns the aggregated text. Cycles in a code agent's inline `agents: {}` graph are rejected at load (`createAgent`); markdown `agents:` delegation rejects self-references at load and bounds deeper cycles at runtime via `limits.maxSubAgentDepth`.
213
213
 
214
+ ## Skills[​](#skills "Direct link to Skills")
215
+
216
+ Skills are on-demand instruction packs — the same `SKILL.md` format Claude Code and Cursor use. Only each skill's `name` + `description` sit in the system prompt (always-on, cheap); the full body loads on demand when the agent (or the user) invokes it. This works on any Databricks-served model — AppKit implements the disclosure itself, so it doesn't depend on a provider-native skills feature.
217
+
218
+ A skill is a directory with a `SKILL.md` plus any bundled reference files:
219
+
220
+ ```text
221
+ config/agents/
222
+ skills/ # shared pool — any agent can opt in
223
+ pdf-forms/
224
+ SKILL.md
225
+ reference.md
226
+ planner/
227
+ agent.md
228
+ skills/ # private to the `planner` agent
229
+ house-style/
230
+ SKILL.md
231
+
232
+ ```
233
+
234
+ ```md
235
+ ---
236
+ name: pdf-forms
237
+ description: Fill and validate PDF form fields from a data record.
238
+ ---
239
+
240
+ To fill a PDF form:
241
+
242
+ 1. Read `reference.md` for the field-name conventions.
243
+ 2. ...
244
+
245
+ ```
246
+
247
+ `name` and `description` are required; `license`, `allowed-tools`, and `metadata` are accepted for compatibility with skills authored elsewhere. Unknown keys warn and are ignored.
248
+
249
+ ### Visibility[​](#visibility "Direct link to Visibility")
250
+
251
+ * **Per-agent skills** (`config/agents/<id>/skills/`) are always visible to that agent.
252
+ * **Global skills** (`config/agents/skills/`, and catalog-volume skills) are **opt-in**: list them in the agent's frontmatter, `skills: [pdf-forms]`. Set `autoInheritSkills: true` (or `{ file, code }`) on the plugin to make every global skill visible without listing — off by default so each agent's always-on catalog stays lean.
253
+
254
+ ### How the agent uses a skill[​](#how-the-agent-uses-a-skill "Direct link to How the agent uses a skill")
255
+
256
+ Two read-only built-in tools are injected into any agent that has a visible catalog:
257
+
258
+ * `load_skill(skill)` — returns the skill's full instructions plus a manifest of its bundled files.
259
+ * `read_skill_file(skill, path)` — returns the contents of one of those bundled files.
260
+
261
+ The model calls `load_skill` on its own when a task matches a skill's description. A **user** can force a specific skill for a turn with the `/skill-name` prefix in chat (or the `send(message, { skill })` option on `useAgentChat`); the skill's instructions are injected into that turn deterministically, and `load_skill` remains available for auto-selection. The client reads the per-agent catalog from the plugin's `clientConfig()` payload to power a picker.
262
+
263
+ ### Catalog skills (Unity Catalog Volume)[​](#catalog-skills-unity-catalog-volume "Direct link to Catalog skills (Unity Catalog Volume)")
264
+
265
+ Point `skillsVolume` (or the `DATABRICKS_VOLUME_AGENT_SKILLS` env var) at a UC Volume laid out the same way — `<volume>/<name>/SKILL.md`. Catalog skills are discovered at boot and on `reload()`, merged into the shared global pool, and read as the **service principal** (`skillCredentialMode` defaults to `"sp"`). They're intended as a shared, curated pool; per-user (OBO) skill volumes are not wired yet. Declaring the optional `volume` resource in the manifest lets the scaffolder grant the SP read access.
266
+
267
+ ### Name collisions[​](#name-collisions "Direct link to Name collisions")
268
+
269
+ Skill names are addressed bare. If two sources provide the same name, each becomes a qualified `<scope>:name` (`agent:`, `bundle:`, `volume:`) and the bare name is rejected as ambiguous with the alternatives listed. Two skills with the same name from the *same* source is a boot-time error.
270
+
271
+ ### v1 caveats[​](#v1-caveats "Direct link to v1 caveats")
272
+
273
+ * **Scripts are not executed.** A skill may reference `scripts/foo.py`; v1 loads prose and reference docs only.
274
+ * **`allowed-tools` is advisory.** It's surfaced as a hint in the loaded skill, not enforced — loading a skill does not restrict the agent's callable tools. It is not a sandbox.
275
+ * **Skill bodies are not per-user access-controlled** (they read as the SP). Keep user-sensitive content out of skill bodies.
276
+
214
277
  ## Level 5: standalone (no `createApp`)[​](#level-5-standalone-no-createapp "Direct link to level-5-standalone-no-createapp")
215
278
 
216
279
  ```ts
@@ -427,6 +490,9 @@ agents({
427
490
  defaultModel?: AgentAdapter | Promise<AgentAdapter> | string,
428
491
  tools?: Record<string, AgentTool>,
429
492
  autoInheritTools?: boolean | { file?: boolean, code?: boolean },
493
+ autoInheritSkills?: boolean | { file?: boolean, code?: boolean }, // default off
494
+ skillsVolume?: string, // UC Volume for catalog skills; falls back to DATABRICKS_VOLUME_AGENT_SKILLS
495
+ skillCredentialMode?: "sp" | "obo", // default "sp" (see Skills)
430
496
  threadStore?: ThreadStore, // default in-memory
431
497
  baseSystemPrompt?: false | string | (ctx: PromptContext) => string,
432
498
  mcp?: {
@@ -628,6 +694,7 @@ appkit.agents.getThreads(userId); // list user's threads
628
694
  | `endpoint` | string | Model serving endpoint name. Shortcut for `model`. |
629
695
  | `model` | string | Same as `endpoint`; either works. |
630
696
  | `tools` | array | Unified tool list. Entries are `plugin:<name>` / `plugin:<name>: [t1, t2]` / `plugin:<name>: { only, except, rename, prefix }` for plugin tools, or a bare `<key>` resolved against `agents({ tools: {...} })` for ambient tools. See "Level 2: scope tools in frontmatter" above for examples. |
697
+ | `skills` | array | Names of global skills (shared `skills/` pool or catalog volume) to make visible to this agent. Per-agent skills under `<id>/skills/` are always visible. See [Skills](#skills). |
631
698
  | `default` | boolean | First agent id (sorted order) with `default: true` becomes the default agent. |
632
699
  | `agents` | array | Sub-agent ids (sibling folders) to delegate to; each becomes an `agent-<id>` tool. Resolves against other markdown and code agents. |
633
700
  | `maxSteps` | number | Adapter max-step hint. |
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@databricks/appkit",
3
3
  "type": "module",
4
- "version": "0.66.1",
4
+ "version": "0.67.0",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
7
7
  "bin": {