@tsuzuku/arise 1.0.0 → 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/README.md +8 -3
- package/arise.schema.json +20 -2
- package/lib/cli.js +8 -1
- package/lib/config.js +44 -6
- package/lib/git.js +73 -18
- package/lib/layout.js +6 -3
- package/lib/lifecycle/nuke.js +34 -33
- package/package.json +4 -1
- package/presets/generic.js +1 -1
- package/presets/laravel.js +1 -1
- package/presets/node.js +1 -1
- package/types.d.ts +6 -1
- package/worktree.schema.json +20 -2
package/README.md
CHANGED
|
@@ -38,8 +38,12 @@ arise --branch feature/login
|
|
|
38
38
|
# Explicitly specify a preset
|
|
39
39
|
arise --branch feature/login --preset laravel
|
|
40
40
|
|
|
41
|
-
#
|
|
42
|
-
arise --branch feature/login --
|
|
41
|
+
# Choose your AI CLI agent (e.g. Claude Code, Aider, Antigravity, or Copilot)
|
|
42
|
+
arise --branch feature/login --agent claude
|
|
43
|
+
arise --branch feature/login -a aider
|
|
44
|
+
|
|
45
|
+
# Specify base branch, custom workspace name, or focus pane
|
|
46
|
+
arise --branch feature/login --source develop --focus claude
|
|
43
47
|
```
|
|
44
48
|
|
|
45
49
|
### 2. Nuke / Clean Up a Worktree
|
|
@@ -82,7 +86,8 @@ Place a `.ariserc.json` in your repository root, worktrees base directory, or `~
|
|
|
82
86
|
},
|
|
83
87
|
"workspace": {
|
|
84
88
|
"labelPrefix": "[API] ",
|
|
85
|
-
"
|
|
89
|
+
"agent": "claude",
|
|
90
|
+
"defaultFocus": "claude"
|
|
86
91
|
},
|
|
87
92
|
"scaffold": {
|
|
88
93
|
"envSource": "/path/to/shared/.env",
|
package/arise.schema.json
CHANGED
|
@@ -34,15 +34,29 @@
|
|
|
34
34
|
},
|
|
35
35
|
"workspace": {
|
|
36
36
|
"type": "object",
|
|
37
|
-
"description": "Herdr workspace naming and focus configuration",
|
|
37
|
+
"description": "Herdr workspace naming, agent, and focus configuration",
|
|
38
38
|
"properties": {
|
|
39
39
|
"labelPrefix": {
|
|
40
40
|
"type": "string",
|
|
41
41
|
"description": "Prefix added to Herdr workspace labels (e.g. '[BE] ')"
|
|
42
42
|
},
|
|
43
|
+
"agent": {
|
|
44
|
+
"type": ["string", "object", "null"],
|
|
45
|
+
"description": "CLI AI agent to run in the workspace pane ('agy', 'claude', 'aider', 'copilot', 'none', or custom command object)",
|
|
46
|
+
"properties": {
|
|
47
|
+
"cmd": {
|
|
48
|
+
"type": ["string", "null"],
|
|
49
|
+
"description": "Command to execute the agent"
|
|
50
|
+
},
|
|
51
|
+
"title": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"description": "Display title for the agent pane in Herdr"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
43
57
|
"defaultFocus": {
|
|
44
58
|
"type": "string",
|
|
45
|
-
"description": "Default pane to focus upon creation ('agy', 'vim', 'logs', 'server', 'shell')"
|
|
59
|
+
"description": "Default pane to focus upon creation ('agent', 'agy', 'claude', 'vim', 'logs', 'server', 'shell')"
|
|
46
60
|
}
|
|
47
61
|
},
|
|
48
62
|
"additionalProperties": false
|
|
@@ -83,6 +97,10 @@
|
|
|
83
97
|
"focus": {
|
|
84
98
|
"type": "boolean",
|
|
85
99
|
"description": "Whether this pane should receive focus by default"
|
|
100
|
+
},
|
|
101
|
+
"isAgent": {
|
|
102
|
+
"type": "boolean",
|
|
103
|
+
"description": "Whether this pane is designated as the AI CLI agent pane"
|
|
86
104
|
}
|
|
87
105
|
},
|
|
88
106
|
"additionalProperties": false
|
package/lib/cli.js
CHANGED
|
@@ -15,7 +15,8 @@ Creation Arguments:
|
|
|
15
15
|
--workspace, -w <name> (Optional) Custom Herdr workspace name.
|
|
16
16
|
--source, -s, --base <src> (Optional) Base branch if creating a new branch. Defaults to preset default (e.g. 'develop' / 'prod').
|
|
17
17
|
--preset, -p <preset> (Optional) Project preset ('node', 'laravel', 'generic', or custom). Auto-detected if omitted.
|
|
18
|
-
--
|
|
18
|
+
--agent, -a <agent> (Optional) AI CLI agent to run in workspace ('agy', 'claude', 'aider', 'copilot', etc.).
|
|
19
|
+
--focus, -f <pane> (Optional) Pane to focus ('agent', 'agy', 'claude', 'vim', 'logs', 'server', 'shell'). Defaults to active agent or 'agy'.
|
|
19
20
|
|
|
20
21
|
Nuke / Cleanup Arguments:
|
|
21
22
|
--nuke, -n [<target>] Nuke the worktree: closes Herdr workspace, removes worktree directory,
|
|
@@ -49,6 +50,7 @@ function parseArgs(argv = process.argv.slice(2)) {
|
|
|
49
50
|
workspaceName: null,
|
|
50
51
|
source: null,
|
|
51
52
|
presetName: null,
|
|
53
|
+
agent: null,
|
|
52
54
|
focusTarget: null,
|
|
53
55
|
installSkill: false,
|
|
54
56
|
skillScope: 'global',
|
|
@@ -97,6 +99,11 @@ function parseArgs(argv = process.argv.slice(2)) {
|
|
|
97
99
|
} else if (arg === '--preset' || arg === '-p') {
|
|
98
100
|
flags.presetName = argv[i + 1];
|
|
99
101
|
i++;
|
|
102
|
+
} else if (arg === '--agent' || arg === '-a') {
|
|
103
|
+
flags.agent = argv[i + 1];
|
|
104
|
+
i++;
|
|
105
|
+
} else if (arg.startsWith('--agent=')) {
|
|
106
|
+
flags.agent = arg.slice(8);
|
|
100
107
|
} else if (arg === '--focus') {
|
|
101
108
|
flags.focusTarget = argv[i + 1];
|
|
102
109
|
i++;
|
package/lib/config.js
CHANGED
|
@@ -91,18 +91,56 @@ function resolveConfiguration(flags = {}, cwd = process.cwd()) {
|
|
|
91
91
|
protectedBranches: (fileConfig.repo && fileConfig.repo.protectedBranches) || (preset.repo && preset.repo.protectedBranches) || ['main', 'master', 'develop', 'prod', 'staging'],
|
|
92
92
|
};
|
|
93
93
|
|
|
94
|
-
// 3.
|
|
94
|
+
// 3. Resolve AI CLI Agent
|
|
95
|
+
const resolvedAgent = flags.agent
|
|
96
|
+
|| process.env.ARISE_AGENT
|
|
97
|
+
|| (fileConfig.workspace && fileConfig.workspace.agent)
|
|
98
|
+
|| (preset.workspace && preset.workspace.agent)
|
|
99
|
+
|| 'agy';
|
|
100
|
+
|
|
101
|
+
// 4. Merge Workspace Settings
|
|
102
|
+
const defaultFocus = (fileConfig.workspace && fileConfig.workspace.defaultFocus)
|
|
103
|
+
|| (preset.workspace && preset.workspace.defaultFocus)
|
|
104
|
+
|| 'agy';
|
|
105
|
+
|
|
95
106
|
const workspaceConfig = {
|
|
96
107
|
labelPrefix: (fileConfig.workspace && fileConfig.workspace.labelPrefix !== undefined)
|
|
97
108
|
? fileConfig.workspace.labelPrefix
|
|
98
109
|
: (preset.workspace && preset.workspace.labelPrefix !== undefined ? preset.workspace.labelPrefix : ''),
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|| 'agy',
|
|
110
|
+
agent: resolvedAgent,
|
|
111
|
+
defaultFocus,
|
|
102
112
|
};
|
|
103
113
|
|
|
104
|
-
//
|
|
105
|
-
const
|
|
114
|
+
// 5. Merge & Customize Layout for Active Agent
|
|
115
|
+
const baseLayout = fileConfig.layout || preset.layout || [];
|
|
116
|
+
const layout = baseLayout.map((pane) => {
|
|
117
|
+
const isAgentPane = pane.isAgent || pane.id === 'agy' || pane.id === 'agent' || pane.id === 'ai';
|
|
118
|
+
if (!isAgentPane) return { ...pane };
|
|
119
|
+
|
|
120
|
+
let agentCmd = pane.cmd;
|
|
121
|
+
let agentTitle = pane.title;
|
|
122
|
+
|
|
123
|
+
if (typeof resolvedAgent === 'string') {
|
|
124
|
+
const lower = resolvedAgent.toLowerCase().trim();
|
|
125
|
+
if (lower === 'none' || lower === 'false' || lower === 'null' || lower === 'disabled') {
|
|
126
|
+
agentCmd = null;
|
|
127
|
+
agentTitle = 'shell';
|
|
128
|
+
} else {
|
|
129
|
+
agentCmd = resolvedAgent;
|
|
130
|
+
agentTitle = resolvedAgent;
|
|
131
|
+
}
|
|
132
|
+
} else if (typeof resolvedAgent === 'object' && resolvedAgent !== null) {
|
|
133
|
+
agentCmd = resolvedAgent.cmd !== undefined ? resolvedAgent.cmd : (resolvedAgent.command || null);
|
|
134
|
+
agentTitle = resolvedAgent.title || resolvedAgent.cmd || pane.title;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
...pane,
|
|
139
|
+
cmd: agentCmd,
|
|
140
|
+
title: agentTitle,
|
|
141
|
+
isAgent: true,
|
|
142
|
+
};
|
|
143
|
+
});
|
|
106
144
|
|
|
107
145
|
// 5. Merge Scaffolding Settings
|
|
108
146
|
const scaffoldConfig = {
|
package/lib/git.js
CHANGED
|
@@ -72,17 +72,47 @@ function getWorktrees(options = {}) {
|
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
function fetchOrigin(options = {}) {
|
|
76
|
+
const gitCmd = getGitPrefix(options);
|
|
77
|
+
try {
|
|
78
|
+
const remotes = execSync(`${gitCmd} remote`, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim().split(/\s+/);
|
|
79
|
+
if (!remotes.includes('origin')) return;
|
|
80
|
+
|
|
81
|
+
// In bare repos, remote.origin.fetch is often not set by default.
|
|
82
|
+
// Setting or fetching with the standard refspec ensures refs/remotes/origin/* are populated.
|
|
83
|
+
try {
|
|
84
|
+
const fetchRefspec = execSync(`${gitCmd} config --get remote.origin.fetch`, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
85
|
+
if (!fetchRefspec) {
|
|
86
|
+
execSync(`${gitCmd} config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"`, { stdio: 'ignore' });
|
|
87
|
+
}
|
|
88
|
+
} catch (e) {
|
|
89
|
+
try {
|
|
90
|
+
execSync(`${gitCmd} config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"`, { stdio: 'ignore' });
|
|
91
|
+
} catch (err) {}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
execSync(`${gitCmd} fetch origin`, { stdio: 'inherit' });
|
|
95
|
+
} catch (e) {
|
|
96
|
+
// Network or remote fetch error - continue gracefully with local refs
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
75
100
|
function branchExistsLocally(branch, options = {}) {
|
|
76
101
|
const gitCmd = getGitPrefix(options);
|
|
77
102
|
try {
|
|
78
|
-
execSync(`${gitCmd} rev-parse --verify "
|
|
103
|
+
execSync(`${gitCmd} rev-parse --verify "refs/heads/${branch}"`, { stdio: 'ignore' });
|
|
79
104
|
return true;
|
|
80
105
|
} catch (e) {
|
|
81
106
|
try {
|
|
82
|
-
|
|
83
|
-
return
|
|
107
|
+
execSync(`${gitCmd} rev-parse --verify "${branch}"`, { stdio: 'ignore' });
|
|
108
|
+
return true;
|
|
84
109
|
} catch (err) {
|
|
85
|
-
|
|
110
|
+
try {
|
|
111
|
+
const list = execSync(`${gitCmd} branch --list "${branch}"`, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
112
|
+
return list !== '';
|
|
113
|
+
} catch (err2) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
86
116
|
}
|
|
87
117
|
}
|
|
88
118
|
}
|
|
@@ -90,16 +120,21 @@ function branchExistsLocally(branch, options = {}) {
|
|
|
90
120
|
function branchExistsRemotely(branch, options = {}) {
|
|
91
121
|
const gitCmd = getGitPrefix(options);
|
|
92
122
|
try {
|
|
93
|
-
execSync(`${gitCmd} rev-parse --verify "origin/${branch}"`, { stdio: 'ignore' });
|
|
123
|
+
execSync(`${gitCmd} rev-parse --verify "refs/remotes/origin/${branch}"`, { stdio: 'ignore' });
|
|
94
124
|
return true;
|
|
95
125
|
} catch (e) {
|
|
96
126
|
try {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const lsRemote = execSync(`${gitCmd} ls-remote --heads origin "${branch}"`, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
100
|
-
return Boolean(lsRemote);
|
|
127
|
+
execSync(`${gitCmd} rev-parse --verify "origin/${branch}"`, { stdio: 'ignore' });
|
|
128
|
+
return true;
|
|
101
129
|
} catch (err) {
|
|
102
|
-
|
|
130
|
+
try {
|
|
131
|
+
const remoteList = execSync(`${gitCmd} branch -r --list "origin/${branch}"`, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
132
|
+
if (remoteList) return true;
|
|
133
|
+
const lsRemote = execSync(`${gitCmd} ls-remote --heads origin "${branch}"`, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
134
|
+
return Boolean(lsRemote);
|
|
135
|
+
} catch (err2) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
103
138
|
}
|
|
104
139
|
}
|
|
105
140
|
}
|
|
@@ -108,6 +143,9 @@ function createWorktree({ worktreePath, branch, source = 'develop', repoDir, bar
|
|
|
108
143
|
const options = { repoDir, bareRepo };
|
|
109
144
|
const gitCmd = getGitPrefix(options);
|
|
110
145
|
|
|
146
|
+
console.log('Fetching latest from origin...');
|
|
147
|
+
fetchOrigin(options);
|
|
148
|
+
|
|
111
149
|
const localExists = branchExistsLocally(branch, options);
|
|
112
150
|
const remoteExists = branchExistsRemotely(branch, options);
|
|
113
151
|
|
|
@@ -116,16 +154,32 @@ function createWorktree({ worktreePath, branch, source = 'develop', repoDir, bar
|
|
|
116
154
|
execSync(`${gitCmd} worktree add "${worktreePath}" "${branch}"`, { stdio: 'inherit' });
|
|
117
155
|
} else if (remoteExists) {
|
|
118
156
|
console.log(`Branch "${branch}" exists on remote. Checking out and adding worktree...`);
|
|
119
|
-
|
|
157
|
+
let startPoint = `origin/${branch}`;
|
|
158
|
+
try {
|
|
159
|
+
execSync(`${gitCmd} rev-parse --verify "${startPoint}"`, { stdio: 'ignore' });
|
|
160
|
+
} catch (e) {
|
|
161
|
+
startPoint = branch;
|
|
162
|
+
}
|
|
163
|
+
if (bareRepo) {
|
|
164
|
+
execSync(`${gitCmd} branch --track "${branch}" "${startPoint}"`, { stdio: 'inherit' });
|
|
165
|
+
execSync(`${gitCmd} worktree add "${worktreePath}" "${branch}"`, { stdio: 'inherit' });
|
|
166
|
+
} else {
|
|
167
|
+
execSync(`${gitCmd} worktree add "${worktreePath}" -b "${branch}" "${startPoint}"`, { stdio: 'inherit' });
|
|
168
|
+
}
|
|
120
169
|
} else {
|
|
121
170
|
// Creating a brand new branch off source
|
|
122
|
-
|
|
171
|
+
let startPoint = null;
|
|
123
172
|
try {
|
|
124
|
-
execSync(`${gitCmd}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
173
|
+
execSync(`${gitCmd} rev-parse --verify "origin/${source}"`, { stdio: 'ignore' });
|
|
174
|
+
startPoint = `origin/${source}`;
|
|
175
|
+
} catch (e) {
|
|
176
|
+
try {
|
|
177
|
+
execSync(`${gitCmd} rev-parse --verify "${source}"`, { stdio: 'ignore' });
|
|
178
|
+
startPoint = source;
|
|
179
|
+
} catch (err) {
|
|
180
|
+
throw new Error(`Cannot create branch "${branch}" off "${source}": Base branch or ref "${source}" does not exist locally or on remote.`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
129
183
|
|
|
130
184
|
console.log(`Creating branch "${branch}" off "${startPoint}" and adding worktree...`);
|
|
131
185
|
if (bareRepo) {
|
|
@@ -156,7 +210,7 @@ function syncPrimaryBranch({ branch, worktreePath, repoDir, bareRepo, protectedB
|
|
|
156
210
|
console.warn(`==> Please commit, stash, or discard your changes if you want to sync with origin.`);
|
|
157
211
|
} else {
|
|
158
212
|
console.log(`==> Syncing ${branch} with origin/${branch}...`);
|
|
159
|
-
|
|
213
|
+
fetchOrigin(options);
|
|
160
214
|
const resetCmd = bareRepo
|
|
161
215
|
? `${gitCmd} --work-tree="${worktreePath}" reset --hard origin/${branch}`
|
|
162
216
|
: `git -C "${worktreePath}" reset --hard origin/${branch}`;
|
|
@@ -215,6 +269,7 @@ module.exports = {
|
|
|
215
269
|
getGitPrefix,
|
|
216
270
|
getRepoRootDir,
|
|
217
271
|
getWorktrees,
|
|
272
|
+
fetchOrigin,
|
|
218
273
|
branchExistsLocally,
|
|
219
274
|
branchExistsRemotely,
|
|
220
275
|
createWorktree,
|
package/lib/layout.js
CHANGED
|
@@ -7,7 +7,7 @@ const DEFAULT_QUADRANT_LAYOUT = [
|
|
|
7
7
|
{ id: 'vim', title: 'vim', cmd: 'vim .', position: 'root' },
|
|
8
8
|
{ id: 'server', title: 'server', cmd: null, split: 'right', from: 'vim' },
|
|
9
9
|
{ id: 'shell', title: 'shell', cmd: null, split: 'down', from: 'vim' },
|
|
10
|
-
{ id: 'agy', title: 'agy', cmd: 'agy', split: 'down', from: 'server', focus: true },
|
|
10
|
+
{ id: 'agy', title: 'agy', cmd: 'agy', split: 'down', from: 'server', focus: true, isAgent: true },
|
|
11
11
|
];
|
|
12
12
|
|
|
13
13
|
/**
|
|
@@ -17,7 +17,7 @@ const DEFAULT_QUADRANT_LAYOUT = [
|
|
|
17
17
|
* @param {Array} options.layout Array of pane definitions
|
|
18
18
|
* @param {string} options.rootPaneId ID of the root pane created with workspace
|
|
19
19
|
* @param {string} options.cwd Working directory for panes
|
|
20
|
-
* @param {string} options.focusTarget Name or ID of pane to focus (e.g. 'agy', 'vim', 'logs', 'shell')
|
|
20
|
+
* @param {string} options.focusTarget Name or ID of pane to focus (e.g. 'agent', 'agy', 'claude', 'vim', 'logs', 'shell')
|
|
21
21
|
*/
|
|
22
22
|
function renderLayout({ layout = DEFAULT_QUADRANT_LAYOUT, rootPaneId, cwd, focusTarget = 'agy' }) {
|
|
23
23
|
const paneMap = new Map(); // id -> paneId
|
|
@@ -31,7 +31,10 @@ function renderLayout({ layout = DEFAULT_QUADRANT_LAYOUT, rootPaneId, cwd, focus
|
|
|
31
31
|
if (!normalizedFocus) return false;
|
|
32
32
|
const idMatch = paneDef.id && paneDef.id.toLowerCase() === normalizedFocus;
|
|
33
33
|
const titleMatch = paneDef.title && paneDef.title.toLowerCase() === normalizedFocus;
|
|
34
|
-
|
|
34
|
+
const cmdMatch = paneDef.cmd && paneDef.cmd.toLowerCase().split(/\s+/)[0] === normalizedFocus;
|
|
35
|
+
const agentAlias = (normalizedFocus === 'agent' || normalizedFocus === 'agy' || normalizedFocus === 'ai') &&
|
|
36
|
+
(paneDef.id === 'agy' || paneDef.id === 'agent' || paneDef.isAgent);
|
|
37
|
+
return Boolean(idMatch || titleMatch || cmdMatch || agentAlias);
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
// 2. Iterate through pane definitions
|
package/lib/lifecycle/nuke.js
CHANGED
|
@@ -108,19 +108,7 @@ async function executeNuke(flags, config, cwd = process.cwd()) {
|
|
|
108
108
|
await config.hooks.onPreNuke(ctx);
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
// 1.
|
|
112
|
-
const prefix = config.workspace.labelPrefix || '';
|
|
113
|
-
const matchTargets = [
|
|
114
|
-
targetSessionName,
|
|
115
|
-
targetDirname,
|
|
116
|
-
targetBranch,
|
|
117
|
-
prefix ? `${prefix}${targetSessionName}` : null,
|
|
118
|
-
prefix ? `${prefix}${targetDirname}` : null,
|
|
119
|
-
].filter(Boolean);
|
|
120
|
-
|
|
121
|
-
herdr.closeWorkspacesMatching(matchTargets);
|
|
122
|
-
|
|
123
|
-
// 2. Remove Worktree & Directory
|
|
111
|
+
// 1. Remove Worktree & Directory
|
|
124
112
|
console.log(`\n==> [1/3] Removing git worktree and directory...`);
|
|
125
113
|
const isRegisteredWorktree = worktrees.some(wt => path.resolve(wt.path) === path.resolve(targetWorktreePath));
|
|
126
114
|
|
|
@@ -148,27 +136,46 @@ async function executeNuke(flags, config, cwd = process.cwd()) {
|
|
|
148
136
|
console.log(`Worktree directory is clean.`);
|
|
149
137
|
}
|
|
150
138
|
|
|
151
|
-
//
|
|
139
|
+
// Helper for final cleanup steps (Herdr workspace close + Post-Nuke hook)
|
|
140
|
+
const finishCleanup = async () => {
|
|
141
|
+
// Post-Nuke Hook
|
|
142
|
+
if (typeof config.hooks.onPostNuke === 'function') {
|
|
143
|
+
await config.hooks.onPostNuke(ctx);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Close Herdr Workspace if running (performed last so active pane is not terminated mid-cleanup)
|
|
147
|
+
const prefix = config.workspace.labelPrefix || '';
|
|
148
|
+
const matchTargets = [
|
|
149
|
+
targetSessionName,
|
|
150
|
+
targetDirname,
|
|
151
|
+
targetBranch,
|
|
152
|
+
prefix ? `${prefix}${targetSessionName}` : null,
|
|
153
|
+
prefix ? `${prefix}${targetDirname}` : null,
|
|
154
|
+
].filter(Boolean);
|
|
155
|
+
|
|
156
|
+
herdr.closeWorkspacesMatching(matchTargets);
|
|
157
|
+
|
|
158
|
+
console.log(`\nCleanup complete!`);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// 2. Delete Local Branch
|
|
152
162
|
if (flags.dirOnly) {
|
|
153
163
|
console.log(`\n==> Skipping branch deletion (--dir-only / --keep-branch).`);
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
process.exit(0);
|
|
164
|
+
await finishCleanup();
|
|
165
|
+
return;
|
|
157
166
|
}
|
|
158
167
|
|
|
159
168
|
const protectedBranches = config.repo.protectedBranches || ['main', 'master', 'develop', 'prod', 'staging', 'production'];
|
|
160
169
|
if (targetBranch && protectedBranches.includes(targetBranch)) {
|
|
161
170
|
console.warn(`\n==> WARNING: "${targetBranch}" is a protected branch. Skipping local and remote branch deletion.`);
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
process.exit(0);
|
|
171
|
+
await finishCleanup();
|
|
172
|
+
return;
|
|
165
173
|
}
|
|
166
174
|
|
|
167
175
|
if (!targetBranch) {
|
|
168
176
|
console.log(`\n==> No branch associated with "${target}". Skipping branch deletion.`);
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
process.exit(0);
|
|
177
|
+
await finishCleanup();
|
|
178
|
+
return;
|
|
172
179
|
}
|
|
173
180
|
|
|
174
181
|
console.log(`\n==> [2/3] Deleting local branch "${targetBranch}"...`);
|
|
@@ -181,12 +188,11 @@ async function executeNuke(flags, config, cwd = process.cwd()) {
|
|
|
181
188
|
console.log(`Local branch "${targetBranch}" does not exist or was already deleted.`);
|
|
182
189
|
}
|
|
183
190
|
|
|
184
|
-
//
|
|
191
|
+
// 3. Delete Remote Branch
|
|
185
192
|
if (flags.keepRemote) {
|
|
186
193
|
console.log(`\n==> Skipping remote branch deletion (--keep-remote / --local-only).`);
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
process.exit(0);
|
|
194
|
+
await finishCleanup();
|
|
195
|
+
return;
|
|
190
196
|
}
|
|
191
197
|
|
|
192
198
|
console.log(`\n==> [3/3] Deleting remote branch "${targetBranch}" from origin...`);
|
|
@@ -198,12 +204,7 @@ async function executeNuke(flags, config, cwd = process.cwd()) {
|
|
|
198
204
|
console.log(`Remote branch "${targetBranch}" does not exist on origin or was already deleted.`);
|
|
199
205
|
}
|
|
200
206
|
|
|
201
|
-
|
|
202
|
-
if (typeof config.hooks.onPostNuke === 'function') {
|
|
203
|
-
await config.hooks.onPostNuke(ctx);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
console.log(`\nCleanup complete!`);
|
|
207
|
+
await finishCleanup();
|
|
207
208
|
}
|
|
208
209
|
|
|
209
210
|
module.exports = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tsuzuku/arise",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
4
7
|
"description": "Arise - Unified workplace-agnostic Git worktree and Herdr workspace orchestrator with pluggable project presets",
|
|
5
8
|
"main": "index.js",
|
|
6
9
|
"bin": {
|
package/presets/generic.js
CHANGED
|
@@ -21,7 +21,7 @@ module.exports = {
|
|
|
21
21
|
layout: [
|
|
22
22
|
{ id: 'vim', title: 'vim', cmd: 'vim .', position: 'root' },
|
|
23
23
|
{ id: 'shell', title: 'shell', cmd: null, split: 'right', from: 'vim' },
|
|
24
|
-
{ id: 'agy', title: 'agy', cmd: 'agy', split: 'down', from: 'shell', focus: true },
|
|
24
|
+
{ id: 'agy', title: 'agy', cmd: 'agy', split: 'down', from: 'shell', focus: true, isAgent: true },
|
|
25
25
|
],
|
|
26
26
|
|
|
27
27
|
hooks: {
|
package/presets/laravel.js
CHANGED
|
@@ -23,7 +23,7 @@ module.exports = {
|
|
|
23
23
|
{ id: 'vim', title: 'vim', cmd: 'vim .', position: 'root' },
|
|
24
24
|
{ id: 'logs', title: 'logs', cmd: 'tail -f storage/logs/laravel.log', split: 'right', from: 'vim' },
|
|
25
25
|
{ id: 'shell', title: 'shell', cmd: null, split: 'down', from: 'vim' },
|
|
26
|
-
{ id: 'agy', title: 'agy', cmd: 'agy', split: 'down', from: 'logs', focus: true },
|
|
26
|
+
{ id: 'agy', title: 'agy', cmd: 'agy', split: 'down', from: 'logs', focus: true, isAgent: true },
|
|
27
27
|
],
|
|
28
28
|
|
|
29
29
|
hooks: {
|
package/presets/node.js
CHANGED
|
@@ -22,7 +22,7 @@ module.exports = {
|
|
|
22
22
|
{ id: 'vim', title: 'vim', cmd: 'vim .', position: 'root' },
|
|
23
23
|
{ id: 'server', title: 'npm server', cmd: 'npm run dev', split: 'right', from: 'vim' },
|
|
24
24
|
{ id: 'shell', title: 'shell', cmd: null, split: 'down', from: 'vim' },
|
|
25
|
-
{ id: 'agy', title: 'agy', cmd: 'agy', split: 'down', from: 'server', focus: true },
|
|
25
|
+
{ id: 'agy', title: 'agy', cmd: 'agy', split: 'down', from: 'server', focus: true, isAgent: true },
|
|
26
26
|
],
|
|
27
27
|
|
|
28
28
|
hooks: {
|
package/types.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export interface CliFlags {
|
|
|
16
16
|
workspaceName: string | null;
|
|
17
17
|
source: string | null;
|
|
18
18
|
presetName: string | null;
|
|
19
|
+
agent: string | null;
|
|
19
20
|
focusTarget: string | null;
|
|
20
21
|
installSkill: boolean;
|
|
21
22
|
skillScope: 'global' | 'local' | null;
|
|
@@ -41,6 +42,8 @@ export interface PaneDefinition {
|
|
|
41
42
|
split?: SplitDirection;
|
|
42
43
|
/** Whether to focus this pane by default */
|
|
43
44
|
focus?: boolean;
|
|
45
|
+
/** Whether this pane is designated as the AI CLI agent pane */
|
|
46
|
+
isAgent?: boolean;
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
export interface RepoConfig {
|
|
@@ -57,7 +60,9 @@ export interface RepoConfig {
|
|
|
57
60
|
export interface WorkspaceConfig {
|
|
58
61
|
/** Prefix added to Herdr workspace labels (e.g. '[BE] ') */
|
|
59
62
|
labelPrefix?: string;
|
|
60
|
-
/**
|
|
63
|
+
/** CLI AI agent to run in the workspace pane ('agy', 'claude', 'aider', 'copilot', 'none', etc.) */
|
|
64
|
+
agent?: string | { cmd: string; title?: string; [key: string]: any } | null;
|
|
65
|
+
/** Default pane to focus ('agent', 'agy', 'claude', 'vim', 'logs', 'server', 'shell') */
|
|
61
66
|
defaultFocus?: string;
|
|
62
67
|
}
|
|
63
68
|
|
package/worktree.schema.json
CHANGED
|
@@ -34,15 +34,29 @@
|
|
|
34
34
|
},
|
|
35
35
|
"workspace": {
|
|
36
36
|
"type": "object",
|
|
37
|
-
"description": "Herdr workspace naming and focus configuration",
|
|
37
|
+
"description": "Herdr workspace naming, agent, and focus configuration",
|
|
38
38
|
"properties": {
|
|
39
39
|
"labelPrefix": {
|
|
40
40
|
"type": "string",
|
|
41
41
|
"description": "Prefix added to Herdr workspace labels (e.g. '[BE] ')"
|
|
42
42
|
},
|
|
43
|
+
"agent": {
|
|
44
|
+
"type": ["string", "object", "null"],
|
|
45
|
+
"description": "CLI AI agent to run in the workspace pane ('agy', 'claude', 'aider', 'copilot', 'none', or custom command object)",
|
|
46
|
+
"properties": {
|
|
47
|
+
"cmd": {
|
|
48
|
+
"type": ["string", "null"],
|
|
49
|
+
"description": "Command to execute the agent"
|
|
50
|
+
},
|
|
51
|
+
"title": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"description": "Display title for the agent pane in Herdr"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
43
57
|
"defaultFocus": {
|
|
44
58
|
"type": "string",
|
|
45
|
-
"description": "Default pane to focus upon creation ('agy', 'vim', 'logs', 'server', 'shell')"
|
|
59
|
+
"description": "Default pane to focus upon creation ('agent', 'agy', 'claude', 'vim', 'logs', 'server', 'shell')"
|
|
46
60
|
}
|
|
47
61
|
},
|
|
48
62
|
"additionalProperties": false
|
|
@@ -83,6 +97,10 @@
|
|
|
83
97
|
"focus": {
|
|
84
98
|
"type": "boolean",
|
|
85
99
|
"description": "Whether this pane should receive focus by default"
|
|
100
|
+
},
|
|
101
|
+
"isAgent": {
|
|
102
|
+
"type": "boolean",
|
|
103
|
+
"description": "Whether this pane is designated as the AI CLI agent pane"
|
|
86
104
|
}
|
|
87
105
|
},
|
|
88
106
|
"additionalProperties": false
|