@fraylabs/possible 0.1.10 → 0.3.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.
- package/package.json +8 -13
- package/src/bookmarks.mjs +72 -0
- package/src/directory.mjs +37 -0
- package/src/index.mjs +84 -14
- package/src/outcome-commands.mjs +134 -0
- package/src/outcome-format.mjs +201 -0
- package/src/sources.mjs +174 -0
- package/assets/possible/SKILL.md +0 -124
- package/assets/possible/agents/openai.yaml +0 -4
- package/assets/possible/references/packs.md +0 -570
- package/src/init.mjs +0 -150
package/src/init.mjs
DELETED
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import { constants } from "node:fs";
|
|
2
|
-
import { copyFile, lstat, mkdir, readFile, readdir } from "node:fs/promises";
|
|
3
|
-
import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
|
|
6
|
-
const DEFAULT_SOURCE = fileURLToPath(new URL("../assets/possible", import.meta.url));
|
|
7
|
-
const INSTALL_SEGMENTS = [".agents", "skills", "possible"];
|
|
8
|
-
|
|
9
|
-
export class InstallConflictError extends Error {
|
|
10
|
-
constructor(conflicts) {
|
|
11
|
-
super(
|
|
12
|
-
`Possible was not installed because existing files conflict:\n${conflicts
|
|
13
|
-
.map((path) => ` - ${path}`)
|
|
14
|
-
.join("\n")}\nMove or remove the conflicting path, then run the command again.`,
|
|
15
|
-
);
|
|
16
|
-
this.name = "InstallConflictError";
|
|
17
|
-
this.conflicts = conflicts;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const pathExists = async (path) => {
|
|
22
|
-
try {
|
|
23
|
-
return await lstat(path);
|
|
24
|
-
} catch (error) {
|
|
25
|
-
if (error?.code === "ENOENT") return null;
|
|
26
|
-
throw error;
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const listTree = async (root, current = "") => {
|
|
31
|
-
const directory = join(root, current);
|
|
32
|
-
const entries = await readdir(directory, { withFileTypes: true });
|
|
33
|
-
const files = [];
|
|
34
|
-
const directories = [];
|
|
35
|
-
|
|
36
|
-
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
37
|
-
const child = join(current, entry.name);
|
|
38
|
-
if (entry.isSymbolicLink()) {
|
|
39
|
-
throw new Error(`The packaged Possible skill contains an unsupported symbolic link: ${child}`);
|
|
40
|
-
}
|
|
41
|
-
if (entry.isDirectory()) {
|
|
42
|
-
directories.push(child);
|
|
43
|
-
const nested = await listTree(root, child);
|
|
44
|
-
directories.push(...nested.directories);
|
|
45
|
-
files.push(...nested.files);
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
if (!entry.isFile()) {
|
|
49
|
-
throw new Error(`The packaged Possible skill contains an unsupported entry: ${child}`);
|
|
50
|
-
}
|
|
51
|
-
files.push(child);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return { directories, files };
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
const sameFile = async (left, right) => {
|
|
58
|
-
const [leftContent, rightContent] = await Promise.all([readFile(left), readFile(right)]);
|
|
59
|
-
return leftContent.equals(rightContent);
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
const displayPath = (projectRoot, path) => relative(projectRoot, path) || ".";
|
|
63
|
-
|
|
64
|
-
const assertInsideProject = (projectRoot, path) => {
|
|
65
|
-
const rel = relative(projectRoot, path);
|
|
66
|
-
if (rel !== "" && rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel)) return;
|
|
67
|
-
throw new Error(`Refusing to write outside the target project: ${path}`);
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
const ensureDirectory = async (projectRoot, path) => {
|
|
71
|
-
assertInsideProject(projectRoot, path);
|
|
72
|
-
try {
|
|
73
|
-
await mkdir(path);
|
|
74
|
-
} catch (error) {
|
|
75
|
-
if (error?.code !== "EEXIST") throw error;
|
|
76
|
-
}
|
|
77
|
-
const stats = await lstat(path);
|
|
78
|
-
if (!stats.isDirectory() || stats.isSymbolicLink()) {
|
|
79
|
-
throw new InstallConflictError([displayPath(projectRoot, path)]);
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
export async function installPossibleSkill({ projectDirectory = process.cwd(), sourceDirectory = DEFAULT_SOURCE } = {}) {
|
|
84
|
-
const projectRoot = resolve(projectDirectory);
|
|
85
|
-
const projectStats = await pathExists(projectRoot);
|
|
86
|
-
if (!projectStats?.isDirectory()) {
|
|
87
|
-
throw new Error(`Target project is not a directory: ${projectRoot}`);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const sourceRoot = resolve(sourceDirectory);
|
|
91
|
-
const sourceStats = await pathExists(sourceRoot);
|
|
92
|
-
if (!sourceStats?.isDirectory()) {
|
|
93
|
-
throw new Error(`Packaged Possible skill is missing: ${sourceRoot}`);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const tree = await listTree(sourceRoot);
|
|
97
|
-
const installRoot = join(projectRoot, ...INSTALL_SEGMENTS);
|
|
98
|
-
const directoryPaths = [
|
|
99
|
-
join(projectRoot, INSTALL_SEGMENTS[0]),
|
|
100
|
-
join(projectRoot, ...INSTALL_SEGMENTS.slice(0, 2)),
|
|
101
|
-
installRoot,
|
|
102
|
-
...tree.directories.map((path) => join(installRoot, path)),
|
|
103
|
-
];
|
|
104
|
-
const conflicts = [];
|
|
105
|
-
|
|
106
|
-
for (const path of directoryPaths) {
|
|
107
|
-
assertInsideProject(projectRoot, path);
|
|
108
|
-
const stats = await pathExists(path);
|
|
109
|
-
if (stats && (!stats.isDirectory() || stats.isSymbolicLink())) {
|
|
110
|
-
conflicts.push(displayPath(projectRoot, path));
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const missingFiles = [];
|
|
115
|
-
for (const sourceRelativePath of tree.files) {
|
|
116
|
-
const sourcePath = join(sourceRoot, sourceRelativePath);
|
|
117
|
-
const destinationPath = join(installRoot, sourceRelativePath);
|
|
118
|
-
assertInsideProject(projectRoot, destinationPath);
|
|
119
|
-
const stats = await pathExists(destinationPath);
|
|
120
|
-
if (!stats) {
|
|
121
|
-
missingFiles.push({ sourcePath, destinationPath });
|
|
122
|
-
} else if (!stats.isFile() || stats.isSymbolicLink() || !(await sameFile(sourcePath, destinationPath))) {
|
|
123
|
-
conflicts.push(displayPath(projectRoot, destinationPath));
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (conflicts.length > 0) {
|
|
128
|
-
throw new InstallConflictError([...new Set(conflicts)].sort());
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
for (const path of directoryPaths) {
|
|
132
|
-
await ensureDirectory(projectRoot, path);
|
|
133
|
-
}
|
|
134
|
-
for (const { sourcePath, destinationPath } of missingFiles) {
|
|
135
|
-
try {
|
|
136
|
-
await copyFile(sourcePath, destinationPath, constants.COPYFILE_EXCL);
|
|
137
|
-
} catch (error) {
|
|
138
|
-
if (error?.code === "EEXIST") {
|
|
139
|
-
throw new InstallConflictError([displayPath(projectRoot, destinationPath)]);
|
|
140
|
-
}
|
|
141
|
-
throw error;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return {
|
|
146
|
-
changed: missingFiles.length > 0,
|
|
147
|
-
filesWritten: missingFiles.length,
|
|
148
|
-
installPath: displayPath(projectRoot, installRoot),
|
|
149
|
-
};
|
|
150
|
-
}
|