@yunhozz/arc 0.1.10 → 0.2.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/MANIFEST.sha256 +36 -27
- package/README.md +18 -30
- package/claude/skill/references/handoff.md +11 -1
- package/claude/skill/references/planning.md +6 -1
- package/claude/skill/runtime/bootstrap.mjs +1 -1
- package/claude/skill/runtime/cli.mjs +8 -0
- package/{lib/cli/workflow.mjs → claude/skill/runtime/command.mjs} +4 -4
- package/codex/skill/references/handoff.md +11 -1
- package/codex/skill/references/planning.md +6 -1
- package/codex/skill/runtime/bootstrap.mjs +1 -1
- package/codex/skill/runtime/cli.mjs +8 -0
- package/codex/skill/runtime/command.mjs +21 -0
- package/lib/cli/postinstall.mjs +3 -16
- package/lib/cli/root.mjs +8 -19
- package/lib/cli/upgrade.mjs +1 -57
- package/lib/installer/manifest.mjs +6 -1
- package/lib/installer/runtime-assets.mjs +25 -0
- package/lib/installer/shell-completion.mjs +15 -20
- package/lib/installer/uninstall.mjs +5 -1
- package/lib/installer/upgrade-state.mjs +2 -2
- package/lib/installer/upgrade.mjs +2 -5
- package/lib/management/operations.mjs +39 -0
- package/lib/management/status.mjs +18 -0
- package/lib/management/update.mjs +58 -0
- package/lib/management/workflow.mjs +51 -0
- package/lib/release/artifact.mjs +1 -1
- package/lib/runtime/bootstrap.mjs +1 -1
- package/lib/runtime/cli.mjs +8 -0
- package/lib/runtime/command.mjs +21 -0
- package/lib/tui/app.mjs +41 -0
- package/lib/tui/render.mjs +21 -0
- package/lib/tui/setup.mjs +20 -0
- package/lib/tui/terminal.mjs +71 -0
- package/lib/tui/workflow.mjs +12 -0
- package/package.json +1 -1
- package/scripts/postinstall.mjs +1 -3
- package/scripts/update-runtime-assets.mjs +6 -1
- package/lib/cli/choices.mjs +0 -47
- package/lib/cli/completion.mjs +0 -55
- package/lib/cli/index.mjs +0 -55
- package/lib/cli/preset.mjs +0 -29
- package/lib/cli/terminal.mjs +0 -19
- package/lib/cli/uninstall.mjs +0 -17
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import {RUNTIME_MODULE_PATHS} from '../lib/installer/runtime-assets.mjs';
|
|
1
2
|
import fs from 'node:fs';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import {fileURLToPath} from 'node:url';
|
|
5
|
+
import {COOPERATIVE_RUNTIME_MODULES} from '../lib/runtime/bootstrap.mjs';
|
|
4
6
|
const args=process.argv.slice(2);const i=args.indexOf('--root');
|
|
5
7
|
const root=i<0?fileURLToPath(new URL('../',import.meta.url)):path.resolve(args[i+1]);
|
|
6
8
|
const check=args.includes('--check');
|
|
@@ -11,7 +13,10 @@ function safe(filename,directory=false){
|
|
|
11
13
|
}
|
|
12
14
|
function sources(relative){const dir=path.join(root,relative);safe(dir,true);const result=[];for(const entry of fs.readdirSync(dir).sort()){const rel=path.join(relative,entry);const file=path.join(root,rel);safe(file,fs.lstatSync(file).isDirectory());if(fs.lstatSync(file).isDirectory())result.push(...sources(rel));else if(entry.endsWith('.mjs'))result.push(rel);else throw Error(`Unexpected canonical runtime file: ${rel}`);}return result;}
|
|
13
15
|
try{
|
|
14
|
-
safe(root,true);const
|
|
16
|
+
safe(root,true);const modules=sources('lib/runtime').map(name=>name.split(path.sep).join('/')).sort();
|
|
17
|
+
for(const name of COOPERATIVE_RUNTIME_MODULES)if(!modules.includes(`lib/runtime/${name}`))throw Error(`Missing runtime module: ${name}`);
|
|
18
|
+
if(modules.length!==RUNTIME_MODULE_PATHS.length||modules.some((name,index)=>name!==RUNTIME_MODULE_PATHS[index]))throw Error('Runtime asset inventory does not match canonical modules');
|
|
19
|
+
const entries=modules.map(name=>[name.slice('lib/runtime/'.length),fs.readFileSync(path.join(root,name))]);
|
|
15
20
|
const policy=path.join(root,'lib/policy/role-policy.json');safe(policy);entries.push(['role-policy.json',fs.readFileSync(policy)]);
|
|
16
21
|
const outputs=[];for(const platform of ['codex','claude'])for(const [name,bytes]of entries){const filename=path.join(root,platform,'skill/runtime',name);safe(filename);outputs.push({filename,bytes});}
|
|
17
22
|
let drift=false;for(const {filename,bytes}of outputs){if(!fs.existsSync(filename)||!fs.readFileSync(filename).equals(bytes)){drift=true;if(!check){fs.mkdirSync(path.dirname(filename),{recursive:true});fs.writeFileSync(filename,bytes);}}}
|
package/lib/cli/choices.mjs
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import readline from 'node:readline';
|
|
2
|
-
import {EventEmitter} from 'node:events';
|
|
3
|
-
function inputError(message){return Object.assign(new Error(message),{code:'ARC_CLI_INPUT'});}
|
|
4
|
-
function cancelled(){return Object.assign(new Error('Installation cancelled.'),{code:'ARC_CANCELLED'});}
|
|
5
|
-
export async function selectChoices(options,{input=process.stdin,output=process.stdout,targetOnly=false,languageDefault}={}){
|
|
6
|
-
if(languageDefault===undefined&&options.target&&(targetOnly||options.preset)||languageDefault!==undefined&&options.language)return options;
|
|
7
|
-
if(!input.isTTY||!output.isTTY)throw inputError(targetOnly?'Non-interactive execution requires --target':'Non-interactive execution requires --target and --preset');
|
|
8
|
-
const menus=languageDefault!==undefined?[['language',['English','한국어'],['en','ko'],languageDefault==='ko'?1:0]]:[['target',['Claude','Codex','All'],['claude','codex','all'],0],['preset',['Economy','Balanced','Smart'],['economy','balanced','smart'],1]].filter(([key])=>!options[key]&&(!targetOnly||key==='target'));
|
|
9
|
-
const wasRaw=input.isRaw,wasFlowing=input.readableFlowing,listeners=new Map(['data','newListener'].map(event=>[event,input.listeners(event)]));
|
|
10
|
-
let menu=0,index=menus[0][3];
|
|
11
|
-
const draw=(redraw=false)=>{const [key,labels]=menus[menu];if(redraw)output.write(`\x1b[${labels.length}A\r`);else output.write(`Select ${key==='target'?'an installation target':key==='language'?'an output language':'a performance preset'} [Up/Down]:\n`);output.write(labels.map((label,i)=>`\x1b[2K${i===index?'>':' '} ${label}`).join('\n')+'\n');};
|
|
12
|
-
const keys=new EventEmitter();let onKey,onData,onEnd,onError,abort;
|
|
13
|
-
try{return await new Promise((resolve,reject)=>{
|
|
14
|
-
abort=()=>reject(cancelled());onEnd=abort;onError=reject;
|
|
15
|
-
onKey=(str,key={})=>{try{if(key.name==='c'&&key.ctrl||key.name==='d'&&key.ctrl)return abort();if(key.name==='up'||key.name==='down'){index=(index+(key.name==='up'?-1:1)+menus[menu][1].length)%menus[menu][1].length;draw(true);}else if(key.name==='return'||key.name==='enter'){const [name,,values]=menus[menu];options[name]=values[index];output.write('\n');if(++menu===menus.length){keys.removeListener('keypress',onKey);resolve(options);}else{index=menus[menu][3];draw();}}}catch(error){reject(error);}};
|
|
16
|
-
keys.on('keypress',onKey);readline.emitKeypressEvents(keys);onData=chunk=>keys.emit('data',chunk);input.on('data',onData);input.once('end',onEnd);input.once('close',onEnd);input.once('error',onError);process.once('SIGINT',abort);process.once('SIGTERM',abort);
|
|
17
|
-
if(input.setRawMode)input.setRawMode(true);input.resume();draw();
|
|
18
|
-
});}finally{
|
|
19
|
-
keys.removeAllListeners();input.removeListener('data',onData);input.removeListener('end',onEnd);input.removeListener('close',onEnd);input.removeListener('error',onError);process.removeListener('SIGINT',abort);process.removeListener('SIGTERM',abort);
|
|
20
|
-
for(const [event,before]of listeners)for(const listener of input.listeners(event))if(!before.includes(listener))input.removeListener(event,listener);
|
|
21
|
-
if(input.setRawMode)input.setRawMode(Boolean(wasRaw));if(wasFlowing!==true)input.pause();
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export async function selectLimits(options,selections,{input=process.stdin,output=process.stdout}={}){
|
|
26
|
-
const{parsePositiveLimit}=await import('../limits.mjs');
|
|
27
|
-
const fields=[['maxTotalSubagents','max_total_subagents',100,'total'],['maxConcurrentSubagents','max_concurrent_subagents',5,'concurrent']].filter(([key])=>options[key]===undefined);
|
|
28
|
-
if(!fields.length)return options;
|
|
29
|
-
const wasRaw=input.isRaw,wasFlowing=input.readableFlowing,listeners=new Map(['data','newListener'].map(event=>[event,input.listeners(event)]));
|
|
30
|
-
let index=0,value='',onKey,onData,onEnd,onError,abort;
|
|
31
|
-
const draw=()=>{const[,,fallback,label]=fields[index];output.write(`Maximum ${label} subagents [Default=${fallback}]: `);};
|
|
32
|
-
try{return await new Promise((resolve,reject)=>{
|
|
33
|
-
abort=()=>reject(cancelled());onEnd=abort;onError=reject;
|
|
34
|
-
onKey=(str,key={})=>{if(key.ctrl&&(key.name==='c'||key.name==='d'))return abort();if(key.name==='return'||key.name==='enter'){
|
|
35
|
-
const[name,,,label]=fields[index];if(value.trim()){try{options[name]=parsePositiveLimit(value,label);}catch(cause){output.write(`\n${cause.message}\n`);value='';draw();return;}}
|
|
36
|
-
if(name==='maxConcurrentSubagents')options.concurrentSelected=true;output.write('\n');value='';if(++index===fields.length){input.removeListener('keypress',onKey);resolve(options);}else draw();
|
|
37
|
-
}else if(key.name==='backspace'){value=value.slice(0,-1);output.write('\b \b');}else if(str&&!key.ctrl&&!key.meta){value+=str;output.write(str);}};
|
|
38
|
-
onData=chunk=>{for(const character of chunk.toString()){if(index===fields.length)break;const name=character==='\r'||character==='\n'?'return':character==='\x03'?'c':character==='\x04'?'d':character==='\x7f'||character==='\b'?'backspace':undefined;onKey(character,{name,ctrl:character==='\x03'||character==='\x04'});}};
|
|
39
|
-
input.on('data',onData);input.once('end',onEnd);input.once('close',onEnd);input.once('error',onError);process.once('SIGINT',abort);process.once('SIGTERM',abort);if(input.setRawMode)input.setRawMode(true);input.resume();draw();
|
|
40
|
-
});}finally{
|
|
41
|
-
input.removeListener('data',onData);input.removeListener('end',onEnd);input.removeListener('close',onEnd);input.removeListener('error',onError);process.removeListener('SIGINT',abort);process.removeListener('SIGTERM',abort);
|
|
42
|
-
for(const[event,before]of listeners)for(const listener of input.listeners(event))if(!before.includes(listener))input.removeListener(event,listener);
|
|
43
|
-
if(input.setRawMode)input.setRawMode(Boolean(wasRaw));if(wasFlowing!==true)input.pause();
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export async function selectLanguage(options,languageDefault,io={}){return selectChoices(options,{...io,languageDefault});}
|
package/lib/cli/completion.mjs
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
export function renderCompletion(shell){
|
|
2
|
-
if(shell==='bash')return `_arc() {
|
|
3
|
-
local cur="\${COMP_WORDS[COMP_CWORD]}" prev="\${COMP_WORDS[COMP_CWORD-1]}" command="\${COMP_WORDS[1]}" choices=""
|
|
4
|
-
case "$prev" in
|
|
5
|
-
--target) [[ $command == -* || $command == preset || $command == uninstall ]] && choices="claude codex all" ;;
|
|
6
|
-
--preset) [[ $command == -* ]] && choices="economy balanced smart" ;;
|
|
7
|
-
--language) [[ $command == -* ]] && choices="en ko" ;;
|
|
8
|
-
*)
|
|
9
|
-
if (( COMP_CWORD == 1 )) && [[ $cur != -* ]]; then choices="preset uninstall completion upgrade"
|
|
10
|
-
else
|
|
11
|
-
case "$command" in
|
|
12
|
-
preset)
|
|
13
|
-
if (( COMP_CWORD == 2 )) && [[ $cur != -* ]]; then choices="economy balanced smart"
|
|
14
|
-
else choices="--target --check --dry-run --help"; fi ;;
|
|
15
|
-
uninstall) choices="--target --dry-run --skip-global --help --version" ;;
|
|
16
|
-
upgrade) choices="--help" ;;
|
|
17
|
-
completion) (( COMP_CWORD == 2 )) && choices="zsh bash" ;;
|
|
18
|
-
-*) choices="--target --preset --language --max-total-subagents --max-concurrent-subagents --check --dry-run --skip-global --help --version" ;;
|
|
19
|
-
esac
|
|
20
|
-
fi ;;
|
|
21
|
-
esac
|
|
22
|
-
COMPREPLY=( $(compgen -W "$choices" -- "$cur") )
|
|
23
|
-
}
|
|
24
|
-
complete -F _arc arc
|
|
25
|
-
`;
|
|
26
|
-
if(shell==='zsh')return `_arc() {
|
|
27
|
-
local -a candidates
|
|
28
|
-
if (( CURRENT == 2 )) && [[ \${words[CURRENT]} != -* ]]; then
|
|
29
|
-
candidates=(preset uninstall completion upgrade)
|
|
30
|
-
else
|
|
31
|
-
case "\${words[CURRENT-1]}" in
|
|
32
|
-
--target) [[ \${words[2]} == -* || \${words[2]} == preset || \${words[2]} == uninstall ]] && candidates=(claude codex all) ;;
|
|
33
|
-
--preset) [[ \${words[2]} == -* ]] && candidates=(economy balanced smart) ;;
|
|
34
|
-
--language) [[ \${words[2]} == -* ]] && candidates=(en ko) ;;
|
|
35
|
-
*) case "\${words[2]}" in
|
|
36
|
-
preset)
|
|
37
|
-
if (( CURRENT == 3 )) && [[ \${words[CURRENT]} != -* ]]; then candidates=(economy balanced smart)
|
|
38
|
-
else candidates=(--target --check --dry-run --help); fi ;;
|
|
39
|
-
uninstall) candidates=(--target --dry-run --skip-global --help --version) ;;
|
|
40
|
-
upgrade) candidates=(--help) ;;
|
|
41
|
-
completion) (( CURRENT == 3 )) && candidates=(zsh bash) ;;
|
|
42
|
-
-*) candidates=(--target --preset --language --max-total-subagents --max-concurrent-subagents --check --dry-run --skip-global --help --version) ;;
|
|
43
|
-
esac ;;
|
|
44
|
-
esac
|
|
45
|
-
fi
|
|
46
|
-
compadd -a candidates
|
|
47
|
-
}
|
|
48
|
-
if (( ! $+functions[compdef] )); then
|
|
49
|
-
autoload -Uz compinit
|
|
50
|
-
compinit -D
|
|
51
|
-
fi
|
|
52
|
-
compdef _arc arc
|
|
53
|
-
`;
|
|
54
|
-
throw new Error('Unsupported completion shell');
|
|
55
|
-
}
|
package/lib/cli/index.mjs
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import os from 'node:os';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import {fileURLToPath} from 'node:url';
|
|
5
|
-
const packageRoot=fileURLToPath(new URL('../../',import.meta.url));
|
|
6
|
-
import{parsePositiveLimit}from'../limits.mjs';
|
|
7
|
-
const usage='Usage: arc --target <claude|codex|all> --preset <economy|balanced|smart> --language <en|ko>\n [--max-total-subagents <count>] [--max-concurrent-subagents <count>]\n [--check | --dry-run] [--skip-global] [--help] [--version]\n --max-total-subagents: positive integer (default: 100)\n --max-concurrent-subagents: positive integer (default: 5)';
|
|
8
|
-
function inputError(message){return Object.assign(new Error(message),{code:'ARC_CLI_INPUT'});}
|
|
9
|
-
function cancelled(){return Object.assign(new Error('Installation cancelled.'),{code:'ARC_CANCELLED'});}
|
|
10
|
-
export function parseArgs(args){
|
|
11
|
-
const options={},seen=new Set();const names={'--max-total-subagents':'maxTotalSubagents','--max-concurrent-subagents':'maxConcurrentSubagents','--target':'target','--preset':'preset','--language':'language','--skip-global':'skipGlobal','--check':'check','--dry-run':'dryRun','--help':'help','--version':'version'};
|
|
12
|
-
for(let i=0;i<args.length;i++){const flag=args[i],key=Object.hasOwn(names,flag)?names[flag]:undefined;if(!key)throw inputError(`Unknown argument: ${flag}`);if(seen.has(flag))throw inputError(`Duplicate argument: ${flag}`);seen.add(flag);if(key==='maxTotalSubagents'||key==='maxConcurrentSubagents'){try{options[key]=parsePositiveLimit(args[++i],flag);}catch(cause){throw inputError(cause.message);}}else if(key==='target'||key==='preset'||key==='language'){const value=args[++i],allowed=key==='target'?['claude','codex','all']:key==='preset'?['economy','balanced','smart']:['en','ko'];if(!allowed.includes(value))throw inputError(`Invalid ${key}: ${value??'missing value'}`);options[key]=value;}else options[key]=true;}
|
|
13
|
-
if(options.check&&options.dryRun)throw inputError('--check and --dry-run cannot be combined');return options;
|
|
14
|
-
}
|
|
15
|
-
export async function selectChoices(options,io){return(await import('./choices.mjs')).selectChoices(options,io);}
|
|
16
|
-
export async function runCli(args=process.argv.slice(2),{input=process.stdin,output=process.stdout,error=process.stderr,cwd=process.cwd(),env=process.env}={}){
|
|
17
|
-
let controller,onInterrupt,progress;
|
|
18
|
-
try{
|
|
19
|
-
const options=parseArgs(args);if(options.help){output.write(usage+'\n');return 0;}if(options.version){output.write(JSON.parse(fs.readFileSync(path.join(packageRoot,'package.json'))).version+'\n');return 0;}
|
|
20
|
-
if((!input.isTTY||!output.isTTY)&&(!options.target||!options.preset||!options.language))throw inputError('Non-interactive execution requires --target, --preset and --language');
|
|
21
|
-
await selectChoices(options,{input,output});
|
|
22
|
-
const {startProgress}=await import('./progress.mjs');
|
|
23
|
-
const interactive=input.isTTY&&output.isTTY,label=options.check?'Checking ARC…':options.dryRun?'Preparing ARC dry run…':'Installing ARC…';
|
|
24
|
-
if(!interactive)progress=startProgress({output,label});
|
|
25
|
-
controller=new AbortController();onInterrupt=()=>controller.abort();process.once('SIGINT',onInterrupt);process.once('SIGTERM',onInterrupt);
|
|
26
|
-
const {loadNative}=await import('../safe-fs/native.mjs');const native=loadNative();
|
|
27
|
-
const [{openRoot},{verifyPayload},{resolveTarget},{buildInstallPlan,validatePlanSet}]=await Promise.all([import('../safe-fs/index.mjs'),import('../installer/manifest.mjs'),import('../installer/targets.mjs'),import('../installer/plan.mjs')]);
|
|
28
|
-
const safeFs={openRoot:p=>openRoot(p,{native})},payload=verifyPayload({packageRoot,safeFs});
|
|
29
|
-
const platforms=options.target==='all'?['codex','claude']:[options.target];
|
|
30
|
-
const {previewLimits,previewLanguage}=await import('../installer/plan.mjs');const{defaultLanguage}=await import('../installer/language.mjs');const selections=platforms.map(platform=>{const target=resolveTarget(platform,{userHome:os.homedir(),env,cwd});return{target,preview:previewLimits({target,safeFs}),languagePreview:previewLanguage({target,safeFs})};});
|
|
31
|
-
if(interactive){const{selectLimits,selectLanguage}=await import('./choices.mjs');await selectLimits(options,selections,{input,output});await selectLanguage(options,defaultLanguage(selections.map(item=>item.languagePreview.existing)),{input,output});}
|
|
32
|
-
progress??=startProgress({output,label});
|
|
33
|
-
const plans=selections.map(({target,preview,languagePreview})=>buildInstallPlan({payload,target,language:options.language,languagePreview,preset:options.preset,skipGlobal:options.skipGlobal,safeFs,limitsPreview:preview,limits:{...(options.maxTotalSubagents===undefined?{}:{max_total_subagents:options.maxTotalSubagents}),...(options.maxConcurrentSubagents===undefined?{}:{max_concurrent_subagents:options.maxConcurrentSubagents})},concurrentExplicit:options.maxConcurrentSubagents!==undefined||Boolean(options.concurrentSelected)}));const {attachShellCompletion}=await import('../installer/shell-completion.mjs');attachShellCompletion(plans,{safeFs});validatePlanSet(plans);
|
|
34
|
-
const languageLabel=options.language==='ko'?'한국어':'English';
|
|
35
|
-
progress.write(`Install: ${platforms.join('/')} · ${options.preset} · ${languageLabel}\n`);
|
|
36
|
-
for(const plan of plans)progress.write(`${plan.target.platform}: ${plan.target.home} · total ${plan.limits.max_total_subagents} · concurrent ${plan.limits.max_concurrent_subagents}\n`);
|
|
37
|
-
if(options.skipGlobal)progress.write('Global settings: skipped\n');
|
|
38
|
-
else progress.write('zsh completion: eval "$(arc completion zsh)" for this session\n');
|
|
39
|
-
if(plans.some(plan=>plan.limits.max_concurrent_subagents>plan.limits.max_total_subagents))progress.write(`Note: fresh ARC sessions cap concurrency at ${Math.min(...plans.map(plan=>plan.limits.max_total_subagents))}.\n`);
|
|
40
|
-
if(options.dryRun)for(const plan of plans){
|
|
41
|
-
for(const name of plan.generated.skill.keys())progress.write(`Would write file: ${path.join(plan.target.home,'skills/arc',name)}\n`);
|
|
42
|
-
for(const name of plan.generated.profiles.keys())progress.write(`Would write file: ${path.join(plan.target.home,'agents',name)}\n`);
|
|
43
|
-
for(const change of plan.globalChanges)progress.write(`Would update global file: ${change.path}\n`);
|
|
44
|
-
}
|
|
45
|
-
if(options.check||options.dryRun){progress.stop();output.write(options.check?'Check completed. No changes made.\n':'Dry run completed. No changes made.\n');return 0;}
|
|
46
|
-
const [{installTargets},{acquireExternalSkills}]=await Promise.all([import('../installer/install.mjs'),import('../installer/acquisition.mjs')]);
|
|
47
|
-
const result=await installTargets(plans,{safeFs,signal:controller.signal,acquisition:async()=>{const work=fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()),'arc-acquire-'));const acquired=await acquireExternalSkills({work,safeFs,signal:controller.signal});const dispose=acquired.dispose;return{...acquired,dispose:async()=>{await dispose();fs.rmdirSync(work);}};}});
|
|
48
|
-
progress.stop();
|
|
49
|
-
const reportResults=()=>{for(const item of result.results){output.write(`${item.platform}: ${item.status}; rollback: ${item.rollback}\n`);for(const recovery of item.residualPaths)output.write(`Residual path: ${recovery}\n`);if(item.error)error.write(`Error: ${item.error.message}\n`);}};
|
|
50
|
-
if(result.interrupted){reportResults();return 130;}
|
|
51
|
-
if(result.results.every(item=>item.status==='installed'&&!item.error)){output.write(`ARC version ${JSON.parse(fs.readFileSync(path.join(packageRoot,'package.json'))).version} installed! [${result.results.map(item=>item.platform).join('/')}]\n`);return 0;}
|
|
52
|
-
reportResults();return 1;
|
|
53
|
-
}catch(cause){progress?.stop();error.write(`Error: ${cause.message}\n`);if(cause.code==='ARC_CLI_INPUT'){error.write(usage+'\n');return 2;}if(cause.code==='ARC_CANCELLED')return 130;return ['ARC_UNSUPPORTED_RUNTIME','ARC_NATIVE_UNAVAILABLE','ARC_TARGET_CONFIGURATION'].includes(cause.code)?2:1;
|
|
54
|
-
}finally{progress?.stop();if(onInterrupt){process.removeListener('SIGINT',onInterrupt);process.removeListener('SIGTERM',onInterrupt);}}
|
|
55
|
-
}
|
package/lib/cli/preset.mjs
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import os from 'node:os';
|
|
2
|
-
import {validatePlanSet} from '../installer/plan.mjs';
|
|
3
|
-
import {selectChoices} from './choices.mjs';
|
|
4
|
-
const usage='Usage: arc preset [economy|balanced|smart] --target <codex|claude|all> [--check | --dry-run] [--help]';
|
|
5
|
-
const inputError=message=>Object.assign(new Error(message),{code:'ARC_CLI_INPUT'});
|
|
6
|
-
export function parseArgs(args){
|
|
7
|
-
const options={},seen=new Set();
|
|
8
|
-
for(let i=0;i<args.length;i++){
|
|
9
|
-
const arg=args[i];if(['economy','balanced','smart'].includes(arg)){if(options.preset)throw inputError('Duplicate preset');options.preset=arg;continue;}
|
|
10
|
-
if(!['--target','--check','--dry-run','--help'].includes(arg))throw inputError(`Unknown argument: ${arg}`);if(seen.has(arg))throw inputError(`Duplicate argument: ${arg}`);seen.add(arg);
|
|
11
|
-
if(arg==='--target'){const value=args[++i];if(!['codex','claude','all'].includes(value))throw inputError('Invalid target');options.target=value;}else options[arg==='--dry-run'?'dryRun':arg.slice(2)]=true;
|
|
12
|
-
}
|
|
13
|
-
if(options.check&&options.dryRun)throw inputError('--check and --dry-run cannot be combined');return options;
|
|
14
|
-
}
|
|
15
|
-
export async function runCli(args=process.argv.slice(2),{input=process.stdin,output=process.stdout,error=process.stderr}={}){
|
|
16
|
-
let onInterrupt;
|
|
17
|
-
try{
|
|
18
|
-
const options=parseArgs(args);if(options.help){output.write(usage+'\n');return 0;}
|
|
19
|
-
await selectChoices(options,{input,output});
|
|
20
|
-
const[{loadNative},{openRoot},{resolveTarget},{buildPresetPlan,switchPresetTargets}]=await Promise.all([import('../safe-fs/native.mjs'),import('../safe-fs/index.mjs'),import('../installer/targets.mjs'),import('../installer/preset.mjs')]);
|
|
21
|
-
const native=loadNative(),safeFs={openRoot:p=>openRoot(p,{native})};
|
|
22
|
-
const plans=(options.target==='all'?['codex','claude']:[options.target]).map(platform=>buildPresetPlan({target:resolveTarget(platform,{userHome:os.homedir(),env:process.env}),preset:options.preset,safeFs}));
|
|
23
|
-
validatePlanSet(plans);
|
|
24
|
-
if(options.check||options.dryRun){for(const plan of plans){output.write(`${plan.target.platform}: ${plan.specs.length} file(s) would change\n`);if(options.dryRun)for(const spec of plan.specs)output.write(`Would write: ${spec.path}\n`);}output.write('Check completed. No changes made.\n');return 0;}
|
|
25
|
-
const controller=new AbortController();onInterrupt=()=>controller.abort();process.once('SIGINT',onInterrupt);const result=await switchPresetTargets(plans,{safeFs,signal:controller.signal});
|
|
26
|
-
for(const item of result.results){output.write(`${item.platform}: ${item.status}; rollback: ${item.rollback}\n`);for(const name of item.residualPaths)output.write(`Residual path: ${name}\n`);if(item.error)error.write(`Error: ${item.error.message}\n`);}
|
|
27
|
-
output.write('Open a new session to use the selected preset.\n');return result.interrupted?130:result.results.every(item=>['updated','unchanged'].includes(item.status)&&!item.error)?0:1;
|
|
28
|
-
}catch(cause){error.write(`Error: ${cause.message}\n`);if(cause.code==='ARC_CLI_INPUT'){error.write(usage+'\n');return 2;}return cause.code==='ARC_CANCELLED'?130:['ARC_UNSUPPORTED_RUNTIME','ARC_NATIVE_UNAVAILABLE','ARC_TARGET_CONFIGURATION'].includes(cause.code)?2:1;}finally{if(onInterrupt)process.removeListener('SIGINT',onInterrupt);}
|
|
29
|
-
}
|
package/lib/cli/terminal.mjs
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import tty from 'node:tty';
|
|
3
|
-
function openControllingTerminal(){
|
|
4
|
-
let inputFd,outputFd,input,output;
|
|
5
|
-
try{
|
|
6
|
-
inputFd=fs.openSync('/dev/tty','r+');outputFd=fs.openSync('/dev/tty','r+');
|
|
7
|
-
input=new tty.ReadStream(inputFd);inputFd=undefined;
|
|
8
|
-
output=new tty.WriteStream(outputFd);outputFd=undefined;
|
|
9
|
-
return{io:{input,output,error:output},dispose(){input.destroy();output.destroy();}};
|
|
10
|
-
}catch(cause){input?.destroy();output?.destroy();if(inputFd!==undefined)fs.closeSync(inputFd);if(outputFd!==undefined)fs.closeSync(outputFd);throw cause;}
|
|
11
|
-
}
|
|
12
|
-
export function openInstallerTerminal({platform=process.platform,env=process.env,input=process.stdin,output=process.stdout,error=process.stderr,openTty=openControllingTerminal}={}){
|
|
13
|
-
if(!['darwin','linux'].includes(platform))return{kind:'skip',reason:'unsupported platform'};
|
|
14
|
-
if(['CI','CONTINUOUS_INTEGRATION','BUILD_NUMBER','RUN_ID'].some(name=>env[name]&&env[name]!=='false'&&env[name]!=='0'))return{kind:'skip',reason:'non-interactive environment'};
|
|
15
|
-
let owned,io;
|
|
16
|
-
try{if(input.isTTY&&output.isTTY)io={input,output,error};else{owned=openTty();io=owned.io;}if(!io.input.isTTY||!io.output.isTTY)throw Error('Terminal is not interactive');}catch{owned?.dispose();return{kind:'skip',reason:'no interactive terminal'};}
|
|
17
|
-
const wasRaw=Boolean(io.input.isRaw),wasFlowing=io.input.readableFlowing;let disposed=false;
|
|
18
|
-
return{kind:'interactive',io,dispose(){if(disposed)return;disposed=true;try{if(io.input.setRawMode&&!io.input.destroyed)io.input.setRawMode(wasRaw);if(wasFlowing!==true)io.input.pause();}finally{owned?.dispose();}}};
|
|
19
|
-
}
|
package/lib/cli/uninstall.mjs
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';import os from 'node:os';import path from 'node:path';import {fileURLToPath} from 'node:url';
|
|
2
|
-
const packageRoot=fileURLToPath(new URL('../../',import.meta.url));
|
|
3
|
-
const usage='Usage: arc uninstall [--target <claude|codex|all>] [--dry-run] [--skip-global] [--help] [--version]';
|
|
4
|
-
function inputError(message){return Object.assign(new Error(message),{code:'ARC_CLI_INPUT'});}
|
|
5
|
-
export function parseArgs(args){const options={},seen=new Set(),names={'--target':'target','--dry-run':'dryRun','--skip-global':'skipGlobal','--help':'help','--version':'version'};for(let i=0;i<args.length;i++){const flag=args[i],key=Object.hasOwn(names,flag)?names[flag]:undefined;if(!key)throw inputError(`Unknown argument: ${flag}`);if(seen.has(flag))throw inputError(`Duplicate argument: ${flag}`);seen.add(flag);if(key==='target'){const value=args[++i];if(!['claude','codex','all'].includes(value))throw inputError(`Invalid target: ${value??'missing value'}`);options.target=value;}else options[key]=true;}return options;}
|
|
6
|
-
export async function selectTarget(options,io={}){return(await import('./choices.mjs')).selectChoices(options,{...io,targetOnly:true});}
|
|
7
|
-
export async function runCli(args=process.argv.slice(2),{input=process.stdin,output=process.stdout,error=process.stderr}={}){
|
|
8
|
-
let onInterrupt;
|
|
9
|
-
try{const options=parseArgs(args);if(options.help){output.write(usage+'\n');return 0;}if(options.version){output.write(JSON.parse(fs.readFileSync(path.join(packageRoot,'package.json'))).version+'\n');return 0;}
|
|
10
|
-
if(!options.target&&(!input.isTTY||!output.isTTY))throw inputError('Non-interactive execution requires --target');await selectTarget(options,{input,output});
|
|
11
|
-
const [{loadNative},{openRoot},{resolveTarget},{buildUninstallPlan,uninstallTargets}]=await Promise.all([import('../safe-fs/native.mjs'),import('../safe-fs/index.mjs'),import('../installer/targets.mjs'),import('../installer/uninstall.mjs')]);const native=loadNative(),safeFs={openRoot:p=>openRoot(p,{native})};
|
|
12
|
-
const plans=(options.target==='all'?['codex','claude']:[options.target]).map(platform=>buildUninstallPlan({target:resolveTarget(platform,{userHome:os.homedir(),env:process.env}),safeFs,skipGlobal:options.skipGlobal}));
|
|
13
|
-
for(const plan of plans){output.write(`${plan.target.platform}: ${plan.target.home}\n`);for(const note of plan.summary)output.write(note+'\n');if(options.dryRun){for(const name of plan.removals)output.write(`Would remove: ${name}\n`);for(const change of plan.globalChanges)output.write(`Would remove managed block: ${change.path}\n`);}}
|
|
14
|
-
if(options.dryRun){output.write('Dry run completed. No changes made.\n');return 0;}
|
|
15
|
-
const controller=new AbortController();onInterrupt=()=>controller.abort();process.once('SIGINT',onInterrupt);const result=await uninstallTargets(plans,{safeFs,signal:controller.signal});for(const item of result.results){output.write(`${item.platform}: ${item.status}; rollback: ${item.rollback}\n`);for(const name of item.residualPaths)output.write(`Residual path: ${name}\n`);if(item.error)error.write(`Error: ${item.error.message}\n`);}return result.interrupted?130:result.results.every(item=>item.status==='uninstalled'&&!item.error)?0:1;
|
|
16
|
-
}catch(cause){error.write(`Error: ${cause.message}\n`);if(cause.code==='ARC_CANCELLED')return 130;if(cause.code==='ARC_CLI_INPUT'){error.write(usage+'\n');return 2;}return ['ARC_UNSUPPORTED_RUNTIME','ARC_NATIVE_UNAVAILABLE','ARC_TARGET_CONFIGURATION'].includes(cause.code)?2:1;}finally{if(onInterrupt)process.removeListener('SIGINT',onInterrupt);}
|
|
17
|
-
}
|