@pingvinen/donna-assistant 0.0.0 → 0.3.2
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 +2 -1
- package/bin/donna-assistant +9 -0
- package/migrations/001-initial.cjs +12 -0
- package/package.json +42 -6
- package/references/.gitkeep +0 -0
- package/src/installer.cjs +98 -0
- package/src/migrator.cjs +44 -0
- package/src/output.cjs +29 -0
- package/src/providers/claude-code.cjs +18 -0
- package/src/providers/index.cjs +21 -0
- package/src/version.cjs +49 -0
- package/stubs/claude-code/donna/setup.md +15 -0
- package/templates/.gitkeep +0 -0
- package/workflows/setup.md +42 -0
package/README.md
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
# donna
|
|
2
|
+
A Claude skill based personal assistant
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
version: "0.1.0",
|
|
5
|
+
description: "Initial directory structure",
|
|
6
|
+
up(ctx) {
|
|
7
|
+
const dirs = ["workflows", "templates", "references"];
|
|
8
|
+
for (const dir of dirs) {
|
|
9
|
+
ctx.fs.mkdirSync(ctx.path.join(ctx.donnaDir, dir), { recursive: true });
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
};
|
package/package.json
CHANGED
|
@@ -1,8 +1,44 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
"name": "@pingvinen/donna-assistant",
|
|
3
|
+
"version": "0.3.2",
|
|
4
|
+
"description": "Donna - your AI powered personal assistant",
|
|
5
|
+
"bin": {
|
|
6
|
+
"donna-assistant": "./bin/donna-assistant"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin/",
|
|
10
|
+
"src/",
|
|
11
|
+
"stubs/",
|
|
12
|
+
"workflows/",
|
|
13
|
+
"migrations/",
|
|
14
|
+
"templates/",
|
|
15
|
+
"references/"
|
|
16
|
+
],
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=18"
|
|
19
|
+
},
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public",
|
|
22
|
+
"provenance": true
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"lint": "biome check .",
|
|
26
|
+
"lint:fix": "biome check --write .",
|
|
27
|
+
"test": "node --test 'test/*.test.cjs'",
|
|
28
|
+
"prepublishOnly": "chmod +x bin/donna-assistant"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@biomejs/biome": "^1.9.0"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"claude-code",
|
|
35
|
+
"assistant",
|
|
36
|
+
"productivity",
|
|
37
|
+
"ai"
|
|
38
|
+
],
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "https://github.com/pingvinen/donna"
|
|
43
|
+
}
|
|
8
44
|
}
|
|
File without changes
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const path = require("node:path");
|
|
5
|
+
const os = require("node:os");
|
|
6
|
+
|
|
7
|
+
const output = require("./output.cjs");
|
|
8
|
+
const version = require("./version.cjs");
|
|
9
|
+
const migrator = require("./migrator.cjs");
|
|
10
|
+
const providers = require("./providers/index.cjs");
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Main installer orchestration.
|
|
14
|
+
*
|
|
15
|
+
* @param {object} [options]
|
|
16
|
+
* @param {string} [options.homeDir] - Override home directory (for testing)
|
|
17
|
+
* @returns {Promise<void>}
|
|
18
|
+
*/
|
|
19
|
+
async function run(options = {}) {
|
|
20
|
+
const homeDir = options.homeDir || os.homedir();
|
|
21
|
+
const donnaDir = path.join(homeDir, ".donna");
|
|
22
|
+
const migrationsDir = path.join(__dirname, "..", "migrations");
|
|
23
|
+
const workflowsSource = path.join(__dirname, "..", "workflows");
|
|
24
|
+
const pkg = require("../package.json");
|
|
25
|
+
const packageVersion = pkg.version;
|
|
26
|
+
|
|
27
|
+
// Print banner
|
|
28
|
+
output.banner();
|
|
29
|
+
|
|
30
|
+
// Create donnaDir if it doesn't exist
|
|
31
|
+
fs.mkdirSync(donnaDir, { recursive: true });
|
|
32
|
+
|
|
33
|
+
// Read current version
|
|
34
|
+
const current = version.readVersion(donnaDir);
|
|
35
|
+
const currentVersion = current?.version || null;
|
|
36
|
+
const lastMigration = current?.lastMigration || 0;
|
|
37
|
+
|
|
38
|
+
// Check if already up to date
|
|
39
|
+
if (currentVersion === packageVersion) {
|
|
40
|
+
// Check for pending migrations too
|
|
41
|
+
const pendingResults = migrator.runMigrations(migrationsDir, donnaDir, lastMigration);
|
|
42
|
+
if (pendingResults.length === 0) {
|
|
43
|
+
output.info(`Already up to date at ${packageVersion}`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// If upgrading (current version exists but differs)
|
|
49
|
+
if (currentVersion && currentVersion !== packageVersion) {
|
|
50
|
+
output.upgradeHeader(currentVersion, packageVersion);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Run migrations
|
|
54
|
+
const results = migrator.runMigrations(migrationsDir, donnaDir, lastMigration);
|
|
55
|
+
|
|
56
|
+
// Track last successful migration number
|
|
57
|
+
let lastSuccessful = lastMigration;
|
|
58
|
+
|
|
59
|
+
for (const result of results) {
|
|
60
|
+
if (result.ok) {
|
|
61
|
+
output.migrationLine(result.description);
|
|
62
|
+
lastSuccessful = result.num;
|
|
63
|
+
} else {
|
|
64
|
+
output.fail(`Migration ${result.num} failed: ${result.error.message}`);
|
|
65
|
+
// Write version.md with last successful migration
|
|
66
|
+
version.writeVersion(donnaDir, packageVersion, lastSuccessful);
|
|
67
|
+
throw result.error;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Detect providers and copy stubs
|
|
72
|
+
const detected = providers.detectProviders(homeDir);
|
|
73
|
+
|
|
74
|
+
if (detected.length > 0) {
|
|
75
|
+
for (const provider of detected) {
|
|
76
|
+
fs.cpSync(provider.stubSource, provider.stubTarget, { recursive: true });
|
|
77
|
+
output.success(`Copied donna:setup to ${provider.stubTarget}`);
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
output.info("No supported AI providers detected");
|
|
81
|
+
output.info("Install Claude Code and re-run to add donna skills");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Copy workflows to donnaDir/workflows/
|
|
85
|
+
const workflowsTarget = path.join(donnaDir, "workflows");
|
|
86
|
+
fs.mkdirSync(workflowsTarget, { recursive: true });
|
|
87
|
+
fs.cpSync(workflowsSource, workflowsTarget, { recursive: true });
|
|
88
|
+
output.success("Installed workflows to ~/.donna/workflows/");
|
|
89
|
+
|
|
90
|
+
// Write version.md
|
|
91
|
+
version.writeVersion(donnaDir, packageVersion, lastSuccessful);
|
|
92
|
+
output.success(`Version ${packageVersion} installed`);
|
|
93
|
+
|
|
94
|
+
// Final message
|
|
95
|
+
output.info("Run /donna:setup in Claude Code to get started.");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = { run };
|
package/src/migrator.cjs
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const path = require("node:path");
|
|
5
|
+
const os = require("node:os");
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Run pending migrations from migrationsDir against donnaDir.
|
|
9
|
+
* Skips migrations with numeric prefix <= lastMigration.
|
|
10
|
+
* Stops on first failure.
|
|
11
|
+
*
|
|
12
|
+
* @param {string} migrationsDir - Directory containing numbered .cjs migration files
|
|
13
|
+
* @param {string} donnaDir - The ~/.donna/ directory
|
|
14
|
+
* @param {number} lastMigration - Last successfully applied migration number
|
|
15
|
+
* @returns {Array<{num: number, description: string, ok: boolean, error?: Error}>}
|
|
16
|
+
*/
|
|
17
|
+
function runMigrations(migrationsDir, donnaDir, lastMigration) {
|
|
18
|
+
if (!fs.existsSync(migrationsDir)) return [];
|
|
19
|
+
|
|
20
|
+
const files = fs
|
|
21
|
+
.readdirSync(migrationsDir)
|
|
22
|
+
.filter((f) => f.endsWith(".cjs"))
|
|
23
|
+
.sort((a, b) => Number.parseInt(a, 10) - Number.parseInt(b, 10));
|
|
24
|
+
|
|
25
|
+
const pending = files.filter((f) => Number.parseInt(f, 10) > lastMigration);
|
|
26
|
+
const results = [];
|
|
27
|
+
|
|
28
|
+
for (const file of pending) {
|
|
29
|
+
const migration = require(path.join(migrationsDir, file));
|
|
30
|
+
const num = Number.parseInt(file, 10);
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
migration.up({ donnaDir, fs, path, os });
|
|
34
|
+
results.push({ num, description: migration.description, ok: true });
|
|
35
|
+
} catch (err) {
|
|
36
|
+
results.push({ num, description: migration.description, ok: false, error: err });
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return results;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = { runMigrations };
|
package/src/output.cjs
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
function banner() {
|
|
4
|
+
console.log("");
|
|
5
|
+
console.log("━━━ DONNA ━━━");
|
|
6
|
+
console.log("");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function success(msg) {
|
|
10
|
+
console.log(` \u2713 ${msg}`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function fail(msg) {
|
|
14
|
+
console.log(` \u2717 ${msg}`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function info(msg) {
|
|
18
|
+
console.log(` ${msg}`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function upgradeHeader(from, to) {
|
|
22
|
+
console.log(` Upgrading ${from} \u2192 ${to}:`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function migrationLine(desc) {
|
|
26
|
+
console.log(` \u2022 ${desc}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = { banner, success, fail, info, upgradeHeader, migrationLine };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const path = require("node:path");
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
name: "Claude Code",
|
|
8
|
+
|
|
9
|
+
detect(homeDir) {
|
|
10
|
+
return fs.existsSync(path.join(homeDir, ".claude"));
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
stubSource: path.join(__dirname, "..", "..", "stubs", "claude-code"),
|
|
14
|
+
|
|
15
|
+
getStubTarget(homeDir) {
|
|
16
|
+
return path.join(homeDir, ".claude", "commands");
|
|
17
|
+
},
|
|
18
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const claudeCode = require("./claude-code.cjs");
|
|
4
|
+
|
|
5
|
+
const PROVIDERS = [claudeCode];
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Detect which AI coding assistant providers are installed.
|
|
9
|
+
*
|
|
10
|
+
* @param {string} homeDir - The user's home directory
|
|
11
|
+
* @returns {Array<{name: string, stubSource: string, stubTarget: string}>}
|
|
12
|
+
*/
|
|
13
|
+
function detectProviders(homeDir) {
|
|
14
|
+
return PROVIDERS.filter((p) => p.detect(homeDir)).map((p) => ({
|
|
15
|
+
name: p.name,
|
|
16
|
+
stubSource: p.stubSource,
|
|
17
|
+
stubTarget: p.getStubTarget(homeDir),
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = { detectProviders };
|
package/src/version.cjs
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const path = require("node:path");
|
|
5
|
+
|
|
6
|
+
function readVersion(donnaDir) {
|
|
7
|
+
const versionPath = path.join(donnaDir, "version.md");
|
|
8
|
+
if (!fs.existsSync(versionPath)) return null;
|
|
9
|
+
|
|
10
|
+
const content = fs.readFileSync(versionPath, "utf8");
|
|
11
|
+
const version = content.match(/\*\*Version:\*\* (.+)/)?.[1] || "0.0.0";
|
|
12
|
+
const lastMigration = Number.parseInt(
|
|
13
|
+
content.match(/\*\*Last migration:\*\* (\d+)/)?.[1] || "0",
|
|
14
|
+
10,
|
|
15
|
+
);
|
|
16
|
+
const installed = content.match(/\*\*Installed:\*\* (.+)/)?.[1] || null;
|
|
17
|
+
const updated = content.match(/\*\*Updated:\*\* (.+)/)?.[1] || null;
|
|
18
|
+
|
|
19
|
+
return { version, lastMigration, installed, updated };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function writeVersion(donnaDir, version, lastMigration) {
|
|
23
|
+
const versionPath = path.join(donnaDir, "version.md");
|
|
24
|
+
const now = new Date().toISOString();
|
|
25
|
+
|
|
26
|
+
let installed = now;
|
|
27
|
+
|
|
28
|
+
// Preserve the original "Installed" timestamp if file already exists
|
|
29
|
+
if (fs.existsSync(versionPath)) {
|
|
30
|
+
const existing = readVersion(donnaDir);
|
|
31
|
+
if (existing?.installed) {
|
|
32
|
+
installed = existing.installed;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const content = [
|
|
37
|
+
"# Donna",
|
|
38
|
+
"",
|
|
39
|
+
`- **Version:** ${version}`,
|
|
40
|
+
`- **Last migration:** ${String(lastMigration).padStart(3, "0")}`,
|
|
41
|
+
`- **Installed:** ${installed}`,
|
|
42
|
+
`- **Updated:** ${now}`,
|
|
43
|
+
"",
|
|
44
|
+
].join("\n");
|
|
45
|
+
|
|
46
|
+
fs.writeFileSync(versionPath, content);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = { readVersion, writeVersion };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: donna:setup
|
|
3
|
+
description: Set up Donna assistant for this machine
|
|
4
|
+
allowed-tools:
|
|
5
|
+
- Read
|
|
6
|
+
- Bash
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<objective>
|
|
10
|
+
Run the Donna setup workflow. This command verifies that Donna is installed correctly by loading the setup workflow from the shared runtime directory and displaying the current installation status.
|
|
11
|
+
</objective>
|
|
12
|
+
|
|
13
|
+
<execution_context>
|
|
14
|
+
@~/.donna/workflows/setup.md
|
|
15
|
+
</execution_context>
|
|
File without changes
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Donna Setup Workflow
|
|
2
|
+
|
|
3
|
+
<objective>
|
|
4
|
+
Display the Donna installation status and confirm the stub-to-workflow pipeline is working.
|
|
5
|
+
</objective>
|
|
6
|
+
|
|
7
|
+
<step name="banner">
|
|
8
|
+
Print the DONNA banner:
|
|
9
|
+
```
|
|
10
|
+
━━━ DONNA ▸ Setup ━━━
|
|
11
|
+
```
|
|
12
|
+
</step>
|
|
13
|
+
|
|
14
|
+
<step name="version">
|
|
15
|
+
Read `~/.donna/version.md` and display the installed version. If the file exists, show:
|
|
16
|
+
```
|
|
17
|
+
Version: {version}
|
|
18
|
+
Installed: {installed date}
|
|
19
|
+
Last updated: {updated date}
|
|
20
|
+
```
|
|
21
|
+
If the file does not exist, show:
|
|
22
|
+
```
|
|
23
|
+
No version file found. Donna may not be installed correctly.
|
|
24
|
+
Try running: npx @pingvinen/donna-assistant
|
|
25
|
+
```
|
|
26
|
+
</step>
|
|
27
|
+
|
|
28
|
+
<step name="status">
|
|
29
|
+
Print the following message:
|
|
30
|
+
```
|
|
31
|
+
This is a stub -- real setup coming in Phase 2.
|
|
32
|
+
```
|
|
33
|
+
</step>
|
|
34
|
+
|
|
35
|
+
<step name="next">
|
|
36
|
+
Print next steps:
|
|
37
|
+
```
|
|
38
|
+
Next steps:
|
|
39
|
+
- Run /donna:setup again after upgrading to see new features
|
|
40
|
+
- Phase 2 will add role configuration, storage setup, and more
|
|
41
|
+
```
|
|
42
|
+
</step>
|