@hydra-acp/archiver 0.1.22 → 0.1.24
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/dist/config.js +1 -1
- package/dist/index.js +33 -9
- package/dist/local-index.js +1 -0
- package/dist/prefix.js +1 -0
- package/dist/restore.js +17 -0
- package/dist/util/log.js +2 -2
- package/package.json +1 -1
package/dist/config.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readFile as
|
|
1
|
+
import{readFile as l,readFileSync as u}from"node:fs";import{homedir as f,hostname as A}from"node:os";import{resolve as s}from"node:path";import{promisify as E}from"node:util";const D=E(l);function H(n){return process.env.HYDRA_ACP_ARCHIVER_CONF??s(n,"archiver.conf")}function m(n){return n==="~"||n.startsWith("~/")?f()+n.slice(1):n}function P(n){const r=new Map;for(const e of n.split(/\r?\n/)){const o=e.trim();if(!o||o.startsWith("#"))continue;const i=o.indexOf("=");if(i===-1)continue;const c=o.slice(0,i).trim();let t=o.slice(i+1).trim();(t.startsWith('"')&&t.endsWith('"')||t.startsWith("'")&&t.endsWith("'"))&&(t=t.slice(1,-1)),r.set(c,m(t))}return r}function y(n){try{return P(u(n,"utf8"))}catch(r){if(r.code==="ENOENT")return new Map;throw r}}const I=new Set(["1","true","yes","on","t"]);function a(n,r,e,o){return process.env[n]??e.get(r)??o}function g(n,r,e){return process.env[n]??e.get(r)}function d(n,r,e,o){const i=process.env[n]??e.get(r);if(!i)return o;const c=Number.parseInt(i,10);return Number.isFinite(c)?c:o}function N(n,r,e,o){const i=process.env[n]??e.get(r);return i===void 0?o:I.has(i.toLowerCase())}function v(n){if(n.startsWith("https://"))return"wss://"+n.slice(8).replace(/\/$/,"")+"/acp";if(n.startsWith("http://"))return"ws://"+n.slice(7).replace(/\/$/,"")+"/acp";throw new Error(`hydraDaemonUrl must start with http:// or https://: ${n}`)}function O(n){const r=n.toLowerCase();if(r==="google-drive"||r==="fs"||r==="s3")return r;throw new Error(`BACKEND must be one of: google-drive, fs, s3 (got "${n}")`)}function T(n){try{const r=u(s(n,"daemon.pid"),"utf8"),e=JSON.parse(r),o=e.loopbackPort??e.port;return typeof o!="number"?void 0:`http://127.0.0.1:${o}`}catch{return}}function b(n={}){const r=n.requireToken??!0,e=process.env.HYDRA_ACP_HOME??s(f(),".hydra-acp"),o=process.env.HYDRA_ACP_DAEMON_URL??T(e)??"http://127.0.0.1:8765";let i=process.env.HYDRA_ACP_TOKEN??"";if(!i)try{i=u(s(e,"auth-token"),"utf8").trim()}catch{}if(!i&&r)throw new Error("Missing HYDRA_ACP_TOKEN env var and no auth-token file. When run as a hydra extension, hydra injects the token automatically.");const c=process.env.HYDRA_ACP_WS_URL??v(o),t=y(H(e)),p=a("HYDRA_ACP_ARCHIVER_CONFIG","CONFIG",t,s(e,"archiver.config.js")),R=s(e,"archiver-google-token.json"),C=s(e,"archiver-state.json"),_=O(a("HYDRA_ACP_ARCHIVER_BACKEND","BACKEND",t,"google-drive")),h=a("HYDRA_ACP_ARCHIVER_S3_BUCKET","S3_BUCKET",t,"");if(_==="s3"&&!h)throw new Error("S3_BUCKET is required when BACKEND is s3. Set it in archiver.conf or via HYDRA_ACP_ARCHIVER_S3_BUCKET.");return{hydraDaemonUrl:o,hydraWsUrl:c,hydraToken:i,hydraHome:e,hydraPollIntervalMs:d("HYDRA_ACP_ARCHIVER_POLL_MS","POLL_MS",t,2e3),ruleConfigPath:p,uploadDebounceMs:d("HYDRA_ACP_ARCHIVER_DEBOUNCE_MS","DEBOUNCE_MS",t,5e3),pullIntervalMs:d("HYDRA_ACP_ARCHIVER_PULL_MS","PULL_MS",t,6e4),backend:_,driveFolderName:a("HYDRA_ACP_ARCHIVER_DRIVE_FOLDER","DRIVE_FOLDER",t,"hydra-acp-archive"),fsDir:a("HYDRA_ACP_ARCHIVER_FS_DIR","FS_DIR",t,s(e,"archive")),s3Bucket:h,s3Region:g("HYDRA_ACP_ARCHIVER_S3_REGION","S3_REGION",t),s3Endpoint:g("HYDRA_ACP_ARCHIVER_S3_ENDPOINT","S3_ENDPOINT",t),prefix:a("HYDRA_ACP_ARCHIVER_PREFIX","PREFIX",t,""),hostId:(g("HYDRA_ACP_ARCHIVER_HOST_ID","HOST_ID",t)??A()).toLowerCase().replace(/[^a-z0-9-]/g,"-"),encryptionKeyPath:g("HYDRA_ACP_ARCHIVER_KEY_PATH","KEY_PATH",t),tokenPath:R,statePath:C,toolContent:w(a("HYDRA_ACP_ARCHIVER_TOOL_CONTENT","TOOL_CONTENT",t,"inline")),debug:N("DEBUG","DEBUG",t,!1)}}function w(n){return n==="references"||n==="summary"?n:"inline"}async function L(n){if(n===void 0)return;let r;try{r=(await D(n,"utf8")).trim()}catch(e){throw e.code==="ENOENT"?new Error(`Encryption key file not found at ${n}. Run \`hydra-acp-archiver keygen\` to generate one.`):e}if(!/^[0-9a-f]{64}$/i.test(r))throw new Error(`Encryption key at ${n} is not a valid 64-character hex string.`);return Buffer.from(r,"hex")}function B(){const n=process.env.HYDRA_ACP_HOME??s(f(),".hydra-acp");return{hydraHome:n,tokenPath:s(n,"archiver-google-token.json")}}export{b as loadConfig,L as loadEncryptionKey,B as loadLoginConfig};
|
package/dist/index.js
CHANGED
|
@@ -1,29 +1,53 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{
|
|
2
|
+
import{readFileSync as $}from"node:fs";import{userInfo as P}from"node:os";import{resolvePrefix as x}from"./prefix.js";import{dirname as A,resolve as S}from"node:path";import{fileURLToPath as R}from"node:url";import{ArchiveLoop as U}from"./archive-loop.js";import{EncryptedBackend as C}from"./backend/encrypted.js";import{makeBackend as E}from"./backend/factory.js";import{ArchiverBridge as T}from"./bridge.js";import{runColdSweep as L}from"./cold-sweep.js";import{loadConfig as D,loadEncryptionKey as G,loadLoginConfig as B}from"./config.js";import{DaemonClient as _}from"./daemon.js";import{HydraDiscovery as M}from"./discovery.js";import{lineageFromKey as H}from"./envelope.js";import{runKeygen as F}from"./keygen.js";import{runGoogleLogin as K}from"./oauth/google.js";import{PullLoop as O}from"./pull-loop.js";import{DEFAULT_RULE as j,loadRule as w}from"./rule.js";import{SyncState as N}from"./state.js";import{logger as Y,setDebug as v}from"./util/log.js";const o=Y("main"),h=`usage: hydra-acp-archiver [<command>]
|
|
3
3
|
|
|
4
4
|
Commands:
|
|
5
5
|
setup Interactive first-run wizard \u2014 picks a backend, sets up
|
|
6
6
|
OAuth/credentials/encryption, writes archiver.conf, and
|
|
7
7
|
(optionally) registers the extension with hydra.
|
|
8
|
-
(no args) Run as a daemon-managed extension (the daemon spawns it
|
|
9
|
-
this way automatically when registered).
|
|
10
8
|
gdrive login Interactive Google OAuth flow \u2014 opens a browser and writes
|
|
11
9
|
the refresh token to ~/.hydra-acp/archiver-google-token.json.
|
|
12
10
|
keygen Generate a symmetric encryption key and write it to
|
|
13
11
|
HYDRA_ACP_ARCHIVER_KEY_PATH (or ~/.hydra-acp/archiver-key).
|
|
12
|
+
restore list [--host <h>] [--agent <a>] [--cwd <s>] [--grep <s>]
|
|
13
|
+
[--since <7d|iso>] [--limit <n>] [--fast] [--json]
|
|
14
|
+
[--only-remote | --only-local]
|
|
15
|
+
List every bundle in the backend (across all hosts).
|
|
16
|
+
Default decrypts each envelope to show title/cwd/agent;
|
|
17
|
+
--fast skips decryption for a metadata-only view. Bundles
|
|
18
|
+
that already exist locally are marked LOCAL=yes and pulled
|
|
19
|
+
from meta.json instead of the backend (free hydration).
|
|
20
|
+
--only-remote hides local-known lineages so you see just
|
|
21
|
+
the ones you'd need to pull to get back.
|
|
22
|
+
restore pull <lineageId> [--host <h>] [--to <file>|-] [--import]
|
|
23
|
+
[--no-replace] [--cwd <path>] [--force]
|
|
24
|
+
Fetch a bundle by lineage. <lineageId> may include or omit
|
|
25
|
+
the hydra_lineage_ prefix. If a session with this lineage
|
|
26
|
+
already exists locally, prints its id and skips the
|
|
27
|
+
network unless --force. Otherwise writes the raw .hydra
|
|
28
|
+
bundle to <file> or stdout; --import POSTs it to the
|
|
29
|
+
daemon's /v1/sessions/import (implies replace=true unless
|
|
30
|
+
--no-replace). If multiple hosts have this lineage, the
|
|
31
|
+
newest wins; use --host to disambiguate.
|
|
14
32
|
|
|
15
33
|
Flags:
|
|
16
34
|
--version, -v Print version and exit.
|
|
17
35
|
--help, -h Show this message.
|
|
18
|
-
`;function
|
|
19
|
-
`);return}if(e===void 0){await W();return}if(e==="gdrive"){const n=process.argv[3];if(n==="login"){await V();return}const
|
|
36
|
+
`;function k(){try{const e=A(R(import.meta.url));return JSON.parse($(S(e,"../package.json"),"utf8")).version??"unknown"}catch{return"unknown"}}const z=new Set(["1","true","yes","on","t"]);async function V(){v(z.has((process.env.DEBUG??"").toLowerCase()));const e=B();o.info(`token=${e.tokenPath}`),await K({tokenPath:e.tokenPath}),o.info("login complete \u2014 you can now start the archiver extension")}async function W(){const e=D();v(e.debug);const n=await G(e.encryptionKeyPath);e.prefix===""&&(e.prefix=x(e.prefix,n),o.info(`auto-prefix (${n!==void 0?"key fingerprint":"username"}): ${e.prefix}`));const i=new N(e.statePath);await i.load(k(),e.prefix,e.backend);function c(r){const t=E({...e,prefix:r});return n!==void 0?new C(t,n):t}const a=c(e.prefix),d=c(e.prefix+e.hostId+"/");await a.init(),await d.init();try{const r=await a.list(),t=new Set;for(const I of r){const y=H(I.key);y!==void 0&&t.add(y)}const b=await i.reconcile(t);o.info(`reconciled state with backend: present=${t.size} pruned=${b}`)}catch(r){o.warn(`state reconcile skipped \u2014 backend list failed: ${r.message}`)}const l=new _({daemonUrl:e.hydraDaemonUrl,token:e.hydraToken,toolContent:e.toolContent});let p=j;p=await w(e.ruleConfigPath);const u=new U({daemon:l,backend:d,state:i,getRule:()=>p,debounceMs:e.uploadDebounceMs,host:{host:e.hostId,user:P().username}}),m=new O({daemon:l,backend:a,state:i,intervalMs:e.pullIntervalMs,hostId:e.hostId});m.start(),L({daemonUrl:e.hydraDaemonUrl,token:e.hydraToken,archive:u}).catch(r=>{o.warn(`cold sweep failed: ${r.message}`)});const s=new Map,f=new M({daemonUrl:e.hydraDaemonUrl,token:e.hydraToken,pollIntervalMs:e.hydraPollIntervalMs,onAdd:r=>{if(s.has(r.sessionId))return;o.info(`attaching to ${r.sessionId} agent=${r.agentId??"?"} cwd=${r.cwd}`);const t=new T({daemonWsUrl:e.hydraWsUrl,token:e.hydraToken,sessionId:r.sessionId,meta:{...r.cwd!==void 0?{cwd:r.cwd}:{},...r.agentId!==void 0?{agentId:r.agentId}:{},...r.title!==void 0?{title:r.title}:{},...r.interactive!==void 0?{interactive:r.interactive}:{}},archive:u});s.set(r.sessionId,t),t.start()},onRemove:r=>{const t=s.get(r);t&&(o.info(`detaching from ${r}`),s.delete(r),t.stop())}});f.start(),process.on("SIGHUP",()=>{o.info(`SIGHUP \u2014 reloading rule from ${e.ruleConfigPath}`),w(e.ruleConfigPath).then(r=>{p=r,o.info("rule reload complete")}).catch(r=>{o.warn(`rule reload failed: ${r.message}`)})});const g=r=>{o.info(`${r} received \u2014 shutting down`),f.stop(),m.stop(),u.stop();for(const t of s.values())t.stop();setTimeout(()=>process.exit(0),200).unref()};process.on("SIGINT",()=>g("SIGINT")),process.on("SIGTERM",()=>g("SIGTERM")),o.info(`hydra-acp-archiver up; daemon=${e.hydraDaemonUrl} backend=${e.backend} host=${e.hostId} rule=${e.ruleConfigPath}`)}async function J(){const e=process.argv[2];if(e==="-v"||e==="--version"){process.stdout.write(`hydra-acp-archiver ${k()}
|
|
37
|
+
`);return}if(e===void 0){if(process.env.HYDRA_ACP_TOKEN){await W();return}process.stdout.write(h);return}if(e==="gdrive"){const n=process.argv[3];if(n==="login"){await V();return}const i="login";process.stderr.write(n!==void 0?`hydra-acp-archiver gdrive: unknown subcommand "${n}"
|
|
20
38
|
|
|
21
|
-
Available: ${
|
|
39
|
+
Available: ${i}
|
|
22
40
|
`:`hydra-acp-archiver gdrive: missing subcommand
|
|
23
41
|
|
|
24
|
-
Available: ${
|
|
42
|
+
Available: ${i}
|
|
25
43
|
`),process.exit(2)}if(e==="login"&&(process.stderr.write(`hydra-acp-archiver: "login" is not a command; did you mean "gdrive login"?
|
|
26
|
-
`),process.exit(2)),e==="keygen"){await
|
|
44
|
+
`),process.exit(2)),e==="keygen"){await F();return}if(e==="restore"){const n=process.argv[3],i=process.argv.slice(4),{parseListArgs:c,parsePullArgs:a,runRestoreList:d,runRestorePull:l}=await import("./restore.js");if(n==="list"){await d(c(i));return}if(n==="pull"){await l(a(i));return}process.stderr.write(n!==void 0?`hydra-acp-archiver restore: unknown subcommand "${n}"
|
|
27
45
|
|
|
28
|
-
|
|
46
|
+
Available: list, pull
|
|
47
|
+
`:`hydra-acp-archiver restore: missing subcommand
|
|
48
|
+
|
|
49
|
+
Available: list, pull
|
|
50
|
+
`),process.exit(2)}if(e==="setup"){const{runSetup:n}=await import("./setup/wizard.js");await n();return}if(e==="-h"||e==="--help"||e==="help"){process.stdout.write(h);return}process.stderr.write(`hydra-acp-archiver: unknown command "${e}"
|
|
51
|
+
|
|
52
|
+
${h}`),process.exit(2)}J().catch(e=>{const n=e.message;process.stderr.write(`hydra-acp-archiver: ${n}
|
|
29
53
|
`),/invalid_grant/i.test(n)&&(process.stderr.write("Google refresh token is invalid. Run `hydra-acp-archiver gdrive login` to re-authorize.\n"),process.exit(78)),process.exit(1)});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{readFile as c,readdir as l}from"node:fs/promises";import{resolve as o}from"node:path";import{logger as p}from"./util/log.js";const d=p("local-index");async function I(g){const s=new Map,n=o(g,"sessions");let a;try{a=await l(n)}catch(t){return d.warn(`local-index: cannot read ${n}: ${t.message}`),s}return await Promise.all(a.map(async t=>{if(!t.startsWith("hydra_session_"))return;const r=o(n,t,"meta.json");try{const i=await c(r,"utf8"),e=JSON.parse(i);if(typeof e.sessionId!="string"||typeof e.lineageId!="string")return;s.set(e.lineageId,{sessionId:e.sessionId,lineageId:e.lineageId,upstreamSessionId:typeof e.upstreamSessionId=="string"?e.upstreamSessionId:void 0,agentId:typeof e.agentId=="string"?e.agentId:void 0,cwd:typeof e.cwd=="string"?e.cwd:void 0,title:typeof e.title=="string"?e.title:void 0,updatedAt:typeof e.updatedAt=="string"?e.updatedAt:void 0})}catch(i){const e=i;e.code!=="ENOENT"&&d.warn(`local-index: ${r}: ${e.message}`)}})),s}export{I as loadLocalIndex};
|
package/dist/prefix.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createHash as t}from"node:crypto";import{userInfo as n}from"node:os";function i(r,e){return r!==""?r:e!==void 0?t("sha256").update(e).digest().subarray(0,8).toString("hex")+"/":n().username.toLowerCase().replace(/[^a-z0-9-]/g,"-")+"/"}export{i as resolvePrefix};
|
package/dist/restore.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import{readFile as U,writeFile as H}from"node:fs/promises";import{EncryptedBackend as j}from"./backend/encrypted.js";import{makeBackend as D}from"./backend/factory.js";import{homedir as F}from"node:os";import{resolve as E}from"node:path";import{loadConfig as z,loadEncryptionKey as K}from"./config.js";import{DaemonClient as Y}from"./daemon.js";import{deserialize as S,lineageFromKey as L}from"./envelope.js";import{loadLocalIndex as x}from"./local-index.js";import{resolvePrefix as G}from"./prefix.js";import{logger as W,setDebug as q}from"./util/log.js";const v=W("restore"),$="hydra_lineage_",J=new Set(["1","true","yes","on","t"]);function M(e){const t=e.lastIndexOf("/");return t<0?{host:void 0,name:e}:{host:e.slice(0,t),name:e.slice(t+1)}}function X(e){return e.startsWith($)?e:$+e}function y(e){return e.startsWith($)?e.slice($.length):e}async function T(e){const t=z({requireToken:e.requireToken});q(t.debug||J.has((process.env.DEBUG??"").toLowerCase()));const s=await K(t.encryptionKeyPath);t.prefix=G(t.prefix,s);const n=D({...t,prefix:t.prefix}),i=s!==void 0?new j(n,s):n;return await i.init(),{backend:i,encryptionKey:s,daemonUrl:t.hydraDaemonUrl,daemonToken:t.hydraToken,hydraHome:t.hydraHome}}function Z(){return process.env.HYDRA_ACP_HOME??E(F(),".hydra-acp")}async function Q(e){const t=E(e,"archiver-restore-cache.json");try{const s=await U(t,"utf8"),n=JSON.parse(s);return n.version!==1||typeof n.entries!="object"?new Map:new Map(Object.entries(n.entries))}catch{return new Map}}async function V(e,t){const s=E(e,"archiver-restore-cache.json"),n={version:1,entries:Object.fromEntries(t)};await H(s,JSON.stringify(n))}function ee(e,t){e.uploadedAt=t.uploadedAt,e.uploadedByHost=t.uploadedByHost,e.uploadedByUser=t.uploadedByUser,e.bundleHash=t.bundleHash,t.agentId!==void 0&&(e.agentId=t.agentId),t.cwd!==void 0&&(e.cwd=t.cwd),t.title!==void 0&&(e.title=t.title)}function te(e){const t={modifiedAt:e.modifiedAt};return e.uploadedAt!==void 0&&(t.uploadedAt=e.uploadedAt),e.uploadedByHost!==void 0&&(t.uploadedByHost=e.uploadedByHost),e.uploadedByUser!==void 0&&(t.uploadedByUser=e.uploadedByUser),e.bundleHash!==void 0&&(t.bundleHash=e.bundleHash),e.agentId!==void 0&&(t.agentId=e.agentId),e.cwd!==void 0&&(t.cwd=e.cwd),e.title!==void 0&&(t.title=e.title),t}function ne(e,t){e.localSessionId=t.sessionId,e.agentId===void 0&&t.agentId!==void 0&&(e.agentId=t.agentId),e.cwd===void 0&&t.cwd!==void 0&&(e.cwd=t.cwd),e.title===void 0&&t.title!==void 0&&(e.title=t.title)}function ie(e){const{host:t}=M(e.key);return{lineageId:L(e.key)??e.key,key:e.key,host:t,size:e.size,modifiedAt:e.modifiedAt}}async function oe(e,t){const s=await e.get(t.key),n=S(s);t.uploadedAt=n.uploadedAt,t.uploadedByHost=n.uploadedBy.host,t.uploadedByUser=n.uploadedBy.user,t.bundleHash=n.bundleHash;const i=se(n.bundle);i&&(typeof i.agentId=="string"&&(t.agentId=i.agentId),typeof i.cwd=="string"&&(t.cwd=i.cwd),typeof i.title=="string"&&(t.title=i.title))}function se(e){if(!e||typeof e!="object"||Array.isArray(e))return;const t=e.session;if(!(!t||typeof t!="object"||Array.isArray(t)))return t}function Ae(e){const t={limit:50,fast:!1,json:!1,onlyRemote:!1,onlyLocal:!1};for(let s=0;s<e.length;s++){const n=e[s],i=()=>{const p=e[++s];if(p===void 0)throw new Error(`missing value for ${n}`);return p};if(n==="--host")t.host=i();else if(n==="--agent")t.agent=i();else if(n==="--cwd")t.cwd=i();else if(n==="--grep")t.grep=i();else if(n==="--since")t.since=i();else if(n==="--limit")t.limit=Number.parseInt(i(),10);else if(n==="--fast")t.fast=!0;else if(n==="--json")t.json=!0;else if(n==="--only-remote")t.onlyRemote=!0;else if(n==="--only-local")t.onlyLocal=!0;else throw new Error(`unknown flag: ${n}`)}if(t.onlyRemote&&t.onlyLocal)throw new Error("--only-remote and --only-local are mutually exclusive");return(!Number.isFinite(t.limit)||t.limit<=0)&&(t.limit=50),t}function re(e){const t=/^(\d+)([smhdw])$/.exec(e.trim());if(t){const n=Number.parseInt(t[1],10),i=t[2],p=i==="s"?1e3:i==="m"?6e4:i==="h"?36e5:i==="d"?864e5:6048e5;return new Date(Date.now()-n*p)}const s=new Date(e);if(Number.isNaN(s.getTime()))throw new Error(`--since: unrecognized value ${e} (use e.g. 7d or an ISO date)`);return s}function r(e,t){const s=[...e].length;return s>=t?e:e+" ".repeat(t-s)}function w(e,t){return e.length<=t?e:e.slice(0,Math.max(0,t-1))+"\u2026"}function N(e){if(!e)return"";const t=Date.parse(e);if(!Number.isFinite(t))return"";const s=Date.now()-t,n=Math.abs(s),i=864e5;return n<6e4?"<1m":n<36e5?`${Math.floor(n/6e4)}m`:n<i?`${Math.floor(n/36e5)}h`:n<30*i?`${Math.floor(n/i)}d`:n<365*i?`${Math.floor(n/(30*i))}mo`:`${Math.floor(n/(365*i))}y`}function de(e){if(!e)return"";const t=process.env.HOME;return t&&e.startsWith(t)?"~"+e.slice(t.length):e}async function Ie(e){const{backend:t,encryptionKey:s,hydraHome:n}=await T({requireToken:!1}),[i,p,f]=await Promise.all([t.list(),x(n),Q(n)]),m=[];for(const o of i){const a=ie(o);if(!a.lineageId.startsWith($)||e.host!==void 0&&a.host!==e.host)continue;const h=p.get(a.lineageId);if(h&&ne(a,h),e.onlyRemote&&h||e.onlyLocal&&!h)continue;const I=f.get(a.key);I&&I.modifiedAt===a.modifiedAt&&ee(a,I),m.push(a)}m.sort((o,a)=>a.modifiedAt.localeCompare(o.modifiedAt));const A=e.since!==void 0?re(e.since).toISOString():void 0;let d=m;const b=e.fast||s===void 0;if(b)A!==void 0&&(d=d.filter(o=>o.modifiedAt>=A)),(e.agent!==void 0||e.cwd!==void 0||e.grep!==void 0)&&process.stderr.write(`note: --agent/--cwd/--grep are ignored in --fast mode (they need decryption)
|
|
2
|
+
`),d=d.slice(0,e.limit);else{const o=[];let h=0;const I=process.stderr.isTTY&&m.length>50,P=u=>{if(A!==void 0&&(u.uploadedAt??u.modifiedAt)<A||e.agent!==void 0&&u.agentId!==e.agent||e.cwd!==void 0&&!(u.cwd??"").includes(e.cwd))return!1;if(e.grep!==void 0){const k=e.grep.toLowerCase();if(!(u.title??"").toLowerCase().includes(k))return!1}return!0};let B=0;const C=async()=>{B!==0&&(await V(n,f).catch(u=>v.warn(`saving cache failed: ${u.message}`)),B=0)};e:for(let u=0;u<m.length;u+=8){const k=m.slice(u,u+8),_=await Promise.all(k.map(async c=>{if(c.localSessionId!==void 0)return{ok:!0,row:c};if(f.has(c.key)&&f.get(c.key).modifiedAt===c.modifiedAt&&c.uploadedAt!==void 0)return{ok:!0,row:c};try{return await oe(t,c),f.set(c.key,te(c)),B+=1,{ok:!0,row:c}}catch(O){return v.warn(`skipping ${c.key}: ${O.message}`),{ok:!1,row:c}}}));B>=32&&await C();for(const c of _)if(h+=1,!!c.ok&&P(c.row)&&(o.push(c.row),o.length>=e.limit))break e;I&&process.stderr.write(`\rhydrated ${h}/${m.length} (${o.length} matches)`)}I&&process.stderr.write("\r\x1B[K"),await C(),o.sort((u,k)=>(k.uploadedAt??k.modifiedAt).localeCompare(u.uploadedAt??u.modifiedAt)),d=o}if(e.json){process.stdout.write(JSON.stringify(d.map(o=>({lineageId:o.lineageId,host:o.host,agentId:o.agentId,uploadedAt:o.uploadedAt??o.modifiedAt,uploadedBy:o.uploadedByHost,title:o.title,cwd:o.cwd,size:o.size,bundleHash:o.bundleHash,key:o.key,localSessionId:o.localSessionId})),null,2)+`
|
|
3
|
+
`);return}if(d.length===0){process.stderr.write(`no bundles found
|
|
4
|
+
`);return}if(b){const o=`${r("LINEAGE",24)} ${r("HOST",20)} ${r("AGE",6)} ${r("SIZE",8)} KEY
|
|
5
|
+
`;process.stdout.write(o);for(const a of d){const h=[r(w(y(a.lineageId),24),24),r(w(a.host??"",20),20),r(N(a.modifiedAt),6),r(String(a.size),8),a.key].join(" ")+`
|
|
6
|
+
`;process.stdout.write(h)}return}const R=`${r("LINEAGE",24)} ${r("HOST",16)} ${r("AGENT",14)} ${r("AGE",6)} ${r("LOCAL",5)} ${r("CWD",32)} TITLE
|
|
7
|
+
`;process.stdout.write(R);let g=0;for(const o of d){o.localSessionId!==void 0&&(g+=1);const a=[r(w(y(o.lineageId),24),24),r(w(o.uploadedByHost??o.host??"",16),16),r(w(o.agentId??"",14),14),r(N(o.uploadedAt??o.modifiedAt),6),r(o.localSessionId!==void 0?"yes":"no",5),r(w(de(o.cwd),32),32),w(o.title??"",80)].join(" ")+`
|
|
8
|
+
`;process.stdout.write(a)}const l=[];d.length===e.limit&&l.push(`showing ${d.length}; pass --limit to see more`),g>0&&!e.onlyLocal&&l.push(`${g}/${d.length} already local (use --only-remote to hide)`),l.length>0&&process.stderr.write(`(${l.join("; ")})
|
|
9
|
+
`)}function ke(e){let t;const s={doImport:!1,replace:!0,force:!1};for(let n=0;n<e.length;n++){const i=e[n],p=()=>{const f=e[++n];if(f===void 0)throw new Error(`missing value for ${i}`);return f};if(i==="--host")s.host=p();else if(i==="--to")s.to=p();else if(i==="--cwd")s.cwd=p();else if(i==="--import")s.doImport=!0;else if(i==="--no-replace")s.replace=!1;else if(i==="--force")s.force=!0;else{if(i.startsWith("--"))throw new Error(`unknown flag: ${i}`);if(t===void 0)t=i;else throw new Error(`unexpected positional: ${i}`)}}if(t===void 0)throw new Error("restore pull: missing <lineageId>");return{identifier:t,...s}}async function be(e){const t=X(e.identifier);if(!e.force){const l=(await x(Z())).get(t);if(l){process.stderr.write(`lineage ${y(t)} is already local as ${l.sessionId}${l.title?` (${l.title})`:""}
|
|
10
|
+
`),process.stderr.write(`use --force to fetch from remote anyway (e.g. to overwrite the local copy)
|
|
11
|
+
`),process.stdout.write(l.sessionId+`
|
|
12
|
+
`);return}}const{backend:s,daemonUrl:n,daemonToken:i}=await T({requireToken:e.doImport}),p=await s.list(),f=[];for(const g of p)if(L(g.key)===t){if(e.host!==void 0){const{host:l}=M(g.key);if(l!==e.host)continue}f.push(g)}if(f.length===0)throw new Error(`no bundle found for lineage ${y(t)}${e.host!==void 0?` (host=${e.host})`:""}`);f.sort((g,l)=>l.modifiedAt.localeCompare(g.modifiedAt));const m=f[0];f.length>1&&process.stderr.write(`${f.length} copies for ${y(t)}; picking newest (${m.key}, modifiedAt=${m.modifiedAt})
|
|
13
|
+
`);const A=await s.get(m.key),d=S(A),b=d.bundle;if(process.stderr.write(`lineage=${y(d.lineageId)} uploadedBy=${d.uploadedBy.host} uploadedAt=${d.uploadedAt} hash=${d.bundleHash}
|
|
14
|
+
`),e.doImport){const g=new Y({daemonUrl:n,token:i}),l={replace:e.replace};e.cwd!==void 0&&(l.cwd=e.cwd);const o=await g.importBundle(b,l);process.stdout.write(`imported ${o.sessionId} (lineage=${y(d.lineageId)})
|
|
15
|
+
`);return}const R=JSON.stringify(b);if(e.to===void 0||e.to==="-"){process.stdout.write(R),process.stdout.isTTY&&process.stdout.write(`
|
|
16
|
+
`);return}await H(e.to,R),process.stderr.write(`wrote ${e.to}
|
|
17
|
+
`)}export{Ae as parseListArgs,ke as parsePullArgs,Ie as runRestoreList,be as runRestorePull};
|
package/dist/util/log.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
let
|
|
2
|
-
`)}function u(r){return r.map(n=>{if(typeof n=="string")return n;if(n instanceof Error)return n.stack??n.message;try{return JSON.stringify(n)}catch{return String(n)}}).join(" ")}function d(r){return{debug(...n){
|
|
1
|
+
let e=!1;function s(r){e=r}function o(r,n,t){const i=new Date().toISOString();(r==="debug"?process.stdout:process.stderr).write(`[${i}] ${r} [${n}] ${u(t)}
|
|
2
|
+
`)}function u(r){return r.map(n=>{if(typeof n=="string")return n;if(n instanceof Error)return n.stack??n.message;try{return JSON.stringify(n)}catch{return String(n)}}).join(" ")}function d(r){return{debug(...n){e&&o("debug",r,n)},info(...n){o("info",r,n)},warn(...n){o("warn",r,n)},error(...n){o("error",r,n)}}}export{d as logger,s as setDebug};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hydra-acp/archiver",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.24",
|
|
4
4
|
"description": "Session sync extension for hydra-acp — uploads session bundles to a shared backend (Google Drive, fs) and imports peers' bundles.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|