@indigoai-us/hq-cli 5.5.0 → 5.5.2-sentry-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/hq-auth-refresh.js +22 -12
- package/dist/bin/hq-auth-refresh.js.map +1 -1
- package/dist/commands/_patterns.d.ts +3 -0
- package/dist/commands/_patterns.d.ts.map +1 -0
- package/dist/commands/_patterns.js +3 -0
- package/dist/commands/_patterns.js.map +1 -0
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +2 -1
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/auth.d.ts +0 -3
- package/dist/commands/auth.d.ts.map +1 -1
- package/dist/commands/auth.js +0 -3
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/cloud.d.ts +12 -2
- package/dist/commands/cloud.d.ts.map +1 -1
- package/dist/commands/cloud.js +112 -80
- package/dist/commands/cloud.js.map +1 -1
- package/dist/commands/groups.d.ts +3 -0
- package/dist/commands/groups.d.ts.map +1 -0
- package/dist/commands/groups.js +341 -0
- package/dist/commands/groups.js.map +1 -0
- package/dist/commands/list.d.ts.map +1 -1
- package/dist/commands/list.js +18 -1
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/login.d.ts +1 -1
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +28 -9
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/logout.d.ts +1 -1
- package/dist/commands/logout.d.ts.map +1 -1
- package/dist/commands/logout.js +8 -3
- package/dist/commands/logout.js.map +1 -1
- package/dist/commands/onboard.d.ts +23 -0
- package/dist/commands/onboard.d.ts.map +1 -0
- package/dist/commands/onboard.js +142 -0
- package/dist/commands/onboard.js.map +1 -0
- package/dist/commands/pack-install.d.ts +44 -0
- package/dist/commands/pack-install.d.ts.map +1 -0
- package/dist/commands/pack-install.js +513 -0
- package/dist/commands/pack-install.js.map +1 -0
- package/dist/commands/pkg-install.d.ts.map +1 -1
- package/dist/commands/pkg-install.js +20 -8
- package/dist/commands/pkg-install.js.map +1 -1
- package/dist/commands/pkg-list.js +4 -4
- package/dist/commands/pkg-list.js.map +1 -1
- package/dist/commands/pkg-update.js +3 -3
- package/dist/commands/pkg-update.js.map +1 -1
- package/dist/commands/secrets.d.ts +12 -0
- package/dist/commands/secrets.d.ts.map +1 -0
- package/dist/commands/secrets.js +756 -0
- package/dist/commands/secrets.js.map +1 -0
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +10 -2
- package/dist/commands/sync.js.map +1 -1
- package/dist/commands/team-sync.d.ts.map +1 -1
- package/dist/commands/team-sync.js +6 -6
- package/dist/commands/team-sync.js.map +1 -1
- package/dist/commands/whoami.d.ts +1 -1
- package/dist/commands/whoami.d.ts.map +1 -1
- package/dist/commands/whoami.js +23 -7
- package/dist/commands/whoami.js.map +1 -1
- package/dist/index.js +24 -2
- package/dist/index.js.map +1 -1
- package/dist/sentry-before-send.d.ts +3 -0
- package/dist/sentry-before-send.d.ts.map +1 -0
- package/dist/sentry-before-send.js +216 -0
- package/dist/sentry-before-send.js.map +1 -0
- package/dist/sentry-dsn.generated.d.ts +2 -0
- package/dist/sentry-dsn.generated.d.ts.map +1 -0
- package/dist/sentry-dsn.generated.js.map +1 -0
- package/dist/sentry.d.ts +4 -0
- package/dist/sentry.d.ts.map +1 -0
- package/dist/sentry.js +19 -0
- package/dist/sentry.js.map +1 -0
- package/dist/strategies/link.d.ts +2 -2
- package/dist/strategies/link.d.ts.map +1 -1
- package/dist/strategies/link.js.map +1 -1
- package/dist/strategies/merge.d.ts +2 -2
- package/dist/strategies/merge.d.ts.map +1 -1
- package/dist/strategies/merge.js.map +1 -1
- package/dist/types.d.ts +37 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -1
- package/dist/utils/cognito-session.d.ts +18 -7
- package/dist/utils/cognito-session.d.ts.map +1 -1
- package/dist/utils/cognito-session.js +35 -8
- package/dist/utils/cognito-session.js.map +1 -1
- package/dist/utils/manifest.d.ts +13 -0
- package/dist/utils/manifest.d.ts.map +1 -1
- package/dist/utils/manifest.js +24 -1
- package/dist/utils/manifest.js.map +1 -1
- package/dist/utils/secrets-cache.d.ts +7 -0
- package/dist/utils/secrets-cache.d.ts.map +1 -0
- package/dist/utils/secrets-cache.js +135 -0
- package/dist/utils/secrets-cache.js.map +1 -0
- package/package.json +17 -6
- package/scripts/generate-dsn.mjs +26 -0
- package/src/bin/hq-auth-refresh.ts +23 -16
- package/src/commands/_patterns.ts +2 -0
- package/src/commands/add.ts +7 -6
- package/src/commands/auth.ts +0 -3
- package/src/commands/cloud.ts +214 -101
- package/src/commands/groups.ts +357 -0
- package/src/commands/list.ts +20 -1
- package/src/commands/login.ts +28 -9
- package/src/commands/logout.ts +8 -3
- package/src/commands/onboard.ts +193 -0
- package/src/commands/pack-install.ts +667 -0
- package/src/commands/pkg-install.ts +34 -16
- package/src/commands/pkg-list.ts +4 -4
- package/src/commands/pkg-update.ts +3 -3
- package/src/commands/secrets.ts +950 -0
- package/src/commands/sync.ts +20 -4
- package/src/commands/team-sync.ts +7 -8
- package/src/commands/whoami.ts +22 -7
- package/src/index.ts +26 -2
- package/src/sentry-before-send.test.ts +359 -0
- package/src/sentry-before-send.ts +221 -0
- package/src/sentry.test.ts +24 -0
- package/src/sentry.ts +19 -0
- package/src/strategies/link.ts +2 -2
- package/src/strategies/merge.ts +2 -2
- package/src/types.ts +54 -4
- package/src/utils/cognito-session.ts +46 -14
- package/src/utils/manifest.test.ts +148 -0
- package/src/utils/manifest.ts +22 -1
- package/src/utils/secrets-cache.ts +130 -0
- package/tsconfig.json +2 -1
- package/dist/utils/auth.d.ts +0 -27
- package/dist/utils/auth.d.ts.map +0 -1
- package/dist/utils/auth.js +0 -155
- package/dist/utils/auth.js.map +0 -1
- package/dist/utils/token-store.d.ts +0 -28
- package/dist/utils/token-store.d.ts.map +0 -1
- package/dist/utils/token-store.js +0 -68
- package/dist/utils/token-store.js.map +0 -1
- package/src/utils/auth.ts +0 -193
- package/src/utils/token-store.ts +0 -83
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@indigoai-us/hq-cli",
|
|
3
|
-
"version": "5.5.0",
|
|
3
|
+
"version": "5.5.2-sentry-rc.0",
|
|
4
4
|
"description": "HQ by Indigo management CLI — modules and cloud sync",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,28 +8,39 @@
|
|
|
8
8
|
"hq-auth-refresh": "dist/bin/hq-auth-refresh.js"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
|
-
"build": "tsc",
|
|
11
|
+
"build": "node scripts/generate-dsn.mjs && tsc",
|
|
12
12
|
"typecheck": "tsc --noEmit",
|
|
13
|
+
"test": "vitest run",
|
|
13
14
|
"clean": "rm -rf dist"
|
|
14
15
|
},
|
|
15
16
|
"dependencies": {
|
|
16
|
-
"@
|
|
17
|
+
"@sentry/node": "^10.49.0",
|
|
18
|
+
"@indigoai-us/hq-cloud": "^5.1.0",
|
|
19
|
+
"@indigoai-us/hq-onboarding": "^0.1.0",
|
|
17
20
|
"chalk": "^5.3.0",
|
|
18
21
|
"commander": "^12.1.0",
|
|
19
22
|
"js-yaml": "^4.1.0",
|
|
20
|
-
"simple-git": "^3.27.0"
|
|
23
|
+
"simple-git": "^3.27.0",
|
|
24
|
+
"semver": "^7.6.3"
|
|
21
25
|
},
|
|
22
26
|
"devDependencies": {
|
|
23
27
|
"@types/js-yaml": "^4.0.9",
|
|
24
28
|
"@types/node": "^22.0.0",
|
|
25
|
-
"typescript": "^5.7.0"
|
|
29
|
+
"typescript": "^5.7.0",
|
|
30
|
+
"@types/semver": "^7.5.8"
|
|
26
31
|
},
|
|
27
32
|
"repository": {
|
|
28
33
|
"type": "git",
|
|
29
34
|
"url": "https://github.com/indigoai-us/hq.git",
|
|
30
35
|
"directory": "packages/hq-cli"
|
|
31
36
|
},
|
|
32
|
-
"keywords": [
|
|
37
|
+
"keywords": [
|
|
38
|
+
"hq",
|
|
39
|
+
"ai",
|
|
40
|
+
"modules",
|
|
41
|
+
"sync",
|
|
42
|
+
"cloud"
|
|
43
|
+
],
|
|
33
44
|
"license": "MIT",
|
|
34
45
|
"type": "module"
|
|
35
46
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { writeFileSync } from "fs";
|
|
2
|
+
import { join, dirname } from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
|
|
5
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
|
|
7
|
+
const isGitHubActions = process.env.GITHUB_ACTIONS === "true";
|
|
8
|
+
const isPublishJob = process.env.GITHUB_JOB === "publish";
|
|
9
|
+
const dsn = process.env.HQ_CLI_PUBLISH_SENTRY_DSN ?? "";
|
|
10
|
+
|
|
11
|
+
if (isGitHubActions && isPublishJob && !dsn) {
|
|
12
|
+
process.stderr.write(
|
|
13
|
+
"[generate-dsn] FATAL: GITHUB_ACTIONS=true, GITHUB_JOB=publish, " +
|
|
14
|
+
"but HQ_CLI_PUBLISH_SENTRY_DSN is not set. " +
|
|
15
|
+
"Add the repo secret HQ_CLI_PUBLISH_SENTRY_DSN before publishing.\n",
|
|
16
|
+
);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const outPath = join(__dirname, "../src/sentry-dsn.generated.ts");
|
|
21
|
+
const content = `export const BUNDLED_DSN = "${dsn}";\n`;
|
|
22
|
+
|
|
23
|
+
writeFileSync(outPath, content, "utf8");
|
|
24
|
+
process.stdout.write(
|
|
25
|
+
`[generate-dsn] Wrote BUNDLED_DSN="${dsn ? "[DSN set]" : ""}" to ${outPath}\n`,
|
|
26
|
+
);
|
|
@@ -11,22 +11,29 @@
|
|
|
11
11
|
* Writes the refreshed tokens to ~/.hq/cognito-tokens.json.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import { initSentry, Sentry } from "../sentry.js";
|
|
14
15
|
import { refreshCachedSession } from "../utils/cognito-session.js";
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
const result = await refreshCachedSession();
|
|
18
|
-
if (result.refreshed) {
|
|
19
|
-
process.exit(0);
|
|
20
|
-
}
|
|
21
|
-
if (result.reason) {
|
|
22
|
-
process.stderr.write(`hq-auth-refresh: ${result.reason}\n`);
|
|
23
|
-
}
|
|
24
|
-
process.exit(1);
|
|
25
|
-
}
|
|
17
|
+
initSentry();
|
|
26
18
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
19
|
+
(async () => {
|
|
20
|
+
let exitCode = 1;
|
|
21
|
+
try {
|
|
22
|
+
const result = await refreshCachedSession();
|
|
23
|
+
if (result.refreshed) {
|
|
24
|
+
exitCode = 0;
|
|
25
|
+
} else {
|
|
26
|
+
if (result.reason) {
|
|
27
|
+
process.stderr.write(`hq-auth-refresh: ${result.reason}\n`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
} catch (err) {
|
|
31
|
+
Sentry.captureException(err);
|
|
32
|
+
process.stderr.write(
|
|
33
|
+
`hq-auth-refresh: ${err instanceof Error ? err.message : String(err)}\n`,
|
|
34
|
+
);
|
|
35
|
+
} finally {
|
|
36
|
+
await Sentry.flush(2000);
|
|
37
|
+
process.exit(exitCode);
|
|
38
|
+
}
|
|
39
|
+
})();
|
package/src/commands/add.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { Command } from 'commander';
|
|
6
6
|
import { findHqRoot, addModule, parseRepoName, isValidRepoUrl } from '../utils/manifest.js';
|
|
7
|
-
import type {
|
|
7
|
+
import type { LegacyModuleDefinition, LegacyStrategy } from '../types.js';
|
|
8
8
|
|
|
9
9
|
export function registerAddCommand(program: Command): void {
|
|
10
10
|
program
|
|
@@ -30,9 +30,10 @@ export function registerAddCommand(program: Command): void {
|
|
|
30
30
|
// Parse name
|
|
31
31
|
const name = options.as || parseRepoName(repoUrl);
|
|
32
32
|
|
|
33
|
-
// Validate strategy
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
// Validate strategy (hq modules add is for legacy git-repo modules;
|
|
34
|
+
// content packs are added via `hq install`, not `hq modules add`)
|
|
35
|
+
const validStrategies: LegacyStrategy[] = ['link', 'merge', 'copy'];
|
|
36
|
+
if (!validStrategies.includes(options.strategy as LegacyStrategy)) {
|
|
36
37
|
console.error(`Error: Invalid strategy "${options.strategy}". Use: ${validStrategies.join(', ')}`);
|
|
37
38
|
process.exit(1);
|
|
38
39
|
}
|
|
@@ -48,11 +49,11 @@ export function registerAddCommand(program: Command): void {
|
|
|
48
49
|
})
|
|
49
50
|
: [{ src: '.', dest: `workers/${name}` }]; // Default: entire repo to workers/
|
|
50
51
|
|
|
51
|
-
const module:
|
|
52
|
+
const module: LegacyModuleDefinition = {
|
|
52
53
|
name,
|
|
53
54
|
repo: repoUrl,
|
|
54
55
|
branch: options.branch,
|
|
55
|
-
strategy: options.strategy as
|
|
56
|
+
strategy: options.strategy as LegacyStrategy,
|
|
56
57
|
paths,
|
|
57
58
|
};
|
|
58
59
|
|
package/src/commands/auth.ts
CHANGED
|
@@ -12,9 +12,6 @@
|
|
|
12
12
|
* into this machine by writing ~/.hq/cognito-tokens.json; once cached, the
|
|
13
13
|
* session is kept fresh by `hq auth refresh` / `hq-auth-refresh` and consumed
|
|
14
14
|
* by the deploy + sync skills.
|
|
15
|
-
*
|
|
16
|
-
* Note: the top-level `hq login` command authenticates against the HQ package
|
|
17
|
-
* registry (Clerk), not Cognito — that is a different auth stack.
|
|
18
15
|
*/
|
|
19
16
|
|
|
20
17
|
import { Command } from "commander";
|
package/src/commands/cloud.ts
CHANGED
|
@@ -1,125 +1,238 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* hq sync commands —
|
|
3
|
-
*
|
|
2
|
+
* `hq sync` commands — push/pull files between the local HQ tree and the
|
|
3
|
+
* company's S3 vault bucket.
|
|
4
|
+
*
|
|
5
|
+
* VLT-5 model: each command resolves a Cognito access token, asks
|
|
6
|
+
* vault-service for the company's bucket + STS-vended credentials, and
|
|
7
|
+
* runs the operation. No daemon, no init step (handled by `hq onboard`),
|
|
8
|
+
* no long-lived background process — every invocation is self-contained.
|
|
9
|
+
*
|
|
10
|
+
* Subcommands:
|
|
11
|
+
* hq sync push [paths...] — broadcast local file(s) to the vault
|
|
12
|
+
* hq sync pull — pull all permitted files from the vault
|
|
13
|
+
* hq sync status — show local journal summary
|
|
4
14
|
*/
|
|
5
15
|
|
|
6
16
|
import { Command } from "commander";
|
|
7
|
-
import
|
|
17
|
+
import chalk from "chalk";
|
|
18
|
+
import * as fs from "fs";
|
|
19
|
+
import * as path from "path";
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
share,
|
|
23
|
+
sync,
|
|
24
|
+
readJournal,
|
|
25
|
+
getJournalPath,
|
|
26
|
+
type ConflictStrategy,
|
|
27
|
+
} from "@indigoai-us/hq-cloud";
|
|
28
|
+
|
|
29
|
+
import {
|
|
30
|
+
DEFAULT_HQ_ROOT,
|
|
31
|
+
ensureCognitoToken,
|
|
32
|
+
buildVaultConfig,
|
|
33
|
+
} from "../utils/cognito-session.js";
|
|
34
|
+
|
|
35
|
+
interface CommonSyncOptions {
|
|
36
|
+
hqRoot: string;
|
|
37
|
+
company?: string;
|
|
38
|
+
}
|
|
8
39
|
|
|
9
40
|
export function registerCloudCommands(program: Command): void {
|
|
10
41
|
program
|
|
11
|
-
.command("
|
|
12
|
-
.description("
|
|
13
|
-
.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
42
|
+
.command("push")
|
|
43
|
+
.description("Push local file(s) to the company vault on S3")
|
|
44
|
+
.argument("[paths...]", "Paths to push (defaults to current directory)")
|
|
45
|
+
.option(
|
|
46
|
+
"--hq-root <path>",
|
|
47
|
+
`Local HQ tree root (default: ${DEFAULT_HQ_ROOT})`,
|
|
48
|
+
DEFAULT_HQ_ROOT,
|
|
49
|
+
)
|
|
50
|
+
.option(
|
|
51
|
+
"--company <slug>",
|
|
52
|
+
"Company slug or UID (defaults to active company in .hq/config.json)",
|
|
53
|
+
)
|
|
54
|
+
.option(
|
|
55
|
+
"--message <msg>",
|
|
56
|
+
"Optional message attached to journal entries for these uploads",
|
|
57
|
+
)
|
|
58
|
+
.option(
|
|
59
|
+
"--on-conflict <strategy>",
|
|
60
|
+
"Conflict strategy: overwrite | keep | abort (omit for interactive)",
|
|
61
|
+
)
|
|
62
|
+
.action(
|
|
63
|
+
async (
|
|
64
|
+
paths: string[],
|
|
65
|
+
options: CommonSyncOptions & {
|
|
66
|
+
message?: string;
|
|
67
|
+
onConflict?: ConflictStrategy;
|
|
68
|
+
},
|
|
69
|
+
) => {
|
|
70
|
+
try {
|
|
71
|
+
const targetPaths =
|
|
72
|
+
paths && paths.length > 0 ? paths : [process.cwd()];
|
|
26
73
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
.
|
|
31
|
-
try {
|
|
32
|
-
const hqRoot = findHqRoot();
|
|
33
|
-
const { startDaemon } = await import("@indigoai-us/hq-cloud");
|
|
34
|
-
await startDaemon(hqRoot);
|
|
35
|
-
console.log("Sync daemon started. Use 'hq sync status' to check.");
|
|
36
|
-
} catch (error) {
|
|
37
|
-
console.error(
|
|
38
|
-
"Error:",
|
|
39
|
-
error instanceof Error ? error.message : error
|
|
40
|
-
);
|
|
41
|
-
process.exit(1);
|
|
42
|
-
}
|
|
43
|
-
});
|
|
74
|
+
console.log(chalk.bold("\nHQ Sync — Push"));
|
|
75
|
+
console.log(` HQ root: ${options.hqRoot}`);
|
|
76
|
+
console.log(` Company: ${options.company ?? "(from .hq/config.json)"}`);
|
|
77
|
+
console.log(` Paths: ${targetPaths.join(", ")}\n`);
|
|
44
78
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
79
|
+
const accessToken = await ensureCognitoToken();
|
|
80
|
+
const result = await share({
|
|
81
|
+
paths: targetPaths,
|
|
82
|
+
company: options.company,
|
|
83
|
+
message: options.message,
|
|
84
|
+
onConflict: options.onConflict,
|
|
85
|
+
vaultConfig: buildVaultConfig(accessToken),
|
|
86
|
+
hqRoot: options.hqRoot,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
if (result.aborted) {
|
|
90
|
+
console.log(
|
|
91
|
+
chalk.yellow(
|
|
92
|
+
`\n⚠ Push aborted (${result.filesUploaded} uploaded, ${result.filesSkipped} skipped)`,
|
|
93
|
+
),
|
|
94
|
+
);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
console.log(
|
|
99
|
+
chalk.green(
|
|
100
|
+
`\n✓ Pushed ${result.filesUploaded} file(s) (${formatBytes(result.bytesUploaded)}, ${result.filesSkipped} skipped)`,
|
|
101
|
+
),
|
|
102
|
+
);
|
|
103
|
+
} catch (err) {
|
|
104
|
+
console.error(
|
|
105
|
+
chalk.red("\n✗ Push failed:"),
|
|
106
|
+
err instanceof Error ? err.message : String(err),
|
|
107
|
+
);
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
);
|
|
62
112
|
|
|
63
113
|
program
|
|
64
|
-
.command("
|
|
65
|
-
.description("
|
|
66
|
-
.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
114
|
+
.command("pull")
|
|
115
|
+
.description("Pull permitted files from the company vault to local HQ")
|
|
116
|
+
.option(
|
|
117
|
+
"--hq-root <path>",
|
|
118
|
+
`Local HQ tree root (default: ${DEFAULT_HQ_ROOT})`,
|
|
119
|
+
DEFAULT_HQ_ROOT,
|
|
120
|
+
)
|
|
121
|
+
.option(
|
|
122
|
+
"--company <slug>",
|
|
123
|
+
"Company slug or UID (defaults to active company in .hq/config.json)",
|
|
124
|
+
)
|
|
125
|
+
.option(
|
|
126
|
+
"--on-conflict <strategy>",
|
|
127
|
+
"Conflict strategy: overwrite | keep | abort (omit for interactive)",
|
|
128
|
+
)
|
|
129
|
+
.action(
|
|
130
|
+
async (
|
|
131
|
+
options: CommonSyncOptions & {
|
|
132
|
+
onConflict?: ConflictStrategy;
|
|
133
|
+
},
|
|
134
|
+
) => {
|
|
135
|
+
try {
|
|
136
|
+
console.log(chalk.bold("\nHQ Sync — Pull"));
|
|
137
|
+
console.log(` HQ root: ${options.hqRoot}`);
|
|
138
|
+
console.log(` Company: ${options.company ?? "(from .hq/config.json)"}\n`);
|
|
139
|
+
|
|
140
|
+
const accessToken = await ensureCognitoToken();
|
|
141
|
+
const result = await sync({
|
|
142
|
+
company: options.company,
|
|
143
|
+
onConflict: options.onConflict,
|
|
144
|
+
vaultConfig: buildVaultConfig(accessToken),
|
|
145
|
+
hqRoot: options.hqRoot,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
if (result.aborted) {
|
|
149
|
+
console.log(
|
|
150
|
+
chalk.yellow(
|
|
151
|
+
`\n⚠ Pull aborted (${result.filesDownloaded} downloaded, ${result.filesSkipped} skipped, ${result.conflicts} conflicts)`,
|
|
152
|
+
),
|
|
153
|
+
);
|
|
154
|
+
process.exit(1);
|
|
79
155
|
}
|
|
156
|
+
|
|
157
|
+
console.log(
|
|
158
|
+
chalk.green(
|
|
159
|
+
`\n✓ Pulled ${result.filesDownloaded} file(s) (${formatBytes(result.bytesDownloaded)}, ${result.filesSkipped} skipped, ${result.conflicts} conflicts)`,
|
|
160
|
+
),
|
|
161
|
+
);
|
|
162
|
+
} catch (err) {
|
|
163
|
+
console.error(
|
|
164
|
+
chalk.red("\n✗ Pull failed:"),
|
|
165
|
+
err instanceof Error ? err.message : String(err),
|
|
166
|
+
);
|
|
167
|
+
process.exit(1);
|
|
80
168
|
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
"Error:",
|
|
84
|
-
error instanceof Error ? error.message : error
|
|
85
|
-
);
|
|
86
|
-
process.exit(1);
|
|
87
|
-
}
|
|
88
|
-
});
|
|
169
|
+
},
|
|
170
|
+
);
|
|
89
171
|
|
|
90
172
|
program
|
|
91
|
-
.command("
|
|
92
|
-
.description("
|
|
93
|
-
.
|
|
173
|
+
.command("status")
|
|
174
|
+
.description("Show local sync journal summary")
|
|
175
|
+
.option(
|
|
176
|
+
"--hq-root <path>",
|
|
177
|
+
`Local HQ tree root (default: ${DEFAULT_HQ_ROOT})`,
|
|
178
|
+
DEFAULT_HQ_ROOT,
|
|
179
|
+
)
|
|
180
|
+
.action((options: { hqRoot: string }) => {
|
|
94
181
|
try {
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
182
|
+
const journalPath = getJournalPath(options.hqRoot);
|
|
183
|
+
if (!fs.existsSync(journalPath)) {
|
|
184
|
+
console.log(chalk.dim("No sync journal yet — run `hq sync push` or `hq sync pull` to create one."));
|
|
185
|
+
console.log(chalk.dim(` Expected at: ${journalPath}`));
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const journal = readJournal(options.hqRoot);
|
|
190
|
+
const entries = Object.entries(journal.files ?? {});
|
|
191
|
+
const lastSyncTimes = entries
|
|
192
|
+
.map(([, entry]) => entry.syncedAt)
|
|
193
|
+
.filter((t): t is string => typeof t === "string")
|
|
194
|
+
.sort();
|
|
195
|
+
const lastSync = lastSyncTimes.at(-1) ?? "never";
|
|
196
|
+
const totalBytes = entries.reduce(
|
|
197
|
+
(acc, [, entry]) => acc + (entry.size ?? 0),
|
|
198
|
+
0,
|
|
103
199
|
);
|
|
104
|
-
process.exit(1);
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
200
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
201
|
+
const configPath = path.join(options.hqRoot, ".hq", "config.json");
|
|
202
|
+
let activeCompany: string | undefined;
|
|
203
|
+
if (fs.existsSync(configPath)) {
|
|
204
|
+
try {
|
|
205
|
+
const cfg = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
206
|
+
activeCompany = cfg.activeCompany;
|
|
207
|
+
} catch {
|
|
208
|
+
// ignore
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
console.log(chalk.bold("\nHQ Sync — Status"));
|
|
213
|
+
console.log(` HQ root: ${options.hqRoot}`);
|
|
214
|
+
console.log(` Active company: ${activeCompany ?? chalk.dim("(none)")}`);
|
|
215
|
+
console.log(` Tracked files: ${entries.length}`);
|
|
216
|
+
console.log(` Total size: ${formatBytes(totalBytes)}`);
|
|
217
|
+
console.log(` Last sync: ${lastSync}`);
|
|
218
|
+
console.log(` Journal: ${journalPath}`);
|
|
219
|
+
} catch (err) {
|
|
118
220
|
console.error(
|
|
119
|
-
"
|
|
120
|
-
|
|
221
|
+
chalk.red("✗ Status failed:"),
|
|
222
|
+
err instanceof Error ? err.message : String(err),
|
|
121
223
|
);
|
|
122
224
|
process.exit(1);
|
|
123
225
|
}
|
|
124
226
|
});
|
|
125
227
|
}
|
|
228
|
+
|
|
229
|
+
function formatBytes(bytes: number): string {
|
|
230
|
+
if (bytes === 0) return "0 B";
|
|
231
|
+
const units = ["B", "KB", "MB", "GB"];
|
|
232
|
+
const exponent = Math.min(
|
|
233
|
+
Math.floor(Math.log(bytes) / Math.log(1024)),
|
|
234
|
+
units.length - 1,
|
|
235
|
+
);
|
|
236
|
+
const value = bytes / Math.pow(1024, exponent);
|
|
237
|
+
return `${value.toFixed(value >= 100 || exponent === 0 ? 0 : 1)} ${units[exponent]}`;
|
|
238
|
+
}
|