@sechroom/cli 2026.6.3 → 2026.6.5
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/index.js +268 -27
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -255,6 +255,37 @@ function spinner(text) {
|
|
|
255
255
|
stop: clear
|
|
256
256
|
};
|
|
257
257
|
}
|
|
258
|
+
function canPrompt() {
|
|
259
|
+
return !quiet && Boolean(process.stdin.isTTY) && Boolean(process.stderr.isTTY);
|
|
260
|
+
}
|
|
261
|
+
async function promptYesNo(question) {
|
|
262
|
+
if (!canPrompt()) return false;
|
|
263
|
+
const { createInterface } = await import("readline");
|
|
264
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
265
|
+
try {
|
|
266
|
+
const answer = await new Promise((resolve) => {
|
|
267
|
+
rl.question(`${question} [y/N] `, resolve);
|
|
268
|
+
});
|
|
269
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
270
|
+
} finally {
|
|
271
|
+
rl.close();
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
async function promptText(question, def) {
|
|
275
|
+
if (!canPrompt()) return def ?? "";
|
|
276
|
+
const { createInterface } = await import("readline");
|
|
277
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
278
|
+
try {
|
|
279
|
+
const suffix = def ? ` [${def}]` : "";
|
|
280
|
+
const answer = await new Promise((resolve) => {
|
|
281
|
+
rl.question(`${question}${suffix} `, resolve);
|
|
282
|
+
});
|
|
283
|
+
const trimmed = answer.trim();
|
|
284
|
+
return trimmed.length > 0 ? trimmed : def ?? "";
|
|
285
|
+
} finally {
|
|
286
|
+
rl.close();
|
|
287
|
+
}
|
|
288
|
+
}
|
|
258
289
|
async function withSpinner(text, fn) {
|
|
259
290
|
const s = spinner(text);
|
|
260
291
|
try {
|
|
@@ -440,37 +471,68 @@ function parseTagArtifactId(id) {
|
|
|
440
471
|
const tags = id.slice("tag:".length).split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
441
472
|
return tags.length > 0 ? tags : null;
|
|
442
473
|
}
|
|
443
|
-
async function
|
|
474
|
+
async function getPersonalWorkspaceId(cfg) {
|
|
475
|
+
const client = await makeClient(cfg);
|
|
476
|
+
const { data } = await client.GET("/me/personal-workspace", {});
|
|
477
|
+
return data?.workspaceId ?? null;
|
|
478
|
+
}
|
|
479
|
+
async function fetchMemoryFields(cfg, id) {
|
|
480
|
+
const client = await makeClient(cfg);
|
|
481
|
+
const { data } = await client.GET("/memories/{memoryId}", { params: { path: { memoryId: id } } });
|
|
482
|
+
const env = data;
|
|
483
|
+
return env?.item ?? env ?? null;
|
|
484
|
+
}
|
|
485
|
+
async function resolveInstruction(cfg, section, personalWorkspaceId) {
|
|
444
486
|
const client = await makeClient(cfg);
|
|
445
487
|
for (const artifact of section.artifacts) {
|
|
446
488
|
const tags = parseTagArtifactId(artifact.id);
|
|
447
489
|
if (!tags) continue;
|
|
448
490
|
const { data } = await client.POST("/memories/search", {
|
|
449
|
-
body: {
|
|
450
|
-
query: null,
|
|
451
|
-
textQuery: null,
|
|
452
|
-
semanticQuery: artifact.title ?? "role instruction template",
|
|
453
|
-
hybrid: true,
|
|
454
|
-
limit: 1,
|
|
455
|
-
includeArchived: false,
|
|
456
|
-
includeSystem: false,
|
|
457
|
-
tags
|
|
458
|
-
}
|
|
491
|
+
body: { query: null, textQuery: null, semanticQuery: artifact.title ?? "role instruction template", hybrid: true, limit: 1, includeArchived: false, includeSystem: false, tags }
|
|
459
492
|
});
|
|
460
493
|
const hits = data ?? [];
|
|
461
494
|
if (hits.length === 0) continue;
|
|
462
|
-
const
|
|
463
|
-
const
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
495
|
+
const templateId = hits[0].id;
|
|
496
|
+
const template = await fetchMemoryFields(cfg, templateId);
|
|
497
|
+
if (typeof template?.text !== "string" || template.text.length === 0) continue;
|
|
498
|
+
const templateTags = template.tags ?? tags;
|
|
499
|
+
if (personalWorkspaceId) {
|
|
500
|
+
const { data: ovr } = await client.POST("/memories/search", {
|
|
501
|
+
body: { query: null, textQuery: null, semanticQuery: "role override", hybrid: true, limit: 1, includeArchived: false, includeSystem: false, tags: ["sechroom:role:override", `sechroom:template-ref:${templateId}`], owner: { type: "Workspace", id: personalWorkspaceId } }
|
|
502
|
+
});
|
|
503
|
+
const ovrHits = ovr ?? [];
|
|
504
|
+
if (ovrHits.length > 0) {
|
|
505
|
+
const override = await fetchMemoryFields(cfg, ovrHits[0].id);
|
|
506
|
+
if (typeof override?.text === "string" && override.text.length > 0) {
|
|
507
|
+
return { title: override.title ?? template.title ?? artifact.title, body: override.text, source: "override", templateId, templateTags };
|
|
508
|
+
}
|
|
509
|
+
}
|
|
470
510
|
}
|
|
511
|
+
return { title: template.title ?? artifact.title, body: template.text, source: "template", templateId, templateTags };
|
|
471
512
|
}
|
|
472
513
|
return null;
|
|
473
514
|
}
|
|
515
|
+
async function createOverride(cfg, template, personalWorkspaceId) {
|
|
516
|
+
const client = await makeClient(cfg);
|
|
517
|
+
const overrideTags = template.templateTags.filter(
|
|
518
|
+
(t) => t !== "sechroom:role:template" && !t.startsWith("sechroom:bundle:") && !t.startsWith("sechroom:template-ref:")
|
|
519
|
+
);
|
|
520
|
+
overrideTags.push("sechroom:role:override", `sechroom:template-ref:${template.templateId}`);
|
|
521
|
+
const { error } = await client.POST("/memories", {
|
|
522
|
+
body: {
|
|
523
|
+
text: template.body,
|
|
524
|
+
type: "reference",
|
|
525
|
+
content: "{}",
|
|
526
|
+
confidence: 1,
|
|
527
|
+
source: "cli-agent-instructions-customize",
|
|
528
|
+
archetype: "Document",
|
|
529
|
+
title: template.title ?? null,
|
|
530
|
+
tags: overrideTags,
|
|
531
|
+
owner: { type: "Workspace", id: personalWorkspaceId }
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
if (error) throw new Error(`creating personal copy failed: ${JSON.stringify(error)}`);
|
|
535
|
+
}
|
|
474
536
|
|
|
475
537
|
// src/setup/apply.ts
|
|
476
538
|
var BLOCK_BEGIN = "<!-- @sechroom/cli:begin (managed \u2014 re-run `sechroom setup agent-files` to refresh) -->";
|
|
@@ -557,11 +619,12 @@ async function applyClient(cfg, setup, target, opts) {
|
|
|
557
619
|
if (!section) {
|
|
558
620
|
actions.push({ kind: "instruction", path: target.instruction.path, status: "skipped", note: `no instruction-file section on surface '${target.instruction.surfaceKey}'` });
|
|
559
621
|
} else {
|
|
560
|
-
const resolved = await
|
|
622
|
+
const resolved = await resolveInstruction(cfg, section, opts.personalWorkspaceId);
|
|
561
623
|
if (!resolved) {
|
|
562
624
|
actions.push({ kind: "instruction", path: target.instruction.path, status: "skipped", note: "no role template found in this tenant \u2014 install the SEM Starter bundle, then re-run `sechroom setup agent-files`" });
|
|
563
625
|
} else {
|
|
564
|
-
|
|
626
|
+
const action = writeInstructionBlock(target.instruction.path, resolved.body, opts.dryRun);
|
|
627
|
+
actions.push(resolved.source === "override" ? { ...action, note: "your personal copy" } : action);
|
|
565
628
|
}
|
|
566
629
|
}
|
|
567
630
|
}
|
|
@@ -569,8 +632,9 @@ async function applyClient(cfg, setup, target, opts) {
|
|
|
569
632
|
}
|
|
570
633
|
|
|
571
634
|
// src/setup/clients.ts
|
|
635
|
+
import { existsSync as existsSync3 } from "fs";
|
|
572
636
|
import { homedir as homedir2 } from "os";
|
|
573
|
-
import { join as join2 } from "path";
|
|
637
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
574
638
|
function claudeDesktopConfigPath(home) {
|
|
575
639
|
switch (process.platform) {
|
|
576
640
|
case "darwin":
|
|
@@ -612,8 +676,49 @@ function clientTargets(cwd) {
|
|
|
612
676
|
}
|
|
613
677
|
var ALL_CLIENT_KEYS = ["claude-code", "claude-desktop", "codex", "cursor"];
|
|
614
678
|
var DEFAULT_CLIENT_KEY = "claude-code";
|
|
679
|
+
function detectInstalledClients(cwd) {
|
|
680
|
+
const home = homedir2();
|
|
681
|
+
const detected = [];
|
|
682
|
+
if (existsSync3(join2(home, ".claude"))) detected.push("claude-code");
|
|
683
|
+
if (existsSync3(dirname2(claudeDesktopConfigPath(home)))) detected.push("claude-desktop");
|
|
684
|
+
if (existsSync3(join2(home, ".codex"))) detected.push("codex");
|
|
685
|
+
if (existsSync3(join2(home, ".cursor")) || existsSync3(join2(cwd, ".cursor"))) detected.push("cursor");
|
|
686
|
+
return detected;
|
|
687
|
+
}
|
|
615
688
|
|
|
616
689
|
// src/commands/setup.ts
|
|
690
|
+
function copyChoice(opts) {
|
|
691
|
+
return opts.copy === true ? "yes" : opts.copy === false ? "no" : "ask";
|
|
692
|
+
}
|
|
693
|
+
async function maybeOfferCopies(cfg, setup, targets, keys, personalWorkspaceId, choice) {
|
|
694
|
+
if (!personalWorkspaceId || choice === "no") return;
|
|
695
|
+
const seen = /* @__PURE__ */ new Set();
|
|
696
|
+
for (const key of keys) {
|
|
697
|
+
const instr = targets[key]?.instruction;
|
|
698
|
+
if (!instr || seen.has(instr.surfaceKey)) continue;
|
|
699
|
+
seen.add(instr.surfaceKey);
|
|
700
|
+
const section = findSection(findSurface(setup, instr.surfaceKey), SectionType.InstructionFile);
|
|
701
|
+
if (!section) continue;
|
|
702
|
+
const resolved = await resolveInstruction(cfg, section, personalWorkspaceId);
|
|
703
|
+
if (!resolved || resolved.source === "override") continue;
|
|
704
|
+
let make = choice === "yes";
|
|
705
|
+
if (choice === "ask") {
|
|
706
|
+
process.stderr.write(
|
|
707
|
+
`
|
|
708
|
+
The ${instr.surfaceKey} agent instructions are the shared template.
|
|
709
|
+
Make a personal copy to tailor them for your workflow \u2014 your agent uses your
|
|
710
|
+
version, the shared template stays clean, and you can discard back anytime.
|
|
711
|
+
`
|
|
712
|
+
);
|
|
713
|
+
make = await promptYesNo("Make a personal copy to customise?");
|
|
714
|
+
}
|
|
715
|
+
if (make) {
|
|
716
|
+
await createOverride(cfg, resolved, personalWorkspaceId);
|
|
717
|
+
process.stderr.write(`\u2713 personal copy created for ${instr.surfaceKey} \u2014 edit it on the Agent setup page or via the API.
|
|
718
|
+
`);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
}
|
|
617
722
|
function resolveClientKeys(raw) {
|
|
618
723
|
const targets = clientTargets(process.cwd());
|
|
619
724
|
if (raw === "all") return [...ALL_CLIENT_KEYS];
|
|
@@ -636,19 +741,24 @@ ${client.label} (${client.key}):
|
|
|
636
741
|
}
|
|
637
742
|
}
|
|
638
743
|
function registerInit(program2) {
|
|
639
|
-
program2.command("init").description("Wire this project for sechroom: write MCP config + agent instruction files from the server's setup descriptors").option("--client <list>", `comma-separated clients (${ALL_CLIENT_KEYS.join(", ")}) or 'all'`, DEFAULT_CLIENT_KEY).option("--dry-run", "print what would be written without writing", false).option("--mcp-only", "only write MCP config (skip agent files)", false).option("--agent-files-only", "only write agent instruction files (skip MCP config)", false).action(async (opts, cmd) => {
|
|
744
|
+
program2.command("init").description("Wire this project for sechroom: write MCP config + agent instruction files from the server's setup descriptors").option("--client <list>", `comma-separated clients (${ALL_CLIENT_KEYS.join(", ")}) or 'all'`, DEFAULT_CLIENT_KEY).option("--dry-run", "print what would be written without writing", false).option("--mcp-only", "only write MCP config (skip agent files)", false).option("--agent-files-only", "only write agent instruction files (skip MCP config)", false).option("--copy", "make a personal copy of the agent instructions you can edit (default: prompt on a TTY, else skip)").action(async (opts, cmd) => {
|
|
640
745
|
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
641
746
|
const setup = await withSpinner("Fetching setup descriptors", () => fetchSetup(cfg));
|
|
642
747
|
const targets = clientTargets(process.cwd());
|
|
643
748
|
const keys = resolveClientKeys(opts.client);
|
|
644
749
|
const json = cmd.optsWithGlobals().json;
|
|
750
|
+
const personalWorkspaceId = await getPersonalWorkspaceId(cfg);
|
|
751
|
+
if (!opts.dryRun && !opts.mcpOnly) {
|
|
752
|
+
await maybeOfferCopies(cfg, setup, targets, keys, personalWorkspaceId, copyChoice(opts));
|
|
753
|
+
}
|
|
645
754
|
const result = [];
|
|
646
755
|
for (const key of keys) {
|
|
647
756
|
const target = targets[key];
|
|
648
757
|
const actions = await applyClient(cfg, setup, target, {
|
|
649
758
|
dryRun: Boolean(opts.dryRun),
|
|
650
759
|
mcp: !opts.agentFilesOnly,
|
|
651
|
-
agentFiles: !opts.mcpOnly
|
|
760
|
+
agentFiles: !opts.mcpOnly,
|
|
761
|
+
personalWorkspaceId
|
|
652
762
|
});
|
|
653
763
|
result.push({ client: key, actions });
|
|
654
764
|
if (!json) printActions(target, actions);
|
|
@@ -675,8 +785,8 @@ function registerSetup(program2) {
|
|
|
675
785
|
setup.command("mcp <client>").description(`Write only the MCP config for a client (${ALL_CLIENT_KEYS.join(", ")})`).option("--dry-run", "print what would be written without writing", false).action(async (client, opts, cmd) => {
|
|
676
786
|
await runSingle(client, cmd, { dryRun: Boolean(opts.dryRun), mcp: true, agentFiles: false });
|
|
677
787
|
});
|
|
678
|
-
setup.command("agent-files <client>").description(`Write only the agent instruction file for a client (${ALL_CLIENT_KEYS.join(", ")})`).option("--dry-run", "print what would be written without writing", false).action(async (client, opts, cmd) => {
|
|
679
|
-
await runSingle(client, cmd, { dryRun: Boolean(opts.dryRun), mcp: false, agentFiles: true });
|
|
788
|
+
setup.command("agent-files <client>").description(`Write only the agent instruction file for a client (${ALL_CLIENT_KEYS.join(", ")})`).option("--dry-run", "print what would be written without writing", false).option("--copy", "make a personal copy you can edit (default: prompt on a TTY, else skip)").action(async (client, opts, cmd) => {
|
|
789
|
+
await runSingle(client, cmd, { dryRun: Boolean(opts.dryRun), mcp: false, agentFiles: true, copy: opts.copy });
|
|
680
790
|
});
|
|
681
791
|
}
|
|
682
792
|
async function runSingle(client, cmd, opts) {
|
|
@@ -685,7 +795,16 @@ async function runSingle(client, cmd, opts) {
|
|
|
685
795
|
const target = targets[client];
|
|
686
796
|
if (!target) fail(`unknown client '${client}'. Known: ${ALL_CLIENT_KEYS.join(", ")}.`);
|
|
687
797
|
const setupData = await withSpinner("Fetching setup descriptors", () => fetchSetup(cfg));
|
|
688
|
-
const
|
|
798
|
+
const personalWorkspaceId = await getPersonalWorkspaceId(cfg);
|
|
799
|
+
if (opts.agentFiles && !opts.dryRun) {
|
|
800
|
+
await maybeOfferCopies(cfg, setupData, targets, [client], personalWorkspaceId, copyChoice(opts));
|
|
801
|
+
}
|
|
802
|
+
const actions = await applyClient(cfg, setupData, target, {
|
|
803
|
+
dryRun: opts.dryRun,
|
|
804
|
+
mcp: opts.mcp,
|
|
805
|
+
agentFiles: opts.agentFiles,
|
|
806
|
+
personalWorkspaceId
|
|
807
|
+
});
|
|
689
808
|
const json = cmd.optsWithGlobals().json;
|
|
690
809
|
if (json) {
|
|
691
810
|
emit({ dryRun: opts.dryRun, client, actions }, true);
|
|
@@ -695,6 +814,127 @@ async function runSingle(client, cmd, opts) {
|
|
|
695
814
|
}
|
|
696
815
|
}
|
|
697
816
|
|
|
817
|
+
// src/commands/onboard.ts
|
|
818
|
+
var DEFAULT_BASE_URL2 = "https://app.sechroom.ai/api";
|
|
819
|
+
function systemTimezone() {
|
|
820
|
+
try {
|
|
821
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|
|
822
|
+
} catch {
|
|
823
|
+
return "UTC";
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
async function ensureConfig(g, yes) {
|
|
827
|
+
const persisted = readPersisted();
|
|
828
|
+
let baseUrl = g.baseUrl ?? process.env.SECHROOM_BASE_URL ?? persisted.baseUrl ?? DEFAULT_BASE_URL2;
|
|
829
|
+
let tenant = g.tenant ?? process.env.SECHROOM_TENANT ?? persisted.tenant ?? "";
|
|
830
|
+
if (canPrompt() && !yes) {
|
|
831
|
+
baseUrl = await promptText("Sechroom API base URL?", baseUrl);
|
|
832
|
+
tenant = await promptText("Tenant id?", tenant || void 0);
|
|
833
|
+
}
|
|
834
|
+
baseUrl = baseUrl.replace(/\/$/, "");
|
|
835
|
+
if (!tenant) {
|
|
836
|
+
fail(
|
|
837
|
+
"No tenant set. Pass --tenant <id>, set SECHROOM_TENANT, or run `sechroom config set tenant <id>` \u2014 the API rejects untenanted requests (HTTP 400)."
|
|
838
|
+
);
|
|
839
|
+
}
|
|
840
|
+
writePersisted({ baseUrl, tenant });
|
|
841
|
+
return { baseUrl, tenant, clientId: persisted.clientId };
|
|
842
|
+
}
|
|
843
|
+
async function ensureAuth(cfg, yes) {
|
|
844
|
+
if (process.env.SECHROOM_TOKEN) return;
|
|
845
|
+
const cached = readToken();
|
|
846
|
+
const usable = Boolean(cached?.accessToken) && (cached.expiresAt === void 0 || Date.now() < cached.expiresAt - 6e4 || Boolean(cached.refreshToken));
|
|
847
|
+
if (usable) return;
|
|
848
|
+
if (!canPrompt() || yes) {
|
|
849
|
+
fail("Not signed in. Run `sechroom login` first, or set SECHROOM_TOKEN for headless use.");
|
|
850
|
+
}
|
|
851
|
+
process.stderr.write("\nNot signed in \u2014 opening the browser to authenticate.\n");
|
|
852
|
+
await login(cfg);
|
|
853
|
+
}
|
|
854
|
+
async function ensureTimezone(cfg, opts) {
|
|
855
|
+
const client = await makeClient(cfg);
|
|
856
|
+
const { data, error } = await client.GET("/me/profile", {});
|
|
857
|
+
if (error) return { timezone: null, action: "skipped", note: "could not read profile" };
|
|
858
|
+
const current = data?.effectiveTimezone;
|
|
859
|
+
if (current && current.trim().length > 0) return { timezone: current, action: "already-set" };
|
|
860
|
+
const system = systemTimezone();
|
|
861
|
+
let tz = system;
|
|
862
|
+
if (canPrompt() && !opts.yes) {
|
|
863
|
+
tz = await promptText("Your timezone (IANA, e.g. Europe/London)?", system);
|
|
864
|
+
} else if (!opts.yes) {
|
|
865
|
+
return {
|
|
866
|
+
timezone: null,
|
|
867
|
+
action: "skipped",
|
|
868
|
+
note: "no timezone set \u2014 re-run interactively or pass --yes to adopt the system timezone"
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
if (!tz) return { timezone: null, action: "skipped", note: "no timezone provided" };
|
|
872
|
+
if (opts.dryRun) return { timezone: tz, action: "dry-run" };
|
|
873
|
+
const { error: putErr } = await client.PUT("/me/profile", {
|
|
874
|
+
body: { displayName: null, photoUrl: null, bio: null, timezone: tz }
|
|
875
|
+
});
|
|
876
|
+
if (putErr) return { timezone: tz, action: "skipped", note: `update failed: ${JSON.stringify(putErr)}` };
|
|
877
|
+
return { timezone: tz, action: "set" };
|
|
878
|
+
}
|
|
879
|
+
async function chooseClients(clientFlag, yes, cwd) {
|
|
880
|
+
if (clientFlag) return resolveClientKeys(clientFlag);
|
|
881
|
+
const detected = detectInstalledClients(cwd);
|
|
882
|
+
const fallback = (detected.length > 0 ? detected : [DEFAULT_CLIENT_KEY]).join(",");
|
|
883
|
+
if (!canPrompt() || yes) return resolveClientKeys(fallback);
|
|
884
|
+
process.stderr.write(
|
|
885
|
+
`
|
|
886
|
+
Available clients: ${ALL_CLIENT_KEYS.join(", ")}
|
|
887
|
+
` + (detected.length > 0 ? `Detected on this machine: ${detected.join(", ")}
|
|
888
|
+
` : "No clients auto-detected.\n")
|
|
889
|
+
);
|
|
890
|
+
const answer = await promptText("Which to wire? (comma-separated, or 'all')", fallback);
|
|
891
|
+
return resolveClientKeys(answer);
|
|
892
|
+
}
|
|
893
|
+
function registerOnboard(program2) {
|
|
894
|
+
program2.command("onboard").description("Guided first-run setup: configure, sign in, set timezone, detect clients, and wire this project").option("--client <list>", `comma-separated clients (${ALL_CLIENT_KEYS.join(", ")}) or 'all' (default: auto-detected)`).option("--copy", "make a personal copy of the agent instructions you can edit (default: prompt on a TTY, else skip)").option("--dry-run", "walk through without writing files or changing the profile", false).option("-y, --yes", "non-interactive: accept defaults (system timezone, detected clients)", false).action(async (opts, cmd) => {
|
|
895
|
+
const g = cmd.optsWithGlobals();
|
|
896
|
+
const json = Boolean(g.json);
|
|
897
|
+
const yes = Boolean(opts.yes);
|
|
898
|
+
const dryRun = Boolean(opts.dryRun);
|
|
899
|
+
const cfg = await ensureConfig(g, yes);
|
|
900
|
+
await ensureAuth(cfg, yes);
|
|
901
|
+
const tz = await ensureTimezone(cfg, { yes, dryRun });
|
|
902
|
+
if (!json && tz.action !== "already-set") {
|
|
903
|
+
const line = tz.action === "set" ? `\u2713 timezone set to ${tz.timezone}
|
|
904
|
+
` : tz.action === "dry-run" ? `(dry run \u2014 would set timezone to ${tz.timezone})
|
|
905
|
+
` : `timezone not set \u2014 ${tz.note}
|
|
906
|
+
`;
|
|
907
|
+
process.stderr.write(line);
|
|
908
|
+
}
|
|
909
|
+
const keys = await chooseClients(opts.client, yes, process.cwd());
|
|
910
|
+
const setup = await withSpinner("Fetching setup descriptors", () => fetchSetup(cfg));
|
|
911
|
+
const targets = clientTargets(process.cwd());
|
|
912
|
+
const personalWorkspaceId = await getPersonalWorkspaceId(cfg);
|
|
913
|
+
if (!dryRun) {
|
|
914
|
+
await maybeOfferCopies(cfg, setup, targets, keys, personalWorkspaceId, copyChoice(opts));
|
|
915
|
+
}
|
|
916
|
+
const result = [];
|
|
917
|
+
for (const key of keys) {
|
|
918
|
+
const target = targets[key];
|
|
919
|
+
const actions = await applyClient(cfg, setup, target, {
|
|
920
|
+
dryRun,
|
|
921
|
+
mcp: true,
|
|
922
|
+
agentFiles: true,
|
|
923
|
+
personalWorkspaceId
|
|
924
|
+
});
|
|
925
|
+
result.push({ client: key, actions });
|
|
926
|
+
if (!json) printActions(target, actions);
|
|
927
|
+
}
|
|
928
|
+
if (json) {
|
|
929
|
+
emit({ dryRun, baseUrl: cfg.baseUrl, tenant: cfg.tenant, timezone: tz, clients: result }, true);
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
process.stdout.write(
|
|
933
|
+
dryRun ? "\n(dry run \u2014 nothing written)\n" : "\nDone. Restart your AI client (or reload MCP) to pick up the new config.\n"
|
|
934
|
+
);
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
|
|
698
938
|
// src/index.ts
|
|
699
939
|
function resolveVersion() {
|
|
700
940
|
try {
|
|
@@ -736,6 +976,7 @@ registerWorklog(program);
|
|
|
736
976
|
registerLookup(program);
|
|
737
977
|
registerInit(program);
|
|
738
978
|
registerSetup(program);
|
|
979
|
+
registerOnboard(program);
|
|
739
980
|
program.parseAsync().catch((err) => {
|
|
740
981
|
process.stderr.write(`error: ${err instanceof Error ? err.message : String(err)}
|
|
741
982
|
`);
|
package/package.json
CHANGED