@subshell-ai/plugin-claude-code 1.0.0 → 2.0.0
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 +203 -0
- package/dist/index.d.ts +61 -45
- package/dist/index.js +88 -63
- package/icon.svg +3 -0
- package/package.json +16 -7
package/LICENSE
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
Copyright 2026 Disaresta, LLC
|
|
2
|
+
|
|
3
|
+
Apache License
|
|
4
|
+
Version 2.0, January 2004
|
|
5
|
+
http://www.apache.org/licenses/
|
|
6
|
+
|
|
7
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
8
|
+
|
|
9
|
+
1. Definitions.
|
|
10
|
+
|
|
11
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
12
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
13
|
+
|
|
14
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
15
|
+
the copyright owner that is granting the License.
|
|
16
|
+
|
|
17
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
18
|
+
other entities that control, are controlled by, or are under common
|
|
19
|
+
control with that entity. For the purposes of this definition,
|
|
20
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
21
|
+
direction or management of such entity, whether by contract or
|
|
22
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
23
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
24
|
+
|
|
25
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
26
|
+
exercising permissions granted by this License.
|
|
27
|
+
|
|
28
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
29
|
+
including but not limited to software source code, documentation
|
|
30
|
+
source, and configuration files.
|
|
31
|
+
|
|
32
|
+
"Object" form shall mean any form resulting from mechanical
|
|
33
|
+
transformation or translation of a Source form, including but
|
|
34
|
+
not limited to compiled object code, generated documentation,
|
|
35
|
+
and conversions to other media types.
|
|
36
|
+
|
|
37
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
38
|
+
Object form, made available under the License, as indicated by a
|
|
39
|
+
copyright notice that is included in or attached to the work
|
|
40
|
+
(an example is provided in the Appendix below).
|
|
41
|
+
|
|
42
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
43
|
+
form, that is based on (or derived from) the Work and for which the
|
|
44
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
45
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
46
|
+
of this License, Derivative Works shall not include works that remain
|
|
47
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
48
|
+
the Work and Derivative Works thereof.
|
|
49
|
+
|
|
50
|
+
"Contribution" shall mean any work of authorship, including
|
|
51
|
+
the original version of the Work and any modifications or additions
|
|
52
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
53
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
54
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
55
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
56
|
+
means any form of electronic, verbal, or written communication sent
|
|
57
|
+
to the Licensor or its representatives, including but not limited to
|
|
58
|
+
communication on electronic mailing lists, source code control systems,
|
|
59
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
60
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
61
|
+
excluding communication that is conspicuously marked or otherwise
|
|
62
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
63
|
+
|
|
64
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
65
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
66
|
+
subsequently incorporated within the Work.
|
|
67
|
+
|
|
68
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
69
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
70
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
71
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
72
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
73
|
+
Work and such Derivative Works in Source or Object form.
|
|
74
|
+
|
|
75
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
76
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
77
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
78
|
+
(except as stated in this section) patent license to make, have made,
|
|
79
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
80
|
+
where such license applies only to those patent claims licensable
|
|
81
|
+
by such Contributor that are necessarily infringed by their
|
|
82
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
83
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
84
|
+
institute patent litigation against any entity (including a
|
|
85
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
86
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
87
|
+
or contributory patent infringement, then any patent licenses
|
|
88
|
+
granted to You under this License for that Work shall terminate
|
|
89
|
+
as of the date such litigation is filed.
|
|
90
|
+
|
|
91
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
92
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
93
|
+
modifications, and in Source or Object form, provided that You
|
|
94
|
+
meet the following conditions:
|
|
95
|
+
|
|
96
|
+
(a) You must give any other recipients of the Work or
|
|
97
|
+
Derivative Works a copy of this License; and
|
|
98
|
+
|
|
99
|
+
(b) You must cause any modified files to carry prominent notices
|
|
100
|
+
stating that You changed the files; and
|
|
101
|
+
|
|
102
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
103
|
+
that You distribute, all copyright, patent, trademark, and
|
|
104
|
+
attribution notices from the Source form of the Work,
|
|
105
|
+
excluding those notices that do not pertain to any part of
|
|
106
|
+
the Derivative Works; and
|
|
107
|
+
|
|
108
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
109
|
+
distribution, then any Derivative Works that You distribute must
|
|
110
|
+
include a readable copy of the attribution notices contained
|
|
111
|
+
within such NOTICE file, excluding those notices that do not
|
|
112
|
+
pertain to any part of the Derivative Works, in at least one
|
|
113
|
+
of the following places: within a NOTICE text file distributed
|
|
114
|
+
as part of the Derivative Works; within the Source form or
|
|
115
|
+
documentation, if provided along with the Derivative Works; or,
|
|
116
|
+
within a display generated by the Derivative Works, if and
|
|
117
|
+
wherever such third-party notices normally appear. The contents
|
|
118
|
+
of the NOTICE file are for informational purposes only and
|
|
119
|
+
do not modify the License. You may add Your own attribution
|
|
120
|
+
notices within Derivative Works that You distribute, alongside
|
|
121
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
122
|
+
that such additional attribution notices cannot be construed
|
|
123
|
+
as modifying the License.
|
|
124
|
+
|
|
125
|
+
You may add Your own copyright statement to Your modifications and
|
|
126
|
+
may provide additional or different license terms and conditions
|
|
127
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
128
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
129
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
130
|
+
the conditions stated in this License.
|
|
131
|
+
|
|
132
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
133
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
134
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
135
|
+
this License, without any additional terms or conditions.
|
|
136
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
137
|
+
the terms of any separate license agreement you may have executed
|
|
138
|
+
with Licensor regarding such Contributions.
|
|
139
|
+
|
|
140
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
141
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
142
|
+
except as required for reasonable and customary use in describing the
|
|
143
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
144
|
+
|
|
145
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
146
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
147
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
148
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
149
|
+
implied, including, without limitation, any warranties or conditions
|
|
150
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
151
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
152
|
+
appropriateness of using or redistributing the Work and assume any
|
|
153
|
+
risks associated with Your exercise of permissions under this License.
|
|
154
|
+
|
|
155
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
156
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
157
|
+
unless required by applicable law (such as deliberate and grossly
|
|
158
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
159
|
+
liable to You for damages, including any direct, indirect, special,
|
|
160
|
+
incidental, or consequential damages of any character arising as a
|
|
161
|
+
result of this License or out of the use or inability to use the
|
|
162
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
163
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
164
|
+
other commercial damages or losses), even if such Contributor
|
|
165
|
+
has been advised of the possibility of such damages.
|
|
166
|
+
|
|
167
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
168
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
169
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
170
|
+
or other liability obligations and/or rights consistent with this
|
|
171
|
+
License. However, in accepting such obligations, You may act only
|
|
172
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
173
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
174
|
+
defend, and hold each Contributor harmless for any liability
|
|
175
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
176
|
+
of your accepting any such warranty or additional liability.
|
|
177
|
+
|
|
178
|
+
END OF TERMS AND CONDITIONS
|
|
179
|
+
|
|
180
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
181
|
+
|
|
182
|
+
To apply the Apache License to your work, attach the following
|
|
183
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
184
|
+
replaced with your own identifying information. (Don't include
|
|
185
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
186
|
+
comment syntax for the file format. We also recommend that a
|
|
187
|
+
file or class name and description of purpose be included on the
|
|
188
|
+
same "printed page" as the copyright notice for easier
|
|
189
|
+
identification within third-party archives.
|
|
190
|
+
|
|
191
|
+
Copyright [yyyy] [name of copyright owner]
|
|
192
|
+
|
|
193
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
194
|
+
you may not use this file except in compliance with the License.
|
|
195
|
+
You may obtain a copy of the License at
|
|
196
|
+
|
|
197
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
198
|
+
|
|
199
|
+
Unless required by applicable law or agreed to in writing, software
|
|
200
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
201
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
202
|
+
See the License for the specific language governing permissions and
|
|
203
|
+
limitations under the License.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region ../../plugin-api/dist/types-
|
|
1
|
+
//#region ../../plugin-api/dist/types-B_wiApwK.d.ts
|
|
2
2
|
//#region src/types.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* The contract a Subshell plugin implements.
|
|
@@ -27,9 +27,9 @@ type PluginType = "agent-harness" | "terminal";
|
|
|
27
27
|
* branches on {@link PluginType}.
|
|
28
28
|
*/
|
|
29
29
|
type PluginCapability = "mcp" | "resume" | "attention" | "settings";
|
|
30
|
-
/** A harness
|
|
31
|
-
interface
|
|
32
|
-
/** Human-friendly
|
|
30
|
+
/** A harness preset as defined by the user (decoded JSON blobs). */
|
|
31
|
+
interface PresetDefinition {
|
|
32
|
+
/** Human-friendly preset name */
|
|
33
33
|
name: string;
|
|
34
34
|
/** Optional longer description */
|
|
35
35
|
description?: string | null;
|
|
@@ -39,21 +39,21 @@ interface ProfileDefinition {
|
|
|
39
39
|
flags: string[];
|
|
40
40
|
/** Settings blob passed to the harness (e.g. claude --settings JSON) */
|
|
41
41
|
settings: Record<string, unknown> | null;
|
|
42
|
-
/** If true, only this
|
|
42
|
+
/** If true, only this preset's config sources apply (isolation) */
|
|
43
43
|
configIsolation: boolean;
|
|
44
|
-
/** If true, new subshells from this
|
|
44
|
+
/** If true, new subshells from this preset auto-restart on exit */
|
|
45
45
|
restartOnExit?: boolean;
|
|
46
46
|
}
|
|
47
|
-
/** Snapshot of a single field-level validation error on a
|
|
48
|
-
interface
|
|
47
|
+
/** Snapshot of a single field-level validation error on a preset. */
|
|
48
|
+
interface PresetValidationIssue {
|
|
49
49
|
/** Field name (e.g. "name", "env", "settings") */
|
|
50
50
|
field: string;
|
|
51
51
|
/** Human-readable problem description */
|
|
52
52
|
message: string;
|
|
53
53
|
}
|
|
54
|
-
interface
|
|
54
|
+
interface PresetValidationResult {
|
|
55
55
|
valid: boolean;
|
|
56
|
-
issues:
|
|
56
|
+
issues: PresetValidationIssue[];
|
|
57
57
|
}
|
|
58
58
|
/** Everything a plugin needs to build a launch command. */
|
|
59
59
|
interface BuildCommandInput {
|
|
@@ -61,8 +61,8 @@ interface BuildCommandInput {
|
|
|
61
61
|
binary: string;
|
|
62
62
|
/** Working directory the harness runs in */
|
|
63
63
|
cwd: string;
|
|
64
|
-
/** The validated
|
|
65
|
-
|
|
64
|
+
/** The validated preset being used */
|
|
65
|
+
preset: PresetDefinition;
|
|
66
66
|
/** Subshell display name ("" = let the harness pick a default) */
|
|
67
67
|
subshellName: string;
|
|
68
68
|
/** Any additional CLI flags from route/request context */
|
|
@@ -88,6 +88,39 @@ interface BuildCommandInput {
|
|
|
88
88
|
id: string;
|
|
89
89
|
mode: "start" | "resume";
|
|
90
90
|
};
|
|
91
|
+
/**
|
|
92
|
+
* How a harness hook re-enters the subshell binary on the PANE's machine, as
|
|
93
|
+
* the host resolved it for that machine: `{ command, args }` with the
|
|
94
|
+
* reporting subcommand already appended, so a plugin adds only its own verb
|
|
95
|
+
* words. Splice it into whatever hook mechanism the harness has.
|
|
96
|
+
*
|
|
97
|
+
* A hook runs where the pane runs, and the only program guaranteed to exist
|
|
98
|
+
* there is the binary that launched the pane — which is why this arrives
|
|
99
|
+
* from the host rather than being a command a plugin writes for itself. The
|
|
100
|
+
* hooks here were once `bun -e '<inlined JS>'`, and every machine without a
|
|
101
|
+
* bun on its PATH (i.e. most of them) opened its harness on
|
|
102
|
+
* `bun: command not found`.
|
|
103
|
+
*
|
|
104
|
+
* Undefined means the host could not resolve one. OMIT the hooks then: a
|
|
105
|
+
* command the pane cannot run costs the same signal and adds an error to
|
|
106
|
+
* every session.
|
|
107
|
+
*/
|
|
108
|
+
reporter?: ReporterSpec;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* A command prefix that re-enters the subshell binary on a pane's machine,
|
|
112
|
+
* ready for a plugin's own verb words to be appended.
|
|
113
|
+
*
|
|
114
|
+
* Shaped like {@link McpLaunchSpec} and resolved from the same host question
|
|
115
|
+
* ("am I a compiled binary, or is `bun` running my entry script?"), but kept
|
|
116
|
+
* separate because they carry different subcommands and the MCP one is
|
|
117
|
+
* operator-overridable while this one is not.
|
|
118
|
+
*/
|
|
119
|
+
interface ReporterSpec {
|
|
120
|
+
/** Executable to run (the binary itself, or the interpreter running it). */
|
|
121
|
+
command: string;
|
|
122
|
+
/** Everything before the plugin's verb words (any entry script, then the subcommand). */
|
|
123
|
+
args: string[];
|
|
91
124
|
}
|
|
92
125
|
/**
|
|
93
126
|
* Restart-resume, implemented only by plugins that can continue a previous
|
|
@@ -136,12 +169,12 @@ interface McpRegistration {
|
|
|
136
169
|
args?: string[];
|
|
137
170
|
/**
|
|
138
171
|
* Extra pane env the harness needs to discover the file (e.g. OPENCODE_CONFIG).
|
|
139
|
-
* Consumed by the HOST, which bakes it into the pane env ahead of
|
|
172
|
+
* Consumed by the HOST, which bakes it into the pane env ahead of preset env;
|
|
140
173
|
* plugins never read it back.
|
|
141
174
|
*/
|
|
142
175
|
env?: Record<string, string>;
|
|
143
176
|
}
|
|
144
|
-
/** One copy-paste line shown in the
|
|
177
|
+
/** One copy-paste line shown in the preset editor for manual-setup harnesses. */
|
|
145
178
|
interface McpSetupStep {
|
|
146
179
|
/** What the user should do, and where the text goes */
|
|
147
180
|
label: string;
|
|
@@ -226,8 +259,8 @@ interface PluginHost {
|
|
|
226
259
|
interface SubshellPlugin {
|
|
227
260
|
/** Builds the argv (no shell) used to launch a subshell. */
|
|
228
261
|
buildCommand(input: BuildCommandInput): string[];
|
|
229
|
-
/** Validates a
|
|
230
|
-
|
|
262
|
+
/** Validates a preset definition before saving. */
|
|
263
|
+
validatePreset(preset: PresetDefinition): PresetValidationResult;
|
|
231
264
|
/** Which optional members below are meaningful on this plugin. */
|
|
232
265
|
capabilities(): PluginCapability[];
|
|
233
266
|
/** Maps a harness exit code to a human label (null = unknown). */
|
|
@@ -259,14 +292,14 @@ interface SubshellPlugin {
|
|
|
259
292
|
mcpRegistration?(launch: McpLaunchSpec, configPath: string): McpRegistration;
|
|
260
293
|
/** How users obtain the subshell MCP tools here. Declare the `mcp` capability with it. */
|
|
261
294
|
mcpSetup?(launch: McpLaunchSpec): McpSetupInfo;
|
|
262
|
-
/** Settings rendered in the
|
|
263
|
-
|
|
264
|
-
/** Known extra env var suggestions for the
|
|
295
|
+
/** Settings rendered in the PRESET editor and stored on the preset. */
|
|
296
|
+
presetSettings?(): SettingsField[];
|
|
297
|
+
/** Known extra env var suggestions for the preset editor. */
|
|
265
298
|
suggestedEnv?(): {
|
|
266
299
|
key: string;
|
|
267
300
|
description: string;
|
|
268
301
|
}[];
|
|
269
|
-
/** Known CLI flag suggestions for the
|
|
302
|
+
/** Known CLI flag suggestions for the preset editor. */
|
|
270
303
|
suggestedFlags?(): {
|
|
271
304
|
flag: string;
|
|
272
305
|
description: string;
|
|
@@ -304,7 +337,11 @@ interface SubshellManifest {
|
|
|
304
337
|
name: string;
|
|
305
338
|
/** One-line description shown in the UI */
|
|
306
339
|
description: string;
|
|
307
|
-
/**
|
|
340
|
+
/**
|
|
341
|
+
* Relative path to this plugin's icon file, e.g. `icon.svg`. Served by
|
|
342
|
+
* the control plane at `/api/plugins/<id>/icon`; absent means the UI
|
|
343
|
+
* renders a monogram instead.
|
|
344
|
+
*/
|
|
308
345
|
icon?: string;
|
|
309
346
|
/** Module to import, relative to the package directory */
|
|
310
347
|
entry: string;
|
|
@@ -328,34 +365,13 @@ interface SubshellManifest {
|
|
|
328
365
|
declare const manifest: SubshellManifest;
|
|
329
366
|
//#endregion
|
|
330
367
|
//#region src/index.d.ts
|
|
331
|
-
/** The `--settings` hooks object injected into every Claude Code launch. */
|
|
332
|
-
declare const ATTENTION_HOOKS: {
|
|
333
|
-
readonly Stop: readonly [{
|
|
334
|
-
readonly hooks: readonly [{
|
|
335
|
-
readonly type: "command";
|
|
336
|
-
readonly command: string;
|
|
337
|
-
}];
|
|
338
|
-
}];
|
|
339
|
-
readonly Notification: readonly [{
|
|
340
|
-
readonly hooks: readonly [{
|
|
341
|
-
readonly type: "command";
|
|
342
|
-
readonly command: string;
|
|
343
|
-
}];
|
|
344
|
-
}];
|
|
345
|
-
readonly SessionStart: readonly [{
|
|
346
|
-
readonly hooks: readonly [{
|
|
347
|
-
readonly type: "command";
|
|
348
|
-
readonly command: string;
|
|
349
|
-
}];
|
|
350
|
-
}];
|
|
351
|
-
};
|
|
352
368
|
/**
|
|
353
369
|
* Built-in harness: Claude Code.
|
|
354
370
|
*
|
|
355
371
|
* Launch shape:
|
|
356
|
-
* claude --settings <json> --name <subshell> [
|
|
372
|
+
* claude --settings <json> --name <subshell> [preset flags] [extra flags]
|
|
357
373
|
* Settings are passed as a JSON string via `--settings` (per-invocation, so
|
|
358
|
-
*
|
|
374
|
+
* preset config never needs to write into the user's real ~/.claude).
|
|
359
375
|
*
|
|
360
376
|
* Identity, detection and install guidance live in this package's
|
|
361
377
|
* package.json `subshell` block, NOT here: the host reads them without
|
|
@@ -368,4 +384,4 @@ declare const ATTENTION_HOOKS: {
|
|
|
368
384
|
*/
|
|
369
385
|
declare const createPlugin: PluginFactory;
|
|
370
386
|
//#endregion
|
|
371
|
-
export {
|
|
387
|
+
export { createPlugin as default, manifest };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { join, resolve } from "node:path";
|
|
2
|
-
//#region ../../plugin-api/dist/manifest-
|
|
2
|
+
//#region ../../plugin-api/dist/manifest-Czd9ea1g.js
|
|
3
3
|
/** Every {@link PluginType}, for anything that has to iterate or validate them. */
|
|
4
4
|
const PLUGIN_TYPES = ["agent-harness", "terminal"];
|
|
5
5
|
/**
|
|
@@ -10,6 +10,17 @@ const PLUGIN_TYPES = ["agent-harness", "terminal"];
|
|
|
10
10
|
*/
|
|
11
11
|
const MCP_SERVER_NAME = "subshell";
|
|
12
12
|
/**
|
|
13
|
+
* Image formats a plugin icon may be. The server maps these to a Content-Type
|
|
14
|
+
* from a fixed table rather than sniffing the file, so this list and that
|
|
15
|
+
* table are one decision: adding a format here without adding it there leaves
|
|
16
|
+
* an icon that parses and then 500s when something asks for it.
|
|
17
|
+
*/
|
|
18
|
+
const ICON_EXTENSIONS = [
|
|
19
|
+
".svg",
|
|
20
|
+
".png",
|
|
21
|
+
".webp"
|
|
22
|
+
];
|
|
23
|
+
/**
|
|
13
24
|
* Ids become directory names under `<dataDir>/plugins/`, so they are path
|
|
14
25
|
* segments and nothing else: no separators, no traversal, no surprises from a
|
|
15
26
|
* case-insensitive filesystem.
|
|
@@ -32,13 +43,18 @@ function parseManifest(pkgJson) {
|
|
|
32
43
|
const block = pkgJson.subshell;
|
|
33
44
|
if (!isRecord(block)) return { error: "package.json has no `subshell` block, so it is not a Subshell plugin" };
|
|
34
45
|
if (typeof block.apiVersion !== "number" || !Number.isInteger(block.apiVersion)) return { error: "`subshell.apiVersion` must be an integer" };
|
|
35
|
-
if (block.apiVersion < 1) return { error: `this plugin declares plugin-api ${block.apiVersion}, which is not a version; the lowest is 1 and this host implements
|
|
36
|
-
if (block.apiVersion >
|
|
46
|
+
if (block.apiVersion < 1) return { error: `this plugin declares plugin-api ${block.apiVersion}, which is not a version; the lowest is 1 and this host implements 2` };
|
|
47
|
+
if (block.apiVersion > 2) return { error: `this plugin needs plugin-api ${block.apiVersion}, but this host implements 2; upgrade the agent` };
|
|
37
48
|
if (typeof block.id !== "string" || !ID_RE.test(block.id)) return { error: "`subshell.id` must be lowercase letters, digits and hyphens, starting alphanumeric (it becomes a directory name)" };
|
|
38
49
|
if (typeof block.type !== "string" || !PLUGIN_TYPES.includes(block.type)) return { error: `\`subshell.type\` must be one of: ${PLUGIN_TYPES.join(", ")}` };
|
|
39
50
|
if (typeof block.name !== "string" || block.name.trim() === "") return { error: "`subshell.name` must be a non-empty string" };
|
|
40
51
|
if (typeof block.description !== "string") return { error: "`subshell.description` must be a string" };
|
|
41
|
-
if (block.icon !== void 0
|
|
52
|
+
if (block.icon !== void 0) {
|
|
53
|
+
if (typeof block.icon !== "string" || block.icon.trim() === "") return { error: "`subshell.icon` must be a non-empty relative path to an image in the package" };
|
|
54
|
+
if (block.icon.startsWith("/") || block.icon.split("/").includes("..")) return { error: "`subshell.icon` must stay inside the package (no leading `/` and no `..` segment)" };
|
|
55
|
+
const icon = block.icon;
|
|
56
|
+
if (!ICON_EXTENSIONS.some((ext) => icon.endsWith(ext))) return { error: `\`subshell.icon\` must end in one of: ${ICON_EXTENSIONS.join(", ")}` };
|
|
57
|
+
}
|
|
42
58
|
if (typeof block.entry !== "string" || block.entry.trim() === "") return { error: "`subshell.entry` must be a non-empty relative path" };
|
|
43
59
|
if (block.entry.startsWith("/") || block.entry.split("/").includes("..")) return { error: "`subshell.entry` must stay inside the package (no leading `/` and no `..` segment)" };
|
|
44
60
|
let detect;
|
|
@@ -81,23 +97,23 @@ function parseManifest(pkgJson) {
|
|
|
81
97
|
//#endregion
|
|
82
98
|
//#region ../../plugin-api/dist/index.js
|
|
83
99
|
/**
|
|
84
|
-
* The
|
|
100
|
+
* The preset checks every built-in harness performs unchanged: a non-empty
|
|
85
101
|
* name, string env values, and flags that look like flags. Harnesses with
|
|
86
102
|
* extra rules run their own checks on top of these.
|
|
87
|
-
* @param
|
|
103
|
+
* @param preset - The decoded preset definition to check
|
|
88
104
|
* @returns Issues per field; `valid` is true when no issues were found
|
|
89
105
|
*/
|
|
90
|
-
function
|
|
106
|
+
function validateGenericPreset(preset) {
|
|
91
107
|
const issues = [];
|
|
92
|
-
if (!
|
|
108
|
+
if (!preset.name || preset.name.trim().length === 0) issues.push({
|
|
93
109
|
field: "name",
|
|
94
|
-
message: "
|
|
110
|
+
message: "Preset name is required."
|
|
95
111
|
});
|
|
96
|
-
for (const [key, value] of Object.entries(
|
|
112
|
+
for (const [key, value] of Object.entries(preset.env)) if (typeof value !== "string") issues.push({
|
|
97
113
|
field: "env",
|
|
98
114
|
message: `Env var "${key}" must be a string.`
|
|
99
115
|
});
|
|
100
|
-
for (const flag of
|
|
116
|
+
for (const flag of preset.flags) if (!flag.startsWith("-")) issues.push({
|
|
101
117
|
field: "flags",
|
|
102
118
|
message: `Flag "${flag}" must start with "-".`
|
|
103
119
|
});
|
|
@@ -119,7 +135,7 @@ function validateGenericProfile(profile) {
|
|
|
119
135
|
const parsed = parseManifest({
|
|
120
136
|
name: "@subshell-ai/plugin-claude-code",
|
|
121
137
|
description: "Claude Code harness plugin for Subshell",
|
|
122
|
-
version: "
|
|
138
|
+
version: "2.0.0",
|
|
123
139
|
license: "Apache-2.0",
|
|
124
140
|
type: "module",
|
|
125
141
|
main: "dist/index.js",
|
|
@@ -128,6 +144,13 @@ const parsed = parseManifest({
|
|
|
128
144
|
"import": "./dist/index.js"
|
|
129
145
|
} },
|
|
130
146
|
types: "dist/index.d.ts",
|
|
147
|
+
homepage: "https://subshell.sh",
|
|
148
|
+
repository: {
|
|
149
|
+
"type": "git",
|
|
150
|
+
"url": "git+https://github.com/subshell-ai/subshell.git",
|
|
151
|
+
"directory": "packages/plugins/claude-code"
|
|
152
|
+
},
|
|
153
|
+
bugs: { "url": "https://github.com/subshell-ai/subshell/issues" },
|
|
131
154
|
scripts: {
|
|
132
155
|
"build": "tsdown",
|
|
133
156
|
"build:dev": "hash-runner",
|
|
@@ -146,15 +169,15 @@ const parsed = parseManifest({
|
|
|
146
169
|
"tsdown": "0.22.14",
|
|
147
170
|
"typescript": "7.0.2"
|
|
148
171
|
},
|
|
149
|
-
files: ["dist"],
|
|
172
|
+
files: ["dist", "icon.svg"],
|
|
150
173
|
publishConfig: { "access": "public" },
|
|
151
174
|
subshell: {
|
|
152
|
-
"apiVersion":
|
|
175
|
+
"apiVersion": 2,
|
|
153
176
|
"id": "claude-code",
|
|
154
177
|
"type": "agent-harness",
|
|
155
178
|
"name": "Claude Code",
|
|
156
179
|
"description": "Anthropic's agentic coding assistant (interactive CLI)",
|
|
157
|
-
"icon": "
|
|
180
|
+
"icon": "icon.svg",
|
|
158
181
|
"entry": "dist/index.js",
|
|
159
182
|
"detect": {
|
|
160
183
|
"binaryName": "claude",
|
|
@@ -345,53 +368,56 @@ const SUGGESTED_FLAGS = [
|
|
|
345
368
|
}
|
|
346
369
|
];
|
|
347
370
|
/**
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
* on the
|
|
352
|
-
*
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
* the
|
|
356
|
-
*
|
|
371
|
+
* One hook command line: the host-resolved reporter, then this hook's verb
|
|
372
|
+
* words, every element POSIX-quoted.
|
|
373
|
+
*
|
|
374
|
+
* A hook runs on the PANE's machine, so the command has to name something that
|
|
375
|
+
* exists there. It used to be `bun -e '<inlined JS>'` — bun was on the pane
|
|
376
|
+
* PATH because the container image shipped it, and no curl was there to use
|
|
377
|
+
* instead. That assumption did not survive leaving the container: on a desktop
|
|
378
|
+
* install the machine has the subshell binary and nothing else, and Claude Code
|
|
379
|
+
* opened every session with `/bin/sh: bun: command not found` while
|
|
380
|
+
* notifications and conversation identity silently never worked.
|
|
381
|
+
*
|
|
382
|
+
* So the reporting moved INTO the binary (`<self> report …`), and what the
|
|
383
|
+
* plugin composes is an argv rather than a program. Bounds, credentials and
|
|
384
|
+
* which fields travel are that subcommand's contract now, which is also why
|
|
385
|
+
* this function no longer needs to know the pane env exists.
|
|
357
386
|
*/
|
|
358
|
-
const
|
|
387
|
+
const reporterHook = (host, reporter, ...verb) => [
|
|
388
|
+
reporter.command,
|
|
389
|
+
...reporter.args,
|
|
390
|
+
...verb
|
|
391
|
+
].map((word) => host.shellQuote(word)).join(" ");
|
|
359
392
|
/**
|
|
360
|
-
*
|
|
361
|
-
* at launch) only survives while the pane keeps that ONE conversation — but
|
|
362
|
-
* /clear, /resume <other>, and /fork start a DIFFERENT transcript id in-pane
|
|
363
|
-
* and nothing else tells the server, so the next restart would resurrect a
|
|
364
|
-
* stale conversation (observed 2026-09-03). The SessionStart hook fires on
|
|
365
|
-
* every such transition (source: startup|resume|clear|compact|fork).
|
|
366
|
-
*
|
|
367
|
-
* Unlike the attention pings, THIS command reads stdin — the deliberate,
|
|
368
|
-
* documented exception to the no-stdin rule (whose rationale is that
|
|
369
|
-
* Stop/Notification payloads carry conversation text): the SessionStart
|
|
370
|
-
* payload is pure metadata ({session_id, transcript_path, cwd, source,…})
|
|
371
|
-
* and ONLY `session_id` is forwarded; the response is fire-and-forget like
|
|
372
|
-
* the pings — every failure path is swallowed.
|
|
393
|
+
* The `--settings` hooks object for one launch.
|
|
373
394
|
*
|
|
374
|
-
*
|
|
375
|
-
*
|
|
376
|
-
*
|
|
377
|
-
*
|
|
395
|
+
* - `Stop` / `Notification` — fire-and-forget attention reporting. The server
|
|
396
|
+
* gates delivery on the subshell's bell and derives the "waiting for you"
|
|
397
|
+
* state; a missed event costs one notification, never a broken turn.
|
|
398
|
+
* - `SessionStart` — conversation identity. The restart-resume pin
|
|
399
|
+
* (`--session-id` at launch) only survives while the pane keeps that ONE
|
|
400
|
+
* conversation, but /clear, /resume <other> and /fork start a DIFFERENT
|
|
401
|
+
* transcript id in-pane and nothing else tells the server, so the next
|
|
402
|
+
* restart would resurrect a stale conversation (observed 2026-09-03). This
|
|
403
|
+
* fires on every such transition (source: startup|resume|clear|compact|fork)
|
|
404
|
+
* and is the one hook that reads stdin — the reporter forwards `session_id`
|
|
405
|
+
* from that payload and nothing else.
|
|
378
406
|
*/
|
|
379
|
-
const
|
|
380
|
-
/** The `--settings` hooks object injected into every Claude Code launch. */
|
|
381
|
-
const ATTENTION_HOOKS = {
|
|
407
|
+
const attentionHooks = (host, reporter) => ({
|
|
382
408
|
Stop: [{ hooks: [{
|
|
383
409
|
type: "command",
|
|
384
|
-
command:
|
|
410
|
+
command: reporterHook(host, reporter, "attention", "turn_complete")
|
|
385
411
|
}] }],
|
|
386
412
|
Notification: [{ hooks: [{
|
|
387
413
|
type: "command",
|
|
388
|
-
command:
|
|
414
|
+
command: reporterHook(host, reporter, "attention", "needs_attention")
|
|
389
415
|
}] }],
|
|
390
416
|
SessionStart: [{ hooks: [{
|
|
391
417
|
type: "command",
|
|
392
|
-
command:
|
|
418
|
+
command: reporterHook(host, reporter, "session")
|
|
393
419
|
}] }]
|
|
394
|
-
};
|
|
420
|
+
});
|
|
395
421
|
/** Claude's transcript folder name for a project dir: every non-alphanumeric → `-`. */
|
|
396
422
|
function projectSlug(cwd) {
|
|
397
423
|
return cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
@@ -400,9 +426,9 @@ function projectSlug(cwd) {
|
|
|
400
426
|
* Built-in harness: Claude Code.
|
|
401
427
|
*
|
|
402
428
|
* Launch shape:
|
|
403
|
-
* claude --settings <json> --name <subshell> [
|
|
429
|
+
* claude --settings <json> --name <subshell> [preset flags] [extra flags]
|
|
404
430
|
* Settings are passed as a JSON string via `--settings` (per-invocation, so
|
|
405
|
-
*
|
|
431
|
+
* preset config never needs to write into the user's real ~/.claude).
|
|
406
432
|
*
|
|
407
433
|
* Identity, detection and install guidance live in this package's
|
|
408
434
|
* package.json `subshell` block, NOT here: the host reads them without
|
|
@@ -413,7 +439,7 @@ function projectSlug(cwd) {
|
|
|
413
439
|
* version probe, shell quoting, a namespaced logger). See
|
|
414
440
|
* `@subshell-ai/plugin-api`.
|
|
415
441
|
*/
|
|
416
|
-
const createPlugin = (
|
|
442
|
+
const createPlugin = (host) => ({
|
|
417
443
|
capabilities: () => [
|
|
418
444
|
"mcp",
|
|
419
445
|
"resume",
|
|
@@ -446,17 +472,16 @@ const createPlugin = (_host) => ({
|
|
|
446
472
|
}
|
|
447
473
|
},
|
|
448
474
|
buildCommand(input) {
|
|
449
|
-
const { binary,
|
|
475
|
+
const { binary, preset, subshellName, extraFlags, mcp, harnessSession, reporter } = input;
|
|
450
476
|
const args = [binary];
|
|
451
477
|
if (mcp?.args) args.push(...mcp.args);
|
|
452
478
|
if (harnessSession) args.push(harnessSession.mode === "resume" ? "--resume" : "--session-id", harnessSession.id);
|
|
453
|
-
const settings = {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
args.push("--settings", JSON.stringify(settings));
|
|
479
|
+
const settings = { ...preset.settings ?? {} };
|
|
480
|
+
if (reporter) settings.hooks = attentionHooks(host, reporter);
|
|
481
|
+
else host.log.warn("no reporter resolved for this launch: attention and conversation-identity hooks are omitted");
|
|
482
|
+
if (Object.keys(settings).length > 0) args.push("--settings", JSON.stringify(settings));
|
|
458
483
|
if (subshellName) args.push("--name", subshellName);
|
|
459
|
-
for (const flag of
|
|
484
|
+
for (const flag of preset.flags) args.push(flag);
|
|
460
485
|
if (extraFlags) args.push(...extraFlags);
|
|
461
486
|
return args;
|
|
462
487
|
},
|
|
@@ -477,10 +502,10 @@ const createPlugin = (_host) => ({
|
|
|
477
502
|
summary: "Subshell registers itself with every Claude Code subshell automatically (via --mcp-config)."
|
|
478
503
|
};
|
|
479
504
|
},
|
|
480
|
-
|
|
481
|
-
return
|
|
505
|
+
validatePreset(preset) {
|
|
506
|
+
return validateGenericPreset(preset);
|
|
482
507
|
},
|
|
483
|
-
|
|
508
|
+
presetSettings() {
|
|
484
509
|
return CLAUDE_SETTINGS_FIELDS;
|
|
485
510
|
},
|
|
486
511
|
suggestedEnv() {
|
|
@@ -499,4 +524,4 @@ const createPlugin = (_host) => ({
|
|
|
499
524
|
}
|
|
500
525
|
});
|
|
501
526
|
//#endregion
|
|
502
|
-
export {
|
|
527
|
+
export { createPlugin as default, manifest };
|
package/icon.svg
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg viewBox="0 0 248 248" fill="none" xmlns="http://www.w3.org/2000/svg"><title>Claude Code</title>
|
|
2
|
+
<path d="M52.4285 162.873L98.7844 136.879L99.5485 134.602L98.7844 133.334H96.4921L88.7237 132.862L62.2346 132.153L39.3113 131.207L17.0249 130.026L11.4214 128.844L6.2 121.873L6.7094 118.447L11.4214 115.257L18.171 115.847L33.0711 116.911L55.485 118.447L71.6586 119.392L95.728 121.873H99.5485L100.058 120.337L98.7844 119.392L97.7656 118.447L74.5877 102.732L49.4995 86.1905L36.3823 76.62L29.3779 71.7757L25.8121 67.2858L24.2839 57.3608L30.6515 50.2716L39.3113 50.8623L41.4763 51.4531L50.2636 58.1879L68.9842 72.7209L93.4357 90.6804L97.0015 93.6343L98.4374 92.6652L98.6571 91.9801L97.0015 89.2625L83.757 65.2772L69.621 40.8192L63.2534 30.6579L61.5978 24.632C60.9565 22.1032 60.579 20.0111 60.579 17.4246L67.8381 7.49965L71.9133 6.19995L81.7193 7.49965L85.7946 11.0443L91.9074 24.9865L101.714 46.8451L116.996 76.62L121.453 85.4816L123.873 93.6343L124.764 96.1155H126.292V94.6976L127.566 77.9197L129.858 57.3608L132.15 30.8942L132.915 23.4505L136.608 14.4708L143.994 9.62643L149.725 12.344L154.437 19.0788L153.8 23.4505L150.998 41.6463L145.522 70.1215L141.957 89.2625H143.994L146.414 86.7813L156.093 74.0206L172.266 53.698L179.398 45.6635L187.803 36.802L193.152 32.5484H203.34L210.726 43.6549L207.415 55.1159L196.972 68.3492L188.312 79.5739L175.896 96.2095L168.191 109.585L168.882 110.689L170.738 110.53L198.755 104.504L213.91 101.787L231.994 98.7149L240.144 102.496L241.036 106.395L237.852 114.311L218.495 119.037L195.826 123.645L162.07 131.592L161.696 131.893L162.137 132.547L177.36 133.925L183.855 134.279H199.774L229.447 136.524L237.215 141.605L241.8 147.867L241.036 152.711L229.065 158.737L213.019 154.956L175.45 145.977L162.587 142.787H160.805V143.85L171.502 154.366L191.242 172.089L215.82 195.011L217.094 200.682L213.91 205.172L210.599 204.699L188.949 188.394L180.544 181.069L161.696 165.118H160.422V166.772L164.752 173.152L187.803 207.771L188.949 218.405L187.294 221.832L181.308 223.959L174.813 222.777L161.187 203.754L147.305 182.486L136.098 163.345L134.745 164.2L128.075 235.42L125.019 239.082L117.887 241.8L111.902 237.31L108.718 229.984L111.902 215.452L115.722 196.547L118.779 181.541L121.58 162.873L123.291 156.636L123.14 156.219L121.773 156.449L107.699 175.752L86.304 204.699L69.3663 222.777L65.291 224.431L58.2867 220.768L58.9235 214.27L62.8713 208.48L86.304 178.705L100.44 160.155L109.551 149.507L109.462 147.967L108.959 147.924L46.6977 188.512L35.6182 189.93L30.7788 185.44L31.4156 178.115L33.7079 175.752L52.4285 162.873Z" fill="#D97757"/>
|
|
3
|
+
</svg>
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@subshell-ai/plugin-claude-code",
|
|
3
3
|
"description": "Claude Code harness plugin for Subshell",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "2.0.0",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
|
@@ -12,6 +12,15 @@
|
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
"types": "dist/index.d.ts",
|
|
15
|
+
"homepage": "https://subshell.sh",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/subshell-ai/subshell.git",
|
|
19
|
+
"directory": "packages/plugins/claude-code"
|
|
20
|
+
},
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/subshell-ai/subshell/issues"
|
|
23
|
+
},
|
|
15
24
|
"scripts": {
|
|
16
25
|
"build": "tsdown",
|
|
17
26
|
"build:dev": "hash-runner",
|
|
@@ -30,19 +39,17 @@
|
|
|
30
39
|
"tsdown": "0.22.14",
|
|
31
40
|
"typescript": "7.0.2"
|
|
32
41
|
},
|
|
33
|
-
"files": [
|
|
34
|
-
"dist"
|
|
35
|
-
],
|
|
42
|
+
"files": ["dist", "icon.svg"],
|
|
36
43
|
"publishConfig": {
|
|
37
44
|
"access": "public"
|
|
38
45
|
},
|
|
39
46
|
"subshell": {
|
|
40
|
-
"apiVersion":
|
|
47
|
+
"apiVersion": 2,
|
|
41
48
|
"id": "claude-code",
|
|
42
49
|
"type": "agent-harness",
|
|
43
50
|
"name": "Claude Code",
|
|
44
51
|
"description": "Anthropic's agentic coding assistant (interactive CLI)",
|
|
45
|
-
"icon": "
|
|
52
|
+
"icon": "icon.svg",
|
|
46
53
|
"entry": "dist/index.js",
|
|
47
54
|
"detect": {
|
|
48
55
|
"binaryName": "claude",
|
|
@@ -54,7 +61,9 @@
|
|
|
54
61
|
".npm-global/bin/claude"
|
|
55
62
|
]
|
|
56
63
|
},
|
|
57
|
-
"hostEnv": [
|
|
64
|
+
"hostEnv": [
|
|
65
|
+
"CLAUDE_CONFIG_DIR"
|
|
66
|
+
],
|
|
58
67
|
"install": {
|
|
59
68
|
"command": "curl -fsSL https://claude.ai/install.sh | bash",
|
|
60
69
|
"docsUrl": "https://code.claude.com/docs/en/setup"
|