@bastani/atomic 0.5.28-2 → 0.5.29-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/assets/settings.schema.json +0 -5
- package/dist/services/config/atomic-config.d.ts +0 -2
- package/dist/services/config/atomic-config.d.ts.map +1 -1
- package/package.json +3 -6
- package/src/cli.ts +6 -0
- package/src/services/config/atomic-config.ts +0 -6
- package/src/services/config/settings.ts +19 -1
- package/src/services/system/auth.test.ts +48 -14
|
@@ -17,11 +17,6 @@
|
|
|
17
17
|
"enum": ["github", "azure-devops", "sapling"],
|
|
18
18
|
"description": "Selected source control provider. On atomic startup, the corresponding GitHub / Azure DevOps MCP servers are enabled in `.claude/settings.json` and `.opencode/opencode.json`; the others are disabled to avoid unnecessary token consumption."
|
|
19
19
|
},
|
|
20
|
-
"lastUpdated": {
|
|
21
|
-
"type": "string",
|
|
22
|
-
"format": "date-time",
|
|
23
|
-
"description": "ISO 8601 timestamp of the last configuration update."
|
|
24
|
-
},
|
|
25
20
|
"providers": {
|
|
26
21
|
"type": "object",
|
|
27
22
|
"description": "Per-provider overrides for chatFlags and envVars. chatFlags replaces built-in defaults entirely when set; envVars are merged on top of defaults (user values win on conflict). Local .atomic/settings.json takes precedence over global ~/.atomic/settings.json.",
|
|
@@ -17,8 +17,6 @@ export declare function isScmProvider(value: unknown): value is ScmProvider;
|
|
|
17
17
|
export interface AtomicConfig {
|
|
18
18
|
/** Version of config schema */
|
|
19
19
|
version?: number;
|
|
20
|
-
/** Timestamp of last init */
|
|
21
|
-
lastUpdated?: string;
|
|
22
20
|
/** Selected source control provider (drives MCP server enable/disable sync). */
|
|
23
21
|
scm?: ScmProvider;
|
|
24
22
|
/** Per-provider overrides for chatFlags and envVars */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"atomic-config.d.ts","sourceRoot":"","sources":["../../../src/services/config/atomic-config.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAOnE,0EAA0E;AAC1E,eAAO,MAAM,aAAa,gDAAiD,CAAC;AAC5E,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,WAAW,CAElE;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,+BAA+B;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,
|
|
1
|
+
{"version":3,"file":"atomic-config.d.ts","sourceRoot":"","sources":["../../../src/services/config/atomic-config.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAOnE,0EAA0E;AAC1E,eAAO,MAAM,aAAa,gDAAiD,CAAC;AAC5E,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,WAAW,CAElE;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,+BAA+B;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gFAAgF;IAChF,GAAG,CAAC,EAAE,WAAW,CAAC;IAClB,uDAAuD;IACvD,SAAS,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,iBAAiB,CAAC,CAAC,CAAC;CAC1D;AAuHD;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAMvF;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,OAAO,CAAC,YAAY,CAAC,GAC7B,OAAO,CAAC,IAAI,CAAC,CAqBf;AAED;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,QAAQ,EAClB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,iBAAiB,CAAC,CAG5B"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/atomic",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.29-0",
|
|
4
4
|
"description": "Configuration management CLI and SDK for coding agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -61,15 +61,12 @@
|
|
|
61
61
|
"typecheck": "bunx tsc --noEmit && bunx tsc -p tests --noEmit",
|
|
62
62
|
"lint": "oxlint --config=oxlint.json src",
|
|
63
63
|
"lint:fix": "oxlint --config=oxlint.json --fix src",
|
|
64
|
-
"prepare": "
|
|
64
|
+
"prepare": "prek install -t pre-commit -t pre-push || true"
|
|
65
65
|
},
|
|
66
|
-
"trustedDependencies": [
|
|
67
|
-
"lefthook"
|
|
68
|
-
],
|
|
69
66
|
"devDependencies": {
|
|
67
|
+
"@j178/prek": "^0.3.10",
|
|
70
68
|
"@types/bun": "^1.3.12",
|
|
71
69
|
"@types/react": "^19.2.14",
|
|
72
|
-
"lefthook": "^2.1.6",
|
|
73
70
|
"oxlint": "^1.61.0",
|
|
74
71
|
"typescript": "^6.0.3",
|
|
75
72
|
"typescript-language-server": "^5.1.3"
|
package/src/cli.ts
CHANGED
|
@@ -404,6 +404,12 @@ export const program = createProgram();
|
|
|
404
404
|
*/
|
|
405
405
|
async function main(): Promise<void> {
|
|
406
406
|
try {
|
|
407
|
+
// Bootstrap `~/.atomic/settings.json` on every invocation if absent,
|
|
408
|
+
// so users always have a file to edit with JSON Schema intellisense
|
|
409
|
+
// wired up. Idempotent; swallows FS errors internally.
|
|
410
|
+
const { ensureGlobalAtomicSettings } = await import("./services/config/settings.ts");
|
|
411
|
+
await ensureGlobalAtomicSettings();
|
|
412
|
+
|
|
407
413
|
// Sync tooling deps and global skills on first launch after install
|
|
408
414
|
// or upgrade. Runs at most once per version bump (gated on a marker
|
|
409
415
|
// file under ~/.atomic). Skipped for `--version` / `--help` so info
|
|
@@ -30,8 +30,6 @@ export function isScmProvider(value: unknown): value is ScmProvider {
|
|
|
30
30
|
export interface AtomicConfig {
|
|
31
31
|
/** Version of config schema */
|
|
32
32
|
version?: number;
|
|
33
|
-
/** Timestamp of last init */
|
|
34
|
-
lastUpdated?: string;
|
|
35
33
|
/** Selected source control provider (drives MCP server enable/disable sync). */
|
|
36
34
|
scm?: ScmProvider;
|
|
37
35
|
/** Per-provider overrides for chatFlags and envVars */
|
|
@@ -97,10 +95,8 @@ function pickAtomicConfig(record: JsonRecord | null): AtomicConfig | null {
|
|
|
97
95
|
|
|
98
96
|
const config: AtomicConfig = {};
|
|
99
97
|
const version = record.version;
|
|
100
|
-
const lastUpdated = record.lastUpdated;
|
|
101
98
|
|
|
102
99
|
if (typeof version === "number") config.version = version;
|
|
103
|
-
if (typeof lastUpdated === "string") config.lastUpdated = lastUpdated;
|
|
104
100
|
if (isScmProvider(record.scm)) config.scm = record.scm;
|
|
105
101
|
|
|
106
102
|
const providers = pickProviders(record.providers);
|
|
@@ -144,7 +140,6 @@ function mergeConfigs(...configs: Array<AtomicConfig | null>): AtomicConfig | nu
|
|
|
144
140
|
for (const config of configs) {
|
|
145
141
|
if (!config) continue;
|
|
146
142
|
if (config.version !== undefined) merged.version = config.version;
|
|
147
|
-
if (config.lastUpdated !== undefined) merged.lastUpdated = config.lastUpdated;
|
|
148
143
|
if (config.scm !== undefined) merged.scm = config.scm;
|
|
149
144
|
|
|
150
145
|
if (config.providers) {
|
|
@@ -186,7 +181,6 @@ export async function saveAtomicConfig(
|
|
|
186
181
|
...currentConfig,
|
|
187
182
|
...updates,
|
|
188
183
|
version: 1,
|
|
189
|
-
lastUpdated: new Date().toISOString(),
|
|
190
184
|
};
|
|
191
185
|
|
|
192
186
|
const nextSettings: JsonRecord = {
|
|
@@ -20,7 +20,6 @@ import type { ScmProvider } from "./atomic-config.ts";
|
|
|
20
20
|
interface AtomicSettings {
|
|
21
21
|
$schema?: string;
|
|
22
22
|
version?: number;
|
|
23
|
-
lastUpdated?: string;
|
|
24
23
|
telemetryEnabled?: boolean;
|
|
25
24
|
scm?: ScmProvider;
|
|
26
25
|
providers?: Partial<Record<AgentKey, ProviderOverrides>>;
|
|
@@ -54,6 +53,25 @@ async function writeGlobalSettings(settings: AtomicSettings): Promise<void> {
|
|
|
54
53
|
await Bun.write(path, JSON.stringify(settings, null, 2));
|
|
55
54
|
}
|
|
56
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Ensure `~/.atomic/settings.json` exists. Called once at CLI startup so
|
|
58
|
+
* users have a valid file to edit (with JSON Schema intellisense wired up
|
|
59
|
+
* via `$schema`) without having to run any explicit init command.
|
|
60
|
+
*
|
|
61
|
+
* Idempotent — no-op if the file already exists. Best-effort: filesystem
|
|
62
|
+
* errors (e.g. read-only home) are swallowed so the CLI never blocks on
|
|
63
|
+
* this side-effect.
|
|
64
|
+
*/
|
|
65
|
+
export async function ensureGlobalAtomicSettings(): Promise<void> {
|
|
66
|
+
try {
|
|
67
|
+
const path = globalSettingsPath();
|
|
68
|
+
if (await Bun.file(path).exists()) return;
|
|
69
|
+
await writeGlobalSettings({ version: 1 });
|
|
70
|
+
} catch (e) {
|
|
71
|
+
console.warn(`[settings] failed to bootstrap global settings: ${errorMessage(e)}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
57
75
|
/**
|
|
58
76
|
* Set telemetry enabled/disabled in global settings.
|
|
59
77
|
*/
|
|
@@ -13,8 +13,13 @@ import {
|
|
|
13
13
|
test,
|
|
14
14
|
expect,
|
|
15
15
|
beforeEach,
|
|
16
|
+
beforeAll,
|
|
17
|
+
afterAll,
|
|
16
18
|
mock,
|
|
17
19
|
} from "bun:test";
|
|
20
|
+
import { chmodSync, mkdtempSync, writeFileSync } from "node:fs";
|
|
21
|
+
import { tmpdir } from "node:os";
|
|
22
|
+
import { join } from "node:path";
|
|
18
23
|
|
|
19
24
|
// ─── Copilot SDK fake ──────────────────────────────────────────────────────
|
|
20
25
|
// `CopilotClient` is a class; the constructor captures latest test state.
|
|
@@ -45,10 +50,6 @@ class FakeCopilotClient {
|
|
|
45
50
|
}
|
|
46
51
|
}
|
|
47
52
|
|
|
48
|
-
mock.module("@github/copilot-sdk", () => ({
|
|
49
|
-
CopilotClient: FakeCopilotClient,
|
|
50
|
-
}));
|
|
51
|
-
|
|
52
53
|
// ─── Claude Agent SDK fake ────────────────────────────────────────────────
|
|
53
54
|
// `query()` returns something with `initializationResult()` and `close()`.
|
|
54
55
|
// We ignore the `prompt` stream — the real SDK consumes it lazily, and the
|
|
@@ -65,17 +66,50 @@ let claudeInit = mock<() => Promise<{ account: ClaudeAccount }>>(async () => ({
|
|
|
65
66
|
}));
|
|
66
67
|
let claudeClose = mock(() => {});
|
|
67
68
|
|
|
68
|
-
mock.module
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
// `mock.module` is process-global in Bun and leaks across every test file
|
|
70
|
+
// loaded in the same run — live ESM bindings in other files rebind to the
|
|
71
|
+
// stub as soon as it registers, and re-registering with the real module in
|
|
72
|
+
// `afterAll` does not restore the original namespace identity. Capture the
|
|
73
|
+
// real SDK modules first, install the mocks only while this file's tests
|
|
74
|
+
// are running, and never mock `claude.ts` (other test files exercise its
|
|
75
|
+
// real exports). All consumers in `auth.ts` use dynamic `await import(...)`,
|
|
76
|
+
// so lazy mock registration is safe here.
|
|
77
|
+
const actualCopilotSdk = await import("@github/copilot-sdk");
|
|
78
|
+
const actualClaudeSdk = await import("@anthropic-ai/claude-agent-sdk");
|
|
79
|
+
|
|
80
|
+
// Put a fake `claude` binary on PATH so `resolveHeadlessClaudeBin()` (called
|
|
81
|
+
// by `checkClaudeAuth`) succeeds without hitting the real CLI on disk. The
|
|
82
|
+
// mocked SDK `query()` never actually spawns the subprocess — the path is
|
|
83
|
+
// only passed through to the SDK constructor.
|
|
84
|
+
let pathBefore: string | undefined;
|
|
85
|
+
|
|
86
|
+
beforeAll(() => {
|
|
87
|
+
const dir = mkdtempSync(join(tmpdir(), "atomic-auth-test-path-"));
|
|
88
|
+
const bin = join(dir, "claude");
|
|
89
|
+
writeFileSync(bin, "#!/usr/bin/env sh\nexit 0\n");
|
|
90
|
+
chmodSync(bin, 0o755);
|
|
91
|
+
pathBefore = process.env.PATH;
|
|
92
|
+
process.env.PATH = `${dir}:${process.env.PATH ?? ""}`;
|
|
93
|
+
|
|
94
|
+
mock.module("@github/copilot-sdk", () => ({
|
|
95
|
+
...actualCopilotSdk,
|
|
96
|
+
CopilotClient: FakeCopilotClient,
|
|
97
|
+
}));
|
|
98
|
+
mock.module("@anthropic-ai/claude-agent-sdk", () => ({
|
|
99
|
+
...actualClaudeSdk,
|
|
100
|
+
query: () => ({
|
|
101
|
+
initializationResult: () => claudeInit(),
|
|
102
|
+
close: () => claudeClose(),
|
|
103
|
+
}),
|
|
104
|
+
}));
|
|
105
|
+
});
|
|
74
106
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}));
|
|
107
|
+
afterAll(() => {
|
|
108
|
+
if (pathBefore === undefined) delete process.env.PATH;
|
|
109
|
+
else process.env.PATH = pathBefore;
|
|
110
|
+
mock.module("@github/copilot-sdk", () => ({ ...actualCopilotSdk }));
|
|
111
|
+
mock.module("@anthropic-ai/claude-agent-sdk", () => ({ ...actualClaudeSdk }));
|
|
112
|
+
});
|
|
79
113
|
|
|
80
114
|
const { checkAgentAuth } = await import("./auth.ts");
|
|
81
115
|
|