@codyswann/lisa 4.81.2 → 4.81.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/lisa.d.ts.map +1 -1
- package/dist/core/lisa.js +3 -0
- package/dist/core/lisa.js.map +1 -1
- package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +47 -8
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/strategies/copy-contents.d.ts.map +1 -1
- package/dist/strategies/copy-contents.js +3 -1
- package/dist/strategies/copy-contents.js.map +1 -1
- package/dist/strategies/copy-overwrite.d.ts.map +1 -1
- package/dist/strategies/copy-overwrite.js +3 -0
- package/dist/strategies/copy-overwrite.js.map +1 -1
- package/dist/strategies/create-only.d.ts.map +1 -1
- package/dist/strategies/create-only.js +8 -0
- package/dist/strategies/create-only.js.map +1 -1
- package/dist/strategies/merge.d.ts.map +1 -1
- package/dist/strategies/merge.js +2 -0
- package/dist/strategies/merge.js.map +1 -1
- package/dist/strategies/package-lisa.d.ts.map +1 -1
- package/dist/strategies/package-lisa.js +2 -0
- package/dist/strategies/package-lisa.js.map +1 -1
- package/dist/strategies/strategy.interface.d.ts +12 -1
- package/dist/strategies/strategy.interface.d.ts.map +1 -1
- package/dist/strategies/tagged-merge.d.ts.map +1 -1
- package/dist/strategies/tagged-merge.js +2 -0
- package/dist/strategies/tagged-merge.js.map +1 -1
- package/dist/transaction/backup.d.ts +27 -1
- package/dist/transaction/backup.d.ts.map +1 -1
- package/dist/transaction/backup.js +65 -3
- package/dist/transaction/backup.js.map +1 -1
- package/dist/transaction/created-paths.d.ts +72 -0
- package/dist/transaction/created-paths.d.ts.map +1 -0
- package/dist/transaction/created-paths.js +176 -0
- package/dist/transaction/created-paths.js.map +1 -0
- package/package.json +4 -4
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/SKILL.md +2 -2
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/cli-manager.mjs +5 -76
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/cli-setup.mjs +165 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/cli-usage.mjs +32 -8
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/clipboard.mjs +119 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/config.mjs +18 -36
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/launch.mjs +1 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/repo-detect.mjs +205 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/session-manager.mjs +16 -13
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/setup-checks.mjs +168 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/token.mjs +98 -28
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-session-manager/SKILL.md +44 -21
- package/plugins/lisa/skills/lisa-session-manager/SKILL.md +2 -2
- package/plugins/lisa/skills/lisa-session-manager/scripts/cli-manager.mjs +5 -76
- package/plugins/lisa/skills/lisa-session-manager/scripts/cli-setup.mjs +165 -0
- package/plugins/lisa/skills/lisa-session-manager/scripts/cli-usage.mjs +32 -8
- package/plugins/lisa/skills/lisa-session-manager/scripts/clipboard.mjs +119 -0
- package/plugins/lisa/skills/lisa-session-manager/scripts/config.mjs +18 -36
- package/plugins/lisa/skills/lisa-session-manager/scripts/launch.mjs +1 -0
- package/plugins/lisa/skills/lisa-session-manager/scripts/repo-detect.mjs +205 -0
- package/plugins/lisa/skills/lisa-session-manager/scripts/session-manager.mjs +16 -13
- package/plugins/lisa/skills/lisa-session-manager/scripts/setup-checks.mjs +168 -0
- package/plugins/lisa/skills/lisa-session-manager/scripts/token.mjs +98 -28
- package/plugins/lisa/skills/lisa-setup-session-manager/SKILL.md +44 -21
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-session-manager/SKILL.md +2 -2
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/cli-manager.mjs +5 -76
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/cli-setup.mjs +165 -0
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/cli-usage.mjs +32 -8
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/clipboard.mjs +119 -0
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/config.mjs +18 -36
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/launch.mjs +1 -0
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/repo-detect.mjs +205 -0
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/session-manager.mjs +16 -13
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/setup-checks.mjs +168 -0
- package/plugins/lisa-agy/skills/lisa-session-manager/scripts/token.mjs +98 -28
- package/plugins/lisa-agy/skills/lisa-setup-session-manager/SKILL.md +44 -21
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-devin/.devin-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/skills/lisa-session-manager/SKILL.md +2 -2
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/cli-manager.mjs +5 -76
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/cli-setup.mjs +165 -0
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/cli-usage.mjs +32 -8
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/clipboard.mjs +119 -0
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/config.mjs +18 -36
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/launch.mjs +1 -0
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/repo-detect.mjs +205 -0
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/session-manager.mjs +16 -13
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/setup-checks.mjs +168 -0
- package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/token.mjs +98 -28
- package/plugins/lisa-copilot/skills/lisa-setup-session-manager/SKILL.md +44 -21
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/skills/lisa-session-manager/SKILL.md +2 -2
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/cli-manager.mjs +5 -76
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/cli-setup.mjs +165 -0
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/cli-usage.mjs +32 -8
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/clipboard.mjs +119 -0
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/config.mjs +18 -36
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/launch.mjs +1 -0
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/repo-detect.mjs +205 -0
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/session-manager.mjs +16 -13
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/setup-checks.mjs +168 -0
- package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/token.mjs +98 -28
- package/plugins/lisa-cursor/skills/lisa-setup-session-manager/SKILL.md +44 -21
- package/plugins/lisa-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-devin/.devin-plugin/plugin.json +1 -1
- package/plugins/lisa-devin/skills/lisa-session-manager/SKILL.md +2 -2
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/cli-manager.mjs +5 -76
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/cli-setup.mjs +165 -0
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/cli-usage.mjs +32 -8
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/clipboard.mjs +119 -0
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/config.mjs +18 -36
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/launch.mjs +1 -0
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/repo-detect.mjs +205 -0
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/session-manager.mjs +16 -13
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/setup-checks.mjs +168 -0
- package/plugins/lisa-devin/skills/lisa-session-manager/scripts/token.mjs +98 -28
- package/plugins/lisa-devin/skills/lisa-setup-session-manager/SKILL.md +44 -21
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-devin/.devin-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-devin/.devin-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-devin/.devin-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-devin/.devin-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-devin/.devin-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-devin/.devin-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-devin/.devin-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-devin/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-devin/.devin-plugin/plugin.json +1 -1
- package/plugins/src/base/skills/lisa-session-manager/SKILL.md +2 -2
- package/plugins/src/base/skills/lisa-session-manager/scripts/cli-manager.mjs +5 -76
- package/plugins/src/base/skills/lisa-session-manager/scripts/cli-setup.mjs +165 -0
- package/plugins/src/base/skills/lisa-session-manager/scripts/cli-usage.mjs +32 -8
- package/plugins/src/base/skills/lisa-session-manager/scripts/clipboard.mjs +119 -0
- package/plugins/src/base/skills/lisa-session-manager/scripts/config.mjs +18 -36
- package/plugins/src/base/skills/lisa-session-manager/scripts/launch.mjs +1 -0
- package/plugins/src/base/skills/lisa-session-manager/scripts/repo-detect.mjs +205 -0
- package/plugins/src/base/skills/lisa-session-manager/scripts/session-manager.mjs +16 -13
- package/plugins/src/base/skills/lisa-session-manager/scripts/setup-checks.mjs +168 -0
- package/plugins/src/base/skills/lisa-session-manager/scripts/token.mjs +98 -28
- package/plugins/src/base/skills/lisa-setup-session-manager/SKILL.md +44 -21
|
@@ -4,6 +4,7 @@ import * as path from "node:path";
|
|
|
4
4
|
import * as os from "node:os";
|
|
5
5
|
import { BackupError, RollbackError } from "../errors/index.js";
|
|
6
6
|
import { generateBackupDirname } from "../utils/file-operations.js";
|
|
7
|
+
import { CreatedPathLedger, occupied } from "./created-paths.js";
|
|
7
8
|
/**
|
|
8
9
|
* Service for backing up and restoring files during operations
|
|
9
10
|
*/
|
|
@@ -11,6 +12,11 @@ export class BackupService {
|
|
|
11
12
|
backupDir = null;
|
|
12
13
|
destDir = "";
|
|
13
14
|
persistentBackupDirname = null;
|
|
15
|
+
/** Files whose pre-apply bytes are already saved; the first copy wins. */
|
|
16
|
+
backedUp = new Set();
|
|
17
|
+
/** Files already copied into this apply's `.lisabak/<timestamp>/`. */
|
|
18
|
+
persisted = new Set();
|
|
19
|
+
created = new CreatedPathLedger("");
|
|
14
20
|
logger;
|
|
15
21
|
/**
|
|
16
22
|
* Initialize backup service with logger
|
|
@@ -21,6 +27,11 @@ export class BackupService {
|
|
|
21
27
|
}
|
|
22
28
|
async init(destDir) {
|
|
23
29
|
this.destDir = destDir;
|
|
30
|
+
this.created = new CreatedPathLedger(destDir);
|
|
31
|
+
// A reused service must not write into an earlier apply's .lisabak dir.
|
|
32
|
+
this.persistentBackupDirname = null;
|
|
33
|
+
this.backedUp.clear();
|
|
34
|
+
this.persisted.clear();
|
|
24
35
|
this.backupDir = await mkdtemp(path.join(os.tmpdir(), "lisa-backup-"));
|
|
25
36
|
this.logger.info(`Backup directory: ${this.backupDir}`);
|
|
26
37
|
}
|
|
@@ -28,32 +39,60 @@ export class BackupService {
|
|
|
28
39
|
if (!this.backupDir) {
|
|
29
40
|
throw new BackupError("backup", "Backup service not initialized");
|
|
30
41
|
}
|
|
42
|
+
// A second write to the same path must not replace the pre-apply copy
|
|
43
|
+
// with this apply's own intermediate bytes, and a file this apply created
|
|
44
|
+
// has no pre-apply bytes at all: rollback removes it instead.
|
|
45
|
+
if (this.created.has(absolutePath) || this.backedUp.has(absolutePath)) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
31
48
|
try {
|
|
32
|
-
|
|
49
|
+
// lstat, not pathExists: a dangling symlink the host already had must
|
|
50
|
+
// be backed up (as a link), never recorded as created and unlinked.
|
|
51
|
+
if (await occupied(absolutePath)) {
|
|
33
52
|
const relativePath = path.relative(this.destDir, absolutePath);
|
|
34
53
|
const backupPath = path.join(this.backupDir, relativePath);
|
|
35
54
|
await fse.ensureDir(path.dirname(backupPath));
|
|
36
55
|
await fse.copy(absolutePath, backupPath);
|
|
56
|
+
this.backedUp.add(absolutePath);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
await this.created.record(absolutePath);
|
|
37
60
|
}
|
|
38
61
|
}
|
|
39
62
|
catch (error) {
|
|
40
63
|
throw new BackupError("backup", `Failed to backup ${absolutePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
41
64
|
}
|
|
42
65
|
}
|
|
66
|
+
forgetCreated(absolutePath) {
|
|
67
|
+
this.created.forget(absolutePath);
|
|
68
|
+
}
|
|
43
69
|
async persistentBackup(absolutePath) {
|
|
70
|
+
// Same first-copy-wins rule as backup(): the .lisabak copy must hold the
|
|
71
|
+
// pre-apply bytes, not this apply's intermediate ones.
|
|
72
|
+
if (this.created.has(absolutePath) || this.persisted.has(absolutePath)) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
44
75
|
try {
|
|
45
76
|
if (await fse.pathExists(absolutePath)) {
|
|
46
77
|
// Generate backup directory name on first call
|
|
47
78
|
if (!this.persistentBackupDirname) {
|
|
48
|
-
this.persistentBackupDirname =
|
|
79
|
+
this.persistentBackupDirname = await this.unusedBackupDirname();
|
|
49
80
|
}
|
|
50
81
|
// Get relative path from destination directory
|
|
51
82
|
const relativePath = path.relative(this.destDir, absolutePath);
|
|
52
83
|
// Create backup in .lisabak/{timestamp}/{relative-path}
|
|
53
84
|
const backupBaseDir = path.join(this.destDir, ".lisabak", this.persistentBackupDirname);
|
|
54
85
|
const backupPath = path.join(backupBaseDir, relativePath);
|
|
86
|
+
// The persistent copy is itself a file this apply creates: rollback
|
|
87
|
+
// restores the original in place, so the copy would be pure debris.
|
|
88
|
+
await this.created.record(backupPath);
|
|
55
89
|
await fse.ensureDir(path.dirname(backupPath));
|
|
56
|
-
|
|
90
|
+
// Never replace an earlier backup's bytes, whoever wrote them.
|
|
91
|
+
await fse.copy(absolutePath, backupPath, {
|
|
92
|
+
overwrite: false,
|
|
93
|
+
errorOnExist: false,
|
|
94
|
+
});
|
|
95
|
+
this.persisted.add(absolutePath);
|
|
57
96
|
this.logger.info(`Backup created: .lisabak/${this.persistentBackupDirname}/${relativePath}`);
|
|
58
97
|
}
|
|
59
98
|
}
|
|
@@ -61,6 +100,20 @@ export class BackupService {
|
|
|
61
100
|
throw new BackupError("backup", `Failed to create persistent backup of ${absolutePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
62
101
|
}
|
|
63
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* A `.lisabak/` directory name no earlier apply has used. The timestamp has
|
|
105
|
+
* one-second precision, so two applies in the same second would otherwise
|
|
106
|
+
* share a directory; a numeric suffix keeps them apart.
|
|
107
|
+
* @param base - Timestamp name to start from
|
|
108
|
+
* @param attempt - Suffix counter (0 = no suffix)
|
|
109
|
+
* @returns An unoccupied directory name under `.lisabak/`
|
|
110
|
+
*/
|
|
111
|
+
async unusedBackupDirname(base = generateBackupDirname(), attempt = 0) {
|
|
112
|
+
const candidate = attempt === 0 ? base : `${base}-${attempt}`;
|
|
113
|
+
return (await occupied(path.join(this.destDir, ".lisabak", candidate)))
|
|
114
|
+
? this.unusedBackupDirname(base, attempt + 1)
|
|
115
|
+
: candidate;
|
|
116
|
+
}
|
|
64
117
|
async rollback() {
|
|
65
118
|
if (!this.backupDir || !(await fse.pathExists(this.backupDir))) {
|
|
66
119
|
throw new RollbackError("No backup directory available");
|
|
@@ -75,6 +128,9 @@ export class BackupService {
|
|
|
75
128
|
await fse.copy(backupFile, destFile);
|
|
76
129
|
this.logger.info(`Restored: ${relativePath}`);
|
|
77
130
|
}
|
|
131
|
+
for (const relativePath of await this.created.removeAll()) {
|
|
132
|
+
this.logger.info(`Removed (created by this apply): ${relativePath}`);
|
|
133
|
+
}
|
|
78
134
|
await this.cleanup();
|
|
79
135
|
this.logger.success("Rollback complete");
|
|
80
136
|
}
|
|
@@ -83,6 +139,9 @@ export class BackupService {
|
|
|
83
139
|
}
|
|
84
140
|
}
|
|
85
141
|
async cleanup() {
|
|
142
|
+
this.created.clear();
|
|
143
|
+
this.backedUp.clear();
|
|
144
|
+
this.persisted.clear();
|
|
86
145
|
if (this.backupDir && (await fse.pathExists(this.backupDir))) {
|
|
87
146
|
await fse.remove(this.backupDir);
|
|
88
147
|
this.backupDir = null;
|
|
@@ -119,6 +178,9 @@ export class DryRunBackupService {
|
|
|
119
178
|
async backup(_absolutePath) {
|
|
120
179
|
// No-op
|
|
121
180
|
}
|
|
181
|
+
forgetCreated(_absolutePath) {
|
|
182
|
+
// No-op
|
|
183
|
+
}
|
|
122
184
|
async persistentBackup(_absolutePath) {
|
|
123
185
|
// No-op
|
|
124
186
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"backup.js","sourceRoot":"","sources":["../../src/transaction/backup.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAChC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEhE,OAAO,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"backup.js","sourceRoot":"","sources":["../../src/transaction/backup.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether anything (file, directory, or dangling symlink) occupies a path.
|
|
3
|
+
* @param absolutePath - Path to probe
|
|
4
|
+
* @returns True when the path is occupied
|
|
5
|
+
*/
|
|
6
|
+
export declare function occupied(absolutePath: string): Promise<boolean>;
|
|
7
|
+
/**
|
|
8
|
+
* The paths one apply created, recorded explicitly before it wrote them, so a
|
|
9
|
+
* rollback can undo creation as well as modification (CodySwannGT/lisa#4459).
|
|
10
|
+
*
|
|
11
|
+
* Only recorded paths are ever candidates for removal. Nothing is inferred
|
|
12
|
+
* from the tree, so a file that existed before the apply, or that someone
|
|
13
|
+
* else writes while it runs, is never touched — including one written into a
|
|
14
|
+
* directory the apply created: directories are removed only when empty.
|
|
15
|
+
*
|
|
16
|
+
* Accepted residual: if another process creates one of the recorded missing
|
|
17
|
+
* directories mid-apply and leaves it EMPTY, rollback removes that empty
|
|
18
|
+
* directory. A non-empty one is always kept, so no content can be lost.
|
|
19
|
+
*/
|
|
20
|
+
export declare class CreatedPathLedger {
|
|
21
|
+
private readonly root;
|
|
22
|
+
private readonly files;
|
|
23
|
+
private readonly dirs;
|
|
24
|
+
/**
|
|
25
|
+
* Create a ledger confined to one destination tree.
|
|
26
|
+
* @param root - Destination directory; nothing outside it is ever recorded
|
|
27
|
+
*/
|
|
28
|
+
constructor(root: string);
|
|
29
|
+
/**
|
|
30
|
+
* Record a path about to be created, plus every missing ancestor directory
|
|
31
|
+
* below the root. Must be called before the write (and before any
|
|
32
|
+
* `ensureDir`), or the ancestors it creates are indistinguishable from
|
|
33
|
+
* pre-existing ones.
|
|
34
|
+
* @param absolutePath - File the caller is about to create
|
|
35
|
+
*/
|
|
36
|
+
record(absolutePath: string): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Walk up from `dir` collecting missing directories below the root.
|
|
39
|
+
* @param dir - Directory to probe
|
|
40
|
+
* @param missing - Missing directories found so far, deepest first
|
|
41
|
+
* @returns The missing directories and the nearest existing ancestor
|
|
42
|
+
*/
|
|
43
|
+
private missingAncestors;
|
|
44
|
+
/**
|
|
45
|
+
* Whether an existing path, with every symlink resolved, is the root or
|
|
46
|
+
* lies inside it.
|
|
47
|
+
* @param existing - Existing ancestor of a path about to be created
|
|
48
|
+
* @returns True when confined to the root
|
|
49
|
+
*/
|
|
50
|
+
private resolvesInsideRoot;
|
|
51
|
+
/**
|
|
52
|
+
* Whether this apply recorded the path as one it created.
|
|
53
|
+
* @param absolutePath - Path to test
|
|
54
|
+
* @returns True when recorded
|
|
55
|
+
*/
|
|
56
|
+
has(absolutePath: string): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Withdraw a file record whose write did not happen. Directory records
|
|
59
|
+
* stay: they are removed only when empty, which is always safe.
|
|
60
|
+
* @param absolutePath - Path to withdraw
|
|
61
|
+
*/
|
|
62
|
+
forget(absolutePath: string): void;
|
|
63
|
+
/**
|
|
64
|
+
* Remove every recorded file that is still a non-directory, then every
|
|
65
|
+
* recorded directory that is now empty, deepest first.
|
|
66
|
+
* @returns Root-relative paths of the files removed
|
|
67
|
+
*/
|
|
68
|
+
removeAll(): Promise<readonly string[]>;
|
|
69
|
+
/** Forget everything (the apply succeeded; its files are now the host's). */
|
|
70
|
+
clear(): void;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=created-paths.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"created-paths.d.ts","sourceRoot":"","sources":["../../src/transaction/created-paths.ts"],"names":[],"mappings":"AAsBA;;;;GAIG;AACH,wBAAsB,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAarE;AA0BD;;;;;;;;;;;;GAYG;AACH,qBAAa,iBAAiB;IAQhB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAPjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqB;IAC3C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAqB;IAE1C;;;OAGG;gBAC0B,IAAI,EAAE,MAAM;IAEzC;;;;;;OAMG;IACG,MAAM,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAsBjD;;;;;OAKG;YACW,gBAAgB;IAU9B;;;;;OAKG;YACW,kBAAkB;IAUhC;;;;OAIG;IACH,GAAG,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO;IAIlC;;;;OAIG;IACH,MAAM,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;IAIlC;;;;OAIG;IACG,SAAS,IAAI,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC;IAiB7C,6EAA6E;IAC7E,KAAK,IAAI,IAAI;CAId"}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { lstat, realpath, rmdir, unlink } from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
/** Errors that mean a rollback removal has nothing (left) to do. */
|
|
4
|
+
const BENIGN_REMOVAL_CODES = new Set(["ENOENT", "ENOTEMPTY", "EEXIST"]);
|
|
5
|
+
/** lstat errors that prove a path is absent. */
|
|
6
|
+
const ABSENCE_CODES = new Set(["ENOENT", "ENOTDIR"]);
|
|
7
|
+
/**
|
|
8
|
+
* Whether `candidate` lies strictly inside `root`.
|
|
9
|
+
* @param root - Directory the apply operates on
|
|
10
|
+
* @param candidate - Absolute path to test
|
|
11
|
+
* @returns True when candidate is a descendant of root, never root itself
|
|
12
|
+
*/
|
|
13
|
+
function isStrictlyInside(root, candidate) {
|
|
14
|
+
const relative = path.relative(root, candidate);
|
|
15
|
+
return (relative !== "" && !relative.startsWith("..") && !path.isAbsolute(relative));
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Whether anything (file, directory, or dangling symlink) occupies a path.
|
|
19
|
+
* @param absolutePath - Path to probe
|
|
20
|
+
* @returns True when the path is occupied
|
|
21
|
+
*/
|
|
22
|
+
export async function occupied(absolutePath) {
|
|
23
|
+
try {
|
|
24
|
+
await lstat(absolutePath);
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
// Only proven absence is "missing". EACCES or ELOOP say nothing about
|
|
29
|
+
// whether the path exists, and reading them as absence would record a
|
|
30
|
+
// host path for deletion without saving it.
|
|
31
|
+
if (ABSENCE_CODES.has(error.code ?? "")) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Run a removal, treating "already gone" and "not empty" as success.
|
|
39
|
+
* @param remove - Removal to attempt
|
|
40
|
+
*/
|
|
41
|
+
async function removeIfPossible(remove) {
|
|
42
|
+
try {
|
|
43
|
+
await remove();
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
const code = error.code ?? "";
|
|
47
|
+
if (!BENIGN_REMOVAL_CODES.has(code)) {
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Number of path segments, used to order directory removal deepest first.
|
|
54
|
+
* @param dir - Absolute directory path
|
|
55
|
+
* @returns Segment count
|
|
56
|
+
*/
|
|
57
|
+
function depth(dir) {
|
|
58
|
+
return dir.split(path.sep).length;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The paths one apply created, recorded explicitly before it wrote them, so a
|
|
62
|
+
* rollback can undo creation as well as modification (CodySwannGT/lisa#4459).
|
|
63
|
+
*
|
|
64
|
+
* Only recorded paths are ever candidates for removal. Nothing is inferred
|
|
65
|
+
* from the tree, so a file that existed before the apply, or that someone
|
|
66
|
+
* else writes while it runs, is never touched — including one written into a
|
|
67
|
+
* directory the apply created: directories are removed only when empty.
|
|
68
|
+
*
|
|
69
|
+
* Accepted residual: if another process creates one of the recorded missing
|
|
70
|
+
* directories mid-apply and leaves it EMPTY, rollback removes that empty
|
|
71
|
+
* directory. A non-empty one is always kept, so no content can be lost.
|
|
72
|
+
*/
|
|
73
|
+
export class CreatedPathLedger {
|
|
74
|
+
root;
|
|
75
|
+
files = new Set();
|
|
76
|
+
dirs = new Set();
|
|
77
|
+
/**
|
|
78
|
+
* Create a ledger confined to one destination tree.
|
|
79
|
+
* @param root - Destination directory; nothing outside it is ever recorded
|
|
80
|
+
*/
|
|
81
|
+
constructor(root) {
|
|
82
|
+
this.root = root;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Record a path about to be created, plus every missing ancestor directory
|
|
86
|
+
* below the root. Must be called before the write (and before any
|
|
87
|
+
* `ensureDir`), or the ancestors it creates are indistinguishable from
|
|
88
|
+
* pre-existing ones.
|
|
89
|
+
* @param absolutePath - File the caller is about to create
|
|
90
|
+
*/
|
|
91
|
+
async record(absolutePath) {
|
|
92
|
+
// An occupied path is not one this apply is about to create, whoever
|
|
93
|
+
// put it there.
|
|
94
|
+
if (!isStrictlyInside(this.root, absolutePath) ||
|
|
95
|
+
(await occupied(absolutePath))) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const { missing, existing } = await this.missingAncestors(path.dirname(absolutePath));
|
|
99
|
+
// A lexical check is not confinement: a pre-existing symlinked ancestor
|
|
100
|
+
// can send the write outside the root, and recording it would let
|
|
101
|
+
// rollback unlink a file that lives elsewhere.
|
|
102
|
+
if (!(await this.resolvesInsideRoot(existing))) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
missing.forEach(dir => this.dirs.add(dir));
|
|
106
|
+
this.files.add(absolutePath);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Walk up from `dir` collecting missing directories below the root.
|
|
110
|
+
* @param dir - Directory to probe
|
|
111
|
+
* @param missing - Missing directories found so far, deepest first
|
|
112
|
+
* @returns The missing directories and the nearest existing ancestor
|
|
113
|
+
*/
|
|
114
|
+
async missingAncestors(dir, missing = []) {
|
|
115
|
+
if (!isStrictlyInside(this.root, dir) || (await occupied(dir))) {
|
|
116
|
+
return { missing, existing: dir };
|
|
117
|
+
}
|
|
118
|
+
return this.missingAncestors(path.dirname(dir), [...missing, dir]);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Whether an existing path, with every symlink resolved, is the root or
|
|
122
|
+
* lies inside it.
|
|
123
|
+
* @param existing - Existing ancestor of a path about to be created
|
|
124
|
+
* @returns True when confined to the root
|
|
125
|
+
*/
|
|
126
|
+
async resolvesInsideRoot(existing) {
|
|
127
|
+
const [resolvedRoot, resolved] = await Promise.all([
|
|
128
|
+
realpath(this.root),
|
|
129
|
+
realpath(existing),
|
|
130
|
+
]);
|
|
131
|
+
return (resolved === resolvedRoot || isStrictlyInside(resolvedRoot, resolved));
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Whether this apply recorded the path as one it created.
|
|
135
|
+
* @param absolutePath - Path to test
|
|
136
|
+
* @returns True when recorded
|
|
137
|
+
*/
|
|
138
|
+
has(absolutePath) {
|
|
139
|
+
return this.files.has(absolutePath);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Withdraw a file record whose write did not happen. Directory records
|
|
143
|
+
* stay: they are removed only when empty, which is always safe.
|
|
144
|
+
* @param absolutePath - Path to withdraw
|
|
145
|
+
*/
|
|
146
|
+
forget(absolutePath) {
|
|
147
|
+
this.files.delete(absolutePath);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Remove every recorded file that is still a non-directory, then every
|
|
151
|
+
* recorded directory that is now empty, deepest first.
|
|
152
|
+
* @returns Root-relative paths of the files removed
|
|
153
|
+
*/
|
|
154
|
+
async removeAll() {
|
|
155
|
+
const removed = [];
|
|
156
|
+
for (const file of this.files) {
|
|
157
|
+
const stat = await lstat(file).catch(() => null);
|
|
158
|
+
if (stat !== null && !stat.isDirectory()) {
|
|
159
|
+
await removeIfPossible(() => unlink(file));
|
|
160
|
+
removed.push(path.relative(this.root, file));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const deepestFirst = [...this.dirs].sort((a, b) => depth(b) - depth(a));
|
|
164
|
+
for (const dir of deepestFirst) {
|
|
165
|
+
await removeIfPossible(() => rmdir(dir));
|
|
166
|
+
}
|
|
167
|
+
this.clear();
|
|
168
|
+
return removed;
|
|
169
|
+
}
|
|
170
|
+
/** Forget everything (the apply succeeded; its files are now the host's). */
|
|
171
|
+
clear() {
|
|
172
|
+
this.files.clear();
|
|
173
|
+
this.dirs.clear();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=created-paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"created-paths.js","sourceRoot":"","sources":["../../src/transaction/created-paths.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -189,7 +189,7 @@
|
|
|
189
189
|
"qs": "^6.16.0"
|
|
190
190
|
},
|
|
191
191
|
"name": "@codyswann/lisa",
|
|
192
|
-
"version": "4.81.
|
|
192
|
+
"version": "4.81.4",
|
|
193
193
|
"description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
|
|
194
194
|
"main": "dist/index.js",
|
|
195
195
|
"exports": {
|
|
@@ -345,7 +345,7 @@
|
|
|
345
345
|
"test": "tests"
|
|
346
346
|
},
|
|
347
347
|
"types": "./dist/index.d.ts",
|
|
348
|
-
"lisaReleaseCommit": "
|
|
349
|
-
"gitHead": "
|
|
350
|
-
"lisaReleaseTag": "v4.81.
|
|
348
|
+
"lisaReleaseCommit": "e9f635e3e13ab6737422426cfe248257a081a4e5",
|
|
349
|
+
"gitHead": "e9f635e3e13ab6737422426cfe248257a081a4e5",
|
|
350
|
+
"lisaReleaseTag": "v4.81.4"
|
|
351
351
|
}
|
|
@@ -25,9 +25,9 @@ channels (Telegram) and cross-session messaging (`SendMessage`, `crossSessionInb
|
|
|
25
25
|
| Runtime | lisa-setup-session-manager | lisa-session-manager (manager) | `managedBy=` |
|
|
26
26
|
|---|---|---|---|
|
|
27
27
|
| Claude Code | supported | supported (reference). Needs `claude` ≥ 2.1.224, `tmux`, `bun` | supported |
|
|
28
|
-
| Codex | config, token
|
|
28
|
+
| Codex | config, token (prompt or `--from-clipboard`), runbook, `trust-status` and `coexistence` are supported. `trust-status` reads Claude Code's trust record, which is the one that matters (workers are Claude Code); accepting trust needs the `claude` CLI. `coexistence` sees only run logs, so also list Codex automations. `start` launches a Claude Code manager, so it works when the `claude` CLI is installed | the skill ships, but `cycle` refuses to run here (no `/loop`, channels or `SendMessage`); tell the operator to run `start` | rejected with the gap message |
|
|
29
29
|
| Cursor / Antigravity (agy) / Copilot / Devin | same as Codex | same as Codex | rejected |
|
|
30
|
-
| OpenCode |
|
|
30
|
+
| OpenCode | same setup subcommands as Codex, but script resolution relies on the npm rung: the CLI imports plugin-level scripts the skill-folder copy lacks, so every subcommand needs the npm package | gap | rejected |
|
|
31
31
|
|
|
32
32
|
On any runtime other than Claude Code, `cycle` stops with: "The session manager runs on Claude Code
|
|
33
33
|
only (background sessions, channels, cross-session messaging). Run `/lisa:session-manager start`
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Manager
|
|
3
|
-
*
|
|
2
|
+
* Manager subcommands of the session-manager CLI: start, stop, status,
|
|
3
|
+
* repo-dir and validate. The setup ones (init-config, store-token,
|
|
4
|
+
* trust-status, coexistence) live in cli-setup.mjs.
|
|
4
5
|
*
|
|
5
6
|
* `start` hosts the manager in tmux. It refuses (exit 2) unless the bot
|
|
6
7
|
* token file exists with owner-only permissions in the project's own state
|
|
@@ -11,15 +12,11 @@
|
|
|
11
12
|
* manager's tmux session: workers hold claimed work and keep running.
|
|
12
13
|
* @module session-manager/cli-manager
|
|
13
14
|
*/
|
|
14
|
-
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
15
|
-
import path from "node:path";
|
|
16
|
-
|
|
17
15
|
import {
|
|
18
16
|
CliError,
|
|
19
17
|
EXIT,
|
|
20
18
|
GLOBAL_CONFIG_FILE,
|
|
21
19
|
asSetupError,
|
|
22
|
-
asSetupErrorAsync,
|
|
23
20
|
intFlag,
|
|
24
21
|
listAgents,
|
|
25
22
|
loadProject,
|
|
@@ -28,12 +25,8 @@ import {
|
|
|
28
25
|
requireLisaRepo,
|
|
29
26
|
run,
|
|
30
27
|
} from "./cli-runtime.mjs";
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
defaultProjectSlug,
|
|
34
|
-
probeOwnSession,
|
|
35
|
-
sessionSurvives,
|
|
36
|
-
} from "./isolation.mjs";
|
|
28
|
+
import { resolveRepoDir } from "./config.mjs";
|
|
29
|
+
import { probeOwnSession, sessionSurvives } from "./isolation.mjs";
|
|
37
30
|
import {
|
|
38
31
|
assertTokenPresent,
|
|
39
32
|
buildForegroundCommand,
|
|
@@ -41,7 +34,6 @@ import {
|
|
|
41
34
|
buildManagerLaunch,
|
|
42
35
|
buildStopManager,
|
|
43
36
|
resolveStateDir,
|
|
44
|
-
storeToken as storeTokenFile,
|
|
45
37
|
} from "./launch.mjs";
|
|
46
38
|
import { readLedger } from "./ledger.mjs";
|
|
47
39
|
import { parseWorkerName, scrubWorkerEnv } from "./workers.mjs";
|
|
@@ -208,69 +200,6 @@ export function validate(ctx) {
|
|
|
208
200
|
};
|
|
209
201
|
}
|
|
210
202
|
|
|
211
|
-
/**
|
|
212
|
-
* `init-config [--slug <slug>] [--write]`: the default block for this
|
|
213
|
-
* project, written to `.lisa.config.json` only with `--write` and only when
|
|
214
|
-
* no block exists. Non-secret fields only; the token never goes here.
|
|
215
|
-
* @param {object} ctx - CLI context
|
|
216
|
-
* @returns {{ json: object }} `{written,block,...}`
|
|
217
|
-
*/
|
|
218
|
-
export function initConfig(ctx) {
|
|
219
|
-
const { global } = loadProject(ctx.root);
|
|
220
|
-
if (global?.sessionManager) {
|
|
221
|
-
return {
|
|
222
|
-
json: { written: false, reason: "exists", block: global.sessionManager },
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
const slug =
|
|
226
|
-
ctx.values.slug ?? defaultProjectSlug(ctx.root, global?.github?.repo);
|
|
227
|
-
const block = asSetupError(() =>
|
|
228
|
-
buildDefaultBlock({
|
|
229
|
-
projectSlug: slug,
|
|
230
|
-
detectedRepos: detectRepos(ctx.root),
|
|
231
|
-
})
|
|
232
|
-
);
|
|
233
|
-
if (!ctx.values.write) return { json: { written: false, block } };
|
|
234
|
-
|
|
235
|
-
const file = path.join(ctx.root, GLOBAL_CONFIG_FILE);
|
|
236
|
-
if (!existsSync(file)) {
|
|
237
|
-
throw new CliError(
|
|
238
|
-
`${file} does not exist; set up Lisa in this project first.`,
|
|
239
|
-
EXIT.SETUP
|
|
240
|
-
);
|
|
241
|
-
}
|
|
242
|
-
const current = JSON.parse(readFileSync(file, "utf8"));
|
|
243
|
-
writeFileSync(
|
|
244
|
-
file,
|
|
245
|
-
`${JSON.stringify({ ...current, sessionManager: block }, null, 2)}\n`
|
|
246
|
-
);
|
|
247
|
-
return { json: { written: true, path: file, block } };
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* `store-token [--replace]`: prompt for the bot token on the operator's
|
|
252
|
-
* terminal and write `<stateDir>/.env`. An existing token is kept (exit 2)
|
|
253
|
-
* unless `--replace` is given. With no TTY it prints the command the operator
|
|
254
|
-
* must run themselves and exits 0 with `stored:false`.
|
|
255
|
-
* @param {object} ctx - CLI context
|
|
256
|
-
* @returns {Promise<{ json: object, exitCode: number }>} Outcome
|
|
257
|
-
*/
|
|
258
|
-
export async function storeToken(ctx) {
|
|
259
|
-
const config = requireConfig(ctx);
|
|
260
|
-
const result = await asSetupErrorAsync(() =>
|
|
261
|
-
storeTokenFile({
|
|
262
|
-
stateDir: config.notify?.stateDir,
|
|
263
|
-
scriptsDir: ctx.scriptsDir,
|
|
264
|
-
projectRoot: ctx.root,
|
|
265
|
-
replace: ctx.values.replace === true,
|
|
266
|
-
env: ctx.env,
|
|
267
|
-
homedir: ctx.homedir,
|
|
268
|
-
})
|
|
269
|
-
);
|
|
270
|
-
const refused = result.stored === false && result.reason !== "no-tty";
|
|
271
|
-
return { json: result, exitCode: refused ? EXIT.SETUP : EXIT.OK };
|
|
272
|
-
}
|
|
273
|
-
|
|
274
203
|
/**
|
|
275
204
|
* `status`: is the manager's tmux session up, and what does the ledger say
|
|
276
205
|
* about each worker it dispatched.
|