@tracemarketplace/cli 0.0.13 → 0.0.17
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/api-client.d.ts +9 -2
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js +80 -15
- package/dist/api-client.js.map +1 -1
- package/dist/api-client.test.d.ts +2 -0
- package/dist/api-client.test.d.ts.map +1 -0
- package/dist/api-client.test.js +34 -0
- package/dist/api-client.test.js.map +1 -0
- package/dist/cli.js +48 -18
- package/dist/cli.js.map +1 -1
- package/dist/commands/auto-submit.d.ts +2 -1
- package/dist/commands/auto-submit.d.ts.map +1 -1
- package/dist/commands/auto-submit.js +43 -56
- package/dist/commands/auto-submit.js.map +1 -1
- package/dist/commands/daemon.d.ts +8 -1
- package/dist/commands/daemon.d.ts.map +1 -1
- package/dist/commands/daemon.js +184 -63
- package/dist/commands/daemon.js.map +1 -1
- package/dist/commands/history.d.ts +3 -1
- package/dist/commands/history.d.ts.map +1 -1
- package/dist/commands/history.js +8 -4
- package/dist/commands/history.js.map +1 -1
- package/dist/commands/login.d.ts +5 -1
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +25 -9
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/register.d.ts +1 -0
- package/dist/commands/register.d.ts.map +1 -1
- package/dist/commands/register.js +4 -39
- package/dist/commands/register.js.map +1 -1
- package/dist/commands/remove-daemon.d.ts +6 -0
- package/dist/commands/remove-daemon.d.ts.map +1 -0
- package/dist/commands/remove-daemon.js +66 -0
- package/dist/commands/remove-daemon.js.map +1 -0
- package/dist/commands/remove-hook.d.ts +6 -0
- package/dist/commands/remove-hook.d.ts.map +1 -0
- package/dist/commands/remove-hook.js +174 -0
- package/dist/commands/remove-hook.js.map +1 -0
- package/dist/commands/setup-hook.d.ts +2 -0
- package/dist/commands/setup-hook.d.ts.map +1 -1
- package/dist/commands/setup-hook.js +85 -41
- package/dist/commands/setup-hook.js.map +1 -1
- package/dist/commands/status.d.ts +3 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +8 -4
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/submit.d.ts +1 -0
- package/dist/commands/submit.d.ts.map +1 -1
- package/dist/commands/submit.js +138 -83
- package/dist/commands/submit.js.map +1 -1
- package/dist/commands/whoami.d.ts +3 -1
- package/dist/commands/whoami.d.ts.map +1 -1
- package/dist/commands/whoami.js +8 -4
- package/dist/commands/whoami.js.map +1 -1
- package/dist/config.d.ts +38 -6
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +175 -17
- package/dist/config.js.map +1 -1
- package/dist/constants.d.ts +8 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +16 -0
- package/dist/constants.js.map +1 -0
- package/dist/flush.d.ts +49 -0
- package/dist/flush.d.ts.map +1 -0
- package/dist/flush.js +405 -0
- package/dist/flush.js.map +1 -0
- package/dist/flush.test.d.ts +2 -0
- package/dist/flush.test.d.ts.map +1 -0
- package/dist/flush.test.js +330 -0
- package/dist/flush.test.js.map +1 -0
- package/dist/submitter.d.ts.map +1 -1
- package/dist/submitter.js +5 -2
- package/dist/submitter.js.map +1 -1
- package/package.json +8 -7
- package/src/api-client.test.ts +47 -0
- package/src/api-client.ts +100 -16
- package/src/cli.ts +55 -19
- package/src/commands/auto-submit.ts +80 -40
- package/src/commands/daemon.ts +243 -60
- package/src/commands/history.ts +9 -4
- package/src/commands/login.ts +37 -9
- package/src/commands/remove-daemon.ts +75 -0
- package/src/commands/remove-hook.ts +194 -0
- package/src/commands/setup-hook.ts +93 -43
- package/src/commands/status.ts +8 -4
- package/src/commands/submit.ts +191 -83
- package/src/commands/whoami.ts +8 -4
- package/src/config.ts +241 -21
- package/src/constants.ts +18 -0
- package/src/flush.test.ts +395 -0
- package/src/flush.ts +591 -0
- package/vitest.config.ts +8 -0
- package/src/commands/register.ts +0 -52
- package/src/submitter.ts +0 -110
package/src/config.ts
CHANGED
|
@@ -1,19 +1,49 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
2
2
|
import { homedir } from "os";
|
|
3
3
|
import { join } from "path";
|
|
4
|
+
import { DEFAULT_PROFILE } from "./constants.js";
|
|
4
5
|
|
|
5
|
-
export interface
|
|
6
|
+
export interface StoredProfileConfig {
|
|
6
7
|
apiKey: string;
|
|
7
8
|
serverUrl: string;
|
|
8
9
|
email: string;
|
|
9
10
|
}
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
export interface Config extends StoredProfileConfig {
|
|
13
|
+
profile: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ConfigStore {
|
|
17
|
+
defaultProfile: string;
|
|
18
|
+
profiles: Record<string, StoredProfileConfig>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type TrackedSessionTool = "claude_code" | "codex_cli" | "cursor";
|
|
22
|
+
|
|
23
|
+
export interface SessionUploadState {
|
|
24
|
+
sourceTool: TrackedSessionTool;
|
|
25
|
+
sourceSessionId: string;
|
|
26
|
+
locator: string;
|
|
27
|
+
nextChunkIndex: number;
|
|
28
|
+
openChunkStartTurn: number;
|
|
29
|
+
lastSeenTurnCount: number;
|
|
30
|
+
lastActivityAt: string | null;
|
|
31
|
+
lastFlushedTurnId: string | null;
|
|
32
|
+
// Async confirmation tracking
|
|
33
|
+
confirmedChunkIndex: number; // all chunks below this index are confirmed in DB
|
|
34
|
+
confirmedOpenChunkStartTurn: number; // openChunkStartTurn to restore if re-submitting
|
|
35
|
+
unconfirmedSince: string | null; // ISO timestamp when chunks first went unconfirmed
|
|
36
|
+
}
|
|
37
|
+
|
|
13
38
|
export interface SubmitState {
|
|
39
|
+
version: 2;
|
|
14
40
|
chunks: Record<string, number>;
|
|
41
|
+
sessions: Record<string, SessionUploadState>;
|
|
15
42
|
}
|
|
16
43
|
|
|
44
|
+
type JsonRecord = Record<string, unknown>;
|
|
45
|
+
const EMPTY_SUBMIT_STATE: SubmitState = { version: 2, chunks: {}, sessions: {} };
|
|
46
|
+
|
|
17
47
|
export function getConfigDir(): string {
|
|
18
48
|
return join(homedir(), ".config", "tracemarketplace");
|
|
19
49
|
}
|
|
@@ -22,40 +52,230 @@ export function getConfigPath(): string {
|
|
|
22
52
|
return join(getConfigDir(), "config.json");
|
|
23
53
|
}
|
|
24
54
|
|
|
25
|
-
export function getStatePath(): string {
|
|
26
|
-
return join(getConfigDir(),
|
|
55
|
+
export function getStatePath(profile?: string): string {
|
|
56
|
+
return join(getConfigDir(), `state${profileSuffix(profile)}.json`);
|
|
27
57
|
}
|
|
28
58
|
|
|
29
|
-
export function
|
|
30
|
-
|
|
31
|
-
|
|
59
|
+
export function getAutoSubmitLogPath(profile?: string): string {
|
|
60
|
+
return join(getConfigDir(), `auto-submit${profileSuffix(profile)}.log`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function getDaemonStatePath(profile?: string): string {
|
|
64
|
+
return join(getConfigDir(), `daemon-state${profileSuffix(profile)}.json`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function getDaemonPidPath(profile?: string): string {
|
|
68
|
+
return join(getConfigDir(), `daemon${profileSuffix(profile)}.pid`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function resolveProfile(profile?: string): string {
|
|
72
|
+
if (profile) return normalizeProfile(profile);
|
|
73
|
+
if (process.env.TRACEMP_PROFILE) return normalizeProfile(process.env.TRACEMP_PROFILE);
|
|
74
|
+
return normalizeProfile(loadConfigStore().defaultProfile);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function loadConfigStore(): ConfigStore {
|
|
78
|
+
const raw = readConfigFile();
|
|
79
|
+
if (isStoredProfileConfig(raw)) {
|
|
80
|
+
return {
|
|
81
|
+
defaultProfile: DEFAULT_PROFILE,
|
|
82
|
+
profiles: { [DEFAULT_PROFILE]: normalizeStoredProfileConfig(raw) },
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!isRecord(raw)) {
|
|
87
|
+
return {
|
|
88
|
+
defaultProfile: DEFAULT_PROFILE,
|
|
89
|
+
profiles: {},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const profiles = normalizeProfiles(raw.profiles);
|
|
94
|
+
const requestedDefault = normalizeProfile(
|
|
95
|
+
typeof raw.defaultProfile === "string" ? raw.defaultProfile : DEFAULT_PROFILE
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
defaultProfile: profiles[requestedDefault]
|
|
100
|
+
? requestedDefault
|
|
101
|
+
: profiles[DEFAULT_PROFILE]
|
|
102
|
+
? DEFAULT_PROFILE
|
|
103
|
+
: requestedDefault,
|
|
104
|
+
profiles,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function loadConfig(profile?: string): Config | null {
|
|
109
|
+
const store = loadConfigStore();
|
|
110
|
+
const resolvedProfile = resolveProfileFromStore(store, profile);
|
|
111
|
+
const selected = store.profiles[resolvedProfile];
|
|
112
|
+
if (!selected) return null;
|
|
113
|
+
return { profile: resolvedProfile, ...selected };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function saveConfig(
|
|
117
|
+
config: StoredProfileConfig,
|
|
118
|
+
options: { profile?: string; setDefault?: boolean } = {}
|
|
119
|
+
): Config {
|
|
120
|
+
const store = loadConfigStore();
|
|
121
|
+
const profile = normalizeProfile(options.profile);
|
|
122
|
+
|
|
123
|
+
store.profiles[profile] = normalizeStoredProfileConfig(config);
|
|
124
|
+
|
|
125
|
+
const normalizedDefault = normalizeProfile(store.defaultProfile);
|
|
126
|
+
if (options.setDefault || !store.profiles[normalizedDefault]) {
|
|
127
|
+
store.defaultProfile = profile;
|
|
128
|
+
} else {
|
|
129
|
+
store.defaultProfile = normalizedDefault;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
writeConfigStore(store);
|
|
133
|
+
return { profile, ...store.profiles[profile] };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function loadState(profile?: string): SubmitState {
|
|
137
|
+
const p = getStatePath(profile);
|
|
138
|
+
if (!existsSync(p)) return EMPTY_SUBMIT_STATE;
|
|
32
139
|
try {
|
|
33
|
-
return JSON.parse(readFileSync(p, "utf-8"))
|
|
140
|
+
return normalizeSubmitState(JSON.parse(readFileSync(p, "utf-8")));
|
|
34
141
|
} catch {
|
|
35
|
-
return
|
|
142
|
+
return EMPTY_SUBMIT_STATE;
|
|
36
143
|
}
|
|
37
144
|
}
|
|
38
145
|
|
|
39
|
-
export function
|
|
146
|
+
export function saveState(state: SubmitState, profile?: string): void {
|
|
40
147
|
mkdirSync(getConfigDir(), { recursive: true });
|
|
41
|
-
writeFileSync(
|
|
148
|
+
writeFileSync(getStatePath(profile), JSON.stringify(state, null, 2) + "\n", "utf-8");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function stateKey(sourceTool: string, sessionId: string): string {
|
|
152
|
+
return `${sourceTool}:${sessionId}`;
|
|
42
153
|
}
|
|
43
154
|
|
|
44
|
-
|
|
45
|
-
const p =
|
|
46
|
-
if (!existsSync(p)) return
|
|
155
|
+
function readConfigFile(): unknown {
|
|
156
|
+
const p = getConfigPath();
|
|
157
|
+
if (!existsSync(p)) return null;
|
|
47
158
|
try {
|
|
48
|
-
return JSON.parse(readFileSync(p, "utf-8"))
|
|
159
|
+
return JSON.parse(readFileSync(p, "utf-8"));
|
|
49
160
|
} catch {
|
|
50
|
-
return
|
|
161
|
+
return null;
|
|
51
162
|
}
|
|
52
163
|
}
|
|
53
164
|
|
|
54
|
-
|
|
165
|
+
function writeConfigStore(store: ConfigStore): void {
|
|
166
|
+
const profiles = normalizeProfiles(store.profiles);
|
|
167
|
+
const requestedDefault = normalizeProfile(store.defaultProfile);
|
|
168
|
+
const defaultProfile = profiles[requestedDefault]
|
|
169
|
+
? requestedDefault
|
|
170
|
+
: profiles[DEFAULT_PROFILE]
|
|
171
|
+
? DEFAULT_PROFILE
|
|
172
|
+
: requestedDefault;
|
|
173
|
+
|
|
55
174
|
mkdirSync(getConfigDir(), { recursive: true });
|
|
56
|
-
writeFileSync(
|
|
175
|
+
writeFileSync(
|
|
176
|
+
getConfigPath(),
|
|
177
|
+
JSON.stringify({ defaultProfile, profiles }, null, 2) + "\n",
|
|
178
|
+
"utf-8"
|
|
179
|
+
);
|
|
57
180
|
}
|
|
58
181
|
|
|
59
|
-
|
|
60
|
-
return
|
|
182
|
+
function resolveProfileFromStore(store: ConfigStore, profile?: string): string {
|
|
183
|
+
if (profile) return normalizeProfile(profile);
|
|
184
|
+
if (process.env.TRACEMP_PROFILE) return normalizeProfile(process.env.TRACEMP_PROFILE);
|
|
185
|
+
return normalizeProfile(store.defaultProfile);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function profileSuffix(profile?: string): string {
|
|
189
|
+
const resolvedProfile = normalizeProfile(profile);
|
|
190
|
+
return resolvedProfile === DEFAULT_PROFILE ? "" : `.${resolvedProfile}`;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function normalizeProfile(profile?: string | null): string {
|
|
194
|
+
const trimmed = (profile ?? "").trim().toLowerCase();
|
|
195
|
+
if (!trimmed) return DEFAULT_PROFILE;
|
|
196
|
+
|
|
197
|
+
const normalized = trimmed
|
|
198
|
+
.replace(/[^a-z0-9_-]+/g, "-")
|
|
199
|
+
.replace(/^-+/, "")
|
|
200
|
+
.replace(/-+$/, "");
|
|
201
|
+
|
|
202
|
+
return normalized || DEFAULT_PROFILE;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function normalizeProfiles(value: unknown): Record<string, StoredProfileConfig> {
|
|
206
|
+
if (!isRecord(value)) return {};
|
|
207
|
+
|
|
208
|
+
const profiles: Record<string, StoredProfileConfig> = {};
|
|
209
|
+
for (const [profile, config] of Object.entries(value)) {
|
|
210
|
+
if (!isStoredProfileConfig(config)) continue;
|
|
211
|
+
profiles[normalizeProfile(profile)] = normalizeStoredProfileConfig(config);
|
|
212
|
+
}
|
|
213
|
+
return profiles;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function normalizeStoredProfileConfig(config: StoredProfileConfig): StoredProfileConfig {
|
|
217
|
+
return {
|
|
218
|
+
apiKey: config.apiKey,
|
|
219
|
+
serverUrl: config.serverUrl,
|
|
220
|
+
email: config.email,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function normalizeSubmitState(value: unknown): SubmitState {
|
|
225
|
+
if (!isRecord(value)) {
|
|
226
|
+
return EMPTY_SUBMIT_STATE;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const chunks = isRecord(value.chunks)
|
|
230
|
+
? Object.fromEntries(
|
|
231
|
+
Object.entries(value.chunks)
|
|
232
|
+
.filter((entry): entry is [string, number] => typeof entry[1] === "number")
|
|
233
|
+
)
|
|
234
|
+
: {};
|
|
235
|
+
|
|
236
|
+
const sessions = isRecord(value.sessions)
|
|
237
|
+
? Object.fromEntries(
|
|
238
|
+
Object.entries(value.sessions)
|
|
239
|
+
.filter((entry): entry is [string, SessionUploadState] => isSessionUploadState(entry[1]))
|
|
240
|
+
)
|
|
241
|
+
: {};
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
version: 2,
|
|
245
|
+
chunks,
|
|
246
|
+
sessions,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function isRecord(value: unknown): value is JsonRecord {
|
|
251
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function isStoredProfileConfig(value: unknown): value is StoredProfileConfig {
|
|
255
|
+
return isRecord(value)
|
|
256
|
+
&& typeof value.apiKey === "string"
|
|
257
|
+
&& typeof value.serverUrl === "string"
|
|
258
|
+
&& typeof value.email === "string";
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function isSessionUploadState(value: unknown): value is SessionUploadState {
|
|
262
|
+
return isRecord(value)
|
|
263
|
+
&& (value.sourceTool === "claude_code" || value.sourceTool === "codex_cli" || value.sourceTool === "cursor")
|
|
264
|
+
&& typeof value.sourceSessionId === "string"
|
|
265
|
+
&& typeof value.locator === "string"
|
|
266
|
+
&& typeof value.nextChunkIndex === "number"
|
|
267
|
+
&& typeof value.openChunkStartTurn === "number"
|
|
268
|
+
&& typeof value.lastSeenTurnCount === "number"
|
|
269
|
+
&& (value.lastActivityAt === null || typeof value.lastActivityAt === "string")
|
|
270
|
+
&& (value.lastFlushedTurnId === null || typeof value.lastFlushedTurnId === "string");
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export function migrateSessionUploadState(value: SessionUploadState): SessionUploadState {
|
|
274
|
+
return {
|
|
275
|
+
...value,
|
|
276
|
+
// Backward compat: existing sessions assume all submitted chunks are confirmed
|
|
277
|
+
confirmedChunkIndex: value.confirmedChunkIndex ?? value.nextChunkIndex,
|
|
278
|
+
confirmedOpenChunkStartTurn: value.confirmedOpenChunkStartTurn ?? value.openChunkStartTurn,
|
|
279
|
+
unconfirmedSince: value.unconfirmedSince ?? null,
|
|
280
|
+
};
|
|
61
281
|
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const CLI_NAME = "tracemp";
|
|
2
|
+
export const DEFAULT_PROFILE = "prod";
|
|
3
|
+
export const PROD_SERVER_URL = "https://trace-marketplace-api.fly.dev";
|
|
4
|
+
export const DEV_SERVER_URL = "http://localhost:3001";
|
|
5
|
+
|
|
6
|
+
export function defaultServerUrlForProfile(profile: string): string {
|
|
7
|
+
return profile === "dev" ? DEV_SERVER_URL : PROD_SERVER_URL;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function inferProfileFromServerUrl(serverUrl: string): string {
|
|
11
|
+
return serverUrl === PROD_SERVER_URL ? DEFAULT_PROFILE : "dev";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function loginCommandForProfile(profile: string): string {
|
|
15
|
+
return profile === DEFAULT_PROFILE
|
|
16
|
+
? `${CLI_NAME} login`
|
|
17
|
+
: `${CLI_NAME} login --profile ${profile}`;
|
|
18
|
+
}
|