netlify-cli 27.11.2 → 27.12.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.
@@ -248,7 +248,7 @@ export default class BaseCommand extends Command {
248
248
  };
249
249
  help.longestSubcommandTermLength = (command) => getCommands(command).reduce((max, cmd) => Math.max(max, cmd.name().length), 0);
250
250
  /** override the longestOptionTermLength to react on hide options flag */
251
- help.longestOptionTermLength = (command, helper) => (command.noBaseOptions === false &&
251
+ help.longestOptionTermLength = (command, helper) => (!command.noBaseOptions &&
252
252
  helper.visibleOptions(command).reduce((max, option) => Math.max(max, helper.optionTerm(option).length), 0)) ||
253
253
  0;
254
254
  help.formatHelp = (command, helper) => {
@@ -284,7 +284,7 @@ export default class BaseCommand extends Command {
284
284
  if (argumentList.length !== 0) {
285
285
  output = [...output, chalk.bold('ARGUMENTS'), formatHelpList(argumentList), ''];
286
286
  }
287
- if (command.#noBaseOptions === false) {
287
+ if (!command.#noBaseOptions) {
288
288
  // Options
289
289
  const optionList = helper
290
290
  .visibleOptions(command)
@@ -91,7 +91,7 @@ export const dev = async (options, command) => {
91
91
  log(`${NETLIFYDEVLOG} Injecting environment variable values for ${chalk.yellow('all scopes')}`);
92
92
  }
93
93
  env = await getDotEnvVariables({ devConfig, env, site });
94
- const { accountId, addonsUrls, capabilities, siteUrl, timeouts } = await getSiteInformation({
94
+ const { accountId, capabilities, siteUrl, timeouts } = await getSiteInformation({
95
95
  // inherited from base command --offline
96
96
  offline: options.offline,
97
97
  api,
@@ -217,7 +217,6 @@ export const dev = async (options, command) => {
217
217
  };
218
218
  const inspectSettings = generateInspectSettings(options.edgeInspect, options.edgeInspectBrk);
219
219
  await startProxyServer({
220
- addonsUrls,
221
220
  aiGatewayContext,
222
221
  api,
223
222
  blobsContext,
@@ -30,7 +30,7 @@ const cloneEnvVars = async ({ api, force, siteFrom, siteTo }) => {
30
30
  const siteId = siteTo.id;
31
31
  // @ts-expect-error TS(7031) FIXME: Binding element 'key' implicitly has an 'any' type... Remove this comment to see the full error message
32
32
  const envVarsToDelete = envelopeTo.filter(({ key }) => keysFrom.includes(key));
33
- if (envVarsToDelete.length !== 0 && Boolean(force) === false) {
33
+ if (envVarsToDelete.length !== 0 && !force) {
34
34
  await promptEnvCloneOverwrite(siteTo.id, envVarsToDelete);
35
35
  }
36
36
  // delete marked env vars in parallel
@@ -34,7 +34,7 @@ const setInEnvelope = async ({ api, context, force, key, scope, secret, siteInfo
34
34
  // @ts-expect-error TS(7006) FIXME: Parameter 'envVar' implicitly has an 'any' type.
35
35
  const existing = envelopeVariables.find((envVar) => envVar.key === key);
36
36
  // Checks if --force is passed and if it is an existing variaible, then we need to prompt the user
37
- if (Boolean(force) === false && existing) {
37
+ if (!force && existing) {
38
38
  await promptOverwriteEnvVariable(key);
39
39
  }
40
40
  const params = { accountId, siteId, key };
@@ -21,7 +21,7 @@ const unsetInEnvelope = async ({ api, context, force, key, siteInfo }) => {
21
21
  // if not, no need to call delete; return early
22
22
  return env;
23
23
  }
24
- if (Boolean(force) === false) {
24
+ if (!force) {
25
25
  await promptOverwriteEnvVariable(key);
26
26
  }
27
27
  const params = { accountId, siteId, key };
@@ -5,13 +5,12 @@ import { createRequire } from 'module';
5
5
  import path, { dirname, join, relative } from 'path';
6
6
  import process from 'process';
7
7
  import { fileURLToPath, pathToFileURL } from 'url';
8
- import { confirm, input, search, select, Separator } from '@inquirer/prompts';
8
+ import { input, search, select, Separator } from '@inquirer/prompts';
9
9
  import { findUp } from 'find-up';
10
10
  import fuzzy from 'fuzzy';
11
11
  import fetch from 'node-fetch';
12
12
  import { createSpinner } from 'nanospinner';
13
13
  import { fileExistsAsync } from '../../lib/fs.js';
14
- import { getAddons, getCurrentAddon, getSiteData } from '../../utils/addons/prepare.js';
15
14
  import { NETLIFYDEVERR, NETLIFYDEVLOG, NETLIFYDEVWARN, chalk, logAndThrowError, log, } from '../../utils/command-helpers.js';
16
15
  import { copyTemplateDir } from '../../utils/copy-template-dir/copy-template-dir.js';
17
16
  import { getDotEnvVariables, injectEnvVariables } from '../../utils/dev.js';
@@ -313,8 +312,7 @@ const downloadFromURL = async function (command, options, argumentName, function
313
312
  // read, execute, and delete function template file if exists
314
313
  const fnTemplateFile = path.join(fnFolder, '.netlify-function-template.mjs');
315
314
  if (await fileExistsAsync(fnTemplateFile)) {
316
- const { default: { addons = [], onComplete }, } = await import(pathToFileURL(fnTemplateFile).href);
317
- await installAddons(command, addons, path.resolve(fnFolder));
315
+ const { default: { onComplete }, } = await import(pathToFileURL(fnTemplateFile).href);
318
316
  await handleOnComplete({ command, onComplete });
319
317
  // delete
320
318
  await unlink(fnTemplateFile);
@@ -400,7 +398,7 @@ const scaffoldFromTemplate = async function (command, options, argumentName, fun
400
398
  log(`${NETLIFYDEVLOG} Open in browser: https://github.com/netlify/cli/issues/new`);
401
399
  }
402
400
  else {
403
- const { addons = [], lang, name: templateName, onComplete } = chosenTemplate;
401
+ const { lang, name: templateName, onComplete } = chosenTemplate;
404
402
  const pathToTemplate = path.join(templatesDir, lang, templateName);
405
403
  if (!fs.existsSync(pathToTemplate)) {
406
404
  throw new Error(`There isn't a corresponding directory to the selected name. Template '${templateName}' is misconfigured`);
@@ -435,33 +433,12 @@ const scaffoldFromTemplate = async function (command, options, argumentName, fun
435
433
  if (funcType === 'edge') {
436
434
  await registerEFInToml(name, command.netlify);
437
435
  }
438
- await installAddons(command, addons, path.resolve(functionPath));
439
436
  await handleOnComplete({ command, onComplete });
440
437
  log();
441
438
  log(chalk.greenBright(`Function created!`));
442
439
  }
443
440
  };
444
441
  const TEMPLATE_PERMISSIONS = 0o777;
445
- // @ts-expect-error TS(7031) FIXME: Binding element 'addonName' implicitly has an 'any... Remove this comment to see the full error message
446
- const createFunctionAddon = async function ({ addonName, addons, api, siteData, siteId }) {
447
- try {
448
- const addon = getCurrentAddon({ addons, addonName });
449
- if (addon && addon.id) {
450
- log(`The "${addonName} add-on" already exists for ${siteData.name}`);
451
- return false;
452
- }
453
- await api.createServiceInstance({
454
- siteId,
455
- addon: addonName,
456
- body: { config: {} },
457
- });
458
- log(`Add-on "${addonName}" created for ${siteData.name}`);
459
- return true;
460
- }
461
- catch (error_) {
462
- return logAndThrowError(error_.message);
463
- }
464
- };
465
442
  /**
466
443
  *
467
444
  * @param {object} config
@@ -481,73 +458,6 @@ const handleOnComplete = async ({ command, onComplete }) => {
481
458
  await onComplete.call(command);
482
459
  }
483
460
  };
484
- /**
485
- *
486
- * @param {object} config
487
- * @param {*} config.addonCreated
488
- * @param {*} config.addonDidInstall
489
- * @param {import('../base-command.js').default} config.command
490
- * @param {string} config.fnPath
491
- */
492
- // @ts-expect-error TS(7031) FIXME: Binding element 'addonCreated' implicitly has an '... Remove this comment to see the full error message
493
- const handleAddonDidInstall = async ({ addonCreated, addonDidInstall, command, fnPath }) => {
494
- const { config } = command.netlify;
495
- if (!addonCreated || !addonDidInstall) {
496
- return;
497
- }
498
- const confirmPostInstall = await confirm({
499
- message: `This template has an optional setup script that runs after addon install. This can be helpful for first time users to try out templates. Run the script?`,
500
- default: false,
501
- });
502
- if (!confirmPostInstall) {
503
- return;
504
- }
505
- await injectEnvVariables({
506
- devConfig: { ...config.dev },
507
- env: command.netlify.cachedConfig.env,
508
- site: command.netlify.site,
509
- });
510
- addonDidInstall(fnPath);
511
- };
512
- /**
513
- *
514
- * @param {import('../base-command.js').default} command
515
- * @param {*} functionAddons
516
- * @param {*} fnPath
517
- * @returns
518
- */
519
- // @ts-expect-error TS(7006) FIXME: Parameter 'command' implicitly has an 'any' type.
520
- const installAddons = async function (command, functionAddons, fnPath) {
521
- if (functionAddons.length === 0) {
522
- return;
523
- }
524
- const { api, site } = command.netlify;
525
- const siteId = site.id;
526
- if (!siteId) {
527
- log('No project id found, please run inside a project directory or `netlify link`');
528
- return false;
529
- }
530
- log(`${NETLIFYDEVLOG} checking Netlify APIs...`);
531
- const [siteData, siteAddons] = await Promise.all([getSiteData({ api, siteId }), getAddons({ api, siteId })]);
532
- // @ts-expect-error TS(7031) FIXME: Binding element 'addonDidInstall' implicitly has a... Remove this comment to see the full error message
533
- const arr = functionAddons.map(async ({ addonDidInstall, addonName }) => {
534
- log(`${NETLIFYDEVLOG} installing addon: ${chalk.yellow.inverse(addonName)}`);
535
- try {
536
- const addonCreated = await createFunctionAddon({
537
- api,
538
- addons: siteAddons,
539
- siteId,
540
- addonName,
541
- siteData,
542
- });
543
- await handleAddonDidInstall({ addonCreated, addonDidInstall, command, fnPath });
544
- }
545
- catch (error_) {
546
- return logAndThrowError(`${NETLIFYDEVERR} Error installing addon: ${error_}`);
547
- }
548
- });
549
- return Promise.all(arr);
550
- };
551
461
  /**
552
462
  *
553
463
  * @param {string} funcName
@@ -4,6 +4,8 @@ export const createInitCommand = (program) => program
4
4
  .description('Configure continuous deployment for a new or existing project. To create a new project without continuous deployment, use `netlify sites:create`')
5
5
  .option('-m, --manual', 'Manually configure a git remote for CI')
6
6
  .option('--git-remote-name <name>', 'Name of Git remote to use. e.g. "origin"')
7
+ .option('--skip-agent-setup', 'Skip installing Netlify skills for AI coding agents into the project')
8
+ .option('--reset-context', 'Replace locally edited Netlify skills with the latest release, and migrate or delete edited copies under renamed or deprecated names')
7
9
  .addHelpText('after', () => {
8
10
  const docsUrl = 'https://docs.netlify.com/cli/get-started/';
9
11
  return `
@@ -12,6 +14,9 @@ For more information about getting started with Netlify CLI, see ${terminalLink(
12
14
  })
13
15
  .action(async (options, command) => {
14
16
  const { init } = await import('./init.js');
15
- await init(options, command);
17
+ await init(options, command, {
18
+ setupAgentSkills: !options.skipAgentSetup,
19
+ resetContext: Boolean(options.resetContext),
20
+ });
16
21
  });
17
22
  //# sourceMappingURL=index.js.map
@@ -6,7 +6,9 @@ type InitExitMessageCustomizer = (code: InitExitCode, defaultMessage: string) =>
6
6
  type InitExtraOptions = {
7
7
  customizeExitMessage?: InitExitMessageCustomizer | undefined;
8
8
  exitAfterConfiguringRepo?: boolean | undefined;
9
+ setupAgentSkills?: boolean | undefined;
10
+ resetContext?: boolean | undefined;
9
11
  };
10
- export declare const init: (options: OptionValues, command: BaseCommand, { customizeExitMessage, exitAfterConfiguringRepo }?: InitExtraOptions) => Promise<SiteInfo>;
12
+ export declare const init: (options: OptionValues, command: BaseCommand, { customizeExitMessage, exitAfterConfiguringRepo, setupAgentSkills: shouldSetupAgentSkills, resetContext, }?: InitExtraOptions) => Promise<SiteInfo>;
11
13
  export {};
12
14
  //# sourceMappingURL=init.d.ts.map
@@ -7,6 +7,7 @@ import { configureRepo } from '../../utils/init/config.js';
7
7
  import { track } from '../../utils/telemetry/index.js';
8
8
  import { link } from '../link/link.js';
9
9
  import { sitesCreate } from '../sites/sites-create.js';
10
+ import { setupAgentSkills } from '../../utils/init/agent-skills.js';
10
11
  import { getBuildSettings, saveNetlifyToml } from '../../utils/init/utils.js';
11
12
  import { LINKED_EXISTING_SITE_EXIT_CODE, LINKED_NEW_SITE_EXIT_CODE } from './constants.js';
12
13
  const persistState = ({ siteInfo, state }) => {
@@ -147,14 +148,33 @@ const logExistingRepoSetupAndExit = ({ repoUrl, siteName, customizeExitMessage,
147
148
  // TODO add support for changing GitHub repo in site:config command
148
149
  exit();
149
150
  };
150
- export const init = async (options, command, { customizeExitMessage, exitAfterConfiguringRepo = false } = {}) => {
151
- command.setAnalyticsPayload({ manual: options.manual, force: options.force });
151
+ const installAgentSkills = async (command, reset) => {
152
+ log();
153
+ const result = await setupAgentSkills({ workingDir: command.netlify.repositoryRoot, reset });
154
+ await track('sites_agentSkillsSetup', {
155
+ installed: result.installed,
156
+ directories: result.directories,
157
+ skillsVersion: result.skillsVersion,
158
+ resetContext: reset,
159
+ ...result.summary,
160
+ });
161
+ };
162
+ export const init = async (options, command, { customizeExitMessage, exitAfterConfiguringRepo = false, setupAgentSkills: shouldSetupAgentSkills = false, resetContext = false, } = {}) => {
163
+ command.setAnalyticsPayload({
164
+ manual: options.manual,
165
+ force: options.force,
166
+ skipAgentSetup: options.skipAgentSetup,
167
+ resetContext: options.resetContext,
168
+ });
152
169
  const { repositoryRoot, state } = command.netlify;
153
170
  const { siteInfo: existingSiteInfo } = command.netlify;
154
171
  // Check logged in status
155
172
  await command.authenticate();
156
173
  // Add .netlify to .gitignore file
157
174
  await ensureNetlifyIgnore(repositoryRoot);
175
+ if (shouldSetupAgentSkills) {
176
+ await installAgentSkills(command, resetContext);
177
+ }
158
178
  const repoUrl = getRepoUrl(existingSiteInfo);
159
179
  if (repoUrl && !options.force) {
160
180
  logExistingAndExit({ siteInfo: existingSiteInfo });
@@ -33,7 +33,7 @@ export const serve = async (options, command) => {
33
33
  log(`${NETLIFYDEVLOG} Injecting environment variable values for ${chalk.yellow('all scopes')}`);
34
34
  }
35
35
  env = await getDotEnvVariables({ devConfig, env, site });
36
- const { accountId, addonsUrls, capabilities, siteUrl, timeouts } = await getSiteInformation({
36
+ const { accountId, capabilities, siteUrl, timeouts } = await getSiteInformation({
37
37
  // inherited from base command --offline
38
38
  offline: options.offline,
39
39
  api,
@@ -145,7 +145,6 @@ export const serve = async (options, command) => {
145
145
  };
146
146
  const inspectSettings = generateInspectSettings(options.edgeInspect, options.edgeInspectBrk);
147
147
  const url = await startProxyServer({
148
- addonsUrls,
149
148
  blobsContext: runtimeBlobsContext,
150
149
  command,
151
150
  config: mergedConfig,
@@ -20,7 +20,7 @@ export const sitesList = async (options, command) => {
20
20
  ssl_url: site.ssl_url,
21
21
  account_name: site.account_name,
22
22
  };
23
- if (site.build_settings && site.build_settings.repo_url) {
23
+ if (site.build_settings?.repo_url) {
24
24
  siteInfo.repo_url = site.build_settings.repo_url;
25
25
  }
26
26
  return siteInfo;
@@ -13,7 +13,7 @@ export const unlink = async (_options, command) => {
13
13
  await track('sites_unlinked', {
14
14
  siteId: siteData.id || siteId,
15
15
  });
16
- if (site && site.configPath) {
16
+ if (site?.configPath) {
17
17
  log(`Unlinked ${site.configPath} from ${siteData ? siteData.name : siteId}`);
18
18
  }
19
19
  else {
@@ -235,7 +235,6 @@ export class EdgeFunctionsRegistryImpl {
235
235
  Object.entries(envConfig).forEach(([key, variable]) => {
236
236
  if (variable.sources.includes('ui') ||
237
237
  variable.sources.includes('account') ||
238
- variable.sources.includes('addons') ||
239
238
  variable.sources.includes('internal') ||
240
239
  variable.sources.some((source) => source.startsWith('.env'))) {
241
240
  env[key] = variable.value;
@@ -105,7 +105,7 @@ export const shouldFetchLatestVersion = async ({ binPath, execArgs, execName, la
105
105
  if (!stdout) {
106
106
  return false;
107
107
  }
108
- const match = stdout.match(new RegExp(pattern));
108
+ const match = new RegExp(pattern).exec(stdout);
109
109
  if (!match) {
110
110
  return false;
111
111
  }
@@ -46,7 +46,8 @@ export const createFormSubmissionHandler = function ({ functionsRegistry, siteUr
46
46
  }
47
47
  const originalUrl = new URL(req.url, 'http://localhost');
48
48
  req.url = `/.netlify/functions/${handlerName}${originalUrl.search}`;
49
- const ct = parseContentType(req);
49
+ // A missing header parses to an empty type and takes the unsupported-type branch below.
50
+ const ct = parseContentType(req.headers['content-type'] ?? '');
50
51
  let fields = {};
51
52
  let files = {};
52
53
  if (ct.type.endsWith('/x-www-form-urlencoded')) {
@@ -73,7 +73,7 @@ export const parseContextFile = (contents) => {
73
73
  const result = {
74
74
  contents,
75
75
  };
76
- const providerContext = contents.match(PROVIDER_CONTEXT_REGEX);
76
+ const providerContext = PROVIDER_CONTEXT_REGEX.exec(contents);
77
77
  if (providerContext) {
78
78
  const [, attributes, innerContents] = providerContext;
79
79
  result.innerContents = innerContents;
@@ -90,7 +90,7 @@ export const parseContextFile = (contents) => {
90
90
  }
91
91
  }
92
92
  }
93
- const contextOverrides = contents.match(PROVIDER_CONTEXT_OVERRIDES_REGEX);
93
+ const contextOverrides = PROVIDER_CONTEXT_OVERRIDES_REGEX.exec(contents);
94
94
  if (contextOverrides) {
95
95
  const [overrideContents, , innerContents] = contextOverrides;
96
96
  result.overrides = {
@@ -22,7 +22,6 @@ interface GetSiteInformationOptions {
22
22
  siteInfo: SiteInfo;
23
23
  }
24
24
  export interface SiteInformationResult {
25
- addonsUrls: Record<string, string>;
26
25
  siteUrl: string;
27
26
  accountId?: string;
28
27
  capabilities: {
package/dist/utils/dev.js CHANGED
@@ -10,10 +10,6 @@ const ENV_VAR_SOURCES = {
10
10
  name: 'shared',
11
11
  printFn: chalk.magenta,
12
12
  },
13
- addons: {
14
- name: 'addon',
15
- printFn: chalk.yellow,
16
- },
17
13
  configFile: {
18
14
  name: 'netlify.toml file',
19
15
  printFn: chalk.green,
@@ -47,25 +43,6 @@ const getAccounts = async ({ api }) => {
47
43
  return logAndThrowError(`Failed retrieving user account: ${error_.message}. ${ERROR_CALL_TO_ACTION}`);
48
44
  }
49
45
  };
50
- // @ts-expect-error TS(7031) FIXME: Binding element 'api' implicitly has an 'any' type... Remove this comment to see the full error message
51
- const getAddons = async ({ api, site }) => {
52
- try {
53
- const addons = await api.listServiceInstancesForSite({ siteId: site.id });
54
- return addons;
55
- }
56
- catch (error_) {
57
- return logAndThrowError(`Failed retrieving addons for site ${chalk.yellow(site.id)}: ${error_.message}. ${ERROR_CALL_TO_ACTION}`);
58
- }
59
- };
60
- // @ts-expect-error TS(7031) FIXME: Binding element 'addons' implicitly has an 'any' t... Remove this comment to see the full error message
61
- const getAddonsInformation = ({ addons, siteInfo }) => {
62
- const urls = Object.fromEntries(
63
- // @ts-expect-error TS(7006) FIXME: Parameter 'addon' implicitly has an 'any' type.
64
- addons.map((addon) => [addon.service_slug, `${siteInfo.ssl_url}${addon.service_path}`]));
65
- // @ts-expect-error TS(7006) FIXME: Parameter 'addon' implicitly has an 'any' type.
66
- const env = Object.assign({}, ...addons.map((addon) => addon.env));
67
- return { urls, env };
68
- };
69
46
  const getSiteAccount = ({ accounts, siteInfo }) => {
70
47
  const siteAccount = accounts.find((account) => account.slug === siteInfo.account_slug);
71
48
  if (!siteAccount) {
@@ -81,11 +58,9 @@ const BACKGROUND_FUNCTION_TIMEOUT = 900;
81
58
  export const getSiteInformation = async ({ api, offline, site, siteInfo, }) => {
82
59
  if (site.id && !offline) {
83
60
  validateSiteInfo({ site, siteInfo });
84
- const [accounts, addons] = await Promise.all([getAccounts({ api }), getAddons({ api, site })]);
85
- const { urls: addonsUrls } = getAddonsInformation({ siteInfo, addons });
61
+ const accounts = await getAccounts({ api });
86
62
  const account = getSiteAccount({ siteInfo, accounts });
87
63
  return {
88
- addonsUrls,
89
64
  siteUrl: siteInfo.ssl_url,
90
65
  accountId: account?.id,
91
66
  capabilities: {
@@ -100,7 +75,6 @@ export const getSiteInformation = async ({ api, offline, site, siteInfo, }) => {
100
75
  }
101
76
  // best defaults we can have without retrieving site information
102
77
  return {
103
- addonsUrls: {},
104
78
  siteUrl: '',
105
79
  capabilities: {
106
80
  aiGatewayDisabled: false,
@@ -180,7 +180,6 @@ export const getEnvelopeEnv = async ({ api, context = 'dev', env, key = '', raw
180
180
  }
181
181
  const generalEnv = filterEnvBySource(env, 'general');
182
182
  const internalEnv = filterEnvBySource(env, 'internal');
183
- const addonsEnv = filterEnvBySource(env, 'addons');
184
183
  const configFileEnv = filterEnvBySource(env, 'configFile');
185
184
  // filter out configFile env vars if a non-configFile scope is passed
186
185
  const includeConfigEnvVars = /any|builds|post[-_]processing/.test(scope);
@@ -188,7 +187,6 @@ export const getEnvelopeEnv = async ({ api, context = 'dev', env, key = '', raw
188
187
  return {
189
188
  ...generalEnv,
190
189
  ...accountEnv,
191
- ...(includeConfigEnvVars ? addonsEnv : {}),
192
190
  ...siteEnv,
193
191
  ...(includeConfigEnvVars ? configFileEnv : {}),
194
192
  ...internalEnv,
@@ -249,7 +247,7 @@ export const translateFromEnvelopeToMongo = (envVars = [], context = 'dev') => e
249
247
  .sort((a, b) => (a.key.toLowerCase() < b.key.toLowerCase() ? -1 : 1))
250
248
  .reduce((acc, cur) => {
251
249
  const envVar = cur.values.find((val) => [context, 'all'].includes((val.context_parameter ?? '') || val.context));
252
- if (envVar && envVar.value) {
250
+ if (envVar?.value) {
253
251
  return {
254
252
  ...acc,
255
253
  [cur.key]: envVar.value,
@@ -11,7 +11,7 @@ const getRepoData = async ({ remoteName, workingDir, }) => {
11
11
  util.promisify(gitconfiglocal)(workingDir),
12
12
  findUp('.git', { cwd: workingDir, type: 'directory' }),
13
13
  ]);
14
- if (!gitDirectory || !gitConfig || !gitConfig.remote || Object.keys(gitConfig.remote).length === 0) {
14
+ if (!gitDirectory || !gitConfig?.remote || Object.keys(gitConfig.remote).length === 0) {
15
15
  throw new Error('No Git remote found');
16
16
  }
17
17
  const baseGitPath = dirname(gitDirectory);
@@ -0,0 +1,96 @@
1
+ export declare const DEFAULT_SKILLS_HOST = "https://netlify-agent-skills.netlify.app";
2
+ export declare const SKILLS_HOST_ENV = "NETLIFY_SKILLS_HOST";
3
+ export declare const DEFAULT_SKILLS_DIRECTORY: string;
4
+ export interface SkillHistoryEntry {
5
+ version: string | null;
6
+ name?: string;
7
+ tree_hash: string;
8
+ }
9
+ export interface ManifestSkill {
10
+ name: string;
11
+ status: 'active' | 'deprecated';
12
+ version: string | null;
13
+ prior_names?: string[];
14
+ description?: string;
15
+ tree_hash: string | null;
16
+ files?: Record<string, string>;
17
+ executable?: string[];
18
+ history?: SkillHistoryEntry[];
19
+ deprecated?: {
20
+ since: string;
21
+ replaced_by?: string;
22
+ };
23
+ }
24
+ export interface SkillsManifest {
25
+ schema_version: number;
26
+ version: string;
27
+ skills: ManifestSkill[];
28
+ }
29
+ export type SkillRecord = {
30
+ name: string;
31
+ status: 'current';
32
+ version: string | null;
33
+ } | {
34
+ name: string;
35
+ status: 'stale';
36
+ version: string | null;
37
+ have: string | null;
38
+ } | {
39
+ name: string;
40
+ status: 'modified';
41
+ version: string | null;
42
+ } | {
43
+ name: string;
44
+ status: 'renamed';
45
+ currentName: string;
46
+ modified: boolean;
47
+ } | {
48
+ name: string;
49
+ status: 'deprecated';
50
+ replacedBy: string | null;
51
+ modified: boolean;
52
+ } | {
53
+ name: string;
54
+ status: 'unknown';
55
+ };
56
+ export interface SkillsClassification {
57
+ skills: SkillRecord[];
58
+ missing: string[];
59
+ }
60
+ export type SkillAction = 'current' | 'added' | 'updated' | 'reset' | 'renamed' | 'removed' | 'kept' | 'ignored' | 'failed';
61
+ export interface SkillActionRecord {
62
+ name: string;
63
+ action: SkillAction;
64
+ detail?: string;
65
+ }
66
+ export interface SkillsSyncResult {
67
+ directory: string;
68
+ actions: SkillActionRecord[];
69
+ }
70
+ export declare const resolveSkillsHost: (env?: NodeJS.ProcessEnv) => string;
71
+ export declare const fetchSkillsManifest: (host: string) => Promise<SkillsManifest>;
72
+ export declare const hashSkillTree: (dir: string, declaredExecutable?: Set<string>) => Promise<string>;
73
+ export declare const classifySkillsDirectory: (root: string, manifest: SkillsManifest) => Promise<SkillsClassification>;
74
+ export declare const installSkill: (host: string, dest: string, skill: ManifestSkill, { force }?: {
75
+ force?: boolean;
76
+ }) => Promise<number>;
77
+ export declare const syncSkills: ({ host, directory, manifest, reset, }: {
78
+ host: string;
79
+ directory: string;
80
+ manifest: SkillsManifest;
81
+ reset?: boolean;
82
+ }) => Promise<SkillsSyncResult>;
83
+ export declare const resolveSkillsDirectories: (workingDir: string, env?: NodeJS.ProcessEnv) => Promise<string[]>;
84
+ export interface AgentSkillsSetupSummary {
85
+ installed: boolean;
86
+ directories: string[];
87
+ skillsVersion?: string;
88
+ summary: Record<SkillAction, number>;
89
+ error?: string;
90
+ }
91
+ export declare const setupAgentSkills: ({ workingDir, env, reset, }: {
92
+ workingDir: string;
93
+ env?: NodeJS.ProcessEnv;
94
+ reset?: boolean;
95
+ }) => Promise<AgentSkillsSetupSummary>;
96
+ //# sourceMappingURL=agent-skills.d.ts.map