@ghship/cli 0.0.0-stage → 1.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/README.md CHANGED
@@ -1,3 +1,109 @@
1
- # Temporary Holding Version
1
+ # GHShip npm distribution
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Go CLI and home AI skill plus per-project integration. Package includes native
4
+ binaries for macOS/Linux/Windows × amd64/arm64, with no postinstall downloads.
5
+
6
+ After publication: `npx @ghship/cli@latest install` installs the home skill, same-version
7
+ persistent CLI, then enters project initialization. `--user-only` stops after user
8
+ installation. Project workflows need Git and a gh with native Issue relationships.
9
+ `ghship doctor` provides official tool installation links when missing.
10
+
11
+ Update/uninstall via external `npx @ghship/cli@latest update|uninstall`. Project lifecycle:
12
+ `ghship project install|status|update|uninstall`; custom `.ghship/skill.md` is preserved.
13
+ Users: `ghship user add|update|delete|list`; list validates tokens and reports actual
14
+ expiry when GitHub returns it, otherwise unknown. Tokens never belong in projects.
15
+
16
+ For a downloaded npm tarball, use:
17
+ `npm exec --package /absolute/ghship.tgz -- ghship install --package-source /absolute/ghship.tgz`.
18
+ Add `--user-only` to install shared tools without initializing a project.
19
+ Registry commands require the selected version to be published.
20
+
21
+ Detailed Chinese tutorial and AI skill: https://github.com/ghship/cli
22
+
23
+ ## License notices
24
+
25
+ GHShip is distributed under the MIT License.
26
+
27
+ MIT License
28
+
29
+ Copyright (c) 2026 GHShip contributors
30
+
31
+ Permission is hereby granted, free of charge, to any person obtaining a copy
32
+ of this software and associated documentation files (the "Software"), to deal
33
+ in the Software without restriction, including without limitation the rights
34
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
35
+ copies of the Software, and to permit persons to whom the Software is
36
+ furnished to do so, subject to the following conditions:
37
+
38
+ The above copyright notice and this permission notice shall be included in all
39
+ copies or substantial portions of the Software.
40
+
41
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
42
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
43
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
44
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
45
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
46
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
47
+ SOFTWARE.
48
+
49
+ The bundled Go binary includes golang.org/x/sys and golang.org/x/term,
50
+ which share the following BSD 3-Clause license:
51
+
52
+ Copyright 2009 The Go Authors.
53
+
54
+ Redistribution and use in source and binary forms, with or without
55
+ modification, are permitted provided that the following conditions are
56
+ met:
57
+
58
+ * Redistributions of source code must retain the above copyright
59
+ notice, this list of conditions and the following disclaimer.
60
+ * Redistributions in binary form must reproduce the above
61
+ copyright notice, this list of conditions and the following disclaimer
62
+ in the documentation and/or other materials provided with the
63
+ distribution.
64
+ * Neither the name of Google LLC nor the names of its
65
+ contributors may be used to endorse or promote products derived from
66
+ this software without specific prior written permission.
67
+
68
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
69
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
70
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
71
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
72
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
73
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
74
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
75
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
76
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
77
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
78
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
79
+
80
+ The bundled binaries include the Go runtime and standard library,
81
+ distributed under the following BSD 3-Clause license:
82
+
83
+ Copyright (c) 2009 The Go Authors. All rights reserved.
84
+
85
+ Redistribution and use in source and binary forms, with or without
86
+ modification, are permitted provided that the following conditions are
87
+ met:
88
+
89
+ * Redistributions of source code must retain the above copyright
90
+ notice, this list of conditions and the following disclaimer.
91
+ * Redistributions in binary form must reproduce the above
92
+ copyright notice, this list of conditions and the following disclaimer
93
+ in the documentation and/or other materials provided with the
94
+ distribution.
95
+ * Neither the name of Google Inc. nor the names of its
96
+ contributors may be used to endorse or promote products derived from
97
+ this software without specific prior written permission.
98
+
99
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
100
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
101
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
102
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
103
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
104
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
105
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
106
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
107
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
108
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
109
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/checksums.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "bin/darwin-amd64/ghship": "f6c0f2c362d2a8631a4797976b775b918e14e4073414442abe7a6130adf125c5",
3
+ "bin/darwin-arm64/ghship": "6dd13e6d4b114308945119919728b50e2d97340f08d1405b9ddc58306d4fbfbb",
4
+ "bin/linux-amd64/ghship": "738329d127602e603562aada7daa270e5114f4e1715d14d5f7281f2ab79d03ea",
5
+ "bin/linux-arm64/ghship": "082b7c6945e6cfd9242a894d7ffb93771cd74fceddd6699a895889e747d5fff6",
6
+ "bin/windows-amd64/ghship.exe": "81c84b93a067b756de38bb2670d9b128801bdbf5ec081f296ea74de147f16621",
7
+ "bin/windows-arm64/ghship.exe": "7ddcae142e6d00e8706b5b29e447c6f83d0cb2bd650d99811ba01456f1776bc7"
8
+ }
package/package.json CHANGED
@@ -1,6 +1,25 @@
1
1
  {
2
2
  "name": "@ghship/cli",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0",
4
+ "description": "Go CLI and AI skill for native GitHub delivery pipelines",
5
+ "bin": {
6
+ "ghship": "run.cjs"
7
+ },
8
+ "files": [
9
+ "run.cjs",
10
+ "bin",
11
+ "README.md",
12
+ "checksums.json"
13
+ ],
14
+ "engines": {
15
+ "node": ">=18"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/ghship/cli.git"
20
+ },
21
+ "scripts": {
22
+ "test": "node --test test.cjs"
23
+ },
24
+ "license": "MIT"
25
+ }
package/run.cjs ADDED
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env node
2
+ const path = require('node:path');
3
+ const fs = require('node:fs');
4
+ const os = require('node:os');
5
+ const crypto = require('node:crypto');
6
+ const { spawn, spawnSync } = require('node:child_process');
7
+ function binaryPath(platform = process.platform, arch = process.arch) {
8
+ const os = {darwin:'darwin', linux:'linux', win32:'windows'}[platform];
9
+ const cpu = {x64:'amd64', arm64:'arm64'}[arch];
10
+ if (!os || !cpu) throw new Error(`Unsupported platform: ${platform}/${arch}`);
11
+ return path.join(__dirname, 'bin', `${os}-${cpu}`, os === 'windows' ? 'ghship.exe' : 'ghship');
12
+ }
13
+ function npmCommand() {
14
+ if (process.env.npm_execpath && fs.existsSync(process.env.npm_execpath)) return [process.execPath, [process.env.npm_execpath]];
15
+ const suffixes = process.platform === 'win32' ? ['npm.cmd'] : ['npm'];
16
+ for (const dir of (process.env.PATH || '').split(path.delimiter)) for (const name of suffixes) {
17
+ const candidate = path.join(dir, name); if (!fs.existsSync(candidate)) continue;
18
+ const real = fs.realpathSync(candidate);
19
+ if (process.platform !== 'win32') return [process.execPath, [real]];
20
+ const cli = path.join(dir, 'node_modules', 'npm', 'bin', 'npm-cli.js');
21
+ if (fs.existsSync(cli)) return [process.execPath, [cli]];
22
+ }
23
+ throw new Error('npm is unavailable. Install Node/npm: https://nodejs.org/en/download');
24
+ }
25
+ function npm(args, capture = false) {
26
+ const [command, base] = npmCommand();
27
+ const result = spawnSync(command, [...base, ...args], {stdio:capture ? ['ignore','pipe','pipe'] : 'inherit', encoding:'utf8'});
28
+ const absentList = args[0] === 'list' && result.status !== 0 && (() => {
29
+ try { const value=JSON.parse(result.stdout); return value.error?.code === 'ENOENT' || (result.status === 1 && !value.error); } catch (_) { return false; }
30
+ })();
31
+ if (result.error || (result.status !== 0 && !absentList)) throw new Error(`npm ${args[0]} failed; retry the same fixed-version command`);
32
+ return capture ? (absentList ? '{"dependencies":{}}' : result.stdout) : '';
33
+ }
34
+ function go(executable,args,capture=false,interactive=false) {
35
+ const result=spawnSync(executable,args,{stdio:capture?[interactive?'inherit':'ignore','pipe',interactive?'inherit':'pipe']:'inherit',encoding:'utf8'});
36
+ if(result.error) throw new Error('Cannot start the packaged Go CLI');
37
+ if (capture && result.status !== 0 && result.stderr) process.stderr.write(result.stderr);
38
+ return {code:result.status===null?1:result.status,stdout:result.stdout};
39
+ }
40
+ function hashFile(file) { return crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex'); }
41
+ function noLinks(file) {
42
+ for(let current=file;;current=path.dirname(current)) {
43
+ try {if(fs.lstatSync(current).isSymbolicLink())throw new Error('Installation path contains a symlink; preserved');}
44
+ catch(error){if(error.code!=='ENOENT')throw error;}
45
+ if(path.dirname(current)===current)break;
46
+ }
47
+ }
48
+ function packageReceipt(root) {
49
+ const base=process.platform==='win32' ? path.join(process.env.APPDATA||path.join(os.homedir(),'AppData','Roaming'),'ghship') : path.join(os.homedir(),'.config','ghship');
50
+ return path.join(base,`npm-${crypto.createHash('sha256').update(root).digest('hex')}.json`);
51
+ }
52
+ function observePackage(root,meta,existing,replacingVerifiedReceipt=false) {
53
+ const directory=path.join(root,meta.name); noLinks(directory);
54
+ const metadata=JSON.parse(fs.readFileSync(path.join(directory,'package.json'),'utf8'));
55
+ if(metadata.name!==meta.name||metadata.version!==existing.version||metadata.bin?.ghship!=='run.cjs')throw new Error('Unrecognized global package; preserved');
56
+ const relative=path.relative(__dirname,binaryPath());
57
+ const observed={root,name:meta.name,version:metadata.version,launcher:hashFile(path.join(directory,'run.cjs')),binary:hashFile(path.join(directory,relative))};
58
+ const current=observed.version===meta.version&&observed.launcher===hashFile(path.join(__dirname,'run.cjs'))&&observed.binary===hashFile(binaryPath());
59
+ const receipt=packageReceipt(root);noLinks(receipt);
60
+ let recorded=null;try{recorded=JSON.parse(fs.readFileSync(receipt,'utf8'));}catch(error){if(error.code!=='ENOENT')throw new Error('Invalid installation receipt; preserved');}
61
+ if(recorded&&!replacingVerifiedReceipt&&!Object.keys(observed).every(key=>recorded[key]===observed[key]))throw new Error('Installation receipt does not match the package; preserved');
62
+ const managed=current||(recorded&&Object.keys(observed).every(key=>recorded[key]===observed[key]));
63
+ if(!managed)throw new Error('Global package ownership/content is unverified; preserved. Inspect npm list -g @ghship/cli before changing it');
64
+ return {observed,current};
65
+ }
66
+ function saveReceipt(root,observed) {
67
+ const file=packageReceipt(root);noLinks(file);fs.mkdirSync(path.dirname(file),{recursive:true,mode:0o700});
68
+ const stage=`${file}.${process.pid}.tmp`;const descriptor=fs.openSync(stage,'wx',0o600);
69
+ try{fs.writeFileSync(descriptor,JSON.stringify(observed));fs.fsyncSync(descriptor);}finally{fs.closeSync(descriptor);}
70
+ try{fs.renameSync(stage,file);}finally{if(fs.existsSync(stage))fs.unlinkSync(stage);}
71
+ }
72
+ function lifecycle(executable,args) {
73
+ const action=args[0], meta=require('./package.json'); let source=`${meta.name}@${meta.version}`;
74
+ const passed=args.slice(1); const sourceIndex=passed.indexOf('--package-source');
75
+ if(sourceIndex>=0) {if(!passed[sourceIndex+1])throw new Error('--package-source requires a local tarball');source=path.resolve(passed[sourceIndex+1]);passed.splice(sourceIndex,2);
76
+ const packed=JSON.parse(npm(['pack',source,'--dry-run','--json'],true));if(packed.length!==1||packed[0].name!==meta.name||packed[0].version!==meta.version)throw new Error('Local package source must match this exact package and version');
77
+ }
78
+ const lifecycleArgs=[];const initArgs=[];let userOnly=false;
79
+ const initValues=new Set(['--project','--repo','--author','--reviewer','--admin','--acceptance-pr']);
80
+ const initSwitches=new Set(['--configure','--non-interactive','--confirm-auto-close']);
81
+ for(let i=0;i<passed.length;i++){
82
+ const arg=passed[i];
83
+ if(arg==='--user-only'){userOnly=true;continue;}
84
+ if(arg==='--dir'){if(!passed[i+1])throw new Error('--dir requires a path');lifecycleArgs.push(arg,passed[++i]);}
85
+ else if(arg==='--json'||initSwitches.has(arg)){initArgs.push(arg);}
86
+ else if(initValues.has(arg)){if(!passed[i+1])throw new Error(`${arg} requires a value`);initArgs.push(arg,passed[++i]);}
87
+ else throw new Error(`Unknown lifecycle option: ${arg}; use ghship help install`);
88
+ }
89
+ const preflight=go(executable,[action,'--skill-only','--check',...lifecycleArgs,'--json'],true);if(preflight.code!==0)return preflight.code;
90
+ const installed=JSON.parse(npm(['list','-g',meta.name,'--depth=0','--json'],true));
91
+ const existing=installed.dependencies && installed.dependencies[meta.name];
92
+ const root=path.resolve(npm(['root','-g'],true).trim());
93
+ const receipt=packageReceipt(root);noLinks(receipt);
94
+ if(!existing&&action!=='uninstall'&&fs.existsSync(receipt))throw new Error('Installation receipt exists without its package; preserved');
95
+ if(existing && (existing.invalid||existing.extraneous))throw new Error('Global package ownership is inconsistent; inspect npm list -g @ghship/cli');
96
+ const observation=existing ? observePackage(root,meta,existing) : null;
97
+ if(action==='uninstall') {
98
+ const removal=go(executable,['uninstall','--skill-only',...lifecycleArgs,'--json'],true);if(removal.code!==0)return removal.code;
99
+ if(existing)npm(['uninstall','-g',meta.name],true);
100
+ const receipt=packageReceipt(root);noLinks(receipt);if(existing&&fs.existsSync(receipt))fs.unlinkSync(receipt);
101
+ console.log(JSON.stringify({userInstalled:false,projectConfigurationsPreserved:true,credentialsPreserved:true}));return 0;
102
+ }
103
+ const skill=go(executable,['install','--skill-only',...lifecycleArgs,'--json'],true);if(skill.code!==0)return skill.code;
104
+ try{if(!observation||!observation.current)npm(['install','-g',source],true);}catch(error){console.error(`Skill is ${meta.version}; CLI installation incomplete. Retry npx ${meta.name}@${meta.version} ${action}`);throw error;}
105
+ const verified=JSON.parse(npm(['list','-g',meta.name,'--depth=0','--json'],true));
106
+ if(!verified.dependencies||verified.dependencies[meta.name]?.version!==meta.version)throw new Error('Installed CLI version does not match skill; installation incomplete');
107
+ const persisted=observePackage(root,meta,verified.dependencies[meta.name],!!observation);if(!persisted.current)throw new Error('Persistent CLI content does not match this package');
108
+ const runtimeVersion=go(path.join(root,meta.name,path.relative(__dirname,executable)),['version'],true);
109
+ if(runtimeVersion.code!==0||runtimeVersion.stdout.trim()!==meta.version)throw new Error('Persistent CLI cannot run the expected version');
110
+ saveReceipt(root,persisted.observed);
111
+ if(action==='update'||userOnly){console.log(JSON.stringify({userInstalled:true,version:meta.version,projectInitialized:false}));return 0;}
112
+ const init=go(executable,['init',...initArgs,'--json'],true,true);
113
+ let result;try{result=JSON.parse(init.stdout);}catch(_){result={ready:false,missing:['Initialization failed before producing a result'],next:['ghship init --json']};}
114
+ result.userInstalled=true;result.version=meta.version;console.log(JSON.stringify(result));return init.code;
115
+ }
116
+ module.exports = {binaryPath};
117
+ if(require.main===module) {
118
+ try {
119
+ const executable=binaryPath();if(!fs.existsSync(executable))throw new Error('Package missing Go binary; reinstall the package');
120
+ const args=process.argv.slice(2);const action=args[0];
121
+ if(['install','update','uninstall'].includes(action)&&!args.includes('--standalone')&&!args.includes('--skill-only')&&!args.includes('--help')){
122
+ if(action!=='install'&&process.env.npm_command!=='exec') {console.error(`Run npx @ghship/cli@latest ${action} from an external npm entry`);process.exitCode=4;}
123
+ else process.exitCode=lifecycle(executable,args);
124
+ } else {
125
+ const child=spawn(executable,args,{stdio:'inherit'});child.on('error',()=>{console.error('Cannot start Go CLI');process.exitCode=1});child.on('exit',(code,signal)=>{if(signal)process.kill(process.pid,signal);else process.exitCode=code===null?1:code});
126
+ }
127
+ }catch(error){console.error(error.message);process.exitCode=1;}
128
+ }