arcane-cli 1.2.0 → 1.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/README.md +11 -0
- package/dist/assets/.arcane/governance/portable-bootstrap.md +1 -1
- package/dist/index.js +236 -99
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,11 +6,11 @@ const require = __createRequire(import.meta.url);
|
|
|
6
6
|
import { Command } from "commander";
|
|
7
7
|
import { createRequire as createRequire2 } from "module";
|
|
8
8
|
import { fileURLToPath } from "url";
|
|
9
|
-
import { dirname as
|
|
9
|
+
import { dirname as dirname11, join as join18 } from "path";
|
|
10
10
|
|
|
11
11
|
// src/commands/init.ts
|
|
12
12
|
import { select as select2, confirm, input as input2 } from "@inquirer/prompts";
|
|
13
|
-
import { dirname as
|
|
13
|
+
import { dirname as dirname4, join as join6 } from "path";
|
|
14
14
|
import chalk2 from "chalk";
|
|
15
15
|
|
|
16
16
|
// src/modules/copier.ts
|
|
@@ -1035,18 +1035,25 @@ function spellIdsInStore(components) {
|
|
|
1035
1035
|
}
|
|
1036
1036
|
return [...ids].sort();
|
|
1037
1037
|
}
|
|
1038
|
+
var USER_AGENT_FANOUT_DIR = ".copilot/agents";
|
|
1039
|
+
var SPELL_FANOUT_CLIENTS = ["codex", "claude"];
|
|
1038
1040
|
var USER_FANOUT_PATHS = {
|
|
1039
1041
|
codex: (id) => `.agents/skills/${id}/SKILL.md`,
|
|
1040
1042
|
claude: (id) => `.claude/commands/${id}.md`
|
|
1041
1043
|
};
|
|
1044
|
+
function userAgentFanoutPath(slug) {
|
|
1045
|
+
return `${USER_AGENT_FANOUT_DIR}/${slug}.agent.md`;
|
|
1046
|
+
}
|
|
1042
1047
|
var FANOUT_CLIENT_LABELS = {
|
|
1043
1048
|
codex: "Codex/Copilot skills",
|
|
1044
|
-
claude: "Claude Code commands"
|
|
1049
|
+
claude: "Claude Code commands",
|
|
1050
|
+
"copilot-agents": "Copilot agent modes"
|
|
1045
1051
|
};
|
|
1046
1052
|
function clientOfFanoutPath(relativePath) {
|
|
1047
1053
|
const normalized = relativePath.replace(/\\/g, "/");
|
|
1048
1054
|
if (normalized.startsWith(".agents/skills/")) return "codex";
|
|
1049
1055
|
if (normalized.startsWith(".claude/commands/")) return "claude";
|
|
1056
|
+
if (normalized.startsWith(`${USER_AGENT_FANOUT_DIR}/`)) return "copilot-agents";
|
|
1050
1057
|
return void 0;
|
|
1051
1058
|
}
|
|
1052
1059
|
async function readSpellSource(storeRoot, id, fallbackDir) {
|
|
@@ -1108,14 +1115,16 @@ async function removeDirectoryIfEmpty(dir) {
|
|
|
1108
1115
|
}
|
|
1109
1116
|
async function syncUserTierFanout(options) {
|
|
1110
1117
|
const rendered = await renderUserFanout(options.storeRoot, options.spellIds, options.fallbackDir);
|
|
1111
|
-
const desired = rendered.files;
|
|
1118
|
+
const desired = [...rendered.files, ...options.extraFiles ?? []];
|
|
1119
|
+
const owned = options.ownedClients;
|
|
1120
|
+
const ownsClient = (client) => owned === void 0 || client !== void 0 && owned.includes(client);
|
|
1112
1121
|
const previous = options.previous ?? {};
|
|
1113
1122
|
const dryRun = Boolean(options.dryRun);
|
|
1114
1123
|
const record = {};
|
|
1115
1124
|
const outcomes = [];
|
|
1116
1125
|
const desiredPaths = new Set(desired.map((f) => f.relativePath));
|
|
1117
1126
|
for (const id of rendered.missing) {
|
|
1118
|
-
for (const client of
|
|
1127
|
+
for (const client of SPELL_FANOUT_CLIENTS) {
|
|
1119
1128
|
const relativePath = USER_FANOUT_PATHS[client](id);
|
|
1120
1129
|
desiredPaths.add(relativePath);
|
|
1121
1130
|
if (previous[relativePath] !== void 0) record[relativePath] = previous[relativePath];
|
|
@@ -1158,6 +1167,10 @@ async function syncUserTierFanout(options) {
|
|
|
1158
1167
|
}
|
|
1159
1168
|
for (const [relativePath, recorded] of Object.entries(previous)) {
|
|
1160
1169
|
if (desiredPaths.has(relativePath)) continue;
|
|
1170
|
+
if (!ownsClient(clientOfFanoutPath(relativePath))) {
|
|
1171
|
+
record[relativePath] = recorded;
|
|
1172
|
+
continue;
|
|
1173
|
+
}
|
|
1161
1174
|
validateTargetPath(options.homeDir, relativePath);
|
|
1162
1175
|
const target = join(options.homeDir, relativePath);
|
|
1163
1176
|
const state = await classifyTarget(target);
|
|
@@ -1198,7 +1211,7 @@ function describeFanoutOutcomes(outcomes, dryRun = false) {
|
|
|
1198
1211
|
const lines = [];
|
|
1199
1212
|
const prefix = dryRun ? "[dry-run] Would " : "";
|
|
1200
1213
|
const byClient = (status) => {
|
|
1201
|
-
const n = { codex: 0, claude: 0 };
|
|
1214
|
+
const n = { codex: 0, claude: 0, "copilot-agents": 0 };
|
|
1202
1215
|
for (const o of outcomes) {
|
|
1203
1216
|
if (o.status !== status) continue;
|
|
1204
1217
|
const client = clientOfFanoutPath(o.relativePath);
|
|
@@ -1209,8 +1222,9 @@ function describeFanoutOutcomes(outcomes, dryRun = false) {
|
|
|
1209
1222
|
const paths = (status) => outcomes.filter((o) => o.status === status).map((o) => ` ~/${o.relativePath}`).join("\n");
|
|
1210
1223
|
if (counts.written > 0) {
|
|
1211
1224
|
const n = byClient("written");
|
|
1225
|
+
const breakdown = Object.keys(n).filter((client) => n[client] > 0).map((client) => `${n[client]} ${FANOUT_CLIENT_LABELS[client]}`).join(", ");
|
|
1212
1226
|
lines.push(
|
|
1213
|
-
`${prefix}${dryRun ? "write" : "Wrote"} ${counts.written} client file(s): ${
|
|
1227
|
+
`${prefix}${dryRun ? "write" : "Wrote"} ${counts.written} client file(s): ${breakdown}.`
|
|
1214
1228
|
);
|
|
1215
1229
|
}
|
|
1216
1230
|
if (counts.pruned > 0) {
|
|
@@ -1248,7 +1262,7 @@ ${paths("kept-edited")}`
|
|
|
1248
1262
|
async function inspectUserTierFanout(homeDir, record) {
|
|
1249
1263
|
const health = {
|
|
1250
1264
|
total: 0,
|
|
1251
|
-
byClient: { codex: 0, claude: 0 },
|
|
1265
|
+
byClient: { codex: 0, claude: 0, "copilot-agents": 0 },
|
|
1252
1266
|
missing: [],
|
|
1253
1267
|
customized: []
|
|
1254
1268
|
};
|
|
@@ -1425,7 +1439,7 @@ function printWarning(message) {
|
|
|
1425
1439
|
// src/modules/agents.ts
|
|
1426
1440
|
import { select, checkbox, Separator } from "@inquirer/prompts";
|
|
1427
1441
|
import { mkdir as mkdir4, writeFile as writeFile6, copyFile as copyFile2 } from "fs/promises";
|
|
1428
|
-
import { join as join4 } from "path";
|
|
1442
|
+
import { dirname as dirname2, join as join4 } from "path";
|
|
1429
1443
|
import { stringify } from "yaml";
|
|
1430
1444
|
|
|
1431
1445
|
// src/config/agent-roles.ts
|
|
@@ -1834,9 +1848,15 @@ var AgentDefinitionNotFoundError = class extends Error {
|
|
|
1834
1848
|
}
|
|
1835
1849
|
};
|
|
1836
1850
|
var AgentRosterNotFoundError = class extends Error {
|
|
1837
|
-
|
|
1851
|
+
/**
|
|
1852
|
+
* Names the tier the caller actually asked for. A `--user` run that reports
|
|
1853
|
+
* the repository's path sends the operator to the wrong file and the wrong
|
|
1854
|
+
* command, which is worse than no message.
|
|
1855
|
+
*/
|
|
1856
|
+
constructor(scope = "repo") {
|
|
1857
|
+
const user = scope === "user";
|
|
1838
1858
|
super(
|
|
1839
|
-
|
|
1859
|
+
`No agent roster found at ${user ? "~/.arcane/agents.yaml" : ".arcane/agents.yaml"}. Run "spell agents init${user ? " --user" : ""}" to create one.`
|
|
1840
1860
|
);
|
|
1841
1861
|
this.name = "AgentRosterNotFoundError";
|
|
1842
1862
|
}
|
|
@@ -1859,30 +1879,33 @@ async function loadAgentDefinition(agentsDir, id) {
|
|
|
1859
1879
|
}
|
|
1860
1880
|
return validateAgentDefinition(parseYaml2(content, filePath), filePath);
|
|
1861
1881
|
}
|
|
1862
|
-
|
|
1863
|
-
|
|
1882
|
+
function agentsBaseDir(targetDir, scope = "repo") {
|
|
1883
|
+
return scope === "user" ? targetDir : join2(targetDir, ".arcane");
|
|
1884
|
+
}
|
|
1885
|
+
async function loadRoster(targetDir, scope = "repo") {
|
|
1886
|
+
const rosterPath = join2(agentsBaseDir(targetDir, scope), "agents.yaml");
|
|
1864
1887
|
let content;
|
|
1865
1888
|
try {
|
|
1866
1889
|
content = await readFile4(rosterPath, "utf8");
|
|
1867
1890
|
} catch {
|
|
1868
|
-
throw new AgentRosterNotFoundError();
|
|
1891
|
+
throw new AgentRosterNotFoundError(scope);
|
|
1869
1892
|
}
|
|
1870
1893
|
return validateAgentRoster(parseYaml2(content, rosterPath), rosterPath);
|
|
1871
1894
|
}
|
|
1872
|
-
async function rosterExists(targetDir) {
|
|
1895
|
+
async function rosterExists(targetDir, scope = "repo") {
|
|
1873
1896
|
try {
|
|
1874
|
-
await access2(join2(targetDir,
|
|
1897
|
+
await access2(join2(agentsBaseDir(targetDir, scope), "agents.yaml"));
|
|
1875
1898
|
return true;
|
|
1876
1899
|
} catch {
|
|
1877
1900
|
return false;
|
|
1878
1901
|
}
|
|
1879
1902
|
}
|
|
1880
|
-
function projectAgentsDir(targetDir) {
|
|
1881
|
-
return join2(targetDir,
|
|
1903
|
+
function projectAgentsDir(targetDir, scope = "repo") {
|
|
1904
|
+
return join2(agentsBaseDir(targetDir, scope), "agents");
|
|
1882
1905
|
}
|
|
1883
1906
|
|
|
1884
1907
|
// src/modules/agent-generator.ts
|
|
1885
|
-
import { writeFile as writeFile5, mkdir as mkdir3 } from "fs/promises";
|
|
1908
|
+
import { writeFile as writeFile5, mkdir as mkdir3, readdir as readdir4 } from "fs/promises";
|
|
1886
1909
|
import { join as join3 } from "path";
|
|
1887
1910
|
import { homedir as homedir2 } from "os";
|
|
1888
1911
|
|
|
@@ -2091,11 +2114,23 @@ function renderAgentRosterSection(entries) {
|
|
|
2091
2114
|
${rows}
|
|
2092
2115
|
`;
|
|
2093
2116
|
}
|
|
2117
|
+
async function existingAgentFiles(agentsDir) {
|
|
2118
|
+
try {
|
|
2119
|
+
const entries = await readdir4(agentsDir);
|
|
2120
|
+
return entries.filter((name) => name.endsWith(".agent.md")).sort().map((name) => `.github/agents/${name}`);
|
|
2121
|
+
} catch {
|
|
2122
|
+
return [];
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2094
2125
|
async function syncAgents(targetDir, assetsDir, roster, options = {}) {
|
|
2095
2126
|
const synced = [];
|
|
2096
2127
|
const skipped = [];
|
|
2097
2128
|
let hasUnresolvedRoles = false;
|
|
2098
|
-
const
|
|
2129
|
+
const userTier = options.scope === "user";
|
|
2130
|
+
const repoOptedOut = !userTier && options.spellScope === "user";
|
|
2131
|
+
const userAgentFiles = [];
|
|
2132
|
+
const unmanagedAgentFiles = [];
|
|
2133
|
+
const projectDefs = projectAgentsDir(targetDir, userTier ? "user" : "repo");
|
|
2099
2134
|
const bundledDefs = join3(assetsDir, "agents");
|
|
2100
2135
|
const resolved = [];
|
|
2101
2136
|
for (const entry of roster.roster) {
|
|
@@ -2117,7 +2152,7 @@ async function syncAgents(targetDir, assetsDir, roster, options = {}) {
|
|
|
2117
2152
|
displayName: entry.name ?? entry.definition
|
|
2118
2153
|
});
|
|
2119
2154
|
}
|
|
2120
|
-
if (roster.openclaw.enabled && options.openclaw !== false) {
|
|
2155
|
+
if (!userTier && roster.openclaw.enabled && options.openclaw !== false) {
|
|
2121
2156
|
const openclawRoot = roster.openclaw.workspace_root.replace(
|
|
2122
2157
|
/^~/,
|
|
2123
2158
|
homedir2()
|
|
@@ -2182,34 +2217,46 @@ async function syncAgents(targetDir, assetsDir, roster, options = {}) {
|
|
|
2182
2217
|
}
|
|
2183
2218
|
if (options.copilot !== false) {
|
|
2184
2219
|
const agentsDir = join3(targetDir, ".github", "agents");
|
|
2185
|
-
if (!options.dryRun) {
|
|
2186
|
-
await mkdir3(agentsDir, { recursive: true });
|
|
2220
|
+
if (!options.dryRun && !userTier) {
|
|
2221
|
+
await mkdir3(repoOptedOut ? join3(targetDir, ".github") : agentsDir, { recursive: true });
|
|
2187
2222
|
}
|
|
2188
2223
|
for (const { def, displayName } of resolved) {
|
|
2189
2224
|
if (!def.clients.copilot?.agent_file) continue;
|
|
2190
2225
|
const slug = displayName.toLowerCase().replace(/\s+/g, "-");
|
|
2191
2226
|
const fileName = `${slug}.agent.md`;
|
|
2227
|
+
const content = renderCopilotAgent(def, displayName);
|
|
2228
|
+
if (userTier) {
|
|
2229
|
+
const relativePath = userAgentFanoutPath(slug);
|
|
2230
|
+
userAgentFiles.push({ relativePath, client: "copilot-agents", content });
|
|
2231
|
+
synced.push(`~/${relativePath}`);
|
|
2232
|
+
continue;
|
|
2233
|
+
}
|
|
2234
|
+
if (repoOptedOut) continue;
|
|
2192
2235
|
if (!options.dryRun) {
|
|
2193
|
-
await writeFile5(
|
|
2194
|
-
join3(agentsDir, fileName),
|
|
2195
|
-
renderCopilotAgent(def, displayName),
|
|
2196
|
-
"utf8"
|
|
2197
|
-
);
|
|
2236
|
+
await writeFile5(join3(agentsDir, fileName), content, "utf8");
|
|
2198
2237
|
} else {
|
|
2199
2238
|
console.log(` [dry-run] Would write: .github/agents/${fileName}`);
|
|
2200
2239
|
}
|
|
2201
2240
|
synced.push(`.github/agents/${fileName}`);
|
|
2202
2241
|
}
|
|
2203
|
-
|
|
2242
|
+
if (repoOptedOut) {
|
|
2243
|
+
unmanagedAgentFiles.push(...await existingAgentFiles(agentsDir));
|
|
2244
|
+
}
|
|
2245
|
+
if (!userTier) {
|
|
2246
|
+
const copilotSection = `${renderSpellRoutingSection()}
|
|
2204
2247
|
${renderAgentRosterSection(resolved)}`;
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2248
|
+
const copilotMerged = await mergeIntoFile(
|
|
2249
|
+
targetDir,
|
|
2250
|
+
".github/copilot-instructions.md",
|
|
2251
|
+
copilotSection,
|
|
2252
|
+
{ force: true, dryRun: options.dryRun }
|
|
2253
|
+
);
|
|
2254
|
+
if (copilotMerged)
|
|
2255
|
+
synced.push(".github/copilot-instructions.md (agents section)");
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
if (userTier) {
|
|
2259
|
+
return { synced, skipped, hasUnresolvedRoles, userAgentFiles, unmanagedAgentFiles };
|
|
2213
2260
|
}
|
|
2214
2261
|
if (options.claude !== false) {
|
|
2215
2262
|
const claudeSection = `${renderSpellRoutingSection()}
|
|
@@ -2233,12 +2280,83 @@ ${renderAgentRosterSection(resolved)}`;
|
|
|
2233
2280
|
);
|
|
2234
2281
|
if (codexMerged) synced.push("AGENTS.md (agents section)");
|
|
2235
2282
|
}
|
|
2236
|
-
return { synced, skipped, hasUnresolvedRoles };
|
|
2283
|
+
return { synced, skipped, hasUnresolvedRoles, userAgentFiles, unmanagedAgentFiles };
|
|
2237
2284
|
}
|
|
2238
2285
|
|
|
2239
2286
|
// src/modules/agents.ts
|
|
2287
|
+
function agentsTargetDir(scope, cwd) {
|
|
2288
|
+
return scope === "user" ? userTierRoot() : cwd;
|
|
2289
|
+
}
|
|
2290
|
+
async function requireUserStore(storeRoot) {
|
|
2291
|
+
try {
|
|
2292
|
+
return await readManifest(storeRoot);
|
|
2293
|
+
} catch {
|
|
2294
|
+
console.error(
|
|
2295
|
+
`
|
|
2296
|
+
\u2717 No user tier at ${storeRoot}.
|
|
2297
|
+
Run \`spell init --user\` once to create it, then re-run this command.
|
|
2298
|
+
`
|
|
2299
|
+
);
|
|
2300
|
+
process.exit(1);
|
|
2301
|
+
throw new Error("unreachable");
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
async function repoSpellScope(targetDir) {
|
|
2305
|
+
try {
|
|
2306
|
+
return effectiveSpellScope(await readManifest(targetDir));
|
|
2307
|
+
} catch {
|
|
2308
|
+
return "repo";
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
async function performAgentSync(targetDir, assetsDir, roster, options) {
|
|
2312
|
+
const scope = options.scope ?? "repo";
|
|
2313
|
+
const spellScope = scope === "user" ? void 0 : await repoSpellScope(targetDir);
|
|
2314
|
+
const result = await syncAgents(targetDir, assetsDir, roster, {
|
|
2315
|
+
...options,
|
|
2316
|
+
scope,
|
|
2317
|
+
...spellScope === void 0 ? {} : { spellScope }
|
|
2318
|
+
});
|
|
2319
|
+
if (scope === "user") {
|
|
2320
|
+
const manifest = await requireUserStore(targetDir);
|
|
2321
|
+
const fanout = await syncUserTierFanout({
|
|
2322
|
+
homeDir: dirname2(targetDir),
|
|
2323
|
+
storeRoot: targetDir,
|
|
2324
|
+
spellIds: [],
|
|
2325
|
+
extraFiles: result.userAgentFiles,
|
|
2326
|
+
ownedClients: ["copilot-agents"],
|
|
2327
|
+
previous: manifest.fanout,
|
|
2328
|
+
dryRun: options.dryRun,
|
|
2329
|
+
fallbackDir: assetsDir
|
|
2330
|
+
});
|
|
2331
|
+
for (const line of describeFanoutOutcomes(fanout.outcomes, options.dryRun)) {
|
|
2332
|
+
console.log(` ${line}`);
|
|
2333
|
+
}
|
|
2334
|
+
if (!options.dryRun) {
|
|
2335
|
+
await writeManifest(targetDir, { ...manifest, fanout: fanout.record });
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
if (result.unmanagedAgentFiles.length > 0) {
|
|
2339
|
+
console.log(
|
|
2340
|
+
`
|
|
2341
|
+
! This repository takes its spells from the user tier (spell_scope: "user"), so it no
|
|
2342
|
+
longer receives agent files of its own. ${result.unmanagedAgentFiles.length} are still on disk
|
|
2343
|
+
from before the opt-out. Nothing here deletes them -- no component ever tracked
|
|
2344
|
+
them, so there is no recorded hash to prove one untouched. Remove them yourself:
|
|
2345
|
+
`
|
|
2346
|
+
);
|
|
2347
|
+
for (const file of result.unmanagedAgentFiles) console.log(` ${file}`);
|
|
2348
|
+
console.log(`
|
|
2349
|
+
git rm ${result.unmanagedAgentFiles.join(" ")}
|
|
2350
|
+
`);
|
|
2351
|
+
}
|
|
2352
|
+
return result;
|
|
2353
|
+
}
|
|
2240
2354
|
async function runAgentsInit(options, targetDir, assetsDir) {
|
|
2241
|
-
|
|
2355
|
+
const scope = options.scope ?? "repo";
|
|
2356
|
+
if (scope === "user") await requireUserStore(targetDir);
|
|
2357
|
+
const baseDir = agentsBaseDir(targetDir, scope);
|
|
2358
|
+
const label = scope === "user" ? "~/.arcane" : ".arcane";
|
|
2359
|
+
if (!options.force && await rosterExists(targetDir, scope)) {
|
|
2242
2360
|
console.log(
|
|
2243
2361
|
'Agent roster already exists at .arcane/agents.yaml. Run "spell agents sync" to regenerate outputs, or use --force to reinitialize.'
|
|
2244
2362
|
);
|
|
@@ -2332,16 +2450,12 @@ All 12 roles across ${allCategories.join(", ")}` : "\nPick individual roles from
|
|
|
2332
2450
|
},
|
|
2333
2451
|
roster: rosterEntries
|
|
2334
2452
|
};
|
|
2335
|
-
const arcaneDir = join4(
|
|
2453
|
+
const arcaneDir = join4(baseDir, "agents");
|
|
2336
2454
|
if (!options.dryRun) {
|
|
2337
2455
|
await mkdir4(arcaneDir, { recursive: true });
|
|
2338
|
-
await writeFile6(
|
|
2339
|
-
join4(targetDir, ".arcane", "agents.yaml"),
|
|
2340
|
-
stringify(roster),
|
|
2341
|
-
"utf8"
|
|
2342
|
-
);
|
|
2456
|
+
await writeFile6(join4(baseDir, "agents.yaml"), stringify(roster), "utf8");
|
|
2343
2457
|
} else {
|
|
2344
|
-
console.log(
|
|
2458
|
+
console.log(` [dry-run] Would write: ${label}/agents.yaml`);
|
|
2345
2459
|
}
|
|
2346
2460
|
for (const roleId of selectedRoleIds) {
|
|
2347
2461
|
const src = join4(assetsDir, "agents", `${roleId}.yaml`);
|
|
@@ -2353,7 +2467,7 @@ All 12 roles across ${allCategories.join(", ")}` : "\nPick individual roles from
|
|
|
2353
2467
|
console.warn(` ! Could not copy definition for "${roleId}" \u2014 skipping`);
|
|
2354
2468
|
}
|
|
2355
2469
|
} else {
|
|
2356
|
-
console.log(` [dry-run] Would copy:
|
|
2470
|
+
console.log(` [dry-run] Would copy: ${label}/agents/${roleId}.yaml`);
|
|
2357
2471
|
}
|
|
2358
2472
|
}
|
|
2359
2473
|
if (options.dryRun) {
|
|
@@ -2364,20 +2478,24 @@ All 12 roles across ${allCategories.join(", ")}` : "\nPick individual roles from
|
|
|
2364
2478
|
return;
|
|
2365
2479
|
}
|
|
2366
2480
|
printStep("Syncing agent definitions to all clients...");
|
|
2367
|
-
const { synced, skipped, hasUnresolvedRoles } = await
|
|
2481
|
+
const { synced, skipped, hasUnresolvedRoles } = await performAgentSync(
|
|
2368
2482
|
targetDir,
|
|
2369
2483
|
assetsDir,
|
|
2370
2484
|
roster,
|
|
2371
|
-
{ dryRun: false }
|
|
2485
|
+
{ dryRun: false, scope }
|
|
2372
2486
|
);
|
|
2373
2487
|
printSuccess("Agent roster initialized");
|
|
2374
2488
|
console.log(` \u{1F4CB} Profile: ${profileId} \xB7 Naming: ${namingStrategy} \xB7 Agents: ${rosterEntries.length}`);
|
|
2375
|
-
console.log(
|
|
2489
|
+
console.log(
|
|
2490
|
+
` \u{1F504} Synced: ${synced.length} outputs (${scope === "user" ? "Copilot agent modes in ~/.copilot/agents" : "Copilot, Claude, Codex, OpenClaw"})`
|
|
2491
|
+
);
|
|
2376
2492
|
if (skipped.length > 0) {
|
|
2377
2493
|
console.log(` \u26A0\uFE0F Skipped: ${skipped.length} (${skipped.join(", ")})`);
|
|
2378
2494
|
}
|
|
2379
2495
|
console.log();
|
|
2380
|
-
console.log(
|
|
2496
|
+
console.log(
|
|
2497
|
+
` \u{1F4A1} Customize: edit ${label}/agents.yaml \u2192 spell agents sync${scope === "user" ? " --user" : ""}`
|
|
2498
|
+
);
|
|
2381
2499
|
console.log();
|
|
2382
2500
|
if (hasUnresolvedRoles) {
|
|
2383
2501
|
console.error(
|
|
@@ -2387,9 +2505,10 @@ All 12 roles across ${allCategories.join(", ")}` : "\nPick individual roles from
|
|
|
2387
2505
|
}
|
|
2388
2506
|
}
|
|
2389
2507
|
async function runAgentsSync(options, targetDir, assetsDir) {
|
|
2508
|
+
const scope = options.scope ?? "repo";
|
|
2390
2509
|
let roster;
|
|
2391
2510
|
try {
|
|
2392
|
-
roster = await loadRoster(targetDir);
|
|
2511
|
+
roster = await loadRoster(targetDir, scope);
|
|
2393
2512
|
} catch (err) {
|
|
2394
2513
|
if (err instanceof AgentRosterNotFoundError || err instanceof AgentConfigValidationError) {
|
|
2395
2514
|
console.error(err.message);
|
|
@@ -2403,11 +2522,11 @@ async function runAgentsSync(options, targetDir, assetsDir) {
|
|
|
2403
2522
|
} else {
|
|
2404
2523
|
console.log("\n Syncing agents...\n");
|
|
2405
2524
|
}
|
|
2406
|
-
const { synced, skipped, hasUnresolvedRoles } = await
|
|
2525
|
+
const { synced, skipped, hasUnresolvedRoles } = await performAgentSync(
|
|
2407
2526
|
targetDir,
|
|
2408
2527
|
assetsDir,
|
|
2409
2528
|
roster,
|
|
2410
|
-
options
|
|
2529
|
+
{ ...options, scope }
|
|
2411
2530
|
);
|
|
2412
2531
|
if (options.dryRun) {
|
|
2413
2532
|
console.log(`
|
|
@@ -2434,10 +2553,10 @@ async function runAgentsSync(options, targetDir, assetsDir) {
|
|
|
2434
2553
|
process.exitCode = 1;
|
|
2435
2554
|
}
|
|
2436
2555
|
}
|
|
2437
|
-
async function runAgentsList(targetDir) {
|
|
2556
|
+
async function runAgentsList(targetDir, scope = "repo") {
|
|
2438
2557
|
let roster;
|
|
2439
2558
|
try {
|
|
2440
|
-
roster = await loadRoster(targetDir);
|
|
2559
|
+
roster = await loadRoster(targetDir, scope);
|
|
2441
2560
|
} catch (err) {
|
|
2442
2561
|
if (err instanceof AgentRosterNotFoundError || err instanceof AgentConfigValidationError) {
|
|
2443
2562
|
console.error(err.message);
|
|
@@ -2479,9 +2598,9 @@ async function runAgentsList(targetDir) {
|
|
|
2479
2598
|
}
|
|
2480
2599
|
|
|
2481
2600
|
// src/modules/push-safety.ts
|
|
2482
|
-
import { mkdir as mkdir5, readFile as readFile6, writeFile as writeFile7, chmod, access as access3, rm as rm4, readdir as
|
|
2601
|
+
import { mkdir as mkdir5, readFile as readFile6, writeFile as writeFile7, chmod, access as access3, rm as rm4, readdir as readdir5 } from "fs/promises";
|
|
2483
2602
|
import { constants } from "fs";
|
|
2484
|
-
import { join as join5, resolve as resolve2, dirname as
|
|
2603
|
+
import { join as join5, resolve as resolve2, dirname as dirname3, basename } from "path";
|
|
2485
2604
|
|
|
2486
2605
|
// src/modules/git.ts
|
|
2487
2606
|
import { execFile } from "child_process";
|
|
@@ -2778,7 +2897,7 @@ async function repositoryRoot(cwd) {
|
|
|
2778
2897
|
]);
|
|
2779
2898
|
const commonDir = stdout.trim();
|
|
2780
2899
|
if (commonDir !== "") {
|
|
2781
|
-
const parent =
|
|
2900
|
+
const parent = dirname3(commonDir);
|
|
2782
2901
|
if (basename(commonDir) === ".git" && parent !== "") return parent;
|
|
2783
2902
|
}
|
|
2784
2903
|
} catch {
|
|
@@ -2868,7 +2987,7 @@ async function defaultHooks(cwd) {
|
|
|
2868
2987
|
return [];
|
|
2869
2988
|
}
|
|
2870
2989
|
try {
|
|
2871
|
-
return (await
|
|
2990
|
+
return (await readdir5(hooksDir)).filter((name) => !name.endsWith(".sample")).sort();
|
|
2872
2991
|
} catch {
|
|
2873
2992
|
return [];
|
|
2874
2993
|
}
|
|
@@ -2902,7 +3021,7 @@ async function removeHook(cwd, hookName) {
|
|
|
2902
3021
|
const hooksDir = join5(root, ARCANE_HOOKS_DIR);
|
|
2903
3022
|
let remaining = [];
|
|
2904
3023
|
try {
|
|
2905
|
-
remaining = await
|
|
3024
|
+
remaining = await readdir5(hooksDir);
|
|
2906
3025
|
} catch {
|
|
2907
3026
|
remaining = [];
|
|
2908
3027
|
}
|
|
@@ -3667,7 +3786,7 @@ async function runInitUser(options, storeRoot, assetsDir, packageVersion) {
|
|
|
3667
3786
|
console.log(chalk2.hex("#a855f7")(`
|
|
3668
3787
|
\u2726 Arcane v${packageVersion}
|
|
3669
3788
|
`));
|
|
3670
|
-
const homeDir =
|
|
3789
|
+
const homeDir = dirname4(storeRoot);
|
|
3671
3790
|
const components = USER_TIER_COMPONENTS.map(
|
|
3672
3791
|
(name) => componentForScope(getComponent(name), "user")
|
|
3673
3792
|
);
|
|
@@ -3715,7 +3834,7 @@ async function runInitUser(options, storeRoot, assetsDir, packageVersion) {
|
|
|
3715
3834
|
fileHashes
|
|
3716
3835
|
});
|
|
3717
3836
|
}
|
|
3718
|
-
const fanout = await syncUserTierFanout({ homeDir, storeRoot, spellIds, fallbackDir: assetsDir });
|
|
3837
|
+
const fanout = await syncUserTierFanout({ homeDir, storeRoot, spellIds, fallbackDir: assetsDir, ownedClients: SPELL_FANOUT_CLIENTS });
|
|
3719
3838
|
const manifest = {
|
|
3720
3839
|
version: packageVersion,
|
|
3721
3840
|
// The tier is un-profiled; "full" records the profile its component set
|
|
@@ -3829,12 +3948,12 @@ async function runAdd(name, options, targetDir, assetsDir, packageVersion) {
|
|
|
3829
3948
|
}
|
|
3830
3949
|
|
|
3831
3950
|
// src/commands/update.ts
|
|
3832
|
-
import { dirname as
|
|
3951
|
+
import { dirname as dirname5, join as join9 } from "path";
|
|
3833
3952
|
import { readFile as readFile7, writeFile as writeFile9 } from "fs/promises";
|
|
3834
3953
|
|
|
3835
3954
|
// src/modules/hub.ts
|
|
3836
3955
|
import { confirm as confirm2, select as select3, input as input3 } from "@inquirer/prompts";
|
|
3837
|
-
import { readdir as
|
|
3956
|
+
import { readdir as readdir6, writeFile as writeFile8 } from "fs/promises";
|
|
3838
3957
|
import { join as join8 } from "path";
|
|
3839
3958
|
var MANIFEST_RETROFITS = [
|
|
3840
3959
|
{
|
|
@@ -3979,7 +4098,7 @@ async function offerRegistryScaffold(targetDir, businessRoot) {
|
|
|
3979
4098
|
const businessRootPath = join8(targetDir, businessRoot);
|
|
3980
4099
|
let ventureDirs;
|
|
3981
4100
|
try {
|
|
3982
|
-
const entries = await
|
|
4101
|
+
const entries = await readdir6(businessRootPath, { withFileTypes: true });
|
|
3983
4102
|
ventureDirs = entries.filter((e) => e.isDirectory() && e.name !== "_template").map((e) => e.name);
|
|
3984
4103
|
} catch {
|
|
3985
4104
|
return;
|
|
@@ -4275,7 +4394,7 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
|
|
|
4275
4394
|
const scope = resolvedScope.scope;
|
|
4276
4395
|
const spellScope = effectiveSpellScope(manifest);
|
|
4277
4396
|
const readopted = findReadoptedSpellComponents(manifest.components, scope, spellScope);
|
|
4278
|
-
const homeDir =
|
|
4397
|
+
const homeDir = dirname5(targetDir);
|
|
4279
4398
|
if (scope === "repo") {
|
|
4280
4399
|
console.warn(
|
|
4281
4400
|
`WARNING: Arcane v${packageVersion} update safety notice: commit your work before updating.`
|
|
@@ -4314,6 +4433,7 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
|
|
|
4314
4433
|
console.log("Already up to date.");
|
|
4315
4434
|
if (scope === "user") {
|
|
4316
4435
|
const fanout = await syncUserTierFanout({
|
|
4436
|
+
ownedClients: SPELL_FANOUT_CLIENTS,
|
|
4317
4437
|
homeDir,
|
|
4318
4438
|
storeRoot: targetDir,
|
|
4319
4439
|
spellIds: spellIdsInStore(manifest.components),
|
|
@@ -4526,6 +4646,7 @@ ${verb} ${orphansToReport.length} orphaned file(s) (tracked before this update,
|
|
|
4526
4646
|
let fanoutRecord;
|
|
4527
4647
|
if (scope === "user") {
|
|
4528
4648
|
const fanout = await syncUserTierFanout({
|
|
4649
|
+
ownedClients: SPELL_FANOUT_CLIENTS,
|
|
4529
4650
|
homeDir,
|
|
4530
4651
|
storeRoot: targetDir,
|
|
4531
4652
|
spellIds: spellIdsInStore(updatedComponents),
|
|
@@ -4589,11 +4710,11 @@ ${verb} ${orphansToReport.length} orphaned file(s) (tracked before this update,
|
|
|
4589
4710
|
}
|
|
4590
4711
|
|
|
4591
4712
|
// src/commands/status.ts
|
|
4592
|
-
import { dirname as
|
|
4713
|
+
import { dirname as dirname7 } from "path";
|
|
4593
4714
|
|
|
4594
4715
|
// src/modules/version-check.ts
|
|
4595
4716
|
import { readFile as readFile8, writeFile as writeFile10, mkdir as mkdir6 } from "fs/promises";
|
|
4596
|
-
import { dirname as
|
|
4717
|
+
import { dirname as dirname6, join as join10 } from "path";
|
|
4597
4718
|
import { homedir as homedir3 } from "os";
|
|
4598
4719
|
import { createRequire } from "module";
|
|
4599
4720
|
var require2 = createRequire(import.meta.url);
|
|
@@ -4622,7 +4743,7 @@ async function readCache(cachePath) {
|
|
|
4622
4743
|
}
|
|
4623
4744
|
}
|
|
4624
4745
|
async function writeCache(cachePath, latest) {
|
|
4625
|
-
await mkdir6(
|
|
4746
|
+
await mkdir6(dirname6(cachePath), { recursive: true });
|
|
4626
4747
|
const entry = { latest, checkedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
4627
4748
|
await writeFile10(cachePath, JSON.stringify(entry, null, 2), "utf-8");
|
|
4628
4749
|
}
|
|
@@ -4763,7 +4884,7 @@ async function runStatus(targetDir, packageVersion, options = {}) {
|
|
|
4763
4884
|
console.log("");
|
|
4764
4885
|
if (scope === "user") {
|
|
4765
4886
|
console.log(` Scope: user \u2014 ${targetDir}`);
|
|
4766
|
-
const health = await inspectUserTierFanout(
|
|
4887
|
+
const health = await inspectUserTierFanout(dirname7(targetDir), manifest.fanout);
|
|
4767
4888
|
console.log(
|
|
4768
4889
|
` Client files: ${health.total} (${health.byClient.codex} ${FANOUT_CLIENT_LABELS.codex}, ${health.byClient.claude} ${FANOUT_CLIENT_LABELS.claude})`
|
|
4769
4890
|
);
|
|
@@ -4826,7 +4947,7 @@ async function runStatus(targetDir, packageVersion, options = {}) {
|
|
|
4826
4947
|
|
|
4827
4948
|
// src/commands/uninstall.ts
|
|
4828
4949
|
import { rm as rm5 } from "fs/promises";
|
|
4829
|
-
import { dirname as
|
|
4950
|
+
import { dirname as dirname8, join as join11 } from "path";
|
|
4830
4951
|
import { confirm as confirm3 } from "@inquirer/prompts";
|
|
4831
4952
|
async function runUninstall(options, targetDir) {
|
|
4832
4953
|
let manifest;
|
|
@@ -4935,7 +5056,7 @@ Run \`spell unblock-push\` first (interactive), then uninstall.`
|
|
|
4935
5056
|
\u2713 Uninstalled \u2014 ${removed} files removed.`);
|
|
4936
5057
|
}
|
|
4937
5058
|
async function uninstallUserTier(options, storeRoot, manifest) {
|
|
4938
|
-
const homeDir =
|
|
5059
|
+
const homeDir = dirname8(storeRoot);
|
|
4939
5060
|
const storeFiles = manifest.components.flatMap((c) => c.files);
|
|
4940
5061
|
const recordedCount = Object.keys(manifest.fanout ?? {}).length;
|
|
4941
5062
|
if (options.dryRun) {
|
|
@@ -4981,6 +5102,8 @@ async function uninstallUserTier(options, storeRoot, manifest) {
|
|
|
4981
5102
|
}
|
|
4982
5103
|
}
|
|
4983
5104
|
await rm5(join11(storeRoot, ".arcane.json"), { force: true });
|
|
5105
|
+
await rm5(join11(storeRoot, "agents"), { recursive: true, force: true });
|
|
5106
|
+
await rm5(join11(storeRoot, "agents.yaml"), { force: true });
|
|
4984
5107
|
await removeDirectoryIfEmpty(join11(storeRoot, USER_TIER_SPELLS_DIR));
|
|
4985
5108
|
await removeDirectoryIfEmpty(storeRoot);
|
|
4986
5109
|
for (const line of describeFanoutOutcomes(fanout.outcomes)) console.log(` ${line}`);
|
|
@@ -4993,8 +5116,8 @@ async function uninstallUserTier(options, storeRoot, manifest) {
|
|
|
4993
5116
|
}
|
|
4994
5117
|
|
|
4995
5118
|
// src/commands/doctor.ts
|
|
4996
|
-
import { access as access4, readdir as
|
|
4997
|
-
import { join as join13, dirname as
|
|
5119
|
+
import { access as access4, readdir as readdir8, readFile as readFile10, mkdir as mkdir7, copyFile as fsCopyFile } from "fs/promises";
|
|
5120
|
+
import { join as join13, dirname as dirname9 } from "path";
|
|
4998
5121
|
import { execFile as execFile3 } from "child_process";
|
|
4999
5122
|
import { promisify as promisify2 } from "util";
|
|
5000
5123
|
|
|
@@ -5064,7 +5187,7 @@ function evaluateIncidentGate(records) {
|
|
|
5064
5187
|
}
|
|
5065
5188
|
|
|
5066
5189
|
// src/modules/denylist-scan.ts
|
|
5067
|
-
import { readFile as readFile9, readdir as
|
|
5190
|
+
import { readFile as readFile9, readdir as readdir7 } from "fs/promises";
|
|
5068
5191
|
import { join as join12, relative } from "path";
|
|
5069
5192
|
function escapeRegExp(value) {
|
|
5070
5193
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -5111,7 +5234,7 @@ async function collectScannableFiles(root, repoRoot = root, extensions = DEFAULT
|
|
|
5111
5234
|
const collected = [];
|
|
5112
5235
|
let entries;
|
|
5113
5236
|
try {
|
|
5114
|
-
entries = await
|
|
5237
|
+
entries = await readdir7(root, { withFileTypes: true });
|
|
5115
5238
|
} catch {
|
|
5116
5239
|
return collected;
|
|
5117
5240
|
}
|
|
@@ -5333,8 +5456,8 @@ async function checkVSCodeExtension(id, label) {
|
|
|
5333
5456
|
];
|
|
5334
5457
|
for (const dir of extDirs) {
|
|
5335
5458
|
try {
|
|
5336
|
-
const { readdir:
|
|
5337
|
-
const entries = await
|
|
5459
|
+
const { readdir: readdir11 } = await import("fs/promises");
|
|
5460
|
+
const entries = await readdir11(dir);
|
|
5338
5461
|
const prefix = id.toLowerCase() + "-";
|
|
5339
5462
|
const found = entries.some(
|
|
5340
5463
|
(e) => e.toLowerCase() === id.toLowerCase() || e.toLowerCase().startsWith(prefix)
|
|
@@ -5496,7 +5619,7 @@ async function fixSessionContinuity(targetDir, assetsDir) {
|
|
|
5496
5619
|
await access4(destPath);
|
|
5497
5620
|
} catch {
|
|
5498
5621
|
const srcPath = join13(assetsDir, file);
|
|
5499
|
-
await mkdir7(
|
|
5622
|
+
await mkdir7(dirname9(destPath), { recursive: true });
|
|
5500
5623
|
await fsCopyFile(srcPath, destPath);
|
|
5501
5624
|
created.push(file);
|
|
5502
5625
|
}
|
|
@@ -5789,7 +5912,7 @@ async function checkSpellScope(targetDir, homeDir = resolveHomeDir()) {
|
|
|
5789
5912
|
let probed = false;
|
|
5790
5913
|
const probe = "spell-status.md";
|
|
5791
5914
|
try {
|
|
5792
|
-
const entries = await
|
|
5915
|
+
const entries = await readdir8(join13(storeRoot, USER_TIER_SPELLS_DIR));
|
|
5793
5916
|
const spells = entries.filter((e) => e.startsWith("spell-") && e.endsWith(".md"));
|
|
5794
5917
|
spellCount = spells.length;
|
|
5795
5918
|
probed = spells.includes(probe);
|
|
@@ -5811,11 +5934,19 @@ async function checkSpellScope(targetDir, homeDir = resolveHomeDir()) {
|
|
|
5811
5934
|
message: `this repository is at v${manifest.version} but the user tier it uses is at v${store.version} \u2014 run \`spell update --user\` (and \`spell update\` here) to bring them together.`
|
|
5812
5935
|
};
|
|
5813
5936
|
}
|
|
5937
|
+
const repoExpectsAgents = await rosterExists(targetDir);
|
|
5938
|
+
if (repoExpectsAgents && !await rosterExists(storeRoot, "user")) {
|
|
5939
|
+
return {
|
|
5940
|
+
name,
|
|
5941
|
+
passed: false,
|
|
5942
|
+
message: `this repository has an agent roster and takes its client files from the user tier, but ${storeRoot} has no roster of its own \u2014 no agent mode can reach a client here. Run \`spell agents init --user\`, or set spell_scope back to "repo" and run \`spell agents sync\`.`
|
|
5943
|
+
};
|
|
5944
|
+
}
|
|
5814
5945
|
return {
|
|
5815
5946
|
name,
|
|
5816
5947
|
passed: true,
|
|
5817
5948
|
blocking: false,
|
|
5818
|
-
message: `user \u2014 ${spellCount} spell(s) from ${storeRoot} (v${store.version}), ${probe} present`
|
|
5949
|
+
message: `user \u2014 ${spellCount} spell(s) from ${storeRoot} (v${store.version}), ${probe} present${repoExpectsAgents ? ", agent roster present in the tier" : ""}`
|
|
5819
5950
|
};
|
|
5820
5951
|
}
|
|
5821
5952
|
async function runDoctor(targetDir, options = {}, assetsDir) {
|
|
@@ -5892,7 +6023,7 @@ async function runDoctor(targetDir, options = {}, assetsDir) {
|
|
|
5892
6023
|
import { readFile as readFile12 } from "fs/promises";
|
|
5893
6024
|
|
|
5894
6025
|
// src/modules/ward.ts
|
|
5895
|
-
import { readdir as
|
|
6026
|
+
import { readdir as readdir9, readFile as readFile11 } from "fs/promises";
|
|
5896
6027
|
import { join as join14, relative as relative2 } from "path";
|
|
5897
6028
|
async function scanFileAllRules(absolutePath, displayPath, rules) {
|
|
5898
6029
|
const findings = [];
|
|
@@ -5951,7 +6082,7 @@ async function scanFilenames(root, rules, repoRoot = root) {
|
|
|
5951
6082
|
const findings = [];
|
|
5952
6083
|
let entries;
|
|
5953
6084
|
try {
|
|
5954
|
-
entries = await
|
|
6085
|
+
entries = await readdir9(root, { withFileTypes: true });
|
|
5955
6086
|
} catch {
|
|
5956
6087
|
return findings;
|
|
5957
6088
|
}
|
|
@@ -6173,8 +6304,8 @@ import { access as access5 } from "fs/promises";
|
|
|
6173
6304
|
import { join as join17 } from "path";
|
|
6174
6305
|
|
|
6175
6306
|
// src/modules/show-report/generate.ts
|
|
6176
|
-
import { mkdir as mkdir8, readdir as
|
|
6177
|
-
import { basename as basename3, dirname as
|
|
6307
|
+
import { mkdir as mkdir8, readdir as readdir10, readFile as readFile14, writeFile as writeFile11 } from "fs/promises";
|
|
6308
|
+
import { basename as basename3, dirname as dirname10, isAbsolute, join as join16, relative as relative3, resolve as resolve4 } from "path";
|
|
6178
6309
|
|
|
6179
6310
|
// src/modules/show-report/model.ts
|
|
6180
6311
|
import { readFile as readFile13 } from "fs/promises";
|
|
@@ -6731,7 +6862,7 @@ async function programFromPlan(rootDir, slug, planRelPath) {
|
|
|
6731
6862
|
program: {
|
|
6732
6863
|
slug,
|
|
6733
6864
|
planRelPath,
|
|
6734
|
-
queueRelPath: toPosix(join16(
|
|
6865
|
+
queueRelPath: toPosix(join16(dirname10(planRelPath), "OPERATOR-QUEUE.md")),
|
|
6735
6866
|
ledgerProgramName: programNameFromTitle(frontmatter.title ?? slug)
|
|
6736
6867
|
}
|
|
6737
6868
|
};
|
|
@@ -6740,7 +6871,7 @@ async function discoverPrograms(rootDir) {
|
|
|
6740
6871
|
const plansDir = join16(rootDir, "docs", "plans");
|
|
6741
6872
|
let entries;
|
|
6742
6873
|
try {
|
|
6743
|
-
entries = (await
|
|
6874
|
+
entries = (await readdir10(plansDir, { withFileTypes: true })).filter((e) => e.isDirectory());
|
|
6744
6875
|
} catch (error) {
|
|
6745
6876
|
if (error.code === "ENOENT") return { programs: [], skipped: [] };
|
|
6746
6877
|
throw error;
|
|
@@ -6757,7 +6888,7 @@ async function discoverPrograms(rootDir) {
|
|
|
6757
6888
|
async function programFromPlanPath(rootDir, planPath) {
|
|
6758
6889
|
const planAbs = isAbsolute(planPath) ? planPath : resolve4(rootDir, planPath);
|
|
6759
6890
|
const planRelPath = toPosix(relative3(rootDir, planAbs));
|
|
6760
|
-
return programFromPlan(rootDir, basename3(
|
|
6891
|
+
return programFromPlan(rootDir, basename3(dirname10(planAbs)), planRelPath);
|
|
6761
6892
|
}
|
|
6762
6893
|
function normalizeLineEndings2(content) {
|
|
6763
6894
|
return content.replace(/\r\n?/g, "\n");
|
|
@@ -6797,7 +6928,7 @@ async function runReportGeneration(options) {
|
|
|
6797
6928
|
const generated = await generateReport(options.rootDir, program2, templateHtml);
|
|
6798
6929
|
const missing = JSON.parse(generated.json).sections.flatMap((section) => section.rows).filter((row) => row.descriptionState === "unwritten").map((row) => row.id);
|
|
6799
6930
|
if (missing.length > 0) unwritten.push({ slug: program2.slug, ids: missing });
|
|
6800
|
-
const outAbs = options.outDir ? isAbsolute(options.outDir) ? options.outDir : resolve4(options.rootDir, options.outDir) : resolve4(options.rootDir,
|
|
6931
|
+
const outAbs = options.outDir ? isAbsolute(options.outDir) ? options.outDir : resolve4(options.rootDir, options.outDir) : resolve4(options.rootDir, dirname10(program2.planRelPath));
|
|
6801
6932
|
const outRel = toPosix(relative3(options.rootDir, outAbs));
|
|
6802
6933
|
const outputs = [
|
|
6803
6934
|
[`${outRel}/show-report.json`, generated.json],
|
|
@@ -6883,9 +7014,10 @@ async function runReport(targetDir, options = {}, assetsDir) {
|
|
|
6883
7014
|
import { realpathSync } from "fs";
|
|
6884
7015
|
var require3 = createRequire2(import.meta.url);
|
|
6885
7016
|
var pkg = require3("../package.json");
|
|
6886
|
-
var ASSETS_DIR = process.env["ARCANE_ASSETS_DIR"] ?? join18(
|
|
7017
|
+
var ASSETS_DIR = process.env["ARCANE_ASSETS_DIR"] ?? join18(dirname11(fileURLToPath(import.meta.url)), "assets");
|
|
6887
7018
|
var program = new Command();
|
|
6888
7019
|
var USER_FLAG_DESCRIPTION = "Operate on the per-user tier at ~/.arcane \u2014 spells shared by every repository on this machine, fanned out to ~/.agents/skills (Codex, Copilot) and ~/.claude/commands (Claude Code)";
|
|
7020
|
+
var USER_TIER_AGENTS_DESCRIPTION = "Operate on the per-user tier at ~/.arcane \u2014 one agent roster shared by every repository on this machine, rendered to ~/.copilot/agents (VS Code agent modes)";
|
|
6889
7021
|
function targetDirFor(opts) {
|
|
6890
7022
|
return opts.user ? userTierRoot() : process.cwd();
|
|
6891
7023
|
}
|
|
@@ -6987,39 +7119,44 @@ agentsCommand.command("init").description(
|
|
|
6987
7119
|
).option(
|
|
6988
7120
|
"--naming <strategy>",
|
|
6989
7121
|
"Naming strategy: arcanos | generic | random | custom"
|
|
6990
|
-
).option("--force", "Reinitialize even if roster already exists").option("--dry-run", "Preview what would be created without writing files").action(
|
|
7122
|
+
).option("--force", "Reinitialize even if roster already exists").option("--dry-run", "Preview what would be created without writing files").option("--user", USER_TIER_AGENTS_DESCRIPTION).action(
|
|
6991
7123
|
async (opts) => {
|
|
7124
|
+
const scope = opts.user ? "user" : "repo";
|
|
6992
7125
|
await runAgentsInit(
|
|
6993
7126
|
{
|
|
6994
7127
|
profile: opts.profile,
|
|
6995
7128
|
naming: opts.naming,
|
|
6996
7129
|
force: opts.force,
|
|
6997
|
-
dryRun: opts.dryRun
|
|
7130
|
+
dryRun: opts.dryRun,
|
|
7131
|
+
scope
|
|
6998
7132
|
},
|
|
6999
|
-
process.cwd(),
|
|
7133
|
+
agentsTargetDir(scope, process.cwd()),
|
|
7000
7134
|
ASSETS_DIR
|
|
7001
7135
|
);
|
|
7002
7136
|
}
|
|
7003
7137
|
);
|
|
7004
7138
|
agentsCommand.command("sync").description(
|
|
7005
7139
|
"Regenerate all client instruction files from canonical agent definitions"
|
|
7006
|
-
).option("--dry-run", "Preview what would be generated without writing files").option("--no-openclaw", "Skip OpenClaw workspace output").option("--no-copilot", "Skip Copilot output").option("--no-claude", "Skip Claude output").option("--no-codex", "Skip Codex output").action(
|
|
7140
|
+
).option("--dry-run", "Preview what would be generated without writing files").option("--no-openclaw", "Skip OpenClaw workspace output").option("--no-copilot", "Skip Copilot output").option("--no-claude", "Skip Claude output").option("--no-codex", "Skip Codex output").option("--user", USER_TIER_AGENTS_DESCRIPTION).action(
|
|
7007
7141
|
async (opts) => {
|
|
7142
|
+
const scope = opts.user ? "user" : "repo";
|
|
7008
7143
|
await runAgentsSync(
|
|
7009
7144
|
{
|
|
7010
7145
|
dryRun: opts.dryRun,
|
|
7011
7146
|
openclaw: opts.openclaw,
|
|
7012
7147
|
copilot: opts.copilot,
|
|
7013
7148
|
claude: opts.claude,
|
|
7014
|
-
codex: opts.codex
|
|
7149
|
+
codex: opts.codex,
|
|
7150
|
+
scope
|
|
7015
7151
|
},
|
|
7016
|
-
process.cwd(),
|
|
7152
|
+
agentsTargetDir(scope, process.cwd()),
|
|
7017
7153
|
ASSETS_DIR
|
|
7018
7154
|
);
|
|
7019
7155
|
}
|
|
7020
7156
|
);
|
|
7021
|
-
agentsCommand.command("list").description("Show the current agent roster with assigned names and roles").action(async () => {
|
|
7022
|
-
|
|
7157
|
+
agentsCommand.command("list").description("Show the current agent roster with assigned names and roles").option("--user", USER_TIER_AGENTS_DESCRIPTION).action(async (opts) => {
|
|
7158
|
+
const scope = opts.user ? "user" : "repo";
|
|
7159
|
+
await runAgentsList(agentsTargetDir(scope, process.cwd()), scope);
|
|
7023
7160
|
});
|
|
7024
7161
|
if (realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
7025
7162
|
await program.parseAsync(process.argv);
|