kiro-kit 0.2.4 → 0.3.1
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 +35 -0
- package/dist/index.js +772 -130
- package/dist/index.js.map +1 -1
- package/dist/presets/backend/hooks/api-schema-validate.js +75 -0
- package/dist/presets/backend/hooks/endpoint-test-coverage.js +77 -0
- package/dist/presets/backend/hooks/migration-safety-check.js +68 -0
- package/dist/presets/backend/manifest.json +23 -0
- package/dist/presets/backend/powers.json +40 -0
- package/dist/presets/data-ai/hooks/data-drift-check.js +77 -0
- package/dist/presets/data-ai/hooks/experiment-log.js +87 -0
- package/dist/presets/data-ai/hooks/model-card-update.js +99 -0
- package/dist/presets/data-ai/manifest.json +23 -0
- package/dist/presets/data-ai/powers.json +34 -0
- package/dist/presets/devops/hooks/container-scan.js +73 -0
- package/dist/presets/devops/hooks/cost-estimation.js +69 -0
- package/dist/presets/devops/hooks/terraform-plan-review.js +67 -0
- package/dist/presets/devops/manifest.json +23 -0
- package/dist/presets/devops/powers.json +40 -0
- package/dist/presets/frontend/hooks/accessibility-check.js +76 -0
- package/dist/presets/frontend/hooks/bundle-size-guard.js +71 -0
- package/dist/presets/frontend/hooks/component-test-reminder.js +71 -0
- package/dist/presets/frontend/manifest.json +23 -0
- package/dist/presets/frontend/powers.json +34 -0
- package/dist/presets/fullstack/hooks/api-client-gen.js +69 -0
- package/dist/presets/fullstack/hooks/deployment-readiness.js +73 -0
- package/dist/presets/fullstack/hooks/type-sync-check.js +69 -0
- package/dist/presets/fullstack/manifest.json +23 -0
- package/dist/presets/fullstack/powers.json +46 -0
- package/dist/presets/mobile/hooks/asset-optimization.js +58 -0
- package/dist/presets/mobile/hooks/platform-parity-check.js +73 -0
- package/dist/presets/mobile/hooks/release-checklist.js +83 -0
- package/dist/presets/mobile/manifest.json +23 -0
- package/dist/presets/mobile/powers.json +34 -0
- package/package.json +73 -65
package/dist/index.js
CHANGED
|
@@ -6,10 +6,10 @@ import process13 from "process";
|
|
|
6
6
|
import { Command } from "commander";
|
|
7
7
|
|
|
8
8
|
// src/commands/init.ts
|
|
9
|
-
import
|
|
10
|
-
import
|
|
9
|
+
import fs14 from "fs";
|
|
10
|
+
import path13 from "path";
|
|
11
11
|
import process5 from "process";
|
|
12
|
-
import
|
|
12
|
+
import readline2 from "readline";
|
|
13
13
|
import crypto3 from "crypto";
|
|
14
14
|
|
|
15
15
|
// src/core/PresetLoader.ts
|
|
@@ -81,7 +81,8 @@ var ArtifactTypeSchema = z.enum([
|
|
|
81
81
|
"docs",
|
|
82
82
|
"doc",
|
|
83
83
|
"config",
|
|
84
|
-
"other"
|
|
84
|
+
"other",
|
|
85
|
+
"powers"
|
|
85
86
|
]);
|
|
86
87
|
var PresetNameSchema = z.enum([
|
|
87
88
|
"frontend",
|
|
@@ -665,6 +666,564 @@ function safePathInside(workspaceRoot, target) {
|
|
|
665
666
|
return resolved.startsWith(root + path8.sep) || resolved === root;
|
|
666
667
|
}
|
|
667
668
|
|
|
669
|
+
// src/core/PowersLoader.ts
|
|
670
|
+
import fs10 from "fs";
|
|
671
|
+
import path9 from "path";
|
|
672
|
+
import { z as z2 } from "zod";
|
|
673
|
+
var PowerTierSchema = z2.enum(["essential", "recommended", "optional"]);
|
|
674
|
+
var PowerEntrySchema = z2.object({
|
|
675
|
+
name: z2.string().min(1),
|
|
676
|
+
url: z2.string().url(),
|
|
677
|
+
description: z2.string().min(1),
|
|
678
|
+
tier: PowerTierSchema
|
|
679
|
+
});
|
|
680
|
+
var PowersConfigSchema = z2.object({
|
|
681
|
+
powers: z2.array(PowerEntrySchema)
|
|
682
|
+
});
|
|
683
|
+
var POWERS_FILENAME = "powers.json";
|
|
684
|
+
var TIER_PRIORITY = {
|
|
685
|
+
essential: 0,
|
|
686
|
+
recommended: 1,
|
|
687
|
+
optional: 2
|
|
688
|
+
};
|
|
689
|
+
function loadPowers(presetDir) {
|
|
690
|
+
const filePath = path9.join(presetDir, POWERS_FILENAME);
|
|
691
|
+
if (!fs10.existsSync(filePath)) {
|
|
692
|
+
return { ok: true, powers: [] };
|
|
693
|
+
}
|
|
694
|
+
let raw;
|
|
695
|
+
try {
|
|
696
|
+
raw = fs10.readFileSync(filePath, "utf-8");
|
|
697
|
+
} catch (err) {
|
|
698
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
699
|
+
return { ok: false, powers: [], error: `Failed to read ${POWERS_FILENAME}: ${message}` };
|
|
700
|
+
}
|
|
701
|
+
let parsed;
|
|
702
|
+
try {
|
|
703
|
+
parsed = JSON.parse(raw);
|
|
704
|
+
} catch {
|
|
705
|
+
return { ok: false, powers: [], error: `${POWERS_FILENAME} is not valid JSON.` };
|
|
706
|
+
}
|
|
707
|
+
const result = PowersConfigSchema.safeParse(parsed);
|
|
708
|
+
if (!result.success) {
|
|
709
|
+
const issues = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
710
|
+
return { ok: false, powers: [], error: `${POWERS_FILENAME} schema validation failed: ${issues}` };
|
|
711
|
+
}
|
|
712
|
+
return { ok: true, powers: result.data.powers };
|
|
713
|
+
}
|
|
714
|
+
function mergePowers(configs) {
|
|
715
|
+
const seen = /* @__PURE__ */ new Map();
|
|
716
|
+
for (const entries of configs) {
|
|
717
|
+
for (const entry of entries) {
|
|
718
|
+
const existing = seen.get(entry.name);
|
|
719
|
+
if (!existing) {
|
|
720
|
+
seen.set(entry.name, entry);
|
|
721
|
+
} else if (TIER_PRIORITY[entry.tier] < TIER_PRIORITY[existing.tier]) {
|
|
722
|
+
seen.set(entry.name, entry);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
return Array.from(seen.values());
|
|
727
|
+
}
|
|
728
|
+
function filterByTier(powers, tiers) {
|
|
729
|
+
const tierSet = new Set(tiers);
|
|
730
|
+
return powers.filter((p) => tierSet.has(p.tier));
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// src/core/MCPConfigurator.ts
|
|
734
|
+
import fs11 from "fs";
|
|
735
|
+
import path10 from "path";
|
|
736
|
+
var MCP_FILENAME = ".mcp.json";
|
|
737
|
+
var SERVER_DEFINITIONS = {
|
|
738
|
+
filesystem: {
|
|
739
|
+
command: "npx",
|
|
740
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "."],
|
|
741
|
+
requiresCredentials: false
|
|
742
|
+
},
|
|
743
|
+
git: {
|
|
744
|
+
command: "npx",
|
|
745
|
+
args: ["-y", "@modelcontextprotocol/server-git", "--repository", "."],
|
|
746
|
+
requiresCredentials: false
|
|
747
|
+
},
|
|
748
|
+
fetch: {
|
|
749
|
+
command: "npx",
|
|
750
|
+
args: ["-y", "@modelcontextprotocol/server-fetch"],
|
|
751
|
+
requiresCredentials: false
|
|
752
|
+
},
|
|
753
|
+
playwright: {
|
|
754
|
+
command: "npx",
|
|
755
|
+
args: ["-y", "@modelcontextprotocol/server-playwright"],
|
|
756
|
+
requiresCredentials: false
|
|
757
|
+
},
|
|
758
|
+
postgres: {
|
|
759
|
+
command: "npx",
|
|
760
|
+
args: ["-y", "@modelcontextprotocol/server-postgres"],
|
|
761
|
+
env: { POSTGRES_URL: "${POSTGRES_URL}" },
|
|
762
|
+
requiresCredentials: true,
|
|
763
|
+
credentialEnvVars: ["POSTGRES_URL"]
|
|
764
|
+
},
|
|
765
|
+
docker: {
|
|
766
|
+
command: "npx",
|
|
767
|
+
args: ["-y", "@modelcontextprotocol/server-docker"],
|
|
768
|
+
env: { DOCKER_HOST: "${DOCKER_HOST}" },
|
|
769
|
+
requiresCredentials: true,
|
|
770
|
+
credentialEnvVars: ["DOCKER_HOST"]
|
|
771
|
+
}
|
|
772
|
+
};
|
|
773
|
+
var PRESET_SERVERS = {
|
|
774
|
+
frontend: { default: ["filesystem", "git", "fetch", "playwright"], optional: [] },
|
|
775
|
+
backend: { default: ["filesystem", "git", "fetch"], optional: ["postgres", "docker"] },
|
|
776
|
+
fullstack: { default: ["filesystem", "git", "fetch", "playwright"], optional: ["postgres", "docker"] },
|
|
777
|
+
mobile: { default: ["filesystem", "git", "fetch"], optional: [] },
|
|
778
|
+
devops: { default: ["filesystem", "git", "fetch"], optional: ["docker", "postgres"] },
|
|
779
|
+
"data-ai": { default: ["filesystem", "git", "fetch"], optional: ["postgres"] }
|
|
780
|
+
};
|
|
781
|
+
function getMCPConfig(presetName) {
|
|
782
|
+
const mapping = PRESET_SERVERS[presetName];
|
|
783
|
+
if (!mapping) {
|
|
784
|
+
return { servers: {} };
|
|
785
|
+
}
|
|
786
|
+
const servers = {};
|
|
787
|
+
for (const name of mapping.default) {
|
|
788
|
+
const def = SERVER_DEFINITIONS[name];
|
|
789
|
+
if (def) {
|
|
790
|
+
servers[name] = { ...def };
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
for (const name of mapping.optional) {
|
|
794
|
+
const def = SERVER_DEFINITIONS[name];
|
|
795
|
+
if (def) {
|
|
796
|
+
servers[name] = { ...def };
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
return { servers };
|
|
800
|
+
}
|
|
801
|
+
function mergeMCPConfig(existing, incoming) {
|
|
802
|
+
const existingServers = existing && typeof existing === "object" && existing !== null ? existing.mcpServers ?? {} : {};
|
|
803
|
+
const merged = { ...existingServers };
|
|
804
|
+
for (const [name, entry] of Object.entries(incoming.servers)) {
|
|
805
|
+
const outputKey = entry.requiresCredentials ? `_disabled_${name}` : name;
|
|
806
|
+
if (merged[name] !== void 0 || merged[`_disabled_${name}`] !== void 0) {
|
|
807
|
+
continue;
|
|
808
|
+
}
|
|
809
|
+
const serverObj = { command: entry.command };
|
|
810
|
+
if (entry.args && entry.args.length > 0) {
|
|
811
|
+
serverObj.args = entry.args;
|
|
812
|
+
}
|
|
813
|
+
if (entry.env && Object.keys(entry.env).length > 0) {
|
|
814
|
+
serverObj.env = entry.env;
|
|
815
|
+
}
|
|
816
|
+
if (entry.requiresCredentials) {
|
|
817
|
+
serverObj._comment = `Requires ${entry.credentialEnvVars?.join(", ") ?? "credentials"} environment variable. Remove '_disabled_' prefix to enable.`;
|
|
818
|
+
}
|
|
819
|
+
merged[outputKey] = serverObj;
|
|
820
|
+
}
|
|
821
|
+
const result = { ...existing ?? {} };
|
|
822
|
+
result.mcpServers = merged;
|
|
823
|
+
return result;
|
|
824
|
+
}
|
|
825
|
+
function writeMCPConfig(workspaceRoot, config) {
|
|
826
|
+
const filePath = path10.join(workspaceRoot, MCP_FILENAME);
|
|
827
|
+
const content = JSON.stringify(config, null, 2) + "\n";
|
|
828
|
+
try {
|
|
829
|
+
fs11.writeFileSync(filePath, content, "utf-8");
|
|
830
|
+
} catch (err) {
|
|
831
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
832
|
+
throw new KKError(
|
|
833
|
+
ErrorCodes.MCP_MERGE_CONFLICT,
|
|
834
|
+
`Failed to write ${MCP_FILENAME}: ${message}`,
|
|
835
|
+
"Check file permissions and ensure the directory exists."
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// src/core/SetupGuideGenerator.ts
|
|
841
|
+
import fs12 from "fs";
|
|
842
|
+
import path11 from "path";
|
|
843
|
+
var SETUP_GUIDE_FILENAME = "POWERS-SETUP.md";
|
|
844
|
+
var KIRO_DIR = ".kiro";
|
|
845
|
+
var TIER_ORDER = [
|
|
846
|
+
{ tier: "essential", heading: "Essential Powers (install these first)" },
|
|
847
|
+
{ tier: "recommended", heading: "Recommended Powers" },
|
|
848
|
+
{ tier: "optional", heading: "Optional Powers" }
|
|
849
|
+
];
|
|
850
|
+
function generateSetupGuide(options) {
|
|
851
|
+
const { powers, presetNames, mcpServers } = options;
|
|
852
|
+
const lines = [];
|
|
853
|
+
lines.push("# Kiro Powers Setup Guide");
|
|
854
|
+
lines.push("");
|
|
855
|
+
lines.push(`## Presets: ${presetNames.join(", ")}`);
|
|
856
|
+
lines.push("");
|
|
857
|
+
let counter = 0;
|
|
858
|
+
for (const { tier, heading } of TIER_ORDER) {
|
|
859
|
+
const tierPowers = powers.filter((p) => p.tier === tier);
|
|
860
|
+
if (tierPowers.length === 0) {
|
|
861
|
+
continue;
|
|
862
|
+
}
|
|
863
|
+
lines.push(`## ${heading}`);
|
|
864
|
+
lines.push("");
|
|
865
|
+
for (const power of tierPowers) {
|
|
866
|
+
counter += 1;
|
|
867
|
+
lines.push(`### ${counter}. ${power.name}`);
|
|
868
|
+
lines.push(`- URL: ${power.url}`);
|
|
869
|
+
lines.push(`- Description: ${power.description}`);
|
|
870
|
+
lines.push(`- How to install: Open Kiro IDE > Powers panel > Search "${power.name}" > Click Install`);
|
|
871
|
+
lines.push("");
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
const serverNames = Object.keys(mcpServers);
|
|
875
|
+
if (serverNames.length > 0) {
|
|
876
|
+
lines.push("## MCP Servers (auto-configured)");
|
|
877
|
+
lines.push("");
|
|
878
|
+
lines.push("The following MCP servers have been configured in `.mcp.json`:");
|
|
879
|
+
lines.push("");
|
|
880
|
+
for (const name of serverNames) {
|
|
881
|
+
const entry = mcpServers[name];
|
|
882
|
+
if (entry.requiresCredentials) {
|
|
883
|
+
lines.push(`- **${name}** (disabled, requires credentials)`);
|
|
884
|
+
} else {
|
|
885
|
+
lines.push(`- **${name}** (enabled)`);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
lines.push("");
|
|
889
|
+
lines.push("To enable disabled servers, set the required environment variables");
|
|
890
|
+
lines.push("and remove the `_disabled_` prefix from the server key in `.mcp.json`.");
|
|
891
|
+
lines.push("");
|
|
892
|
+
}
|
|
893
|
+
return lines.join("\n");
|
|
894
|
+
}
|
|
895
|
+
function writeSetupGuide(workspaceRoot, content) {
|
|
896
|
+
const dirPath = path11.join(workspaceRoot, KIRO_DIR);
|
|
897
|
+
const filePath = path11.join(dirPath, SETUP_GUIDE_FILENAME);
|
|
898
|
+
try {
|
|
899
|
+
if (!fs12.existsSync(dirPath)) {
|
|
900
|
+
fs12.mkdirSync(dirPath, { recursive: true });
|
|
901
|
+
}
|
|
902
|
+
fs12.writeFileSync(filePath, content, "utf-8");
|
|
903
|
+
} catch (err) {
|
|
904
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
905
|
+
throw new KKError(
|
|
906
|
+
ErrorCodes.MCP_MERGE_CONFLICT,
|
|
907
|
+
`Failed to write ${SETUP_GUIDE_FILENAME}: ${message}`,
|
|
908
|
+
"Check file permissions and ensure the .kiro directory is writable."
|
|
909
|
+
);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
// src/core/EnvTemplateGenerator.ts
|
|
914
|
+
import fs13 from "fs";
|
|
915
|
+
import path12 from "path";
|
|
916
|
+
var ENV_FILENAME = ".env.example";
|
|
917
|
+
function collectEnvVars(mcpConfig, powers) {
|
|
918
|
+
const vars = [];
|
|
919
|
+
for (const [name, entry] of Object.entries(mcpConfig.servers)) {
|
|
920
|
+
if (!entry.requiresCredentials) {
|
|
921
|
+
continue;
|
|
922
|
+
}
|
|
923
|
+
const envVars = entry.credentialEnvVars ?? [];
|
|
924
|
+
const envMap = entry.env ?? {};
|
|
925
|
+
for (const key of envVars) {
|
|
926
|
+
const placeholder = envMap[key] ?? `your-${key.toLowerCase().replace(/_/g, "-")}`;
|
|
927
|
+
vars.push({
|
|
928
|
+
key,
|
|
929
|
+
placeholder,
|
|
930
|
+
comment: `Required by ${name} MCP server`,
|
|
931
|
+
service: `${name} MCP Server`
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
for (const power of powers) {
|
|
936
|
+
if (power.tier === "essential" || power.tier === "recommended") {
|
|
937
|
+
const knownPowerEnvVars = getKnownPowerEnvVars(power.name);
|
|
938
|
+
for (const envVar of knownPowerEnvVars) {
|
|
939
|
+
vars.push(envVar);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
return vars;
|
|
944
|
+
}
|
|
945
|
+
function parseExistingEnv(content) {
|
|
946
|
+
const keys = /* @__PURE__ */ new Set();
|
|
947
|
+
for (const line of content.split("\n")) {
|
|
948
|
+
const trimmed = line.trim();
|
|
949
|
+
if (trimmed === "" || trimmed.startsWith("#")) {
|
|
950
|
+
continue;
|
|
951
|
+
}
|
|
952
|
+
const match = trimmed.match(/^#?\s*([A-Z_][A-Z0-9_]*)=/);
|
|
953
|
+
if (match) {
|
|
954
|
+
keys.add(match[1]);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
return keys;
|
|
958
|
+
}
|
|
959
|
+
function generateEnvTemplate(existing, newVars) {
|
|
960
|
+
const existingKeys = existing ? parseExistingEnv(existing) : /* @__PURE__ */ new Set();
|
|
961
|
+
const varsToAdd = newVars.filter((v) => !existingKeys.has(v.key));
|
|
962
|
+
if (varsToAdd.length === 0) {
|
|
963
|
+
return existing ?? "";
|
|
964
|
+
}
|
|
965
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
966
|
+
for (const v of varsToAdd) {
|
|
967
|
+
const group = grouped.get(v.service) ?? [];
|
|
968
|
+
group.push(v);
|
|
969
|
+
grouped.set(v.service, group);
|
|
970
|
+
}
|
|
971
|
+
const lines = [];
|
|
972
|
+
for (const [service, vars] of grouped) {
|
|
973
|
+
lines.push(`# ${service}`);
|
|
974
|
+
for (const v of vars) {
|
|
975
|
+
lines.push(`# ${v.comment}`);
|
|
976
|
+
lines.push(`# ${v.key}=${v.placeholder}`);
|
|
977
|
+
}
|
|
978
|
+
lines.push("");
|
|
979
|
+
}
|
|
980
|
+
const newContent = lines.join("\n");
|
|
981
|
+
if (!existing || existing.trim() === "") {
|
|
982
|
+
return newContent;
|
|
983
|
+
}
|
|
984
|
+
const separator = existing.endsWith("\n") ? "" : "\n";
|
|
985
|
+
return existing + separator + "\n" + newContent;
|
|
986
|
+
}
|
|
987
|
+
function readExistingEnv(workspaceRoot) {
|
|
988
|
+
const filePath = path12.join(workspaceRoot, ENV_FILENAME);
|
|
989
|
+
try {
|
|
990
|
+
return fs13.readFileSync(filePath, "utf-8");
|
|
991
|
+
} catch {
|
|
992
|
+
return null;
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
function writeEnvTemplate(workspaceRoot, content) {
|
|
996
|
+
const filePath = path12.join(workspaceRoot, ENV_FILENAME);
|
|
997
|
+
try {
|
|
998
|
+
fs13.writeFileSync(filePath, content, "utf-8");
|
|
999
|
+
} catch (err) {
|
|
1000
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1001
|
+
throw new KKError(
|
|
1002
|
+
ErrorCodes.MCP_MERGE_CONFLICT,
|
|
1003
|
+
`Failed to write ${ENV_FILENAME}: ${message}`,
|
|
1004
|
+
"Check file permissions or create the file manually."
|
|
1005
|
+
);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
function getKnownPowerEnvVars(powerName) {
|
|
1009
|
+
const mapping = {
|
|
1010
|
+
Supabase: [
|
|
1011
|
+
{
|
|
1012
|
+
key: "SUPABASE_URL",
|
|
1013
|
+
placeholder: "https://your-project.supabase.co",
|
|
1014
|
+
comment: "Supabase project URL",
|
|
1015
|
+
service: "Supabase Power"
|
|
1016
|
+
},
|
|
1017
|
+
{
|
|
1018
|
+
key: "SUPABASE_ANON_KEY",
|
|
1019
|
+
placeholder: "your-anon-key",
|
|
1020
|
+
comment: "Supabase anonymous key",
|
|
1021
|
+
service: "Supabase Power"
|
|
1022
|
+
}
|
|
1023
|
+
],
|
|
1024
|
+
Firebase: [
|
|
1025
|
+
{
|
|
1026
|
+
key: "FIREBASE_PROJECT_ID",
|
|
1027
|
+
placeholder: "your-project-id",
|
|
1028
|
+
comment: "Firebase project ID",
|
|
1029
|
+
service: "Firebase Power"
|
|
1030
|
+
}
|
|
1031
|
+
],
|
|
1032
|
+
Stripe: [
|
|
1033
|
+
{
|
|
1034
|
+
key: "STRIPE_SECRET_KEY",
|
|
1035
|
+
placeholder: "sk_test_your-key",
|
|
1036
|
+
comment: "Stripe secret key",
|
|
1037
|
+
service: "Stripe Power"
|
|
1038
|
+
}
|
|
1039
|
+
],
|
|
1040
|
+
Datadog: [
|
|
1041
|
+
{
|
|
1042
|
+
key: "DD_API_KEY",
|
|
1043
|
+
placeholder: "your-datadog-api-key",
|
|
1044
|
+
comment: "Datadog API key",
|
|
1045
|
+
service: "Datadog Power"
|
|
1046
|
+
}
|
|
1047
|
+
],
|
|
1048
|
+
Neon: [
|
|
1049
|
+
{
|
|
1050
|
+
key: "NEON_DATABASE_URL",
|
|
1051
|
+
placeholder: "postgresql://user:password@host/dbname",
|
|
1052
|
+
comment: "Neon database connection string",
|
|
1053
|
+
service: "Neon Power"
|
|
1054
|
+
}
|
|
1055
|
+
]
|
|
1056
|
+
};
|
|
1057
|
+
return mapping[powerName] ?? [];
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
// src/prompts/PowersPrompter.ts
|
|
1061
|
+
import readline from "readline";
|
|
1062
|
+
var TIER_ORDER2 = ["essential", "recommended", "optional"];
|
|
1063
|
+
var TIER_LABELS = {
|
|
1064
|
+
essential: "Essential",
|
|
1065
|
+
recommended: "Recommended",
|
|
1066
|
+
optional: "Optional"
|
|
1067
|
+
};
|
|
1068
|
+
var TIER_OPTIONS = [
|
|
1069
|
+
{ label: "Essential only", tiers: ["essential"] },
|
|
1070
|
+
{ label: "Essential + Recommended", tiers: ["essential", "recommended"] },
|
|
1071
|
+
{ label: "All (Essential + Recommended + Optional)", tiers: ["essential", "recommended", "optional"] },
|
|
1072
|
+
{ label: "None (skip Powers)", tiers: [] }
|
|
1073
|
+
];
|
|
1074
|
+
async function promptPowersTier(powers, flags) {
|
|
1075
|
+
if (powers.length === 0) {
|
|
1076
|
+
return { selectedTiers: [], confirmMCP: false };
|
|
1077
|
+
}
|
|
1078
|
+
if (flags.powersFlag === "none") {
|
|
1079
|
+
return { selectedTiers: [], confirmMCP: false };
|
|
1080
|
+
}
|
|
1081
|
+
if (flags.powersFlag === "all") {
|
|
1082
|
+
return { selectedTiers: ["essential", "recommended", "optional"], confirmMCP: true };
|
|
1083
|
+
}
|
|
1084
|
+
if (flags.yes === true) {
|
|
1085
|
+
return { selectedTiers: ["essential", "recommended"], confirmMCP: true };
|
|
1086
|
+
}
|
|
1087
|
+
return interactiveTierPrompt(powers);
|
|
1088
|
+
}
|
|
1089
|
+
function displayPowersRecommendations(powers, quiet) {
|
|
1090
|
+
if (quiet || powers.length === 0) {
|
|
1091
|
+
return;
|
|
1092
|
+
}
|
|
1093
|
+
process.stdout.write("\n");
|
|
1094
|
+
process.stdout.write(color.bold("Recommended Kiro Powers:") + "\n");
|
|
1095
|
+
for (const tier of TIER_ORDER2) {
|
|
1096
|
+
const tierPowers = powers.filter((p) => p.tier === tier);
|
|
1097
|
+
if (tierPowers.length === 0) continue;
|
|
1098
|
+
process.stdout.write("\n");
|
|
1099
|
+
process.stdout.write(` ${color.cyan(TIER_LABELS[tier])}:
|
|
1100
|
+
`);
|
|
1101
|
+
for (const power of tierPowers) {
|
|
1102
|
+
process.stdout.write(` ${color.bold(power.name)} - ${power.description}
|
|
1103
|
+
`);
|
|
1104
|
+
process.stdout.write(` ${color.dim(power.url)}
|
|
1105
|
+
`);
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
const counts = countByTier(powers);
|
|
1109
|
+
const parts = [];
|
|
1110
|
+
if (counts.essential > 0) parts.push(`${counts.essential} essential`);
|
|
1111
|
+
if (counts.recommended > 0) parts.push(`${counts.recommended} recommended`);
|
|
1112
|
+
if (counts.optional > 0) parts.push(`${counts.optional} optional`);
|
|
1113
|
+
process.stdout.write("\n");
|
|
1114
|
+
process.stdout.write(color.dim(`${parts.join(", ")} Powers recommended`) + "\n");
|
|
1115
|
+
}
|
|
1116
|
+
function countByTier(powers) {
|
|
1117
|
+
const counts = { essential: 0, recommended: 0, optional: 0 };
|
|
1118
|
+
for (const p of powers) {
|
|
1119
|
+
counts[p.tier]++;
|
|
1120
|
+
}
|
|
1121
|
+
return counts;
|
|
1122
|
+
}
|
|
1123
|
+
async function interactiveTierPrompt(powers) {
|
|
1124
|
+
if (!process.stdin.isTTY) {
|
|
1125
|
+
return { selectedTiers: ["essential", "recommended"], confirmMCP: true };
|
|
1126
|
+
}
|
|
1127
|
+
return new Promise((resolve2, reject) => {
|
|
1128
|
+
let cursor = 1;
|
|
1129
|
+
let rendered = false;
|
|
1130
|
+
const rl = readline.createInterface({
|
|
1131
|
+
input: process.stdin,
|
|
1132
|
+
output: process.stdout,
|
|
1133
|
+
terminal: false
|
|
1134
|
+
});
|
|
1135
|
+
const render = () => {
|
|
1136
|
+
if (rendered) {
|
|
1137
|
+
process.stdout.write("\x1B[u");
|
|
1138
|
+
} else {
|
|
1139
|
+
process.stdout.write("\x1B[s");
|
|
1140
|
+
}
|
|
1141
|
+
rendered = true;
|
|
1142
|
+
process.stdout.write("\x1B[J");
|
|
1143
|
+
process.stdout.write(
|
|
1144
|
+
color.bold("? Which Powers tiers to include in setup guide?") + color.dim(" (Arrow keys to select, Enter to confirm)") + "\n"
|
|
1145
|
+
);
|
|
1146
|
+
for (let i = 0; i < TIER_OPTIONS.length; i++) {
|
|
1147
|
+
const marker = cursor === i ? color.cyan(">") : " ";
|
|
1148
|
+
const label = cursor === i ? color.cyan(TIER_OPTIONS[i].label) : TIER_OPTIONS[i].label;
|
|
1149
|
+
process.stdout.write(` ${marker} ${label}
|
|
1150
|
+
`);
|
|
1151
|
+
}
|
|
1152
|
+
};
|
|
1153
|
+
render();
|
|
1154
|
+
process.stdin.setRawMode(true);
|
|
1155
|
+
process.stdin.resume();
|
|
1156
|
+
process.stdin.setEncoding("utf-8");
|
|
1157
|
+
let escBuffer = "";
|
|
1158
|
+
const handleArrow = (seq) => {
|
|
1159
|
+
if (seq === "\x1B[A" || seq === "\x1BOA") {
|
|
1160
|
+
cursor = (cursor - 1 + TIER_OPTIONS.length) % TIER_OPTIONS.length;
|
|
1161
|
+
render();
|
|
1162
|
+
return true;
|
|
1163
|
+
}
|
|
1164
|
+
if (seq === "\x1B[B" || seq === "\x1BOB") {
|
|
1165
|
+
cursor = (cursor + 1) % TIER_OPTIONS.length;
|
|
1166
|
+
render();
|
|
1167
|
+
return true;
|
|
1168
|
+
}
|
|
1169
|
+
return false;
|
|
1170
|
+
};
|
|
1171
|
+
const cleanup = () => {
|
|
1172
|
+
process.stdin.setRawMode(false);
|
|
1173
|
+
process.stdin.removeListener("data", onData);
|
|
1174
|
+
process.stdin.pause();
|
|
1175
|
+
rl.close();
|
|
1176
|
+
};
|
|
1177
|
+
const onData = (key) => {
|
|
1178
|
+
if (escBuffer.length > 0) {
|
|
1179
|
+
escBuffer += key;
|
|
1180
|
+
if (escBuffer.length >= 3) {
|
|
1181
|
+
const seq = escBuffer;
|
|
1182
|
+
escBuffer = "";
|
|
1183
|
+
handleArrow(seq);
|
|
1184
|
+
}
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
if (key.length >= 3 && key.startsWith("\x1B")) {
|
|
1188
|
+
handleArrow(key);
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
if (key === "\x1B") {
|
|
1192
|
+
escBuffer = key;
|
|
1193
|
+
setTimeout(() => {
|
|
1194
|
+
if (escBuffer.length > 0) {
|
|
1195
|
+
escBuffer = "";
|
|
1196
|
+
}
|
|
1197
|
+
}, 50);
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
if (key === "") {
|
|
1201
|
+
cleanup();
|
|
1202
|
+
reject(new Error("SIGINT"));
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
if (key === "\r" || key === "\n") {
|
|
1206
|
+
cleanup();
|
|
1207
|
+
const selected = TIER_OPTIONS[cursor];
|
|
1208
|
+
const confirmMCP = selected.tiers.length > 0;
|
|
1209
|
+
resolve2({ selectedTiers: selected.tiers, confirmMCP });
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
if (key === "k") {
|
|
1213
|
+
cursor = (cursor - 1 + TIER_OPTIONS.length) % TIER_OPTIONS.length;
|
|
1214
|
+
render();
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
if (key === "j") {
|
|
1218
|
+
cursor = (cursor + 1) % TIER_OPTIONS.length;
|
|
1219
|
+
render();
|
|
1220
|
+
return;
|
|
1221
|
+
}
|
|
1222
|
+
};
|
|
1223
|
+
process.stdin.on("data", onData);
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
|
|
668
1227
|
// src/commands/init.ts
|
|
669
1228
|
function setupSigintHandler() {
|
|
670
1229
|
process5.on("SIGINT", () => {
|
|
@@ -674,25 +1233,16 @@ function setupSigintHandler() {
|
|
|
674
1233
|
async function multiPickPrompt(items) {
|
|
675
1234
|
const selected = /* @__PURE__ */ new Set();
|
|
676
1235
|
let cursor = 0;
|
|
677
|
-
const rl = readline.createInterface({
|
|
678
|
-
input: process5.stdin,
|
|
679
|
-
output: process5.stdout,
|
|
680
|
-
terminal: false
|
|
681
|
-
});
|
|
682
1236
|
if (!process5.stdin.isTTY) {
|
|
683
|
-
rl.close();
|
|
684
1237
|
return [];
|
|
685
1238
|
}
|
|
686
1239
|
return new Promise((resolve2, reject) => {
|
|
687
1240
|
let rendered = false;
|
|
688
1241
|
const render = () => {
|
|
689
1242
|
if (rendered) {
|
|
690
|
-
process5.stdout.write(
|
|
691
|
-
} else {
|
|
692
|
-
process5.stdout.write("\x1B[s");
|
|
1243
|
+
process5.stdout.write(`\x1B[${items.length + 1}A\r`);
|
|
693
1244
|
}
|
|
694
1245
|
rendered = true;
|
|
695
|
-
process5.stdout.write("\x1B[J");
|
|
696
1246
|
process5.stdout.write(
|
|
697
1247
|
color.bold("? Select presets to install:") + color.dim(" (Space to select, <a> toggle all, Enter to confirm)") + "\n"
|
|
698
1248
|
);
|
|
@@ -701,7 +1251,7 @@ async function multiPickPrompt(items) {
|
|
|
701
1251
|
const check = selected.has(i) ? color.green("[x]") : "[ ]";
|
|
702
1252
|
const name = color.bold(items[i].name.padEnd(12));
|
|
703
1253
|
const desc = color.dim(`- ${items[i].description}`);
|
|
704
|
-
process5.stdout.write(
|
|
1254
|
+
process5.stdout.write(`\x1B[2K ${marker} ${check} ${name} ${desc}
|
|
705
1255
|
`);
|
|
706
1256
|
}
|
|
707
1257
|
};
|
|
@@ -710,6 +1260,11 @@ async function multiPickPrompt(items) {
|
|
|
710
1260
|
process5.stdin.resume();
|
|
711
1261
|
process5.stdin.setEncoding("utf-8");
|
|
712
1262
|
let escBuffer = "";
|
|
1263
|
+
const cleanup = () => {
|
|
1264
|
+
process5.stdin.setRawMode(false);
|
|
1265
|
+
process5.stdin.removeListener("data", onData);
|
|
1266
|
+
process5.stdin.pause();
|
|
1267
|
+
};
|
|
713
1268
|
const handleArrow = (seq) => {
|
|
714
1269
|
if (seq === "\x1B[A" || seq === "\x1BOA") {
|
|
715
1270
|
cursor = (cursor - 1 + items.length) % items.length;
|
|
@@ -747,18 +1302,12 @@ async function multiPickPrompt(items) {
|
|
|
747
1302
|
return;
|
|
748
1303
|
}
|
|
749
1304
|
if (key === "") {
|
|
750
|
-
|
|
751
|
-
process5.stdin.removeListener("data", onData);
|
|
752
|
-
process5.stdin.pause();
|
|
753
|
-
rl.close();
|
|
1305
|
+
cleanup();
|
|
754
1306
|
reject(new Error("SIGINT"));
|
|
755
1307
|
return;
|
|
756
1308
|
}
|
|
757
1309
|
if (key === "\r" || key === "\n") {
|
|
758
|
-
|
|
759
|
-
process5.stdin.removeListener("data", onData);
|
|
760
|
-
process5.stdin.pause();
|
|
761
|
-
rl.close();
|
|
1310
|
+
cleanup();
|
|
762
1311
|
const result = [...selected].map((i) => items[i].name);
|
|
763
1312
|
resolve2(result);
|
|
764
1313
|
return;
|
|
@@ -798,7 +1347,7 @@ async function multiPickPrompt(items) {
|
|
|
798
1347
|
async function confirmPrompt(message) {
|
|
799
1348
|
if (!process5.stdin.isTTY) return true;
|
|
800
1349
|
return new Promise((resolve2, reject) => {
|
|
801
|
-
const rl =
|
|
1350
|
+
const rl = readline2.createInterface({
|
|
802
1351
|
input: process5.stdin,
|
|
803
1352
|
output: process5.stdout
|
|
804
1353
|
});
|
|
@@ -815,7 +1364,7 @@ async function confirmPrompt(message) {
|
|
|
815
1364
|
}
|
|
816
1365
|
async function conflictPrompt(target) {
|
|
817
1366
|
if (!process5.stdin.isTTY) return "skip";
|
|
818
|
-
const relTarget =
|
|
1367
|
+
const relTarget = path13.relative(process5.cwd(), target);
|
|
819
1368
|
process5.stdout.write(
|
|
820
1369
|
`
|
|
821
1370
|
${color.yellow("?")} File ${color.bold(relTarget)} already exists with different content.
|
|
@@ -826,7 +1375,7 @@ ${color.yellow("?")} File ${color.bold(relTarget)} already exists with different
|
|
|
826
1375
|
`
|
|
827
1376
|
);
|
|
828
1377
|
return new Promise((resolve2, reject) => {
|
|
829
|
-
const rl =
|
|
1378
|
+
const rl = readline2.createInterface({
|
|
830
1379
|
input: process5.stdin,
|
|
831
1380
|
output: process5.stdout
|
|
832
1381
|
});
|
|
@@ -849,7 +1398,7 @@ function sha2562(data) {
|
|
|
849
1398
|
return crypto3.createHash("sha256").update(data).digest("hex");
|
|
850
1399
|
}
|
|
851
1400
|
function registerInitCommand(program2) {
|
|
852
|
-
program2.command("init").description("Initialize workspace with selected presets").option("-y, --yes", "Skip confirmation, accept defaults").option("--preset <name>", "Specify preset (repeatable)", collectPreset, []).option("--force", "Overwrite all files (with backup)").option("--skip-existing", "Skip all existing files").option("--no-color", "Disable ANSI colors").action(async (opts) => {
|
|
1401
|
+
program2.command("init").description("Initialize workspace with selected presets").option("-y, --yes", "Skip confirmation, accept defaults").option("--preset <name>", "Specify preset (repeatable)", collectPreset, []).option("--force", "Overwrite all files (with backup)").option("--skip-existing", "Skip all existing files").option("--no-color", "Disable ANSI colors").option("--powers <mode>", "Powers setup mode: none, all, or interactive (default)", "interactive").option("--quiet", "Suppress non-essential output including Powers recommendations").action(async (opts) => {
|
|
853
1402
|
setupSigintHandler();
|
|
854
1403
|
try {
|
|
855
1404
|
await runInit(opts);
|
|
@@ -926,18 +1475,18 @@ async function runInit(opts) {
|
|
|
926
1475
|
const settingsFiles = manifest.files.filter((f) => f.type === "settings");
|
|
927
1476
|
const statuslineFiles = manifest.files.filter((f) => f.type === "statusline");
|
|
928
1477
|
for (const fileEntry of regularFiles) {
|
|
929
|
-
const sourcePath =
|
|
930
|
-
const targetPath =
|
|
1478
|
+
const sourcePath = path13.join(presetDir, fileEntry.source);
|
|
1479
|
+
const targetPath = path13.resolve(workspaceRoot, fileEntry.target);
|
|
931
1480
|
if (!safePathInside(workspaceRoot, fileEntry.target)) {
|
|
932
1481
|
logger.warn(`Skipping unsafe path: ${fileEntry.target}`);
|
|
933
1482
|
filesSkipped++;
|
|
934
1483
|
continue;
|
|
935
1484
|
}
|
|
936
|
-
if (!
|
|
1485
|
+
if (!fs14.existsSync(sourcePath)) {
|
|
937
1486
|
logger.debug(`Source file missing: ${sourcePath}`);
|
|
938
1487
|
continue;
|
|
939
1488
|
}
|
|
940
|
-
const sourceContent =
|
|
1489
|
+
const sourceContent = fs14.readFileSync(sourcePath);
|
|
941
1490
|
const action = await resolve({
|
|
942
1491
|
target: targetPath,
|
|
943
1492
|
sourceContent,
|
|
@@ -948,11 +1497,11 @@ async function runInit(opts) {
|
|
|
948
1497
|
});
|
|
949
1498
|
switch (action) {
|
|
950
1499
|
case "WRITE_NEW":
|
|
951
|
-
|
|
1500
|
+
fs14.mkdirSync(path13.dirname(targetPath), { recursive: true });
|
|
952
1501
|
atomicWrite(targetPath, sourceContent.toString("utf-8"));
|
|
953
1502
|
if (fileEntry.executable && process5.platform !== "win32") {
|
|
954
1503
|
try {
|
|
955
|
-
|
|
1504
|
+
fs14.chmodSync(targetPath, 493);
|
|
956
1505
|
} catch {
|
|
957
1506
|
}
|
|
958
1507
|
}
|
|
@@ -963,7 +1512,7 @@ async function runInit(opts) {
|
|
|
963
1512
|
atomicWrite(targetPath, sourceContent.toString("utf-8"));
|
|
964
1513
|
if (fileEntry.executable && process5.platform !== "win32") {
|
|
965
1514
|
try {
|
|
966
|
-
|
|
1515
|
+
fs14.chmodSync(targetPath, 493);
|
|
967
1516
|
} catch {
|
|
968
1517
|
}
|
|
969
1518
|
}
|
|
@@ -997,55 +1546,148 @@ async function runInit(opts) {
|
|
|
997
1546
|
}
|
|
998
1547
|
}
|
|
999
1548
|
if (mcpFiles.length > 0 && manifest.mcpServers) {
|
|
1000
|
-
const mcpPath =
|
|
1549
|
+
const mcpPath = path13.join(workspaceRoot, ".kiro/settings/mcp.json");
|
|
1001
1550
|
let existingMcp = null;
|
|
1002
|
-
if (
|
|
1551
|
+
if (fs14.existsSync(mcpPath)) {
|
|
1003
1552
|
try {
|
|
1004
|
-
existingMcp = JSON.parse(
|
|
1553
|
+
existingMcp = JSON.parse(fs14.readFileSync(mcpPath, "utf-8"));
|
|
1005
1554
|
} catch {
|
|
1006
1555
|
existingMcp = null;
|
|
1007
1556
|
}
|
|
1008
1557
|
}
|
|
1009
1558
|
const merged = mergeMCP(existingMcp, manifest.mcpServers, manifest.name);
|
|
1010
|
-
|
|
1559
|
+
fs14.mkdirSync(path13.dirname(mcpPath), { recursive: true });
|
|
1011
1560
|
atomicWrite(mcpPath, JSON.stringify(merged, null, 2) + "\n");
|
|
1012
1561
|
filesWritten++;
|
|
1013
1562
|
}
|
|
1014
1563
|
if (settingsFiles.length > 0) {
|
|
1015
|
-
const settingsPath =
|
|
1564
|
+
const settingsPath = path13.join(workspaceRoot, ".kiro/settings.json");
|
|
1016
1565
|
let existingSettings = null;
|
|
1017
|
-
if (
|
|
1566
|
+
if (fs14.existsSync(settingsPath)) {
|
|
1018
1567
|
try {
|
|
1019
1568
|
existingSettings = JSON.parse(
|
|
1020
|
-
|
|
1569
|
+
fs14.readFileSync(settingsPath, "utf-8")
|
|
1021
1570
|
);
|
|
1022
1571
|
} catch {
|
|
1023
1572
|
existingSettings = null;
|
|
1024
1573
|
}
|
|
1025
1574
|
}
|
|
1026
|
-
const presetSettingsPath =
|
|
1027
|
-
if (
|
|
1575
|
+
const presetSettingsPath = path13.join(presetDir, "settings.json");
|
|
1576
|
+
if (fs14.existsSync(presetSettingsPath)) {
|
|
1028
1577
|
const presetSettings = JSON.parse(
|
|
1029
|
-
|
|
1578
|
+
fs14.readFileSync(presetSettingsPath, "utf-8")
|
|
1030
1579
|
);
|
|
1031
1580
|
const resolvedSettings = resolveSettingsCommand(
|
|
1032
1581
|
presetSettings
|
|
1033
1582
|
);
|
|
1034
1583
|
const merged = mergeSettings(existingSettings, resolvedSettings);
|
|
1035
|
-
|
|
1584
|
+
fs14.mkdirSync(path13.dirname(settingsPath), { recursive: true });
|
|
1036
1585
|
atomicWrite(settingsPath, JSON.stringify(merged, null, 2) + "\n");
|
|
1037
1586
|
filesWritten++;
|
|
1038
1587
|
}
|
|
1039
1588
|
}
|
|
1040
|
-
const mcpExampleSource =
|
|
1041
|
-
if (
|
|
1042
|
-
const mcpExampleTarget =
|
|
1043
|
-
if (!
|
|
1044
|
-
|
|
1045
|
-
|
|
1589
|
+
const mcpExampleSource = path13.join(presetDir, ".mcp.json.example");
|
|
1590
|
+
if (fs14.existsSync(mcpExampleSource)) {
|
|
1591
|
+
const mcpExampleTarget = path13.join(workspaceRoot, ".kiro/.mcp.json.example");
|
|
1592
|
+
if (!fs14.existsSync(mcpExampleTarget)) {
|
|
1593
|
+
fs14.mkdirSync(path13.dirname(mcpExampleTarget), { recursive: true });
|
|
1594
|
+
fs14.copyFileSync(mcpExampleSource, mcpExampleTarget);
|
|
1046
1595
|
}
|
|
1047
1596
|
}
|
|
1048
1597
|
}
|
|
1598
|
+
try {
|
|
1599
|
+
if (opts.powers !== "none") {
|
|
1600
|
+
const allPowerEntries = presets.map((p) => {
|
|
1601
|
+
const result = loadPowers(p.dir);
|
|
1602
|
+
if (!result.ok) {
|
|
1603
|
+
logger.warn(`Powers: ${result.error}`);
|
|
1604
|
+
}
|
|
1605
|
+
return result.powers;
|
|
1606
|
+
});
|
|
1607
|
+
const mergedPowers = mergePowers(allPowerEntries);
|
|
1608
|
+
if (mergedPowers.length > 0) {
|
|
1609
|
+
const promptResult = await promptPowersTier(mergedPowers, {
|
|
1610
|
+
powersFlag: opts.powers,
|
|
1611
|
+
yes: opts.yes
|
|
1612
|
+
});
|
|
1613
|
+
const filteredPowers = promptResult.selectedTiers.length > 0 ? filterByTier(mergedPowers, promptResult.selectedTiers) : [];
|
|
1614
|
+
if (promptResult.confirmMCP) {
|
|
1615
|
+
try {
|
|
1616
|
+
const presetMCPConfigs = presets.map((p) => getMCPConfig(p.manifest.name));
|
|
1617
|
+
let combinedMCP = null;
|
|
1618
|
+
const mcpJsonPath = path13.join(workspaceRoot, ".mcp.json");
|
|
1619
|
+
if (fs14.existsSync(mcpJsonPath)) {
|
|
1620
|
+
try {
|
|
1621
|
+
combinedMCP = JSON.parse(fs14.readFileSync(mcpJsonPath, "utf-8"));
|
|
1622
|
+
} catch {
|
|
1623
|
+
logger.warn("Existing .mcp.json is invalid JSON, will create fresh.");
|
|
1624
|
+
combinedMCP = null;
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
for (const mcpConfig of presetMCPConfigs) {
|
|
1628
|
+
combinedMCP = mergeMCPConfig(combinedMCP, mcpConfig);
|
|
1629
|
+
}
|
|
1630
|
+
let confirmWrite = true;
|
|
1631
|
+
if (!opts.yes) {
|
|
1632
|
+
confirmWrite = await confirmPrompt(
|
|
1633
|
+
"Configure MCP servers in .mcp.json? (Y/n)"
|
|
1634
|
+
);
|
|
1635
|
+
}
|
|
1636
|
+
if (confirmWrite && combinedMCP) {
|
|
1637
|
+
writeMCPConfig(workspaceRoot, combinedMCP);
|
|
1638
|
+
if (!opts.quiet) {
|
|
1639
|
+
logger.info("MCP servers configured in .mcp.json");
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
} catch (err) {
|
|
1643
|
+
logger.warn(`MCP configuration failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
displayPowersRecommendations(filteredPowers, opts.quiet ?? false);
|
|
1647
|
+
try {
|
|
1648
|
+
const mcpServersForGuide = presets.reduce(
|
|
1649
|
+
(acc, p) => ({ ...acc, ...getMCPConfig(p.manifest.name).servers }),
|
|
1650
|
+
{}
|
|
1651
|
+
);
|
|
1652
|
+
const guideContent = generateSetupGuide({
|
|
1653
|
+
powers: filteredPowers,
|
|
1654
|
+
presetNames: selectedNames,
|
|
1655
|
+
mcpServers: mcpServersForGuide
|
|
1656
|
+
});
|
|
1657
|
+
writeSetupGuide(workspaceRoot, guideContent);
|
|
1658
|
+
if (!opts.quiet) {
|
|
1659
|
+
logger.info("Setup guide written to .kiro/POWERS-SETUP.md");
|
|
1660
|
+
}
|
|
1661
|
+
} catch (err) {
|
|
1662
|
+
logger.warn(`Setup guide generation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1663
|
+
}
|
|
1664
|
+
try {
|
|
1665
|
+
const mcpConfigForEnv = presets.reduce(
|
|
1666
|
+
(acc, p) => {
|
|
1667
|
+
const cfg = getMCPConfig(p.manifest.name);
|
|
1668
|
+
return { servers: { ...acc.servers, ...cfg.servers } };
|
|
1669
|
+
},
|
|
1670
|
+
{ servers: {} }
|
|
1671
|
+
);
|
|
1672
|
+
const envVars = collectEnvVars(mcpConfigForEnv, filteredPowers);
|
|
1673
|
+
if (envVars.length > 0) {
|
|
1674
|
+
const existingEnv = readExistingEnv(workspaceRoot);
|
|
1675
|
+
const envContent = generateEnvTemplate(existingEnv, envVars);
|
|
1676
|
+
if (envContent.trim().length > 0) {
|
|
1677
|
+
writeEnvTemplate(workspaceRoot, envContent);
|
|
1678
|
+
if (!opts.quiet) {
|
|
1679
|
+
logger.info("Environment template updated in .env.example");
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
} catch (err) {
|
|
1684
|
+
logger.warn(`Env template generation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
} catch (err) {
|
|
1689
|
+
logger.warn(`Powers integration failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1690
|
+
}
|
|
1049
1691
|
const kitVersion = getKitVersion();
|
|
1050
1692
|
const presetMetas = presets.map((p) => ({
|
|
1051
1693
|
name: p.manifest.name,
|
|
@@ -1094,7 +1736,7 @@ function generateTimestamp2() {
|
|
|
1094
1736
|
function getKitVersion() {
|
|
1095
1737
|
try {
|
|
1096
1738
|
const pkgPath = new URL("../package.json", import.meta.url);
|
|
1097
|
-
const pkg2 = JSON.parse(
|
|
1739
|
+
const pkg2 = JSON.parse(fs14.readFileSync(pkgPath, "utf-8"));
|
|
1098
1740
|
return pkg2.version;
|
|
1099
1741
|
} catch {
|
|
1100
1742
|
return "0.1.0";
|
|
@@ -1102,8 +1744,8 @@ function getKitVersion() {
|
|
|
1102
1744
|
}
|
|
1103
1745
|
|
|
1104
1746
|
// src/commands/add.ts
|
|
1105
|
-
import
|
|
1106
|
-
import
|
|
1747
|
+
import fs15 from "fs";
|
|
1748
|
+
import path14 from "path";
|
|
1107
1749
|
import process6 from "process";
|
|
1108
1750
|
import crypto4 from "crypto";
|
|
1109
1751
|
function sha2563(data) {
|
|
@@ -1123,7 +1765,7 @@ function generateTimestamp3() {
|
|
|
1123
1765
|
function getKitVersion2() {
|
|
1124
1766
|
try {
|
|
1125
1767
|
const pkgPath = new URL("../../package.json", import.meta.url);
|
|
1126
|
-
const pkg2 = JSON.parse(
|
|
1768
|
+
const pkg2 = JSON.parse(fs15.readFileSync(pkgPath, "utf-8"));
|
|
1127
1769
|
return pkg2.version;
|
|
1128
1770
|
} catch {
|
|
1129
1771
|
return "0.1.0";
|
|
@@ -1131,8 +1773,8 @@ function getKitVersion2() {
|
|
|
1131
1773
|
}
|
|
1132
1774
|
async function conflictPrompt2(target) {
|
|
1133
1775
|
if (!process6.stdin.isTTY) return "skip";
|
|
1134
|
-
const { default:
|
|
1135
|
-
const relTarget =
|
|
1776
|
+
const { default: readline3 } = await import("readline");
|
|
1777
|
+
const relTarget = path14.relative(process6.cwd(), target);
|
|
1136
1778
|
process6.stdout.write(
|
|
1137
1779
|
`
|
|
1138
1780
|
${color.yellow("?")} File ${color.bold(relTarget)} already exists with different content.
|
|
@@ -1143,7 +1785,7 @@ ${color.yellow("?")} File ${color.bold(relTarget)} already exists with different
|
|
|
1143
1785
|
`
|
|
1144
1786
|
);
|
|
1145
1787
|
return new Promise((resolve2) => {
|
|
1146
|
-
const rl =
|
|
1788
|
+
const rl = readline3.createInterface({ input: process6.stdin, output: process6.stdout });
|
|
1147
1789
|
rl.question(` Choice (overwrite/skip/diff/all): `, (answer) => {
|
|
1148
1790
|
rl.close();
|
|
1149
1791
|
const a = answer.trim().toLowerCase();
|
|
@@ -1174,9 +1816,9 @@ async function runAdd(presetName, opts) {
|
|
|
1174
1816
|
);
|
|
1175
1817
|
process6.exit(1);
|
|
1176
1818
|
}
|
|
1177
|
-
const kiroDir =
|
|
1178
|
-
if (!
|
|
1179
|
-
|
|
1819
|
+
const kiroDir = path14.join(workspaceRoot, ".kiro");
|
|
1820
|
+
if (!fs15.existsSync(kiroDir)) {
|
|
1821
|
+
fs15.mkdirSync(kiroDir, { recursive: true });
|
|
1180
1822
|
logger.info("Created .kiro/ directory.");
|
|
1181
1823
|
}
|
|
1182
1824
|
const preset = load(presetName);
|
|
@@ -1195,18 +1837,18 @@ async function runAdd(presetName, opts) {
|
|
|
1195
1837
|
);
|
|
1196
1838
|
const statuslineFiles = manifest.files.filter((f) => f.type === "statusline");
|
|
1197
1839
|
for (const fileEntry of regularFiles) {
|
|
1198
|
-
const sourcePath =
|
|
1199
|
-
const targetPath =
|
|
1840
|
+
const sourcePath = path14.join(presetDir, fileEntry.source);
|
|
1841
|
+
const targetPath = path14.resolve(workspaceRoot, fileEntry.target);
|
|
1200
1842
|
if (!safePathInside(workspaceRoot, fileEntry.target)) {
|
|
1201
1843
|
logger.warn(`Skipping unsafe path: ${fileEntry.target}`);
|
|
1202
1844
|
filesSkipped++;
|
|
1203
1845
|
continue;
|
|
1204
1846
|
}
|
|
1205
|
-
if (!
|
|
1847
|
+
if (!fs15.existsSync(sourcePath)) {
|
|
1206
1848
|
logger.debug(`Source file missing: ${sourcePath}`);
|
|
1207
1849
|
continue;
|
|
1208
1850
|
}
|
|
1209
|
-
const sourceContent =
|
|
1851
|
+
const sourceContent = fs15.readFileSync(sourcePath);
|
|
1210
1852
|
const action = await resolve({
|
|
1211
1853
|
target: targetPath,
|
|
1212
1854
|
sourceContent,
|
|
@@ -1217,11 +1859,11 @@ async function runAdd(presetName, opts) {
|
|
|
1217
1859
|
});
|
|
1218
1860
|
switch (action) {
|
|
1219
1861
|
case "WRITE_NEW":
|
|
1220
|
-
|
|
1862
|
+
fs15.mkdirSync(path14.dirname(targetPath), { recursive: true });
|
|
1221
1863
|
atomicWrite(targetPath, sourceContent.toString("utf-8"));
|
|
1222
1864
|
if (fileEntry.executable && process6.platform !== "win32") {
|
|
1223
1865
|
try {
|
|
1224
|
-
|
|
1866
|
+
fs15.chmodSync(targetPath, 493);
|
|
1225
1867
|
} catch {
|
|
1226
1868
|
}
|
|
1227
1869
|
}
|
|
@@ -1232,7 +1874,7 @@ async function runAdd(presetName, opts) {
|
|
|
1232
1874
|
atomicWrite(targetPath, sourceContent.toString("utf-8"));
|
|
1233
1875
|
if (fileEntry.executable && process6.platform !== "win32") {
|
|
1234
1876
|
try {
|
|
1235
|
-
|
|
1877
|
+
fs15.chmodSync(targetPath, 493);
|
|
1236
1878
|
} catch {
|
|
1237
1879
|
}
|
|
1238
1880
|
}
|
|
@@ -1266,37 +1908,37 @@ async function runAdd(presetName, opts) {
|
|
|
1266
1908
|
}
|
|
1267
1909
|
}
|
|
1268
1910
|
if (manifest.mcpServers) {
|
|
1269
|
-
const mcpPath =
|
|
1911
|
+
const mcpPath = path14.join(workspaceRoot, ".kiro/settings/mcp.json");
|
|
1270
1912
|
let existingMcp = null;
|
|
1271
|
-
if (
|
|
1913
|
+
if (fs15.existsSync(mcpPath)) {
|
|
1272
1914
|
try {
|
|
1273
|
-
existingMcp = JSON.parse(
|
|
1915
|
+
existingMcp = JSON.parse(fs15.readFileSync(mcpPath, "utf-8"));
|
|
1274
1916
|
} catch {
|
|
1275
1917
|
existingMcp = null;
|
|
1276
1918
|
}
|
|
1277
1919
|
}
|
|
1278
1920
|
const merged = mergeMCP(existingMcp, manifest.mcpServers, manifest.name);
|
|
1279
|
-
|
|
1921
|
+
fs15.mkdirSync(path14.dirname(mcpPath), { recursive: true });
|
|
1280
1922
|
atomicWrite(mcpPath, JSON.stringify(merged, null, 2) + "\n");
|
|
1281
1923
|
filesWritten++;
|
|
1282
1924
|
}
|
|
1283
|
-
const presetSettingsPath =
|
|
1284
|
-
if (
|
|
1285
|
-
const settingsPath =
|
|
1925
|
+
const presetSettingsPath = path14.join(presetDir, "settings.json");
|
|
1926
|
+
if (fs15.existsSync(presetSettingsPath)) {
|
|
1927
|
+
const settingsPath = path14.join(workspaceRoot, ".kiro/settings.json");
|
|
1286
1928
|
let existingSettings = null;
|
|
1287
|
-
if (
|
|
1929
|
+
if (fs15.existsSync(settingsPath)) {
|
|
1288
1930
|
try {
|
|
1289
|
-
existingSettings = JSON.parse(
|
|
1931
|
+
existingSettings = JSON.parse(fs15.readFileSync(settingsPath, "utf-8"));
|
|
1290
1932
|
} catch {
|
|
1291
1933
|
existingSettings = null;
|
|
1292
1934
|
}
|
|
1293
1935
|
}
|
|
1294
|
-
const presetSettings = JSON.parse(
|
|
1936
|
+
const presetSettings = JSON.parse(fs15.readFileSync(presetSettingsPath, "utf-8"));
|
|
1295
1937
|
const resolvedSettings = resolveSettingsCommand(
|
|
1296
1938
|
presetSettings
|
|
1297
1939
|
);
|
|
1298
1940
|
const merged = mergeSettings(existingSettings, resolvedSettings);
|
|
1299
|
-
|
|
1941
|
+
fs15.mkdirSync(path14.dirname(settingsPath), { recursive: true });
|
|
1300
1942
|
atomicWrite(settingsPath, JSON.stringify(merged, null, 2) + "\n");
|
|
1301
1943
|
filesWritten++;
|
|
1302
1944
|
}
|
|
@@ -1511,8 +2153,8 @@ ${color.bold(manifest.name)} v${manifest.version}
|
|
|
1511
2153
|
}
|
|
1512
2154
|
|
|
1513
2155
|
// src/commands/update.ts
|
|
1514
|
-
import
|
|
1515
|
-
import
|
|
2156
|
+
import fs16 from "fs";
|
|
2157
|
+
import path15 from "path";
|
|
1516
2158
|
import process9 from "process";
|
|
1517
2159
|
import crypto5 from "crypto";
|
|
1518
2160
|
function sha2564(data) {
|
|
@@ -1532,7 +2174,7 @@ function generateTimestamp4() {
|
|
|
1532
2174
|
function getKitVersion3() {
|
|
1533
2175
|
try {
|
|
1534
2176
|
const pkgPath = new URL("../../package.json", import.meta.url);
|
|
1535
|
-
const pkg2 = JSON.parse(
|
|
2177
|
+
const pkg2 = JSON.parse(fs16.readFileSync(pkgPath, "utf-8"));
|
|
1536
2178
|
return pkg2.version;
|
|
1537
2179
|
} catch {
|
|
1538
2180
|
return "0.1.0";
|
|
@@ -1540,8 +2182,8 @@ function getKitVersion3() {
|
|
|
1540
2182
|
}
|
|
1541
2183
|
async function conflictPrompt3(target) {
|
|
1542
2184
|
if (!process9.stdin.isTTY) return "skip";
|
|
1543
|
-
const { default:
|
|
1544
|
-
const relTarget =
|
|
2185
|
+
const { default: readline3 } = await import("readline");
|
|
2186
|
+
const relTarget = path15.relative(process9.cwd(), target);
|
|
1545
2187
|
process9.stdout.write(
|
|
1546
2188
|
`
|
|
1547
2189
|
${color.yellow("?")} File ${color.bold(relTarget)} has changed in the new version.
|
|
@@ -1552,7 +2194,7 @@ ${color.yellow("?")} File ${color.bold(relTarget)} has changed in the new versio
|
|
|
1552
2194
|
`
|
|
1553
2195
|
);
|
|
1554
2196
|
return new Promise((resolve2) => {
|
|
1555
|
-
const rl =
|
|
2197
|
+
const rl = readline3.createInterface({ input: process9.stdin, output: process9.stdout });
|
|
1556
2198
|
rl.question(` Choice (overwrite/skip/diff/all): `, (answer) => {
|
|
1557
2199
|
rl.close();
|
|
1558
2200
|
const a = answer.trim().toLowerCase();
|
|
@@ -1609,11 +2251,11 @@ async function runUpdate(opts) {
|
|
|
1609
2251
|
const updatedFiles = [];
|
|
1610
2252
|
for (const fileEntry of bundled.manifest.files) {
|
|
1611
2253
|
if (["mcp", "settings", "statusline"].includes(fileEntry.type)) continue;
|
|
1612
|
-
const sourcePath =
|
|
1613
|
-
const targetPath =
|
|
2254
|
+
const sourcePath = path15.join(presetDir, fileEntry.source);
|
|
2255
|
+
const targetPath = path15.resolve(workspaceRoot, fileEntry.target);
|
|
1614
2256
|
if (!safePathInside(workspaceRoot, fileEntry.target)) continue;
|
|
1615
|
-
if (!
|
|
1616
|
-
const sourceContent =
|
|
2257
|
+
if (!fs16.existsSync(sourcePath)) continue;
|
|
2258
|
+
const sourceContent = fs16.readFileSync(sourcePath);
|
|
1617
2259
|
const newHash = sha2564(sourceContent);
|
|
1618
2260
|
const trackedFile = trackedPreset.files.find((f) => f.target === fileEntry.target);
|
|
1619
2261
|
if (trackedFile && trackedFile.contentHash === newHash) {
|
|
@@ -1629,11 +2271,11 @@ async function runUpdate(opts) {
|
|
|
1629
2271
|
});
|
|
1630
2272
|
switch (action) {
|
|
1631
2273
|
case "WRITE_NEW":
|
|
1632
|
-
|
|
2274
|
+
fs16.mkdirSync(path15.dirname(targetPath), { recursive: true });
|
|
1633
2275
|
atomicWrite(targetPath, sourceContent.toString("utf-8"));
|
|
1634
2276
|
if (fileEntry.executable && process9.platform !== "win32") {
|
|
1635
2277
|
try {
|
|
1636
|
-
|
|
2278
|
+
fs16.chmodSync(targetPath, 493);
|
|
1637
2279
|
} catch {
|
|
1638
2280
|
}
|
|
1639
2281
|
}
|
|
@@ -1644,7 +2286,7 @@ async function runUpdate(opts) {
|
|
|
1644
2286
|
atomicWrite(targetPath, sourceContent.toString("utf-8"));
|
|
1645
2287
|
if (fileEntry.executable && process9.platform !== "win32") {
|
|
1646
2288
|
try {
|
|
1647
|
-
|
|
2289
|
+
fs16.chmodSync(targetPath, 493);
|
|
1648
2290
|
} catch {
|
|
1649
2291
|
}
|
|
1650
2292
|
}
|
|
@@ -1732,8 +2374,8 @@ function runRestore(opts) {
|
|
|
1732
2374
|
}
|
|
1733
2375
|
|
|
1734
2376
|
// src/commands/doctor.ts
|
|
1735
|
-
import
|
|
1736
|
-
import
|
|
2377
|
+
import fs17 from "fs";
|
|
2378
|
+
import path16 from "path";
|
|
1737
2379
|
import process11 from "process";
|
|
1738
2380
|
function registerDoctorCommand(program2) {
|
|
1739
2381
|
program2.command("doctor").description("Run workspace health checks").option("--fix", "Auto-fix fixable issues").action((opts) => {
|
|
@@ -1796,8 +2438,8 @@ function checkNodeVersion() {
|
|
|
1796
2438
|
};
|
|
1797
2439
|
}
|
|
1798
2440
|
function checkKiroDir(workspaceRoot) {
|
|
1799
|
-
const kiroDir =
|
|
1800
|
-
if (
|
|
2441
|
+
const kiroDir = path16.join(workspaceRoot, ".kiro");
|
|
2442
|
+
if (fs17.existsSync(kiroDir) && fs17.statSync(kiroDir).isDirectory()) {
|
|
1801
2443
|
return { name: "kiro-dir", result: "PASS", message: ".kiro/ directory exists" };
|
|
1802
2444
|
}
|
|
1803
2445
|
return {
|
|
@@ -1807,12 +2449,12 @@ function checkKiroDir(workspaceRoot) {
|
|
|
1807
2449
|
};
|
|
1808
2450
|
}
|
|
1809
2451
|
function checkMcpJson(workspaceRoot) {
|
|
1810
|
-
const mcpPath =
|
|
1811
|
-
if (!
|
|
2452
|
+
const mcpPath = path16.join(workspaceRoot, ".kiro/settings/mcp.json");
|
|
2453
|
+
if (!fs17.existsSync(mcpPath)) {
|
|
1812
2454
|
return { name: "mcp-json", result: "WARN", message: ".kiro/settings/mcp.json not found (optional)" };
|
|
1813
2455
|
}
|
|
1814
2456
|
try {
|
|
1815
|
-
const content =
|
|
2457
|
+
const content = fs17.readFileSync(mcpPath, "utf-8");
|
|
1816
2458
|
JSON.parse(content);
|
|
1817
2459
|
return { name: "mcp-json", result: "PASS", message: ".kiro/settings/mcp.json is valid JSON" };
|
|
1818
2460
|
} catch {
|
|
@@ -1822,19 +2464,19 @@ function checkMcpJson(workspaceRoot) {
|
|
|
1822
2464
|
message: ".kiro/settings/mcp.json is not valid JSON",
|
|
1823
2465
|
fixable: true,
|
|
1824
2466
|
fixAction: () => {
|
|
1825
|
-
const mcpPath2 =
|
|
1826
|
-
|
|
2467
|
+
const mcpPath2 = path16.join(workspaceRoot, ".kiro/settings/mcp.json");
|
|
2468
|
+
fs17.writeFileSync(mcpPath2, '{\n "mcpServers": {}\n}\n', "utf-8");
|
|
1827
2469
|
}
|
|
1828
2470
|
};
|
|
1829
2471
|
}
|
|
1830
2472
|
}
|
|
1831
2473
|
function checkTracking(workspaceRoot) {
|
|
1832
|
-
const trackingPath =
|
|
1833
|
-
if (!
|
|
2474
|
+
const trackingPath = path16.join(workspaceRoot, ".kiro/.kiro-kit.json");
|
|
2475
|
+
if (!fs17.existsSync(trackingPath)) {
|
|
1834
2476
|
return { name: "tracking", result: "WARN", message: ".kiro/.kiro-kit.json not found (no presets installed)" };
|
|
1835
2477
|
}
|
|
1836
2478
|
try {
|
|
1837
|
-
const content =
|
|
2479
|
+
const content = fs17.readFileSync(trackingPath, "utf-8");
|
|
1838
2480
|
JSON.parse(content);
|
|
1839
2481
|
return { name: "tracking", result: "PASS", message: ".kiro/.kiro-kit.json is valid" };
|
|
1840
2482
|
} catch {
|
|
@@ -1858,8 +2500,8 @@ function checkTrackedFiles(workspaceRoot) {
|
|
|
1858
2500
|
const missing = [];
|
|
1859
2501
|
for (const preset of tracking.presets) {
|
|
1860
2502
|
for (const file of preset.files) {
|
|
1861
|
-
const fullPath =
|
|
1862
|
-
if (!
|
|
2503
|
+
const fullPath = path16.resolve(workspaceRoot, file.target);
|
|
2504
|
+
if (!fs17.existsSync(fullPath)) {
|
|
1863
2505
|
missing.push(file.target);
|
|
1864
2506
|
}
|
|
1865
2507
|
}
|
|
@@ -1874,20 +2516,20 @@ function checkTrackedFiles(workspaceRoot) {
|
|
|
1874
2516
|
};
|
|
1875
2517
|
}
|
|
1876
2518
|
function checkSteeringFrontMatter(workspaceRoot) {
|
|
1877
|
-
const steeringDir =
|
|
1878
|
-
if (!
|
|
2519
|
+
const steeringDir = path16.join(workspaceRoot, ".kiro/steering");
|
|
2520
|
+
if (!fs17.existsSync(steeringDir)) {
|
|
1879
2521
|
return { name: "steering-fm", result: "PASS", message: "No steering files to check" };
|
|
1880
2522
|
}
|
|
1881
2523
|
const issues = [];
|
|
1882
2524
|
const walkDir = (dir) => {
|
|
1883
2525
|
let entries;
|
|
1884
2526
|
try {
|
|
1885
|
-
entries =
|
|
2527
|
+
entries = fs17.readdirSync(dir, { withFileTypes: true });
|
|
1886
2528
|
} catch {
|
|
1887
2529
|
return;
|
|
1888
2530
|
}
|
|
1889
2531
|
for (const entry of entries) {
|
|
1890
|
-
const fullPath =
|
|
2532
|
+
const fullPath = path16.join(dir, entry.name);
|
|
1891
2533
|
if (entry.isDirectory()) {
|
|
1892
2534
|
walkDir(fullPath);
|
|
1893
2535
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -1911,7 +2553,7 @@ function checkSteeringFrontMatter(workspaceRoot) {
|
|
|
1911
2553
|
}
|
|
1912
2554
|
function checkSingleSteering(filePath, issues) {
|
|
1913
2555
|
try {
|
|
1914
|
-
const content =
|
|
2556
|
+
const content = fs17.readFileSync(filePath, "utf-8");
|
|
1915
2557
|
const lines = content.split("\n");
|
|
1916
2558
|
if (lines[0]?.trim() !== "---") return;
|
|
1917
2559
|
let endIdx = -1;
|
|
@@ -1935,16 +2577,16 @@ function fixSteeringWhitespace(steeringDir) {
|
|
|
1935
2577
|
const walkAndFix = (dir) => {
|
|
1936
2578
|
let entries;
|
|
1937
2579
|
try {
|
|
1938
|
-
entries =
|
|
2580
|
+
entries = fs17.readdirSync(dir, { withFileTypes: true });
|
|
1939
2581
|
} catch {
|
|
1940
2582
|
return;
|
|
1941
2583
|
}
|
|
1942
2584
|
for (const entry of entries) {
|
|
1943
|
-
const fullPath =
|
|
2585
|
+
const fullPath = path16.join(dir, entry.name);
|
|
1944
2586
|
if (entry.isDirectory()) {
|
|
1945
2587
|
walkAndFix(fullPath);
|
|
1946
2588
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
1947
|
-
const content =
|
|
2589
|
+
const content = fs17.readFileSync(fullPath, "utf-8");
|
|
1948
2590
|
const lines = content.split("\n");
|
|
1949
2591
|
if (lines[0]?.trim() !== "---") continue;
|
|
1950
2592
|
let endIdx = -1;
|
|
@@ -1964,7 +2606,7 @@ function fixSteeringWhitespace(steeringDir) {
|
|
|
1964
2606
|
}
|
|
1965
2607
|
}
|
|
1966
2608
|
if (changed) {
|
|
1967
|
-
|
|
2609
|
+
fs17.writeFileSync(fullPath, lines.join("\n"), "utf-8");
|
|
1968
2610
|
}
|
|
1969
2611
|
}
|
|
1970
2612
|
}
|
|
@@ -1972,12 +2614,12 @@ function fixSteeringWhitespace(steeringDir) {
|
|
|
1972
2614
|
walkAndFix(steeringDir);
|
|
1973
2615
|
}
|
|
1974
2616
|
function checkMetadataJson(workspaceRoot) {
|
|
1975
|
-
const metaPath =
|
|
1976
|
-
if (!
|
|
2617
|
+
const metaPath = path16.join(workspaceRoot, ".kiro/metadata.json");
|
|
2618
|
+
if (!fs17.existsSync(metaPath)) {
|
|
1977
2619
|
return { name: "metadata-json", result: "WARN", message: ".kiro/metadata.json not found (optional)" };
|
|
1978
2620
|
}
|
|
1979
2621
|
try {
|
|
1980
|
-
const content =
|
|
2622
|
+
const content = fs17.readFileSync(metaPath, "utf-8");
|
|
1981
2623
|
const parsed = JSON.parse(content);
|
|
1982
2624
|
if (!parsed.version || !parsed.name) {
|
|
1983
2625
|
return { name: "metadata-json", result: "FAIL", message: ".kiro/metadata.json missing required fields (version, name)" };
|
|
@@ -1991,12 +2633,12 @@ function checkStatuslineExecBit(workspaceRoot) {
|
|
|
1991
2633
|
if (process11.platform === "win32") {
|
|
1992
2634
|
return { name: "statusline-exec", result: "PASS", message: "Statusline exec bit check (skipped on Windows)" };
|
|
1993
2635
|
}
|
|
1994
|
-
const shPath =
|
|
1995
|
-
if (!
|
|
2636
|
+
const shPath = path16.join(workspaceRoot, ".kiro/statusline.sh");
|
|
2637
|
+
if (!fs17.existsSync(shPath)) {
|
|
1996
2638
|
return { name: "statusline-exec", result: "PASS", message: "No statusline.sh to check" };
|
|
1997
2639
|
}
|
|
1998
2640
|
try {
|
|
1999
|
-
const stat =
|
|
2641
|
+
const stat = fs17.statSync(shPath);
|
|
2000
2642
|
const isExecutable = (stat.mode & 73) !== 0;
|
|
2001
2643
|
if (isExecutable) {
|
|
2002
2644
|
return { name: "statusline-exec", result: "PASS", message: "statusline.sh is executable" };
|
|
@@ -2007,7 +2649,7 @@ function checkStatuslineExecBit(workspaceRoot) {
|
|
|
2007
2649
|
message: "statusline.sh is not executable",
|
|
2008
2650
|
fixable: true,
|
|
2009
2651
|
fixAction: () => {
|
|
2010
|
-
|
|
2652
|
+
fs17.chmodSync(shPath, 493);
|
|
2011
2653
|
}
|
|
2012
2654
|
};
|
|
2013
2655
|
} catch {
|
|
@@ -2016,24 +2658,24 @@ function checkStatuslineExecBit(workspaceRoot) {
|
|
|
2016
2658
|
}
|
|
2017
2659
|
|
|
2018
2660
|
// src/commands/telemetry.ts
|
|
2019
|
-
import
|
|
2020
|
-
import
|
|
2661
|
+
import fs18 from "fs";
|
|
2662
|
+
import path17 from "path";
|
|
2021
2663
|
import os2 from "os";
|
|
2022
2664
|
import process12 from "process";
|
|
2023
|
-
var CONFIG_DIR =
|
|
2024
|
-
var CONFIG_FILE =
|
|
2665
|
+
var CONFIG_DIR = path17.join(os2.homedir(), ".kiro-kit");
|
|
2666
|
+
var CONFIG_FILE = path17.join(CONFIG_DIR, "config.json");
|
|
2025
2667
|
function readConfig() {
|
|
2026
2668
|
try {
|
|
2027
|
-
if (
|
|
2028
|
-
return JSON.parse(
|
|
2669
|
+
if (fs18.existsSync(CONFIG_FILE)) {
|
|
2670
|
+
return JSON.parse(fs18.readFileSync(CONFIG_FILE, "utf-8"));
|
|
2029
2671
|
}
|
|
2030
2672
|
} catch {
|
|
2031
2673
|
}
|
|
2032
2674
|
return { telemetry: false };
|
|
2033
2675
|
}
|
|
2034
2676
|
function writeConfig(config) {
|
|
2035
|
-
|
|
2036
|
-
|
|
2677
|
+
fs18.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
2678
|
+
fs18.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
2037
2679
|
}
|
|
2038
2680
|
function registerTelemetryCommand(program2) {
|
|
2039
2681
|
const telemetry = program2.command("telemetry").description("Manage anonymous usage telemetry");
|