@robbiesrobotics/alice-agents 1.4.5 → 1.4.7
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/README.md +19 -3
- package/bin/alice-install.mjs +22 -1
- package/lib/agent-registry.mjs +33 -0
- package/lib/coding-agent.mjs +187 -0
- package/lib/config-merger.mjs +5 -1
- package/lib/doctor.mjs +42 -24
- package/lib/installer.mjs +83 -19
- package/lib/license.mjs +158 -14
- package/lib/manifest.mjs +18 -9
- package/lib/mission-control.mjs +12 -0
- package/lib/release-guard.mjs +131 -0
- package/lib/skills.mjs +0 -1
- package/lib/workspace-scaffolder.mjs +11 -16
- package/package.json +5 -3
- package/templates/agents-pro.json +550 -0
- package/templates/mission-control-bridge/index.ts +21 -0
- package/templates/workspaces/dylan/SOUL.md +1 -1
- package/templates/workspaces/dylan/TOOLS.md +4 -4
- package/templates/workspaces/felix/SOUL.md +1 -1
- package/templates/workspaces/felix/TOOLS.md +3 -3
package/lib/license.mjs
CHANGED
|
@@ -7,19 +7,71 @@ const LICENSE_DIR = join(homedir(), '.alice');
|
|
|
7
7
|
const LICENSE_FILE = join(LICENSE_DIR, 'license');
|
|
8
8
|
const VALIDATE_URL = 'https://getalice.av3.ai/api/license/validate';
|
|
9
9
|
const KEY_REGEX = /^ALICE-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$/;
|
|
10
|
+
const GRACE_PERIOD_MS = 72 * 60 * 60 * 1000;
|
|
11
|
+
const REVALIDATE_INTERVAL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
12
|
+
|
|
13
|
+
function nowIso() {
|
|
14
|
+
return new Date().toISOString();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function parseLicenseRecord(raw) {
|
|
18
|
+
const trimmed = raw?.trim();
|
|
19
|
+
if (!trimmed) return null;
|
|
20
|
+
|
|
21
|
+
if (trimmed.startsWith('{')) {
|
|
22
|
+
try {
|
|
23
|
+
const parsed = JSON.parse(trimmed);
|
|
24
|
+
if (!parsed?.key) return null;
|
|
25
|
+
return parsed;
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
key: trimmed,
|
|
33
|
+
plan: 'pro',
|
|
34
|
+
status: 'validated',
|
|
35
|
+
validatedAt: null,
|
|
36
|
+
updatedAt: null,
|
|
37
|
+
source: 'legacy',
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function createLicenseRecord(key, data = {}) {
|
|
42
|
+
return {
|
|
43
|
+
key: key.trim().toUpperCase(),
|
|
44
|
+
plan: data.plan || 'pro',
|
|
45
|
+
status: data.status || 'validated',
|
|
46
|
+
validatedAt: data.status === 'validated' ? (data.validatedAt || nowIso()) : (data.validatedAt || null),
|
|
47
|
+
graceUntil: data.status === 'grace' ? (data.graceUntil || new Date(Date.now() + GRACE_PERIOD_MS).toISOString()) : null,
|
|
48
|
+
updatedAt: nowIso(),
|
|
49
|
+
transport: data.transport || null,
|
|
50
|
+
source: data.source || 'local',
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function shouldRevalidate(record) {
|
|
55
|
+
if (!record?.validatedAt) return true;
|
|
56
|
+
const validatedAt = Date.parse(record.validatedAt);
|
|
57
|
+
if (Number.isNaN(validatedAt)) return true;
|
|
58
|
+
return Date.now() - validatedAt >= REVALIDATE_INTERVAL_MS;
|
|
59
|
+
}
|
|
10
60
|
|
|
11
61
|
export function readStoredLicense() {
|
|
12
62
|
try {
|
|
13
63
|
if (!existsSync(LICENSE_FILE)) return null;
|
|
14
|
-
return readFileSync(LICENSE_FILE, 'utf8')
|
|
64
|
+
return parseLicenseRecord(readFileSync(LICENSE_FILE, 'utf8'));
|
|
15
65
|
} catch {
|
|
16
66
|
return null;
|
|
17
67
|
}
|
|
18
68
|
}
|
|
19
69
|
|
|
20
|
-
export function storeLicense(key) {
|
|
70
|
+
export function storeLicense(key, data = {}) {
|
|
21
71
|
mkdirSync(LICENSE_DIR, { recursive: true });
|
|
22
|
-
|
|
72
|
+
const record = createLicenseRecord(key, data);
|
|
73
|
+
writeFileSync(LICENSE_FILE, JSON.stringify(record, null, 2) + '\n', 'utf8');
|
|
74
|
+
return record;
|
|
23
75
|
}
|
|
24
76
|
|
|
25
77
|
export function isValidFormat(key) {
|
|
@@ -27,24 +79,116 @@ export function isValidFormat(key) {
|
|
|
27
79
|
}
|
|
28
80
|
|
|
29
81
|
export async function validateLicenseRemote(key) {
|
|
30
|
-
// Returns { valid: boolean, plan
|
|
82
|
+
// Returns { valid: boolean, plan?: string, message?: string, transient?: boolean, graceEligible?: boolean, transport?: string }
|
|
83
|
+
const normalizedKey = key.trim().toUpperCase();
|
|
84
|
+
const postBody = JSON.stringify({ key: normalizedKey });
|
|
85
|
+
|
|
31
86
|
try {
|
|
32
|
-
const res = await fetch(
|
|
87
|
+
const res = await fetch(VALIDATE_URL, {
|
|
88
|
+
method: 'POST',
|
|
89
|
+
headers: { 'content-type': 'application/json' },
|
|
90
|
+
body: postBody,
|
|
33
91
|
signal: AbortSignal.timeout(5000),
|
|
34
92
|
});
|
|
35
|
-
if (!res.ok
|
|
36
|
-
|
|
93
|
+
if (!res.ok && [404, 405, 415].includes(res.status)) {
|
|
94
|
+
const fallback = await fetch(`${VALIDATE_URL}?key=${encodeURIComponent(normalizedKey)}`, {
|
|
95
|
+
signal: AbortSignal.timeout(5000),
|
|
96
|
+
});
|
|
97
|
+
if (!fallback.ok) {
|
|
98
|
+
return {
|
|
99
|
+
valid: false,
|
|
100
|
+
message: 'Validation service unavailable',
|
|
101
|
+
transient: fallback.status >= 500,
|
|
102
|
+
graceEligible: isValidFormat(normalizedKey),
|
|
103
|
+
transport: 'get',
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return { ...(await fallback.json()), transport: 'get' };
|
|
107
|
+
}
|
|
108
|
+
if (!res.ok) {
|
|
109
|
+
return {
|
|
110
|
+
valid: false,
|
|
111
|
+
message: res.status >= 500 ? 'Validation service unavailable' : 'License key not recognized',
|
|
112
|
+
transient: res.status >= 500,
|
|
113
|
+
graceEligible: res.status >= 500 && isValidFormat(normalizedKey),
|
|
114
|
+
transport: 'post',
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
return { ...(await res.json()), transport: 'post' };
|
|
37
118
|
} catch {
|
|
38
|
-
|
|
39
|
-
|
|
119
|
+
return {
|
|
120
|
+
valid: false,
|
|
121
|
+
message: 'Validation service unavailable',
|
|
122
|
+
transient: true,
|
|
123
|
+
graceEligible: isValidFormat(normalizedKey),
|
|
124
|
+
transport: 'post',
|
|
125
|
+
};
|
|
40
126
|
}
|
|
41
127
|
}
|
|
42
128
|
|
|
43
|
-
export async function checkProLicense() {
|
|
129
|
+
export async function checkProLicense(options = {}) {
|
|
44
130
|
// Returns: { licensed: boolean, key: string|null, source: 'stored'|'none' }
|
|
45
|
-
const
|
|
46
|
-
if (
|
|
47
|
-
return { licensed:
|
|
131
|
+
const record = readStoredLicense();
|
|
132
|
+
if (!record || !isValidFormat(record.key)) {
|
|
133
|
+
return { licensed: false, key: null, source: 'none' };
|
|
48
134
|
}
|
|
49
|
-
|
|
135
|
+
|
|
136
|
+
if (record.status === 'grace') {
|
|
137
|
+
const graceUntil = Date.parse(record.graceUntil || '');
|
|
138
|
+
if (!Number.isNaN(graceUntil) && graceUntil > Date.now()) {
|
|
139
|
+
return {
|
|
140
|
+
licensed: true,
|
|
141
|
+
key: record.key,
|
|
142
|
+
source: 'grace',
|
|
143
|
+
provisional: true,
|
|
144
|
+
graceUntil: record.graceUntil,
|
|
145
|
+
needsRevalidation: true,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
return { licensed: false, key: null, source: 'expired_grace', message: 'Temporary grace period expired' };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const shouldRefresh = options.revalidate === true || shouldRevalidate(record);
|
|
152
|
+
if (shouldRefresh) {
|
|
153
|
+
const refreshed = await validateLicenseRemote(record.key);
|
|
154
|
+
if (refreshed.valid) {
|
|
155
|
+
const stored = storeLicense(record.key, {
|
|
156
|
+
status: 'validated',
|
|
157
|
+
plan: refreshed.plan || record.plan || 'pro',
|
|
158
|
+
transport: refreshed.transport,
|
|
159
|
+
source: 'remote',
|
|
160
|
+
});
|
|
161
|
+
return {
|
|
162
|
+
licensed: true,
|
|
163
|
+
key: stored.key,
|
|
164
|
+
source: 'revalidated',
|
|
165
|
+
provisional: false,
|
|
166
|
+
validatedAt: stored.validatedAt,
|
|
167
|
+
needsRevalidation: false,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (refreshed.transient) {
|
|
172
|
+
return {
|
|
173
|
+
licensed: true,
|
|
174
|
+
key: record.key,
|
|
175
|
+
source: 'stored',
|
|
176
|
+
provisional: false,
|
|
177
|
+
validatedAt: record.validatedAt,
|
|
178
|
+
needsRevalidation: true,
|
|
179
|
+
message: refreshed.message,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return { licensed: false, key: null, source: 'invalid', message: refreshed.message || 'License key not recognized' };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
licensed: true,
|
|
188
|
+
key: record.key,
|
|
189
|
+
source: record.source || 'stored',
|
|
190
|
+
provisional: false,
|
|
191
|
+
validatedAt: record.validatedAt,
|
|
192
|
+
needsRevalidation: false,
|
|
193
|
+
};
|
|
50
194
|
}
|
package/lib/manifest.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
|
|
5
5
|
const MANIFEST_NAME = '.alice-manifest.json';
|
|
6
|
+
const PACKAGE_VERSION = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version || '0.0.0';
|
|
6
7
|
|
|
7
8
|
export function getManifestPath() {
|
|
8
9
|
return join(homedir(), '.openclaw', MANIFEST_NAME);
|
|
@@ -18,16 +19,24 @@ export function readManifest() {
|
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
export function writeManifest(data) {
|
|
22
|
+
const existing = readManifest() || {};
|
|
23
|
+
const now = new Date().toISOString();
|
|
21
24
|
const manifest = {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
...existing,
|
|
26
|
+
version: data.version || existing.version || PACKAGE_VERSION,
|
|
27
|
+
schemaVersion: 2,
|
|
28
|
+
packageVersion: data.packageVersion || existing.packageVersion || PACKAGE_VERSION,
|
|
29
|
+
installedAt: data.installedAt || existing.installedAt || now,
|
|
30
|
+
updatedAt: now,
|
|
31
|
+
tier: data.tier ?? existing.tier ?? 'starter',
|
|
32
|
+
agents: Array.isArray(data.agents) ? [...new Set(data.agents)] : (existing.agents || []),
|
|
33
|
+
userName: data.userName ?? existing.userName ?? null,
|
|
34
|
+
userTimezone: data.userTimezone ?? existing.userTimezone ?? null,
|
|
35
|
+
modelPreset: data.modelPreset ?? existing.modelPreset ?? null,
|
|
36
|
+
missionControl: data.missionControl ?? existing.missionControl ?? null,
|
|
37
|
+
skills: Array.isArray(data.skills) ? [...new Set(data.skills)] : (existing.skills || []),
|
|
38
|
+
codingTool: data.codingTool ?? existing.codingTool ?? null,
|
|
39
|
+
codingSkill: data.codingSkill ?? existing.codingSkill ?? null,
|
|
31
40
|
};
|
|
32
41
|
writeFileSync(getManifestPath(), JSON.stringify(manifest, null, 2) + '\n', 'utf8');
|
|
33
42
|
return manifest;
|
package/lib/mission-control.mjs
CHANGED
|
@@ -53,6 +53,10 @@ export function buildMissionControlSettings(input = {}) {
|
|
|
53
53
|
const sourceNode = String(input.sourceNode || hostname() || 'openclaw-local').trim();
|
|
54
54
|
const ingestToken = String(input.ingestToken || '').trim();
|
|
55
55
|
const workerToken = String(input.workerToken || ingestToken || '').trim();
|
|
56
|
+
const teamId = String(input.teamId || '').trim();
|
|
57
|
+
const teamSlug = String(input.teamSlug || '').trim();
|
|
58
|
+
const teamName = String(input.teamName || '').trim();
|
|
59
|
+
const teamPlan = String(input.teamPlan || '').trim();
|
|
56
60
|
|
|
57
61
|
return {
|
|
58
62
|
enabled: input.enabled !== false,
|
|
@@ -66,6 +70,10 @@ export function buildMissionControlSettings(input = {}) {
|
|
|
66
70
|
nodeRegisterUrl,
|
|
67
71
|
nodeHeartbeatUrl,
|
|
68
72
|
sourceNode,
|
|
73
|
+
...(teamId ? { teamId } : {}),
|
|
74
|
+
...(teamSlug ? { teamSlug } : {}),
|
|
75
|
+
...(teamName ? { teamName } : {}),
|
|
76
|
+
...(teamPlan ? { teamPlan } : {}),
|
|
69
77
|
...(ingestToken ? { ingestToken } : {}),
|
|
70
78
|
...(workerToken ? { workerToken } : {}),
|
|
71
79
|
};
|
|
@@ -154,6 +162,10 @@ export function configureMissionControlCloud(input = {}) {
|
|
|
154
162
|
runtimeBaseUrl: settings.runtimeBaseUrl,
|
|
155
163
|
adminHeartbeatUrl: settings.adminHeartbeatUrl,
|
|
156
164
|
sourceNode: settings.sourceNode,
|
|
165
|
+
teamId: settings.teamId ?? null,
|
|
166
|
+
teamSlug: settings.teamSlug ?? null,
|
|
167
|
+
teamName: settings.teamName ?? null,
|
|
168
|
+
teamPlan: settings.teamPlan ?? null,
|
|
157
169
|
hasIngestToken: !!settings.ingestToken,
|
|
158
170
|
hasWorkerToken: !!settings.workerToken,
|
|
159
171
|
},
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const DEFAULT_REPO_ROOT = join(__dirname, '..');
|
|
8
|
+
|
|
9
|
+
export function readPackageVersion(repoRoot = DEFAULT_REPO_ROOT) {
|
|
10
|
+
const packagePath = join(repoRoot, 'package.json');
|
|
11
|
+
const pkg = JSON.parse(readFileSync(packagePath, 'utf8'));
|
|
12
|
+
return String(pkg.version || '').trim();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function getExpectedTag(version) {
|
|
16
|
+
return `v${version}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function git(repoRoot, args) {
|
|
20
|
+
return execFileSync('git', args, {
|
|
21
|
+
cwd: repoRoot,
|
|
22
|
+
encoding: 'utf8',
|
|
23
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
24
|
+
}).trim();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getTagsOnHead(repoRoot = DEFAULT_REPO_ROOT) {
|
|
28
|
+
const output = git(repoRoot, ['tag', '--points-at', 'HEAD']);
|
|
29
|
+
return output ? output.split('\n').map((line) => line.trim()).filter(Boolean) : [];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function hasChangelogEntry(repoRoot, version) {
|
|
33
|
+
const changelogPath = join(repoRoot, 'landing', 'content', 'changelog.md');
|
|
34
|
+
if (!existsSync(changelogPath)) return false;
|
|
35
|
+
const changelog = readFileSync(changelogPath, 'utf8');
|
|
36
|
+
return changelog.includes(`## v${version} — `);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function getReleaseNotePath(repoRoot, version) {
|
|
40
|
+
return join(repoRoot, 'releases', `v${version}.md`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function getReleaseNoteChecks(repoRoot, version) {
|
|
44
|
+
const releaseNotePath = getReleaseNotePath(repoRoot, version);
|
|
45
|
+
if (!existsSync(releaseNotePath)) {
|
|
46
|
+
return {
|
|
47
|
+
exists: false,
|
|
48
|
+
hasTitle: false,
|
|
49
|
+
approved: false,
|
|
50
|
+
hasAnnouncementSection: false,
|
|
51
|
+
hasChannelsSection: false,
|
|
52
|
+
path: releaseNotePath,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const content = readFileSync(releaseNotePath, 'utf8');
|
|
57
|
+
return {
|
|
58
|
+
exists: true,
|
|
59
|
+
hasTitle: new RegExp(`^#\\s+v${version}\\b`, 'm').test(content),
|
|
60
|
+
approved: /^Status:\s*approved\s*$/im.test(content),
|
|
61
|
+
hasAnnouncementSection: /^##\s+Announcement\s*$/im.test(content),
|
|
62
|
+
hasChannelsSection: /^##\s+Channels\s*$/im.test(content),
|
|
63
|
+
path: releaseNotePath,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function collectReleaseIssues(repoRoot = DEFAULT_REPO_ROOT) {
|
|
68
|
+
const version = readPackageVersion(repoRoot);
|
|
69
|
+
const expectedTag = getExpectedTag(version);
|
|
70
|
+
const issues = [];
|
|
71
|
+
|
|
72
|
+
if (!version) {
|
|
73
|
+
issues.push('package.json is missing a version.');
|
|
74
|
+
return issues;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const tagsOnHead = getTagsOnHead(repoRoot);
|
|
78
|
+
if (!tagsOnHead.includes(expectedTag)) {
|
|
79
|
+
issues.push(`HEAD is missing the matching git tag ${expectedTag}.`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!hasChangelogEntry(repoRoot, version)) {
|
|
83
|
+
issues.push(`landing/content/changelog.md is missing the v${version} entry.`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const releaseNote = getReleaseNoteChecks(repoRoot, version);
|
|
87
|
+
if (!releaseNote.exists) {
|
|
88
|
+
issues.push(`Missing release brief: releases/v${version}.md.`);
|
|
89
|
+
} else {
|
|
90
|
+
if (!releaseNote.hasTitle) {
|
|
91
|
+
issues.push(`releases/v${version}.md is missing the '# v${version}' title.`);
|
|
92
|
+
}
|
|
93
|
+
if (!releaseNote.approved) {
|
|
94
|
+
issues.push(`releases/v${version}.md must include 'Status: approved' before publish.`);
|
|
95
|
+
}
|
|
96
|
+
if (!releaseNote.hasAnnouncementSection) {
|
|
97
|
+
issues.push(`releases/v${version}.md is missing the '## Announcement' section.`);
|
|
98
|
+
}
|
|
99
|
+
if (!releaseNote.hasChannelsSection) {
|
|
100
|
+
issues.push(`releases/v${version}.md is missing the '## Channels' section.`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return issues;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function runReleaseGuard(repoRoot = DEFAULT_REPO_ROOT) {
|
|
108
|
+
const version = readPackageVersion(repoRoot);
|
|
109
|
+
const issues = collectReleaseIssues(repoRoot);
|
|
110
|
+
|
|
111
|
+
if (issues.length) {
|
|
112
|
+
const formatted = issues.map((issue) => `- ${issue}`).join('\n');
|
|
113
|
+
throw new Error(
|
|
114
|
+
`Release guard blocked publish for v${version}.\n${formatted}\n\n` +
|
|
115
|
+
'Fix the release brief, changelog, and git tag before running npm publish again.',
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return `Release guard passed for v${version}.`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const isEntrypoint = process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1];
|
|
123
|
+
|
|
124
|
+
if (isEntrypoint) {
|
|
125
|
+
try {
|
|
126
|
+
console.log(runReleaseGuard());
|
|
127
|
+
} catch (error) {
|
|
128
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
}
|
package/lib/skills.mjs
CHANGED
|
@@ -51,7 +51,6 @@ export const SKILL_CATALOG = [
|
|
|
51
51
|
skills: [
|
|
52
52
|
{ id: 'github', label: 'GitHub', desc: 'Issues, PRs, CI via gh CLI' },
|
|
53
53
|
{ id: 'gh-issues', label: 'GitHub Issues Bot', desc: 'Auto-fix issues and open PRs' },
|
|
54
|
-
{ id: 'coding-agent', label: 'Coding Agent', desc: 'Delegate tasks to Codex / Claude Code' },
|
|
55
54
|
{ id: '1password', label: '1Password', desc: 'Secrets and credentials via op CLI' },
|
|
56
55
|
],
|
|
57
56
|
},
|
|
@@ -2,6 +2,7 @@ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
|
2
2
|
import { join, dirname } from 'node:path';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { buildCodingAgentSkillContent, resolveCodingAgentPreference } from './coding-agent.mjs';
|
|
5
6
|
|
|
6
7
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
8
|
const TEMPLATES_DIR = join(__dirname, '..', 'templates', 'workspaces');
|
|
@@ -100,23 +101,17 @@ export function scaffoldWorkspace(agent, userInfo, agentCount) {
|
|
|
100
101
|
return { workspaceDir, written, skipped };
|
|
101
102
|
}
|
|
102
103
|
|
|
103
|
-
export function scaffoldSkills() {
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
mkdirSync(
|
|
104
|
+
export function scaffoldSkills(preference = null) {
|
|
105
|
+
const codingAgentPreference = preference || resolveCodingAgentPreference();
|
|
106
|
+
const codingAgentSkillDir = join(SKILLS_DIR, codingAgentPreference.skillId);
|
|
107
|
+
mkdirSync(codingAgentSkillDir, { recursive: true });
|
|
107
108
|
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (existsSync(skillTemplatePath)) {
|
|
112
|
-
const content = readFileSync(skillTemplatePath, 'utf8');
|
|
113
|
-
writeFileSync(skillDestPath, content, 'utf8');
|
|
114
|
-
return true;
|
|
115
|
-
}
|
|
116
|
-
return false;
|
|
109
|
+
const skillDestPath = join(codingAgentSkillDir, 'SKILL.md');
|
|
110
|
+
writeFileSync(skillDestPath, buildCodingAgentSkillContent(codingAgentPreference), 'utf8');
|
|
111
|
+
return codingAgentPreference;
|
|
117
112
|
}
|
|
118
113
|
|
|
119
|
-
export function scaffoldAll(agents, userInfo) {
|
|
114
|
+
export function scaffoldAll(agents, userInfo, skillPreference = null) {
|
|
120
115
|
const agentCount = agents.length;
|
|
121
116
|
const results = [];
|
|
122
117
|
|
|
@@ -126,7 +121,7 @@ export function scaffoldAll(agents, userInfo) {
|
|
|
126
121
|
}
|
|
127
122
|
|
|
128
123
|
// Install skills
|
|
129
|
-
scaffoldSkills();
|
|
124
|
+
const installedSkill = scaffoldSkills(skillPreference);
|
|
130
125
|
|
|
131
|
-
return results;
|
|
126
|
+
return { workspaces: results, installedSkill };
|
|
132
127
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@robbiesrobotics/alice-agents",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.7",
|
|
4
4
|
"description": "A.L.I.C.E. — 28 AI agents for OpenClaw. One conversation, one team.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"alice-agents": "bin/alice-install.mjs"
|
|
@@ -23,15 +23,17 @@
|
|
|
23
23
|
"tools/",
|
|
24
24
|
"snapshots/",
|
|
25
25
|
"templates/agents-starter.json",
|
|
26
|
+
"templates/agents-pro.json",
|
|
26
27
|
"templates/mission-control-bridge/",
|
|
27
28
|
"templates/skills/",
|
|
28
29
|
"templates/workspaces/",
|
|
29
|
-
"SELF-HEALING-SPEC.md",
|
|
30
30
|
"README.md"
|
|
31
31
|
],
|
|
32
32
|
"scripts": {
|
|
33
33
|
"test": "node --test test/*.test.mjs",
|
|
34
|
-
"test:check": "node --check lib/*.mjs bin/*.mjs"
|
|
34
|
+
"test:check": "node --check lib/*.mjs bin/*.mjs",
|
|
35
|
+
"release:check": "node lib/release-guard.mjs",
|
|
36
|
+
"prepublishOnly": "npm test && npm run test:check && npm run release:check"
|
|
35
37
|
},
|
|
36
38
|
"publishConfig": {
|
|
37
39
|
"access": "public"
|