@nortal/ai1st-kit-cli 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/dist/adapter/claude-code.js +1 -0
- package/dist/adapter/index.js +0 -0
- package/dist/auth/index.js +1 -0
- package/dist/auth/keychain.js +2 -0
- package/dist/auth/pat-prompt.js +1 -0
- package/dist/auth/probe-https.js +1 -0
- package/dist/auth/probe-ssh.js +1 -0
- package/dist/cli.js +4 -0
- package/dist/commands/auth.js +1 -0
- package/dist/commands/install.js +1 -0
- package/dist/commands/lifecycle.js +1 -0
- package/dist/commands/list.js +1 -0
- package/dist/core/classifier.js +1 -0
- package/dist/core/flags.js +1 -0
- package/dist/core/lockfile.js +1 -0
- package/dist/core/manifest-scanner.js +1 -0
- package/dist/core/manifest.js +1 -0
- package/dist/core/planner.js +1 -0
- package/dist/core/prompts.js +2 -0
- package/dist/core/state-diff.js +1 -0
- package/dist/core/target-dir.js +1 -0
- package/dist/core/wrapper.js +1 -0
- package/dist/errors.js +1 -0
- package/dist/generated/content-ref.js +1 -0
- package/dist/generated/welcome-content.js +20 -0
- package/dist/handlers/files.js +1 -0
- package/dist/handlers/hooks.js +2 -0
- package/dist/handlers/mcp.js +3 -0
- package/dist/index.js +5 -0
- package/dist/log.js +4 -0
- package/dist/resolver/index.js +1 -0
- package/dist/resolver/release-discovery.js +1 -0
- package/dist/resolver/remote-git.js +1 -0
- package/dist/schemas/index.js +1 -0
- package/dist/schemas/install-plan.js +1 -0
- package/dist/schemas/kit-config.js +1 -0
- package/dist/schemas/list-output.js +1 -0
- package/dist/schemas/lockfile.js +1 -0
- package/dist/schemas/manifests.js +0 -0
- package/dist/schemas/skill-frontmatter.js +1 -0
- package/dist/ui/banner.js +4 -0
- package/dist/util/advisory-lock.js +2 -0
- package/dist/util/canonical-json.js +2 -0
- package/dist/util/fs.js +1 -0
- package/dist/util/hash.js +1 -0
- package/dist/util/package-root.js +1 -0
- package/dist/util/semver.js +1 -0
- package/dist/util/spawn.js +1 -0
- package/dist/util/verbose-log.js +3 -0
- package/package.json +52 -0
- package/wrapper-templates/README.md.template +120 -0
- package/wrapper-templates/gitignore.template +186 -0
- package/wrapper-templates/project-repos.gitkeep +0 -0
package/README.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# @nortal/ai1st-kit-cli
|
|
2
|
+
|
|
3
|
+
Nortal's AI-Kit installer. A small CLI that drops Nortal's curated set of AI tooling (Claude Code skills, agents, MCP server registrations, hooks, constitutions) into any project and keeps it in sync as the kit evolves.
|
|
4
|
+
|
|
5
|
+
## Quick start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx @nortal/ai1st-kit-cli install
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Requirements
|
|
12
|
+
|
|
13
|
+
- Node.js >= 20
|
|
14
|
+
- Git access to Nortal's internal GitLab (SSH key, HTTPS credential, or a personal access token — the CLI guides you through setup on first run)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{homedir as o}from"node:os";import{isAbsolute as t,join as d}from"node:path";import{CliError as a}from"../errors.js";function p(e){if(!t(e))throw new a({code:"E_INTERNAL",message:`createClaudeCodeAdapter: expected an absolute targetDir, got "${e}"`});const r=d(o(),".claude");return{id:"claude-code",paths(){return{project:e,user:r}}}}export{p as createClaudeCodeAdapter};
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{CliError as p}from"../errors.js";import{getKeychain as T}from"./keychain.js";import{probeSsh as v}from"./probe-ssh.js";import{probeHttps as g}from"./probe-https.js";import{promptForPat as w}from"./pat-prompt.js";function u(t){if(t.startsWith("https://"))return t;if(t.startsWith("git@")){const n=t.slice(4),e=n.indexOf(":");if(e<0)return t;const o=n.slice(0,e);let r=n.slice(e+1);return r.startsWith("/")&&(r=r.slice(1)),`https://${o}/${r}`}return t}function I(t){if(t.startsWith("git@"))return t;if(t.startsWith("https://")){const n=t.slice(8),e=n.indexOf("/");if(e<0)return t;const o=n.slice(0,e),r=n.slice(e+1);return`git@${o}:${r}`}return t}async function A(t){try{return(await fetch("https://gitlab.nortal.com/api/v4/user",{method:"GET",headers:{"PRIVATE-TOKEN":t},signal:AbortSignal.timeout(5e3)})).status===200}catch{return!1}}function h(){return{mode:"ssh",preferredUrlFor:t=>I(t),applyToGitEnv:()=>{}}}function f(){return{mode:"https",preferredUrlFor:t=>u(t),applyToGitEnv:()=>{}}}function _(t){return/^[A-Za-z0-9._\-]+$/.test(t)}function s(t,n){return{mode:n,preferredUrlFor:e=>{const o=u(e);if(n!=="env-token")return o;const r=new URL(o);return r.username="oauth2",r.password=t,r.toString()},applyToGitEnv:e=>{const o=Buffer.from(`oauth2:${t}`).toString("base64");e.GIT_TERMINAL_PROMPT="0",e.GIT_CONFIG_COUNT="1",e.GIT_CONFIG_KEY_0="http.extraHeader",e.GIT_CONFIG_VALUE_0=`Authorization: Basic ${o}`,e.AI1ST_GIT_TOKEN=t}}}async function y(t,n={}){const e=n.probeSshImpl??v,o=n.probeHttpsImpl??g,r=n.validatePatImpl??A,m=n.getKeychainImpl??T,d=n.promptForPatImpl??w;if(!t.interactive){const l=(process.env.GITLAB_TOKEN??"").trim();if(l.length>0)return s(l,"env-token");if((await e(t.contentRepo)).ok)return h();if((await o(t.contentRepo)).ok)return f();throw new p({code:"E_AUTH",message:"No usable credentials in non-interactive mode. Next: set GITLAB_TOKEN env var or run interactively.",headline:"No usable GitLab credentials in non-interactive mode.",todo:["Set the `GITLAB_TOKEN` environment variable to a valid PAT, or","Re-run interactively (omit `--yes`) so the CLI can prompt for credentials."]})}if((await e(t.contentRepo)).ok)return h();if((await o(t.contentRepo)).ok)return f();const a=await m(),i=await a.getPassword();if(i&&_(i)&&await r(i))return s(i,"pat-stored");i&&await a.deletePassword();const c=await d({contentRepo:t.contentRepo,keychain:a,validatePat:r});if("cancelled"in c)throw new p({code:"E_AUTH",message:"No GitLab credentials provided. Next: re-run and supply a PAT, or configure SSH for gitlab.nortal.com."});return s(c.token,"pat-prompted")}export{y as probeAndResolveAuth,u as toHttpsUrl,I as toSshUrl,A as validatePat};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{readFile as u,writeFile as w,mkdir as f,chmod as m,stat as p}from"node:fs/promises";import{homedir as y}from"node:os";import{dirname as g,join as b}from"node:path";import{log as h}from"../log.js";const o="nortal-ai1st-kit-cli",a="gitlab-pat";let s=!1;function A(){s=!1}function i(){return b(y(),".config","nortal-ai1st","credentials.json")}async function c(t){let e;try{e=await u(t,"utf8")}catch(r){if(r.code==="ENOENT")return null;throw r}let n;try{n=JSON.parse(e)}catch(r){throw new Error(`credentials file at ${t} is not valid JSON: ${r.message}`)}if(n===null||typeof n!="object"||Array.isArray(n))throw new Error(`credentials file at ${t} must be a JSON object`);if(!s&&process.argv.includes("--verbose"))try{const r=await p(t);process.platform!=="win32"&&(r.mode&63)!==0&&(h.warn(`credentials file ${t} has wide permissions (mode ${(r.mode&511).toString(8)}); recommend chmod 0600`),s=!0)}catch{}return n}async function d(t,e){await f(g(t),{recursive:!0}),await w(t,JSON.stringify(e,null,2)+`
|
|
2
|
+
`,"utf8"),process.platform!=="win32"&&await m(t,384)}function l(){return{async getPassword(){const t=i(),e=await c(t);if(e===null)return null;const n=e[a];return typeof n=="string"?n:null},async setPassword(t){const e=i(),r=await c(e)??{};r[a]=t,await d(e,r)},async deletePassword(){const t=i(),e=await c(t);e!==null&&(delete e[a],await d(t,e))}}}function C(t){return{async getPassword(){return t.getPassword(o,a)},async setPassword(e){await t.setPassword(o,a,e)},async deletePassword(){await t.deletePassword(o,a)}}}async function P(){if(process.env.AI1ST_KIT_FORCE_KEYCHAIN_FALLBACK==="1")return l();try{const t=await import("keytar"),e=t.default??t;return typeof e.getPassword!="function"?l():C(e)}catch{return l()}}const K={resetFileModeWarning:A};export{a as KEYCHAIN_ACCOUNT,o as KEYCHAIN_SERVICE,K as __test__,P as getKeychain};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{spawn as i}from"node:child_process";import{password as c,isCancel as l}from"@clack/prompts";import{log as o}from"../log.js";const s="https://gitlab.nortal.com/-/user_settings/personal_access_tokens?name=Nortal+AI1st-Kit+Installer&description=Used+by+npx+%40nortal%2Fai1st-kit-cli+to+fetch+kit+content&scopes=read_api,read_repository",p='In the browser, click "Create personal access token" and copy the token. Set the expiration to the maximum allowed (1 year, set by your admin) before clicking Create.';function d(e){let r,t;process.platform==="darwin"?(r="open",t=[e]):process.platform==="win32"?(r="cmd",t=["/c","start",'""',e]):(r="xdg-open",t=[e]);try{const a=i(r,t,{shell:!1,stdio:"ignore",detached:!0});a.on("error",()=>{o.warn("could not auto-open the browser; please copy/paste the URL above")}),a.unref()}catch{o.warn("could not auto-open the browser; please copy/paste the URL above")}}async function m(e){const r=e.openBrowser??d;o.info("Open this URL in your browser to create a GitLab Personal Access Token:"),o.info(s),o.info(p),r(s);const t=await c({message:"Paste the GitLab PAT (glpat-\u2026)",validate:n=>n&&n.startsWith("glpat-")?void 0:'Token should start with "glpat-"'});if(l(t))return{cancelled:!0};const a=t;return/^[A-Za-z0-9._\-]+$/.test(a)?await e.validatePat(a)?(await e.keychain.setPassword(a),{token:a}):(o.warn("Token validation failed; treating as cancelled. Re-run the install to try again."),{cancelled:!0}):(o.warn("Pasted token contains characters that are unsafe to embed in a git header (whitespace/control/quotes); treating as cancelled."),{cancelled:!0})}export{p as PAT_GUIDANCE,s as PAT_URL,d as openInBrowser,m as promptForPat};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{spawn as l}from"node:child_process";function c(t){if(t.startsWith("https://"))return t;if(t.startsWith("git@")){const n=t.slice(4),s=n.indexOf(":");if(s<0)return t;const e=n.slice(0,s);let i=n.slice(s+1);return i.startsWith("/")&&(i=i.slice(1)),`https://${e}/${i}`}return t}async function a(t){const n=c(t);return new Promise(s=>{let e=!1;const i={...process.env,GIT_TERMINAL_PROMPT:"0",GIT_ASKPASS:"",SSH_ASKPASS:"",GIT_CONFIG_NOSYSTEM:"1"},r=l("git",["ls-remote",n,"HEAD"],{shell:!1,signal:AbortSignal.timeout(5e3),stdio:["ignore","ignore","ignore"],env:i});r.on("error",()=>{e||(e=!0,s({ok:!1}))}),r.on("exit",o=>{e||(e=!0,s({ok:o===0}))})})}export{a as probeHttps};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{spawn as h}from"node:child_process";function T(t){if(t.startsWith("git@"))return t;if(t.startsWith("https://")){const e=t.slice(8),n=e.indexOf("/");if(n<0)return t;const s=e.slice(0,n),o=e.slice(n+1);return`git@${s}:${o}`}return t}const _=5e3,m=1e4;async function a(t,e){return new Promise(n=>{let s=!1,o=!1;const i=new AbortController,u=setTimeout(()=>{o=!0,i.abort()},e),S=Math.max(1,Math.ceil(e/1e3)),f={...process.env,GIT_TERMINAL_PROMPT:"0",GIT_ASKPASS:"",SSH_ASKPASS:"",GIT_SSH_COMMAND:`ssh -o BatchMode=yes -o ConnectTimeout=${S}`,GIT_CONFIG_NOSYSTEM:"1"},c=r=>{s||(s=!0,clearTimeout(u),n({ok:r,timedOut:o}))},l=h("git",["ls-remote",t,"HEAD"],{shell:!1,signal:i.signal,stdio:["ignore","ignore","ignore"],env:f});l.on("error",()=>c(!1)),l.on("exit",r=>c(r===0))})}async function d(t){const e=T(t),n=await a(e,_);return n.ok?{ok:!0}:n.timedOut?{ok:(await a(e,m)).ok}:{ok:!1}}export{d as probeSsh};
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{mkdir as U,readFile as V}from"node:fs/promises";import{join as K}from"node:path";import*as q from"@clack/prompts";import{Command as F}from"commander";import{computeStateDiff as Y,install as W,isEmptyDiff as G,printInstallSummary as J}from"./commands/install.js";import{readPriorLockfileOrUndefined as X}from"./commands/lifecycle.js";import{list as z,listJson as Z,printListResult as B}from"./commands/list.js";import{assertLatestContentRefMutex as H,buildInstallPlanFromFlags as Q,MODULES_ALL_SENTINEL as x,parseContentRefOrThrow as T}from"./core/flags.js";import{loadManifest as L}from"./core/manifest.js";import{confirmInteractive as E,runInteractivePrompts as ee}from"./core/prompts.js";import{assertWrapperPreconditions as te,WRAPPER_SCAFFOLD_MODULE_ID as A}from"./core/wrapper.js";import{assertTargetDirPreconditions as $,resolveTargetDir as b}from"./core/target-dir.js";import{CliError as c}from"./errors.js";import{log as l,setLogGuide as oe}from"./log.js";import{welcomeBanner as ne}from"./ui/banner.js";import{dispatchAuthStatus as re,dispatchAuthLogout as ie}from"./commands/auth.js";import{probeAndResolveAuth as se}from"./auth/index.js";import{createResolver as ae}from"./resolver/index.js";import{discoverVersions as le}from"./resolver/release-discovery.js";import{CONTENT_REPO as P,MAX_CONTENT_REF as C,MIN_CONTENT_REF as D}from"./generated/content-ref.js";import{tagSemverGte as de,tagSemverLt as ce}from"./util/semver.js";import{acquireAdvisoryLock as ue}from"./util/advisory-lock.js";import{getPackageRoot as fe}from"./util/package-root.js";import{createVerboseLogger as M}from"./util/verbose-log.js";import{ListOutputSchema as pe}from"./schemas/list-output.js";async function dt(e={}){const o=S(e),t=new F,n=await Se();t.name("ai1st-kit").description("Nortal AI1st-Kit installer").version(n),t.command("install").description("Install, update, or converge an AI1st-Kit installation").option("--mode <mode>","install mode (existing-repository | wrapper-repository)").option("--modules <csv>",'comma-separated module IDs, or "all" for every non-MCP module declared by the kit').option("--mcp <csv>","comma-separated MCP module IDs").option("--constitutions <csv>","comma-separated constitution module IDs").option("--hooks","install hooks").option("--yes","non-interactive mode (every required flag must be provided)").option("--verbose","write a structured log of the run").option("--content-ref <vX.Y.Z>","Pin to a specific AI1st-Kit version (overrides the picker / latest-resolution).").option("--latest","Resolve to the latest in-range AI1st-Kit version (skips the picker). Mutually exclusive with --content-ref.").option("--confirm-uninstall",'Acknowledge that an empty --modules and --mcp selection means "uninstall the kit and delete the lockfile".').option("--target-dir <path>","project root to install into (defaults to the current working directory)").action(async i=>{await me(i,o)}),t.command("list").description("List installed modules").option("--json","emit a stable JSON document instead of human-readable text").option("--verbose","write a structured log of the run").option("--target-dir <path>","project root to read the lockfile from (defaults to the current working directory)").action(async i=>{await Me(i)});const r=t.command("auth").description("Manage GitLab credentials");return r.command("status").description("Show current auth mode and stored token status").action(re),r.command("logout").description("Delete stored GitLab PAT from the OS keychain").action(ie),t}async function me(e,o={}){const t=S(o),n=e.yes??!1;!n&&process.stdout.isTTY===!0&&(l.plain(ne()),oe(!0));const i=process.cwd(),s=Pe(e.mode),f=await Oe({flags:e,originalCwd:i,deps:t});if(f.kind==="cancelled"){l.info("Install cancelled.");return}const d=f.targetDir;await $(d,s),s==="wrapper-repository"&&await U(d,{recursive:!0});const p=e.verbose??!1,a=M({targetDir:d,enabled:p});g(a,"info","install start",{flags:e});const m=await ue({targetDir:d,command:"install"});try{H({latest:e.latest??!1,contentRef:T(e.contentRef)});const u=await X(d);if(Ne(e.mode,u),n&&u===void 0&&e.mode===void 0)throw new c({code:"E_INVALID_INPUT",message:"install --yes requires --mode=<existing-repository|wrapper-repository>. Next: re-run with --mode=existing-repository or --mode=wrapper-repository.",headline:"`install --yes` requires `--mode=...`.",todo:["Re-run with `--mode=existing-repository` (drop in to a project) or `--mode=wrapper-repository` (umbrella folder)."]});const w=await t.probeAuth({contentRepo:P,interactive:!n}),v=await he({flags:e,priorLockfile:u,deps:t,targetDir:d,seedMode:s,auth:w});if(v.kind==="cancelled"){l.info("Install cancelled."),g(a,"info","install cancelled"),await a.flush();return}const{plan:h,manifest:R,resolver:N}=v.state,I=Y({priorLockfile:u,desiredModules:h.modules,desiredMcp:h.mcp,desiredContentRef:h.contentRef,kitConfig:R});if(I.reverseDepConflicts.length>0)throw Ae(I);if(G(I)&&!ke(u)){l.ok("Already at desired state; no changes."),g(a,"info","install end",{noOp:!0}),await a.flush();return}const k=be(h,I,u);if(k&&De(h,e),xe(I,k),!h.yes&&await Ce(k)!=="confirmed"){l.info("Install cancelled."),g(a,"info","install cancelled",{stage:"proceed-confirm"}),await a.flush();return}const y=await W({plan:h,targetDir:d,resolver:N,stateDiff:I,uninstallEmptyState:k});if(J(y),g(a,"info","install end",{installedModules:y.installedModules,removedModules:y.removedModules,conflicts:y.conflicts.length,lockfileDeleted:y.lockfileDeleted}),h.yes&&y.conflicts.length>0)throw new c({code:"E_CONFLICT",message:`${y.conflicts.length} file(s) conflict in --yes mode. Next: review the .new files listed in the summary, merge by hand, delete the .new files, and re-run with --yes once the working tree is clean.`});await a.flush()}catch(u){throw a.setEnabled(!0),g(a,"error","install error",{error:j(u)}),await a.flush(),u}finally{await m.release()}}async function he(e){const{flags:o,priorLockfile:t,deps:n,targetDir:r,seedMode:i,auth:s}=e,f=T(o.contentRef),d=o.yes??!1,p=await n.discoverVersions({auth:s,contentRepo:P,minContentRef:D,maxContentRef:C});if(p.length===0)throw new c({code:"E_RESOLVE",message:`No AI1st-Kit versions found in the compat range [${D}, ${C??"unbounded"}). Next: ask the content repo owner to publish a release tag, or upgrade the CLI: npx @nortal/ai1st-kit-cli@latest install.`,headline:"Couldn't find any AI1st-Kit versions in your compatibility range.",todo:[`The content repo has no strict vX.Y.Z tag in [${D}, ${C??"unbounded"}).`,"Upgrade the CLI (npx @nortal/ai1st-kit-cli@latest install) or contact the content-repo owner."]});return f!==void 0&&(Le(f),$e(f,p)),d?ge({flags:o,priorLockfile:t,versions:p,auth:s,deps:n,explicitContentRef:f,targetDir:r,seedMode:i}):ye({flags:o,priorLockfile:t,versions:p,auth:s,deps:n,explicitContentRef:f,targetDir:r,seedMode:i})}async function ge(e){const{flags:o,priorLockfile:t,versions:n,auth:r,deps:i,explicitContentRef:s,targetDir:f}=e,d=ve({explicitContentRef:s,latest:o.latest??!1,priorLockfile:t,versions:n}),p=Ie(o,t),a=Q(p,d);if(t===void 0&&a.missingFields.includes("mode"))throw new c({code:"E_INVALID_INPUT",message:"install --yes requires --mode=<existing-repository|wrapper-repository>. Next: re-run with --mode=existing-repository or --mode=wrapper-repository.",headline:"`install --yes` requires `--mode=...`.",todo:["Re-run with `--mode=existing-repository` (drop in to a project) or `--mode=wrapper-repository` (umbrella folder)."]});let m=a.plan;if(t!==void 0&&a.missingFields.includes("mode")){const N=t.modules[A]!==void 0?"wrapper-repository":"existing-repository";m={...a.plan,mode:N}}m.mode==="wrapper-repository"&&t===void 0&&await te(f);const u=i.buildResolver(r,d),w=m.modules.length===1&&m.modules[0]===x,v=await u.resolve("remote-git:."),h=await L(v.rootDir);let R=m;if(w&&(R=Te(m,h)),t===void 0&&R.modules.length===0&&R.mcp.length===0)throw new c({code:"E_INVALID_INPUT",message:"--yes requires an explicit module selection. Next: pass --modules=<csv> with the modules you want, or --modules=all to install every module declared by the kit.",headline:"--yes requires an explicit module selection.",todo:["Pass --modules=<csv> with the modules you want.","Use --modules=all to install every module declared by the kit."]});return we({contentRef:d,explicitContentRef:s,latest:o.latest??!1,priorInstalledRef:t?.installedRef??null}),{kind:"resolved",state:{plan:R,auth:r,resolver:u,manifest:h}}}function we(e){if(e.explicitContentRef!==void 0){l.info(`Using AI1st-Kit ${e.contentRef} (--content-ref pin)`);return}if(e.latest){l.info(`Using AI1st-Kit ${e.contentRef} (--latest)`);return}if(e.priorInstalledRef!==null&&e.priorInstalledRef===e.contentRef){l.info(`Using AI1st-Kit ${e.contentRef} (carried forward from prior install)`);return}if(e.priorInstalledRef!==null&&e.priorInstalledRef!==e.contentRef){l.warn(`Prior install pinned ${e.priorInstalledRef}, which is no longer available in the content repo; falling back to latest in range: ${e.contentRef}.`);return}l.info(`Using AI1st-Kit ${e.contentRef} (latest in range)`)}async function ye(e){const{flags:o,priorLockfile:t,versions:n,auth:r,deps:i,explicitContentRef:s,seedMode:f}=e;s!==void 0&&l.info(`Using AI1st-Kit ${s} (--content-ref pin)`);const d=Re(s,t?.installedRef??null,n),p=i.buildResolver(r,d),a=await p.resolve("remote-git:."),m=await L(a.rootDir),u=o.verbose??!1,w=await ee({manifest:m,verbose:u,seedMode:f,versions:n,...s!==void 0?{preselectedContentRef:s}:{},...t!==void 0?{priorLockfile:t}:{}});if(w.kind==="cancelled")return{kind:"cancelled"};const v=w.plan.contentRef===d?p:i.buildResolver(r,w.plan.contentRef);return{kind:"resolved",state:{plan:w.plan,auth:r,resolver:v,manifest:m}}}function ve(e){if(e.explicitContentRef!==void 0)return e.explicitContentRef;if(e.latest)return e.versions[0].tag;const o=e.priorLockfile?.installedRef;return typeof o=="string"&&e.versions.some(t=>t.tag===o)?o:e.versions[0].tag}function Re(e,o,t){return e!==void 0?e:o!==null&&t.some(n=>n.tag===o)?o:t[0].tag}function Ie(e,o){if(o===void 0)return e;const t={...e};if(e.modules===void 0){const n=_(o,"non-mcp");t.modules=n.join(",")}if(e.mcp===void 0){const n=_(o,"mcp");t.mcp=n.join(",")}return t}function _(e,o){const t=[];for(const n of Object.keys(e.modules)){if(n===A)continue;const r=e.modules[n];(o==="mcp"?r.kind==="mcp":r.kind!=="mcp")&&t.push(n)}return t.sort()}function ke(e){if(e===void 0)return!1;for(const o of Object.values(e.modules))if(Object.keys(o.customizations).length>0)return!0;return!1}function be(e,o,t){return t===void 0||e.modules.length>0||e.mcp.length>0?!1:o.modulesToKeep.length===0&&o.modulesToAdd.length===0&&o.mcpToKeep.length===0&&o.mcpToAdd.length===0}async function Ce(e){return e?E({message:"This will uninstall every module and delete the lockfile. Proceed?",initialValue:!1}):E({message:"Proceed?",initialValue:!0})}function De(e,o){const t=e.yes,n=o.confirmUninstall===!0;if(t&&!n)throw new c({code:"E_INVALID_INPUT",message:"Refusing to uninstall without --confirm-uninstall. Next: re-run with --confirm-uninstall to confirm, or omit --modules=/--mcp= to keep your current selection.",headline:"Refusing to uninstall without --confirm-uninstall.",todo:["Re-run with --confirm-uninstall to confirm.","Or omit --modules=/--mcp= to keep your current selection."]})}function Ne(e,o){if(e===void 0||o===void 0)return;const t=o.modules[A]!==void 0?"wrapper-repository":"existing-repository";if(e!==t)throw e!=="existing-repository"&&e!=="wrapper-repository"?new c({code:"E_INVALID_INPUT",message:`Unknown --mode value "${e}". Next: pass --mode=existing-repository or --mode=wrapper-repository.`,headline:`Unknown --mode value "${e}".`,todo:["Pass --mode=existing-repository or --mode=wrapper-repository."]}):new c({code:"E_INVALID_INPUT",message:`--mode=${e} contradicts the prior lockfile (mode: ${t}). Next: re-run without --mode= or with --mode=${t}.`,headline:`--mode=${e} contradicts the prior lockfile.`,todo:[`The lockfile records mode: ${t}.`,`Re-run without --mode= or with --mode=${t}.`]})}function Ae(e){const o=e.reverseDepConflicts.map(n=>` ${n.removed} -> still required by ${n.retainedDependents.join(", ")}`),t=e.reverseDepConflicts.map(n=>`Keep ${n.removed} or remove the modules that require it (${n.retainedDependents.join(", ")}).`);return new c({code:"E_RESOLVE",message:`Refusing to remove modules that other installed modules still require:
|
|
2
|
+
${o.join(`
|
|
3
|
+
`)}
|
|
4
|
+
Next: keep the required modules or remove their dependents too.`,headline:"Refusing to remove modules that other installed modules still require.",todo:t})}function xe(e,o){if(o&&l.warn("Empty desired state -- this will uninstall every module and delete the lockfile."),e.tagChange!==null){const t=e.tagChange.from??"(none)";l.info(`Content tag: ${t} -> ${e.tagChange.to}`)}e.modulesToAdd.length>0&&l.info(`Modules to add: ${e.modulesToAdd.join(", ")}`),e.modulesToRemove.length>0&&l.info(`Modules to remove: ${e.modulesToRemove.join(", ")}`),e.mcpToAdd.length>0&&l.info(`MCP to add: ${e.mcpToAdd.join(", ")}`),e.mcpToRemove.length>0&&l.info(`MCP to remove: ${e.mcpToRemove.join(", ")}`)}function Te(e,o){if(e.modules.length!==1||e.modules[0]!==x)return e;const t=Object.keys(o.modules).filter(n=>o.modules[n].kind!=="mcp").sort();return{...e,modules:t}}function Le(e){Ee(e,D,C)}function Ee(e,o,t){de(e,o)||O(e,o,t),t!==null&&!ce(e,t)&&O(e,o,t)}function O(e,o,t){const n=t??"unbounded";throw new c({code:"E_INVALID_INPUT",message:`--content-ref ${e} is outside the compat range [${o}, ${n}). Next: pass a tag in range, or upgrade the CLI.`,headline:`AI1st-Kit version ${e} is outside this CLI's compatibility range.`,todo:[`Pass a tag in [${o}, ${n}).`,"Or upgrade the CLI: npx @nortal/ai1st-kit-cli@latest install."]})}function $e(e,o){if(o.some(n=>n.tag===e))return;const t=o.slice(0,5).map(n=>n.tag).join(", ");throw new c({code:"E_INVALID_INPUT",message:`--content-ref ${e} is not a tag in the content repo. Next: available versions: ${t}.`,headline:`AI1st-Kit version ${e} was not found in the content repo.`,todo:[`Available versions: ${t}.`]})}function Pe(e){if(e!==void 0){if(e==="existing-repository")return"existing-repository";if(e==="wrapper-repository")return"wrapper-repository";throw new c({code:"E_INVALID_INPUT",message:`Unknown --mode value "${e}". Next: pass --mode=existing-repository or --mode=wrapper-repository.`})}}async function Me(e){const o=process.cwd(),t=b(e.targetDir??o,o);await $(t,void 0);const n=e.verbose??!1,r=M({targetDir:t,enabled:n});g(r,"info","list start");try{if(e.json){const i=await Z({targetDir:t}),s=pe.safeParse(i);if(!s.success)throw new c({code:"E_INTERNAL",message:"list --json output failed schema validation",cause:s.error});l.json(JSON.stringify(s.data,null,2))}else{const i=await z({targetDir:t});B(i)}g(r,"info","list end"),await r.flush()}catch(i){throw r.setEnabled(!0),g(r,"error","list error",{error:j(i)}),await r.flush(),i}}function S(e){return{probeAuth:e.probeAuth??se,buildResolver:e.buildResolver??ae,discoverVersions:e.discoverVersions??le,promptForTargetDir:e.promptForTargetDir??_e}}async function _e(e){return q.text({message:"Where should I install the AI1st-Kit?",placeholder:e.defaultValue,defaultValue:e.defaultValue})}async function Oe(e){const{flags:o,originalCwd:t,deps:n}=e;if(o.targetDir!==void 0)return{kind:"resolved",targetDir:b(o.targetDir,t)};if(o.yes===!0)return{kind:"resolved",targetDir:b(t,t)};const r=await n.promptForTargetDir({defaultValue:t});return typeof r!="string"?{kind:"cancelled"}:{kind:"resolved",targetDir:b(r,t)}}function g(e,o,t,n){e.append({ts:new Date().toISOString(),level:o,message:t,...n!==void 0?{data:n}:{}})}function j(e){return e instanceof c?{code:e.code,message:e.message,stack:e.stack??null}:e instanceof Error?{name:e.name,message:e.message,stack:e.stack??null}:{value:String(e)}}async function Se(){const e=K(fe(),"package.json"),o=await V(e,"utf8"),t=JSON.parse(o);if(t===null||typeof t!="object"||!("version"in t)||typeof t.version!="string")throw new c({code:"E_INTERNAL",message:'package.json is missing a string "version" field. Next: rebuild the CLI tarball.'});return t.version}export{Le as assertContentRefInRange,Ee as assertContentRefInRangeAgainst,dt as buildCliProgram,me as dispatchInstall,Me as dispatchList};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{getKeychain as a}from"../auth/keychain.js";import{probeHttps as p}from"../auth/probe-https.js";import{probeSsh as u}from"../auth/probe-ssh.js";import{CONTENT_REPO as r}from"../generated/content-ref.js";import{log as t}from"../log.js";function n(o,e){return`${o.padEnd(18)}${e}`}async function k(){const o=(process.env.GITLAB_TOKEN??"").trim();t.info(n("GITLAB_TOKEN env:",o.length>0?"set":"not set"));const[e,c]=await Promise.all([u(r),p(r)]);t.info(n("SSH:",e.ok?"ok":"fail")),t.info(n("HTTPS:",c.ok?"ok":"fail"));const i=await(await a()).getPassword(),s=i!==null?i.trim():null,l=s!==null&&s.length>0?`glpat-***...${s.slice(-4)}`:"none";t.info(n("stored PAT:",l))}async function w(){await(await a()).deletePassword(),t.info("logged out")}export{w as dispatchAuthLogout,k as dispatchAuthStatus};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{readdir as R,rm as x,rmdir as W,unlink as z}from"node:fs/promises";import{dirname as P,isAbsolute as L,join as I,relative as K,resolve as H}from"node:path";import{createClaudeCodeAdapter as U}from"../adapter/claude-code.js";import{computeStateDiff as V,isEmptyDiff as B}from"../core/state-diff.js";import{loadManifest as G}from"../core/manifest.js";import{scanManifests as J}from"../core/manifest-scanner.js";import{readLockfile as X,writeLockfile as Y}from"../core/lockfile.js";import{expandPlan as q}from"../core/planner.js";import{WRAPPER_SCAFFOLD_MODULE_ID as u,WRAPPER_SCAFFOLD_SOURCE as Q,assertWrapperPathsDisjoint as Z,assertWrapperPreconditions as ee,scaffoldWrapper as oe}from"../core/wrapper.js";import{CliError as w}from"../errors.js";import{installFiles as te}from"../handlers/files.js";import{installHooks as ne,removeHooks as ie}from"../handlers/hooks.js";import{createClaudeMcpRunner as se,installMcp as re,uninstallMcp as le,validateAllMcpCommands as ae}from"../handlers/mcp.js";import{log as n}from"../log.js";import{KIT_NAME as N,LOCKFILE_VERSION as ce}from"../schemas/lockfile.js";import{sha256File as fe}from"../util/hash.js";const _="remote-git:.",de="ai1st-kit-lock.json";async function Te(e){if(!L(e.targetDir))throw new w({code:"E_INTERNAL",message:`install: expected an absolute targetDir, got "${e.targetDir}"`});e.plan.mode==="wrapper-repository"&&e.uninstallEmptyState!==!0&&await ee(e.targetDir);const o=await e.resolver.resolve(_),i=await G(o.rootDir),s=await J(o.rootDir,i),r=q(e.plan,i),l=I(e.targetDir,de),a=await ge(l),d=U(e.targetDir);ae(r,i),e.plan.mode==="wrapper-repository"&&e.uninstallEmptyState!==!0&&Z(i);const m=[],k=[],p=[],M=[],E=[],D=[],h={},v=Object.create(null),C=e.mcpRunner??se(),$=e.stateDiff;if($!==void 0){for(const t of Object.keys(a?.modules??{})){if(t===u)continue;const c=a.modules[t];h[t]=c}for(const t of Object.keys(a?.mcp??{}))v[t]=a.mcp[t]}const g=[];if($!==void 0){const t=await pe({stateDiff:$,priorLockfile:a,config:i,targetDir:e.targetDir,adapter:d,mcpRunner:C});for(const c of t.removedModuleIds)delete h[c],delete v[c],g.push(c);for(const c of t.mcpFailures)E.push(c)}for(const t of r){const c=i.modules[t];if(c===void 0)throw new w({code:"E_INTERNAL",message:`install: planner returned unknown module "${t}"`});switch(c.kind){case"files":{const f=await te({moduleId:t,module:c,resolved:o,ownedFiles:s.byModule[t]??[],adapter:d,priorLockfileEntry:a?.modules[t],installedRef:e.plan.contentRef??null});h[t]={kind:"files",source:c.source,resolved:o.resolved,files:f.installedFiles,customizations:f.customizations},m.push(t);for(const y of f.conflicts)p.push({...y,moduleId:t});for(const y of f.resolvedConflicts)M.push({relPath:y.relPath,moduleId:t});break}case"mcp":{const f=await re({moduleId:t,module:c,adapter:d,runner:C});v[t]={registered_with:f.registeredWith},h[t]={kind:"mcp",source:_,resolved:o.resolved,files:{},customizations:{}},f.ok?m.push(t):E.push({moduleId:t,message:f.error.message});break}case"hooks":{const f=await ne({moduleId:t,module:c,resolved:o,adapter:d});h[t]={kind:"hooks",source:_,resolved:o.resolved,files:{},merges_into:c.merges_into,customizations:{}},f.ok?m.push(t):D.push({moduleId:t,message:f.error.message});break}default:{const f=c;throw new w({code:"E_INTERNAL",message:`install: unhandled module kind for "${t}": ${JSON.stringify(f)}`})}}}const F=a?.modules[u]!==void 0;if(e.plan.mode==="wrapper-repository"&&e.uninstallEmptyState!==!0){const t=await oe(e.targetDir,d,a?.modules[u]);h[u]={kind:"files",source:Q,resolved:t.resolved,files:t.installedFiles,customizations:{}},m.push(u);for(const c of t.conflicts)p.push({...c,moduleId:u})}else F&&e.uninstallEmptyState!==!0&&(h[u]=a.modules[u]);if(m.sort(),g.sort(),k.sort(),e.uninstallEmptyState===!0){if(F){const y=a.modules[u];await b(e.targetDir,u,y),g.push(u),g.sort()}await we(l);const t=!0,c=[],f=[];return{kitName:N,kitVersion:e.plan.contentRef,installedModules:m,removedModules:g,mcpModuleIds:c,hooksModuleIds:f,deferredModules:k,conflicts:p,resolvedConflicts:M,mcpFailures:E,hooksFailures:D,lockfilePath:l,lockfileDeleted:t,needsProjectInit:!1}}const S={lockfileVersion:ce,kit:{name:N,version:e.plan.contentRef},installedRef:e.plan.contentRef,modules:h,mcp:v,generated:new Date().toISOString()};await Y(l,S);const j=m.filter(t=>h[t]?.kind==="mcp").slice().sort(),A=m.filter(t=>h[t]?.kind==="hooks").slice().sort();return{kitName:N,kitVersion:e.plan.contentRef,installedModules:m,removedModules:g,mcpModuleIds:j,hooksModuleIds:A,deferredModules:k,conflicts:p,resolvedConflicts:M,mcpFailures:E,hooksFailures:D,lockfilePath:l,lockfileDeleted:!1,needsProjectInit:await ue(e.targetDir)}}const me=4;async function ue(e){const o=I(e,".ai_project_memory");try{return!await O(o,0)}catch{return!0}}async function O(e,o){if(o>me)return!1;const i=await R(e,{withFileTypes:!0});for(const s of i){if(s.isFile()&&s.name.toLowerCase().includes("constitution"))return!0;if(s.isDirectory()){const r=I(e,s.name);if(await O(r,o+1))return!0}}return!1}async function pe(e){const o=[],i=[];for(const s of[...e.stateDiff.modulesToRemove].sort()){const r=e.priorLockfile?.modules[s];r!==void 0&&(r.kind==="files"?await b(e.targetDir,s,r):r.kind==="hooks"&&r.merges_into!==void 0&&(T(s,r.merges_into),await ie({moduleId:s,adapter:e.adapter,mergesInto:r.merges_into})),o.push(s))}for(const s of[...e.stateDiff.mcpToRemove].sort()){const r=e.config.modules[s];if(r!==void 0&&r.kind==="mcp"){const l=await le({moduleId:s,module:r,runner:e.mcpRunner});l.ok||i.push({moduleId:s,message:l.error.message})}else i.push({moduleId:s,message:`MCP module ${s} is no longer declared by the kit at the resolved tag; the lockfile entry was dropped but the registration could not be removed automatically. Next: run "claude mcp list" and "claude mcp remove <name>" manually if the server is still registered.`});o.push(s)}return{removedModuleIds:o,mcpFailures:i}}async function b(e,o,i){const s=[],r=[],l=new Set;for(const a of Object.keys(i.files)){T(o,a);const d=I(e,...a.split("/"));let m;try{m=await fe(d)}catch(k){const p=k;if(p.code==="ENOENT"){s.push(a);continue}throw new w({code:"E_INTERNAL",message:`install: failed to read ${d} while reconciling ${o} (${p.code??"unknown"}). Next: check filesystem permissions and re-run.`,cause:k})}if(m===i.files[a]){try{await x(d,{force:!1})}catch(k){const p=k;if(p.code!=="ENOENT")throw new w({code:"E_INTERNAL",message:`install: failed to delete ${d} while reconciling ${o} (${p.code??"unknown"}). Next: check filesystem permissions and re-run.`,cause:k})}s.push(a),l.add(P(d))}else r.push(a)}return await he(l,e),{deleted:s,preserved:r}}function T(e,o){if(L(o))throw new w({code:"E_LOCKFILE",message:`install: lockfile entry for "${e}" contains an absolute path "${o}" that escapes the install target.`});for(const i of o.split("/"))if(i==="..")throw new w({code:"E_LOCKFILE",message:`install: lockfile entry for "${e}" contains an unsafe path "${o}" that escapes the install target.`})}async function he(e,o){const i=H(o),s=new Set;for(const r of e){let l=r;for(;!s.has(l)&&ke(l,i);){s.add(l);let a;try{a=await R(l)}catch{break}if(a.length>0)break;try{await W(l)}catch{break}const d=P(l);if(d===l)break;l=d}}}function ke(e,o){const i=K(o,e);return!(i===""||i==="."||i.startsWith("..")||L(i))}async function we(e){try{await z(e)}catch(o){const i=o;if(i.code==="ENOENT")return;throw new w({code:"E_LOCKFILE",message:`install: failed to delete lockfile at ${e} (${i.code??"unknown"}). Next: check filesystem permissions and remove the file by hand.`,cause:o})}}function Se(e){if(e.lockfileDeleted){if(n.ok("Uninstall complete. Lockfile deleted."),e.removedModules.length>0){n.info(""),n.info(`Modules removed (${e.removedModules.length}):`);for(const o of e.removedModules)n.removed(o)}return}if(n.info(`Install complete (kit ${e.kitName}@${e.kitVersion}).`),n.info(""),n.info(`Modules installed (${e.installedModules.length}):`),e.installedModules.length===0)n.info(" (none)");else for(const o of e.installedModules)n.ok(o);if(e.removedModules.length>0){n.info(""),n.info(`Modules removed (${e.removedModules.length}):`);for(const o of e.removedModules)n.removed(o)}if(e.mcpModuleIds.length>0){n.info(""),n.info(`MCP servers (${e.mcpModuleIds.length}):`);for(const o of e.mcpModuleIds)n.ok(o)}if(e.hooksModuleIds.length>0){n.info(""),n.info(`Hooks merges (${e.hooksModuleIds.length}):`);for(const o of e.hooksModuleIds)n.ok(o)}if(e.resolvedConflicts.length>0){n.info(""),n.info(`Conflicts resolved (${e.resolvedConflicts.length}):`);for(const o of e.resolvedConflicts)n.ok(o.relPath)}if(e.conflicts.length>0){n.info(""),n.info(`Conflicts pending (${e.conflicts.length}):`);for(const o of e.conflicts)n.warn(`${o.relPath} -> ${o.conflictPath}`);n.info("Next: open <file>.new alongside <file>, merge what you want into <file>, then DELETE <file>.new. Re-running install will accept your merged version as the new baseline. Your customization is then preserved across future bundle updates -- each new bundle emits a fresh <file>.new for you to re-merge.")}if(e.mcpFailures.length>0){n.info(""),n.info(`MCP failures (${e.mcpFailures.length}):`);for(const o of e.mcpFailures)n.warn(`${o.moduleId}: ${o.message}`)}if(e.hooksFailures.length>0){n.info(""),n.info(`Hooks failures (${e.hooksFailures.length}):`);for(const o of e.hooksFailures)n.warn(`${o.moduleId}: ${o.message}`)}n.info(""),n.info(`Lockfile: ${e.lockfilePath}`),e.needsProjectInit&&n.next("first-time setup -- open this project in your AI coding agent and run /ai1st-kit-project-init to initialize constitutions and project memory.","/ai1st-kit-project-init")}async function ge(e){try{return await X(e)}catch(o){if(o instanceof w&&o.code==="E_LOCKFILE"&&o.cause?.code==="ENOENT")return;throw o}}export{V as computeStateDiff,Te as install,B as isEmptyDiff,Se as printInstallSummary};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{isAbsolute as i,join as s}from"node:path";import{readLockfile as a}from"../core/lockfile.js";import{CliError as n}from"../errors.js";const r="ai1st-kit-lock.json";async function d(o){if(!i(o))throw new n({code:"E_INTERNAL",message:`lifecycle: expected an absolute targetDir, got "${o}"`});try{return await a(s(o,r))}catch(t){if(t instanceof n&&t.code==="E_LOCKFILE"&&t.cause?.code==="ENOENT")return;throw t}}async function L(o){if(!i(o))throw new n({code:"E_INTERNAL",message:`lifecycle: expected an absolute targetDir, got "${o}"`});const t=s(o,r);let c;try{c=await a(t)}catch(e){throw e instanceof n&&e.code==="E_LOCKFILE"&&e.cause?.code==="ENOENT"?new n({code:"E_INVALID_INPUT",message:`No installed kit at ${o} (no ${r}). Next: run 'ai1st-kit install' first.`,cause:e}):e}return{lockfile:c,lockfilePath:t}}function N(o,t){const c=Object.create(null);for(const e of Object.keys(o))e!==t&&(e==="__proto__"||e==="constructor"||e==="prototype"||(c[e]=o[e]));return c}export{N as filterKey,d as readPriorLockfileOrUndefined,L as requireLockfile};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{join as f}from"node:path";import{log as o}from"../log.js";import{pathExists as l}from"../util/fs.js";import{requireLockfile as m}from"./lifecycle.js";async function u(t){const{lockfile:e}=await m(t.targetDir),s=Object.keys(e.modules).sort().map(i=>({moduleId:i,kind:e.modules[i].kind})),c=Object.keys(e.mcp).sort().map(i=>({moduleId:i,registeredWith:e.mcp[i].registered_with}));return{kitName:e.kit.name,kitVersion:e.kit.version,modules:s,mcps:c}}async function h(t){const{lockfile:e}=await m(t.targetDir),s=[];for(const r of Object.values(e.modules))for(const n of Object.keys(r.files))await l(f(t.targetDir,n+".new"))&&s.push(n);s.sort();const c={};for(const[r,n]of Object.entries(e.modules))c[r]={kind:n.kind,fileCount:Object.keys(n.files).length};const i={};for(const[r,n]of Object.entries(e.mcp))i[r]={registeredWith:n.registered_with};return{listSchemaVersion:1,kit:{name:e.kit.name,version:e.kit.version},modules:c,mcp:i,status:s.length>0?"conflicts-pending":"up-to-date",conflictPaths:s}}function g(t){if(o.info(`Kit: ${t.kitName}@${t.kitVersion}`),o.info(""),o.info(`Modules (${t.modules.length}):`),t.modules.length===0)o.info(" (none)");else for(const e of t.modules)o.ok(`${e.moduleId} (${e.kind})`);if(o.info(""),o.info(`MCP servers (${t.mcps.length}):`),t.mcps.length===0)o.info(" (none)");else for(const e of t.mcps)o.ok(`${e.moduleId} (registered with ${e.registeredWith})`)}export{u as list,h as listJson,g as printListResult};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{CliError as s}from"../errors.js";function a(u){const{existingHash:i,lockfileHash:e,bundleHash:n,filePath:r,customization:f,currentRef:o,pendingMergeOnDisk:c}=u;if(i===void 0){if(e===void 0&&n!==void 0)return{kind:"install-from-bundle"};if(e!==void 0&&n!==void 0)return{kind:"restore-from-bundle"};if(e!==void 0&&n===void 0)return{kind:"cleanup-orphan"};throw new s({code:"E_INTERNAL",message:`classifier: nothing to classify for ${r}`})}if(e===void 0)return n!==void 0&&i===n?{kind:"claim-existing"}:{kind:"conflict",conflictPath:`${r}.new`};const t=i===e,d=i===n;return t&&d?{kind:"no-op"}:t&&!d?{kind:"upstream-overwrite"}:!t&&d?{kind:"user-reverted-overwrite"}:f!==void 0&&o!==null&&f.reconciledAgainst===o&&!c?{kind:"keep-customization"}:{kind:"conflict",conflictPath:`${r}.new`}}export{a as classify};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{CliError as t}from"../errors.js";function g(e){if(e!==void 0){if(/^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(e))return e;throw new t({code:"E_INVALID_INPUT",message:`--content-ref "${e}" is not a strict vX.Y.Z tag. Next: pass --content-ref=v<MAJOR>.<MINOR>.<PATCH> (no pre-release, no leading zeros).`,headline:`Invalid --content-ref value "${e}".`,todo:["Pass a strict vX.Y.Z tag, for example --content-ref=v1.0.0."]})}}function N(e){if(e.latest&&e.contentRef!==void 0)throw new t({code:"E_INVALID_INPUT",message:"--latest and --content-ref cannot both be set. Next: pick one. Use --latest to take the newest in-range version, or --content-ref=vX.Y.Z to pin a specific one.",headline:"--latest and --content-ref are mutually exclusive.",todo:["Pass --latest to take the newest in-range version, or --content-ref vX.Y.Z to pin a specific one."]})}function y(e,o){const n=[],i=f(e.mode);i===void 0&&n.push("mode");const s=r(e.modules);u(s);const a=r(e.mcp),d=r(e.constitutions),l=e.hooks??!1,c=e.yes??!1,p=e.verbose??!1;return(i??"existing-repository")==="wrapper-repository"?{plan:{mode:"wrapper-repository",modules:s,mcp:a,constitutions:d,hooks:l,yes:c,verbose:p,contentRef:o},missingFields:n}:{plan:{mode:"existing-repository",modules:s,mcp:a,constitutions:d,hooks:l,yes:c,verbose:p,contentRef:o},missingFields:n}}function f(e){if(e!==void 0){if(e==="existing-repository")return"existing-repository";if(e==="wrapper-repository")return"wrapper-repository";throw new t({code:"E_INVALID_INPUT",message:`Unknown --mode value "${e}". Next: pass --mode=existing-repository or --mode=wrapper-repository.`})}}const m="all";function r(e){return e===void 0?[]:e.split(",").map(o=>o.trim()).filter(o=>o.length>0)}function u(e){if(!(e.length<=1)&&e.includes(m))throw new t({code:"E_INVALID_INPUT",message:"--modules=all cannot be combined with other module IDs. Next: pass --modules=all alone, or pass an explicit comma-separated list of module IDs.",headline:"--modules=all cannot be combined with other module IDs.",todo:["Pass --modules=all alone, or pass an explicit comma-separated list of module IDs."]})}export{m as MODULES_ALL_SENTINEL,N as assertLatestContentRefMutex,y as buildInstallPlanFromFlags,g as parseContentRefOrThrow,r as parseCsv,u as validateModulesCsv};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{readFile as h,writeFile as p}from"node:fs/promises";import{CliError as a}from"../errors.js";import{InstalledRefSchema as m,LOCKFILE_VERSION as l,LockfileReaderSchema as w,LockfileSchema as E}from"../schemas/lockfile.js";import{canonicalize as g}from"../util/canonical-json.js";import{atomicRename as k}from"../util/fs.js";function O(e,o){if(e===null||typeof e!="object"||Array.isArray(e))return e;const t=e;if(o===1){const{profile:i,...n}=t;return{...n,installedRef:null,modules:u(n.modules),lockfileVersion:l}}if(o===2){const{profile:i,...n}=t;return{...n,modules:u(n.modules),lockfileVersion:l}}if(o===3)return{...t,modules:u(t.modules),lockfileVersion:l};if(o===4){const i=typeof t.installedRef=="string"?t.installedRef:null;return{...t,modules:b(t.modules,i),lockfileVersion:l}}return t}function u(e){if(e===null||typeof e!="object"||Array.isArray(e))return e;const o={};for(const[t,i]of Object.entries(e)){if(i===null||typeof i!="object"||Array.isArray(i)){o[t]=i;continue}const n=i,{pendingConflicts:r,...s}=n;o[t]=Object.hasOwn(s,"customizations")?s:{...s,customizations:{}}}return o}function b(e,o){if(e===null||typeof e!="object"||Array.isArray(e))return e;const t={};for(const[i,n]of Object.entries(e)){if(n===null||typeof n!="object"||Array.isArray(n)){t[i]=n;continue}const r=n,{pendingConflicts:s,customizations:c,...d}=r,f=d.files,y=f!==null&&typeof f=="object"&&!Array.isArray(f)?f:{},L=v(s,y,o);t[i]={...d,customizations:L}}return t}function v(e,o,t){if(e===null||typeof e!="object"||Array.isArray(e))return{};const i={};for(const[n,r]of Object.entries(e)){const s=o[n];if(s===void 0)continue;const c=C(r,t);c!==void 0&&(i[n]={modifiedHash:s,reconciledAgainst:c})}return i}function C(e,o){if(typeof e=="string"&&m.safeParse(e).success)return e;if(o!==null&&m.safeParse(o).success)return o}async function F(e){let o;try{o=await h(e,{encoding:"utf8"})}catch(s){const c=s;throw c.code==="ENOENT"?new a({code:"E_LOCKFILE",message:`No lockfile at ${e}. Next: run 'ai1st-kit install' first.`,cause:s}):new a({code:"E_LOCKFILE",message:`Failed to read lockfile at ${e} (${c.code??"unknown"}). Next: check filesystem permissions.`,cause:s})}let t;try{t=JSON.parse(o)}catch(s){throw new a({code:"E_LOCKFILE",message:`Lockfile at ${e} is not valid JSON: ${s.message}. Next: move the file aside and re-run install; the classifier will re-claim existing files.`,cause:s})}const i=j(t,e);if(i>l)throw new a({code:"E_LOCKFILE",message:`Lockfile at ${e} has version ${i}, but this CLI supports up to version ${l}. Next: upgrade the CLI: 'npx @nortal/ai1st-kit-cli@latest <command>'.`});const n=O(t,i),r=w.safeParse(n);if(!r.success){const s=r.error.issues[0],c=s?`${s.message} at ${s.path.join(".")||"<root>"}`:"schema validation failed";throw new a({code:"E_LOCKFILE",message:`Lockfile at ${e} failed schema validation: ${c}. Next: move the file aside and re-run install.`,cause:r.error})}return r.data}async function z(e,o){const t=E.safeParse(o);if(!t.success){const s=t.error.issues[0],c=s?`${s.message} at ${s.path.join(".")||"<root>"}`:"schema validation failed";throw new a({code:"E_LOCKFILE",message:`writeLockfile: input failed schema validation: ${c}. Next: this is a CLI bug; re-run with --verbose and report the cause.`,cause:t.error})}const i=$({...t.data,lockfileVersion:l}),n=g(i),r=`${e}.tmp`;try{await p(r,n,{encoding:"utf8"})}catch(s){const c=s;throw new a({code:"E_LOCKFILE",message:`writeLockfile: failed to write ${r} (${c.code??"unknown"}). Next: check filesystem permissions and free space.`,cause:s})}await k(r,e,{errorCode:"E_LOCKFILE"})}function $(e){const o={};for(const[t,i]of Object.entries(e.modules))if(i.customizations&&Object.keys(i.customizations).length===0){const{customizations:n,...r}=i;o[t]=r}else o[t]=i;return{...e,modules:o}}function j(e,o){if(typeof e!="object"||e===null||Array.isArray(e))throw new a({code:"E_LOCKFILE",message:`Lockfile at ${o} is not a JSON object at the top level. Next: move the file aside and re-run install.`});const t=e.lockfileVersion;if(typeof t!="number")throw new a({code:"E_LOCKFILE",message:`Lockfile at ${o} is missing a numeric 'lockfileVersion' field. Next: move the file aside and re-run install.`});if(!Number.isInteger(t))throw new a({code:"E_LOCKFILE",message:`Lockfile at ${o} has a non-integer 'lockfileVersion' value (${t}). Next: move the file aside and re-run install.`});return t}export{O as applyLockfileDefaults,F as readLockfile,z as writeLockfile};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{readdir as p,readFile as I}from"node:fs/promises";import m from"node:path";import f from"node:path/posix";import u from"picomatch";import{parse as w}from"yaml";import{CliError as d}from"../errors.js";import{SkillFrontmatterSchema as y,parseOrThrow as N}from"../schemas/index.js";async function x(s,r){const a=new Map;for(const[e,n]of Object.entries(r.modules))n.kind==="files"&&a.set(e,u(n.paths,{dot:!0}));const t={},i={};for await(const e of h(s,s)){m.basename(e.absPath)==="SKILL.md"&&await g(e.absPath,e.relPosix);const n=[];for(const[o,l]of a)l(e.relPosix)&&n.push(o);if(n.length>1){const o=[...n].sort();throw new d({code:"E_INVALID_INPUT",message:`kit.config.yaml: file "${e.relPosix}" is claimed by both module "${o[0]}" and module "${o[1]}"${o.length>2?` (and also: ${o.slice(2).map(l=>`"${l}"`).join(", ")})`:""}`})}if(n.length===1){const o=n[0];t[e.relPosix]=o,(i[o]??=[]).push(e.relPosix)}}const c={};for(const e of Object.keys(i).sort())c[e]=i[e].slice().sort();return{owners:t,byModule:c}}async function*h(s,r){let a;try{a=await p(s,{withFileTypes:!0})}catch(t){const i=t?.message??"readdir failed",c=m.relative(r,s),e=c===""?s:c.split(m.sep).join(f.sep);throw new d({code:"E_INVALID_INPUT",message:`${e}: ${i}`,cause:t})}for(const t of a)if(!t.isSymbolicLink()){if(t.isDirectory()){if(t.name===".git")continue;yield*h(m.join(s,t.name),r);continue}if(t.isFile()){const i=m.join(s,t.name),e=m.relative(r,i).split(m.sep).join(f.sep);yield{absPath:i,relPosix:e}}}}async function g(s,r){let a;try{a=await I(s,"utf8")}catch(o){const l=o?.message??"read failed";throw new d({code:"E_INVALID_INPUT",message:`${r}: ${l}`,cause:o})}a.charCodeAt(0)===65279&&(a=a.slice(1));const t=a.match(/^---\r?\n(?:([\s\S]*?)\r?\n)?---/);if(!t)throw new d({code:"E_INVALID_INPUT",message:`${r}: SKILL.md missing YAML frontmatter (expected leading ---/---)`});const i=t[1]??"";let c;try{c=w(i)}catch(o){const l=o?.message??"parse failed";throw new d({code:"E_INVALID_INPUT",message:`${r}: YAML parse error in frontmatter: ${l}`,cause:o})}const e=N(y,c,{code:"E_INVALID_INPUT",schemaName:"SkillFrontmatterSchema"}),n=m.basename(m.dirname(s));if(e.name!==n)throw new d({code:"E_INVALID_INPUT",message:`${r}: SKILL.md frontmatter "name: ${e.name}" does not match folder basename "${n}"`})}export{x as scanManifests};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{readFile as c}from"node:fs/promises";import l from"node:path";import{parse as f,YAMLParseError as d}from"yaml";import{CliError as a}from"../errors.js";import{KitConfigSchema as u,parseOrThrow as I}from"../schemas/index.js";async function _(r){const n=l.join(r,"kit.config.yaml");let s;try{s=await c(n,"utf8")}catch(e){const i=e?.message??"read failed";throw new a({code:"E_INVALID_INPUT",message:`kit.config.yaml: ${i}`,cause:e})}let o;try{o=f(s)}catch(e){const i=e instanceof d&&e.linePos?` at line ${e.linePos[0].line}`:"",m=e?.message??"parse failed";throw new a({code:"E_INVALID_INPUT",message:`kit.config.yaml: YAML parse error${i}: ${m}`,cause:e})}if(o==null)throw new a({code:"E_INVALID_INPUT",message:"kit.config.yaml: empty or null root"});const t=I(u,o,{code:"E_INVALID_INPUT",schemaName:"KitConfigSchema"});return p(t),t}function p(r){const n=new Set(Object.keys(r.modules));for(const[s,o]of Object.entries(r.modules)){const t=o.requires??[];for(const e of t)if(!n.has(e))throw new a({code:"E_INVALID_INPUT",message:`kit.config.yaml: module "${s}" requires unknown module "${e}"`})}}export{_ as loadManifest};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{CliError as r}from"../errors.js";function h(o,n){const t=new Set([...o.modules,...o.mcp,...o.constitutions]);return p(t,n)}function p(o,n){for(const t of o)if(!Object.hasOwn(n.modules,t))throw new r({code:"E_INVALID_INPUT",message:`kit.config.yaml: unknown module "${t}"`});return d(o,n)}function d(o,n){const t=new Map,u=[],c=[];function l(e){const i=t.get(e);if(i==="black")return;if(i==="gray"){const s=c.indexOf(e),m=[...c.slice(s),e].join(" -> ");throw new r({code:"E_INVALID_INPUT",message:`kit.config.yaml: requires cycle detected: ${m}`})}if(t.set(e,"gray"),c.push(e),!Object.hasOwn(n.modules,e)){const s=c.length>1?c[c.length-2]:e;throw new r({code:"E_INVALID_INPUT",message:`kit.config.yaml: module "${s}" requires unknown module "${e}"`})}const a=(n.modules[e].requires??[]).slice().sort();for(const s of a)l(s);c.pop(),t.set(e,"black"),u.push(e)}const f=[...o].sort();for(const e of f)l(e);return u}export{h as expandPlan,p as expandRequiresClosure};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import*as d from"@clack/prompts";import{CliError as f}from"../errors.js";import{WRAPPER_SCAFFOLD_MODULE_ID as C}from"./wrapper.js";const E=12,R=["A wrapper repo is an umbrella folder placed next to your project repos.","It holds the AI1st-Kit (skills, agents, hooks, MCP wiring) once, and every","sibling project inherits the same setup -- kit updates land in one place,","and no kit files pollute a project repo's git history.","","Pick project-repo only when the kit must live inside a single project","(for example, a public repo whose contributors expect the kit committed)."].join(`
|
|
2
|
+
`);async function S(e){return L(e,g())}async function L(e,t){if(e.versions.length===0)throw new f({code:"E_INTERNAL",message:"runInteractivePrompts: empty version list reached the picker"});const n=await A(e,t);if(n==="cancelled")return{kind:"cancelled"};const i=await O(e,t);if(i==="cancelled")return{kind:"cancelled"};const l=x(e.manifest);if(l.length===0)throw new f({code:"E_INVALID_INPUT",message:"This kit declares no non-MCP modules, so the interactive installer has nothing to offer. Next: ask the content repo owner to declare at least one `files` / `hooks` / `constitutions` module, or pin a content tag (--content-ref=vX.Y.Z) that predates the regression.",headline:"The selected AI1st-Kit version declares no installable modules.",todo:["Pin a different content tag with --content-ref=vX.Y.Z.","Or ask the content-repo owner to publish a kit that declares at least one non-MCP module."]});const a=b(e.priorLockfile,"non-mcp"),s=b(e.priorLockfile,"mcp"),o=I(a,l),p=e.priorLockfile!==void 0,c=await t.multiselect({message:"Modules to install?",options:l,required:!p,maxItems:E,...o.length>0?{initialValues:o}:{}});if(t.isCancel(c))return{kind:"cancelled"};const u=c,m=D(e.manifest);let h=[];if(m.length>0){const r=I(s,m),y=await t.multiselect({message:"MCP servers?",options:m,required:!1,...r.length>0?{initialValues:r}:{}});if(t.isCancel(y))return{kind:"cancelled"};h=y}const w=j(e.manifest);let k=[];if(w.length>0){const r=await t.multiselect({message:"Constitutions (optional)?",options:w,required:!1});if(t.isCancel(r))return{kind:"cancelled"};k=r}const _=Object.values(e.manifest.modules).some(r=>r?.kind==="hooks");let v=!1;if(_){const r=await t.confirm({message:"Install hooks?",initialValue:!1});if(t.isCancel(r))return{kind:"cancelled"};v=r}return n==="wrapper-repository"?{kind:"plan",plan:{mode:"wrapper-repository",modules:u,mcp:h,constitutions:k,hooks:v,yes:!1,verbose:e.verbose,contentRef:i}}:{kind:"plan",plan:{mode:"existing-repository",modules:u,mcp:h,constitutions:k,hooks:v,yes:!1,verbose:e.verbose,contentRef:i}}}async function O(e,t){if(e.preselectedContentRef!==void 0)return e.preselectedContentRef;const n=e.priorLockfile?.installedRef??null,i=e.versions[0].tag,l=e.versions.map((o,p)=>{const c=p===0,u=n!==null&&o.tag===n;return c&&u?{value:o.tag,label:`${o.tag} (latest, current)`}:c?{value:o.tag,label:`${o.tag} (latest)`}:u?{value:o.tag,label:`${o.tag} (current)`}:{value:o.tag,label:o.tag}}),a=n!==null&&e.versions.some(o=>o.tag===n)?n:i,s=await t.select({message:"Which AI1st-Kit version would you like to install?",options:l,initialValue:a});return t.isCancel(s)?"cancelled":s}async function A(e,t){if(e.seedMode!==void 0)return e.seedMode;if(e.priorLockfile!==void 0)return N(e.priorLockfile);d.log.message(R);const n=await t.select({message:"Install mode?",options:[{value:"wrapper-repository",label:"wrapper-repo (recommended -- creates an umbrella repository around project repos)"},{value:"existing-repository",label:"project-repo (install into the current project repo)"}]});return t.isCancel(n)?"cancelled":n}function N(e){return e.modules[C]!==void 0?"wrapper-repository":"existing-repository"}function I(e,t){const n=new Set(t.map(i=>i.value));return e.filter(i=>n.has(i))}function b(e,t){if(e===void 0)return[];const n=[];for(const i of Object.keys(e.modules)){if(i===C)continue;const l=e.modules[i];(t==="mcp"?l.kind==="mcp":l.kind!=="mcp")&&n.push(i)}return n.sort()}function g(){return{select:d.select,multiselect:d.multiselect,confirm:d.confirm,isCancel:d.isCancel}}const M={files:0,constitutions:1,hooks:2};function x(e){const t=Object.keys(e.modules).filter(n=>e.modules[n]?.kind!=="mcp");return t.sort((n,i)=>{const l=M[e.modules[n]?.kind??""]??99,a=M[e.modules[i]?.kind??""]??99;return l!==a?l-a:n.localeCompare(i)}),t.map(n=>({value:n,label:n}))}function D(e){return Object.keys(e.modules).filter(t=>e.modules[t]?.kind==="mcp").sort().map(t=>({value:t,label:t}))}function j(e){return[]}async function W(e){return T(e,g())}async function T(e,t){if(e.versions.length===0)throw new f({code:"E_INTERNAL",message:"runUpdatePrompts: empty version list reached the picker"});const n=typeof e.installedRef=="string"?e.installedRef:null,i=n===null?"No installed version recorded (v1 lockfile). Update to which version?":`Currently installed: ${n}. Update to which version?`,l=e.versions[0].tag,a=e.versions.map((o,p)=>{const c=p===0,u=n!==null&&o.tag===n;return c&&u?{value:o.tag,label:`${o.tag} (latest, current)`}:c?{value:o.tag,label:`${o.tag} (latest)`}:u?{value:o.tag,label:`${o.tag} (current)`}:{value:o.tag,label:o.tag}});a.push({value:P,label:"Cancel"});const s=await t.select({message:i,options:a,initialValue:l});if(t.isCancel(s))return{kind:"cancelled"};if(s===P)return{kind:"cancelled"};if(typeof s!="string")throw new f({code:"E_INTERNAL",message:`runUpdatePrompts: picker returned non-string value (typeof=${typeof s})`});return{kind:"picked",tag:s}}const P="__cancel__";async function q(e){return U(e,g())}async function U(e,t){const n=await t.confirm({message:e.message,initialValue:e.initialValue});return t.isCancel(n)?"cancelled":n===!0?"confirmed":"denied"}export{q as confirmInteractive,U as confirmInteractiveWithDeps,S as runInteractivePrompts,L as runInteractivePromptsWithDeps,W as runUpdatePrompts,T as runUpdatePromptsWithDeps};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{WRAPPER_SCAFFOLD_MODULE_ID as h}from"./wrapper.js";function a(e){const s=R(e.priorLockfile),r=new Set(e.desiredModules),t=new Set(e.desiredMcp),o=f(s.nonMcp,r),c=f(s.mcp,t),n=S(o.toRemove,o.toKeep,o.toAdd,e.kitConfig),i=e.priorLockfile?.installedRef??null,d=i===e.desiredContentRef?null:{from:i,to:e.desiredContentRef};return{modulesToAdd:o.toAdd,modulesToRemove:o.toRemove,modulesToKeep:o.toKeep,mcpToAdd:c.toAdd,mcpToRemove:c.toRemove,mcpToKeep:c.toKeep,tagChange:d,reverseDepConflicts:n}}function A(e){return e.modulesToAdd.length===0&&e.modulesToRemove.length===0&&e.mcpToAdd.length===0&&e.mcpToRemove.length===0&&e.tagChange===null&&e.reverseDepConflicts.length===0}function R(e){if(e===void 0)return{nonMcp:[],mcp:[]};const s=[],r=[];for(const t of Object.keys(e.modules)){if(t===h)continue;e.modules[t].kind==="mcp"?r.push(t):s.push(t)}return{nonMcp:s,mcp:r}}function f(e,s){const r=new Set(e),t=[],o=[],c=[];for(const n of s)r.has(n)?c.push(n):t.push(n);for(const n of e)s.has(n)||o.push(n);return t.sort(),o.sort(),c.sort(),{toAdd:t,toRemove:o,toKeep:c}}function S(e,s,r,t){if(e.length===0)return[];const o=new Set(e),c=[...s,...r],n=new Map;for(const d of c){const m=t.modules[d]?.requires??[];for(const u of m){if(!o.has(u))continue;const l=n.get(u)??[];l.push(d),n.set(u,l)}}const i=[];for(const d of[...e].sort()){const p=n.get(d);p===void 0||p.length===0||i.push({removed:d,retainedDependents:[...new Set(p)].sort()})}return i}export{a as computeStateDiff,A as isEmptyDiff};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{stat as s}from"node:fs/promises";import{homedir as n}from"node:os";import{dirname as d,resolve as a}from"node:path";import{CliError as i}from"../errors.js";function w(e,t){const r=e.trim();if(r.length===0)throw new i({code:"E_INVALID_INPUT",message:"Target directory cannot be empty. Next: pass --target-dir=<path> or press Enter at the prompt to accept the current working directory.",headline:"Target directory cannot be empty.",todo:["Pass --target-dir=<path> with an explicit path.","Or press Enter at the prompt to accept the current working directory."]});const o=c(r);return a(t,o)}function c(e){return e==="~"?n():e.startsWith("~/")?a(n(),e.slice(2)):e}async function N(e,t){await p(e);const r=await h(e);if(r===void 0){if(t==="wrapper-repository")return;throw new i({code:"E_INVALID_INPUT",message:`Target directory ${e} does not exist. Next: create it first (e.g. mkdir -p ${e}) or pass --target-dir=<existing-dir>.`,headline:`Target directory ${e} does not exist.`,todo:[`Create it first (e.g. mkdir -p ${e}).`,"Or pass --target-dir=<existing-dir>."]})}if(!r.isDirectory())throw new i({code:"E_INVALID_INPUT",message:`Target path ${e} is a file, not a directory. Next: pass --target-dir=<dir>.`,headline:`Target path ${e} is a file, not a directory.`,todo:["Pass --target-dir=<dir> pointing at a directory."]})}async function p(e){const t=d(e);try{await s(t)}catch(r){const o=r;throw o.code==="ENOENT"?new i({code:"E_INVALID_INPUT",message:`Parent directory ${t} does not exist. Next: create the parent directory or pass a target whose parent already exists.`,headline:`Parent directory ${t} does not exist.`,todo:["Create the parent directory first.","Or pass a target whose parent already exists."]}):new i({code:"E_INTERNAL",message:`assertTargetDirPreconditions: stat ${t} failed (${o.code??"unknown"}). Next: check filesystem permissions on the parent directory.`,cause:r})}}async function h(e){try{return await s(e)}catch(t){const r=t;if(r.code==="ENOENT")return;throw new i({code:"E_INTERNAL",message:`assertTargetDirPreconditions: stat ${e} failed (${r.code??"unknown"}). Next: check filesystem permissions on the target directory.`,cause:t})}}export{N as assertTargetDirPreconditions,w as resolveTargetDir};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{readFile as m,stat as k}from"node:fs/promises";import{join as i}from"node:path";import R from"node:path/posix";import _ from"picomatch";import{classify as L}from"../core/classifier.js";import{CliError as o}from"../errors.js";import{writeManagedFile as g}from"../util/fs.js";import{sha256Buffer as T,sha256File as v}from"../util/hash.js";import{getPackageRoot as w}from"../util/package-root.js";const $="Wrapper-repository mode requires an empty folder. The current folder contains a git repo. Pick 'existing-repository' instead, or run from an empty folder.",E="wrapper-scaffold",q="cli-bundled:wrapper-templates",W=[".gitignore","project-repos/.gitkeep","README.md"],O=[{bundleFile:"gitignore.template",targetPath:".gitignore"},{bundleFile:"project-repos.gitkeep",targetPath:"project-repos/.gitkeep"},{bundleFile:"README.md.template",targetPath:"README.md"}];async function J(r){const e=i(r,".git");try{await k(e)}catch(t){const a=t;if(a.code==="ENOENT")return;throw new o({code:"E_INTERNAL",message:`assertWrapperPreconditions: stat ${e} failed (${a.code??"unknown"}). Next: confirm the target folder is readable.`,cause:t})}throw new o({code:"E_INVALID_INPUT",message:$})}function Y(r){if(Object.hasOwn(r.modules,E))throw new o({code:"E_INVALID_INPUT",message:`kit.config.yaml: module id "${E}" is reserved for the wrapper-mode scaffold. Next: rename the colliding module in kit.config.yaml.`});for(const[e,t]of Object.entries(r.modules))if(t.kind==="files")for(const a of t.paths){const s=_(a,{dot:!0});for(const c of W)if(s(c))throw new o({code:"E_INVALID_INPUT",message:`kit.config.yaml: module "${e}" claims wrapper-template target "${c}" via paths glob "${a}". The wrapper-mode scaffold owns these three paths exclusively. Next: tighten the module's paths: glob to exclude wrapper artifacts, or remove the colliding glob.`})}}async function K(r,e,t){const a=Object.create(null),s=[],c=i(w(),"wrapper-templates");for(const{bundleFile:b,targetPath:p}of O){const P=i(c,b),f=p.split("/"),d=i(r,...f),l=R.join(...f),u=await C(P,p),h=T(u),y=await j(d),A=t!==void 0&&Object.hasOwn(t.files,l)?t.files[l]:void 0,I=L({existingHash:y,lockfileHash:A,bundleHash:h,filePath:d,customization:void 0,currentRef:null,pendingMergeOnDisk:!1}),n=await x(I,{relPath:l,targetAbs:d,bundleBytes:u,bundleHash:h});n.skip||(n.recordHash!==void 0&&(a[l]=n.recordHash),n.conflict!==void 0&&s.push(n.conflict))}const N=await F();return{installedFiles:a,conflicts:s,resolved:N}}async function x(r,e){switch(r.kind){case"no-op":case"claim-existing":return{recordHash:e.bundleHash};case"install-from-bundle":case"restore-from-bundle":case"upstream-overwrite":case"user-reverted-overwrite":return await g(e.targetAbs,e.targetAbs,e.bundleBytes),{recordHash:e.bundleHash};case"conflict":{const t=`${e.targetAbs}.new`;return await g(e.targetAbs,t,e.bundleBytes),{recordHash:e.bundleHash,conflict:{relPath:e.relPath,conflictPath:`${e.relPath}.new`}}}case"cleanup-orphan":return{skip:!0};case"keep-customization":throw new o({code:"E_INTERNAL",message:`scaffoldWrapper: classifier emitted "${r.kind}" for "${e.relPath}", but the wrapper scaffold never records customizations. Next: this is a CLI bug; re-run with --verbose and report the cause.`});default:{const t=r;throw new o({code:"E_INTERNAL",message:`scaffoldWrapper: unhandled classifier action "${r.kind}".`})}}}async function C(r,e){try{return await m(r)}catch(t){const a=t;throw new o({code:"E_INTERNAL",message:`scaffoldWrapper: failed to read wrapper template ${r} for target "${e}" (${a.code??"unknown"}). Next: this is a CLI packaging bug; reinstall "@nortal/ai1st-kit-cli" and retry.`,cause:t})}}async function j(r){try{return await v(r)}catch(e){const t=e;if(t.code==="ENOENT")return;throw t.code==="EISDIR"?new o({code:"E_CONFLICT",message:`scaffoldWrapper: a directory exists at the wrapper artifact path ${r}. Next: remove or move the directory before re-running install.`,cause:e}):new o({code:"E_CONFLICT",message:`scaffoldWrapper: failed to hash existing wrapper artifact ${r} (${t.code??"unknown"}). Next: check filesystem permissions on the wrapper folder.`,cause:e})}}async function F(){const r=i(w(),"package.json");let e;try{e=await m(r,"utf8")}catch(s){throw new o({code:"E_INTERNAL",message:`scaffoldWrapper: failed to read CLI package.json at ${r}. Next: reinstall "@nortal/ai1st-kit-cli" and retry.`,cause:s})}let t;try{t=JSON.parse(e)}catch(s){throw new o({code:"E_INTERNAL",message:`scaffoldWrapper: failed to parse CLI package.json at ${r}. Next: reinstall "@nortal/ai1st-kit-cli" and retry.`,cause:s})}if(t===null||typeof t!="object"||!("version"in t)||typeof t.version!="string")throw new o({code:"E_INTERNAL",message:'scaffoldWrapper: CLI package.json is missing a string "version" field. Next: rebuild the CLI tarball.'});const a=t.version.trim();if(a.length===0)throw new o({code:"E_INTERNAL",message:'scaffoldWrapper: CLI package.json "version" field is empty after trimming. Next: rebuild the CLI tarball.'});return`cli-bundled:${a}`}export{E as WRAPPER_SCAFFOLD_MODULE_ID,q as WRAPPER_SCAFFOLD_SOURCE,W as WRAPPER_SCAFFOLD_TARGETS,Y as assertWrapperPathsDisjoint,J as assertWrapperPreconditions,K as scaffoldWrapper};
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
class n extends Error{code;cause;headline;todo;runbook;constructor(e){super(e.message),this.name="CliError",this.code=e.code,e.cause!==void 0&&(this.cause=e.cause),e.headline!==void 0&&(this.headline=e.headline),e.todo!==void 0&&(this.todo=e.todo),e.runbook!==void 0&&(this.runbook=e.runbook)}}const i={E_RESOLVE:1,E_CONFLICT:1,E_MCP:1,E_INVALID_INPUT:2,E_LOCKFILE:1,E_AUTH:1,E_INTERNAL:1};function d(o,e){return o instanceof n?o.code==="E_CONFLICT"&&e?3:i[o.code]:1}export{n as CliError,i as EXIT_CODE_MAP,d as mapErrorToExitCode};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const t="git@gitlab.nortal.com:ai-first/public/n-ai1st-kit.git",o="v3.0.0",i=null,N="2026-05-11T00:00:00Z";export{N as BOUND_AT,t as CONTENT_REPO,i as MAX_CONTENT_REF,o as MIN_CONTENT_REF};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const n={banner_wide:`\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
|
|
2
|
+
\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D
|
|
3
|
+
\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551
|
|
4
|
+
\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551
|
|
5
|
+
\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551
|
|
6
|
+
\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D
|
|
7
|
+
`,banner_narrow:`\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557
|
|
8
|
+
\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551
|
|
9
|
+
\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551
|
|
10
|
+
\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551
|
|
11
|
+
\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
|
|
12
|
+
\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
13
|
+
|
|
14
|
+
\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
|
|
15
|
+
\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D
|
|
16
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551
|
|
17
|
+
\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551
|
|
18
|
+
\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551
|
|
19
|
+
\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D
|
|
20
|
+
`,tagline:"AI1st-Kit for Nortal -- skills, agents, hooks, and guardrails.",sections:[{title:"Useful links",items:[{label:"AI1st Source",value:"https://gitlab.nortal.com/ai-first/public/n-ai1st-kit"},{label:"Toolbox",value:"https://nortal.atlassian.net/wiki/spaces/ENG/pages/727908606/AI-First+Toolbox"}]}],footer:null};export{n as WELCOME_CONTENT};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{readFile as y}from"node:fs/promises";import{join as w}from"node:path";import{classify as v}from"../core/classifier.js";import{CliError as n}from"../errors.js";import{pathExists as I,writeManagedFile as b}from"../util/fs.js";import{sha256Buffer as k,sha256File as L}from"../util/hash.js";async function j(t){const e=Object.create(null),s=[],r=[],l=Object.create(null),c=[],g=t.adapter.paths().project,d=t.installedRef;for(const o of t.ownedFiles){const u=o.split("/"),N=w(t.resolved.rootDir,...u),a=w(g,...u),h=await $(N,o),f=k(h),m=await z(a),E=t.priorLockfileEntry?.files[o],p=t.priorLockfileEntry?.customizations?.[o],H=await I(`${a}.new`),F=v({existingHash:m,lockfileHash:E,bundleHash:f,filePath:a,customization:p,currentRef:d,pendingMergeOnDisk:H}),i=await R(F,{relPath:o,targetAbs:a,bundleBytes:h,bundleHash:f,existingHash:m,priorCustomization:p,currentRef:d});if(i.skip){c.push(o);continue}i.recordHash!==void 0&&(e[o]=i.recordHash),i.conflict!==void 0&&s.push(i.conflict),i.customization!==void 0&&(l[o]=i.customization),i.resolved!==void 0&&r.push(i.resolved)}return{moduleId:t.moduleId,installedFiles:e,conflicts:s,resolvedConflicts:r,customizations:l,skippedFiles:c}}async function R(t,e){switch(t.kind){case"no-op":case"claim-existing":return{recordHash:e.bundleHash};case"install-from-bundle":case"restore-from-bundle":case"upstream-overwrite":case"user-reverted-overwrite":return await b(e.targetAbs,e.targetAbs,e.bundleBytes),{recordHash:e.bundleHash};case"conflict":{if(e.existingHash===void 0)throw new n({code:"E_INTERNAL",message:`installFiles: classifier emitted conflict for "${e.relPath}" but no on-disk bytes were hashed. Next: this is a CLI bug; re-run with --verbose and report the cause.`});if(e.currentRef===null)throw new n({code:"E_INTERNAL",message:`installFiles: classifier emitted conflict for "${e.relPath}" but the install has no installedRef to record against. Next: this is a CLI bug; re-run with --verbose and report the cause.`});const s=`${e.targetAbs}.new`;return await b(e.targetAbs,s,e.bundleBytes),{recordHash:e.bundleHash,conflict:{relPath:e.relPath,conflictPath:`${e.relPath}.new`},customization:{modifiedHash:e.existingHash,reconciledAgainst:e.currentRef}}}case"keep-customization":{if(e.existingHash===void 0)throw new n({code:"E_INTERNAL",message:`installFiles: classifier emitted keep-customization for "${e.relPath}" but no on-disk bytes were hashed. Next: this is a CLI bug; re-run with --verbose and report the cause.`});if(e.currentRef===null)throw new n({code:"E_INTERNAL",message:`installFiles: classifier emitted keep-customization for "${e.relPath}" but the install has no installedRef to record against. Next: this is a CLI bug; re-run with --verbose and report the cause.`});const s={recordHash:e.bundleHash,customization:{modifiedHash:e.existingHash,reconciledAgainst:e.currentRef}};return e.priorCustomization!==void 0&&e.priorCustomization.modifiedHash!==e.existingHash&&(s.resolved={relPath:e.relPath}),s}case"cleanup-orphan":return{skip:!0};default:{const s=t;throw new n({code:"E_INTERNAL",message:`installFiles: unhandled classifier action "${t.kind}".`})}}}async function $(t,e){try{return await y(t)}catch(s){const r=s;throw r.code==="ENOENT"?new n({code:"E_INTERNAL",message:`installFiles: planner scheduled "${e}" but it is missing in the resolved bundle at ${t}. Next: this is a CLI bug; re-run with --verbose and report the cause.`,cause:s}):new n({code:"E_INTERNAL",message:`installFiles: failed to read bundle file ${t} (${r.code??"unknown"}). Next: re-run the install; if the failure persists, report the cause.`,cause:s})}}async function z(t){try{return await L(t)}catch(e){const s=e;if(s.code==="ENOENT")return;throw s.code==="EISDIR"?new n({code:"E_CONFLICT",message:`installFiles: a directory exists at the managed path ${t}. Next: remove or move the directory before installing.`,cause:e}):new n({code:"E_CONFLICT",message:`installFiles: failed to hash existing target ${t} (${s.code??"unknown"}). Next: check filesystem permissions on the install target.`,cause:e})}}export{j as installFiles};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{mkdir as g,readFile as f,writeFile as x}from"node:fs/promises";import{dirname as E,join as m}from"node:path";import{z as l}from"zod";import{CliError as c}from"../errors.js";import{atomicRename as v}from"../util/fs.js";import{sha256Buffer as k}from"../util/hash.js";const p="@nortal/ai1st-kit-cli/hooks",h=l.record(l.string().min(1),l.array(l.record(l.string().min(1),l.unknown())));async function R(e){N("merges_into",e.module.merges_into,e.moduleId),N("payload_path",e.module.payload_path,e.moduleId);const t=m(e.adapter.paths().project,e.module.merges_into),o=m(e.resolved.rootDir,e.module.payload_path),r=await O(o,e.moduleId),s=await $(t);if(!s.ok)return{ok:!1,moduleId:e.moduleId,mergedWith:e.adapter.id,warnings:[],error:s.error};const n=b(s.value,r),i=s.bytes,a=w(n);return i!==void 0&&y(i,a)?{ok:!0,moduleId:e.moduleId,action:"noop",mergedWith:e.adapter.id,warnings:[]}:(await _(t,a),{ok:!0,moduleId:e.moduleId,action:"merged",mergedWith:e.adapter.id,warnings:[]})}async function D(e){N("mergesInto",e.mergesInto,e.moduleId);const t=m(e.adapter.paths().project,e.mergesInto);let o;try{o=await f(t,"utf8")}catch(a){const d=a;if(d.code==="ENOENT")return{ok:!0,action:"noop"};throw new c({code:"E_INTERNAL",message:`removeHooks: failed to read ${t} (${d.code??"unknown"}). Next: check filesystem permissions.`,cause:a})}let r;try{r=JSON.parse(o)}catch(a){throw new c({code:"E_INVALID_INPUT",message:`removeHooks: ${t} is not valid JSON. Next: hand-fix or delete the file and re-run.`,cause:a})}const s=h.safeParse(r);if(!s.success)throw new c({code:"E_INVALID_INPUT",message:`removeHooks: ${t} is not a valid Claude Code hooks file shape. Next: hand-fix or delete the file.`,cause:s.error});const n=Object.create(null);for(const[a,d]of u(s.data))n[a]=d.filter(I=>I._owner!==p);const i=w(n);return y(o,i)?{ok:!0,action:"noop"}:(await _(t,i),{ok:!0,action:"removed"})}function N(e,t,o){const r=()=>{throw new c({code:"E_INVALID_INPUT",message:`hooks module ${o}: ${e} "${t}" must be a project-relative path without ".." segments. Next: edit kit.config.yaml.`})};(t.startsWith("/")||t.startsWith("\\"))&&r(),/^[A-Za-z]:/.test(t)&&r();const s=t.split(/[\\/]/);for(const n of s)n===".."&&r()}async function O(e,t){let o;try{o=await f(e,"utf8")}catch(n){throw new c({code:"E_INVALID_INPUT",message:`hooks module ${t}: failed to read payload "${e}". Next: re-run the content publish or fix the bundle JSON.`,cause:n})}let r;try{r=JSON.parse(o)}catch(n){throw new c({code:"E_INVALID_INPUT",message:`hooks module ${t}: payload "${e}" is not valid JSON. Next: re-run the content publish or fix the bundle JSON.`,cause:n})}const s=h.safeParse(r);if(!s.success){const n=s.error.issues[0],i=n&&n.path.length>0?n.path.join("."):"<root>",a=n?n.message:"shape mismatch";throw new c({code:"E_INVALID_INPUT",message:`hooks module ${t}: payload "${e}" shape invalid at ${i} - ${a}. Next: fix the bundle JSON.`,cause:s.error})}return s.data}async function $(e){let t;try{t=await f(e,"utf8")}catch(s){const n=s;return n.code==="ENOENT"?{ok:!0,value:{},bytes:void 0}:{ok:!1,error:{code:"E_INTERNAL",message:`installHooks: failed to read ${e} (${n.code??"unknown"}). Next: check filesystem permissions.`}}}let o;try{o=JSON.parse(t)}catch{return{ok:!1,error:{code:"E_INTERNAL",message:`installHooks: ${e} exists but is not valid JSON. Next: hand-fix the file or delete it and re-run.`}}}const r=h.safeParse(o);return r.success?{ok:!0,value:r.data,bytes:t}:{ok:!1,error:{code:"E_INVALID_INPUT",message:`installHooks: ${e} does not match the Claude Code hooks file shape. Next: hand-fix or delete it and re-run.`}}}function b(e,t){const o=Object.create(null);for(const[r,s]of u(e))o[r]=[...s];for(const[r,s]of u(t)){const n=o[r],i=s.map(A);if(n===void 0)o[r]=i;else{const a=n.filter(d=>d._owner!==p);o[r]=[...a,...i]}}return o}function A(e){const t=Object.create(null);t._owner=p;for(const[o,r]of u(e))o!=="_owner"&&(t[o]=r);return t}function u(e){const t=[];for(const o of Object.keys(e))o==="__proto__"||o==="constructor"||o==="prototype"||t.push([o,e[o]]);return t}function w(e){const t=Object.create(null),o=Object.keys(e).filter(r=>r!=="__proto__"&&r!=="constructor"&&r!=="prototype");for(const r of o.sort())t[r]=e[r];return`${JSON.stringify(t,null,2)}
|
|
2
|
+
`}function y(e,t){return e.length!==t.length?!1:k(e)===k(t)}async function _(e,t){await g(E(e),{recursive:!0});const o=`${e}.tmp`;await x(o,t),await v(o,e,{errorCode:"E_INTERNAL"})}export{p as HOOKS_OWNER_MARKER,h as HooksFileSchema,R as installHooks,D as removeHooks};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{CliError as i}from"../errors.js";import{resolveExecutable as f,runCommand as d,SAFE_ARGV_TOKEN as p}from"../util/spawn.js";const u=200;async function k(e){const t=a(e.moduleId,e.module.install.command),n=e.adapter.id;if(!await e.runner.isAvailable())return{ok:!1,moduleId:e.moduleId,registeredWith:n,error:{code:"E_MCP",message:`claude binary not found; MCP module ${e.moduleId} skipped. Next: install Claude Code (https://docs.claude.com/en/docs/claude-code) and re-run \`npx @nortal/ai1st-kit-cli update\` to register the deferred MCP servers.`}};const o=l(t),r=await e.runner.listRegistrations();if(o!==void 0&&r.ok&&r.names.has(o))return{ok:!0,moduleId:e.moduleId,action:"noop",registeredWith:n};const s=await e.runner.register(t);if(s.exitCode===0)return{ok:!0,moduleId:e.moduleId,action:"registered",registeredWith:n};const m=c(s.stderr).slice(0,u)||"<no stderr>";return{ok:!1,moduleId:e.moduleId,registeredWith:n,error:{code:"E_MCP",message:`claude mcp add failed for module ${e.moduleId} (exit ${s.exitCode}): ${m}. Next: run "claude mcp list" to inspect current state and "claude mcp add ..." manually to surface the underlying error.`}}}function w(){const e=f("claude");return{async isAvailable(){try{return await d(e,["--version"]),!0}catch(t){if(t instanceof i&&t.code==="E_INTERNAL"){const n=t.cause;if(n?.code==="ENOENT"||n?.code==="EACCES")return!1}throw t}},async listRegistrations(){const t=await d(e,["mcp","list"]);return t.exitCode!==0?{ok:!1,exitCode:t.exitCode,stderr:t.stderr}:{ok:!0,names:I(t.stdout)}},async register(t){return d(e,t)},async unregister(t){return d(e,["mcp","remove",t])}}}async function N(e){const t=a(e.moduleId,e.module.install.command),n=l(t);if(n===void 0)return{ok:!0,moduleId:e.moduleId,action:"noop",name:void 0};if(!await e.runner.isAvailable())return{ok:!0,moduleId:e.moduleId,action:"noop",name:n};const o=await e.runner.unregister(n);if(o.exitCode===0)return{ok:!0,moduleId:e.moduleId,action:"unregistered",name:n};const r=c(o.stderr).slice(0,u)||"<no stderr>";return{ok:!1,moduleId:e.moduleId,name:n,error:{code:"E_MCP",message:`claude mcp remove failed for module ${e.moduleId} (exit ${o.exitCode}): ${r}.`}}}function v(e,t){for(const n of e){const o=t.modules[n];o!==void 0&&o.kind==="mcp"&&a(n,o.install.command)}}function a(e,t){if(t.length<2)throw new i({code:"E_INVALID_INPUT",message:`mcp module ${e}: install.command requires at least 'claude' plus one argument; got ${t.length} token(s). Next: edit kit.config.yaml so the command names a registration (e.g. ['claude','mcp','add','<name>',...]).`});if(t[0]!=="claude")throw new i({code:"E_INVALID_INPUT",message:`mcp module ${e}: install.command[0] must be "claude", got "${t[0]}"; manifest argv allowlist forbids non-claude executables. Next: edit kit.config.yaml to use the canonical claude argv.`});for(let n=1;n<t.length;n+=1){const o=t[n];if(!p.test(o))throw new i({code:"E_INVALID_INPUT",message:`mcp module ${e}: install.command[${n}] = "${o}" contains characters outside [A-Za-z0-9._@/:=+-]; manifest argv allowlist rejects it. Next: edit kit.config.yaml to remove the offending characters.`})}return t.slice(1)}function l(e){const t=e.indexOf("add");if(!(t<0))for(let n=t+1;n<e.length;n+=1){const o=e[n];if(!o.startsWith("-"))return o}}function I(e){const t=new Set;for(const n of e.split(`
|
|
2
|
+
`)){const o=n.trim();if(o===""||x(o))continue;const r=o.split(/\s+/)[0];r===void 0||r===""||t.add(r)}return t}function x(e){const t=e.toLowerCase();return t.startsWith("no mcp servers")||t.startsWith("available mcp servers")||t.startsWith("mcp servers:")}function c(e){for(const t of e.split(`
|
|
3
|
+
`)){const n=t.trim();if(n!=="")return n}return""}export{w as createClaudeMcpRunner,l as extractRegistrationName,k as installMcp,I as parseClaudeMcpListOutput,N as uninstallMcp,v as validateAllMcpCommands};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
{const e=Number(process.versions.node.split(".")[0]);(!Number.isFinite(e)||e<20)&&(process.stderr.write(`E_INVALID_INPUT: Node ${process.versions.node||"(unknown)"} is too old; ai1st-kit requires Node >= 20. Next: upgrade Node and re-run.
|
|
3
|
+
`),process.exit(2))}import{mkdir as a,writeFile as c}from"node:fs/promises";import{join as n}from"node:path";import{buildCliProgram as u}from"./cli.js";import{CliError as d,mapErrorToExitCode as l}from"./errors.js";import{formatCliError as p,log as o}from"./log.js";const g="https://gitlab.nortal.com/ai-first/public/n-ai1st-kit-cli/-/issues",m=process.argv.includes("--yes"),f=process.argv.includes("--json");try{await(await u()).parseAsync(process.argv),process.exit(0)}catch(e){const t=l(e,m);if(e instanceof d)if(f)process.stderr.write(w(e)+`
|
|
4
|
+
`);else{const r=process.stderr.isTTY===!0;process.stderr.write(p(e,{tty:r}))}else{o.error(`E_INTERNAL: ${String(e)}`);const r=await h(e);r!==void 0&&o.error(`Log written to: ${r}`),o.error(`Report: file an issue at ${g} with the log attached`)}process.exit(t)}function w(e){const t={code:e.code,message:e.message},r=e.runbook??b(e.message);return r!==void 0&&(t.runbook=r),JSON.stringify(t)}function b(e){const t=". Runbook: ",r=e.indexOf(t);if(r!==-1)return e.slice(r+t.length).trimEnd()}async function h(e){try{const t=n(process.cwd(),"installer-logs");await a(t,{recursive:!0});const r=new Date().toISOString().replace(/[:.]/g,"-"),s=n(t,`install-${r}.log`),i={ts:new Date().toISOString(),level:"error",message:"unhandled crash at top level",data:N(e)};return await c(s,JSON.stringify(i)+`
|
|
5
|
+
`,"utf8"),s}catch{return}}function N(e){return e instanceof Error?{name:e.name,message:e.message,stack:e.stack??null}:{value:String(e)}}
|
package/dist/log.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import t,{createColors as b}from"picocolors";const c=b(!0);let s=!1;const x=`${t.gray("\u2502")} `,g=t.gray("\u2502");function y(o){s=o}function r(){return s?x:""}function i(){return s?g:""}const m={info:o=>{console.log(`${r()}${t.gray(o)}`)},ok:o=>{console.log(`${r()}${t.green(`\u2713 ${o}`)}`)},removed:o=>{console.log(`${r()}${t.red(`\u2717 ${o}`)}`)},warn:o=>{console.log(`${r()}${t.yellow(`\u26A0 ${o}`)}`)},error:o=>{console.error(t.red(`\u2717 ${o}`))},next:(o,n)=>{const e=t.bold(t.cyan("\u2192 Next: ")),d=n!==void 0&&o.includes(n)?o.split(n).map(l=>t.cyan(l)).join(t.bold(t.green(n))):t.cyan(o);console.log(i()),console.log(`${r()}${e}${d}`),console.log(i())},json:o=>{console.log(o)},plain:o=>{console.log(o)},debug:o=>{}};function h(o,n){if(!n.tty)return`${o.code}: ${o.message}
|
|
2
|
+
`;const e=o.headline!==void 0?{headline:o.headline,todo:o.todo===void 0?[]:[...o.todo],runbook:o.runbook}:p(o.message);return k(o.code,e)}const u=". Next: ",f=". Runbook: ";function p(o){const n=o.indexOf(u);if(n===-1)return{headline:o.trimEnd(),todo:[],runbook:void 0};const e=o.slice(0,n+1).trimEnd(),d=o.slice(n+u.length),l=d.indexOf(f);if(l===-1)return{headline:e,todo:[d.trimEnd()],runbook:void 0};const $=d.slice(0,l+1).trimEnd(),a=d.slice(l+f.length).trimEnd();return{headline:e,todo:[$],runbook:a}}function k(o,n){const e=[];if(e.push(`${c.red("x")} ${n.headline}`),n.todo.length>0){e.push("");for(const d of n.todo)e.push(` ${d}`)}return n.runbook!==void 0&&(e.push(""),e.push(c.dim(` Runbook: ${n.runbook}`))),e.push(""),e.push(c.dim(` (${o})`)),e.join(`
|
|
3
|
+
`)+`
|
|
4
|
+
`}export{h as formatCliError,m as log,y as setLogGuide};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{CliError as i}from"../errors.js";import{RemoteGitResolver as n}from"./remote-git.js";function l(r,o){const t=new n(r,o);return{async resolve(e){if(e==="remote-git:.")return t.resolve(e);throw new i({code:"E_RESOLVE",message:`Source spec "${e}" is not supported in v1.0. Next: only 'remote-git:.' is supported.`})}}}export{l as createResolver};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{spawn as m}from"node:child_process";import{CliError as g}from"../errors.js";import{compareTagSemver as p,parseTagSemver as d,tagSemverGte as h,tagSemverLt as w}from"../util/semver.js";const f="https://gitlab.nortal.com/ai-first/public/n-ai1st-kit/-/wikis/install-runbook";async function L(n){const i=n.auth.preferredUrlFor(n.contentRepo),a={...process.env};n.auth.applyToGitEnv(a);const l=n.gitLsRemoteTagsImpl??S;let s;try{s=await l(i,a)}catch(e){throw new g({code:"E_RESOLVE",message:`Failed to list content tags: ${e.message}. Next: check network/auth. Runbook: ${f}`,cause:e,headline:"Couldn't list versions in the AI1st-Kit content repo.",todo:[`Check your network and your GitLab access (try \`git ls-remote ${n.contentRepo}\`).`],runbook:f})}const r=R(s),o=[];for(const[e,t]of r)d(e)!==null&&h(e,n.minContentRef)&&(n.maxContentRef!==null&&!w(e,n.maxContentRef)||o.push({tag:e,sha:t}));return o.sort((e,t)=>p(t.tag,e.tag)),o}function R(n){const i=new Map,a=new Set;for(const l of n.split(/\r?\n/)){const s=l.trim();if(s.length===0)continue;const r=s.split(/\s+/);if(r.length<2)continue;const o=r[0],e=r[1];if(!/^[0-9a-f]{40}$/.test(o)||!e.startsWith("refs/tags/"))continue;let t=e.slice(10);t.endsWith("^{}")?(t=t.slice(0,-3),i.set(t,o),a.add(t)):a.has(t)||i.set(t,o)}return i}async function S(n,i){return new Promise((a,l)=>{let s="",r="",o=!1;const e=m("git",["ls-remote","--tags",n],{shell:!1,env:i,signal:AbortSignal.timeout(1e4)});e.stdout?.on("data",t=>{s+=t.toString("utf8")}),e.stderr?.on("data",t=>{r+=t.toString("utf8")}),e.on("error",t=>{o||(o=!0,l(new Error(`git ls-remote --tags failed to spawn: ${t.message}`)))}),e.on("exit",t=>{if(o)return;if(o=!0,t===0){a(s);return}if(t===null){l(new Error("git ls-remote --tags timed out after 10s"));return}const c=r.trim(),u=new Error(`git ls-remote --tags exited ${t}: ${c.slice(-200)}`);c.length>0&&(u.cause=new Error(c)),l(u)})})}export{L as discoverVersions,S as gitLsRemoteTags};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{spawn as g}from"node:child_process";import{stat as $,rm as w}from"node:fs/promises";import{homedir as b}from"node:os";import{join as m}from"node:path";import{CONTENT_REPO as y}from"../generated/content-ref.js";import{CliError as f}from"../errors.js";import{log as v}from"../log.js";class T{auth;contentRepo;contentRef;cacheRoot;revParseHeadFn;constructor(c,i,t={}){this.auth=c,this.contentRepo=t.contentRepo??y,this.contentRef=i,this.cacheRoot=t.cacheRoot??m(b(),".cache","nortal-ai1st"),this.revParseHeadFn=t.gitRevParseHeadImpl??S}async resolve(c){if(c!=="remote-git:.")throw new f({code:"E_RESOLVE",message:`Source spec "${c}" is not supported in v1.0. Next: only 'remote-git:.' is supported.`});const i=this.auth.preferredUrlFor(this.contentRepo),t={...process.env};this.auth.applyToGitEnv(t);let o=await R(i,`refs/tags/${this.contentRef}^{}`,t);if(o||(o=await R(i,`refs/tags/${this.contentRef}`,t)),!o)throw new f({code:"E_RESOLVE",message:`Tag '${this.contentRef}' not found in content repo '${this.contentRepo}'. Next: check the bound content ref or your access. Runbook: https://gitlab.nortal.com/ai-first/public/n-ai1st-kit/-/wikis/install-runbook`,headline:`Tag '${this.contentRef}' not found in content repo '${this.contentRepo}'.`,todo:["Check the bound content ref or your access."],runbook:"https://gitlab.nortal.com/ai-first/public/n-ai1st-kit/-/wikis/install-runbook"});const e=m(this.cacheRoot,o),a=m(e,"kit.config.yaml");if(!await d(a)){await w(e,{recursive:!0,force:!0});try{await E(i,this.contentRef,e,t)}catch(s){throw new f({code:"E_RESOLVE",message:`Failed to clone content repo: ${s.message}. Next: check network/auth. Runbook: https://gitlab.nortal.com/ai-first/public/n-ai1st-kit/-/wikis/install-runbook`,cause:s})}if(await this.verifyClonedSha(e,o,t),!await d(a)){v.warn(`Cache at '${e}' is missing kit.config.yaml after clone \u2014 re-cloning once.`),await w(e,{recursive:!0,force:!0});try{await E(i,this.contentRef,e,t)}catch(s){throw new f({code:"E_RESOLVE",message:`Failed to clone content repo (retry): ${s.message}. Runbook: https://gitlab.nortal.com/ai-first/public/n-ai1st-kit/-/wikis/install-runbook`,cause:s})}if(await this.verifyClonedSha(e,o,t),!await d(a))throw new f({code:"E_RESOLVE",message:`Cloned content repo at '${e}' is missing kit.config.yaml. Runbook: https://gitlab.nortal.com/ai-first/public/n-ai1st-kit/-/wikis/install-runbook`})}}return{rootDir:e,source:"remote-git:.",resolved:`${this.contentRepo}#${o}`}}async verifyClonedSha(c,i,t){const o=await this.revParseHeadFn(c,t);if(o!==i)throw new f({code:"E_RESOLVE",message:`Cloned tag '${this.contentRef}' resolved to commit ${o} but ls-remote reported ${i}. The tag may have moved during the clone. Next: re-run; if persistent, contact the content-repo owner.`})}}async function d(l){try{return(await $(l)).isFile()}catch{return!1}}async function R(l,c,i){return new Promise((t,o)=>{let e="",a="";const n=["ls-remote",l,c];let s=!1;const u=g("git",n,{shell:!1,env:i,signal:AbortSignal.timeout(1e4)});u.stdout?.on("data",r=>{e+=r.toString("utf8")}),u.stderr?.on("data",r=>{a+=r.toString("utf8")}),u.on("error",r=>{s||(s=!0,o(new Error(`git ls-remote failed to spawn: ${r.message}`)))}),u.on("exit",r=>{if(s)return;if(s=!0,r!==0){o(r===null?new Error("git ls-remote timed out after 10s"):new Error(`git ls-remote exited ${r}: ${a.trim().slice(-200)}`));return}const p=e.split(/\r?\n/).find(k=>k.length>0);if(!p){t(null);return}const h=p.split(/\s+/)[0];h&&/^[0-9a-f]{40}$/.test(h)?t(h):t(null)})})}async function E(l,c,i,t){return new Promise((o,e)=>{let a="",n=!1;const u=g("git",["clone","--depth=1","--branch",c,l,i],{shell:!1,env:t,signal:AbortSignal.timeout(12e4)});u.stderr?.on("data",r=>{a+=r.toString("utf8")}),u.on("error",r=>{n||(n=!0,e(new Error(`git clone failed to spawn: ${r.message}`)))}),u.on("exit",r=>{if(!n){if(n=!0,r===0){o();return}if(r===null){e(new Error("git clone timed out after 120s"));return}e(new Error(`git clone exited ${r}: ${a.trim().slice(-200)}`))}})})}async function S(l,c){return new Promise((i,t)=>{let o="",e="";const a=g("git",["-C",l,"rev-parse","HEAD"],{shell:!1,env:c});a.stdout?.on("data",n=>{o+=n.toString("utf8")}),a.stderr?.on("data",n=>{e+=n.toString("utf8")}),a.on("error",n=>{t(new Error(`git rev-parse failed to spawn: ${n.message}`))}),a.on("exit",n=>{if(n!==0){t(new Error(`git rev-parse exited ${n}: ${e.trim().slice(-200)}`));return}const s=o.trim();if(!/^[0-9a-f]{40}$/.test(s)){t(new Error(`git rev-parse produced unexpected output: ${s.slice(0,80)}`));return}i(s)})})}export{T as RemoteGitResolver,E as gitCloneShallow,R as gitLsRemoteTag,S as gitRevParseHead};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{CliError as m}from"../errors.js";export*from"./install-plan.js";export*from"./kit-config.js";export*from"./list-output.js";export*from"./lockfile.js";export*from"./manifests.js";export*from"./skill-frontmatter.js";function f(s,t,e){const o=s.safeParse(t);if(o.success)return o.data;const r=o.error.issues[0],a=r&&r.path.length>0?r.path.join("."):"<root>",n=r?r.message:"unknown validation error",c=`${e.schemaName}: ${a} ${n}`;throw new m({code:e.code,message:c,cause:o.error})}export{f as parseOrThrow};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{z as o}from"zod";const e={modules:o.array(o.string().min(1)),mcp:o.array(o.string().min(1)),constitutions:o.array(o.string().min(1)),hooks:o.boolean(),yes:o.boolean(),verbose:o.boolean(),contentRef:o.string().regex(/^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/)},t=o.object({mode:o.literal("existing-repository"),...e}).strict(),r=o.object({mode:o.literal("wrapper-repository"),...e}).strict(),i=o.discriminatedUnion("mode",[t,r]);export{t as ExistingRepositoryPlanSchema,i as InstallPlanSchema,r as WrapperRepositoryPlanSchema};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{z as i}from"zod";const t=i.object({kind:i.literal("files"),source:i.string().min(1),paths:i.array(i.string().min(1)).min(1),requires:i.array(i.string().min(1)).optional()}).strict(),r=i.object({kind:i.literal("mcp"),install:i.object({command:i.array(i.string().min(1)).min(1)}).strict(),requires:i.array(i.string().min(1)).optional()}).strict(),o=i.object({kind:i.literal("hooks"),merges_into:i.string().min(1),payload_path:i.string().min(1),requires:i.array(i.string().min(1)).optional()}).strict(),n=i.discriminatedUnion("kind",[t,r,o]),s=i.object({$schema:i.string().optional(),apiVersion:i.literal("ai1st-kit/v1"),modules:i.record(i.string(),n)}).strict();export{t as FilesModuleSchema,o as HooksModuleSchema,s as KitConfigSchema,r as McpModuleSchema,n as ModuleSchema};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{z as t}from"zod";const e=1,i=t.object({kind:t.enum(["files","mcp","hooks"]),fileCount:t.number().int().nonnegative()}).strict(),n=t.object({registeredWith:t.string().min(1)}).strict(),s=t.object({listSchemaVersion:t.literal(e),kit:t.object({name:t.string().min(1),version:t.string().min(1)}).strict(),modules:t.record(t.string(),i),mcp:t.record(t.string(),n),status:t.enum(["up-to-date","conflicts-pending"]),conflictPaths:t.array(t.string())}).strict();export{e as LIST_SCHEMA_VERSION,n as ListOutputMcpSchema,i as ListOutputModuleSchema,s as ListOutputSchema};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{z as e}from"zod";const o=5,g="@nortal/ai1st-kit-cli",s=e.string().regex(/^sha256-[0-9a-f]{64}$/),r=e.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/),n=e.string().regex(/^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/),l=e.object({modifiedHash:s,reconciledAgainst:n}).strict(),c=e.object({kind:e.enum(["files","mcp","hooks"]),source:e.string().min(1),resolved:e.string().min(1),files:e.record(e.string(),s),merges_into:e.string().min(1).optional(),customizations:e.record(e.string().min(1),l).default(()=>({}))}).strict().refine(t=>Object.keys(t.customizations).every(i=>i in t.files),t=>{const i=Object.keys(t.customizations).find(m=>!(m in t.files));return{message:`customizations[${i??"<unknown>"}] has no matching files[${i??"<unknown>"}] entry; every customization must be a customization OF a managed file`,path:["customizations",i??""]}}),a=e.object({registered_with:e.string().min(1)}).strict(),f=e.object({lockfileVersion:e.literal(o),kit:e.object({name:e.string().min(1),version:e.string().min(1)}).strict(),installedRef:n,modules:e.record(e.string(),c),mcp:e.record(e.string(),a),generated:r}).strict(),u=e.object({lockfileVersion:e.literal(o),kit:e.object({name:e.string().min(1),version:e.string().min(1)}).strict(),installedRef:e.union([n,e.null()]),modules:e.record(e.string(),c),mcp:e.record(e.string(),a),generated:r}).strict();export{l as FileCustomizationSchema,n as InstalledRefSchema,r as IsoUtcTimestampSchema,g as KIT_NAME,o as LOCKFILE_VERSION,a as LockfileMcpEntrySchema,c as LockfileModuleSchema,u as LockfileReaderSchema,f as LockfileSchema,s as Sha256HashSchema};
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{z as t}from"zod";const i=t.object({name:t.string().min(1),description:t.string().min(1)}).passthrough();export{i as SkillFrontmatterSchema};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import c from"picocolors";import{WELCOME_CONTENT as l}from"../generated/welcome-content.js";const a=1,d=1,B=3,N=80;function E(n){return m(n).reduce((s,o)=>o.length>s?o.length:s,0)}const L=E(l.banner_wide),b=E(l.banner_narrow),I=L+2*(a+d),O=b+2*(a+d),D=I+B+2;function V(n){const o=(n?.columns??process.stdout.columns??N)>=D,t=o?I:O,e=o?l.banner_wide:l.banner_narrow,u=t-2*(d+a),h=o?L:b,A=H(l.tagline,u),r=[];r.push(f()),r.push(i(T(t))),r.push(i(_(t)));for(const p of m(e)){const g=p.padEnd(h);r.push(i(W(c.green(g),h,t)))}r.push(i(_(t)));for(const p of A){const g=w(p,u);r.push(i(W(g,u,t)))}r.push(i(_(t))),r.push(i($(t))),r.push(f()),r.push(i(R(t)));for(const p of l.sections)r.push(f()),r.push(...C(p));return r.push(f()),r.push(i(R(t))),r.join(`
|
|
2
|
+
`)+`
|
|
3
|
+
`}function f(){return c.gray("\u2502")}function i(n){return`${c.gray("\u2502")} ${n}`}function T(n){return c.green("\u256D"+"\u2500".repeat(n-2)+"\u256E")}function $(n){return c.green("\u2570"+"\u2500".repeat(n-2)+"\u256F")}function R(n){return c.green("\u2500".repeat(n))}function _(n){return W("",0,n)}function W(n,s,o){const t=o-2*d,e=" ".repeat(a),u=t-2*a-s,h=u>0?" ".repeat(u):"";return`${c.green("\u2502")}${e}${n}${h}${e}${c.green("\u2502")}`}function w(n,s){if(n.length>=s)return n;const o=s-n.length,t=Math.floor(o/2),e=o-t;return" ".repeat(t)+n+" ".repeat(e)}function H(n,s){const o=n.split(" "),t=[];let e="";for(const u of o)e.length===0?e=u:e.length+1+u.length<=s?e+=" "+u:(t.push(e),e=u);return e.length>0&&t.push(e),t}function m(n){return n.split(`
|
|
4
|
+
`).filter(s=>s.length>0)}function C(n){const s=n.items.reduce((t,e)=>e.label.length>t?e.label.length:t,0),o=[];o.push(i(c.bold(n.title)));for(const t of n.items)o.push(i(` ${t.label.padEnd(s)} ${c.dim(t.value)}`));return o}export{V as welcomeBanner};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{open as s,readFile as c,unlink as d}from"node:fs/promises";import{join as l}from"node:path";import{CliError as a}from"../errors.js";const u=".ai1st-kit.lock";async function w(i){const t=l(i.targetDir,u),e=JSON.stringify({pid:process.pid,started:new Date().toISOString(),command:i.command});try{const r=await s(t,"wx");try{await r.writeFile(e+`
|
|
2
|
+
`,{encoding:"utf8"})}finally{await r.close()}}catch(r){const o=r;if(o.code==="EEXIST"){const n=await h(t);throw new a({code:"E_RESOLVE",message:`Another ai1st-kit run is in progress in this directory (${n}). Next: wait for it to finish; if you believe the lock is stale, delete ${t} and re-run.`,headline:"Another ai1st-kit run is already in progress in this directory.",todo:[`Wait for the other run (${n}) to finish.`,`If the lock is stale, delete ${t} and re-run.`],cause:r})}throw new a({code:"E_INTERNAL",message:`Failed to acquire advisory lock at ${t} (${o.code??"unknown"}). Next: check filesystem permissions.`,cause:r})}return{async release(){try{await d(t)}catch{}}}}async function h(i){try{const t=(await c(i,"utf8")).trim(),e=JSON.parse(t);return typeof e.pid=="number"&&typeof e.started=="string"?`pid=${e.pid}, started=${e.started}`:"unknown holder"}catch{return"unknown holder"}}export{w as acquireAdvisoryLock};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{CliError as c}from"../errors.js";const N=new Set(["__proto__","constructor","prototype"]);function y(e){const n=l(e,[],new WeakSet);return JSON.stringify(n,null,2)+`
|
|
2
|
+
`}function l(e,n,a){if(e===null)return null;if(e instanceof Date){if(Number.isNaN(e.getTime()))throw new c({code:"E_INTERNAL",message:`canonicalize: invalid Date (NaN epoch) at ${i(n)}.`});const s=e.toISOString();if(!s.endsWith("Z"))throw new c({code:"E_INTERNAL",message:`canonicalize: Date at ${i(n)} produced non-Z timestamp '${s}'.`});return s}const r=typeof e;if(r==="undefined"||r==="function"||r==="symbol"||r==="bigint")throw new c({code:"E_INTERNAL",message:`canonicalize: unsupported value of type '${r}' at ${i(n)}.`});if(r==="number"&&!Number.isFinite(e))throw new c({code:"E_INTERNAL",message:`canonicalize: non-finite number at ${i(n)}.`});if(r!=="object")return e;const t=e;if(a.has(t))throw new c({code:"E_INTERNAL",message:`canonicalize: circular reference detected at ${i(n)}.`});a.add(t);try{if(Array.isArray(t))return t.map((o,f)=>l(o,[...n,String(f)],a));const s=Object.getPrototypeOf(t);if(s!==Object.prototype&&s!==null)throw new c({code:"E_INTERNAL",message:`canonicalize: unsupported non-plain object (${m(t)}) at ${i(n)}.`});const d=Object.keys(t).sort(),u=Object.create(null);for(const o of d){if(N.has(o))throw new c({code:"E_INTERNAL",message:`canonicalize: forbidden prototype-pollution key '${o}' at ${i([...n,o])}.`});const f=t[o];u[o]=l(f,[...n,o],a)}return u}finally{a.delete(t)}}function i(e){return e.length===0?"<root>":e.join(".")}function m(e){const n=e.constructor?.name;return n&&n!=="Object"?n:Object.prototype.toString.call(e)}export{y as canonicalize};
|
package/dist/util/fs.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{access as l,mkdir as h,rename as w,writeFile as f}from"node:fs/promises";import{constants as u}from"node:fs";import{dirname as p}from"node:path";import{CliError as d}from"../errors.js";async function R(e){try{return await l(e,u.F_OK),!0}catch{return!1}}const y=new Set(["EBUSY","EACCES","EPERM"]);async function E(e,t,s={}){const m=s.retries??5,i=s.baseDelayMs??50,c=s.errorCode??"E_LOCKFILE";let o=0,r;for(;;)try{await w(e,t);return}catch(n){const a=n;if(r=a,!y.has(a.code??""))throw new d({code:c,message:`atomicRename: failed to rename ${e} -> ${t} (${a.code??"unknown"}). Next: confirm the source exists and the destination directory is writable.`,cause:n});if(o>=m)throw new d({code:c,message:`atomicRename: failed to rename ${e} -> ${t} after ${m} retries (last error: ${a.code??"unknown"}). Next: check that no other process holds the file (Windows AV is a common cause); retry the install.`,cause:r});await $(i*2**o),o+=1}}function $(e){return new Promise(t=>setTimeout(t,e))}async function g(e,t,s,m={}){const i=m.errorCode??"E_CONFLICT",c=p(t);try{await h(c,{recursive:!0})}catch(r){const n=r,a=n.code==="ENOTDIR"?"a non-directory exists in the parent path":"check filesystem permissions on the install target";throw new d({code:i,message:`writeManagedFile: failed to create parent directory ${c} for ${e} (${n.code??"unknown"}). Next: ${a}.`,cause:r})}const o=`${t}.tmp`;try{await f(o,s)}catch(r){const n=r;throw new d({code:i,message:`writeManagedFile: failed to write ${o} for ${e} (${n.code??"unknown"}). Next: check filesystem permissions and free space on the install target.`,cause:r})}await E(o,t,{errorCode:i})}export{E as atomicRename,R as pathExists,g as writeManagedFile};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createHash as r}from"node:crypto";import{readFile as o}from"node:fs/promises";import{isAbsolute as i}from"node:path";import{CliError as s}from"../errors.js";function a(e){return`sha256-${r("sha256").update(e).digest("hex")}`}async function c(e){if(!i(e))throw new s({code:"E_INTERNAL",message:`sha256File: expected an absolute path, got ${e}`});const t=await o(e);return a(t)}export{a as sha256Buffer,c as sha256File};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{dirname as r,resolve as e}from"node:path";import{fileURLToPath as t}from"node:url";function a(){const o=r(t(import.meta.url));return e(o,"..","..")}export{a as getPackageRoot};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{CliError as m}from"../errors.js";const u=/^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;function o(e){const r=u.exec(e);return r===null?null:{major:Number.parseInt(r[1],10),minor:Number.parseInt(r[2],10),patch:Number.parseInt(r[3],10)}}function i(e,r){const t=o(e),n=o(r);if(t===null||n===null){const a=t===null?e:r;throw new m({code:"E_INVALID_INPUT",message:`compareTagSemver: invalid semver tag "${a}"; expected vX.Y.Z.`,headline:`Internal: invalid semver tag "${a}".`,todo:["This is a bug. Please report it."]})}return t.major!==n.major?t.major<n.major?-1:1:t.minor!==n.minor?t.minor<n.minor?-1:1:t.patch!==n.patch?t.patch<n.patch?-1:1:0}function p(e,r){return i(e,r)>=0}function l(e,r){return i(e,r)<0}export{i as compareTagSemver,o as parseTagSemver,p as tagSemverGte,l as tagSemverLt};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{spawn as E}from"node:child_process";import{CliError as c}from"../errors.js";const h=/^[A-Za-z0-9._@/:=+-]+$/;function C(e){return process.platform==="win32"?`${e}.cmd`:e}const _=3e4,A=2e3,f=8*1024*1024;function p(e,i){if(e.length>=f)return e;const s=f-e.length;return i.length<=s?e+i:e+i.slice(0,s)}function I(e,i,s={}){const m=s.timeoutMs??_;return new Promise((T,a)=>{let n=!1;const t=E(e,[...i],{shell:!1,stdio:["ignore","pipe","pipe"]});let l="",u="";t.stdout.setEncoding("utf8"),t.stderr.setEncoding("utf8"),t.stdout.on("data",o=>{l=p(l,o)}),t.stderr.on("data",o=>{u=p(u,o)});let r;const d=setTimeout(()=>{n||(n=!0,t.kill("SIGTERM"),r=setTimeout(()=>{t.killed||t.kill("SIGKILL")},A),r.unref?.(),a(new c({code:"E_INTERNAL",message:`runCommand: "${e}" timed out after ${m}ms. Next: re-run with a longer timeout, or investigate why the command is hanging.`})))},m);t.on("error",o=>{n||(n=!0,clearTimeout(d),r&&clearTimeout(r),a(new c({code:"E_INTERNAL",message:`runCommand: failed to spawn "${e}" (${o.code??"unknown"}). Next: confirm the binary is installed and on PATH.`,cause:o})))}),t.on("close",o=>{r&&clearTimeout(r),!n&&(n=!0,clearTimeout(d),T({exitCode:o??0,stdout:l,stderr:u}))})})}export{h as SAFE_ARGV_TOKEN,C as resolveExecutable,I as runCommand};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{mkdir as u,writeFile as f}from"node:fs/promises";import{join as n}from"node:path";import{atomicRename as p}from"./fs.js";const s="installer-logs";function D(e){const o=[];let i=e.enabled;return{append(t){o.push(t)},setEnabled(t){i=t},async flush(){if(!i)return;const t=n(e.targetDir,s);await u(t,{recursive:!0});const l=`install-${g(new Date)}.log`,r=n(t,l),c=o.map(m=>JSON.stringify(m)).join(`
|
|
2
|
+
`)+`
|
|
3
|
+
`,a=`${r}.tmp`;return await f(a,c,"utf8"),await p(a,r,{errorCode:"E_INTERNAL"}),r}}}function g(e){return e.toISOString().replace(/[:.]/g,"-")}function E(e){return n(e,s)}export{D as createVerboseLogger,E as verboseLogDirFor};
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nortal/ai1st-kit-cli",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"license": "ISC",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ai1st-kit": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=20"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"wrapper-templates"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc -p tsconfig.build.json",
|
|
18
|
+
"prebuild": "npm run bind",
|
|
19
|
+
"postbuild": "node scripts/minify-dist.mjs",
|
|
20
|
+
"test": "vitest run",
|
|
21
|
+
"test:watch": "vitest",
|
|
22
|
+
"check": "tsc --noEmit && prettier --check . && vitest run",
|
|
23
|
+
"precheck": "npm run bind",
|
|
24
|
+
"bind": "npm run bind-content && npm run bind-banner && prettier --log-level=warn --write src/generated",
|
|
25
|
+
"bind-content": "node scripts/bind-content.mjs",
|
|
26
|
+
"bind-banner": "node scripts/bind-banner.mjs",
|
|
27
|
+
"prepublishOnly": "npm run check && npm run build"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/node": "^25.6.0",
|
|
31
|
+
"@types/picomatch": "^4.0.3",
|
|
32
|
+
"esbuild": "^0.25.0",
|
|
33
|
+
"node-pty": "^1.1.0",
|
|
34
|
+
"prettier": "^3.8.3",
|
|
35
|
+
"typescript": "^5.9.3",
|
|
36
|
+
"vitest": "^4.1.5"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@clack/prompts": "^1.3.0",
|
|
40
|
+
"commander": "^12.1.0",
|
|
41
|
+
"picocolors": "^1.1.1",
|
|
42
|
+
"picomatch": "^4.0.4",
|
|
43
|
+
"yaml": "^2.8.4",
|
|
44
|
+
"zod": "^3.25.76"
|
|
45
|
+
},
|
|
46
|
+
"optionalDependencies": {
|
|
47
|
+
"keytar": "^7.9.0"
|
|
48
|
+
},
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# AI-First Wrapper
|
|
2
|
+
|
|
3
|
+
This is a wrapper repository that provides AI-Kit framework configuration and houses one or more client project repositories for managed operations work.
|
|
4
|
+
|
|
5
|
+
## AI-Kit Framework
|
|
6
|
+
|
|
7
|
+
This repo includes the [AI-Kit framework](https://gitlab.nortal.com/ai-first/public/n-ai1st-kit/-/blob/main/README.md) for AI-assisted development workflows, templates, and quality gates.
|
|
8
|
+
|
|
9
|
+
## Project Layout
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
<wrapper-dir>/
|
|
13
|
+
├── .ai/ # AI-Kit framework (templates, core memory, workflows)
|
|
14
|
+
├── .ai_project_memory/ # Project context and constitutions
|
|
15
|
+
├── .claude/ # Claude Code config, commands, agents, skills
|
|
16
|
+
├── project-repos/
|
|
17
|
+
│ └── <project>/ # Client project (separate git repo)
|
|
18
|
+
└── README.md
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Setup
|
|
22
|
+
|
|
23
|
+
### 1. Clone this wrapper repository
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
git clone <wrapper-repo-url>
|
|
27
|
+
cd <wrapper-dir>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### 2. Add the client project under `project-repos/`
|
|
31
|
+
|
|
32
|
+
The `<project>` lives in its own Git repository. The wrapper repo does not track its contents — you can either clone it fresh inside `project-repos/`, or, if you already have it checked out elsewhere on your machine, link it in via a symlink.
|
|
33
|
+
|
|
34
|
+
#### Option A — clone fresh
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
cd project-repos
|
|
38
|
+
git clone <project-repo-url>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
#### Option B — symlink an existing checkout
|
|
42
|
+
|
|
43
|
+
Use this if you already have the project cloned somewhere on your machine and want to reuse that working copy.
|
|
44
|
+
|
|
45
|
+
**macOS / Linux:**
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
cd project-repos
|
|
49
|
+
ln -s /absolute/path/to/your/<project> <project>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Windows (PowerShell, run as Administrator or with Developer Mode enabled):**
|
|
53
|
+
|
|
54
|
+
```powershell
|
|
55
|
+
cd project-repos
|
|
56
|
+
New-Item -ItemType SymbolicLink -Path .\<project> -Target C:\absolute\path\to\your\<project>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Windows (cmd, run as Administrator):**
|
|
60
|
+
|
|
61
|
+
```cmd
|
|
62
|
+
cd project-repos
|
|
63
|
+
mklink /D <project> C:\absolute\path\to\your\<project>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
> Note: Windows symlinks require either an elevated shell or Developer Mode enabled (Settings → Privacy & security → For developers). If you don't have either, use Option A and clone fresh.
|
|
67
|
+
|
|
68
|
+
The `.gitignore` excludes everything inside `project-repos/` (except `.gitkeep`), so the symlink itself won't be committed to the wrapper repo.
|
|
69
|
+
|
|
70
|
+
### 3. Managing repositories independently
|
|
71
|
+
|
|
72
|
+
Each repository is managed independently. The wrapper repo does not track client project contents.
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# Working with the wrapper repo
|
|
76
|
+
cd <wrapper-dir>
|
|
77
|
+
git add . && git commit -m "update wrapper config"
|
|
78
|
+
git push
|
|
79
|
+
|
|
80
|
+
# Working with the client project
|
|
81
|
+
cd <wrapper-dir>/project-repos/<project>
|
|
82
|
+
git add . && git commit -m "feature work"
|
|
83
|
+
git pull
|
|
84
|
+
git push
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Treat `commit`, `push`, and `pull` operations for each repo separately — changes in one do not affect the other.
|
|
88
|
+
|
|
89
|
+
## Repositories
|
|
90
|
+
|
|
91
|
+
| Repository | URL |
|
|
92
|
+
|------------|-----|
|
|
93
|
+
| Wrapper repo | `<wrapper-repo-url>` |
|
|
94
|
+
| Project repo | `<project-repo-url>` |
|
|
95
|
+
|
|
96
|
+
## Next Steps: AI-Kit Quick Reference
|
|
97
|
+
|
|
98
|
+
### First-time project setup
|
|
99
|
+
|
|
100
|
+
Run the project init command to populate project memory and configure AI-Kit for your codebase. This performs a deep analysis of the projects inside the `project-repos/` folder — scanning code structure, dependencies, patterns, and architecture to build a comprehensive project context that all subsequent AI commands rely on.
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
/ai1st-kit-project-init do deep analysis of projects inside project-repos folder
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Development workflow
|
|
107
|
+
|
|
108
|
+
Once initialized, use these commands to go from idea to verified implementation:
|
|
109
|
+
|
|
110
|
+
| Command | Purpose |
|
|
111
|
+
|---------|---------|
|
|
112
|
+
| `/ai1st-po-specify` | Create feature spec from description |
|
|
113
|
+
| `/ai1st-po-clarify` | Ask clarification questions, update spec |
|
|
114
|
+
| `/ai1st-dev-plan` | Generate implementation plan |
|
|
115
|
+
| `/ai1st-dev-tasks` | Generate dependency-ordered task list |
|
|
116
|
+
| `/ai1st-dev-implement` | Execute all tasks from plan |
|
|
117
|
+
| `/ai1st-dev-test` | Run test tasks, generate coverage |
|
|
118
|
+
| `/ai1st-dev-verify` | Code review against spec and constitution |
|
|
119
|
+
|
|
120
|
+
For full details see the [AI-Kit framework documentation](https://gitlab.nortal.com/ai-first/public/n-ai1st-kit/-/blob/main/README.md).
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# ========================
|
|
2
|
+
# OS Generated Files
|
|
3
|
+
# ========================
|
|
4
|
+
.DS_Store
|
|
5
|
+
.DS_Store?
|
|
6
|
+
._*
|
|
7
|
+
.Spotlight-V100
|
|
8
|
+
.Trashes
|
|
9
|
+
ehthumbs.db
|
|
10
|
+
Thumbs.db
|
|
11
|
+
Desktop.ini
|
|
12
|
+
$RECYCLE.BIN/
|
|
13
|
+
|
|
14
|
+
# ========================
|
|
15
|
+
# IDEs and Editors
|
|
16
|
+
# ========================
|
|
17
|
+
|
|
18
|
+
# JetBrains (IntelliJ, WebStorm, PyCharm, etc.)
|
|
19
|
+
.idea/
|
|
20
|
+
*.iml
|
|
21
|
+
*.iws
|
|
22
|
+
*.ipr
|
|
23
|
+
out/
|
|
24
|
+
|
|
25
|
+
# Visual Studio Code
|
|
26
|
+
.vscode/*
|
|
27
|
+
!.vscode/settings.json
|
|
28
|
+
!.vscode/tasks.json
|
|
29
|
+
!.vscode/launch.json
|
|
30
|
+
!.vscode/extensions.json
|
|
31
|
+
|
|
32
|
+
# Visual Studio
|
|
33
|
+
.vs/
|
|
34
|
+
*.suo
|
|
35
|
+
*.ntvs*
|
|
36
|
+
*.njsproj
|
|
37
|
+
*.sln.docx.user
|
|
38
|
+
*.user
|
|
39
|
+
|
|
40
|
+
# Eclipse
|
|
41
|
+
.classpath
|
|
42
|
+
.project
|
|
43
|
+
.settings/
|
|
44
|
+
*.launch
|
|
45
|
+
bin/
|
|
46
|
+
|
|
47
|
+
# Sublime Text
|
|
48
|
+
*.sublime-project
|
|
49
|
+
*.sublime-workspace
|
|
50
|
+
|
|
51
|
+
# Vim / Neovim
|
|
52
|
+
*.swp
|
|
53
|
+
*.swo
|
|
54
|
+
*.swn
|
|
55
|
+
*~
|
|
56
|
+
.netrwhist
|
|
57
|
+
tags
|
|
58
|
+
|
|
59
|
+
# Emacs
|
|
60
|
+
*~
|
|
61
|
+
\#*\#
|
|
62
|
+
.#*
|
|
63
|
+
*.elc
|
|
64
|
+
|
|
65
|
+
# ========================
|
|
66
|
+
# Languages & Runtimes
|
|
67
|
+
# ========================
|
|
68
|
+
|
|
69
|
+
# Node.js
|
|
70
|
+
node_modules/
|
|
71
|
+
npm-debug.log*
|
|
72
|
+
yarn-debug.log*
|
|
73
|
+
yarn-error.log*
|
|
74
|
+
pnpm-debug.log*
|
|
75
|
+
.pnpm-store/
|
|
76
|
+
package-lock.json.bak
|
|
77
|
+
|
|
78
|
+
# Java / Maven / Gradle
|
|
79
|
+
target/
|
|
80
|
+
build/
|
|
81
|
+
*.class
|
|
82
|
+
*.jar
|
|
83
|
+
*.war
|
|
84
|
+
*.ear
|
|
85
|
+
*.nar
|
|
86
|
+
.gradle/
|
|
87
|
+
gradle-app.setting
|
|
88
|
+
!gradle-wrapper.jar
|
|
89
|
+
|
|
90
|
+
# Python
|
|
91
|
+
__pycache__/
|
|
92
|
+
*.py[cod]
|
|
93
|
+
*$py.class
|
|
94
|
+
*.egg-info/
|
|
95
|
+
dist/
|
|
96
|
+
.eggs/
|
|
97
|
+
*.egg
|
|
98
|
+
.venv/
|
|
99
|
+
venv/
|
|
100
|
+
env/
|
|
101
|
+
.env.local
|
|
102
|
+
|
|
103
|
+
# TypeScript / Angular
|
|
104
|
+
dist/
|
|
105
|
+
*.js.map
|
|
106
|
+
.angular/
|
|
107
|
+
|
|
108
|
+
# ========================
|
|
109
|
+
# Build & Package Outputs
|
|
110
|
+
# ========================
|
|
111
|
+
*.log
|
|
112
|
+
logs/
|
|
113
|
+
*.pid
|
|
114
|
+
*.seed
|
|
115
|
+
*.pid.lock
|
|
116
|
+
|
|
117
|
+
# ========================
|
|
118
|
+
# Environment & Secrets
|
|
119
|
+
# ========================
|
|
120
|
+
.env
|
|
121
|
+
.env.*
|
|
122
|
+
!.env.example
|
|
123
|
+
!.env.template
|
|
124
|
+
*.pem
|
|
125
|
+
*.key
|
|
126
|
+
*.cert
|
|
127
|
+
*.p12
|
|
128
|
+
*.jks
|
|
129
|
+
credentials.json
|
|
130
|
+
secrets.json
|
|
131
|
+
*-secret.*
|
|
132
|
+
|
|
133
|
+
# ========================
|
|
134
|
+
# Testing & Coverage
|
|
135
|
+
# ========================
|
|
136
|
+
coverage/
|
|
137
|
+
*.lcov
|
|
138
|
+
.nyc_output/
|
|
139
|
+
test-results/
|
|
140
|
+
playwright-report/
|
|
141
|
+
test-output/
|
|
142
|
+
|
|
143
|
+
# ========================
|
|
144
|
+
# Temp & Cache
|
|
145
|
+
# ========================
|
|
146
|
+
.tmp
|
|
147
|
+
.tmp*
|
|
148
|
+
*.tmp*
|
|
149
|
+
.tmp/
|
|
150
|
+
.cache/
|
|
151
|
+
*.bak
|
|
152
|
+
*.orig
|
|
153
|
+
*.rej
|
|
154
|
+
|
|
155
|
+
# ========================
|
|
156
|
+
# Docker
|
|
157
|
+
# ========================
|
|
158
|
+
docker-compose.override.yml
|
|
159
|
+
|
|
160
|
+
# ========================
|
|
161
|
+
# Client Projects (separate repos)
|
|
162
|
+
# ========================
|
|
163
|
+
project-repos/*
|
|
164
|
+
!project-repos/.gitkeep
|
|
165
|
+
|
|
166
|
+
# ========================
|
|
167
|
+
# AI Agent Local Files
|
|
168
|
+
# ========================
|
|
169
|
+
.claude/settings.local.json
|
|
170
|
+
.claude/CLAUDE.local.md
|
|
171
|
+
|
|
172
|
+
# ========================
|
|
173
|
+
# Kit Installer Artifacts
|
|
174
|
+
# ========================
|
|
175
|
+
.ai1st-kit.lock
|
|
176
|
+
installer-logs/
|
|
177
|
+
|
|
178
|
+
# ========================
|
|
179
|
+
# Documentation Build
|
|
180
|
+
# ========================
|
|
181
|
+
site/
|
|
182
|
+
_site/
|
|
183
|
+
.docusaurus/
|
|
184
|
+
|
|
185
|
+
# playwright logs
|
|
186
|
+
/.playwright-mcp/
|
|
File without changes
|