@neocompose/cli 0.26.5 → 0.27.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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.27.0] - 2026-08-10
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `neo logout [--api <url>]`: targeted local credential deletion for exactly
|
|
8
|
+
one API origin — the namespaced OS-keychain entry and the credentials-file
|
|
9
|
+
row — plus best-effort server session revocation. Other origins, other
|
|
10
|
+
namespaces, and the rest of the config home are never touched.
|
|
11
|
+
- `NEO_COMPOSE_CONFIG_HOME`: an absolute directory that replaces
|
|
12
|
+
`$XDG_CONFIG_HOME/neo-compose` for Neo credential state only, so isolated
|
|
13
|
+
environments (P53 agent rigs) keep their own credential store without
|
|
14
|
+
changing the ambient XDG configuration of unrelated tools.
|
|
15
|
+
- `NEO_COMPOSE_CREDENTIAL_NAMESPACE`: scopes the OS-keychain account
|
|
16
|
+
(`<namespace>::<api origin>`) so isolated environments sharing one OS user
|
|
17
|
+
cannot read or delete each other's tokens.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- The credential-store docstring described a two-level precedence with the
|
|
22
|
+
OS keychain "layering in later"; the keychain layer has existed for some
|
|
23
|
+
time. The docstring now matches the real precedence (env var, then
|
|
24
|
+
keychain, then credentials file) and documents that the metadata row is
|
|
25
|
+
written even when the keychain holds the token.
|
|
26
|
+
|
|
3
27
|
## [0.26.5] - 2026-08-10
|
|
4
28
|
|
|
5
29
|
### Changed
|
package/README.md
CHANGED
|
@@ -419,6 +419,7 @@ authorization boundary.
|
|
|
419
419
|
| `--version` | Print the installed CLI package version. |
|
|
420
420
|
| `login` | Authenticate the selected profile. |
|
|
421
421
|
| `whoami` | Inspect the selected profile. |
|
|
422
|
+
| `logout [--api <url>]` | Delete the stored credential for one API origin and revoke its session. |
|
|
422
423
|
| `init --project <id> [--version <id>]` | Create a format-4 working copy and perform its first reset pull. |
|
|
423
424
|
| `doctor` | Validate format/compiler/editor/source/file contracts; no .NET discovery. |
|
|
424
425
|
| `pull [--force\|--reset] [--regenerate-source-names]` | Merge remote changes or regenerate canonical source, names, and binaries. |
|
package/dist/neo.mjs
CHANGED
|
@@ -44,7 +44,12 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
44
44
|
// src/token-store.ts
|
|
45
45
|
var token_store_exports = {};
|
|
46
46
|
__export(token_store_exports, {
|
|
47
|
+
NEO_CONFIG_HOME_ENV_VAR: () => NEO_CONFIG_HOME_ENV_VAR,
|
|
48
|
+
NEO_CREDENTIAL_NAMESPACE_ENV_VAR: () => NEO_CREDENTIAL_NAMESPACE_ENV_VAR,
|
|
47
49
|
NEO_TOKEN_ENV_VAR: () => NEO_TOKEN_ENV_VAR,
|
|
50
|
+
credentialsDir: () => credentialsDir,
|
|
51
|
+
deleteCredential: () => deleteCredential,
|
|
52
|
+
keychainAccount: () => keychainAccount,
|
|
48
53
|
loadCredential: () => loadCredential,
|
|
49
54
|
loadToken: () => loadToken,
|
|
50
55
|
saveCredential: () => saveCredential
|
|
@@ -54,12 +59,22 @@ import {
|
|
|
54
59
|
existsSync,
|
|
55
60
|
mkdirSync,
|
|
56
61
|
readFileSync,
|
|
62
|
+
rmSync,
|
|
57
63
|
writeFileSync
|
|
58
64
|
} from "node:fs";
|
|
59
65
|
import { execFileSync } from "node:child_process";
|
|
60
66
|
import { homedir } from "node:os";
|
|
61
|
-
import { join } from "node:path";
|
|
67
|
+
import { isAbsolute, join } from "node:path";
|
|
62
68
|
function credentialsDir() {
|
|
69
|
+
const configHome = process.env[NEO_CONFIG_HOME_ENV_VAR];
|
|
70
|
+
if (configHome !== void 0 && configHome !== "") {
|
|
71
|
+
if (!isAbsolute(configHome)) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`${NEO_CONFIG_HOME_ENV_VAR} must be an absolute directory path, got "${configHome}".`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
return configHome;
|
|
77
|
+
}
|
|
63
78
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
64
79
|
const base = xdg !== void 0 && xdg !== "" ? xdg : join(homedir(), ".config");
|
|
65
80
|
return join(base, "neo-compose");
|
|
@@ -80,6 +95,11 @@ function readCredentialsFile() {
|
|
|
80
95
|
}
|
|
81
96
|
return file;
|
|
82
97
|
}
|
|
98
|
+
function keychainAccount(apiBaseUrl) {
|
|
99
|
+
const namespace = process.env[NEO_CREDENTIAL_NAMESPACE_ENV_VAR];
|
|
100
|
+
if (namespace === void 0 || namespace === "") return apiBaseUrl;
|
|
101
|
+
return `${namespace}::${apiBaseUrl}`;
|
|
102
|
+
}
|
|
83
103
|
function keychainSet(account, secret) {
|
|
84
104
|
if (process.platform !== "darwin") return false;
|
|
85
105
|
try {
|
|
@@ -116,8 +136,24 @@ function keychainGet(account) {
|
|
|
116
136
|
return null;
|
|
117
137
|
}
|
|
118
138
|
}
|
|
139
|
+
function keychainDelete(account) {
|
|
140
|
+
if (process.platform !== "darwin") return false;
|
|
141
|
+
try {
|
|
142
|
+
execFileSync(
|
|
143
|
+
"security",
|
|
144
|
+
["delete-generic-password", "-s", KEYCHAIN_SERVICE, "-a", account],
|
|
145
|
+
{ stdio: "ignore" }
|
|
146
|
+
);
|
|
147
|
+
return true;
|
|
148
|
+
} catch {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
119
152
|
function saveCredential(credential) {
|
|
120
|
-
const inKeychain = keychainSet(
|
|
153
|
+
const inKeychain = keychainSet(
|
|
154
|
+
keychainAccount(credential.apiBaseUrl),
|
|
155
|
+
credential.token
|
|
156
|
+
);
|
|
121
157
|
mkdirSync(credentialsDir(), { recursive: true, mode: 448 });
|
|
122
158
|
const file = readCredentialsFile();
|
|
123
159
|
file.credentials[credential.apiBaseUrl] = inKeychain ? { ...credential, token: "" } : credential;
|
|
@@ -132,7 +168,7 @@ function saveCredential(credential) {
|
|
|
132
168
|
function loadToken(apiBaseUrl) {
|
|
133
169
|
const envToken = process.env[NEO_TOKEN_ENV_VAR];
|
|
134
170
|
if (envToken !== void 0 && envToken !== "") return envToken;
|
|
135
|
-
const fromKeychain = keychainGet(apiBaseUrl);
|
|
171
|
+
const fromKeychain = keychainGet(keychainAccount(apiBaseUrl));
|
|
136
172
|
if (fromKeychain !== null) return fromKeychain;
|
|
137
173
|
const file = readCredentialsFile();
|
|
138
174
|
const credential = file.credentials[apiBaseUrl];
|
|
@@ -143,11 +179,38 @@ function loadCredential(apiBaseUrl) {
|
|
|
143
179
|
const file = readCredentialsFile();
|
|
144
180
|
return file.credentials[apiBaseUrl] ?? null;
|
|
145
181
|
}
|
|
146
|
-
|
|
182
|
+
function deleteCredential(apiBaseUrl) {
|
|
183
|
+
const account = keychainAccount(apiBaseUrl);
|
|
184
|
+
const fromKeychain = keychainGet(account);
|
|
185
|
+
const file = readCredentialsFile();
|
|
186
|
+
const fileEntry = file.credentials[apiBaseUrl];
|
|
187
|
+
const token = fromKeychain ?? (fileEntry !== void 0 && fileEntry.token !== "" ? fileEntry.token : null);
|
|
188
|
+
const deletedKeychainEntry = keychainDelete(account);
|
|
189
|
+
let deletedFileEntry = false;
|
|
190
|
+
if (fileEntry !== void 0) {
|
|
191
|
+
delete file.credentials[apiBaseUrl];
|
|
192
|
+
deletedFileEntry = true;
|
|
193
|
+
const path = credentialsPath();
|
|
194
|
+
if (Object.keys(file.credentials).length === 0) {
|
|
195
|
+
rmSync(path, { force: true });
|
|
196
|
+
} else {
|
|
197
|
+
writeFileSync(path, `${JSON.stringify(file, null, 2)}
|
|
198
|
+
`, {
|
|
199
|
+
encoding: "utf8",
|
|
200
|
+
mode: 384
|
|
201
|
+
});
|
|
202
|
+
chmodSync(path, 384);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return { token, deletedKeychainEntry, deletedFileEntry };
|
|
206
|
+
}
|
|
207
|
+
var NEO_TOKEN_ENV_VAR, NEO_CONFIG_HOME_ENV_VAR, NEO_CREDENTIAL_NAMESPACE_ENV_VAR, KEYCHAIN_SERVICE;
|
|
147
208
|
var init_token_store = __esm({
|
|
148
209
|
"src/token-store.ts"() {
|
|
149
210
|
"use strict";
|
|
150
211
|
NEO_TOKEN_ENV_VAR = "NEO_COMPOSE_TOKEN";
|
|
212
|
+
NEO_CONFIG_HOME_ENV_VAR = "NEO_COMPOSE_CONFIG_HOME";
|
|
213
|
+
NEO_CREDENTIAL_NAMESPACE_ENV_VAR = "NEO_COMPOSE_CREDENTIAL_NAMESPACE";
|
|
151
214
|
KEYCHAIN_SERVICE = "neo-compose-cli";
|
|
152
215
|
}
|
|
153
216
|
});
|
|
@@ -427,6 +490,44 @@ async function runLogin(options) {
|
|
|
427
490
|
return;
|
|
428
491
|
}
|
|
429
492
|
}
|
|
493
|
+
async function runLogout(apiBaseUrl) {
|
|
494
|
+
const { deleteCredential: deleteCredential2 } = await Promise.resolve().then(() => (init_token_store(), token_store_exports));
|
|
495
|
+
const deleted = deleteCredential2(apiBaseUrl);
|
|
496
|
+
if (deleted.token === null && !deleted.deletedKeychainEntry && !deleted.deletedFileEntry) {
|
|
497
|
+
console.log(`No credentials stored for "${apiBaseUrl}".`);
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
if (deleted.token !== null) {
|
|
501
|
+
try {
|
|
502
|
+
const response = await fetch(new URL("/api/auth/sign-out", apiBaseUrl), {
|
|
503
|
+
body: "{}",
|
|
504
|
+
headers: {
|
|
505
|
+
Authorization: `Bearer ${deleted.token}`,
|
|
506
|
+
"Content-Type": "application/json"
|
|
507
|
+
},
|
|
508
|
+
method: "POST"
|
|
509
|
+
});
|
|
510
|
+
if (response.ok) {
|
|
511
|
+
console.log(`Revoked the server session at ${apiBaseUrl}.`);
|
|
512
|
+
} else {
|
|
513
|
+
console.log(
|
|
514
|
+
`Server session revocation returned ${response.status}; the local credential was still deleted.`
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
} catch {
|
|
518
|
+
console.log(
|
|
519
|
+
"Server session revocation was unreachable; the local credential was still deleted."
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
const surfaces = [
|
|
524
|
+
deleted.deletedKeychainEntry ? "Keychain entry" : null,
|
|
525
|
+
deleted.deletedFileEntry ? "credentials file entry" : null
|
|
526
|
+
].filter((surface) => surface !== null);
|
|
527
|
+
console.log(
|
|
528
|
+
`Logged out of ${apiBaseUrl}${surfaces.length > 0 ? ` (removed ${surfaces.join(" and ")})` : ""}.`
|
|
529
|
+
);
|
|
530
|
+
}
|
|
430
531
|
async function runWhoami(apiBaseUrl) {
|
|
431
532
|
const { loadToken: loadToken2 } = await Promise.resolve().then(() => (init_token_store(), token_store_exports));
|
|
432
533
|
const token = loadToken2(apiBaseUrl);
|
|
@@ -93879,7 +93980,7 @@ import {
|
|
|
93879
93980
|
readFileSync as readFileSync6,
|
|
93880
93981
|
readdirSync,
|
|
93881
93982
|
renameSync as renameSync4,
|
|
93882
|
-
rmSync,
|
|
93983
|
+
rmSync as rmSync2,
|
|
93883
93984
|
writeFileSync as writeFileSync6
|
|
93884
93985
|
} from "node:fs";
|
|
93885
93986
|
import { basename, dirname as dirname5, extname, join as join6, relative, sep } from "node:path";
|
|
@@ -94159,7 +94260,7 @@ function writeVerifiedBinaryDownloadV4(destination, bytes, expectedSha256) {
|
|
|
94159
94260
|
writeFileSync6(temporary, bytes);
|
|
94160
94261
|
renameSync4(temporary, destination);
|
|
94161
94262
|
} finally {
|
|
94162
|
-
|
|
94263
|
+
rmSync2(temporary, { force: true });
|
|
94163
94264
|
}
|
|
94164
94265
|
}
|
|
94165
94266
|
function writeBinaryConflictArtifactV4(root, fileId, fileName2, bytes, expectedSha256) {
|
|
@@ -95284,7 +95385,7 @@ import {
|
|
|
95284
95385
|
mkdirSync as mkdirSync7,
|
|
95285
95386
|
readFileSync as readFileSync8,
|
|
95286
95387
|
readdirSync as readdirSync3,
|
|
95287
|
-
rmSync as
|
|
95388
|
+
rmSync as rmSync3,
|
|
95288
95389
|
writeFileSync as writeFileSync7,
|
|
95289
95390
|
statSync
|
|
95290
95391
|
} from "node:fs";
|
|
@@ -95296,14 +95397,14 @@ function resetWorkspaceToProjectSourcesV4(workspace, document, options = {}) {
|
|
|
95296
95397
|
const previous = managedFilesBeforeReset(workspace.root);
|
|
95297
95398
|
const preservedSpecs = preserveManagedSpecs(workspace.root);
|
|
95298
95399
|
for (const directory of FORMAT_4_MANAGED_DIRECTORIES) {
|
|
95299
|
-
|
|
95400
|
+
rmSync3(join8(workspace.root, directory), { recursive: true, force: true });
|
|
95300
95401
|
}
|
|
95301
|
-
|
|
95402
|
+
rmSync3(join8(workspace.root, "Scripts"), { recursive: true, force: true });
|
|
95302
95403
|
for (const file of LEGACY_ROOT_FILES) {
|
|
95303
|
-
|
|
95404
|
+
rmSync3(join8(workspace.root, file), { force: true });
|
|
95304
95405
|
}
|
|
95305
95406
|
for (const privatePath of LEGACY_PRIVATE_PATHS) {
|
|
95306
|
-
|
|
95407
|
+
rmSync3(join8(workspace.root, privatePath), { recursive: true, force: true });
|
|
95307
95408
|
}
|
|
95308
95409
|
for (const [path, bytes] of preservedSpecs) {
|
|
95309
95410
|
const absolute = join8(workspace.root, path);
|
|
@@ -95559,7 +95660,7 @@ var init_http = __esm({
|
|
|
95559
95660
|
});
|
|
95560
95661
|
|
|
95561
95662
|
// src/project-source/project-file-pull.ts
|
|
95562
|
-
import { existsSync as existsSync6, rmSync as
|
|
95663
|
+
import { existsSync as existsSync6, rmSync as rmSync4 } from "node:fs";
|
|
95563
95664
|
import { join as join9 } from "node:path";
|
|
95564
95665
|
async function pullProjectBinariesV4(args) {
|
|
95565
95666
|
let client = args.client ?? null;
|
|
@@ -95688,7 +95789,7 @@ async function pullProjectBinariesV4(args) {
|
|
|
95688
95789
|
conflicted += 1;
|
|
95689
95790
|
continue;
|
|
95690
95791
|
}
|
|
95691
|
-
|
|
95792
|
+
rmSync4(absolute, { force: true });
|
|
95692
95793
|
removePreviousConflict(args.workspace.root, previous.projectBinary);
|
|
95693
95794
|
}
|
|
95694
95795
|
return { states, downloaded, conflicted, conflicts };
|
|
@@ -95758,7 +95859,7 @@ function fileName(data) {
|
|
|
95758
95859
|
}
|
|
95759
95860
|
function removePreviousConflict(root, state) {
|
|
95760
95861
|
if (state?.conflict?.artifactPath === void 0) return;
|
|
95761
|
-
|
|
95862
|
+
rmSync4(join9(root, state.conflict.artifactPath), { force: true });
|
|
95762
95863
|
}
|
|
95763
95864
|
var init_project_file_pull = __esm({
|
|
95764
95865
|
"src/project-source/project-file-pull.ts"() {
|
|
@@ -95823,7 +95924,7 @@ __export(pull_exports, {
|
|
|
95823
95924
|
import {
|
|
95824
95925
|
mkdirSync as mkdirSync8,
|
|
95825
95926
|
writeFileSync as writeFileSync8,
|
|
95826
|
-
rmSync as
|
|
95927
|
+
rmSync as rmSync5,
|
|
95827
95928
|
existsSync as existsSync7,
|
|
95828
95929
|
readFileSync as readFileSync9
|
|
95829
95930
|
} from "node:fs";
|
|
@@ -96212,7 +96313,7 @@ async function finishFormat4Pull(args) {
|
|
|
96212
96313
|
if (previousPath === void 0 || emittedPaths.has(previousPath)) continue;
|
|
96213
96314
|
const absolute = join10(workspace.root, previousPath);
|
|
96214
96315
|
if (existsSync7(absolute)) {
|
|
96215
|
-
|
|
96316
|
+
rmSync5(absolute);
|
|
96216
96317
|
removed += 1;
|
|
96217
96318
|
}
|
|
96218
96319
|
}
|
|
@@ -100378,7 +100479,7 @@ import { createHash as createHash10, randomUUID as randomUUID2 } from "node:cryp
|
|
|
100378
100479
|
import {
|
|
100379
100480
|
mkdirSync as mkdirSync10,
|
|
100380
100481
|
writeFileSync as writeFileSync10,
|
|
100381
|
-
rmSync as
|
|
100482
|
+
rmSync as rmSync6,
|
|
100382
100483
|
existsSync as existsSync11,
|
|
100383
100484
|
readFileSync as readFileSync15
|
|
100384
100485
|
} from "node:fs";
|
|
@@ -101174,7 +101275,7 @@ async function runPush(workspace, options, preparationOverride) {
|
|
|
101174
101275
|
});
|
|
101175
101276
|
} finally {
|
|
101176
101277
|
if (preparedBuildDir !== void 0) {
|
|
101177
|
-
|
|
101278
|
+
rmSync6(preparedBuildDir, { recursive: true, force: true });
|
|
101178
101279
|
}
|
|
101179
101280
|
}
|
|
101180
101281
|
} else {
|
|
@@ -102387,7 +102488,7 @@ ${finalErrors.map(
|
|
|
102387
102488
|
const previousPath = recordState.file;
|
|
102388
102489
|
if (previousPath === void 0 || emittedPaths.has(previousPath)) continue;
|
|
102389
102490
|
const absolute = join15(workspace.root, previousPath);
|
|
102390
|
-
if (existsSync11(absolute))
|
|
102491
|
+
if (existsSync11(absolute)) rmSync6(absolute);
|
|
102391
102492
|
}
|
|
102392
102493
|
for (const file of files) {
|
|
102393
102494
|
const absolute = join15(workspace.root, file.path);
|
|
@@ -103197,7 +103298,7 @@ var init_registry2 = __esm({
|
|
|
103197
103298
|
PROJECT_SCHEMA_CONTRACT = Object.freeze({
|
|
103198
103299
|
formatVersion: 3,
|
|
103199
103300
|
contractVersion: "3.9",
|
|
103200
|
-
cliVersion: "0.
|
|
103301
|
+
cliVersion: "0.27.0",
|
|
103201
103302
|
projectFileUploadBatchSize: 32,
|
|
103202
103303
|
documentRecords: {
|
|
103203
103304
|
member: {
|
|
@@ -104498,14 +104599,14 @@ import {
|
|
|
104498
104599
|
readFileSync as readFileSync16,
|
|
104499
104600
|
realpathSync,
|
|
104500
104601
|
renameSync as renameSync5,
|
|
104501
|
-
rmSync as
|
|
104602
|
+
rmSync as rmSync7,
|
|
104502
104603
|
statSync as statSync2,
|
|
104503
104604
|
writeFileSync as writeFileSync11
|
|
104504
104605
|
} from "node:fs";
|
|
104505
104606
|
import {
|
|
104506
104607
|
basename as basename3,
|
|
104507
104608
|
dirname as dirname9,
|
|
104508
|
-
isAbsolute,
|
|
104609
|
+
isAbsolute as isAbsolute2,
|
|
104509
104610
|
join as join16,
|
|
104510
104611
|
relative as relative5,
|
|
104511
104612
|
resolve as resolve3,
|
|
@@ -104706,7 +104807,7 @@ function preparedHookCandidate(workspace) {
|
|
|
104706
104807
|
"test-build"
|
|
104707
104808
|
);
|
|
104708
104809
|
const pathFromRoot = relative5(cacheRoot, directory);
|
|
104709
|
-
if (
|
|
104810
|
+
if (isAbsolute2(pathFromRoot)) {
|
|
104710
104811
|
throw new NeoTestPreparedCandidateError(
|
|
104711
104812
|
"NEO_PREPARED_BUILD_DIR must not resolve to an absolute path outside this workspace's .neo/test-build directory."
|
|
104712
104813
|
);
|
|
@@ -104906,7 +105007,7 @@ function selectedSpecPaths(workspace, selectors) {
|
|
|
104906
105007
|
);
|
|
104907
105008
|
for (const selector of normalizedSelectors) {
|
|
104908
105009
|
if (/[*?]/u.test(selector)) continue;
|
|
104909
|
-
if (
|
|
105010
|
+
if (isAbsolute2(selector)) {
|
|
104910
105011
|
throw new Error(
|
|
104911
105012
|
`Spec selector ${JSON.stringify(selector)} must be workspace-relative.`
|
|
104912
105013
|
);
|
|
@@ -105691,16 +105792,16 @@ function maintainNeoTestBuildCache(root, maxBytes = TEST_BUILD_CACHE_LIMIT_BYTES
|
|
|
105691
105792
|
const resolvedProtected = protectedDirectory === void 0 ? null : resolve3(protectedDirectory);
|
|
105692
105793
|
const protectedInsideRoot = resolvedProtected !== null && (() => {
|
|
105693
105794
|
const fromRoot = relative5(resolvedRoot, resolvedProtected);
|
|
105694
|
-
return fromRoot === "" || !fromRoot.startsWith(`..${sep5}`) && fromRoot !== ".." && !
|
|
105795
|
+
return fromRoot === "" || !fromRoot.startsWith(`..${sep5}`) && fromRoot !== ".." && !isAbsolute2(fromRoot);
|
|
105695
105796
|
})();
|
|
105696
105797
|
const isProtected = (path) => {
|
|
105697
105798
|
if (!protectedInsideRoot || resolvedProtected === null) return false;
|
|
105698
105799
|
const fromProtected = relative5(resolvedProtected, resolve3(path));
|
|
105699
|
-
return fromProtected === "" || !fromProtected.startsWith(`..${sep5}`) && fromProtected !== ".." && !
|
|
105800
|
+
return fromProtected === "" || !fromProtected.startsWith(`..${sep5}`) && fromProtected !== ".." && !isAbsolute2(fromProtected);
|
|
105700
105801
|
};
|
|
105701
105802
|
for (const file of testBuildFiles(root)) {
|
|
105702
105803
|
if (!isProtected(file.path) && basename3(file.path).includes(".tmp-") && now - file.modifiedMs >= ABANDONED_TEMP_MAX_AGE_MS) {
|
|
105703
|
-
|
|
105804
|
+
rmSync7(file.path, { force: true });
|
|
105704
105805
|
}
|
|
105705
105806
|
}
|
|
105706
105807
|
const files = testBuildFiles(root);
|
|
@@ -105710,7 +105811,7 @@ function maintainNeoTestBuildCache(root, maxBytes = TEST_BUILD_CACHE_LIMIT_BYTES
|
|
|
105710
105811
|
).sort((left, right) => left.modifiedMs - right.modifiedMs);
|
|
105711
105812
|
for (const file of removable) {
|
|
105712
105813
|
if (total <= maxBytes) break;
|
|
105713
|
-
|
|
105814
|
+
rmSync7(file.path, { force: true });
|
|
105714
105815
|
total -= file.size;
|
|
105715
105816
|
}
|
|
105716
105817
|
}
|
|
@@ -106004,7 +106105,7 @@ async function runTest(workspace, options, dependencies = {}) {
|
|
|
106004
106105
|
if (options.outputFile !== null) {
|
|
106005
106106
|
try {
|
|
106006
106107
|
atomicWrite(
|
|
106007
|
-
|
|
106108
|
+
isAbsolute2(options.outputFile) ? options.outputFile : join16(workspace.root, options.outputFile),
|
|
106008
106109
|
serialized
|
|
106009
106110
|
);
|
|
106010
106111
|
} catch (error) {
|
|
@@ -106217,7 +106318,7 @@ import {
|
|
|
106217
106318
|
existsSync as existsSync13,
|
|
106218
106319
|
readFileSync as readFileSync17
|
|
106219
106320
|
} from "node:fs";
|
|
106220
|
-
import { extname as extname2, isAbsolute as
|
|
106321
|
+
import { extname as extname2, isAbsolute as isAbsolute3, join as join17, relative as relative6, sep as sep6 } from "node:path";
|
|
106221
106322
|
function inspectNeoDoctor(workspace) {
|
|
106222
106323
|
const formatCompatible = workspace.config.formatVersion === CURRENT_FORMAT_VERSION;
|
|
106223
106324
|
const compiler = inspectCompilerContract();
|
|
@@ -106397,7 +106498,7 @@ function inspectTrackedBinary(root, record3, errors) {
|
|
|
106397
106498
|
const binary = record3.projectBinary;
|
|
106398
106499
|
if (!binary) return;
|
|
106399
106500
|
const path = binary.path.replaceAll("\\", "/");
|
|
106400
|
-
if (
|
|
106501
|
+
if (isAbsolute3(path) || path.split("/").includes("..")) {
|
|
106401
106502
|
errors.push(
|
|
106402
106503
|
`Project file ${record3.recordId} has unsafe tracked path ${JSON.stringify(binary.path)}.`
|
|
106403
106504
|
);
|
|
@@ -109474,7 +109575,7 @@ __export(resolve_exports, {
|
|
|
109474
109575
|
runResolve: () => runResolve,
|
|
109475
109576
|
workspaceFilePath: () => workspaceFilePath
|
|
109476
109577
|
});
|
|
109477
|
-
import { readFileSync as readFileSync19, rmSync as
|
|
109578
|
+
import { readFileSync as readFileSync19, rmSync as rmSync8, writeFileSync as writeFileSync14 } from "node:fs";
|
|
109478
109579
|
import { join as join21 } from "node:path";
|
|
109479
109580
|
function runResolve(workspace, side) {
|
|
109480
109581
|
let resolvedFiles = 0;
|
|
@@ -109500,12 +109601,12 @@ function runResolve(workspace, side) {
|
|
|
109500
109601
|
);
|
|
109501
109602
|
binary.sha256 = conflict2.remoteSha256;
|
|
109502
109603
|
} else {
|
|
109503
|
-
|
|
109604
|
+
rmSync8(destination, { force: true });
|
|
109504
109605
|
binary.sha256 = null;
|
|
109505
109606
|
}
|
|
109506
109607
|
}
|
|
109507
109608
|
if (conflict2.artifactPath !== void 0) {
|
|
109508
|
-
|
|
109609
|
+
rmSync8(join21(workspace.root, conflict2.artifactPath), { force: true });
|
|
109509
109610
|
}
|
|
109510
109611
|
delete binary.conflict;
|
|
109511
109612
|
resolvedBinaries += 1;
|
|
@@ -109604,6 +109705,7 @@ ${h("Start")}
|
|
|
109604
109705
|
login ${d("[--api <url>] [--profile editor|release] [--save-project <id>]")}
|
|
109605
109706
|
init ${d("[--project <id>] [--version <id>] [--dir <path>] (interactive pickers)")}
|
|
109606
109707
|
whoami ${d("[--api <url>]")}
|
|
109708
|
+
logout ${d("[--api <url>] delete the stored credential for one API origin")}
|
|
109607
109709
|
help ${d("show this command overview")}
|
|
109608
109710
|
--version ${d("print the installed CLI version")}
|
|
109609
109711
|
|
|
@@ -109762,7 +109864,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
|
|
|
109762
109864
|
async function main() {
|
|
109763
109865
|
const args = parseArgs(process.argv.slice(2));
|
|
109764
109866
|
if (args.command === "--version") {
|
|
109765
|
-
console.log("0.
|
|
109867
|
+
console.log("0.27.0");
|
|
109766
109868
|
return;
|
|
109767
109869
|
}
|
|
109768
109870
|
if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {
|
|
@@ -109787,6 +109889,9 @@ async function main() {
|
|
|
109787
109889
|
case "whoami":
|
|
109788
109890
|
await runWhoami(apiBaseUrl);
|
|
109789
109891
|
return;
|
|
109892
|
+
case "logout":
|
|
109893
|
+
await runLogout(apiBaseUrl);
|
|
109894
|
+
return;
|
|
109790
109895
|
case "init":
|
|
109791
109896
|
{
|
|
109792
109897
|
const { runInit: runInit2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
package/package.json
CHANGED
|
@@ -83,7 +83,7 @@ wrappers.
|
|
|
83
83
|
The marker near the top of `SKILL.md` must exactly match the package version:
|
|
84
84
|
|
|
85
85
|
```html
|
|
86
|
-
<!-- reviewed-through-cli: 0.
|
|
86
|
+
<!-- reviewed-through-cli: 0.27.0 -->
|
|
87
87
|
```
|
|
88
88
|
|
|
89
89
|
The quoted version above is checked too, so this instruction cannot go stale
|
|
@@ -145,5 +145,16 @@ and a protected file only as fallback. Use `NEO_COMPOSE_TOKEN` or
|
|
|
145
145
|
`--token-stdin` in CI. The editor profile cannot publish releases; server
|
|
146
146
|
scopes remain the security boundary.
|
|
147
147
|
|
|
148
|
+
`neo logout [--api <url>]` deletes the stored credential for exactly one API
|
|
149
|
+
origin (keychain entry and credentials-file row) and revokes the server
|
|
150
|
+
session best-effort. It never clears other origins or the whole store.
|
|
151
|
+
|
|
152
|
+
Isolated environments (P53 agent rigs) set `NEO_COMPOSE_CONFIG_HOME` (an
|
|
153
|
+
absolute directory replacing `$XDG_CONFIG_HOME/neo-compose` for Neo state
|
|
154
|
+
only) and `NEO_COMPOSE_CREDENTIAL_NAMESPACE` (scopes the OS-keychain account)
|
|
155
|
+
so concurrent environments sharing one OS user cannot read, overwrite, or
|
|
156
|
+
delete each other's tokens. Login, logout, and authenticated commands all
|
|
157
|
+
honor both variables.
|
|
158
|
+
|
|
148
159
|
Pass explicit project/version IDs and flags in automation. Prefer `--json` for
|
|
149
160
|
machine-readable output and do not depend on interactive pickers or confirms.
|