aas-setup 1.0.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 +21 -0
- package/README.md +73 -0
- package/bin/aas-setup.mjs +34 -0
- package/configs/opencode/agent/ai-slop-remover.md +55 -0
- package/configs/opencode/agent/docs-writer.md +15 -0
- package/configs/opencode/agent/review.md +18 -0
- package/configs/opencode/agent/security-audit.md +17 -0
- package/configs/opencode/command/batch.md +58 -0
- package/configs/opencode/command/plannotator-annotate.md +6 -0
- package/configs/opencode/command/plannotator-last.md +3 -0
- package/configs/opencode/command/plannotator-review.md +6 -0
- package/configs/opencode/command/simplify.md +47 -0
- package/configs/opencode/opencode.json +137 -0
- package/configs/pi/AGENTS.md +49 -0
- package/configs/pi/mcp.json +24 -0
- package/configs/pi/models.json +0 -0
- package/configs/pi/settings.json +37 -0
- package/configs/pi/themes/dracula.json +87 -0
- package/configs/pi/themes/kanagawa.json +88 -0
- package/configs/reference/MEMORY.md +239 -0
- package/configs/reference/agent-memory.md +5 -0
- package/configs/reference/best-practices.md +270 -0
- package/configs/reference/git-guidelines.md +62 -0
- package/package.json +37 -0
- package/src/agents/opencode.mjs +22 -0
- package/src/agents/pi.mjs +22 -0
- package/src/agents/types.mjs +30 -0
- package/src/commands/init.mjs +99 -0
- package/src/commands/select.mjs +52 -0
- package/src/lib/exec.mjs +35 -0
- package/src/lib/fs.mjs +107 -0
- package/src/lib/log.mjs +39 -0
- package/src/lib/paths.mjs +47 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Git Safety Guidelines
|
|
2
|
+
|
|
3
|
+
## Safe Git Operations
|
|
4
|
+
|
|
5
|
+
AI agents should follow these principles when working with git:
|
|
6
|
+
|
|
7
|
+
### ✅ Allowed Operations
|
|
8
|
+
|
|
9
|
+
- **Read operations**: `git status`, `git log`, `git diff`, `git show`
|
|
10
|
+
- **Safe commits**: `git add`, `git commit`
|
|
11
|
+
- **Branch management**: `git branch`, `git checkout -b`, `git switch`
|
|
12
|
+
- **Safe push**: `git push` (standard push without force)
|
|
13
|
+
- **Inspection**: `git blame`, `git ls-files`, `git rev-parse`
|
|
14
|
+
|
|
15
|
+
### ⛔ Operations to Avoid
|
|
16
|
+
|
|
17
|
+
Avoid these dangerous git commands without explicit user approval:
|
|
18
|
+
|
|
19
|
+
- **Add all files**: `git add -A`, `git add --all` (stages all changes including untracked files; prefer `git add <specific-files>` or `git add -p` for interactive staging)
|
|
20
|
+
- **Force push**: `git push --force`, `git push -f` (not recommended; if absolutely required, `--force-with-lease` is safer)
|
|
21
|
+
- **History rewriting**: `git rebase -i`, `git filter-branch`
|
|
22
|
+
- **Amending pushed commits**: `git commit --amend` (only safe for local, unpushed commits)
|
|
23
|
+
- **Reset operations**: `git reset`, `git reset --hard`, `git reset --mixed`, `git reset --soft` (unstages files or moves HEAD; can lose work or change history)
|
|
24
|
+
- **Force operations**: `git checkout --force`, `git clean -f/-d`, `git branch -D`
|
|
25
|
+
- **Stash deletion**: `git stash drop`, `git stash clear`
|
|
26
|
+
- **Reference manipulation**: `git update-ref -d`, `git reflog expire`
|
|
27
|
+
|
|
28
|
+
### Best Practices
|
|
29
|
+
|
|
30
|
+
- Always use `git --no-pager` to prevent interactive pagers in scripts
|
|
31
|
+
- Check repository state with `git status` before operations
|
|
32
|
+
- Stage files intentionally: use `git add <specific-files>` or `git add -p` for interactive staging
|
|
33
|
+
- Use `git diff` to verify changes before committing
|
|
34
|
+
- Prefer `git switch` over `git checkout` for branch switching (Git 2.23+; use `git checkout <branch>` for older versions)
|
|
35
|
+
- Use descriptive commit messages following conventional commits format
|
|
36
|
+
- Create feature branches instead of working directly on main/master
|
|
37
|
+
- Pull before push to avoid conflicts: `git pull origin <branch>` (or `git fetch origin && git merge origin/<branch>` for more control)
|
|
38
|
+
|
|
39
|
+
### Error Handling
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Check if git repository
|
|
43
|
+
if ! git rev-parse --git-dir > /dev/null 2>&1; then
|
|
44
|
+
echo "Error: Not a git repository" >&2
|
|
45
|
+
exit 1
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# Check for uncommitted changes
|
|
49
|
+
if ! git diff-index --quiet HEAD --; then
|
|
50
|
+
echo "Warning: Uncommitted changes detected" >&2
|
|
51
|
+
fi
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Pre-commit Checklist
|
|
55
|
+
|
|
56
|
+
- [ ] Shell scripts pass `bash -n` syntax check
|
|
57
|
+
- [ ] Tested with `--dry-run`
|
|
58
|
+
- [ ] No absolute paths in configs
|
|
59
|
+
- [ ] Colors and logging functions used consistently
|
|
60
|
+
- [ ] Error handling with `set -e` and guard clauses
|
|
61
|
+
- [ ] Documentation updated if workflow changed
|
|
62
|
+
- [ ] Git operations follow safety guidelines
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "aas-setup",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Initialize Pi and OpenCode agent environments from a curated snapshot",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"aas-setup": "./bin/aas-setup.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"src",
|
|
12
|
+
"configs"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"test": "node --test",
|
|
16
|
+
"dev": "node bin/aas-setup.mjs"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"cli",
|
|
20
|
+
"pi",
|
|
21
|
+
"opencode",
|
|
22
|
+
"agent-setup"
|
|
23
|
+
],
|
|
24
|
+
"author": "kk",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@biomejs/biome": "^2.5.1"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@inquirer/prompts": "^7.10.1",
|
|
31
|
+
"chalk": "^5.3.0",
|
|
32
|
+
"commander": "^12.0.0"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=18.0.0"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// opencode.mjs — OpenCode agent descriptor.
|
|
2
|
+
// Pure data; the initializer in src/commands/init.mjs loops over a list of
|
|
3
|
+
// these. Paths with ~ are expanded at runtime by the initializer.
|
|
4
|
+
|
|
5
|
+
/** @type {import('./types.mjs').AgentDescriptor} */
|
|
6
|
+
export const opencode = {
|
|
7
|
+
id: "opencode",
|
|
8
|
+
name: "OpenCode",
|
|
9
|
+
home: "~/.config/opencode",
|
|
10
|
+
configSource: "configs/opencode",
|
|
11
|
+
referenceDest: "~/.config/opencode/aas-setup",
|
|
12
|
+
install: {
|
|
13
|
+
kind: "curl",
|
|
14
|
+
url: "https://opencode.ai/install",
|
|
15
|
+
bin: "opencode",
|
|
16
|
+
},
|
|
17
|
+
deploy: {
|
|
18
|
+
overwriteFiles: ["opencode.json"],
|
|
19
|
+
replaceDirs: ["agent", "command"],
|
|
20
|
+
referenceSource: "configs/reference",
|
|
21
|
+
},
|
|
22
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// pi.mjs — Pi agent descriptor.
|
|
2
|
+
// Pure data; the initializer in src/commands/init.mjs loops over a list of
|
|
3
|
+
// these. Paths with ~ are expanded at runtime by the initializer.
|
|
4
|
+
|
|
5
|
+
/** @type {import('./types.mjs').AgentDescriptor} */
|
|
6
|
+
export const pi = {
|
|
7
|
+
id: "pi",
|
|
8
|
+
name: "Pi",
|
|
9
|
+
home: "~/.pi/agent",
|
|
10
|
+
configSource: "configs/pi",
|
|
11
|
+
referenceDest: "~/.pi/agent/aas-setup",
|
|
12
|
+
install: {
|
|
13
|
+
kind: "npm",
|
|
14
|
+
pkg: "@mariozechner/pi-coding-agent",
|
|
15
|
+
bin: "pi",
|
|
16
|
+
},
|
|
17
|
+
deploy: {
|
|
18
|
+
overwriteFiles: ["settings.json", "mcp.json", "models.json", "AGENTS.md"],
|
|
19
|
+
replaceDirs: ["themes"],
|
|
20
|
+
referenceSource: "configs/reference",
|
|
21
|
+
},
|
|
22
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// types.mjs — JSDoc type shapes for agent descriptors.
|
|
2
|
+
// No runtime exports; used only for type hints in editors.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {Object} InstallSpec
|
|
6
|
+
* @property {"npm"|"curl"} kind
|
|
7
|
+
* @property {string} [pkg] — npm package name (kind === "npm")
|
|
8
|
+
* @property {string} [url] — curl install URL (kind === "curl")
|
|
9
|
+
* @property {string} bin — binary name to detect on PATH
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {Object} DeploySpec
|
|
14
|
+
* @property {string[]} overwriteFiles — files copied flat into home
|
|
15
|
+
* @property {string[]} replaceDirs — subdirs rm-rf'd then fully replaced
|
|
16
|
+
* @property {string} referenceSource — package-relative path to reference docs
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {Object} AgentDescriptor
|
|
21
|
+
* @property {string} id
|
|
22
|
+
* @property {string} name
|
|
23
|
+
* @property {string} home — ~-prefixed agent home
|
|
24
|
+
* @property {string} configSource — package-relative path to agent configs
|
|
25
|
+
* @property {string} referenceDest — ~-prefixed destination for reference docs
|
|
26
|
+
* @property {InstallSpec} install
|
|
27
|
+
* @property {DeploySpec} deploy
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// init.mjs — the initializer.
|
|
2
|
+
// Loops over the two agent descriptors and runs detect → install → (backup)
|
|
3
|
+
// → deploy-configs → deploy-reference. Each step honors { dryRun }.
|
|
4
|
+
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { resolve } from "node:path";
|
|
7
|
+
import { opencode } from "../agents/opencode.mjs";
|
|
8
|
+
import { pi } from "../agents/pi.mjs";
|
|
9
|
+
import { detectBinary, runCommand } from "../lib/exec.mjs";
|
|
10
|
+
import { backupToTar, copyDir, copyDirFiles, copyFile, ensureDir, rmrf } from "../lib/fs.mjs";
|
|
11
|
+
import { log } from "../lib/log.mjs";
|
|
12
|
+
import { expandHome, packageRoot, pkgPath } from "../lib/paths.mjs";
|
|
13
|
+
|
|
14
|
+
const AGENTS = [pi, opencode];
|
|
15
|
+
|
|
16
|
+
export { AGENTS };
|
|
17
|
+
|
|
18
|
+
/** Directory for --backup tars: ~/ai-tools-backup-<timestamp>/ */
|
|
19
|
+
function backupDirFor() {
|
|
20
|
+
const ts = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
21
|
+
return resolve(homedir(), `ai-tools-backup-${ts}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Install step: detect the agent's binary on PATH; if missing, run the
|
|
26
|
+
* install command. No-op (print only) in dry-run.
|
|
27
|
+
*/
|
|
28
|
+
async function installStep(agent, { dryRun }) {
|
|
29
|
+
const present = dryRun ? false : detectBinary(agent.install.bin);
|
|
30
|
+
if (present) {
|
|
31
|
+
if (!dryRun) log.skip(agent.name);
|
|
32
|
+
else log.dry(`detect ${agent.install.bin} (assume present → skip)`);
|
|
33
|
+
return 0;
|
|
34
|
+
}
|
|
35
|
+
// install
|
|
36
|
+
const cmdline = installCmdline(agent.install);
|
|
37
|
+
if (dryRun) {
|
|
38
|
+
log.dry(`detect ${agent.install.bin} → missing → install`);
|
|
39
|
+
}
|
|
40
|
+
log.install(agent.name);
|
|
41
|
+
return runCommand(cmdline, { dryRun });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function installCmdline(spec) {
|
|
45
|
+
if (spec.kind === "npm") return `npm install -g ${spec.pkg}`;
|
|
46
|
+
if (spec.kind === "curl") return `curl -fsSL ${spec.url} | bash`;
|
|
47
|
+
throw new Error(`unknown install kind: ${spec.kind}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Deploy agent configs: overwrite flat files, replace subdirs wholesale.
|
|
52
|
+
*/
|
|
53
|
+
function deployConfigs(agent, pkgRoot, { dryRun }) {
|
|
54
|
+
const home = expandHome(agent.home);
|
|
55
|
+
const srcDir = pkgPath(pkgRoot, agent.configSource);
|
|
56
|
+
ensureDir(home, { dryRun });
|
|
57
|
+
for (const file of agent.deploy.overwriteFiles) {
|
|
58
|
+
copyFile(resolve(srcDir, file), resolve(home, file), { dryRun });
|
|
59
|
+
}
|
|
60
|
+
for (const dir of agent.deploy.replaceDirs) {
|
|
61
|
+
copyDir(resolve(srcDir, dir), resolve(home, dir), { dryRun });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function deployReference(agent, pkgRoot, { dryRun }) {
|
|
66
|
+
const dest = expandHome(agent.referenceDest);
|
|
67
|
+
const src = pkgPath(pkgRoot, agent.deploy.referenceSource);
|
|
68
|
+
ensureDir(dest, { dryRun });
|
|
69
|
+
copyDirFiles(src, dest, { dryRun });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Back up the agent's existing home to a tar (no-op if absent).
|
|
74
|
+
*/
|
|
75
|
+
function backupAgentHome(agent, backupDir, { dryRun }) {
|
|
76
|
+
backupToTar(expandHome(agent.home), backupDir, { dryRun });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Initialize both Pi and OpenCode. Returns 0 on success, non-zero on failure.
|
|
81
|
+
*
|
|
82
|
+
* @param {{ dryRun?: boolean, backup?: boolean }} opts
|
|
83
|
+
* @returns {Promise<number>}
|
|
84
|
+
*/
|
|
85
|
+
export async function initialize({ dryRun = false, backup = false, agents = AGENTS } = {}) {
|
|
86
|
+
const pkgRoot = packageRoot(import.meta.url);
|
|
87
|
+
const backupDir = backup ? backupDirFor() : null;
|
|
88
|
+
log.info(dryRun ? "Dry-run mode — no changes will be made" : "Initializing agent environments");
|
|
89
|
+
if (backup) log.info(`Backing up existing agent homes to ${backupDir}`);
|
|
90
|
+
for (const agent of agents) {
|
|
91
|
+
log.step(`Configure ${agent.name}`);
|
|
92
|
+
await installStep(agent, { dryRun });
|
|
93
|
+
if (backup) backupAgentHome(agent, backupDir, { dryRun });
|
|
94
|
+
deployConfigs(agent, pkgRoot, { dryRun });
|
|
95
|
+
deployReference(agent, pkgRoot, { dryRun });
|
|
96
|
+
}
|
|
97
|
+
log.success("Setup complete");
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// select.mjs — resolve which agents to configure.
|
|
2
|
+
//
|
|
3
|
+
// Resolution order:
|
|
4
|
+
// 1. positional args (aas-setup pi opencode) → validate ids, use exactly those
|
|
5
|
+
// 2. non-TTY (CI / piped) → all agents, no prompt
|
|
6
|
+
// 3. TTY → interactive multi-select,
|
|
7
|
+
// both pre-checked by default
|
|
8
|
+
//
|
|
9
|
+
// Exits non-zero on unknown agent ids.
|
|
10
|
+
|
|
11
|
+
import { checkbox } from "@inquirer/prompts";
|
|
12
|
+
import { log } from "../lib/log.mjs";
|
|
13
|
+
import { AGENTS } from "./init.mjs";
|
|
14
|
+
|
|
15
|
+
const VALID_IDS = new Set(AGENTS.map((a) => a.id));
|
|
16
|
+
|
|
17
|
+
export async function selectAgents(positional, { tty = false } = {}) {
|
|
18
|
+
// 1. explicit positional args
|
|
19
|
+
if (positional.length > 0) {
|
|
20
|
+
const invalid = positional.filter((id) => !VALID_IDS.has(id));
|
|
21
|
+
if (invalid.length > 0) {
|
|
22
|
+
log.error(`Unknown agent(s): ${invalid.join(", ")}`);
|
|
23
|
+
log.info(`Valid ids: ${AGENTS.map((a) => a.id).join(", ")}`);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
return positional;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// 2. non-interactive: all agents
|
|
30
|
+
if (!tty) {
|
|
31
|
+
log.info("Non-interactive mode — configuring all agents");
|
|
32
|
+
return AGENTS.map((a) => a.id);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 3. interactive multi-select
|
|
36
|
+
const selected = await checkbox({
|
|
37
|
+
message: "Select agents to configure:",
|
|
38
|
+
default: true,
|
|
39
|
+
choices: AGENTS.map((a) => ({
|
|
40
|
+
name: a.name,
|
|
41
|
+
value: a.id,
|
|
42
|
+
checked: true,
|
|
43
|
+
})),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (selected.length === 0) {
|
|
47
|
+
log.warning("No agents selected — nothing to do");
|
|
48
|
+
process.exit(0);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return selected;
|
|
52
|
+
}
|
package/src/lib/exec.mjs
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// exec.mjs — exec + detect helpers that honor --dry-run.
|
|
2
|
+
//
|
|
3
|
+
// runCommand takes a single shell command string. In dry-run mode it prints
|
|
4
|
+
// "[dry] <cmd>" instead of spawning. In real mode it spawns with shell:true
|
|
5
|
+
// and inherited stdio, returning the exit code.
|
|
6
|
+
|
|
7
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Run a shell command. If dryRun is true, only print the command.
|
|
11
|
+
* Returns 0 on success, non-zero on failure. Resolves a Promise.
|
|
12
|
+
*/
|
|
13
|
+
export function runCommand(cmdline, { dryRun = false, cwd } = {}) {
|
|
14
|
+
if (dryRun) {
|
|
15
|
+
console.log(`[dry] ${cmdline}`);
|
|
16
|
+
return Promise.resolve(0);
|
|
17
|
+
}
|
|
18
|
+
return new Promise((resolve) => {
|
|
19
|
+
const child = spawn(cmdline, { cwd, stdio: "inherit", shell: true });
|
|
20
|
+
child.on("close", (code) => resolve(code ?? 1));
|
|
21
|
+
child.on("error", () => resolve(1));
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Detect whether a binary is on PATH. Synchronous — used by the detect step
|
|
27
|
+
* to decide install-vs-skip. Returns boolean.
|
|
28
|
+
*/
|
|
29
|
+
export function detectBinary(name) {
|
|
30
|
+
const result = spawnSync("command", ["-v", name], {
|
|
31
|
+
shell: true,
|
|
32
|
+
stdio: "ignore",
|
|
33
|
+
});
|
|
34
|
+
return result.status === 0;
|
|
35
|
+
}
|
package/src/lib/fs.mjs
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// fs.mjs — filesystem helpers for deploy.
|
|
2
|
+
// All functions take an optional { dryRun } option: when true, print the
|
|
3
|
+
// planned action with full paths and do NOT touch the filesystem.
|
|
4
|
+
|
|
5
|
+
import { execFileSync } from "node:child_process";
|
|
6
|
+
import { cpSync, existsSync, mkdirSync, readdirSync, rmSync, statSync } from "node:fs";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { basename, dirname, resolve } from "node:path";
|
|
9
|
+
import { log } from "./log.mjs";
|
|
10
|
+
|
|
11
|
+
/** Strip the leading home directory from an absolute path for nicer tar names. */
|
|
12
|
+
function stripHome(p) {
|
|
13
|
+
const h = homedir();
|
|
14
|
+
return p.startsWith(h) ? p.slice(h.length).replace(/^\/+/, "") : p;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Ensure a directory exists (recursive). Prints in dry-run.
|
|
19
|
+
*/
|
|
20
|
+
export function ensureDir(dir, { dryRun = false } = {}) {
|
|
21
|
+
if (dryRun) {
|
|
22
|
+
log.dry(`mkdir -p ${dir}`);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (!existsSync(dir)) {
|
|
26
|
+
mkdirSync(dir, { recursive: true });
|
|
27
|
+
log.info(`mkdir ${dir}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Remove a directory recursively. No-op if it doesn't exist. Prints in dry-run.
|
|
33
|
+
*/
|
|
34
|
+
export function rmrf(dir, { dryRun = false } = {}) {
|
|
35
|
+
if (dryRun) {
|
|
36
|
+
if (existsSync(dir)) log.dry(`rm -rf ${dir}`);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (existsSync(dir)) {
|
|
40
|
+
rmSync(dir, { recursive: true, force: true });
|
|
41
|
+
log.info(`rm -rf ${dir}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Copy a single file, creating parent dirs as needed. Prints in dry-run.
|
|
47
|
+
*/
|
|
48
|
+
export function copyFile(src, dest, { dryRun = false } = {}) {
|
|
49
|
+
if (dryRun) {
|
|
50
|
+
log.dry(`cp ${src} → ${dest}`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
ensureDir(dirname(dest), { dryRun: false });
|
|
54
|
+
cpSync(src, dest, { force: true });
|
|
55
|
+
log.deploy(dest);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Copy a directory recursively, fully replacing the destination.
|
|
60
|
+
* Calls rmrf(dest) then copies src → dest. Prints in dry-run.
|
|
61
|
+
*/
|
|
62
|
+
export function copyDir(src, dest, { dryRun = false } = {}) {
|
|
63
|
+
if (dryRun) {
|
|
64
|
+
log.dry(`rm -rf ${dest} && cp -r ${src} ${dest}`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
rmrf(dest, { dryRun: false });
|
|
68
|
+
ensureDir(dirname(dest), { dryRun: false });
|
|
69
|
+
cpSync(src, dest, { recursive: true, force: true });
|
|
70
|
+
log.deploy(dest);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Copy every file in sourceDir (non-recursively) into destDir.
|
|
75
|
+
* Used to deploy reference docs (flat file set). Prints in dry-run.
|
|
76
|
+
*/
|
|
77
|
+
export function copyDirFiles(sourceDir, destDir, { dryRun = false } = {}) {
|
|
78
|
+
if (!existsSync(sourceDir)) return;
|
|
79
|
+
const entries = readdirSync(sourceDir);
|
|
80
|
+
for (const entry of entries) {
|
|
81
|
+
const src = resolve(sourceDir, entry);
|
|
82
|
+
const stat = statSync(src);
|
|
83
|
+
if (stat.isFile()) {
|
|
84
|
+
copyFile(src, resolve(destDir, entry), { dryRun });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Back up a directory tree to a tar file under backupDir.
|
|
91
|
+
* Skips silently if the source does not exist. Prints in dry-run.
|
|
92
|
+
* Returns the created tar path (or null if skipped/no-op).
|
|
93
|
+
*/
|
|
94
|
+
export function backupToTar(src, backupDir, { dryRun = false } = {}) {
|
|
95
|
+
if (!existsSync(src)) return null;
|
|
96
|
+
const base = basename(stripHome(src));
|
|
97
|
+
const ts = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
98
|
+
const tar = resolve(backupDir, `${base}-${ts}.tar.gz`);
|
|
99
|
+
if (dryRun) {
|
|
100
|
+
log.dry(`backup ${src} → ${tar}`);
|
|
101
|
+
return tar;
|
|
102
|
+
}
|
|
103
|
+
ensureDir(backupDir, { dryRun: false });
|
|
104
|
+
execFileSync("tar", ["-czf", tar, "-C", dirname(src), base], { stdio: "ignore" });
|
|
105
|
+
log.info(`backup ${src} → ${tar}`);
|
|
106
|
+
return tar;
|
|
107
|
+
}
|
package/src/lib/log.mjs
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// log.mjs — structured stdout logger built on chalk.
|
|
2
|
+
// Every action prints a full resolved path so the user can see exactly what
|
|
3
|
+
// the CLI did.
|
|
4
|
+
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
|
|
7
|
+
function fullPath(p) {
|
|
8
|
+
return typeof p === "string" ? p : String(p);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const log = {
|
|
12
|
+
info(msg) {
|
|
13
|
+
console.log(`${chalk.cyan("ℹ")} ${msg}`);
|
|
14
|
+
},
|
|
15
|
+
success(msg) {
|
|
16
|
+
console.log(`${chalk.green("✓")} ${msg}`);
|
|
17
|
+
},
|
|
18
|
+
warning(msg) {
|
|
19
|
+
console.log(`${chalk.yellow("⚠")} ${msg}`);
|
|
20
|
+
},
|
|
21
|
+
error(msg) {
|
|
22
|
+
console.error(`${chalk.red("✗")} ${msg}`);
|
|
23
|
+
},
|
|
24
|
+
dry(msg) {
|
|
25
|
+
console.log(`${chalk.magenta("[dry]")} ${msg}`);
|
|
26
|
+
},
|
|
27
|
+
step(msg) {
|
|
28
|
+
console.log(`${chalk.dim("─")} ${msg}`);
|
|
29
|
+
},
|
|
30
|
+
install(name) {
|
|
31
|
+
console.log(`${chalk.cyan("ℹ")} Installing ${name}...`);
|
|
32
|
+
},
|
|
33
|
+
skip(name) {
|
|
34
|
+
console.log(`${chalk.dim("•")} ${name} already installed, skipping`);
|
|
35
|
+
},
|
|
36
|
+
deploy(dest) {
|
|
37
|
+
console.log(`${chalk.green("✓")} Deployed → ${fullPath(dest)}`);
|
|
38
|
+
},
|
|
39
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// paths.mjs — resolve package root and agent homes.
|
|
2
|
+
// Resolves from import.meta.url (NOT process.cwd()) so `npx aas-setup` works
|
|
3
|
+
// regardless of the user's current directory.
|
|
4
|
+
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { dirname, resolve } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Resolve the package root (the directory containing bin/, src/, configs/).
|
|
11
|
+
* Pass import.meta.url from the calling module.
|
|
12
|
+
*/
|
|
13
|
+
export function packageRoot(importMetaUrl) {
|
|
14
|
+
const here = dirname(fileURLToPath(importMetaUrl));
|
|
15
|
+
// src/lib/paths.mjs → up two = package root
|
|
16
|
+
return resolve(here, "..", "..");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Resolve a path inside the package root. Segments are package-relative
|
|
21
|
+
* (e.g. "configs", "pi", "settings.json"). Descriptors already carry the
|
|
22
|
+
* "configs/<agent>" prefix, so this does NOT add a magic "configs/".
|
|
23
|
+
*/
|
|
24
|
+
export function pkgPath(pkgRoot, ...segments) {
|
|
25
|
+
return resolve(pkgRoot, ...segments);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Expand a leading ~ to the user's home directory.
|
|
30
|
+
*/
|
|
31
|
+
export function expandHome(p) {
|
|
32
|
+
if (!p) return p;
|
|
33
|
+
if (p === "~") return homedir();
|
|
34
|
+
if (p.startsWith("~/")) return resolve(homedir(), p.slice(2));
|
|
35
|
+
return p;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The two agent homes, as absolute paths.
|
|
40
|
+
*/
|
|
41
|
+
export function agentHomes() {
|
|
42
|
+
const home = homedir();
|
|
43
|
+
return {
|
|
44
|
+
pi: resolve(home, ".pi", "agent"),
|
|
45
|
+
opencode: resolve(home, ".config", "opencode"),
|
|
46
|
+
};
|
|
47
|
+
}
|