@anhlp/alp-core 0.0.0-beta.1
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/LICENSE +202 -0
- package/NOTICE +7 -0
- package/README.md +7 -0
- package/dist/adapter.d.ts +20 -0
- package/dist/adapter.js +30 -0
- package/dist/atomic.d.ts +2 -0
- package/dist/atomic.js +28 -0
- package/dist/backup.d.ts +27 -0
- package/dist/backup.js +582 -0
- package/dist/beads.d.ts +65 -0
- package/dist/beads.js +213 -0
- package/dist/delegation.d.ts +2 -0
- package/dist/delegation.js +32 -0
- package/dist/errors.d.ts +4 -0
- package/dist/errors.js +11 -0
- package/dist/formulas.d.ts +25 -0
- package/dist/formulas.js +201 -0
- package/dist/hook-run.d.ts +44 -0
- package/dist/hook-run.js +60 -0
- package/dist/hooks.d.ts +35 -0
- package/dist/hooks.js +30 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +63 -0
- package/dist/init.d.ts +30 -0
- package/dist/init.js +88 -0
- package/dist/library-edit.d.ts +90 -0
- package/dist/library-edit.js +315 -0
- package/dist/library.d.ts +28 -0
- package/dist/library.js +108 -0
- package/dist/log.d.ts +37 -0
- package/dist/log.js +156 -0
- package/dist/migrations.d.ts +9 -0
- package/dist/migrations.js +95 -0
- package/dist/models.d.ts +11 -0
- package/dist/models.js +44 -0
- package/dist/permissions.d.ts +14 -0
- package/dist/permissions.js +229 -0
- package/dist/profiles.d.ts +27 -0
- package/dist/profiles.js +122 -0
- package/dist/promptsafe.d.ts +1 -0
- package/dist/promptsafe.js +13 -0
- package/dist/providers.d.ts +90 -0
- package/dist/providers.js +101 -0
- package/dist/resolver.d.ts +41 -0
- package/dist/resolver.js +240 -0
- package/dist/safe-path.d.ts +2 -0
- package/dist/safe-path.js +24 -0
- package/dist/shell.d.ts +16 -0
- package/dist/shell.js +22 -0
- package/dist/state-schema.d.ts +9 -0
- package/dist/state-schema.js +54 -0
- package/dist/task-rows.d.ts +47 -0
- package/dist/task-rows.js +45 -0
- package/dist/tasks.d.ts +128 -0
- package/dist/tasks.js +835 -0
- package/dist/teams.d.ts +69 -0
- package/dist/teams.js +159 -0
- package/dist/template-path.d.ts +2 -0
- package/dist/template-path.js +10 -0
- package/dist/trust.d.ts +8 -0
- package/dist/trust.js +47 -0
- package/dist/types.d.ts +51 -0
- package/dist/upgrade.d.ts +10 -0
- package/dist/upgrade.js +200 -0
- package/dist/user-settings.d.ts +6 -0
- package/dist/user-settings.js +72 -0
- package/dist/validation.d.ts +17 -0
- package/dist/validation.js +295 -0
- package/dist/verify.d.ts +15 -0
- package/dist/verify.js +123 -0
- package/dist/workflow.d.ts +14 -0
- package/dist/workflow.js +24 -0
- package/package.json +309 -0
- package/src/adapter.d.ts +20 -0
- package/src/adapter.js +29 -0
- package/src/atomic.d.ts +2 -0
- package/src/atomic.js +28 -0
- package/src/backup.d.ts +27 -0
- package/src/backup.js +570 -0
- package/src/beads.js +236 -0
- package/src/delegation.js +28 -0
- package/src/errors.js +7 -0
- package/src/formulas.d.ts +25 -0
- package/src/formulas.js +202 -0
- package/src/hook-run.js +57 -0
- package/src/hooks.js +36 -0
- package/src/index.js +31 -0
- package/src/init.js +92 -0
- package/src/library-edit.js +346 -0
- package/src/library.js +117 -0
- package/src/log.d.ts +37 -0
- package/src/log.js +177 -0
- package/src/migrations.d.ts +9 -0
- package/src/migrations.js +111 -0
- package/src/models.js +45 -0
- package/src/permissions.d.ts +14 -0
- package/src/permissions.js +242 -0
- package/src/profiles.js +139 -0
- package/src/promptsafe.d.ts +1 -0
- package/src/promptsafe.js +19 -0
- package/src/providers.js +122 -0
- package/src/resolver.js +238 -0
- package/src/safe-path.js +20 -0
- package/src/shell.js +28 -0
- package/src/state-schema.d.ts +9 -0
- package/src/state-schema.js +60 -0
- package/src/task-rows.js +44 -0
- package/src/tasks.d.ts +128 -0
- package/src/tasks.js +881 -0
- package/src/teams.js +170 -0
- package/src/template-path.js +8 -0
- package/src/trust.js +59 -0
- package/src/types.d.ts +51 -0
- package/src/upgrade.js +195 -0
- package/src/user-settings.d.ts +6 -0
- package/src/user-settings.js +76 -0
- package/src/validation.d.ts +17 -0
- package/src/validation.js +302 -0
- package/src/verify.d.ts +15 -0
- package/src/verify.js +127 -0
- package/src/workflow.js +28 -0
- package/templates/ALP.md +52 -0
- package/templates/agents/lead/AGENT.md +90 -0
- package/templates/agents/main/AGENT.md +150 -0
- package/templates/agents/oracle/AGENT.md +44 -0
- package/templates/agents/peer/AGENT.md +55 -0
- package/templates/agents/reviewer/AGENT.md +80 -0
- package/templates/agents/supervisor/AGENT.md +72 -0
- package/templates/providers/antigravity.json +6 -0
- package/templates/providers/opencode.json +7 -0
- package/templates/role-skills.json +8 -0
- package/templates/skills/bug-loop/SKILL.md +44 -0
- package/templates/skills/bug-loop/references/test-proof.md +25 -0
- package/templates/skills/goal-griller/SKILL.md +49 -0
- package/templates/skills/prompt-leverage/SKILL.md +37 -0
- package/templates/skills/prompt-leverage/references/brief-template.md +27 -0
- package/templates/skills/sequence-execution-plan/SKILL.md +42 -0
- package/templates/skills/sequence-execution-plan/references/plan-template.md +22 -0
- package/templates/skills/smart-commits/SKILL.md +40 -0
- package/templates/skills/xia/SKILL.md +42 -0
- package/templates/teams/cafe/HOUSE_RULES.md +1 -0
- package/templates/teams/cafe/team.json +15 -0
- package/templates/teams/pho/HOUSE_RULES.md +1 -0
- package/templates/teams/pho/team.json +14 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export * as adapter from "./adapter.js";
|
|
2
|
+
export * as atomic from "./atomic.js";
|
|
3
|
+
export * as backup from "./backup.js";
|
|
4
|
+
export * as beads from "./beads.js";
|
|
5
|
+
export * as delegation from "./delegation.js";
|
|
6
|
+
export * as errors from "./errors.js";
|
|
7
|
+
export * as formulas from "./formulas.js";
|
|
8
|
+
export * as hook_run from "./hook-run.js";
|
|
9
|
+
export * as hooks from "./hooks.js";
|
|
10
|
+
export * as init from "./init.js";
|
|
11
|
+
export * as library_edit from "./library-edit.js";
|
|
12
|
+
export * as library from "./library.js";
|
|
13
|
+
export * as log from "./log.js";
|
|
14
|
+
export * as migrations from "./migrations.js";
|
|
15
|
+
export * as models from "./models.js";
|
|
16
|
+
export * as permissions from "./permissions.js";
|
|
17
|
+
export * as profiles from "./profiles.js";
|
|
18
|
+
export * as promptsafe from "./promptsafe.js";
|
|
19
|
+
export * as providers from "./providers.js";
|
|
20
|
+
export * as resolver from "./resolver.js";
|
|
21
|
+
export * as state_schema from "./state-schema.js";
|
|
22
|
+
export * as task_rows from "./task-rows.js";
|
|
23
|
+
export * as tasks from "./tasks.js";
|
|
24
|
+
export * as teams from "./teams.js";
|
|
25
|
+
export * as trust from "./trust.js";
|
|
26
|
+
export * as upgrade from "./upgrade.js";
|
|
27
|
+
export * as user_settings from "./user-settings.js";
|
|
28
|
+
export * as validation from "./validation.js";
|
|
29
|
+
export * as verify from "./verify.js";
|
|
30
|
+
export * as workflow from "./workflow.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// packages/core/src/index.js
|
|
2
|
+
import * as adapter from "./adapter.js";
|
|
3
|
+
import * as atomic from "./atomic.js";
|
|
4
|
+
import * as backup from "./backup.js";
|
|
5
|
+
import * as beads from "./beads.js";
|
|
6
|
+
import * as delegation from "./delegation.js";
|
|
7
|
+
import * as errors from "./errors.js";
|
|
8
|
+
import * as formulas from "./formulas.js";
|
|
9
|
+
import * as hook_run from "./hook-run.js";
|
|
10
|
+
import * as hooks from "./hooks.js";
|
|
11
|
+
import * as init from "./init.js";
|
|
12
|
+
import * as library_edit from "./library-edit.js";
|
|
13
|
+
import * as library from "./library.js";
|
|
14
|
+
import * as log from "./log.js";
|
|
15
|
+
import * as migrations from "./migrations.js";
|
|
16
|
+
import * as models from "./models.js";
|
|
17
|
+
import * as permissions from "./permissions.js";
|
|
18
|
+
import * as profiles from "./profiles.js";
|
|
19
|
+
import * as promptsafe from "./promptsafe.js";
|
|
20
|
+
import * as providers from "./providers.js";
|
|
21
|
+
import * as resolver from "./resolver.js";
|
|
22
|
+
import * as state_schema from "./state-schema.js";
|
|
23
|
+
import * as task_rows from "./task-rows.js";
|
|
24
|
+
import * as tasks from "./tasks.js";
|
|
25
|
+
import * as teams from "./teams.js";
|
|
26
|
+
import * as trust from "./trust.js";
|
|
27
|
+
import * as upgrade from "./upgrade.js";
|
|
28
|
+
import * as user_settings from "./user-settings.js";
|
|
29
|
+
import * as validation from "./validation.js";
|
|
30
|
+
import * as verify from "./verify.js";
|
|
31
|
+
import * as workflow from "./workflow.js";
|
|
32
|
+
export {
|
|
33
|
+
adapter,
|
|
34
|
+
atomic,
|
|
35
|
+
backup,
|
|
36
|
+
beads,
|
|
37
|
+
delegation,
|
|
38
|
+
errors,
|
|
39
|
+
formulas,
|
|
40
|
+
hook_run,
|
|
41
|
+
hooks,
|
|
42
|
+
init,
|
|
43
|
+
library,
|
|
44
|
+
library_edit,
|
|
45
|
+
log,
|
|
46
|
+
migrations,
|
|
47
|
+
models,
|
|
48
|
+
permissions,
|
|
49
|
+
profiles,
|
|
50
|
+
promptsafe,
|
|
51
|
+
providers,
|
|
52
|
+
resolver,
|
|
53
|
+
state_schema,
|
|
54
|
+
task_rows,
|
|
55
|
+
tasks,
|
|
56
|
+
teams,
|
|
57
|
+
trust,
|
|
58
|
+
upgrade,
|
|
59
|
+
user_settings,
|
|
60
|
+
validation,
|
|
61
|
+
verify,
|
|
62
|
+
workflow
|
|
63
|
+
};
|
package/dist/init.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fill missing scaffold files without replacing existing user content: ALP.md and
|
|
3
|
+
* .alp/settings.json. Agents, skills, MCP servers and hooks come from ALP's built-ins
|
|
4
|
+
* and the user's library (ALPD §41); a project adds its own under .alp/ only to
|
|
5
|
+
* override them.
|
|
6
|
+
* @param {string} projectRoot
|
|
7
|
+
* @param {{ templateRoot?: URL, templates?: Record<string, string>, library?: string, syncProfiles?: boolean, onlyManagedProfiles?: boolean, teamIds?: string[] }} [options]
|
|
8
|
+
*/
|
|
9
|
+
export function initProject(projectRoot: string, { templateRoot, templates, library, syncProfiles, onlyManagedProfiles, teamIds }?: {
|
|
10
|
+
templateRoot?: URL;
|
|
11
|
+
templates?: Record<string, string>;
|
|
12
|
+
library?: string;
|
|
13
|
+
syncProfiles?: boolean;
|
|
14
|
+
onlyManagedProfiles?: boolean;
|
|
15
|
+
teamIds?: string[];
|
|
16
|
+
}): Promise<{
|
|
17
|
+
updated?: string[] | undefined;
|
|
18
|
+
created: string[];
|
|
19
|
+
preserved: string[];
|
|
20
|
+
}>;
|
|
21
|
+
/** Refresh an existing project's settings only; a template Save must not scaffold unrelated folders.
|
|
22
|
+
* @param {string} projectRoot
|
|
23
|
+
* @param {{ library?: string, templates?: Record<string, string>, teamIds?: string[], replaceModels?: boolean }} [options]
|
|
24
|
+
*/
|
|
25
|
+
export function syncProjectSettings(projectRoot: string, { library, templates, teamIds, replaceModels }?: {
|
|
26
|
+
library?: string;
|
|
27
|
+
templates?: Record<string, string>;
|
|
28
|
+
teamIds?: string[];
|
|
29
|
+
replaceModels?: boolean;
|
|
30
|
+
}): Promise<boolean>;
|
package/dist/init.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// packages/core/src/init.js
|
|
2
|
+
import { shippedTemplateRoot } from "./template-path.js";
|
|
3
|
+
import { mkdir, writeFile, lstat, readFile, rename, rm } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
|
+
import { projectProfiles, profileDefaults, syncTemplateProfiles } from "./profiles.js";
|
|
7
|
+
import { validateSettings } from "./validation.js";
|
|
8
|
+
async function initProject(projectRoot, { templateRoot, templates, library, syncProfiles = true, onlyManagedProfiles = false, teamIds } = {}) {
|
|
9
|
+
const root = path.resolve(projectRoot);
|
|
10
|
+
const sourceRoot = () => templateRoot ?? shippedTemplateRoot();
|
|
11
|
+
const template = (name) => templates ? Promise.resolve(templates[name]) : readFile(new URL(name, sourceRoot()), "utf8");
|
|
12
|
+
const scaffold = path.join(root, ".alp");
|
|
13
|
+
const existing = await lstat(scaffold).catch((error) => {
|
|
14
|
+
if (error.code !== "ENOENT") throw error;
|
|
15
|
+
});
|
|
16
|
+
if (existing && !existing.isDirectory()) throw new Error(`Expected a directory: ${scaffold}`);
|
|
17
|
+
const team = await projectProfiles({ root, templates, library, teamIds });
|
|
18
|
+
const files = {
|
|
19
|
+
"ALP.md": await template("ALP.md"),
|
|
20
|
+
".alp/settings.json": JSON.stringify({ defaultAgent: "main", workflow: { mode: "pho" }, team, templateDefaults: profileDefaults(team) }, null, 2) + "\n"
|
|
21
|
+
};
|
|
22
|
+
const directories = [".alp"];
|
|
23
|
+
const created = [];
|
|
24
|
+
const preserved = [];
|
|
25
|
+
const updated = [];
|
|
26
|
+
await mkdir(root, { recursive: true });
|
|
27
|
+
for (const directory of directories) {
|
|
28
|
+
const target = path.join(root, directory);
|
|
29
|
+
try {
|
|
30
|
+
await mkdir(target);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
if (error.code !== "EEXIST") throw error;
|
|
33
|
+
if (!(await lstat(target)).isDirectory()) throw new Error(`Expected a directory: ${target}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
for (const [name, content] of Object.entries(files)) {
|
|
37
|
+
const target = path.join(root, name);
|
|
38
|
+
try {
|
|
39
|
+
await writeFile(target, content, { flag: "wx" });
|
|
40
|
+
created.push(name);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
if (error.code !== "EEXIST") throw error;
|
|
43
|
+
if (!(await lstat(target)).isFile()) throw new Error(`Expected a regular file: ${target}`);
|
|
44
|
+
preserved.push(name);
|
|
45
|
+
if (name === ".alp/settings.json" && syncProfiles) {
|
|
46
|
+
const before = await readFile(target, "utf8");
|
|
47
|
+
const settings = JSON.parse(before);
|
|
48
|
+
validateSettings(settings, target);
|
|
49
|
+
const next = !onlyManagedProfiles || settings.team || settings.profile || settings.templateDefaults ? await syncTemplateProfiles(settings, { root, library, templates, teamIds }) : settings;
|
|
50
|
+
validateSettings(next, target);
|
|
51
|
+
if (JSON.stringify(next) !== JSON.stringify(settings)) {
|
|
52
|
+
if (await readFile(target, "utf8") !== before) throw new Error(`File changed during initialization: ${target}`);
|
|
53
|
+
await writeFile(target, JSON.stringify(next, null, 2) + "\n");
|
|
54
|
+
updated.push(name);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return { created, preserved, ...updated.length ? { updated } : {} };
|
|
60
|
+
}
|
|
61
|
+
async function syncProjectSettings(projectRoot, { library, templates, teamIds, replaceModels = false } = {}) {
|
|
62
|
+
const root = path.resolve(projectRoot);
|
|
63
|
+
const file = path.join(root, ".alp/settings.json");
|
|
64
|
+
const info = await lstat(file).catch((error) => {
|
|
65
|
+
if (error.code !== "ENOENT") throw error;
|
|
66
|
+
});
|
|
67
|
+
if (!info) return false;
|
|
68
|
+
if (!info.isFile()) throw new Error(`Expected a regular file: ${file}`);
|
|
69
|
+
const before = await readFile(file, "utf8");
|
|
70
|
+
const settings = JSON.parse(before);
|
|
71
|
+
validateSettings(settings, file);
|
|
72
|
+
const next = await syncTemplateProfiles(settings, { root, library, templates, teamIds, replaceModels });
|
|
73
|
+
validateSettings(next, file);
|
|
74
|
+
if (JSON.stringify(settings) === JSON.stringify(next)) return false;
|
|
75
|
+
const temporary = `${file}.${randomUUID()}.tmp`;
|
|
76
|
+
try {
|
|
77
|
+
await writeFile(temporary, JSON.stringify(next, null, 2) + "\n", { flag: "wx", mode: info.mode });
|
|
78
|
+
if (await readFile(file, "utf8") !== before) throw new Error(`Settings changed while saving the template: ${file}`);
|
|
79
|
+
await rename(temporary, file);
|
|
80
|
+
} finally {
|
|
81
|
+
await rm(temporary, { force: true });
|
|
82
|
+
}
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
export {
|
|
86
|
+
initProject,
|
|
87
|
+
syncProjectSettings
|
|
88
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One entry: the version that applies (the project's, else the library's, else the
|
|
3
|
+
* built-in), or one scope's version with `scope`. `revision` goes back with a save.
|
|
4
|
+
* @param {'agents' | 'skills' | 'mcp' | 'hooks' | 'teams' | 'providers'} kind
|
|
5
|
+
* @param {string} name
|
|
6
|
+
* @param {{ root?: string, library?: string, templates?: Record<string, string>, scope?: 'library' | 'project' }} [options]
|
|
7
|
+
* @returns {Promise<{ kind: 'agents' | 'skills' | 'mcp' | 'hooks' | 'teams' | 'providers', name: string, source: 'builtin' | 'library' | 'project', overrides?: 'builtin' | 'library' | 'project', content: Record<string, unknown>, revision: string | null, usedBy: string[] }>}
|
|
8
|
+
*/
|
|
9
|
+
export function getEntry(kind: "agents" | "skills" | "mcp" | "hooks" | "teams" | "providers", name: string, { root, library, templates, scope }?: {
|
|
10
|
+
root?: string;
|
|
11
|
+
library?: string;
|
|
12
|
+
templates?: Record<string, string>;
|
|
13
|
+
scope?: "library" | "project";
|
|
14
|
+
}): Promise<{
|
|
15
|
+
kind: "agents" | "skills" | "mcp" | "hooks" | "teams" | "providers";
|
|
16
|
+
name: string;
|
|
17
|
+
source: "builtin" | "library" | "project";
|
|
18
|
+
overrides?: "builtin" | "library" | "project";
|
|
19
|
+
content: Record<string, unknown>;
|
|
20
|
+
revision: string | null;
|
|
21
|
+
usedBy: string[];
|
|
22
|
+
}>;
|
|
23
|
+
/** The skills the library gives `agent`, as role-skills.json lists them. */
|
|
24
|
+
export function givenSkills(library: any, agent: any): Promise<string[]>;
|
|
25
|
+
/**
|
|
26
|
+
* Sets the skills the library gives an agent (role-skills.json), so a built-in agent's
|
|
27
|
+
* skills change without copying its instructions. Every skill must be in the library.
|
|
28
|
+
* @returns {Promise<{ skills: string[] }>}
|
|
29
|
+
*/
|
|
30
|
+
export function setGivenSkills(agent: any, skills: any, { library }?: {}): Promise<{
|
|
31
|
+
skills: string[];
|
|
32
|
+
}>;
|
|
33
|
+
/** Every entry of a kind with its source and users, as `alp <kind>` lists them. */
|
|
34
|
+
export function listEntries(kind: any, { root, library, templates }?: {}): Promise<{
|
|
35
|
+
name: string;
|
|
36
|
+
source: "builtin" | "library" | "project";
|
|
37
|
+
path?: string;
|
|
38
|
+
overrides?: "builtin" | "library";
|
|
39
|
+
description?: string;
|
|
40
|
+
usedBy?: string[];
|
|
41
|
+
}[] | {
|
|
42
|
+
usedBy: any[];
|
|
43
|
+
path?: string | undefined;
|
|
44
|
+
overrides?: "library" | "builtin" | undefined;
|
|
45
|
+
name: string;
|
|
46
|
+
source: "library" | "project" | "builtin";
|
|
47
|
+
}[]>;
|
|
48
|
+
/** Who refers to an entry: agents naming it, teams with it as a member, settings and role-skills.json. */
|
|
49
|
+
export function usersOf(kind: any, name: any, { root, library, templates }?: {}): Promise<any[]>;
|
|
50
|
+
/**
|
|
51
|
+
* Creates or replaces an entry in a scope. `revision` is what getEntry returned for
|
|
52
|
+
* that scope (null to create); a different current revision refuses the save.
|
|
53
|
+
* @returns {Promise<{ revision: string | null }>}
|
|
54
|
+
*/
|
|
55
|
+
export function saveEntry(kind: any, name: any, content: any, { scope, root, library, templates, revision }?: {}): Promise<{
|
|
56
|
+
revision: string | null;
|
|
57
|
+
}>;
|
|
58
|
+
/**
|
|
59
|
+
* Removes an entry from a scope. When nothing else of that name is left below it, an
|
|
60
|
+
* entry others refer to stays, and the error lists them.
|
|
61
|
+
* @returns {Promise<{ removed: boolean, now?: 'builtin' | 'library' }>}
|
|
62
|
+
*/
|
|
63
|
+
export function deleteEntry(kind: any, name: any, { scope, root, library, templates, revision }?: {}): Promise<{
|
|
64
|
+
removed: boolean;
|
|
65
|
+
now?: "builtin" | "library";
|
|
66
|
+
}>;
|
|
67
|
+
/** Copies the entry that applies under `from` to a new entry `to` in a scope; built-ins are copied this way. */
|
|
68
|
+
export function duplicateEntry(kind: any, from: any, to: any, { scope, root, library, templates }?: {}): Promise<{
|
|
69
|
+
revision: string | null;
|
|
70
|
+
}>;
|
|
71
|
+
/** Renames an entry within a scope; refused while others refer to it by its old name. */
|
|
72
|
+
export function renameEntry(kind: any, from: any, to: any, { scope, root, library, templates }?: {}): Promise<{
|
|
73
|
+
revision: string | null;
|
|
74
|
+
}>;
|
|
75
|
+
/**
|
|
76
|
+
* Editing the library (ALPD §43): agents, skills, MCP servers, hooks and teams, in the
|
|
77
|
+
* user's library (ALP_HOME) or as a project's override (.alp/). Built-ins are never
|
|
78
|
+
* written; duplicate one, or save an entry of its name, to change it. Every save is
|
|
79
|
+
* checked first and written file by file through a temporary file and a rename; a
|
|
80
|
+
* revision taken when the entry was read refuses a save over someone else's change.
|
|
81
|
+
*
|
|
82
|
+
* An entry's content, by kind:
|
|
83
|
+
* agents { instructions, config? } AGENT.md and agent.json
|
|
84
|
+
* skills { body } SKILL.md
|
|
85
|
+
* mcp { server } mcp/<name>.json
|
|
86
|
+
* hooks { hook } hooks/<name>.json
|
|
87
|
+
* teams { team, houseRules? } team.json and HOUSE_RULES.md
|
|
88
|
+
* providers { provider } providers/<name>.json, in the library only (ALPD §46)
|
|
89
|
+
*/
|
|
90
|
+
export const EDIT_KINDS: string[];
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
// packages/core/src/library-edit.js
|
|
2
|
+
import { mkdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { moveFile } from "./atomic.js";
|
|
6
|
+
import { AlpError } from "./errors.js";
|
|
7
|
+
import { assertNoSymlinks } from "./safe-path.js";
|
|
8
|
+
import { agentSources, builtinText, jsonObject, libraryEntries, optionalText } from "./resolver.js";
|
|
9
|
+
import { teamSources, validateTeam } from "./teams.js";
|
|
10
|
+
import { normalizeMcp, validateAgentConfig, validateHook } from "./validation.js";
|
|
11
|
+
import { validateProvider } from "./providers.js";
|
|
12
|
+
var EDIT_KINDS = ["agents", "skills", "mcp", "hooks", "teams", "providers"];
|
|
13
|
+
var validName = (name) => typeof name === "string" && /^[\w.-]+$/.test(name) && name !== "." && name !== "..";
|
|
14
|
+
var object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
15
|
+
var fail = (code, message) => {
|
|
16
|
+
throw new AlpError(code, message);
|
|
17
|
+
};
|
|
18
|
+
var FILES = {
|
|
19
|
+
agents: (name) => [[`${name}/AGENT.md`, "instructions", "text"], [`${name}/agent.json`, "config", "json"]],
|
|
20
|
+
skills: (name) => [[`${name}/SKILL.md`, "body", "text"]],
|
|
21
|
+
mcp: (name) => [[`${name}.json`, "server", "json"]],
|
|
22
|
+
hooks: (name) => [[`${name}.json`, "hook", "json"]],
|
|
23
|
+
teams: (name) => [[`${name}/team.json`, "team", "json"], [`${name}/HOUSE_RULES.md`, "houseRules", "text"]],
|
|
24
|
+
providers: (name) => [[`${name}.json`, "provider", "json"]]
|
|
25
|
+
};
|
|
26
|
+
var ownDirectory = (kind) => ["agents", "skills", "teams"].includes(kind);
|
|
27
|
+
function kindOf(kind) {
|
|
28
|
+
if (!EDIT_KINDS.includes(kind)) fail("INVALID_KIND", `Unknown kind '${kind}'; use one of ${EDIT_KINDS.join(", ")}`);
|
|
29
|
+
return kind;
|
|
30
|
+
}
|
|
31
|
+
function nameOf(name) {
|
|
32
|
+
if (!validName(name)) fail("INVALID_NAME", `'${name}' is not a valid name: use letters, digits, '.', '_' or '-'`);
|
|
33
|
+
return name;
|
|
34
|
+
}
|
|
35
|
+
function base(scope, { root, library }, kind) {
|
|
36
|
+
if (kind === "providers" && scope !== "library") fail("INVALID_SCOPE", "Providers live only in your library");
|
|
37
|
+
if (scope === "library") return library ?? fail("INVALID_SCOPE", "No library: ALP_HOME is not set");
|
|
38
|
+
if (scope === "project") return root ? path.join(path.resolve(root), ".alp") : fail("INVALID_SCOPE", "No project directory");
|
|
39
|
+
return fail("INVALID_SCOPE", `Scope must be library or project, not '${scope}'`);
|
|
40
|
+
}
|
|
41
|
+
var revisionOf = (content) => content === void 0 ? null : createHash("sha256").update(JSON.stringify(content)).digest("hex").slice(0, 16);
|
|
42
|
+
async function safeEntryPaths(directory, kind, name) {
|
|
43
|
+
for (const [file] of FILES[kind](name)) {
|
|
44
|
+
await assertNoSymlinks(directory, path.join(kind, file));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async function readLayer(kind, name, directory) {
|
|
48
|
+
await safeEntryPaths(directory, kind, name);
|
|
49
|
+
const content = {};
|
|
50
|
+
let found = false;
|
|
51
|
+
for (const [file, key, type] of FILES[kind](name)) {
|
|
52
|
+
const target = path.join(directory, kind, file);
|
|
53
|
+
const text = await optionalText(target);
|
|
54
|
+
if (text === void 0) continue;
|
|
55
|
+
found = true;
|
|
56
|
+
if (type === "text") {
|
|
57
|
+
content[key] = text;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
content[key] = JSON.parse(text);
|
|
62
|
+
} catch (cause) {
|
|
63
|
+
throw new AlpError("INVALID_ENTRY", `${target}: ${cause.message}`, { cause });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const main = { agents: "instructions", skills: "body", teams: "team" }[kind];
|
|
67
|
+
return found && (!main || content[main] !== void 0) ? content : void 0;
|
|
68
|
+
}
|
|
69
|
+
async function readBuiltin(kind, name, templates) {
|
|
70
|
+
if (kind === "agents") {
|
|
71
|
+
const instructions = await builtinText(`agents/${name}/AGENT.md`, templates);
|
|
72
|
+
if (instructions === void 0) return void 0;
|
|
73
|
+
const config = await builtinText(`agents/${name}/agent.json`, templates);
|
|
74
|
+
return { instructions, ...config !== void 0 ? { config: JSON.parse(config) } : {} };
|
|
75
|
+
}
|
|
76
|
+
if (kind === "teams") {
|
|
77
|
+
const team = await builtinText(`teams/${name}/team.json`, templates);
|
|
78
|
+
if (team === void 0) return void 0;
|
|
79
|
+
return { team: JSON.parse(team), houseRules: await builtinText(`teams/${name}/HOUSE_RULES.md`, templates) ?? "" };
|
|
80
|
+
}
|
|
81
|
+
return void 0;
|
|
82
|
+
}
|
|
83
|
+
async function layers(kind, name, { root, library, templates }) {
|
|
84
|
+
return {
|
|
85
|
+
builtin: await readBuiltin(kind, name, templates),
|
|
86
|
+
library: library ? await readLayer(kind, name, library) : void 0,
|
|
87
|
+
project: root && kind !== "providers" ? await readLayer(kind, name, path.join(path.resolve(root), ".alp")) : void 0
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
async function getEntry(kind, name, { root, library, templates, scope } = {}) {
|
|
91
|
+
kindOf(kind);
|
|
92
|
+
nameOf(name);
|
|
93
|
+
const found = await layers(kind, name, { root, library, templates });
|
|
94
|
+
const order = scope ? [scope] : ["project", "library", "builtin"];
|
|
95
|
+
const source = order.find((layer) => found[layer] !== void 0);
|
|
96
|
+
if (!source) fail("NOT_FOUND", `No ${kind} entry '${name}'${scope ? ` in the ${scope}` : ""}`);
|
|
97
|
+
const below = ["builtin", "library", "project"].slice(0, ["builtin", "library", "project"].indexOf(source)).reverse().find((layer) => found[layer] !== void 0);
|
|
98
|
+
return {
|
|
99
|
+
kind,
|
|
100
|
+
name,
|
|
101
|
+
source: (
|
|
102
|
+
/** @type {'builtin' | 'library' | 'project'} */
|
|
103
|
+
source
|
|
104
|
+
),
|
|
105
|
+
...below ? { overrides: (
|
|
106
|
+
/** @type {'builtin' | 'library'} */
|
|
107
|
+
below
|
|
108
|
+
) } : {},
|
|
109
|
+
content: found[source],
|
|
110
|
+
revision: revisionOf(found[source]),
|
|
111
|
+
usedBy: await usersOf(kind, name, { root, library, templates }),
|
|
112
|
+
// The skills the library gives an agent by name, whatever layer defines it (ALPD §49).
|
|
113
|
+
...kind === "agents" && library ? { librarySkills: await givenSkills(library, name) } : {}
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
async function readGiven(library) {
|
|
117
|
+
const file = path.join(library, "role-skills.json");
|
|
118
|
+
const text = await optionalText(file);
|
|
119
|
+
if (text === void 0) return { file, roles: {} };
|
|
120
|
+
let roles;
|
|
121
|
+
try {
|
|
122
|
+
roles = JSON.parse(text);
|
|
123
|
+
} catch (cause) {
|
|
124
|
+
throw new AlpError("INVALID_LIBRARY", `${file}: ${cause.message}`, { cause });
|
|
125
|
+
}
|
|
126
|
+
if (!object(roles)) fail("INVALID_LIBRARY", `${file}: expected an object of agent names to skill lists`);
|
|
127
|
+
return { file, roles };
|
|
128
|
+
}
|
|
129
|
+
async function givenSkills(library, agent) {
|
|
130
|
+
const { roles } = await readGiven(library);
|
|
131
|
+
return Array.isArray(roles[agent]) ? roles[agent].filter((name) => typeof name === "string") : [];
|
|
132
|
+
}
|
|
133
|
+
async function setGivenSkills(agent, skills, { library } = {}) {
|
|
134
|
+
nameOf(agent);
|
|
135
|
+
if (!library) fail("INVALID_SCOPE", "No library: ALP_HOME is not set");
|
|
136
|
+
if (!Array.isArray(skills) || !skills.every(validName)) fail("INVALID_ENTRY", "Skills must be a list of skill names");
|
|
137
|
+
const unique = [...new Set(skills)];
|
|
138
|
+
for (const skill of unique) if (!await exists(path.join(library, "skills", skill, "SKILL.md"))) fail("NOT_FOUND", `No skill '${skill}' in the library`);
|
|
139
|
+
const { file, roles } = await readGiven(library);
|
|
140
|
+
if (unique.length) roles[agent] = unique;
|
|
141
|
+
else delete roles[agent];
|
|
142
|
+
await writeAtomic(file, JSON.stringify(roles, null, 2) + "\n");
|
|
143
|
+
return { skills: unique };
|
|
144
|
+
}
|
|
145
|
+
async function listEntries(kind, { root, library, templates } = {}) {
|
|
146
|
+
kindOf(kind);
|
|
147
|
+
if (kind !== "teams") return libraryEntries(kind, root, { library, templates });
|
|
148
|
+
const rows = [];
|
|
149
|
+
for (const [name, source] of await teamSources(root, { library, templates })) rows.push({ name, source: source.source, ...source.overrides ? { overrides: source.overrides } : {}, ...source.directory ? { path: source.directory } : {}, usedBy: await usersOf(kind, name, { root, library, templates }) });
|
|
150
|
+
return rows;
|
|
151
|
+
}
|
|
152
|
+
async function usersOf(kind, name, { root, library, templates } = {}) {
|
|
153
|
+
const users = /* @__PURE__ */ new Set();
|
|
154
|
+
if (kind === "providers" && root) {
|
|
155
|
+
const settings = await jsonObject(path.join(path.resolve(root), ".alp", "settings.json"), "INVALID_SETTINGS").catch(() => ({}));
|
|
156
|
+
if (settings.runtime?.provider === name) users.add("project settings");
|
|
157
|
+
}
|
|
158
|
+
if (kind === "teams") {
|
|
159
|
+
if (root) {
|
|
160
|
+
const settings = await jsonObject(path.join(path.resolve(root), ".alp", "settings.json"), "INVALID_SETTINGS").catch(() => ({}));
|
|
161
|
+
if (settings.workflow?.mode === name) users.add("project settings");
|
|
162
|
+
}
|
|
163
|
+
return [...users];
|
|
164
|
+
}
|
|
165
|
+
if (kind === "agents") {
|
|
166
|
+
for (const [team, source] of await teamSources(root, { library, templates })) {
|
|
167
|
+
const text = source.directory ? await optionalText(path.join(source.directory, "team.json")) : await builtinText(`teams/${team}/team.json`, templates);
|
|
168
|
+
try {
|
|
169
|
+
const config = JSON.parse(text ?? "{}");
|
|
170
|
+
if (config.main === name || Object.hasOwn(config.members ?? {}, name) || config.supervisor?.agent === name) users.add(`team ${team}`);
|
|
171
|
+
} catch {
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (root) {
|
|
175
|
+
const settings = await jsonObject(path.join(path.resolve(root), ".alp", "settings.json"), "INVALID_SETTINGS").catch(() => ({}));
|
|
176
|
+
if (settings.defaultAgent === name) users.add("project settings");
|
|
177
|
+
for (const [owner, targets] of Object.entries(object(settings.delegation) ? settings.delegation : {})) if (owner === name || Array.isArray(targets) && targets.includes(name)) users.add("project delegation");
|
|
178
|
+
}
|
|
179
|
+
return [...users].sort();
|
|
180
|
+
}
|
|
181
|
+
const rows = await libraryEntries(kind, root, { library, templates });
|
|
182
|
+
for (const user of rows.find((row) => row.name === name)?.usedBy ?? []) users.add(`agent ${user}`);
|
|
183
|
+
return [...users].sort();
|
|
184
|
+
}
|
|
185
|
+
async function check(kind, name, content, { scope, root, library, templates }) {
|
|
186
|
+
if (!object(content)) fail("INVALID_ENTRY", `${kind} '${name}': content must be an object`);
|
|
187
|
+
const where = `${scope} ${kind} '${name}'`;
|
|
188
|
+
const visible = scope === "project" ? { root, library, templates } : { library, templates };
|
|
189
|
+
const text = (key, required) => {
|
|
190
|
+
if (content[key] === void 0 && !required) return;
|
|
191
|
+
if (typeof content[key] !== "string" || required && !content[key].trim()) fail("INVALID_ENTRY", `${where}: ${key} must be ${required ? "nonempty " : ""}text`);
|
|
192
|
+
};
|
|
193
|
+
const only = (keys) => {
|
|
194
|
+
for (const key of Object.keys(content)) if (!keys.includes(key)) fail("INVALID_ENTRY", `${where}: unknown field '${key}'; expected ${keys.join(", ")}`);
|
|
195
|
+
};
|
|
196
|
+
if (kind === "agents") {
|
|
197
|
+
only(["instructions", "config"]);
|
|
198
|
+
text("instructions", true);
|
|
199
|
+
if (content.config !== void 0) {
|
|
200
|
+
validateAgentConfig(content.config, `${where} agent.json`);
|
|
201
|
+
for (const [list, entries] of [["skills", "skills"], ["mcp", "mcp"], ["hooks", "hooks"]]) {
|
|
202
|
+
if (!content.config[list]?.length) continue;
|
|
203
|
+
const known = new Set((await libraryEntries(entries, visible.root, visible)).map((row) => row.name));
|
|
204
|
+
for (const entry of content.config[list]) if (!known.has(entry)) fail("NOT_FOUND", `${where}: ${list} names '${entry}', which is not in the ${scope === "project" ? "project or the library" : "library"}`);
|
|
205
|
+
}
|
|
206
|
+
const provider = content.config.provider;
|
|
207
|
+
if (provider !== void 0 && !["codex", "claude"].includes(provider) && !(await libraryEntries("providers", void 0, { library })).some((row) => row.name === provider)) {
|
|
208
|
+
fail("NOT_FOUND", `${where}: provider '${provider}' is neither codex, claude nor an ACP provider in your library`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
} else if (kind === "skills") {
|
|
212
|
+
only(["body"]);
|
|
213
|
+
text("body", true);
|
|
214
|
+
} else if (kind === "mcp") {
|
|
215
|
+
only(["server"]);
|
|
216
|
+
normalizeMcp({ mcpServers: { [name]: content.server } }, base(scope, { root, library }, kind), where);
|
|
217
|
+
} else if (kind === "hooks") {
|
|
218
|
+
only(["hook"]);
|
|
219
|
+
validateHook(content.hook, where);
|
|
220
|
+
} else if (kind === "providers") {
|
|
221
|
+
only(["provider"]);
|
|
222
|
+
validateProvider(content.provider, where, name);
|
|
223
|
+
} else {
|
|
224
|
+
only(["team", "houseRules"]);
|
|
225
|
+
text("houseRules", false);
|
|
226
|
+
validateTeam(content.team, `${where} team.json`);
|
|
227
|
+
const agents = await agentSources(visible.root, visible);
|
|
228
|
+
const supervisor = content.team.supervisor ? [content.team.supervisor.agent] : [];
|
|
229
|
+
for (const agent of [...Object.keys(content.team.members), ...supervisor]) if (!agents.has(agent)) fail("NOT_FOUND", `${where}: agent '${agent}' is not built in, nor in the ${scope === "project" ? "project or the library" : "library"}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
async function writeAtomic(file, text) {
|
|
233
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
234
|
+
const temporary = `${file}.${randomUUID().slice(0, 8)}.tmp`;
|
|
235
|
+
await writeFile(temporary, text);
|
|
236
|
+
await moveFile(temporary, file);
|
|
237
|
+
}
|
|
238
|
+
var exists = (target) => stat(target).then(() => true, () => false);
|
|
239
|
+
async function saveEntry(kind, name, content, { scope, root, library, templates, revision } = {}) {
|
|
240
|
+
kindOf(kind);
|
|
241
|
+
nameOf(name);
|
|
242
|
+
const directory = base(scope, { root, library }, kind);
|
|
243
|
+
await safeEntryPaths(directory, kind, name);
|
|
244
|
+
await check(kind, name, content, { scope, root, library, templates });
|
|
245
|
+
const current = await readLayer(kind, name, directory);
|
|
246
|
+
if (revision !== void 0 && revision !== revisionOf(current)) {
|
|
247
|
+
fail("REVISION_CONFLICT", current === void 0 ? `${kind} '${name}' was removed from the ${scope} since you opened it` : revision === null ? `${kind} '${name}' already exists in the ${scope}` : `${kind} '${name}' changed in the ${scope} since you opened it; reload it and apply your change again`);
|
|
248
|
+
}
|
|
249
|
+
if (scope === "library") await mkdir(directory, { recursive: true, mode: 448 });
|
|
250
|
+
for (const [file, key, type] of FILES[kind](name)) {
|
|
251
|
+
const target = path.join(directory, kind, file);
|
|
252
|
+
const value = content[key];
|
|
253
|
+
const empty = value === void 0 || type === "json" && key === "config" && !Object.keys(value).length || type === "text" && key === "houseRules" && !value.trim();
|
|
254
|
+
if (empty) {
|
|
255
|
+
await rm(target, { force: true });
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
await writeAtomic(target, type === "json" ? JSON.stringify(value, null, 2) + "\n" : value.endsWith("\n") ? value : `${value}
|
|
259
|
+
`);
|
|
260
|
+
}
|
|
261
|
+
return { revision: revisionOf(await readLayer(kind, name, directory)) };
|
|
262
|
+
}
|
|
263
|
+
async function deleteEntry(kind, name, { scope, root, library, templates, revision } = {}) {
|
|
264
|
+
kindOf(kind);
|
|
265
|
+
nameOf(name);
|
|
266
|
+
const directory = base(scope, { root, library }, kind);
|
|
267
|
+
const current = await readLayer(kind, name, directory);
|
|
268
|
+
if (current === void 0) fail("NOT_FOUND", `No ${kind} entry '${name}' in the ${scope}`);
|
|
269
|
+
if (revision !== void 0 && revision !== revisionOf(current)) fail("REVISION_CONFLICT", `${kind} '${name}' changed in the ${scope} since you opened it`);
|
|
270
|
+
const found = await layers(kind, name, { root: scope === "project" ? root : void 0, library: scope === "project" ? library : void 0, templates });
|
|
271
|
+
const fallback = scope === "project" ? found.library ?? found.builtin : found.builtin;
|
|
272
|
+
if (fallback === void 0) {
|
|
273
|
+
const users = await usersOf(kind, name, { root, library, templates });
|
|
274
|
+
if (users.length) fail("IN_USE", `${kind} '${name}' is used by ${users.join(", ")}; change them first`);
|
|
275
|
+
}
|
|
276
|
+
if (ownDirectory(kind)) await rm(path.join(directory, kind, name), { recursive: true, force: true });
|
|
277
|
+
else for (const [file] of FILES[kind](name)) await rm(path.join(directory, kind, file), { force: true });
|
|
278
|
+
const now = scope === "project" && found.library !== void 0 ? "library" : "builtin";
|
|
279
|
+
return { removed: true, ...fallback !== void 0 ? { now } : {} };
|
|
280
|
+
}
|
|
281
|
+
async function duplicateEntry(kind, from, to, { scope, root, library, templates } = {}) {
|
|
282
|
+
const { content } = await getEntry(kind, from, { root, library, templates });
|
|
283
|
+
return saveEntry(kind, to, structuredClone(content), { scope, root, library, templates, revision: null });
|
|
284
|
+
}
|
|
285
|
+
async function renameEntry(kind, from, to, { scope, root, library, templates } = {}) {
|
|
286
|
+
kindOf(kind);
|
|
287
|
+
nameOf(from);
|
|
288
|
+
nameOf(to);
|
|
289
|
+
const directory = base(scope, { root, library }, kind);
|
|
290
|
+
const content = await readLayer(kind, from, directory);
|
|
291
|
+
if (content === void 0) fail("NOT_FOUND", `No ${kind} entry '${from}' in the ${scope}`);
|
|
292
|
+
if (await readLayer(kind, to, directory) !== void 0) fail("REVISION_CONFLICT", `${kind} '${to}' already exists in the ${scope}`);
|
|
293
|
+
const users = await usersOf(kind, from, { root, library, templates });
|
|
294
|
+
if (users.length) fail("IN_USE", `${kind} '${from}' is used by ${users.join(", ")}; change them first`);
|
|
295
|
+
await check(kind, to, content, { scope, root, library, templates });
|
|
296
|
+
if (ownDirectory(kind)) {
|
|
297
|
+
if (await exists(path.join(directory, kind, to))) fail("REVISION_CONFLICT", `${path.join(directory, kind, to)} already exists`);
|
|
298
|
+
await rename(path.join(directory, kind, from), path.join(directory, kind, to));
|
|
299
|
+
} else {
|
|
300
|
+
await rename(path.join(directory, kind, `${from}.json`), path.join(directory, kind, `${to}.json`));
|
|
301
|
+
}
|
|
302
|
+
return { revision: revisionOf(await readLayer(kind, to, directory)) };
|
|
303
|
+
}
|
|
304
|
+
export {
|
|
305
|
+
EDIT_KINDS,
|
|
306
|
+
deleteEntry,
|
|
307
|
+
duplicateEntry,
|
|
308
|
+
getEntry,
|
|
309
|
+
givenSkills,
|
|
310
|
+
listEntries,
|
|
311
|
+
renameEntry,
|
|
312
|
+
saveEntry,
|
|
313
|
+
setGivenSkills,
|
|
314
|
+
usersOf
|
|
315
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The user's library: skills, role assignments and default providers, kept in
|
|
3
|
+
* ALP_HOME and shared by every project. ALP seeds it from its templates once,
|
|
4
|
+
* then updates only files the user has not changed; a file the user edited or
|
|
5
|
+
* deleted stays that way across app updates.
|
|
6
|
+
*/
|
|
7
|
+
/** ALP_HOME, the user's ALP directory: alpd's home and the skill library. */
|
|
8
|
+
export function alpHome(env?: NodeJS.ProcessEnv): string;
|
|
9
|
+
/**
|
|
10
|
+
* Seeds or updates the library in `home`. A file is written when ALP never
|
|
11
|
+
* shipped it before, and replaced only while it still holds what ALP last wrote.
|
|
12
|
+
* @param {string} home
|
|
13
|
+
* @param {{ templateRoot?: URL, templates?: Record<string, string> }} [options]
|
|
14
|
+
*/
|
|
15
|
+
export function seedLibrary(home: string, options?: {
|
|
16
|
+
templateRoot?: URL;
|
|
17
|
+
templates?: Record<string, string>;
|
|
18
|
+
}): Promise<{
|
|
19
|
+
created: string[];
|
|
20
|
+
updated: string[];
|
|
21
|
+
}>;
|
|
22
|
+
/** Seeds a library once per process; sessions call it on every open. */
|
|
23
|
+
export function ensureLibrary(home: any, options?: {}): any;
|
|
24
|
+
/** The library skills assigned to an agent, as named absolute SKILL.md paths that exist. */
|
|
25
|
+
export function librarySkills(home: any, agent: any): Promise<{
|
|
26
|
+
name: any;
|
|
27
|
+
path: string;
|
|
28
|
+
}[]>;
|