psadt-deploy-skill 0.26.7 → 0.27.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.
Files changed (3) hide show
  1. package/README.md +973 -865
  2. package/bin/install.mjs +201 -125
  3. package/package.json +37 -37
package/bin/install.mjs CHANGED
@@ -2,16 +2,20 @@
2
2
  /**
3
3
  * psadt-deploy-skill - installer for the psadt-deploy Claude Code skill.
4
4
  *
5
- * npx psadt-deploy-skill install/update in ~/.claude/skills/psadt-deploy
5
+ * npx psadt-deploy-skill install/update the newest release in ~/.claude/skills/psadt-deploy
6
6
  * npx psadt-deploy-skill --project install into ./.claude/skills/psadt-deploy instead
7
7
  * npx psadt-deploy-skill --dir <path> install into an explicit folder
8
- * npx psadt-deploy-skill --ref <ref> a branch or tag instead of main
8
+ * npx psadt-deploy-skill --ref v0.26.7 pin an exact release
9
+ * npx psadt-deploy-skill --ref main the development branch, explicitly
9
10
  * npx psadt-deploy-skill --no-setup skip the setup doctor
10
11
  *
11
12
  * Design notes that matter:
12
13
  *
13
14
  * - Zero dependencies. Node 18 has global fetch, Windows ships bsdtar as tar.exe, and an installer that
14
15
  * pulls in a dependency tree is an installer that can break for reasons unrelated to the skill.
16
+ * - The default is the newest RELEASE TAG, not main. This skill registers an Entra app with admin
17
+ * consent and writes to an Intune tenant; installing whatever happened to land on main an hour ago is
18
+ * not a defensible default for that. main is still one flag away.
15
19
  * - The package ships ONLY bin/. The skill itself comes from GitHub at install time, so a new skill
16
20
  * version needs no npm republish - only a change to this file does.
17
21
  * - This script never writes the skill config. Resolving the config home is Get-PsadtConfig's job
@@ -35,7 +39,7 @@ function flagValue(name) {
35
39
  return i >= 0 && i + 1 < argv.length ? argv[i + 1] : null;
36
40
  }
37
41
  const wantHelp = argv.includes('--help') || argv.includes('-h');
38
- const ref = flagValue('--ref') || 'main';
42
+ const explicitRef = flagValue('--ref');
39
43
  const noSetup = argv.includes('--no-setup');
40
44
  const explicitDir = flagValue('--dir');
41
45
  const projectScope = argv.includes('--project');
@@ -43,11 +47,13 @@ const projectScope = argv.includes('--project');
43
47
  if (wantHelp) {
44
48
  console.log(`psadt-deploy-skill - install the psadt-deploy Claude Code skill
45
49
 
46
- npx psadt-deploy-skill [--dir <path>] [--project] [--ref <branch|tag>] [--no-setup]
50
+ npx psadt-deploy-skill [--dir <path>] [--project] [--ref <tag|branch>] [--no-setup]
47
51
 
48
52
  --dir <path> install into this folder
49
53
  --project install into ./.claude/skills/${SKILL_FOLDER} (default is the user's ~/.claude/skills)
50
- --ref <ref> branch or tag to install (default: main)
54
+ --ref <ref> tag or branch to install. Default: the newest release tag.
55
+ --ref v0.26.7 pin an exact release (recommended for managed environments)
56
+ --ref main the development branch
51
57
  --no-setup do not run the setup doctor afterwards
52
58
  `);
53
59
  process.exit(0);
@@ -70,145 +76,215 @@ function has(exe) {
70
76
  const r = runCapture(exe, ['--version']);
71
77
  return !r.error && r.status === 0;
72
78
  }
79
+ // Thrown by fail() to unwind out of main(). Nothing else throws it and nothing else catches it.
80
+ class InstallFailure extends Error {}
73
81
  function fail(msg) {
74
82
  console.error(`\n ERROR ${msg}\n`);
75
- process.exit(1);
83
+ // Deliberately NOT process.exit(). Once Node's fetch has opened a pooled socket, an explicit exit
84
+ // aborts the process with "Assertion failed: !(handle->flags & UV_HANDLE_CLOSING)" and exit code
85
+ // 127 instead of 1 - so a mistyped --ref prints a correct error message and then looks like an
86
+ // installer crash, and any wrapper reading the exit code gets the wrong number. Setting exitCode
87
+ // and unwinding lets the event loop drain, which exits 1 cleanly.
88
+ process.exitCode = 1;
89
+ throw new InstallFailure(msg);
76
90
  }
77
91
 
78
- if (process.platform !== 'win32') {
79
- fail('This skill builds Windows Intune packages and needs Windows (PowerShell, IntuneWinAppUtil, PSADT).');
80
- }
92
+ async function main() {
93
+ if (process.platform !== 'win32') {
94
+ fail('This skill builds Windows Intune packages and needs Windows (PowerShell, IntuneWinAppUtil, PSADT).');
95
+ }
81
96
 
82
- console.log(`\npsadt-deploy-skill\n target : ${target}\n ref : ${ref}`);
97
+ // --- which ref ----------------------------------------------------------------------------------
98
+ // Without --ref this installs the newest RELEASE TAG. The tag list is the only source: this repository
99
+ // publishes tags rather than GitHub Releases, so /releases/latest answers 404 here.
100
+ async function newestReleaseTag() {
101
+ try {
102
+ const res = await fetch(`https://api.github.com/repos/${REPO}/tags?per_page=100`, {
103
+ headers: { 'User-Agent': 'psadt-deploy-skill', Accept: 'application/vnd.github+json' },
104
+ });
105
+ if (!res.ok) return null;
106
+ const semver = /^v(\d+)\.(\d+)\.(\d+)$/;
107
+ const tags = (await res.json())
108
+ .map((t) => [t.name, semver.exec(t.name)])
109
+ .filter(([, m]) => m)
110
+ // The API orders tags by commit date, which is not version order once anything is tagged out of
111
+ // sequence. Sort numerically per component - a string sort would put v0.9.0 above v0.26.7.
112
+ .sort((a, b) => Number(b[1][1]) - Number(a[1][1]) || Number(b[1][2]) - Number(a[1][2]) || Number(b[1][3]) - Number(a[1][3]));
113
+ return tags.length ? tags[0][0] : null;
114
+ } catch {
115
+ return null;
116
+ }
117
+ }
83
118
 
84
- // --- 1. get the skill --------------------------------------------------------------------------
85
- // Order of preference: update an existing clone, else clone, else tarball. The tarball route exists
86
- // because plenty of managed machines have no git at all.
87
- const gitAvailable = has('git');
88
- mkdirSync(target, { recursive: true });
119
+ let ref = explicitRef;
120
+ let refNote = '';
121
+ if (!ref) {
122
+ ref = await newestReleaseTag();
123
+ refNote = ' (newest release)';
124
+ if (!ref) {
125
+ // Offline, rate-limited, or a repository this machine cannot read. Falling back keeps the
126
+ // installer working; saying so out loud keeps the user from believing they got a pinned release.
127
+ ref = 'main';
128
+ refNote = ' (FALLBACK: could not read the tag list, installing the development branch)';
129
+ }
130
+ }
131
+
132
+ console.log(`\npsadt-deploy-skill\n target : ${target}\n ref : ${ref}${refNote}`);
89
133
 
90
- if (existsSync(join(target, '.git'))) {
91
- if (!gitAvailable) fail(`${target} is a git clone but git is not available to update it.`);
92
- console.log('\n updating the existing clone (git pull --ff-only)');
93
- const r = run('git', ['-C', target, 'pull', '--ff-only', 'origin', ref]);
94
- if (r.status !== 0) {
95
- fail('git pull --ff-only failed. Local changes in the skill folder? Commit or discard them, or install into a fresh folder with --dir.');
134
+ // --- 1. get the skill --------------------------------------------------------------------------
135
+ // Order of preference: update an existing clone, else clone, else tarball. The tarball route exists
136
+ // because plenty of managed machines have no git at all.
137
+ const gitAvailable = has('git');
138
+ mkdirSync(target, { recursive: true });
139
+
140
+ if (existsSync(join(target, '.git'))) {
141
+ if (!gitAvailable) fail(`${target} is a git clone but git is not available to update it.`);
142
+ console.log('\n updating the existing clone (git pull --ff-only)');
143
+ const r = run('git', ['-C', target, 'pull', '--ff-only', 'origin', ref]);
144
+ if (r.status !== 0) {
145
+ fail(
146
+ `git pull --ff-only origin ${ref} failed. Three usual causes: local changes in the skill folder ` +
147
+ '(commit or discard them); the clone is already AHEAD of the newest release, which is normal on a ' +
148
+ 'development clone (re-run with --ref main); or the ref does not exist. Failing that, install into ' +
149
+ 'a fresh folder with --dir.'
150
+ );
151
+ }
152
+ } else if (gitAvailable) {
153
+ console.log('\n cloning (git clone --depth 1)');
154
+ // Clone into a temp folder and move the contents, because git refuses a non-empty target.
155
+ const stage = join(tmpdir(), `psadt-skill-${Date.now()}`);
156
+ const r = run('git', ['clone', '--depth', '1', '--branch', ref, `https://github.com/${REPO}.git`, stage]);
157
+ if (r.status !== 0) {
158
+ fail(
159
+ `git clone failed. If ${REPO} is private you need access to it: sign in with the GitHub CLI (gh auth login), ` +
160
+ 'configure a credential helper, or clone it manually and point this installer at the folder with --dir.'
161
+ );
162
+ }
163
+ // robocopy is on every Windows box and handles the merge without a shell. 0-7 are success codes.
164
+ const c = runCapture('robocopy', [stage, target, '/E', '/NFL', '/NDL', '/NJH', '/NJS', '/NP']);
165
+ rmSync(stage, { recursive: true, force: true });
166
+ if ((c.status ?? 8) > 7) fail('Copying the clone into the target folder failed.');
167
+ } else {
168
+ console.log('\n no git found - downloading the tarball');
169
+ // The bare-ref form, NOT refs/heads/<ref>: refs/heads only resolves BRANCHES, so every tag 404'd on
170
+ // this route while working fine on the two git routes - which is exactly the route a managed machine
171
+ // without git ends up on, and exactly the ref a managed machine should be pinning.
172
+ const url = `https://codeload.github.com/${REPO}/tar.gz/${ref}`;
173
+ const tgz = join(tmpdir(), `psadt-skill-${Date.now()}.tar.gz`);
174
+ try {
175
+ const res = await fetch(url);
176
+ if (res.status === 404) {
177
+ // GitHub answers 404 - not 403 - both for a private repository you cannot see and for a ref that
178
+ // does not exist. Without git there is no way to authenticate here, so name both possibilities
179
+ // instead of "download failed".
180
+ fail(
181
+ `Could not download ${REPO} at ref "${ref}" (HTTP 404). Either that tag/branch does not exist, ` +
182
+ 'or the repository is not publicly readable and this installer cannot authenticate without git. ' +
183
+ 'Check the ref, or install git and sign in to GitHub, or clone the repository manually and ' +
184
+ 're-run with --dir <folder>.'
185
+ );
186
+ }
187
+ if (!res.ok) throw new Error(`HTTP ${res.status} for ${url}`);
188
+ writeFileSync(tgz, Buffer.from(await res.arrayBuffer()));
189
+ } catch (e) {
190
+ // The 404 branch above already reported precisely what is wrong and threw; letting it fall in
191
+ // here would re-wrap that message as "Download failed: <the whole explanation>".
192
+ if (e instanceof InstallFailure) throw e;
193
+ fail(`Download failed: ${e.message}`);
194
+ }
195
+ // The archive's top-level directory is <repo>-<ref-without-any-leading-v>/, so strip one component
196
+ // rather than trying to predict the name.
197
+ const r = run('tar', ['-xzf', tgz, '-C', target, '--strip-components=1']);
198
+ rmSync(tgz, { force: true });
199
+ if (r.status !== 0) fail('Extracting the tarball failed (tar.exe is expected in C:\\Windows\\System32).');
96
200
  }
97
- } else if (gitAvailable) {
98
- console.log('\n cloning (git clone --depth 1)');
99
- // Clone into a temp folder and move the contents, because git refuses a non-empty target.
100
- const stage = join(tmpdir(), `psadt-skill-${Date.now()}`);
101
- const r = run('git', ['clone', '--depth', '1', '--branch', ref, `https://github.com/${REPO}.git`, stage]);
102
- if (r.status !== 0) {
103
- fail(
104
- `git clone failed. If ${REPO} is private you need access to it: sign in with the GitHub CLI (gh auth login), ` +
105
- 'configure a credential helper, or clone it manually and point this installer at the folder with --dir.'
106
- );
201
+
202
+ if (!existsSync(join(target, 'SKILL.md'))) {
203
+ fail(`${target} has no SKILL.md - the download did not produce a skill folder.`);
107
204
  }
108
- // robocopy is on every Windows box and handles the merge without a shell. 0-7 are success codes.
109
- const c = runCapture('robocopy', [stage, target, '/E', '/NFL', '/NDL', '/NJH', '/NJS', '/NP']);
110
- rmSync(stage, { recursive: true, force: true });
111
- if ((c.status ?? 8) > 7) fail('Copying the clone into the target folder failed.');
112
- } else {
113
- console.log('\n no git found - downloading the tarball');
114
- const url = `https://codeload.github.com/${REPO}/tar.gz/refs/heads/${ref}`;
115
- const tgz = join(tmpdir(), `psadt-skill-${Date.now()}.tar.gz`);
205
+ console.log(' OK skill files in place');
206
+
207
+ // --- 2. PowerShell ------------------------------------------------------------------------------
208
+ // pwsh first; powershell.exe as the fallback. -ExecutionPolicy Bypass on BOTH: 5.1 defaults to
209
+ // Restricted, and a GPO can pin pwsh to AllSigned - either way an unsigned script would not run.
210
+ const psExe = has('pwsh') ? 'pwsh' : 'powershell';
211
+ const psBase = ['-NoProfile', '-ExecutionPolicy', 'Bypass'];
212
+
213
+ // --- 3. record the commit (via the script that owns the config, not from here) -------------------
116
214
  try {
117
- const res = await fetch(url);
118
- if (res.status === 404) {
119
- // GitHub answers 404 - not 403 - for a private repository you cannot see. Without git there is no
120
- // way to authenticate here, so say what is actually wrong instead of "download failed".
121
- fail(
122
- `${REPO} is not publicly readable (HTTP 404), and without git this installer cannot authenticate. ` +
123
- 'Either install git and sign in to GitHub, or clone the repository manually and re-run with ' +
124
- '--dir <folder>.'
125
- );
215
+ const res = await fetch(`https://api.github.com/repos/${REPO}/commits/${ref}`, {
216
+ headers: { 'User-Agent': 'psadt-deploy-skill', Accept: 'application/vnd.github+json' },
217
+ });
218
+ if (res.ok) {
219
+ const sha = (await res.json()).sha;
220
+ if (sha) {
221
+ const setCfg = join(target, 'scripts', 'Set-PsadtConfig.ps1');
222
+ // The ref is recorded next to the sha because Update-PsadtSkill.ps1 cannot otherwise tell a
223
+ // release-pinned installation from one tracking main, and would report a pinned machine as
224
+ // permanently "behind" every time main moved.
225
+ // -Command, not -File: -File passes every argument as a string and -Updates wants a hashtable.
226
+ run(psExe, [...psBase, '-Command', `& '${setCfg}' -Updates @{'tooling.skillCommit'='${sha}'; 'tooling.skillRef'='${ref}'}`]);
227
+ console.log(` OK recorded commit ${sha.slice(0, 7)} at ref ${ref}`);
228
+ }
229
+ } else if (res.status === 404) {
230
+ // 404 on a repo you just cloned = the repo is private and this call is unauthenticated. Worth saying
231
+ // once: the skill works, only the sha-based update check falls back to comparing versions.
232
+ console.log(` -- ${REPO} is not publicly readable, so the commit sha was not recorded.`);
233
+ console.log(' The skill works; "update skill" compares CHANGELOG versions instead of shas.');
234
+ } else {
235
+ console.log(` -- could not read the commit sha (HTTP ${res.status}) - not fatal`);
126
236
  }
127
- if (!res.ok) throw new Error(`HTTP ${res.status} for ${url}`);
128
- writeFileSync(tgz, Buffer.from(await res.arrayBuffer()));
129
- } catch (e) {
130
- fail(`Download failed: ${e.message}`);
237
+ } catch {
238
+ // A missing commit sha only means the next update check compares versions instead of shas.
239
+ console.log(' -- could not record the commit sha (offline?) - not fatal');
131
240
  }
132
- // The archive's top-level directory is <repo>-<ref>/, so strip exactly one component.
133
- const r = run('tar', ['-xzf', tgz, '-C', target, '--strip-components=1']);
134
- rmSync(tgz, { force: true });
135
- if (r.status !== 0) fail('Extracting the tarball failed (tar.exe is expected in C:\\Windows\\System32).');
136
- }
137
241
 
138
- if (!existsSync(join(target, 'SKILL.md'))) {
139
- fail(`${target} has no SKILL.md - the download did not produce a skill folder.`);
140
- }
141
- console.log(' OK skill files in place');
242
+ // --- 4. the setup doctor ------------------------------------------------------------------------
243
+ if (noSetup) {
244
+ console.log('\n skipped the setup doctor (--no-setup). Run it later:');
245
+ console.log(` ${psExe} -File "${join(target, 'scripts', 'Initialize-PsadtSkill.ps1')}" -Fix\n`);
246
+ return;
247
+ }
142
248
 
143
- // --- 2. PowerShell ------------------------------------------------------------------------------
144
- // pwsh first; powershell.exe as the fallback. -ExecutionPolicy Bypass on BOTH: 5.1 defaults to
145
- // Restricted, and a GPO can pin pwsh to AllSigned - either way an unsigned script would not run.
146
- const psExe = has('pwsh') ? 'pwsh' : 'powershell';
147
- const psBase = ['-NoProfile', '-ExecutionPolicy', 'Bypass'];
249
+ const doctor = join(target, 'scripts', 'Initialize-PsadtSkill.ps1');
250
+ const jsonPath = join(tmpdir(), `psadt-doctor-${Date.now()}.json`);
251
+ console.log('\n running the setup doctor (-Fix)\n');
252
+ // -JsonPath rather than -Json: stdout is inherited so the user sees the doctor's own table live, and the
253
+ // machine-readable copy comes back through the file.
254
+ // -Command with Out-Null rather than -File: the doctor also RETURNS its result object, and with inherited
255
+ // stdout that object gets dumped underneath its own table. Out-Null drops the object; Write-Host output
256
+ // (the table) is unaffected.
257
+ const d = run(psExe, [...psBase, '-Command', `& '${doctor}' -Fix -JsonPath '${jsonPath}' | Out-Null`]);
148
258
 
149
- // --- 3. record the commit (via the script that owns the config, not from here) -------------------
150
- try {
151
- const res = await fetch(`https://api.github.com/repos/${REPO}/commits/${ref}`, {
152
- headers: { 'User-Agent': 'psadt-deploy-skill', Accept: 'application/vnd.github+json' },
153
- });
154
- if (res.ok) {
155
- const sha = (await res.json()).sha;
156
- if (sha) {
157
- const setCfg = join(target, 'scripts', 'Set-PsadtConfig.ps1');
158
- // -Command, not -File: -File passes every argument as a string and -Updates wants a hashtable.
159
- run(psExe, [...psBase, '-Command', `& '${setCfg}' -Updates @{'tooling.skillCommit'='${sha}'}`]);
160
- console.log(` OK recorded commit ${sha.slice(0, 7)}`);
259
+ let verdict = null;
260
+ try {
261
+ if (existsSync(jsonPath)) verdict = JSON.parse(readFileSync(jsonPath, 'utf8'));
262
+ } catch {
263
+ /* the doctor printed its table anyway */
264
+ }
265
+ rmSync(jsonPath, { force: true });
266
+
267
+ if (verdict) {
268
+ console.log(`\n setup: ${verdict.Overall}`);
269
+ const missing = Array.isArray(verdict.Missing) ? verdict.Missing : [];
270
+ if (missing.length) {
271
+ console.log(`\n ${missing.length} value(s) still need you: ${missing.join(', ')}`);
272
+ console.log(" Open Claude Code in a package folder and say: \"psadt setup\"");
273
+ } else if (verdict.Overall === 'GREEN') {
274
+ console.log(' Ready. Open Claude Code and say what you want packaged.');
161
275
  }
162
- } else if (res.status === 404) {
163
- // 404 on a repo you just cloned = the repo is private and this call is unauthenticated. Worth saying
164
- // once: the skill works, only the sha-based update check falls back to comparing versions.
165
- console.log(` -- ${REPO} is not publicly readable, so the commit sha was not recorded.`);
166
- console.log(' The skill works; "update skill" compares CHANGELOG versions instead of shas.');
167
- } else {
168
- console.log(` -- could not read the commit sha (HTTP ${res.status}) - not fatal`);
276
+ } else if (d.status !== 0) {
277
+ console.log('\n The setup doctor did not complete. Run it manually:');
278
+ console.log(` ${psExe} -File "${doctor}" -Fix`);
169
279
  }
170
- } catch {
171
- // A missing commit sha only means the next update check compares versions instead of shas.
172
- console.log(' -- could not record the commit sha (offline?) - not fatal');
173
- }
174
280
 
175
- // --- 4. the setup doctor ------------------------------------------------------------------------
176
- if (noSetup) {
177
- console.log('\n skipped the setup doctor (--no-setup). Run it later:');
178
- console.log(` ${psExe} -File "${join(target, 'scripts', 'Initialize-PsadtSkill.ps1')}" -Fix\n`);
179
- process.exit(0);
281
+ console.log('');
180
282
  }
181
283
 
182
- const doctor = join(target, 'scripts', 'Initialize-PsadtSkill.ps1');
183
- const jsonPath = join(tmpdir(), `psadt-doctor-${Date.now()}.json`);
184
- console.log('\n running the setup doctor (-Fix)\n');
185
- // -JsonPath rather than -Json: stdout is inherited so the user sees the doctor's own table live, and the
186
- // machine-readable copy comes back through the file.
187
- // -Command with Out-Null rather than -File: the doctor also RETURNS its result object, and with inherited
188
- // stdout that object gets dumped underneath its own table. Out-Null drops the object; Write-Host output
189
- // (the table) is unaffected.
190
- const d = run(psExe, [...psBase, '-Command', `& '${doctor}' -Fix -JsonPath '${jsonPath}' | Out-Null`]);
191
-
192
- let verdict = null;
284
+ // process.exit(0) has the same libuv problem as process.exit(1) once a fetch has run, so the
285
+ // happy paths return out of main() instead of exiting and the process ends by draining the loop.
193
286
  try {
194
- if (existsSync(jsonPath)) verdict = JSON.parse(readFileSync(jsonPath, 'utf8'));
195
- } catch {
196
- /* the doctor printed its table anyway */
197
- }
198
- rmSync(jsonPath, { force: true });
199
-
200
- if (verdict) {
201
- console.log(`\n setup: ${verdict.Overall}`);
202
- const missing = Array.isArray(verdict.Missing) ? verdict.Missing : [];
203
- if (missing.length) {
204
- console.log(`\n ${missing.length} value(s) still need you: ${missing.join(', ')}`);
205
- console.log(" Open Claude Code in a package folder and say: \"psadt setup\"");
206
- } else if (verdict.Overall === 'GREEN') {
207
- console.log(' Ready. Open Claude Code and say what you want packaged.');
208
- }
209
- } else if (d.status !== 0) {
210
- console.log('\n The setup doctor did not complete. Run it manually:');
211
- console.log(` ${psExe} -File "${doctor}" -Fix`);
287
+ await main();
288
+ } catch (e) {
289
+ if (!(e instanceof InstallFailure)) throw e;
212
290
  }
213
-
214
- console.log('');
package/package.json CHANGED
@@ -1,37 +1,37 @@
1
- {
2
- "name": "psadt-deploy-skill",
3
- "version": "0.26.7",
4
- "description": "Installer for the psadt-deploy Claude Code skill: build, test and deploy PSADT v4.x Intune Win32 packages.",
5
- "keywords": [
6
- "psadt",
7
- "psappdeploytoolkit",
8
- "intune",
9
- "win32",
10
- "claude-code",
11
- "claude-skill",
12
- "packaging"
13
- ],
14
- "homepage": "https://github.com/pt1987/claude-code-psadt-skill#readme",
15
- "bugs": {
16
- "url": "https://github.com/pt1987/claude-code-psadt-skill/issues"
17
- },
18
- "repository": {
19
- "type": "git",
20
- "url": "git+https://github.com/pt1987/claude-code-psadt-skill.git"
21
- },
22
- "license": "MIT",
23
- "author": "Patrick Taubert, PHAT Consulting GmbH",
24
- "type": "module",
25
- "bin": {
26
- "psadt-deploy-skill": "bin/install.mjs"
27
- },
28
- "files": [
29
- "bin"
30
- ],
31
- "engines": {
32
- "node": ">=18"
33
- },
34
- "os": [
35
- "win32"
36
- ]
37
- }
1
+ {
2
+ "name": "psadt-deploy-skill",
3
+ "version": "0.27.1",
4
+ "description": "Installer for the psadt-deploy Claude Code skill: build, test and deploy PSADT v4.x Intune Win32 packages.",
5
+ "keywords": [
6
+ "psadt",
7
+ "psappdeploytoolkit",
8
+ "intune",
9
+ "win32",
10
+ "claude-code",
11
+ "claude-skill",
12
+ "packaging"
13
+ ],
14
+ "homepage": "https://github.com/pt1987/claude-code-psadt-skill#readme",
15
+ "bugs": {
16
+ "url": "https://github.com/pt1987/claude-code-psadt-skill/issues"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/pt1987/claude-code-psadt-skill.git"
21
+ },
22
+ "license": "MIT",
23
+ "author": "Patrick Taubert, PHAT Consulting GmbH",
24
+ "type": "module",
25
+ "bin": {
26
+ "psadt-deploy-skill": "bin/install.mjs"
27
+ },
28
+ "files": [
29
+ "bin"
30
+ ],
31
+ "engines": {
32
+ "node": ">=18"
33
+ },
34
+ "os": [
35
+ "win32"
36
+ ]
37
+ }