@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 CHANGED
@@ -1 +1,2 @@
1
- Placeholder release for trusted publishing setup. Real content will be published via CI.
1
+ # donna
2
+ A Claude skill based personal assistant
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const { run } = require("../src/installer.cjs");
5
+
6
+ run().catch((err) => {
7
+ console.error(`\nInstallation failed: ${err.message}`);
8
+ process.exit(1);
9
+ });
@@ -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
- "name": "@pingvinen/donna-assistant",
3
- "version": "0.0.0",
4
- "description": "Donna - your AI powered personal assistant (placeholder for trusted publishing setup)",
5
- "publishConfig": {
6
- "access": "public"
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 };
@@ -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 };
@@ -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>