runshift 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts CHANGED
@@ -1,20 +1,27 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { init } from "./commands/init.js";
4
+ import { remove } from "./commands/remove.js";
4
5
 
5
6
  const args = process.argv.slice(2);
6
7
  const command = args[0] ?? "init";
7
8
 
8
9
  switch (command) {
9
10
  case "init":
10
- init().catch((err) => {
11
+ init(args.slice(1)).catch((err) => {
12
+ console.error(err.message ?? err);
13
+ process.exit(1);
14
+ });
15
+ break;
16
+ case "remove":
17
+ remove().catch((err) => {
11
18
  console.error(err.message ?? err);
12
19
  process.exit(1);
13
20
  });
14
21
  break;
15
22
  case "--version":
16
23
  case "-v":
17
- console.log("runshift 0.0.2");
24
+ console.log("runshift 0.0.3");
18
25
  break;
19
26
  case "--help":
20
27
  case "-h":
@@ -22,11 +29,16 @@ switch (command) {
22
29
  runshift — the control plane for agents, wherever they run.
23
30
 
24
31
  Usage:
25
- npx runshift init Read your repo, generate governance rules
32
+ npx runshift init [options] Read your repo, generate governance rules
33
+ npx runshift remove Revert the runshift install commit
26
34
 
27
35
  Options:
28
- --version, -v Show version
29
- --help, -h Show this help
36
+ --version, -v Show version
37
+ --help, -h Show this help
38
+
39
+ Init options:
40
+ --dry-run Preview changes without writing files
41
+ --branch <name> Run on a new branch (default: relay-init)
30
42
  `);
31
43
  break;
32
44
  default:
package/src/types.ts CHANGED
@@ -17,6 +17,7 @@ export interface RepoContext {
17
17
  migrationCount: number;
18
18
  migrationNames: string[];
19
19
  rootConfigs: string[];
20
+ protectedPaths: string[];
20
21
  gitState: {
21
22
  branch: string;
22
23
  isDirty: boolean;
@@ -41,4 +42,5 @@ export interface InitResponse {
41
42
  files: GeneratedFile[];
42
43
  findings: Findings;
43
44
  summary: string;
45
+ previewId?: string;
44
46
  }
package/src/ui/display.ts CHANGED
@@ -13,8 +13,9 @@ export function showBanner(): void {
13
13
  horizontalLayout: "default",
14
14
  });
15
15
  console.log(amber(banner));
16
- console.log(muted(" v0.0.2"));
17
- console.log(muted(" the control plane for agents, wherever they run.\n"));
16
+ console.log(muted(" v0.0.3"));
17
+ console.log(muted(" the control plane for agents, wherever they run."));
18
+ console.log(dim(" usage: npx runshift init [--dry-run] [--branch <name>]\n"));
18
19
  console.log(divider + "\n");
19
20
  }
20
21
 
@@ -62,13 +63,17 @@ export function showScanResults(context: RepoContext): void {
62
63
  }
63
64
 
64
65
  const existingRuleKeys = Object.keys(context.existingRules);
66
+ const protectedSet = new Set(context.protectedPaths);
65
67
  const cursorRules = existingRuleKeys.filter((k) => k.startsWith(".cursor/rules/"));
66
68
  if (cursorRules.length > 0) {
67
- detections.push(`.cursor/rules/ ${cursorRules.length} existing file${cursorRules.length === 1 ? "" : "s"} detected`);
69
+ const protectedCount = cursorRules.filter((k) => protectedSet.has(k)).length;
70
+ const suffix = protectedCount > 0 ? ` (${protectedCount} protected)` : "";
71
+ detections.push(`.cursor/rules/ — ${cursorRules.length} existing file${cursorRules.length === 1 ? "" : "s"} detected${suffix}`);
68
72
  }
69
73
 
70
74
  if (existingRuleKeys.includes("CLAUDE.md")) {
71
- detections.push("existing CLAUDE.md detected");
75
+ const claudeProtected = protectedSet.has("CLAUDE.md") ? " (protected — human-written)" : "";
76
+ detections.push(`existing CLAUDE.md detected${claudeProtected}`);
72
77
  }
73
78
 
74
79
  if (context.tsconfig) {
@@ -120,6 +125,19 @@ export function showFileList(files: GeneratedFile[]): void {
120
125
  console.log(dim("\n + = create, ~ = update existing file\n"));
121
126
  }
122
127
 
128
+ export function showSelectedFiles(allFiles: GeneratedFile[], selectedPaths: string[]): void {
129
+ const selected = new Set(selectedPaths);
130
+ console.log(amber(" relay understood:\n"));
131
+ for (const file of allFiles) {
132
+ if (selected.has(file.path)) {
133
+ console.log(dim(" ✓ ") + file.path + dim(" (included)"));
134
+ } else {
135
+ console.log(dim(" ✗ ") + muted(file.path) + dim(" (excluded)"));
136
+ }
137
+ }
138
+ console.log();
139
+ }
140
+
123
141
  export function showWriting(filePath: string): void {
124
142
  console.log(dim(" ✓ ") + filePath);
125
143
  }
@@ -128,6 +146,16 @@ export function showCommit(): void {
128
146
  console.log(dim(" ✓ ") + "committed");
129
147
  }
130
148
 
149
+ export function showProtectedFiles(protectedPaths: string[]): void {
150
+ if (protectedPaths.length === 0) return;
151
+ console.log();
152
+ console.log(amber(" relay preserved your existing files:"));
153
+ for (const p of protectedPaths) {
154
+ console.log(dim(` ~ ${p} — human-written, not overwritten`));
155
+ }
156
+ console.log(dim(" run npx runshift update to review suggested changes.\n"));
157
+ }
158
+
131
159
  export function showSummary(summary: string): void {
132
160
  console.log(muted(` ${summary.replace(/\n/g, "\n ")}\n`));
133
161
  }
@@ -142,6 +170,33 @@ export function showSuccess(): void {
142
170
  console.log("\n" + divider + "\n");
143
171
  }
144
172
 
173
+ export function showDataPolicy(): void {
174
+ console.log(amber(" relay will send to runshift.ai:\n"));
175
+ console.log(dim(" ✓ package.json (dependencies and scripts only)"));
176
+ console.log(dim(" ✓ directory structure (top 2 levels, folder names only)"));
177
+ console.log(dim(" ✓ .env.example (key names only — values are never read)"));
178
+ console.log(dim(" ✓ existing CLAUDE.md (if present)"));
179
+ console.log(dim(" ✓ existing .cursor/rules/ (if present)"));
180
+ console.log(dim(" ✓ migration file names (no file contents)"));
181
+ console.log(dim("\n no source code is sent."));
182
+ console.log(dim(" no secret values are ever read.\n"));
183
+ }
184
+
185
+ export function showDryRunComplete(): void {
186
+ console.log("\n" + divider + "\n");
187
+ console.log(amber(" dry run complete — no files written."));
188
+ console.log(dim(" run npx runshift init to install.\n"));
189
+ console.log(divider + "\n");
190
+ }
191
+
192
+ export function showCancelled(): void {
193
+ console.log();
194
+ console.log(amber(" no changes made."));
195
+ console.log(dim(" run npx runshift init when you're ready."));
196
+ console.log();
197
+ console.log(divider + "\n");
198
+ }
199
+
145
200
  export function showError(type: "network" | "rate-limit" | "validation" | "server", message?: string): void {
146
201
  console.log();
147
202
  switch (type) {
package/src/ui/prompt.ts CHANGED
@@ -31,3 +31,45 @@ export async function promptChoice(question: string): Promise<"y" | "a" | "n"> {
31
31
  export async function promptFilePath(question: string): Promise<string> {
32
32
  return ask(question);
33
33
  }
34
+
35
+ export async function promptFileSelection(question: string): Promise<"a" | "s" | "n"> {
36
+ const answer = await ask(question);
37
+ const normalized = answer.toLowerCase();
38
+ if (normalized === "a" || normalized === "accept") return "a";
39
+ if (normalized === "s" || normalized === "select") return "s";
40
+ return "n";
41
+ }
42
+
43
+ export function promptPreview(message: string): Promise<boolean> {
44
+ return new Promise((resolve) => {
45
+ process.stdout.write(message);
46
+
47
+ const wasRaw = process.stdin.isRaw;
48
+ process.stdin.setRawMode(true);
49
+ process.stdin.resume();
50
+
51
+ const onData = (key: Buffer) => {
52
+ process.stdin.setRawMode(wasRaw ?? false);
53
+ process.stdin.pause();
54
+ process.stdin.removeListener("data", onData);
55
+
56
+ const ch = key.toString();
57
+
58
+ // ctrl-c
59
+ if (ch === "\x03") {
60
+ console.log();
61
+ process.exit(0);
62
+ }
63
+
64
+ if (ch.toLowerCase() === "o") {
65
+ console.log("o\n");
66
+ resolve(true);
67
+ } else {
68
+ console.log("\n");
69
+ resolve(false);
70
+ }
71
+ };
72
+
73
+ process.stdin.on("data", onData);
74
+ });
75
+ }