@indigoai-us/hq-cli 5.60.0 → 5.62.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/commands/agents.d.ts +109 -0
- package/dist/commands/agents.js +385 -0
- package/dist/commands/db-migrate.d.ts +6 -0
- package/dist/commands/db-migrate.js +42 -0
- package/dist/commands/db-provision.d.ts +15 -0
- package/dist/commands/db-provision.js +78 -0
- package/dist/commands/db-sql.d.ts +9 -0
- package/dist/commands/db-sql.js +81 -0
- package/dist/commands/db-status.d.ts +7 -0
- package/dist/commands/db-status.js +70 -0
- package/dist/commands/db.d.ts +9 -0
- package/dist/commands/db.js +23 -0
- package/dist/commands/integrations.d.ts +78 -0
- package/dist/commands/integrations.js +309 -0
- package/dist/commands/members.js +4 -4
- package/dist/commands/outposts.d.ts +60 -0
- package/dist/commands/outposts.js +255 -0
- package/dist/commands/pack-install.d.ts +7 -1
- package/dist/commands/pack-install.js +86 -15
- package/dist/commands/packs.d.ts +2 -1
- package/dist/commands/packs.js +13 -8
- package/dist/commands/secrets.d.ts +13 -0
- package/dist/commands/secrets.js +149 -10
- package/dist/commands/skill.d.ts +153 -0
- package/dist/commands/skill.js +593 -0
- package/dist/commands/workers.d.ts +48 -0
- package/dist/commands/workers.js +229 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.js +14 -240
- package/dist/lib/db/control-plane.d.ts +45 -0
- package/dist/lib/db/control-plane.js +81 -0
- package/dist/lib/db/local.d.ts +49 -0
- package/dist/lib/db/local.js +106 -0
- package/dist/lib/db/migrate.d.ts +41 -0
- package/dist/lib/db/migrate.js +104 -0
- package/dist/lib/db/paths.d.ts +56 -0
- package/dist/lib/db/paths.js +103 -0
- package/dist/lib/db/remote-engine.d.ts +58 -0
- package/dist/lib/db/remote-engine.js +90 -0
- package/dist/lib/db/remote-sql.d.ts +22 -0
- package/dist/lib/db/remote-sql.js +39 -0
- package/dist/lib/db/sql.d.ts +49 -0
- package/dist/lib/db/sql.js +132 -0
- package/dist/main.d.ts +7 -0
- package/dist/main.js +272 -0
- package/dist/utils/cognito-session.js +3 -3
- package/dist/utils/sandbox-runner-client.d.ts +13 -0
- package/dist/utils/sandbox-runner-client.js +83 -6
- package/dist/utils/version-check.d.ts +6 -0
- package/dist/utils/version-check.js +78 -2
- package/package.json +9 -1
- package/pnpm-workspace.yaml +2 -0
- package/src/commands/agents.test.ts +297 -0
- package/src/commands/agents.ts +561 -0
- package/src/commands/db-migrate.ts +55 -0
- package/src/commands/db-provision.ts +102 -0
- package/src/commands/db-sql.ts +124 -0
- package/src/commands/db-status.ts +100 -0
- package/src/commands/db.ts +26 -0
- package/src/commands/integrations.test.ts +284 -0
- package/src/commands/integrations.ts +438 -0
- package/src/commands/members.ts +2 -2
- package/src/commands/outposts.test.ts +177 -0
- package/src/commands/outposts.ts +338 -0
- package/src/commands/pack-install.ts +115 -18
- package/src/commands/pack-update-cache.test.ts +149 -0
- package/src/commands/packs.ts +28 -7
- package/src/commands/secrets.parse-destination.test.ts +38 -0
- package/src/commands/secrets.test.ts +342 -0
- package/src/commands/secrets.ts +227 -13
- package/src/commands/skill.test.ts +770 -0
- package/src/commands/skill.ts +796 -0
- package/src/commands/workers.test.ts +158 -0
- package/src/commands/workers.ts +298 -0
- package/src/index.test.ts +32 -0
- package/src/index.ts +11 -274
- package/src/lib/db/control-plane.test.ts +59 -0
- package/src/lib/db/control-plane.ts +113 -0
- package/src/lib/db/local.test.ts +81 -0
- package/src/lib/db/local.ts +148 -0
- package/src/lib/db/migrate.test.ts +133 -0
- package/src/lib/db/migrate.ts +137 -0
- package/src/lib/db/paths.test.ts +112 -0
- package/src/lib/db/paths.ts +128 -0
- package/src/lib/db/remote-engine.test.ts +44 -0
- package/src/lib/db/remote-engine.ts +148 -0
- package/src/lib/db/remote-sql.test.ts +32 -0
- package/src/lib/db/remote-sql.ts +62 -0
- package/src/lib/db/sql.test.ts +106 -0
- package/src/lib/db/sql.ts +192 -0
- package/src/main.ts +314 -0
- package/src/utils/cognito-session.ts +1 -1
- package/src/utils/sandbox-runner-client.test.ts +128 -0
- package/src/utils/sandbox-runner-client.ts +100 -4
- package/src/utils/version-check.test.ts +30 -0
- package/src/utils/version-check.ts +72 -0
- package/test/commands/db-tenant-isolation.test.ts +94 -0
- package/test/commands/db.test.ts +85 -0
|
@@ -8,7 +8,9 @@ import { CLI_VERSION } from "../cli-version.js";
|
|
|
8
8
|
const PACKAGE_NAME = "@indigoai-us/hq-cli";
|
|
9
9
|
const REGISTRY_URL = `https://registry.npmjs.org/${encodeURIComponent(PACKAGE_NAME)}/latest`;
|
|
10
10
|
const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
11
|
+
const CACHE_TTL_JITTER_MS = 60 * 60 * 1000;
|
|
11
12
|
const FETCH_TIMEOUT_MS = 3_000;
|
|
13
|
+
const REFRESH_LOCK_STALE_MS = 10 * 60 * 1000;
|
|
12
14
|
|
|
13
15
|
interface CacheEntry {
|
|
14
16
|
latest: string;
|
|
@@ -19,6 +21,10 @@ function cachePath(): string {
|
|
|
19
21
|
return path.join(os.homedir(), ".hq", "version-check.json");
|
|
20
22
|
}
|
|
21
23
|
|
|
24
|
+
function lockPath(): string {
|
|
25
|
+
return path.join(os.homedir(), ".hq", "version-check.lock");
|
|
26
|
+
}
|
|
27
|
+
|
|
22
28
|
function isOptedOut(): boolean {
|
|
23
29
|
return process.env.HQ_NO_UPDATE_CHECK === "1";
|
|
24
30
|
}
|
|
@@ -49,6 +55,58 @@ function writeCache(entry: CacheEntry): void {
|
|
|
49
55
|
}
|
|
50
56
|
}
|
|
51
57
|
|
|
58
|
+
function freshEnough(entry: CacheEntry, now = Date.now()): boolean {
|
|
59
|
+
const jitter = Math.floor(Math.random() * CACHE_TTL_JITTER_MS);
|
|
60
|
+
return now - entry.fetchedAt <= CACHE_TTL_MS - jitter;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function isKnownNoninteractiveStatusProbe(argv: readonly string[] = process.argv): boolean {
|
|
64
|
+
const args = argv.slice(2);
|
|
65
|
+
const positional = args.filter((arg) => !arg.startsWith("-"));
|
|
66
|
+
const json = args.includes("--json") || !process.stdout.isTTY;
|
|
67
|
+
if (!json) return false;
|
|
68
|
+
if (positional[0] === "mcp" && positional[1] === "status") return true;
|
|
69
|
+
if (positional[0] === "packs" && (positional[1] === "list" || positional[1] === "ls")) return true;
|
|
70
|
+
if (
|
|
71
|
+
positional[0] === "packages" &&
|
|
72
|
+
positional[1] === "packs" &&
|
|
73
|
+
(positional[2] === "list" || positional[2] === "ls")
|
|
74
|
+
) {
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
if ((positional[0] === "sources" || positional[0] === "signals") && positional[1] === "list") {
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function acquireRefreshLock(now = Date.now()): (() => void) | null {
|
|
84
|
+
const dir = lockPath();
|
|
85
|
+
try {
|
|
86
|
+
fs.mkdirSync(path.dirname(dir), { recursive: true });
|
|
87
|
+
fs.mkdirSync(dir);
|
|
88
|
+
fs.writeFileSync(path.join(dir, "owner"), `${process.pid}\n${now}\n`);
|
|
89
|
+
return () => {
|
|
90
|
+
try {
|
|
91
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
92
|
+
} catch {
|
|
93
|
+
// best-effort lock cleanup
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
} catch {
|
|
97
|
+
try {
|
|
98
|
+
const stat = fs.statSync(dir);
|
|
99
|
+
if (now - stat.mtimeMs > REFRESH_LOCK_STALE_MS) {
|
|
100
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
101
|
+
return acquireRefreshLock(now);
|
|
102
|
+
}
|
|
103
|
+
} catch {
|
|
104
|
+
// ignore lock inspection failures
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
52
110
|
export function maybeWarnNewVersion(): void {
|
|
53
111
|
if (isOptedOut()) return;
|
|
54
112
|
const entry = readCache();
|
|
@@ -68,7 +126,14 @@ export function maybeWarnNewVersion(): void {
|
|
|
68
126
|
|
|
69
127
|
export async function refreshVersionCache(): Promise<void> {
|
|
70
128
|
if (isOptedOut()) return;
|
|
129
|
+
if (isKnownNoninteractiveStatusProbe()) return;
|
|
130
|
+
const existing = readCache();
|
|
131
|
+
if (existing && freshEnough(existing)) return;
|
|
132
|
+
const releaseLock = acquireRefreshLock();
|
|
133
|
+
if (!releaseLock) return;
|
|
71
134
|
try {
|
|
135
|
+
const lockedExisting = readCache();
|
|
136
|
+
if (lockedExisting && freshEnough(lockedExisting)) return;
|
|
72
137
|
const res = await fetch(REGISTRY_URL, {
|
|
73
138
|
headers: { Accept: "application/json" },
|
|
74
139
|
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
@@ -79,5 +144,12 @@ export async function refreshVersionCache(): Promise<void> {
|
|
|
79
144
|
writeCache({ latest: body.version, fetchedAt: Date.now() });
|
|
80
145
|
} catch {
|
|
81
146
|
// best-effort; offline / registry down / timeout — silent
|
|
147
|
+
} finally {
|
|
148
|
+
releaseLock();
|
|
82
149
|
}
|
|
83
150
|
}
|
|
151
|
+
|
|
152
|
+
export const __test__ = {
|
|
153
|
+
CACHE_TTL_MS,
|
|
154
|
+
isKnownNoninteractiveStatusProbe,
|
|
155
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tenant isolation regression suite (US-011) — CLI + control-plane client side.
|
|
3
|
+
*
|
|
4
|
+
* API matrix lives primarily in hq-pro; this suite asserts CLI path denial and
|
|
5
|
+
* secret-ref namespacing expectations.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import os from "node:os";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
|
|
12
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
13
|
+
|
|
14
|
+
import { ControlPlaneDbClient } from "../../src/lib/db/control-plane.js";
|
|
15
|
+
import { openLocalDb } from "../../src/lib/db/local.js";
|
|
16
|
+
import { resolveLocalDbPath } from "../../src/lib/db/paths.js";
|
|
17
|
+
import { runLocalSql } from "../../src/lib/db/sql.js";
|
|
18
|
+
|
|
19
|
+
const temps: string[] = [];
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
while (temps.length) {
|
|
23
|
+
const d = temps.pop();
|
|
24
|
+
if (d) fs.rmSync(d, { recursive: true, force: true });
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe("tenant isolation (local + remote client)", () => {
|
|
29
|
+
it("company A cannot open company B local path by override", () => {
|
|
30
|
+
const home = fs.mkdtempSync(path.join(os.tmpdir(), "hq-db-iso-"));
|
|
31
|
+
temps.push(home);
|
|
32
|
+
openLocalDb("alpha", { home }).close();
|
|
33
|
+
openLocalDb("beta", { home }).close();
|
|
34
|
+
const betaPath = resolveLocalDbPath("beta", { home });
|
|
35
|
+
|
|
36
|
+
expect(() =>
|
|
37
|
+
runLocalSql({
|
|
38
|
+
company: "alpha",
|
|
39
|
+
home,
|
|
40
|
+
sql: "SELECT 1",
|
|
41
|
+
dbPathOverride: betaPath,
|
|
42
|
+
}),
|
|
43
|
+
).toThrow(/denied/i);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("Y cannot provision X via control plane (403)", async () => {
|
|
47
|
+
const fetchImpl = vi.fn(async () => {
|
|
48
|
+
return new Response(JSON.stringify({ error: "Forbidden" }), {
|
|
49
|
+
status: 403,
|
|
50
|
+
});
|
|
51
|
+
}) as unknown as typeof fetch;
|
|
52
|
+
|
|
53
|
+
const client = new ControlPlaneDbClient({
|
|
54
|
+
baseUrl: "https://api.example",
|
|
55
|
+
getAccessToken: async () => "tok-y",
|
|
56
|
+
fetchImpl,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
await expect(
|
|
60
|
+
client.provision({ companyUid: "cmp_x", companySlug: "x" }),
|
|
61
|
+
).rejects.toMatchObject({ status: 403 });
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("secret refs are company-namespaced", async () => {
|
|
65
|
+
const fetchImpl = vi.fn(async () => {
|
|
66
|
+
return new Response(
|
|
67
|
+
JSON.stringify({
|
|
68
|
+
ok: true,
|
|
69
|
+
companyUid: "cmp_x",
|
|
70
|
+
companySlug: "x",
|
|
71
|
+
engineId: "aurora-dsql",
|
|
72
|
+
resourceArn: "arn:aws:dsql:us-east-1:1:cluster/x",
|
|
73
|
+
region: "us-east-1",
|
|
74
|
+
status: "ready",
|
|
75
|
+
secretRef: "ssm:/hq/cmp_x/secrets/DB/REMOTE/X",
|
|
76
|
+
idempotent: false,
|
|
77
|
+
}),
|
|
78
|
+
{ status: 200 },
|
|
79
|
+
);
|
|
80
|
+
}) as unknown as typeof fetch;
|
|
81
|
+
|
|
82
|
+
const client = new ControlPlaneDbClient({
|
|
83
|
+
baseUrl: "https://api.example",
|
|
84
|
+
getAccessToken: async () => "tok",
|
|
85
|
+
fetchImpl,
|
|
86
|
+
});
|
|
87
|
+
const result = await client.provision({
|
|
88
|
+
companyUid: "cmp_x",
|
|
89
|
+
companySlug: "x",
|
|
90
|
+
});
|
|
91
|
+
expect(result.secretRef).toContain("cmp_x");
|
|
92
|
+
expect(result.secretRef).not.toContain("cmp_y");
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* C1 local `hq db` harness (vault-databases US-006).
|
|
3
|
+
*
|
|
4
|
+
* Offline only: temp HOME + temp HQ root, zero network.
|
|
5
|
+
* Covers path resolution, status auto-create, sql select, migrate apply/idempotent.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import os from "node:os";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
|
|
12
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
13
|
+
|
|
14
|
+
import { ensureAndStatusLocalDb } from "../../src/lib/db/local.js";
|
|
15
|
+
import { migrateLocalDb } from "../../src/lib/db/migrate.js";
|
|
16
|
+
import { resolveLocalDbPath } from "../../src/lib/db/paths.js";
|
|
17
|
+
import { runLocalSql } from "../../src/lib/db/sql.js";
|
|
18
|
+
|
|
19
|
+
const temps: string[] = [];
|
|
20
|
+
|
|
21
|
+
function fixture(): { home: string; hqRoot: string } {
|
|
22
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), "hq-db-harness-"));
|
|
23
|
+
temps.push(root);
|
|
24
|
+
const home = path.join(root, "home");
|
|
25
|
+
const hqRoot = path.join(root, "hq");
|
|
26
|
+
fs.mkdirSync(home, { recursive: true });
|
|
27
|
+
fs.mkdirSync(hqRoot, { recursive: true });
|
|
28
|
+
return { home, hqRoot };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
afterEach(() => {
|
|
32
|
+
while (temps.length > 0) {
|
|
33
|
+
const d = temps.pop();
|
|
34
|
+
if (d) fs.rmSync(d, { recursive: true, force: true });
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe("hq db C1 harness (offline)", () => {
|
|
39
|
+
it("path resolution for two company slugs under documented root", () => {
|
|
40
|
+
const { home } = fixture();
|
|
41
|
+
const a = resolveLocalDbPath("indigo", { home });
|
|
42
|
+
const b = resolveLocalDbPath("acme", { home });
|
|
43
|
+
expect(a).toContain(path.join(".hq", "db", "indigo", "vault.db"));
|
|
44
|
+
expect(b).toContain(path.join(".hq", "db", "acme", "vault.db"));
|
|
45
|
+
expect(a).not.toBe(b);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("status auto-creates WAL SQLite and reopen is stable", () => {
|
|
49
|
+
const { home } = fixture();
|
|
50
|
+
const first = ensureAndStatusLocalDb("fixture", { home });
|
|
51
|
+
expect(first.healthy).toBe(true);
|
|
52
|
+
expect(first.journalMode?.toLowerCase()).toBe("wal");
|
|
53
|
+
expect(fs.existsSync(first.path)).toBe(true);
|
|
54
|
+
|
|
55
|
+
const size1 = fs.statSync(first.path).size;
|
|
56
|
+
const second = ensureAndStatusLocalDb("fixture", { home });
|
|
57
|
+
expect(second.healthy).toBe(true);
|
|
58
|
+
expect(fs.statSync(second.path).size).toBeGreaterThanOrEqual(size1);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("sql select after migrate smoke table", () => {
|
|
62
|
+
const { home, hqRoot } = fixture();
|
|
63
|
+
const migDir = path.join(hqRoot, "companies", "fixture", "db", "migrations");
|
|
64
|
+
fs.mkdirSync(migDir, { recursive: true });
|
|
65
|
+
fs.writeFileSync(
|
|
66
|
+
path.join(migDir, "001_smoke.sql"),
|
|
67
|
+
"CREATE TABLE smoke (id INTEGER PRIMARY KEY, v TEXT);\nINSERT INTO smoke (v) VALUES ('ok');\n",
|
|
68
|
+
"utf8",
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const mig = migrateLocalDb({ company: "fixture", home, hqRoot });
|
|
72
|
+
expect(mig.applied).toContain("001_smoke.sql");
|
|
73
|
+
|
|
74
|
+
const rows = runLocalSql({
|
|
75
|
+
company: "fixture",
|
|
76
|
+
home,
|
|
77
|
+
sql: "SELECT v FROM smoke",
|
|
78
|
+
});
|
|
79
|
+
expect(rows.rows[0]).toMatchObject({ v: "ok" });
|
|
80
|
+
|
|
81
|
+
const again = migrateLocalDb({ company: "fixture", home, hqRoot });
|
|
82
|
+
expect(again.applied).toEqual([]);
|
|
83
|
+
expect(again.skipped.length).toBeGreaterThan(0);
|
|
84
|
+
});
|
|
85
|
+
});
|