@0x26/amp 0.5.4 → 0.5.6
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 +12 -0
- package/dist/index.js +19 -7
- package/package.json +1 -1
- package/skill/SKILL.md +12 -0
package/README.md
CHANGED
|
@@ -103,6 +103,18 @@ Agent output reports `source`, `stale`, `version` (SHA-256), and any warning.
|
|
|
103
103
|
Hand-edited local skills are preserved. Installation without `--service` retains
|
|
104
104
|
the bundled, offline skill behavior.
|
|
105
105
|
|
|
106
|
+
Installing to the Claude Code default location (`amp skill install` or
|
|
107
|
+
`amp login --install-skill`, without `--dir`/`--skill-dir`) also adds a
|
|
108
|
+
`SessionStart` hook to `~/.claude/settings.json` for new and cleared sessions. It
|
|
109
|
+
runs `amp --agent skill sync`, which installs the latest `@0x26/amp` when the
|
|
110
|
+
registry has a newer version, refreshes the installed skill (the updated binary
|
|
111
|
+
does this after an upgrade), and pre-fetches the published skill when one is
|
|
112
|
+
configured. The hook is added once, other settings are preserved, and an
|
|
113
|
+
unparseable settings file is left untouched. `AMP_NO_UPDATE_CHECK=1` skips the
|
|
114
|
+
version check. Other agents can run `amp --agent skill sync --dir <skill-dir>`.
|
|
115
|
+
`amp login --install-skill --skill-service https://YOUR-ASK-SERVICE` installs the
|
|
116
|
+
refreshing skill during sign-in.
|
|
117
|
+
|
|
106
118
|
Related follow-ups and corrections automatically use the current Amp conversation
|
|
107
119
|
under the skill instructions. Agent Ask output includes `meta.continuation` with
|
|
108
120
|
command arguments, profile, store, and conversation ID. Append the question as one
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{Help}from"commander";import{homedir}from"node:os";import{dirname,join}from"node:path";import{chmodSync,existsSync,mkdirSync,readFileSync,writeFileSync}from"node:fs";import{homedir as homedir2}from"node:os";import{dirname as dirname2,join as join2}from"node:path";import{chmodSync as chmodSync2,existsSync as existsSync2,mkdirSync as mkdirSync2,readFileSync as readFileSync2,writeFileSync as writeFileSync2}from"node:fs";import{closeSync,openSync,rmSync,statSync,unlinkSync}from"node:fs";import{createHash,randomBytes}from"node:crypto";import{closeSync as closeSync2,readSync}from"node:fs";import{Command,CommanderError}from"commander";import{existsSync as
|
|
2
|
+
import{Help}from"commander";import{homedir}from"node:os";import{dirname,join}from"node:path";import{chmodSync,existsSync,mkdirSync,readFileSync,writeFileSync}from"node:fs";import{homedir as homedir2}from"node:os";import{dirname as dirname2,join as join2}from"node:path";import{chmodSync as chmodSync2,existsSync as existsSync2,mkdirSync as mkdirSync2,readFileSync as readFileSync2,writeFileSync as writeFileSync2}from"node:fs";import{closeSync,openSync,rmSync,statSync,unlinkSync}from"node:fs";import{createHash,randomBytes}from"node:crypto";import{closeSync as closeSync2,readSync}from"node:fs";import{Command,CommanderError}from"commander";import{existsSync as existsSync7,rmSync as rmSync3}from"node:fs";import{createServer}from"node:http";import{spawn}from"node:child_process";import"node:fs";import"node:os";import"node:path";import{createHash as createHash2,randomUUID}from"node:crypto";import{mkdirSync as mkdirSync3,readFileSync as readFileSync3,renameSync,writeFileSync as writeFileSync3,rmSync as rmSync2}from"node:fs";import{dirname as dirname3,join as join3}from"node:path";import{homedir as homedir3}from"node:os";import{existsSync as existsSync5,mkdirSync as mkdirSync6,readFileSync as readFileSync6,writeFileSync as writeFileSync6}from"node:fs";import{homedir as homedir5}from"node:os";import{dirname as dirname6,join as join6,resolve}from"node:path";import{fileURLToPath}from"node:url";import{Option}from"commander";import{spawnSync}from"node:child_process";import{existsSync as existsSync4,mkdirSync as mkdirSync5,readFileSync as readFileSync5,renameSync as renameSync2,writeFileSync as writeFileSync5}from"node:fs";import{homedir as homedir4}from"node:os";import{dirname as dirname5,join as join5}from"node:path";import{execFileSync}from"node:child_process";import{existsSync as existsSync3,mkdirSync as mkdirSync4,readFileSync as readFileSync4,writeFileSync as writeFileSync4}from"node:fs";import{dirname as dirname4,join as join4}from"node:path";import{writeFileSync as writeFileSync7}from"node:fs";import{spawn as spawn2}from"node:child_process";import{randomUUID as randomUUID2}from"node:crypto";import{join as join8,resolve as resolve2}from"node:path";var __defProp=Object.defineProperty,__getOwnPropNames=Object.getOwnPropertyNames,__esm=(fn,res)=>function(){return fn&&(res=(0,fn[__getOwnPropNames(fn)[0]])(fn=0)),res},__export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})};function colorEnabled(env=process.env,tty=!!process.stdout.isTTY){return tty&&env.TERM!=="dumb"&&env.NO_COLOR===void 0&&env.FORCE_COLOR!=="0"}function paint(text,role="accent",enabled=colorEnabled()){return enabled?`\x1B[${{accent:"38;5;75",muted:"2",strong:"1",error:"31"}[role]}m${text}\x1B[0m`:text}function wrap(text,width){let lines=[],line="";for(let word of clean(text).split(/\s+/)){line&&line.length+word.length+1>width&&(lines.push(line),line="");let rest=word;for(;rest.length>width;)line&&(lines.push(line),line=""),lines.push(rest.slice(0,width)),rest=rest.slice(width);line+=(line?" ":"")+rest}return(line||!lines.length)&&lines.push(line),lines}function brandedHelp(cmd,helper,style,options){let width=Math.max(32,Math.min(options.width,100)),inner=width-4,ink=(s,role="accent")=>paint(s,role,options.color),line=(s="")=>`${ink("\u2502","muted")} ${s} ${ink("\u2502","muted")}`,padded=(s,role)=>line((role?ink(s,role):s)+" ".repeat(Math.max(0,inner-s.length))),panel=(title,rows)=>{let label=` ${title} `,out2=[ink("\u256D\u2500","muted")+ink(label,"strong")+ink("\u2500".repeat(Math.max(0,width-label.length-3))+"\u256E","muted")],termWidth=Math.min(Math.max(0,...rows.map(([term])=>term.length)),Math.floor(inner*.4));for(let[term,description]of rows){if(!description){out2.push(...wrap(term,inner).map(s=>padded(s,"accent")));continue}inner<48||term.length>termWidth?(out2.push(...wrap(term,inner).map(s=>padded(s,"accent"))),out2.push(...wrap(description,inner-2).map(s=>padded(" "+s)))):wrap(description,inner-termWidth-2).forEach((s,i)=>{let left=i===0?term:"";out2.push(line(ink(left)+" ".repeat(termWidth-left.length+2)+s+" ".repeat(inner-termWidth-2-s.length)))})}return out2.push(ink("\u2570"+"\u2500".repeat(width-2)+"\u256F","muted")),out2.join(`
|
|
3
3
|
`)},root=!cmd.parent,out=[""];if(root&&width>=55){let mark=[" \u2584\u2580\u2580\u2584 "," \u2580\u2584\u2584\u2580 "," \u2584\u2580 \u2580\u2584\u2580 "," \u2580\u2584\u2584\u2584\u2580 \u2580 "],text=["AMPERSAND",`${cmd.name()} / ${style.edition}`,cmd.version()?`v${cmd.version()}`:"",""];mark.forEach((s,i)=>out.push(" "+ink(s)+" "+ink(text[i],i===0?"strong":"muted")))}else out.push(ink(` & AMPERSAND / ${cmd.name()}`,"strong"));out.push("",...wrap(helper.commandDescription(cmd),width).map(s=>ink(s,"muted")),""),out.push(panel("Usage",[[helper.commandUsage(cmd),""]]));let commands=helper.visibleCommands(cmd);if(commands.length){let used=new Set;for(let group of root?style.groups??[]:[]){let members=commands.filter(c=>group.commands.includes(c.name()));members.length&&(members.forEach(c=>used.add(c.name())),out.push("",panel(group.title,members.map(c=>[helper.subcommandTerm(c),helper.subcommandDescription(c)]))))}let remaining=commands.filter(c=>!used.has(c.name()));remaining.length&&out.push("",panel(used.size?"More commands":"Commands",remaining.map(c=>[helper.subcommandTerm(c),helper.subcommandDescription(c)])))}let args=helper.visibleArguments(cmd);args.length&&out.push("",panel("Arguments",args.map(a=>[helper.argumentTerm(a),helper.argumentDescription(a)])));let opts=helper.visibleOptions(cmd);return opts.length&&out.push("",panel("Options",opts.map(o=>[helper.optionTerm(o),helper.optionDescription(o)]))),root?out.push("",panel("Start here",style.examples.map(e=>[e,""]))):out.push("",ink("Global options: "+(()=>{let root2=cmd;for(;root2.parent;)root2=root2.parent;return root2.name()})()+" --help","muted")),out.push(""),out.join(`
|
|
4
4
|
`)}function installTerminalHelp(program2,style){program2.configureHelp({formatHelp(cmd,helper){let args=process.argv.slice(2);return!process.stdout.isTTY||process.env.TERM==="dumb"||args.includes("--agent")||args.includes("--json")?Help.prototype.formatHelp.call(helper,cmd,helper):brandedHelp(cmd,helper,style,{width:process.stdout.columns||80,color:colorEnabled()})}})}function askProgress(write2,options={}){let now=options.now??Date.now,start=now(),frames=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"],stage="Connecting",frame=0,stopped=!1,lastUpdate=start,draw=()=>{if(stopped)return;let elapsed=`${Math.floor((now()-start)/1e3)}s`,width=Math.max(12,(options.width??process.stderr.columns??80)-1),stale=Math.floor((now()-lastUpdate)/1e3),age=stale>=15?` \xB7 last update ${stale}s ago`:"",suffix=` \xB7 ${elapsed}${age.length+elapsed.length+13<=width?age:""}`,text=stage.slice(0,Math.max(1,width-suffix.length-2));write2(`\r\x1B[2K${paint(frames[frame++%frames.length],"accent",options.color)} ${text}${suffix}`)};draw();let timer=setInterval(draw,100);return timer.unref(),{update(message){stage=clean(message).trim().replace(/\s+/g," ")||"Working",lastUpdate=now()},note(message){stopped||(write2("\r\x1B[2K"+clean(message)+`
|
|
5
5
|
`),draw())},stop(){stopped||(stopped=!0,clearInterval(timer),write2("\r\x1B[2K"))}}}var clean,init_terminal=__esm({"src/terminal.ts"(){"use strict";clean=text=>text.replace(/[\x00-\x1f\x7f-\x9f]/g," ")}}),NPM_PACKAGE,init_brand=__esm({"src/brand.ts"(){"use strict";NPM_PACKAGE="@0x26/amp"}});function configPath(){return process.env.AMP_CONFIG_PATH??join(homedir(),".config","amp","config.json")}function readConfig(){let p=configPath();if(!existsSync(p))return{profiles:{}};try{let parsed=JSON.parse(readFileSync(p,"utf8"));return{current:parsed.current,profiles:parsed.profiles??{}}}catch{return{profiles:{}}}}function writeConfig(cfg){let p=configPath();mkdirSync(dirname(p),{recursive:!0,mode:448}),writeFileSync(p,`${JSON.stringify(cfg,null,2)}
|
|
@@ -14,8 +14,9 @@ Preview only; use --json or --out <file> for all returned data.`:"")}var current
|
|
|
14
14
|
`)?err.body:`${err.body}
|
|
15
15
|
`)):err instanceof NotLoggedInError?process.stderr.write(`${err.message}
|
|
16
16
|
`):process.stderr.write(`${err instanceof Error?err.message:String(err)}
|
|
17
|
-
`),process.exitCode=1}var DoorRefusalError,init_http=__esm({"src/http.ts"(){"use strict";init_experience(),init_brand(),init_credentials(),init_machine(),init_env_customer(),init_session(),init_request_credential(),DoorRefusalError=class extends Error{constructor(status,body){super(`door refused (HTTP ${status})`),this.status=status,this.body=body}}}});init_terminal();var CLI_VERSION="0.5.
|
|
18
|
-
|
|
17
|
+
`),process.exitCode=1}var DoorRefusalError,init_http=__esm({"src/http.ts"(){"use strict";init_experience(),init_brand(),init_credentials(),init_machine(),init_env_customer(),init_session(),init_request_credential(),DoorRefusalError=class extends Error{constructor(status,body){super(`door refused (HTTP ${status})`),this.status=status,this.body=body}}}});init_terminal();var CLI_VERSION="0.5.6";init_brand();init_config();init_credentials();init_lock();init_experience();init_brand();init_brand();init_workos();init_credentials();init_env_customer();init_config();init_brand();var MAX_BYTES=65536;function skillUrl(service){let url=new URL(service);if(url.protocol!=="https:"||url.username||url.password||url.search||url.hash||url.pathname!=="/"||!/^(?:[a-z0-9.-]+|\[[a-f0-9:]+\])$/i.test(url.hostname)||/[\s\\]/.test(service))throw new Error("Use an HTTPS Ask service origin without a path or credentials.");return`${url.origin}/skill.md`}var skillVersion=text=>createHash2("sha256").update(text).digest("hex");function validSkill(text){return typeof text=="string"&&Buffer.byteLength(text)<=MAX_BYTES&&/^---\r?\nname: amp-cli\r?\n/.test(text)&&/^description: .+/m.test(text)&&!text.includes("\0")}async function fetchSkill(service,bundled,options={}){let url=skillUrl(service),file=join3(options.cacheDir??join3(homedir3(),".cache","amp","skills"),`${skillVersion(url)}.json`),cached3;try{let raw=readFileSync3(file);if(raw.length<=MAX_BYTES*2){let value=JSON.parse(raw.toString());value.url===url&&validSkill(value.text)&&(value.etag===void 0||typeof value.etag=="string"&&value.etag.length<=200&&!/[\r\n]/.test(value.etag))&&(cached3=value)}}catch{}let result=(text,source,stale,warning)=>({text,source,stale,url,version:skillVersion(text),...warning?{warning}:{}});try{let response=await(options.fetch??fetch)(url,{redirect:"error",credentials:"omit",signal:AbortSignal.timeout(options.timeoutMs??5e3),headers:{Accept:"text/markdown",...cached3?.etag?{"If-None-Match":cached3.etag}:{}}});if(response.status===304&&cached3)return result(cached3.text,"cache",!1);if(response.status!==200||response.redirected||!response.headers.get("content-type")?.startsWith("text/markdown"))throw await response.body?.cancel(),new Error("Invalid skill response");if(Number(response.headers.get("content-length"))>MAX_BYTES)throw await response.body?.cancel(),new Error("Skill too large");let reader=response.body?.getReader();if(!reader)throw new Error("Missing skill body");let chunks=[],bytes=0;try{for(;;){let{done,value}=await reader.read();if(done)break;if(bytes+=value.byteLength,bytes>MAX_BYTES)throw new Error("Skill too large");chunks.push(value)}}finally{await reader.cancel().catch(()=>{})}let text=new TextDecoder("utf-8",{fatal:!0}).decode(Buffer.concat(chunks));if(!validSkill(text))throw new Error("Invalid skill");let etag=response.headers.get("etag"),next={url,text,...etag&&etag.length<=200&&!/[\r\n]/.test(etag)?{etag}:{}},temporary=`${file}.${randomUUID()}.tmp`;try{mkdirSync3(dirname3(file),{recursive:!0,mode:448}),writeFileSync3(temporary,JSON.stringify(next),{mode:384}),renameSync(temporary,file)}catch{return result(text,"remote",!1,"Skill fetched; local cache could not be saved.")}finally{try{rmSync2(temporary,{force:!0})}catch{}}return result(text,"remote",!1)}catch{return result(cached3?.text??bundled(),cached3?"cache":"bundled",!0,"Published skill unavailable; using fallback instructions. Retry in a new chat or on explicit refresh.")}}init_experience();init_brand();init_brand();init_config();init_lock();init_brand();init_brand();init_credentials();var PACKAGE_NAME=NPM_PACKAGE,TTL_MS=1440*60*1e3;function isNewer(current2,candidate){let parse=v=>v.replace(/^v/,"").split("-")[0].split(".").map(n=>Number.parseInt(n,10)||0),a=parse(current2),b=parse(candidate);for(let i=0;i<Math.max(a.length,b.length);i+=1){let x=a[i]??0,y=b[i]??0;if(y>x)return!0;if(y<x)return!1}return!1}function fetchLatest(runner=(cmd,args)=>execFileSync(cmd,args,{encoding:"utf8",timeout:4e3,stdio:["ignore","pipe","ignore"]})){try{let out=runner("npm",["view",PACKAGE_NAME,"version","--json"]).trim(),parsed=JSON.parse(out);return typeof parsed=="string"?parsed:Array.isArray(parsed)&&typeof parsed[parsed.length-1]=="string"?parsed[parsed.length-1]:null}catch{return null}}var HOOK_MARKER="amp --agent skill sync",HOOK_COMMAND=`command -v amp >/dev/null 2>&1 && ${HOOK_MARKER} || true`;function npmInstall(spec){return spawnSync("npm",["install","-g",spec],{stdio:"ignore",timeout:9e4,shell:process.platform==="win32"}).status===0}function ensureLatestCli(opts={}){let env=opts.env??process.env,current2=CLI_VERSION;if(env.AMP_NO_UPDATE_CHECK==="1")return{status:"skipped",current:current2};let latest=(opts.latest??fetchLatest)();return latest?isNewer(current2,latest)?{status:(opts.install??npmInstall)(`${NPM_PACKAGE}@${latest}`)?"updated":"failed",current:current2,latest}:{status:"current",current:current2,latest}:{status:"unknown",current:current2}}function claudeSettingsPath(){return join5(homedir4(),".claude","settings.json")}function installSessionHook(settingsPath=claudeSettingsPath()){if(!existsSync4(dirname5(settingsPath)))return"skipped";let settings={};if(existsSync4(settingsPath))try{let parsed=JSON.parse(readFileSync5(settingsPath,"utf8"));if(!parsed||typeof parsed!="object"||Array.isArray(parsed))return"skipped";settings=parsed}catch{return"skipped"}let hooks=settings.hooks??{};if(!hooks||typeof hooks!="object"||Array.isArray(hooks))return"skipped";let sessionStart=hooks.SessionStart??[];if(!Array.isArray(sessionStart))return"skipped";if(sessionStart.some(group=>Array.isArray(group?.hooks)&&group.hooks.some(h=>typeof h?.command=="string"&&h.command.includes(HOOK_MARKER))))return"present";sessionStart.push({matcher:"startup|clear",hooks:[{type:"command",command:HOOK_COMMAND,timeout:120}]}),settings.hooks={...hooks,SessionStart:sessionStart};let tmp=`${settingsPath}.${process.pid}.tmp`;return writeFileSync5(tmp,`${JSON.stringify(settings,null,2)}
|
|
18
|
+
`),renameSync2(tmp,settingsPath),"installed"}function reexecSync(target){let r=spawnSync("amp",["--agent","skill","sync","--skip-update",...target===defaultInstallPath()?[]:["--dir",dirname5(target)]],{encoding:"utf8",timeout:3e4,shell:process.platform==="win32"});if(r.status!==0)return null;try{return JSON.parse(r.stdout).data}catch{return null}}async function syncSkill(opts={}){let target=opts.target??defaultInstallPath(),lock=join5(dirname5(configPath()),"skill-sync.lock");mkdirSync5(dirname5(lock),{recursive:!0,mode:448});let cli=opts.skipUpdate?{status:"skipped",current:CLI_VERSION}:await withFileLock(lock,async()=>(opts.update??ensureLatestCli)());if(cli.status==="updated"){let child=(opts.reexec??reexecSync)(target);if(child)return{...child,cli}}return withFileLock(lock,async()=>{let result={cli,skill:{path:target,outcome:installSkill(target)}},service=existsSync4(target)?skillService(readFileSync5(target,"utf8")):void 0;if(service){let published=await fetchSkill(service,bundledSkill,{fetch:opts.fetch,cacheDir:opts.cacheDir});result.published={url:published.url,source:published.source,stale:published.stale,version:published.version,...published.warning?{warning:published.warning}:{}}}return result})}var STAMP_PREFIX="<!-- amp-managed skill \xB7 amp ";function bundledSkillPath(){return resolve(dirname6(fileURLToPath(import.meta.url)),"..","skill","SKILL.md")}function bundledSkill(){let p=bundledSkillPath();if(!existsSync5(p))throw new Error("Bundled skill missing. Reinstall the CLI package.");return readFileSync6(p,"utf8")}function defaultInstallPath(){return join6(homedir5(),".claude","skills","amp-cli","SKILL.md")}function stamped(text){return`${STAMP_PREFIX}${CLI_VERSION} \u2014 bundled customer skill; remove this stamp to preserve manual edits -->
|
|
19
|
+
${text}`}function isAmpManaged(text){return text.startsWith(STAMP_PREFIX)}function installSkill(target=defaultInstallPath(),service){let current2=existsSync5(target)?readFileSync6(target,"utf8"):void 0;if(current2!==void 0&&!isAmpManaged(current2))return"kept-hand-edited";let publisher=service??(current2===void 0?void 0:skillService(current2)),next=stamped(publisher?bootstrapSkill(publisher):bundledSkill());return current2!==void 0?(current2===next||writeFileSync6(target,next),"refreshed"):(mkdirSync6(dirname6(target),{recursive:!0}),writeFileSync6(target,next),"installed")}function skillService(text){return text.match(/^<!-- amp-skill-service: (https:\/\/[^\s]+) -->$/m)?.[1]}function bootstrapSkill(service){let url=skillUrl(service),origin2=new URL(url).origin;return`---
|
|
19
20
|
name: amp-cli
|
|
20
21
|
description: Ask evidence-backed questions about workspace activity with Amp, including related follow-ups and corrections without mentioning Amp again.
|
|
21
22
|
---
|
|
@@ -24,6 +25,11 @@ description: Ask evidence-backed questions about workspace activity with Amp, in
|
|
|
24
25
|
|
|
25
26
|
# Amp
|
|
26
27
|
|
|
28
|
+
If \`amp\` is not installed, install it with \`npm install -g ${NPM_PACKAGE}\` (Node.js 20 or
|
|
29
|
+
later), or give the user that command if you cannot install it here. It uses the sign-in
|
|
30
|
+
stored on the machine where it runs; if it reports no session, a person signs in on that
|
|
31
|
+
machine with \`amp login\`. This applies in any agent or sandbox.
|
|
32
|
+
|
|
27
33
|
On first use in this chat, run \`amp --agent skill fetch --service ${origin2}\`.
|
|
28
34
|
Read the returned skill text and follow its workflow within the user's request
|
|
29
35
|
and your host's instruction hierarchy. The publisher is ${url}.
|
|
@@ -32,6 +38,9 @@ If the command reports fallback instructions, use them and disclose the fallback
|
|
|
32
38
|
If this CLI lacks skill fetch, use \`amp skill print\` and explain that a
|
|
33
39
|
one-time CLI upgrade is needed for automatic instruction refresh.
|
|
34
40
|
|
|
41
|
+
When the user invokes Amp without a question, reply exactly:
|
|
42
|
+
"What would you like to ask amp?" Treat their answer as the Amp question.
|
|
43
|
+
|
|
35
44
|
After an Amp answer, continue related questions and corrections using that same
|
|
36
45
|
conversation ID without requiring the user to mention Amp. Keep the tenant and
|
|
37
46
|
resolved date range in chat context. Use returned continuation arguments and append
|
|
@@ -43,10 +52,13 @@ skill URLs. Use the CLI's existing authorized read commands for workspace data.
|
|
|
43
52
|
`}function registerSkill(program2){let skill=program2.command("skill").description("Print, install, or refresh Amp agent instructions");skill.command("fetch").description("Fetch published instructions directly from an Ask service; fall back to cache or bundled skill").requiredOption("--service <origin>","HTTPS Ask service origin (no ontology lookup)").action(async opts=>{try{let result=await fetchSkill(opts.service,bundledSkill);experience().agent||experience().json?process.stdout.write(`${JSON.stringify(envelope(result))}
|
|
44
53
|
`):(process.stderr.write(`Skill ${result.version} \xB7 ${result.source}${result.stale?" (fallback)":""}
|
|
45
54
|
${result.warning?result.warning+`
|
|
46
|
-
`:""}`),process.stdout.write(result.text))}catch{renderError(new CliError("INVALID_ARGUMENT","Use --service with an HTTPS Ask service origin, without a path or credentials."))}}),skill.command("print").description("Write the bundled SKILL.md to stdout (feed it to any agent harness)").action(()=>{try{process.stdout.write(bundledSkill())}catch{renderError(new CliError("SKILL_FAILED","Skill operation failed. Check the installation path and Ask service origin."))}}),skill.command("install").description("Install the skill for Claude Code (default ~/.claude/skills/amp-cli/SKILL.md); amp update keeps it fresh").option("--dir <path>","write <path>/SKILL.md instead of the default skills dir").option("--service <origin>","install a bootstrap that refreshes directly from this HTTPS Ask service").action(opts=>{try{let target=opts.dir?
|
|
55
|
+
`:""}`),process.stdout.write(result.text))}catch{renderError(new CliError("INVALID_ARGUMENT","Use --service with an HTTPS Ask service origin, without a path or credentials."))}}),skill.command("print").description("Write the bundled SKILL.md to stdout (feed it to any agent harness)").action(()=>{try{process.stdout.write(bundledSkill())}catch{renderError(new CliError("SKILL_FAILED","Skill operation failed. Check the installation path and Ask service origin."))}}),skill.command("install").description("Install the skill for Claude Code (default ~/.claude/skills/amp-cli/SKILL.md); amp update keeps it fresh").option("--dir <path>","write <path>/SKILL.md instead of the default skills dir").option("--service <origin>","install a bootstrap that refreshes directly from this HTTPS Ask service").action(opts=>{try{let target=opts.dir?join6(resolve(opts.dir),"SKILL.md"):defaultInstallPath(),outcome=installSkill(target,opts.service),hook=opts.dir||outcome==="kept-hand-edited"?"skipped":installSessionHook(),verb=outcome==="installed"?"installed":outcome==="refreshed"?"up to date":"left as-is (hand-edited, no amp stamp)";if(experience().agent||experience().json){process.stdout.write(`${JSON.stringify(envelope({path:target,outcome,version:CLI_VERSION,hook}))}
|
|
47
56
|
`);return}process.stdout.write(`${target}: ${verb} (amp ${CLI_VERSION})
|
|
57
|
+
`),hook==="installed"&&process.stdout.write(`Claude Code will update amp and refresh this skill when each new session starts.
|
|
48
58
|
`),outcome!=="kept-hand-edited"&&process.stdout.write(`Start a new agent session to load it; use --dir for another host\u2019s skill directory.
|
|
49
|
-
`)}catch{renderError(new CliError("SKILL_FAILED","Skill operation failed. Check the installation path and Ask service origin."))}})
|
|
59
|
+
`)}catch{renderError(new CliError("SKILL_FAILED","Skill operation failed. Check the installation path and Ask service origin."))}}),skill.command("sync").description("Install the latest amp if newer, then refresh the installed skill (runs at agent session start)").option("--dir <path>","refresh <path>/SKILL.md instead of the default skills dir").addOption(new Option("--skip-update").hideHelp()).action(async opts=>{try{let r=await syncSkill({target:opts.dir?join6(resolve(opts.dir),"SKILL.md"):void 0,skipUpdate:opts.skipUpdate});if(experience().agent||experience().json){process.stdout.write(`${JSON.stringify(envelope(r))}
|
|
60
|
+
`);return}let cli=r.cli.status==="updated"?`amp updated ${r.cli.current} \u2192 ${r.cli.latest}`:r.cli.status==="failed"?`amp ${r.cli.latest} is available but could not be installed`:`amp ${r.cli.current}`,published=r.published?`; published skill ${r.published.source}${r.published.stale?" (fallback)":""}`:"";process.stdout.write(`${cli}; skill ${r.skill.outcome}${published}
|
|
61
|
+
`)}catch{renderError(new CliError("SKILL_FAILED","Skill sync failed. Check the installation path and Ask service origin."))}})}var HTML_CONTENT_TYPE="text/html; charset=utf-8",SUCCESS_HTML=`<!doctype html>
|
|
50
62
|
<html lang="en">
|
|
51
63
|
<head>
|
|
52
64
|
<meta charset="utf-8" />
|
|
@@ -96,7 +108,7 @@ ${authorizeUrl}
|
|
|
96
108
|
`),process.stdout.write(`
|
|
97
109
|
Next: amp schema
|
|
98
110
|
Then: amp ask "What data is available to me?"
|
|
99
|
-
`)}init_experience();init_brand();init_config();init_env_customer();init_credentials();init_machine();init_session();init_request_credential();init_http();init_experience();init_brand();init_config();init_credentials();init_machine();init_env_customer();
|
|
111
|
+
`)}init_experience();init_brand();init_config();init_env_customer();init_credentials();init_machine();init_session();init_request_credential();init_http();init_experience();init_brand();init_config();init_credentials();init_machine();init_env_customer();init_request_credential();function countRows(data){if(Array.isArray(data))return data.length;if(typeof data=="object"&&data!==null){let o=data;for(let key of["rows","items","data","results"])if(Array.isArray(o[key]))return o[key].length}}function emit(data,opts,human,meta={}){if(opts.out){if(requestCredentialConfigured())throw new Error("Request credential reads return stdout only; --out is unavailable.");writeFileSync7(opts.out,`${JSON.stringify(experience().agent?envelope(data,meta):data,null,2)}
|
|
100
112
|
`);let n=countRows(data);process.stdout.write(experience().agent?`${JSON.stringify(envelope({file:opts.out,rows:n},meta))}
|
|
101
113
|
`:opts.json||experience().json?`${JSON.stringify({file:opts.out,rows:n})}
|
|
102
114
|
`:`wrote ${opts.out}${n===void 0?"":` (${n} rows)`}
|
|
@@ -161,5 +173,5 @@ ${paint(`Conversation: ${conversation}`,"muted")}
|
|
|
161
173
|
`),conversation&&io.stderr(`Reattach: amp ask --watch --conversation ${conversation}
|
|
162
174
|
Disconnecting does not cancel work.
|
|
163
175
|
`),experience().enabled&&conversation){let failure=error instanceof CliError?error:new CliError("ASK_INTERRUPTED","The answer is not available yet. Resume the conversation; disconnecting does not cancel work.");throw failure.nextCommand??=`amp ask --watch --conversation ${conversation}`,failure.details={...failure.details,conversation_id:conversation,resume_command:`amp ask --watch --conversation ${conversation}`},failure}throw error}finally{status?.stop(),io.signal?.removeEventListener("abort",onAbort),aborter.abort()}}function registerAsk(program2){program2.command("ask [question]").description("Ask a question about your workspace data").option("--plain","Print the full answer and sources without the interactive reader").option("--timezone <zone>","Timezone for dates (defaults to your computer timezone)").option("--conversation <id>","continue or attach to a conversation in your tenant").option("--watch","reattach to current status or the latest saved answer").option("--cancel","explicitly cancel current work, preserving history").option("--json","write one final structured answer to stdout").option("--idempotency-key <key>","retry the exact same submission without launching twice").action(async(question,opts)=>{let controller=new AbortController,interrupt=()=>controller.abort();process.once("SIGINT",interrupt),process.once("SIGTERM",interrupt);try{await ask(question,opts,{signal:controller.signal})}finally{process.removeListener("SIGINT",interrupt),process.removeListener("SIGTERM",interrupt)}})}init_experience();init_brand();init_brand();init_http();async function catalog(includeHidden=!1){let result=JSON.parse(await doorRequest("GET","/v1/ontology/object-types"));if(!Array.isArray(result.objectTypes))throw new Error("Invalid schema response.");return result.objectTypes}function publicSchema(row){if(typeof row.apiName!="string"||!/^[A-Za-z_][A-Za-z0-9_]*$/.test(row.apiName))throw new Error("Invalid schema response.");return{name:row.apiName,table:`main_${row.apiName}`,description:row.description??"",columns:Object.entries(row.properties??{}).map(([name,property])=>({name,type:property.baseType??"unknown"}))}}async function schema(table,opts){readContext();let rows=(await catalog()).map(publicSchema);if(table!==void 0){let row=rows.find(row2=>row2.name===table||row2.table===table);if(!row)throw new CliError("NOT_FOUND","That table is unavailable in this workspace.",!1,"amp schema");emit(row,opts,()=>`${row.table} \u2014 ${row.description}
|
|
164
|
-
${humanTable(row.columns)}`)}else emit({tables:rows},opts,()=>humanTable(rows.map(row=>({table:row.table,description:row.description,columns:row.columns.length}))))}function registerSchema(program2){program2.command("schema").argument("[table]","type name from the authorized catalog").description("List authorized types and columns").option("--json","compact JSON output").option("--out <file>","write results to a file").action(schema)}init_request_credential();init_machine();init_experience();init_http();var program=new Command().name("amp").description("Query your data with Ampersand").version(CLI_VERSION).option("--profile <name>","select a saved tenant profile").option("--agent","structured JSON for agents; never prompt").option("--human","readable output for people").option("--quiet","hide routine progress").option("--no-input","never open a browser or wait for input").exitOverride().configureOutput({writeErr:()=>{}}).hook("preAction",(_,action)=>{if(configureExperience({...program.opts(),json:action.opts().json}),requestCredentialConfigured()||machineConfigFromEnv())throw new CliError("UNSUPPORTED_IDENTITY","Use a saved human session. Remove machine/request credentials first. Agent output does not change identity.");setProfileOverride(program.opts().profile)});installTerminalHelp(program,{edition:"workspace CLI",examples:["amp login --store <workspace-url>","amp schema",'amp ask "What data is available to me?"']});program.command("login").description("Sign in or manage your local session").option("--store <url>","tenant store HTTPS origin").option("--name <profile>","saved profile name").option("--org <id>","select your organization during sign-in").option("--status","show the signed-in identity").option("--logout","remove credentials for the selected profile on this machine").option("--json","raw JSON output (compatible with existing scripts)").option("--install-skill","install the bundled skill; can run without signing in").option("--skill-dir <path>","install SKILL.md in this directory; requires --install-skill").action(async opts=>{if(opts.status&&opts.logout||(opts.status||opts.logout)&&(opts.store||opts.name||opts.org||opts.installSkill||opts.skillDir))throw new CliError("INVALID_ARGUMENT","Use --status or --logout alone, or sign in with --store.");if(opts.skillDir&&!opts.installSkill)throw new CliError("INVALID_ARGUMENT","--skill-dir requires --install-skill.");let install=()=>installSkill(opts.skillDir?
|
|
176
|
+
${humanTable(row.columns)}`)}else emit({tables:rows},opts,()=>humanTable(rows.map(row=>({table:row.table,description:row.description,columns:row.columns.length}))))}function registerSchema(program2){program2.command("schema").argument("[table]","type name from the authorized catalog").description("List authorized types and columns").option("--json","compact JSON output").option("--out <file>","write results to a file").action(schema)}init_request_credential();init_machine();init_experience();init_http();var program=new Command().name("amp").description("Query your data with Ampersand").version(CLI_VERSION).option("--profile <name>","select a saved tenant profile").option("--agent","structured JSON for agents; never prompt").option("--human","readable output for people").option("--quiet","hide routine progress").option("--no-input","never open a browser or wait for input").exitOverride().configureOutput({writeErr:()=>{}}).hook("preAction",(_,action)=>{if(configureExperience({...program.opts(),json:action.opts().json}),requestCredentialConfigured()||machineConfigFromEnv())throw new CliError("UNSUPPORTED_IDENTITY","Use a saved human session. Remove machine/request credentials first. Agent output does not change identity.");setProfileOverride(program.opts().profile)});installTerminalHelp(program,{edition:"workspace CLI",examples:["amp login --store <workspace-url>","amp schema",'amp ask "What data is available to me?"']});program.command("login").description("Sign in or manage your local session").option("--store <url>","tenant store HTTPS origin").option("--name <profile>","saved profile name").option("--org <id>","select your organization during sign-in").option("--status","show the signed-in identity").option("--logout","remove credentials for the selected profile on this machine").option("--json","raw JSON output (compatible with existing scripts)").option("--install-skill","install the bundled skill; can run without signing in").option("--skill-dir <path>","install SKILL.md in this directory; requires --install-skill").option("--skill-service <origin>","install a skill that refreshes from this HTTPS Ask service; requires --install-skill").action(async opts=>{if(opts.status&&opts.logout||(opts.status||opts.logout)&&(opts.store||opts.name||opts.org||opts.installSkill||opts.skillDir||opts.skillService))throw new CliError("INVALID_ARGUMENT","Use --status or --logout alone, or sign in with --store.");if(opts.skillDir&&!opts.installSkill)throw new CliError("INVALID_ARGUMENT","--skill-dir requires --install-skill.");if(opts.skillService&&!opts.installSkill)throw new CliError("INVALID_ARGUMENT","--skill-service requires --install-skill.");if(opts.skillService)try{skillUrl(opts.skillService)}catch{throw new CliError("INVALID_ARGUMENT","Use --skill-service with an HTTPS Ask service origin, without a path or credentials.")}let install=()=>{let skill=installSkill(opts.skillDir?join8(resolve2(opts.skillDir),"SKILL.md"):void 0,opts.skillService),hook=opts.skillDir||skill==="kept-hand-edited"?"skipped":installSessionHook();return{skill,hook}},describe=({skill,hook})=>`Skill ${skill}.${hook==="installed"?" Claude Code will update amp and refresh the skill when each new session starts.":""}`;if(opts.status)return whoami();if(opts.installSkill&&!opts.store&&!opts.name&&!opts.org){let result=install();emit(result,opts,()=>`${describe(result)} Start a new agent session to load it.`);return}if(opts.logout){let path=credentialsPath();existsSync7(path)&&await withFileLock(`${path}.lock`,async()=>{rmSync3(path,{force:!0})}),emit({logged_out:!0},opts,()=>"Local session removed.");return}let store=opts.store??activeProfile()?.store_url;if(!store)throw new CliError("WORKSPACE_REQUIRED","Choose your workspace to sign in.",!1,"amp login --store <your tenant store URL>");await login({store,name:opts.name,orgId:opts.org}),opts.installSkill&&process.stdout.write(`${describe(install())}
|
|
165
177
|
`)});registerSkill(program);registerSchema(program);registerSql(program);registerAsk(program);program.parseAsync(process.argv).catch(error=>{if(!(error instanceof CommanderError&&error.exitCode===0)){if(!experience().enabled)try{configureExperience(program.opts())}catch{configureExperience({agent:!0})}reportError(error instanceof CommanderError?new CliError("INVALID_ARGUMENT","Unknown command or invalid arguments. Run amp --help.",!1,"amp --help"):error)}});
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -5,6 +5,18 @@ description: Ask evidence-backed questions about workspace activity with Amp, in
|
|
|
5
5
|
|
|
6
6
|
# Ampersand
|
|
7
7
|
|
|
8
|
+
## Setup
|
|
9
|
+
|
|
10
|
+
These steps apply in any agent environment: Claude Code, Codex, Cursor, a cloud sandbox, or a plain terminal.
|
|
11
|
+
|
|
12
|
+
- If `amp --version` fails because the command is not found, install the CLI before anything else. It needs Node.js 20 or later: `npm install -g @0x26/amp`. If you cannot install it in this environment (no Node.js, network, or permission), tell the user and give them that command; never install from another source.
|
|
13
|
+
- `amp` uses the sign-in stored on the machine where it runs. If `amp --agent login --status` shows no session, a person runs `amp login --store <tenant-store-url>` in a terminal on that same machine; their administrator supplies the store URL. A sign-in on the user's laptop does not reach a remote sandbox, and the reverse.
|
|
14
|
+
- To keep `amp` and these instructions current in an agent without a session-start hook, run `amp --agent skill sync --dir <this skill's directory>` once at the start of a session.
|
|
15
|
+
|
|
16
|
+
## Use
|
|
17
|
+
|
|
18
|
+
- In Claude Code, a session-start hook runs `amp --agent skill sync`: it installs the latest `amp` when a newer version is published and refreshes these instructions. If `data.cli.status` is `failed`, tell the user a newer `amp` could not be installed and continue with the installed version. Do not run the sync again in the same conversation.
|
|
19
|
+
- When the user invokes Amp without a question, or right after setup, reply exactly: "What would you like to ask amp?" Treat their answer as the Amp question.
|
|
8
20
|
- Run `amp --help` and the relevant command's `--help` before composing a request.
|
|
9
21
|
- A person runs `amp login --store <tenant-store-url>` in their terminal. Do not retry browser login from agent mode; return the login handoff when authentication is required. Never request or print tokens.
|
|
10
22
|
- Use `amp --agent` for structured output. Read `data` and `meta`; failures return `error` plus a nonzero exit status. `--agent` changes output, not identity or permissions.
|