attacca-forge 0.5.0 → 0.5.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/.claude-plugin/marketplace.json +20 -0
- package/package.json +2 -1
- package/src/commands/install.js +121 -21
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
|
|
3
|
+
"name": "attacca-forge",
|
|
4
|
+
"description": "Open-source AI agent development toolkit: spec writing, evaluation, intent engineering, and build orchestration for production-grade agents",
|
|
5
|
+
"owner": {
|
|
6
|
+
"name": "Attacca"
|
|
7
|
+
},
|
|
8
|
+
"plugins": [
|
|
9
|
+
{
|
|
10
|
+
"name": "attacca-forge",
|
|
11
|
+
"description": "AI agent development methodology — design, evaluate, and align autonomous agents with production-grade specifications, factorial stress testing, and intent engineering",
|
|
12
|
+
"version": "0.5.2",
|
|
13
|
+
"author": {
|
|
14
|
+
"name": "Attacca"
|
|
15
|
+
},
|
|
16
|
+
"source": "./plugins/attacca-forge",
|
|
17
|
+
"category": "development"
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "attacca-forge",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "Spec-driven AI development toolkit — design, evaluate, stress-test, and certify autonomous agents from your terminal",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"bin/",
|
|
34
34
|
"src/",
|
|
35
35
|
"plugins/",
|
|
36
|
+
".claude-plugin/",
|
|
36
37
|
"docs/",
|
|
37
38
|
"examples/",
|
|
38
39
|
"LICENSE",
|
package/src/commands/install.js
CHANGED
|
@@ -2,10 +2,86 @@
|
|
|
2
2
|
// attacca-forge install — Install skills into Claude Code plugin directory
|
|
3
3
|
// =============================================================================
|
|
4
4
|
|
|
5
|
-
import { cpSync, existsSync, mkdirSync } from 'node:fs';
|
|
5
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
6
6
|
import { join } from 'node:path';
|
|
7
7
|
import { getClaudeDir, getPluginDir, isClaudeInstalled } from '../utils/detect-claude.js';
|
|
8
8
|
|
|
9
|
+
const MARKETPLACE_NAME = 'attacca-forge';
|
|
10
|
+
const PLUGIN_NAME = 'attacca-forge';
|
|
11
|
+
|
|
12
|
+
function readJsonFile(path) {
|
|
13
|
+
try {
|
|
14
|
+
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
15
|
+
} catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function writeJsonFile(path, data) {
|
|
21
|
+
writeFileSync(path, JSON.stringify(data, null, 2) + '\n', 'utf-8');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function registerMarketplace(claudeDir, targetDir) {
|
|
25
|
+
const marketplacesPath = join(claudeDir, 'plugins', 'known_marketplaces.json');
|
|
26
|
+
const marketplaces = readJsonFile(marketplacesPath) || {};
|
|
27
|
+
|
|
28
|
+
if (marketplaces[MARKETPLACE_NAME]) {
|
|
29
|
+
marketplaces[MARKETPLACE_NAME].lastUpdated = new Date().toISOString();
|
|
30
|
+
} else {
|
|
31
|
+
marketplaces[MARKETPLACE_NAME] = {
|
|
32
|
+
source: {
|
|
33
|
+
source: 'directory',
|
|
34
|
+
path: targetDir,
|
|
35
|
+
},
|
|
36
|
+
installLocation: targetDir,
|
|
37
|
+
lastUpdated: new Date().toISOString(),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
writeJsonFile(marketplacesPath, marketplaces);
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function registerPlugin(claudeDir, pluginDir, version) {
|
|
46
|
+
const installedPath = join(claudeDir, 'plugins', 'installed_plugins.json');
|
|
47
|
+
const installed = readJsonFile(installedPath) || { version: 2, plugins: {} };
|
|
48
|
+
|
|
49
|
+
if (!installed.version) installed.version = 2;
|
|
50
|
+
if (!installed.plugins) installed.plugins = {};
|
|
51
|
+
|
|
52
|
+
const pluginKey = `${PLUGIN_NAME}@${MARKETPLACE_NAME}`;
|
|
53
|
+
const now = new Date().toISOString();
|
|
54
|
+
|
|
55
|
+
// Set up cache directory and copy skills there
|
|
56
|
+
const cachePath = join(claudeDir, 'plugins', 'cache', MARKETPLACE_NAME, PLUGIN_NAME, version);
|
|
57
|
+
if (!existsSync(cachePath)) mkdirSync(cachePath, { recursive: true });
|
|
58
|
+
|
|
59
|
+
const sourceSkills = join(pluginDir, 'skills');
|
|
60
|
+
const cacheSkills = join(cachePath, 'skills');
|
|
61
|
+
if (existsSync(sourceSkills)) {
|
|
62
|
+
cpSync(sourceSkills, cacheSkills, { recursive: true, force: true });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (installed.plugins[pluginKey]) {
|
|
66
|
+
installed.plugins[pluginKey][0].version = version;
|
|
67
|
+
installed.plugins[pluginKey][0].installPath = cachePath;
|
|
68
|
+
installed.plugins[pluginKey][0].lastUpdated = now;
|
|
69
|
+
} else {
|
|
70
|
+
installed.plugins[pluginKey] = [
|
|
71
|
+
{
|
|
72
|
+
scope: 'user',
|
|
73
|
+
installPath: cachePath,
|
|
74
|
+
version,
|
|
75
|
+
installedAt: now,
|
|
76
|
+
lastUpdated: now,
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
writeJsonFile(installedPath, installed);
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
|
|
9
85
|
export default async function install({ args, cwd, rootDir }) {
|
|
10
86
|
console.log('\n Attacca Forge — Install Skills');
|
|
11
87
|
console.log(' ===============================\n');
|
|
@@ -22,44 +98,68 @@ export default async function install({ args, cwd, rootDir }) {
|
|
|
22
98
|
|
|
23
99
|
console.log(' ✓ Claude Code detected');
|
|
24
100
|
|
|
25
|
-
// Source
|
|
101
|
+
// Source directories (shipped with this package)
|
|
102
|
+
// Repo layout:
|
|
103
|
+
// .claude-plugin/marketplace.json ← marketplace descriptor
|
|
104
|
+
// plugins/attacca-forge/ ← plugin with .claude-plugin/plugin.json + skills/
|
|
105
|
+
const sourceMarketplace = join(rootDir, '.claude-plugin', 'marketplace.json');
|
|
26
106
|
const sourcePlugin = join(rootDir, 'plugins', 'attacca-forge');
|
|
107
|
+
|
|
27
108
|
if (!existsSync(sourcePlugin)) {
|
|
28
109
|
console.error(' ✗ Plugin source not found. Package may be corrupted.');
|
|
29
110
|
process.exit(1);
|
|
30
111
|
}
|
|
31
112
|
|
|
32
|
-
//
|
|
33
|
-
const
|
|
34
|
-
const
|
|
113
|
+
// Read version from package.json
|
|
114
|
+
const pkg = readJsonFile(join(rootDir, 'package.json'));
|
|
115
|
+
const version = pkg?.version || '0.5.2';
|
|
35
116
|
|
|
36
|
-
//
|
|
37
|
-
|
|
117
|
+
// Target: ~/.claude/plugins/local/attacca-forge/
|
|
118
|
+
// Installed layout (mirrors nirbound-marketplace):
|
|
119
|
+
// .claude-plugin/marketplace.json
|
|
120
|
+
// plugins/attacca-forge/.claude-plugin/plugin.json
|
|
121
|
+
// plugins/attacca-forge/skills/
|
|
122
|
+
const claudeDir = getClaudeDir();
|
|
123
|
+
const targetDir = getPluginDir();
|
|
38
124
|
|
|
39
|
-
// Copy plugin files
|
|
40
125
|
const existed = existsSync(targetDir);
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
//
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
if (existsSync(
|
|
47
|
-
|
|
48
|
-
cpSync(rootPluginJson, targetRootPlugin, { recursive: true, force: true });
|
|
126
|
+
if (!existsSync(targetDir)) mkdirSync(targetDir, { recursive: true });
|
|
127
|
+
|
|
128
|
+
// 1. Copy marketplace descriptor to target root
|
|
129
|
+
const targetMarketplaceDir = join(targetDir, '.claude-plugin');
|
|
130
|
+
if (!existsSync(targetMarketplaceDir)) mkdirSync(targetMarketplaceDir, { recursive: true });
|
|
131
|
+
if (existsSync(sourceMarketplace)) {
|
|
132
|
+
cpSync(sourceMarketplace, join(targetMarketplaceDir, 'marketplace.json'), { force: true });
|
|
49
133
|
}
|
|
50
134
|
|
|
135
|
+
// 2. Copy plugin (with .claude-plugin/plugin.json + skills/) into nested directory
|
|
136
|
+
const targetPlugin = join(targetDir, 'plugins', 'attacca-forge');
|
|
137
|
+
if (!existsSync(targetPlugin)) mkdirSync(targetPlugin, { recursive: true });
|
|
138
|
+
cpSync(sourcePlugin, targetPlugin, { recursive: true, force: true });
|
|
139
|
+
|
|
51
140
|
// Count skills
|
|
52
|
-
const
|
|
141
|
+
const targetSkills = join(targetPlugin, 'skills');
|
|
53
142
|
let skillCount = 0;
|
|
54
|
-
if (existsSync(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return existsSync(join(skillsDir, f, 'SKILL.md'));
|
|
143
|
+
if (existsSync(targetSkills)) {
|
|
144
|
+
skillCount = readdirSync(targetSkills).filter((f) => {
|
|
145
|
+
return existsSync(join(targetSkills, f, 'SKILL.md'));
|
|
58
146
|
}).length;
|
|
59
147
|
}
|
|
60
148
|
|
|
61
149
|
console.log(` ✓ ${existed ? 'Updated' : 'Installed'} ${skillCount} skills to Claude Code`);
|
|
62
150
|
console.log(` Location: ${targetDir}`);
|
|
151
|
+
|
|
152
|
+
// 3. Register marketplace in known_marketplaces.json
|
|
153
|
+
const pluginsDir = join(claudeDir, 'plugins');
|
|
154
|
+
if (!existsSync(pluginsDir)) mkdirSync(pluginsDir, { recursive: true });
|
|
155
|
+
|
|
156
|
+
registerMarketplace(claudeDir, targetDir);
|
|
157
|
+
console.log(' ✓ Marketplace registered');
|
|
158
|
+
|
|
159
|
+
// 4. Register plugin in installed_plugins.json + populate cache
|
|
160
|
+
registerPlugin(claudeDir, targetPlugin, version);
|
|
161
|
+
console.log(' ✓ Plugin registered');
|
|
162
|
+
|
|
63
163
|
console.log('');
|
|
64
164
|
console.log(' Restart Claude Code to load the new skills.');
|
|
65
165
|
console.log('');
|