kiro-kit 0.2.4 → 0.3.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/README.md +35 -0
- package/dist/index.js +764 -112
- 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 +1 -1
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,7 +1233,7 @@ function setupSigintHandler() {
|
|
|
674
1233
|
async function multiPickPrompt(items) {
|
|
675
1234
|
const selected = /* @__PURE__ */ new Set();
|
|
676
1235
|
let cursor = 0;
|
|
677
|
-
const rl =
|
|
1236
|
+
const rl = readline2.createInterface({
|
|
678
1237
|
input: process5.stdin,
|
|
679
1238
|
output: process5.stdout,
|
|
680
1239
|
terminal: false
|
|
@@ -798,7 +1357,7 @@ async function multiPickPrompt(items) {
|
|
|
798
1357
|
async function confirmPrompt(message) {
|
|
799
1358
|
if (!process5.stdin.isTTY) return true;
|
|
800
1359
|
return new Promise((resolve2, reject) => {
|
|
801
|
-
const rl =
|
|
1360
|
+
const rl = readline2.createInterface({
|
|
802
1361
|
input: process5.stdin,
|
|
803
1362
|
output: process5.stdout
|
|
804
1363
|
});
|
|
@@ -815,7 +1374,7 @@ async function confirmPrompt(message) {
|
|
|
815
1374
|
}
|
|
816
1375
|
async function conflictPrompt(target) {
|
|
817
1376
|
if (!process5.stdin.isTTY) return "skip";
|
|
818
|
-
const relTarget =
|
|
1377
|
+
const relTarget = path13.relative(process5.cwd(), target);
|
|
819
1378
|
process5.stdout.write(
|
|
820
1379
|
`
|
|
821
1380
|
${color.yellow("?")} File ${color.bold(relTarget)} already exists with different content.
|
|
@@ -826,7 +1385,7 @@ ${color.yellow("?")} File ${color.bold(relTarget)} already exists with different
|
|
|
826
1385
|
`
|
|
827
1386
|
);
|
|
828
1387
|
return new Promise((resolve2, reject) => {
|
|
829
|
-
const rl =
|
|
1388
|
+
const rl = readline2.createInterface({
|
|
830
1389
|
input: process5.stdin,
|
|
831
1390
|
output: process5.stdout
|
|
832
1391
|
});
|
|
@@ -849,7 +1408,7 @@ function sha2562(data) {
|
|
|
849
1408
|
return crypto3.createHash("sha256").update(data).digest("hex");
|
|
850
1409
|
}
|
|
851
1410
|
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) => {
|
|
1411
|
+
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
1412
|
setupSigintHandler();
|
|
854
1413
|
try {
|
|
855
1414
|
await runInit(opts);
|
|
@@ -926,18 +1485,18 @@ async function runInit(opts) {
|
|
|
926
1485
|
const settingsFiles = manifest.files.filter((f) => f.type === "settings");
|
|
927
1486
|
const statuslineFiles = manifest.files.filter((f) => f.type === "statusline");
|
|
928
1487
|
for (const fileEntry of regularFiles) {
|
|
929
|
-
const sourcePath =
|
|
930
|
-
const targetPath =
|
|
1488
|
+
const sourcePath = path13.join(presetDir, fileEntry.source);
|
|
1489
|
+
const targetPath = path13.resolve(workspaceRoot, fileEntry.target);
|
|
931
1490
|
if (!safePathInside(workspaceRoot, fileEntry.target)) {
|
|
932
1491
|
logger.warn(`Skipping unsafe path: ${fileEntry.target}`);
|
|
933
1492
|
filesSkipped++;
|
|
934
1493
|
continue;
|
|
935
1494
|
}
|
|
936
|
-
if (!
|
|
1495
|
+
if (!fs14.existsSync(sourcePath)) {
|
|
937
1496
|
logger.debug(`Source file missing: ${sourcePath}`);
|
|
938
1497
|
continue;
|
|
939
1498
|
}
|
|
940
|
-
const sourceContent =
|
|
1499
|
+
const sourceContent = fs14.readFileSync(sourcePath);
|
|
941
1500
|
const action = await resolve({
|
|
942
1501
|
target: targetPath,
|
|
943
1502
|
sourceContent,
|
|
@@ -948,11 +1507,11 @@ async function runInit(opts) {
|
|
|
948
1507
|
});
|
|
949
1508
|
switch (action) {
|
|
950
1509
|
case "WRITE_NEW":
|
|
951
|
-
|
|
1510
|
+
fs14.mkdirSync(path13.dirname(targetPath), { recursive: true });
|
|
952
1511
|
atomicWrite(targetPath, sourceContent.toString("utf-8"));
|
|
953
1512
|
if (fileEntry.executable && process5.platform !== "win32") {
|
|
954
1513
|
try {
|
|
955
|
-
|
|
1514
|
+
fs14.chmodSync(targetPath, 493);
|
|
956
1515
|
} catch {
|
|
957
1516
|
}
|
|
958
1517
|
}
|
|
@@ -963,7 +1522,7 @@ async function runInit(opts) {
|
|
|
963
1522
|
atomicWrite(targetPath, sourceContent.toString("utf-8"));
|
|
964
1523
|
if (fileEntry.executable && process5.platform !== "win32") {
|
|
965
1524
|
try {
|
|
966
|
-
|
|
1525
|
+
fs14.chmodSync(targetPath, 493);
|
|
967
1526
|
} catch {
|
|
968
1527
|
}
|
|
969
1528
|
}
|
|
@@ -997,54 +1556,147 @@ async function runInit(opts) {
|
|
|
997
1556
|
}
|
|
998
1557
|
}
|
|
999
1558
|
if (mcpFiles.length > 0 && manifest.mcpServers) {
|
|
1000
|
-
const mcpPath =
|
|
1559
|
+
const mcpPath = path13.join(workspaceRoot, ".kiro/settings/mcp.json");
|
|
1001
1560
|
let existingMcp = null;
|
|
1002
|
-
if (
|
|
1561
|
+
if (fs14.existsSync(mcpPath)) {
|
|
1003
1562
|
try {
|
|
1004
|
-
existingMcp = JSON.parse(
|
|
1563
|
+
existingMcp = JSON.parse(fs14.readFileSync(mcpPath, "utf-8"));
|
|
1005
1564
|
} catch {
|
|
1006
1565
|
existingMcp = null;
|
|
1007
1566
|
}
|
|
1008
1567
|
}
|
|
1009
1568
|
const merged = mergeMCP(existingMcp, manifest.mcpServers, manifest.name);
|
|
1010
|
-
|
|
1569
|
+
fs14.mkdirSync(path13.dirname(mcpPath), { recursive: true });
|
|
1011
1570
|
atomicWrite(mcpPath, JSON.stringify(merged, null, 2) + "\n");
|
|
1012
1571
|
filesWritten++;
|
|
1013
1572
|
}
|
|
1014
1573
|
if (settingsFiles.length > 0) {
|
|
1015
|
-
const settingsPath =
|
|
1574
|
+
const settingsPath = path13.join(workspaceRoot, ".kiro/settings.json");
|
|
1016
1575
|
let existingSettings = null;
|
|
1017
|
-
if (
|
|
1576
|
+
if (fs14.existsSync(settingsPath)) {
|
|
1018
1577
|
try {
|
|
1019
1578
|
existingSettings = JSON.parse(
|
|
1020
|
-
|
|
1579
|
+
fs14.readFileSync(settingsPath, "utf-8")
|
|
1021
1580
|
);
|
|
1022
1581
|
} catch {
|
|
1023
1582
|
existingSettings = null;
|
|
1024
1583
|
}
|
|
1025
1584
|
}
|
|
1026
|
-
const presetSettingsPath =
|
|
1027
|
-
if (
|
|
1585
|
+
const presetSettingsPath = path13.join(presetDir, "settings.json");
|
|
1586
|
+
if (fs14.existsSync(presetSettingsPath)) {
|
|
1028
1587
|
const presetSettings = JSON.parse(
|
|
1029
|
-
|
|
1588
|
+
fs14.readFileSync(presetSettingsPath, "utf-8")
|
|
1030
1589
|
);
|
|
1031
1590
|
const resolvedSettings = resolveSettingsCommand(
|
|
1032
1591
|
presetSettings
|
|
1033
1592
|
);
|
|
1034
1593
|
const merged = mergeSettings(existingSettings, resolvedSettings);
|
|
1035
|
-
|
|
1594
|
+
fs14.mkdirSync(path13.dirname(settingsPath), { recursive: true });
|
|
1036
1595
|
atomicWrite(settingsPath, JSON.stringify(merged, null, 2) + "\n");
|
|
1037
1596
|
filesWritten++;
|
|
1038
1597
|
}
|
|
1039
1598
|
}
|
|
1040
|
-
const mcpExampleSource =
|
|
1041
|
-
if (
|
|
1042
|
-
const mcpExampleTarget =
|
|
1043
|
-
if (!
|
|
1044
|
-
|
|
1045
|
-
|
|
1599
|
+
const mcpExampleSource = path13.join(presetDir, ".mcp.json.example");
|
|
1600
|
+
if (fs14.existsSync(mcpExampleSource)) {
|
|
1601
|
+
const mcpExampleTarget = path13.join(workspaceRoot, ".kiro/.mcp.json.example");
|
|
1602
|
+
if (!fs14.existsSync(mcpExampleTarget)) {
|
|
1603
|
+
fs14.mkdirSync(path13.dirname(mcpExampleTarget), { recursive: true });
|
|
1604
|
+
fs14.copyFileSync(mcpExampleSource, mcpExampleTarget);
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
try {
|
|
1609
|
+
if (opts.powers !== "none") {
|
|
1610
|
+
const allPowerEntries = presets.map((p) => {
|
|
1611
|
+
const result = loadPowers(p.dir);
|
|
1612
|
+
if (!result.ok) {
|
|
1613
|
+
logger.warn(`Powers: ${result.error}`);
|
|
1614
|
+
}
|
|
1615
|
+
return result.powers;
|
|
1616
|
+
});
|
|
1617
|
+
const mergedPowers = mergePowers(allPowerEntries);
|
|
1618
|
+
if (mergedPowers.length > 0) {
|
|
1619
|
+
const promptResult = await promptPowersTier(mergedPowers, {
|
|
1620
|
+
powersFlag: opts.powers,
|
|
1621
|
+
yes: opts.yes
|
|
1622
|
+
});
|
|
1623
|
+
const filteredPowers = promptResult.selectedTiers.length > 0 ? filterByTier(mergedPowers, promptResult.selectedTiers) : [];
|
|
1624
|
+
if (promptResult.confirmMCP) {
|
|
1625
|
+
try {
|
|
1626
|
+
const presetMCPConfigs = presets.map((p) => getMCPConfig(p.manifest.name));
|
|
1627
|
+
let combinedMCP = null;
|
|
1628
|
+
const mcpJsonPath = path13.join(workspaceRoot, ".mcp.json");
|
|
1629
|
+
if (fs14.existsSync(mcpJsonPath)) {
|
|
1630
|
+
try {
|
|
1631
|
+
combinedMCP = JSON.parse(fs14.readFileSync(mcpJsonPath, "utf-8"));
|
|
1632
|
+
} catch {
|
|
1633
|
+
logger.warn("Existing .mcp.json is invalid JSON, will create fresh.");
|
|
1634
|
+
combinedMCP = null;
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
for (const mcpConfig of presetMCPConfigs) {
|
|
1638
|
+
combinedMCP = mergeMCPConfig(combinedMCP, mcpConfig);
|
|
1639
|
+
}
|
|
1640
|
+
let confirmWrite = true;
|
|
1641
|
+
if (!opts.yes) {
|
|
1642
|
+
confirmWrite = await confirmPrompt(
|
|
1643
|
+
"Configure MCP servers in .mcp.json? (Y/n)"
|
|
1644
|
+
);
|
|
1645
|
+
}
|
|
1646
|
+
if (confirmWrite && combinedMCP) {
|
|
1647
|
+
writeMCPConfig(workspaceRoot, combinedMCP);
|
|
1648
|
+
if (!opts.quiet) {
|
|
1649
|
+
logger.info("MCP servers configured in .mcp.json");
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
} catch (err) {
|
|
1653
|
+
logger.warn(`MCP configuration failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
displayPowersRecommendations(filteredPowers, opts.quiet ?? false);
|
|
1657
|
+
try {
|
|
1658
|
+
const mcpServersForGuide = presets.reduce(
|
|
1659
|
+
(acc, p) => ({ ...acc, ...getMCPConfig(p.manifest.name).servers }),
|
|
1660
|
+
{}
|
|
1661
|
+
);
|
|
1662
|
+
const guideContent = generateSetupGuide({
|
|
1663
|
+
powers: filteredPowers,
|
|
1664
|
+
presetNames: selectedNames,
|
|
1665
|
+
mcpServers: mcpServersForGuide
|
|
1666
|
+
});
|
|
1667
|
+
writeSetupGuide(workspaceRoot, guideContent);
|
|
1668
|
+
if (!opts.quiet) {
|
|
1669
|
+
logger.info("Setup guide written to .kiro/POWERS-SETUP.md");
|
|
1670
|
+
}
|
|
1671
|
+
} catch (err) {
|
|
1672
|
+
logger.warn(`Setup guide generation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1673
|
+
}
|
|
1674
|
+
try {
|
|
1675
|
+
const mcpConfigForEnv = presets.reduce(
|
|
1676
|
+
(acc, p) => {
|
|
1677
|
+
const cfg = getMCPConfig(p.manifest.name);
|
|
1678
|
+
return { servers: { ...acc.servers, ...cfg.servers } };
|
|
1679
|
+
},
|
|
1680
|
+
{ servers: {} }
|
|
1681
|
+
);
|
|
1682
|
+
const envVars = collectEnvVars(mcpConfigForEnv, filteredPowers);
|
|
1683
|
+
if (envVars.length > 0) {
|
|
1684
|
+
const existingEnv = readExistingEnv(workspaceRoot);
|
|
1685
|
+
const envContent = generateEnvTemplate(existingEnv, envVars);
|
|
1686
|
+
if (envContent.trim().length > 0) {
|
|
1687
|
+
writeEnvTemplate(workspaceRoot, envContent);
|
|
1688
|
+
if (!opts.quiet) {
|
|
1689
|
+
logger.info("Environment template updated in .env.example");
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
} catch (err) {
|
|
1694
|
+
logger.warn(`Env template generation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1695
|
+
}
|
|
1046
1696
|
}
|
|
1047
1697
|
}
|
|
1698
|
+
} catch (err) {
|
|
1699
|
+
logger.warn(`Powers integration failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1048
1700
|
}
|
|
1049
1701
|
const kitVersion = getKitVersion();
|
|
1050
1702
|
const presetMetas = presets.map((p) => ({
|
|
@@ -1094,7 +1746,7 @@ function generateTimestamp2() {
|
|
|
1094
1746
|
function getKitVersion() {
|
|
1095
1747
|
try {
|
|
1096
1748
|
const pkgPath = new URL("../package.json", import.meta.url);
|
|
1097
|
-
const pkg2 = JSON.parse(
|
|
1749
|
+
const pkg2 = JSON.parse(fs14.readFileSync(pkgPath, "utf-8"));
|
|
1098
1750
|
return pkg2.version;
|
|
1099
1751
|
} catch {
|
|
1100
1752
|
return "0.1.0";
|
|
@@ -1102,8 +1754,8 @@ function getKitVersion() {
|
|
|
1102
1754
|
}
|
|
1103
1755
|
|
|
1104
1756
|
// src/commands/add.ts
|
|
1105
|
-
import
|
|
1106
|
-
import
|
|
1757
|
+
import fs15 from "fs";
|
|
1758
|
+
import path14 from "path";
|
|
1107
1759
|
import process6 from "process";
|
|
1108
1760
|
import crypto4 from "crypto";
|
|
1109
1761
|
function sha2563(data) {
|
|
@@ -1123,7 +1775,7 @@ function generateTimestamp3() {
|
|
|
1123
1775
|
function getKitVersion2() {
|
|
1124
1776
|
try {
|
|
1125
1777
|
const pkgPath = new URL("../../package.json", import.meta.url);
|
|
1126
|
-
const pkg2 = JSON.parse(
|
|
1778
|
+
const pkg2 = JSON.parse(fs15.readFileSync(pkgPath, "utf-8"));
|
|
1127
1779
|
return pkg2.version;
|
|
1128
1780
|
} catch {
|
|
1129
1781
|
return "0.1.0";
|
|
@@ -1131,8 +1783,8 @@ function getKitVersion2() {
|
|
|
1131
1783
|
}
|
|
1132
1784
|
async function conflictPrompt2(target) {
|
|
1133
1785
|
if (!process6.stdin.isTTY) return "skip";
|
|
1134
|
-
const { default:
|
|
1135
|
-
const relTarget =
|
|
1786
|
+
const { default: readline3 } = await import("readline");
|
|
1787
|
+
const relTarget = path14.relative(process6.cwd(), target);
|
|
1136
1788
|
process6.stdout.write(
|
|
1137
1789
|
`
|
|
1138
1790
|
${color.yellow("?")} File ${color.bold(relTarget)} already exists with different content.
|
|
@@ -1143,7 +1795,7 @@ ${color.yellow("?")} File ${color.bold(relTarget)} already exists with different
|
|
|
1143
1795
|
`
|
|
1144
1796
|
);
|
|
1145
1797
|
return new Promise((resolve2) => {
|
|
1146
|
-
const rl =
|
|
1798
|
+
const rl = readline3.createInterface({ input: process6.stdin, output: process6.stdout });
|
|
1147
1799
|
rl.question(` Choice (overwrite/skip/diff/all): `, (answer) => {
|
|
1148
1800
|
rl.close();
|
|
1149
1801
|
const a = answer.trim().toLowerCase();
|
|
@@ -1174,9 +1826,9 @@ async function runAdd(presetName, opts) {
|
|
|
1174
1826
|
);
|
|
1175
1827
|
process6.exit(1);
|
|
1176
1828
|
}
|
|
1177
|
-
const kiroDir =
|
|
1178
|
-
if (!
|
|
1179
|
-
|
|
1829
|
+
const kiroDir = path14.join(workspaceRoot, ".kiro");
|
|
1830
|
+
if (!fs15.existsSync(kiroDir)) {
|
|
1831
|
+
fs15.mkdirSync(kiroDir, { recursive: true });
|
|
1180
1832
|
logger.info("Created .kiro/ directory.");
|
|
1181
1833
|
}
|
|
1182
1834
|
const preset = load(presetName);
|
|
@@ -1195,18 +1847,18 @@ async function runAdd(presetName, opts) {
|
|
|
1195
1847
|
);
|
|
1196
1848
|
const statuslineFiles = manifest.files.filter((f) => f.type === "statusline");
|
|
1197
1849
|
for (const fileEntry of regularFiles) {
|
|
1198
|
-
const sourcePath =
|
|
1199
|
-
const targetPath =
|
|
1850
|
+
const sourcePath = path14.join(presetDir, fileEntry.source);
|
|
1851
|
+
const targetPath = path14.resolve(workspaceRoot, fileEntry.target);
|
|
1200
1852
|
if (!safePathInside(workspaceRoot, fileEntry.target)) {
|
|
1201
1853
|
logger.warn(`Skipping unsafe path: ${fileEntry.target}`);
|
|
1202
1854
|
filesSkipped++;
|
|
1203
1855
|
continue;
|
|
1204
1856
|
}
|
|
1205
|
-
if (!
|
|
1857
|
+
if (!fs15.existsSync(sourcePath)) {
|
|
1206
1858
|
logger.debug(`Source file missing: ${sourcePath}`);
|
|
1207
1859
|
continue;
|
|
1208
1860
|
}
|
|
1209
|
-
const sourceContent =
|
|
1861
|
+
const sourceContent = fs15.readFileSync(sourcePath);
|
|
1210
1862
|
const action = await resolve({
|
|
1211
1863
|
target: targetPath,
|
|
1212
1864
|
sourceContent,
|
|
@@ -1217,11 +1869,11 @@ async function runAdd(presetName, opts) {
|
|
|
1217
1869
|
});
|
|
1218
1870
|
switch (action) {
|
|
1219
1871
|
case "WRITE_NEW":
|
|
1220
|
-
|
|
1872
|
+
fs15.mkdirSync(path14.dirname(targetPath), { recursive: true });
|
|
1221
1873
|
atomicWrite(targetPath, sourceContent.toString("utf-8"));
|
|
1222
1874
|
if (fileEntry.executable && process6.platform !== "win32") {
|
|
1223
1875
|
try {
|
|
1224
|
-
|
|
1876
|
+
fs15.chmodSync(targetPath, 493);
|
|
1225
1877
|
} catch {
|
|
1226
1878
|
}
|
|
1227
1879
|
}
|
|
@@ -1232,7 +1884,7 @@ async function runAdd(presetName, opts) {
|
|
|
1232
1884
|
atomicWrite(targetPath, sourceContent.toString("utf-8"));
|
|
1233
1885
|
if (fileEntry.executable && process6.platform !== "win32") {
|
|
1234
1886
|
try {
|
|
1235
|
-
|
|
1887
|
+
fs15.chmodSync(targetPath, 493);
|
|
1236
1888
|
} catch {
|
|
1237
1889
|
}
|
|
1238
1890
|
}
|
|
@@ -1266,37 +1918,37 @@ async function runAdd(presetName, opts) {
|
|
|
1266
1918
|
}
|
|
1267
1919
|
}
|
|
1268
1920
|
if (manifest.mcpServers) {
|
|
1269
|
-
const mcpPath =
|
|
1921
|
+
const mcpPath = path14.join(workspaceRoot, ".kiro/settings/mcp.json");
|
|
1270
1922
|
let existingMcp = null;
|
|
1271
|
-
if (
|
|
1923
|
+
if (fs15.existsSync(mcpPath)) {
|
|
1272
1924
|
try {
|
|
1273
|
-
existingMcp = JSON.parse(
|
|
1925
|
+
existingMcp = JSON.parse(fs15.readFileSync(mcpPath, "utf-8"));
|
|
1274
1926
|
} catch {
|
|
1275
1927
|
existingMcp = null;
|
|
1276
1928
|
}
|
|
1277
1929
|
}
|
|
1278
1930
|
const merged = mergeMCP(existingMcp, manifest.mcpServers, manifest.name);
|
|
1279
|
-
|
|
1931
|
+
fs15.mkdirSync(path14.dirname(mcpPath), { recursive: true });
|
|
1280
1932
|
atomicWrite(mcpPath, JSON.stringify(merged, null, 2) + "\n");
|
|
1281
1933
|
filesWritten++;
|
|
1282
1934
|
}
|
|
1283
|
-
const presetSettingsPath =
|
|
1284
|
-
if (
|
|
1285
|
-
const settingsPath =
|
|
1935
|
+
const presetSettingsPath = path14.join(presetDir, "settings.json");
|
|
1936
|
+
if (fs15.existsSync(presetSettingsPath)) {
|
|
1937
|
+
const settingsPath = path14.join(workspaceRoot, ".kiro/settings.json");
|
|
1286
1938
|
let existingSettings = null;
|
|
1287
|
-
if (
|
|
1939
|
+
if (fs15.existsSync(settingsPath)) {
|
|
1288
1940
|
try {
|
|
1289
|
-
existingSettings = JSON.parse(
|
|
1941
|
+
existingSettings = JSON.parse(fs15.readFileSync(settingsPath, "utf-8"));
|
|
1290
1942
|
} catch {
|
|
1291
1943
|
existingSettings = null;
|
|
1292
1944
|
}
|
|
1293
1945
|
}
|
|
1294
|
-
const presetSettings = JSON.parse(
|
|
1946
|
+
const presetSettings = JSON.parse(fs15.readFileSync(presetSettingsPath, "utf-8"));
|
|
1295
1947
|
const resolvedSettings = resolveSettingsCommand(
|
|
1296
1948
|
presetSettings
|
|
1297
1949
|
);
|
|
1298
1950
|
const merged = mergeSettings(existingSettings, resolvedSettings);
|
|
1299
|
-
|
|
1951
|
+
fs15.mkdirSync(path14.dirname(settingsPath), { recursive: true });
|
|
1300
1952
|
atomicWrite(settingsPath, JSON.stringify(merged, null, 2) + "\n");
|
|
1301
1953
|
filesWritten++;
|
|
1302
1954
|
}
|
|
@@ -1511,8 +2163,8 @@ ${color.bold(manifest.name)} v${manifest.version}
|
|
|
1511
2163
|
}
|
|
1512
2164
|
|
|
1513
2165
|
// src/commands/update.ts
|
|
1514
|
-
import
|
|
1515
|
-
import
|
|
2166
|
+
import fs16 from "fs";
|
|
2167
|
+
import path15 from "path";
|
|
1516
2168
|
import process9 from "process";
|
|
1517
2169
|
import crypto5 from "crypto";
|
|
1518
2170
|
function sha2564(data) {
|
|
@@ -1532,7 +2184,7 @@ function generateTimestamp4() {
|
|
|
1532
2184
|
function getKitVersion3() {
|
|
1533
2185
|
try {
|
|
1534
2186
|
const pkgPath = new URL("../../package.json", import.meta.url);
|
|
1535
|
-
const pkg2 = JSON.parse(
|
|
2187
|
+
const pkg2 = JSON.parse(fs16.readFileSync(pkgPath, "utf-8"));
|
|
1536
2188
|
return pkg2.version;
|
|
1537
2189
|
} catch {
|
|
1538
2190
|
return "0.1.0";
|
|
@@ -1540,8 +2192,8 @@ function getKitVersion3() {
|
|
|
1540
2192
|
}
|
|
1541
2193
|
async function conflictPrompt3(target) {
|
|
1542
2194
|
if (!process9.stdin.isTTY) return "skip";
|
|
1543
|
-
const { default:
|
|
1544
|
-
const relTarget =
|
|
2195
|
+
const { default: readline3 } = await import("readline");
|
|
2196
|
+
const relTarget = path15.relative(process9.cwd(), target);
|
|
1545
2197
|
process9.stdout.write(
|
|
1546
2198
|
`
|
|
1547
2199
|
${color.yellow("?")} File ${color.bold(relTarget)} has changed in the new version.
|
|
@@ -1552,7 +2204,7 @@ ${color.yellow("?")} File ${color.bold(relTarget)} has changed in the new versio
|
|
|
1552
2204
|
`
|
|
1553
2205
|
);
|
|
1554
2206
|
return new Promise((resolve2) => {
|
|
1555
|
-
const rl =
|
|
2207
|
+
const rl = readline3.createInterface({ input: process9.stdin, output: process9.stdout });
|
|
1556
2208
|
rl.question(` Choice (overwrite/skip/diff/all): `, (answer) => {
|
|
1557
2209
|
rl.close();
|
|
1558
2210
|
const a = answer.trim().toLowerCase();
|
|
@@ -1609,11 +2261,11 @@ async function runUpdate(opts) {
|
|
|
1609
2261
|
const updatedFiles = [];
|
|
1610
2262
|
for (const fileEntry of bundled.manifest.files) {
|
|
1611
2263
|
if (["mcp", "settings", "statusline"].includes(fileEntry.type)) continue;
|
|
1612
|
-
const sourcePath =
|
|
1613
|
-
const targetPath =
|
|
2264
|
+
const sourcePath = path15.join(presetDir, fileEntry.source);
|
|
2265
|
+
const targetPath = path15.resolve(workspaceRoot, fileEntry.target);
|
|
1614
2266
|
if (!safePathInside(workspaceRoot, fileEntry.target)) continue;
|
|
1615
|
-
if (!
|
|
1616
|
-
const sourceContent =
|
|
2267
|
+
if (!fs16.existsSync(sourcePath)) continue;
|
|
2268
|
+
const sourceContent = fs16.readFileSync(sourcePath);
|
|
1617
2269
|
const newHash = sha2564(sourceContent);
|
|
1618
2270
|
const trackedFile = trackedPreset.files.find((f) => f.target === fileEntry.target);
|
|
1619
2271
|
if (trackedFile && trackedFile.contentHash === newHash) {
|
|
@@ -1629,11 +2281,11 @@ async function runUpdate(opts) {
|
|
|
1629
2281
|
});
|
|
1630
2282
|
switch (action) {
|
|
1631
2283
|
case "WRITE_NEW":
|
|
1632
|
-
|
|
2284
|
+
fs16.mkdirSync(path15.dirname(targetPath), { recursive: true });
|
|
1633
2285
|
atomicWrite(targetPath, sourceContent.toString("utf-8"));
|
|
1634
2286
|
if (fileEntry.executable && process9.platform !== "win32") {
|
|
1635
2287
|
try {
|
|
1636
|
-
|
|
2288
|
+
fs16.chmodSync(targetPath, 493);
|
|
1637
2289
|
} catch {
|
|
1638
2290
|
}
|
|
1639
2291
|
}
|
|
@@ -1644,7 +2296,7 @@ async function runUpdate(opts) {
|
|
|
1644
2296
|
atomicWrite(targetPath, sourceContent.toString("utf-8"));
|
|
1645
2297
|
if (fileEntry.executable && process9.platform !== "win32") {
|
|
1646
2298
|
try {
|
|
1647
|
-
|
|
2299
|
+
fs16.chmodSync(targetPath, 493);
|
|
1648
2300
|
} catch {
|
|
1649
2301
|
}
|
|
1650
2302
|
}
|
|
@@ -1732,8 +2384,8 @@ function runRestore(opts) {
|
|
|
1732
2384
|
}
|
|
1733
2385
|
|
|
1734
2386
|
// src/commands/doctor.ts
|
|
1735
|
-
import
|
|
1736
|
-
import
|
|
2387
|
+
import fs17 from "fs";
|
|
2388
|
+
import path16 from "path";
|
|
1737
2389
|
import process11 from "process";
|
|
1738
2390
|
function registerDoctorCommand(program2) {
|
|
1739
2391
|
program2.command("doctor").description("Run workspace health checks").option("--fix", "Auto-fix fixable issues").action((opts) => {
|
|
@@ -1796,8 +2448,8 @@ function checkNodeVersion() {
|
|
|
1796
2448
|
};
|
|
1797
2449
|
}
|
|
1798
2450
|
function checkKiroDir(workspaceRoot) {
|
|
1799
|
-
const kiroDir =
|
|
1800
|
-
if (
|
|
2451
|
+
const kiroDir = path16.join(workspaceRoot, ".kiro");
|
|
2452
|
+
if (fs17.existsSync(kiroDir) && fs17.statSync(kiroDir).isDirectory()) {
|
|
1801
2453
|
return { name: "kiro-dir", result: "PASS", message: ".kiro/ directory exists" };
|
|
1802
2454
|
}
|
|
1803
2455
|
return {
|
|
@@ -1807,12 +2459,12 @@ function checkKiroDir(workspaceRoot) {
|
|
|
1807
2459
|
};
|
|
1808
2460
|
}
|
|
1809
2461
|
function checkMcpJson(workspaceRoot) {
|
|
1810
|
-
const mcpPath =
|
|
1811
|
-
if (!
|
|
2462
|
+
const mcpPath = path16.join(workspaceRoot, ".kiro/settings/mcp.json");
|
|
2463
|
+
if (!fs17.existsSync(mcpPath)) {
|
|
1812
2464
|
return { name: "mcp-json", result: "WARN", message: ".kiro/settings/mcp.json not found (optional)" };
|
|
1813
2465
|
}
|
|
1814
2466
|
try {
|
|
1815
|
-
const content =
|
|
2467
|
+
const content = fs17.readFileSync(mcpPath, "utf-8");
|
|
1816
2468
|
JSON.parse(content);
|
|
1817
2469
|
return { name: "mcp-json", result: "PASS", message: ".kiro/settings/mcp.json is valid JSON" };
|
|
1818
2470
|
} catch {
|
|
@@ -1822,19 +2474,19 @@ function checkMcpJson(workspaceRoot) {
|
|
|
1822
2474
|
message: ".kiro/settings/mcp.json is not valid JSON",
|
|
1823
2475
|
fixable: true,
|
|
1824
2476
|
fixAction: () => {
|
|
1825
|
-
const mcpPath2 =
|
|
1826
|
-
|
|
2477
|
+
const mcpPath2 = path16.join(workspaceRoot, ".kiro/settings/mcp.json");
|
|
2478
|
+
fs17.writeFileSync(mcpPath2, '{\n "mcpServers": {}\n}\n', "utf-8");
|
|
1827
2479
|
}
|
|
1828
2480
|
};
|
|
1829
2481
|
}
|
|
1830
2482
|
}
|
|
1831
2483
|
function checkTracking(workspaceRoot) {
|
|
1832
|
-
const trackingPath =
|
|
1833
|
-
if (!
|
|
2484
|
+
const trackingPath = path16.join(workspaceRoot, ".kiro/.kiro-kit.json");
|
|
2485
|
+
if (!fs17.existsSync(trackingPath)) {
|
|
1834
2486
|
return { name: "tracking", result: "WARN", message: ".kiro/.kiro-kit.json not found (no presets installed)" };
|
|
1835
2487
|
}
|
|
1836
2488
|
try {
|
|
1837
|
-
const content =
|
|
2489
|
+
const content = fs17.readFileSync(trackingPath, "utf-8");
|
|
1838
2490
|
JSON.parse(content);
|
|
1839
2491
|
return { name: "tracking", result: "PASS", message: ".kiro/.kiro-kit.json is valid" };
|
|
1840
2492
|
} catch {
|
|
@@ -1858,8 +2510,8 @@ function checkTrackedFiles(workspaceRoot) {
|
|
|
1858
2510
|
const missing = [];
|
|
1859
2511
|
for (const preset of tracking.presets) {
|
|
1860
2512
|
for (const file of preset.files) {
|
|
1861
|
-
const fullPath =
|
|
1862
|
-
if (!
|
|
2513
|
+
const fullPath = path16.resolve(workspaceRoot, file.target);
|
|
2514
|
+
if (!fs17.existsSync(fullPath)) {
|
|
1863
2515
|
missing.push(file.target);
|
|
1864
2516
|
}
|
|
1865
2517
|
}
|
|
@@ -1874,20 +2526,20 @@ function checkTrackedFiles(workspaceRoot) {
|
|
|
1874
2526
|
};
|
|
1875
2527
|
}
|
|
1876
2528
|
function checkSteeringFrontMatter(workspaceRoot) {
|
|
1877
|
-
const steeringDir =
|
|
1878
|
-
if (!
|
|
2529
|
+
const steeringDir = path16.join(workspaceRoot, ".kiro/steering");
|
|
2530
|
+
if (!fs17.existsSync(steeringDir)) {
|
|
1879
2531
|
return { name: "steering-fm", result: "PASS", message: "No steering files to check" };
|
|
1880
2532
|
}
|
|
1881
2533
|
const issues = [];
|
|
1882
2534
|
const walkDir = (dir) => {
|
|
1883
2535
|
let entries;
|
|
1884
2536
|
try {
|
|
1885
|
-
entries =
|
|
2537
|
+
entries = fs17.readdirSync(dir, { withFileTypes: true });
|
|
1886
2538
|
} catch {
|
|
1887
2539
|
return;
|
|
1888
2540
|
}
|
|
1889
2541
|
for (const entry of entries) {
|
|
1890
|
-
const fullPath =
|
|
2542
|
+
const fullPath = path16.join(dir, entry.name);
|
|
1891
2543
|
if (entry.isDirectory()) {
|
|
1892
2544
|
walkDir(fullPath);
|
|
1893
2545
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -1911,7 +2563,7 @@ function checkSteeringFrontMatter(workspaceRoot) {
|
|
|
1911
2563
|
}
|
|
1912
2564
|
function checkSingleSteering(filePath, issues) {
|
|
1913
2565
|
try {
|
|
1914
|
-
const content =
|
|
2566
|
+
const content = fs17.readFileSync(filePath, "utf-8");
|
|
1915
2567
|
const lines = content.split("\n");
|
|
1916
2568
|
if (lines[0]?.trim() !== "---") return;
|
|
1917
2569
|
let endIdx = -1;
|
|
@@ -1935,16 +2587,16 @@ function fixSteeringWhitespace(steeringDir) {
|
|
|
1935
2587
|
const walkAndFix = (dir) => {
|
|
1936
2588
|
let entries;
|
|
1937
2589
|
try {
|
|
1938
|
-
entries =
|
|
2590
|
+
entries = fs17.readdirSync(dir, { withFileTypes: true });
|
|
1939
2591
|
} catch {
|
|
1940
2592
|
return;
|
|
1941
2593
|
}
|
|
1942
2594
|
for (const entry of entries) {
|
|
1943
|
-
const fullPath =
|
|
2595
|
+
const fullPath = path16.join(dir, entry.name);
|
|
1944
2596
|
if (entry.isDirectory()) {
|
|
1945
2597
|
walkAndFix(fullPath);
|
|
1946
2598
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
1947
|
-
const content =
|
|
2599
|
+
const content = fs17.readFileSync(fullPath, "utf-8");
|
|
1948
2600
|
const lines = content.split("\n");
|
|
1949
2601
|
if (lines[0]?.trim() !== "---") continue;
|
|
1950
2602
|
let endIdx = -1;
|
|
@@ -1964,7 +2616,7 @@ function fixSteeringWhitespace(steeringDir) {
|
|
|
1964
2616
|
}
|
|
1965
2617
|
}
|
|
1966
2618
|
if (changed) {
|
|
1967
|
-
|
|
2619
|
+
fs17.writeFileSync(fullPath, lines.join("\n"), "utf-8");
|
|
1968
2620
|
}
|
|
1969
2621
|
}
|
|
1970
2622
|
}
|
|
@@ -1972,12 +2624,12 @@ function fixSteeringWhitespace(steeringDir) {
|
|
|
1972
2624
|
walkAndFix(steeringDir);
|
|
1973
2625
|
}
|
|
1974
2626
|
function checkMetadataJson(workspaceRoot) {
|
|
1975
|
-
const metaPath =
|
|
1976
|
-
if (!
|
|
2627
|
+
const metaPath = path16.join(workspaceRoot, ".kiro/metadata.json");
|
|
2628
|
+
if (!fs17.existsSync(metaPath)) {
|
|
1977
2629
|
return { name: "metadata-json", result: "WARN", message: ".kiro/metadata.json not found (optional)" };
|
|
1978
2630
|
}
|
|
1979
2631
|
try {
|
|
1980
|
-
const content =
|
|
2632
|
+
const content = fs17.readFileSync(metaPath, "utf-8");
|
|
1981
2633
|
const parsed = JSON.parse(content);
|
|
1982
2634
|
if (!parsed.version || !parsed.name) {
|
|
1983
2635
|
return { name: "metadata-json", result: "FAIL", message: ".kiro/metadata.json missing required fields (version, name)" };
|
|
@@ -1991,12 +2643,12 @@ function checkStatuslineExecBit(workspaceRoot) {
|
|
|
1991
2643
|
if (process11.platform === "win32") {
|
|
1992
2644
|
return { name: "statusline-exec", result: "PASS", message: "Statusline exec bit check (skipped on Windows)" };
|
|
1993
2645
|
}
|
|
1994
|
-
const shPath =
|
|
1995
|
-
if (!
|
|
2646
|
+
const shPath = path16.join(workspaceRoot, ".kiro/statusline.sh");
|
|
2647
|
+
if (!fs17.existsSync(shPath)) {
|
|
1996
2648
|
return { name: "statusline-exec", result: "PASS", message: "No statusline.sh to check" };
|
|
1997
2649
|
}
|
|
1998
2650
|
try {
|
|
1999
|
-
const stat =
|
|
2651
|
+
const stat = fs17.statSync(shPath);
|
|
2000
2652
|
const isExecutable = (stat.mode & 73) !== 0;
|
|
2001
2653
|
if (isExecutable) {
|
|
2002
2654
|
return { name: "statusline-exec", result: "PASS", message: "statusline.sh is executable" };
|
|
@@ -2007,7 +2659,7 @@ function checkStatuslineExecBit(workspaceRoot) {
|
|
|
2007
2659
|
message: "statusline.sh is not executable",
|
|
2008
2660
|
fixable: true,
|
|
2009
2661
|
fixAction: () => {
|
|
2010
|
-
|
|
2662
|
+
fs17.chmodSync(shPath, 493);
|
|
2011
2663
|
}
|
|
2012
2664
|
};
|
|
2013
2665
|
} catch {
|
|
@@ -2016,24 +2668,24 @@ function checkStatuslineExecBit(workspaceRoot) {
|
|
|
2016
2668
|
}
|
|
2017
2669
|
|
|
2018
2670
|
// src/commands/telemetry.ts
|
|
2019
|
-
import
|
|
2020
|
-
import
|
|
2671
|
+
import fs18 from "fs";
|
|
2672
|
+
import path17 from "path";
|
|
2021
2673
|
import os2 from "os";
|
|
2022
2674
|
import process12 from "process";
|
|
2023
|
-
var CONFIG_DIR =
|
|
2024
|
-
var CONFIG_FILE =
|
|
2675
|
+
var CONFIG_DIR = path17.join(os2.homedir(), ".kiro-kit");
|
|
2676
|
+
var CONFIG_FILE = path17.join(CONFIG_DIR, "config.json");
|
|
2025
2677
|
function readConfig() {
|
|
2026
2678
|
try {
|
|
2027
|
-
if (
|
|
2028
|
-
return JSON.parse(
|
|
2679
|
+
if (fs18.existsSync(CONFIG_FILE)) {
|
|
2680
|
+
return JSON.parse(fs18.readFileSync(CONFIG_FILE, "utf-8"));
|
|
2029
2681
|
}
|
|
2030
2682
|
} catch {
|
|
2031
2683
|
}
|
|
2032
2684
|
return { telemetry: false };
|
|
2033
2685
|
}
|
|
2034
2686
|
function writeConfig(config) {
|
|
2035
|
-
|
|
2036
|
-
|
|
2687
|
+
fs18.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
2688
|
+
fs18.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
2037
2689
|
}
|
|
2038
2690
|
function registerTelemetryCommand(program2) {
|
|
2039
2691
|
const telemetry = program2.command("telemetry").description("Manage anonymous usage telemetry");
|