@aion0/forge 0.5.44 → 0.5.46
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/RELEASE_NOTES.md +3 -15
- package/package.json +1 -1
- package/tsconfig.json +3 -1
- package/intellij-plugin/README.md +0 -53
- package/intellij-plugin/build.gradle.kts +0 -60
- package/intellij-plugin/gradle/gradle-daemon-jvm.properties +0 -12
- package/intellij-plugin/gradle.properties +0 -9
- package/intellij-plugin/publish.sh +0 -78
- package/intellij-plugin/settings.gradle.kts +0 -7
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/action/LoginAction.kt +0 -49
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/action/LogoutAction.kt +0 -18
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/action/OpenWebUIAction.kt +0 -13
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/action/SwitchConnectionAction.kt +0 -26
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/api/ForgeClient.kt +0 -115
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/auth/Auth.kt +0 -31
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/connection/ConnectionManager.kt +0 -95
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/settings/ForgeConfigurable.kt +0 -81
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/DocsView.kt +0 -99
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/ForgeStatusBarWidgetFactory.kt +0 -94
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/ForgeToolWindowFactory.kt +0 -27
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/ForgeTreeView.kt +0 -176
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/Helpers.kt +0 -48
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/PipelinesView.kt +0 -226
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/TerminalsView.kt +0 -309
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/TreeNodeData.kt +0 -33
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/WorkspacesView.kt +0 -166
- package/intellij-plugin/src/main/resources/META-INF/plugin.xml +0 -88
- package/intellij-plugin/src/main/resources/icons/forge.svg +0 -3
- package/vscode-extension/.vscodeignore +0 -11
- package/vscode-extension/README.md +0 -48
- package/vscode-extension/media/icon.png +0 -0
- package/vscode-extension/media/icon.svg +0 -3
- package/vscode-extension/package-lock.json +0 -4046
- package/vscode-extension/package.json +0 -514
- package/vscode-extension/publish.sh +0 -49
- package/vscode-extension/src/api/client.ts +0 -217
- package/vscode-extension/src/auth/auth.ts +0 -32
- package/vscode-extension/src/commands/auth.ts +0 -44
- package/vscode-extension/src/commands/connection.ts +0 -113
- package/vscode-extension/src/commands/docs.ts +0 -40
- package/vscode-extension/src/commands/pipeline.ts +0 -103
- package/vscode-extension/src/commands/server.ts +0 -50
- package/vscode-extension/src/commands/smith.ts +0 -112
- package/vscode-extension/src/commands/task.ts +0 -43
- package/vscode-extension/src/commands/terminal.ts +0 -279
- package/vscode-extension/src/commands/workspace.ts +0 -138
- package/vscode-extension/src/connection/manager.ts +0 -80
- package/vscode-extension/src/docs/fs-provider.ts +0 -94
- package/vscode-extension/src/docs/result-provider.ts +0 -33
- package/vscode-extension/src/docs/transport.ts +0 -22
- package/vscode-extension/src/extension.ts +0 -314
- package/vscode-extension/src/statusbar.ts +0 -70
- package/vscode-extension/src/terminal/pseudoterm.ts +0 -123
- package/vscode-extension/src/views/docs.ts +0 -145
- package/vscode-extension/src/views/pipelines.ts +0 -222
- package/vscode-extension/src/views/terminals.ts +0 -91
- package/vscode-extension/src/views/workspaces.ts +0 -243
- package/vscode-extension/tsconfig.json +0 -16
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import * as vscode from 'vscode';
|
|
2
|
-
import { ForgeClient } from '../api/client';
|
|
3
|
-
import { detectDocsTransport } from '../docs/transport';
|
|
4
|
-
|
|
5
|
-
interface DocFileNode {
|
|
6
|
-
name: string;
|
|
7
|
-
path: string;
|
|
8
|
-
type: 'file' | 'dir';
|
|
9
|
-
fileType?: 'md' | 'image' | 'other';
|
|
10
|
-
children?: DocFileNode[];
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export class DocsProvider implements vscode.TreeDataProvider<DocItem> {
|
|
14
|
-
private _onDidChange = new vscode.EventEmitter<DocItem | undefined | void>();
|
|
15
|
-
onDidChangeTreeData = this._onDidChange.event;
|
|
16
|
-
|
|
17
|
-
// Cache the latest doc tree so we can resolve path → root-index lookups
|
|
18
|
-
// for "open file" without refetching the whole tree.
|
|
19
|
-
private cached: { roots: string[]; rootPaths: string[]; trees: DocFileNode[][] } | null = null;
|
|
20
|
-
|
|
21
|
-
constructor(private client: ForgeClient) {}
|
|
22
|
-
|
|
23
|
-
refresh(): void {
|
|
24
|
-
this.cached = null;
|
|
25
|
-
this._onDidChange.fire();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
getTreeItem(el: DocItem): vscode.TreeItem {
|
|
29
|
-
return el;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async getChildren(parent?: DocItem): Promise<DocItem[]> {
|
|
33
|
-
if (!parent) {
|
|
34
|
-
const r = await this.client.listDocs();
|
|
35
|
-
if (r.status === 401 || r.status === 403) return [loginPrompt()];
|
|
36
|
-
if (!r.ok || !r.data) return [hint('⚠ ' + (r.error || 'Not connected'))];
|
|
37
|
-
const roots = r.data.roots || [];
|
|
38
|
-
if (roots.length === 0) return [hint('No doc roots — add one in Settings → Doc Roots')];
|
|
39
|
-
|
|
40
|
-
// /api/docs only returns the tree for ONE root at a time (the rootIdx
|
|
41
|
-
// query). For each root, fire a separate request to get its tree.
|
|
42
|
-
const trees: DocFileNode[][] = [];
|
|
43
|
-
for (let i = 0; i < roots.length; i++) {
|
|
44
|
-
if (i === 0) {
|
|
45
|
-
// First call already happened — its tree is in r.data.tree
|
|
46
|
-
trees.push(r.data.tree || []);
|
|
47
|
-
} else {
|
|
48
|
-
const sub = await this.client.request<any>(`/api/docs?root=${i}`);
|
|
49
|
-
trees.push(sub.ok ? (sub.data?.tree || []) : []);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
const rootPaths = r.data.rootPaths || [];
|
|
53
|
-
this.cached = { roots, rootPaths, trees };
|
|
54
|
-
|
|
55
|
-
const transport = detectDocsTransport();
|
|
56
|
-
|
|
57
|
-
// Always show docRoot as a parent — even if there's only one — so the
|
|
58
|
-
// inline ⌨️ "Open Terminal" action has somewhere to live.
|
|
59
|
-
return roots.map((name, i) => {
|
|
60
|
-
const item = new DocItem(
|
|
61
|
-
name,
|
|
62
|
-
vscode.TreeItemCollapsibleState.Expanded, // expand by default
|
|
63
|
-
'forge.docRoot',
|
|
64
|
-
{ rootIdx: i, rootPath: rootPaths[i], rootName: name, view: 'docs' },
|
|
65
|
-
`${rootPaths[i] || name}\nMode: ${transport === 'local' ? 'local file' : 'remote (forge HTTP)'}`,
|
|
66
|
-
);
|
|
67
|
-
item.iconPath = new vscode.ThemeIcon('folder-library');
|
|
68
|
-
// Description: distinguish local vs remote at a glance.
|
|
69
|
-
item.description = transport === 'local' ? '$(home) local' : '$(globe) remote';
|
|
70
|
-
return item;
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (parent.contextValue === 'forge.docRoot' && this.cached) {
|
|
75
|
-
const idx = parent.meta.rootIdx;
|
|
76
|
-
const tree = this.cached.trees[idx] || [];
|
|
77
|
-
const rootPath = this.cached.rootPaths[idx];
|
|
78
|
-
return tree.map(n => buildNode(n, idx, rootPath));
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (parent.contextValue === 'forge.docDir' && this.cached) {
|
|
82
|
-
const children = parent.meta.children as DocFileNode[] | undefined;
|
|
83
|
-
if (!children) return [];
|
|
84
|
-
return children.map(n => buildNode(n, parent.meta.rootIdx, parent.meta.rootPath));
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return [];
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
class DocItem extends vscode.TreeItem {
|
|
92
|
-
constructor(
|
|
93
|
-
label: string,
|
|
94
|
-
collapsible: vscode.TreeItemCollapsibleState,
|
|
95
|
-
contextValue: string,
|
|
96
|
-
public meta: any = {},
|
|
97
|
-
tooltip?: string,
|
|
98
|
-
) {
|
|
99
|
-
super(label, collapsible);
|
|
100
|
-
this.contextValue = contextValue;
|
|
101
|
-
if (tooltip) this.tooltip = tooltip;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function buildNode(n: DocFileNode, rootIdx: number, rootPath: string): DocItem {
|
|
106
|
-
if (n.type === 'dir') {
|
|
107
|
-
const item = new DocItem(
|
|
108
|
-
n.name,
|
|
109
|
-
vscode.TreeItemCollapsibleState.Collapsed,
|
|
110
|
-
'forge.docDir',
|
|
111
|
-
{ rootIdx, rootPath, children: n.children || [], path: n.path },
|
|
112
|
-
n.path,
|
|
113
|
-
);
|
|
114
|
-
item.iconPath = new vscode.ThemeIcon('folder');
|
|
115
|
-
return item;
|
|
116
|
-
}
|
|
117
|
-
// File
|
|
118
|
-
const icon = n.fileType === 'md' ? 'markdown'
|
|
119
|
-
: n.fileType === 'image' ? 'file-media'
|
|
120
|
-
: 'file';
|
|
121
|
-
const item = new DocItem(
|
|
122
|
-
n.name,
|
|
123
|
-
vscode.TreeItemCollapsibleState.None,
|
|
124
|
-
'forge.docFile',
|
|
125
|
-
{ rootIdx, rootPath, path: n.path, fileType: n.fileType },
|
|
126
|
-
n.path,
|
|
127
|
-
);
|
|
128
|
-
item.iconPath = new vscode.ThemeIcon(icon);
|
|
129
|
-
item.command = {
|
|
130
|
-
command: 'forge.openDoc',
|
|
131
|
-
title: 'Open Doc',
|
|
132
|
-
arguments: [{ rootIdx, rootPath, path: n.path, fileType: n.fileType, name: n.name }],
|
|
133
|
-
};
|
|
134
|
-
return item;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function hint(msg: string): DocItem {
|
|
138
|
-
return new DocItem(msg, vscode.TreeItemCollapsibleState.None, 'forge.hint');
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function loginPrompt(): DocItem {
|
|
142
|
-
const item = new DocItem('🔑 Click to login', vscode.TreeItemCollapsibleState.None, 'forge.login');
|
|
143
|
-
item.command = { command: 'forge.login', title: 'Login' };
|
|
144
|
-
return item;
|
|
145
|
-
}
|
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
import * as vscode from 'vscode';
|
|
2
|
-
import { ForgeClient } from '../api/client';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Pipelines tree:
|
|
6
|
-
* PIPELINES
|
|
7
|
-
* └─ 📁 <project>
|
|
8
|
-
* ├─ ⚙ <binding> (workflow attached to project — schedule, enable/disable)
|
|
9
|
-
* ├─ ⚙ <binding>
|
|
10
|
-
* ├─ ─── Recent Runs ───
|
|
11
|
-
* ├─ ✓/▶ <run> (most-recent runs of this project's workflows)
|
|
12
|
-
* └─ ...
|
|
13
|
-
*/
|
|
14
|
-
export class PipelinesProvider implements vscode.TreeDataProvider<PItem> {
|
|
15
|
-
private _onDidChange = new vscode.EventEmitter<PItem | undefined | void>();
|
|
16
|
-
onDidChangeTreeData = this._onDidChange.event;
|
|
17
|
-
|
|
18
|
-
constructor(private client: ForgeClient) {}
|
|
19
|
-
|
|
20
|
-
refresh(): void {
|
|
21
|
-
this._onDidChange.fire();
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
getTreeItem(el: PItem): vscode.TreeItem {
|
|
25
|
-
return el;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async getChildren(parent?: PItem): Promise<PItem[]> {
|
|
29
|
-
if (!parent) {
|
|
30
|
-
const r = await this.client.listProjects();
|
|
31
|
-
if (r.status === 401 || r.status === 403) return [loginPrompt()];
|
|
32
|
-
if (!r.ok || !Array.isArray(r.data)) return [hint('⚠ ' + (r.error || 'Not connected'))];
|
|
33
|
-
if (r.data.length === 0) return [hint('No projects configured — add one in Settings → Project Roots')];
|
|
34
|
-
const sorted = [...r.data].sort((a: any, b: any) => (a.name || '').localeCompare(b.name || ''));
|
|
35
|
-
return sorted.map((p: any) => {
|
|
36
|
-
const item = new PItem(
|
|
37
|
-
p.name,
|
|
38
|
-
vscode.TreeItemCollapsibleState.Collapsed,
|
|
39
|
-
'forge.pipelineProject',
|
|
40
|
-
{ projectPath: p.path, projectName: p.name, view: 'tasks' },
|
|
41
|
-
p.path,
|
|
42
|
-
);
|
|
43
|
-
item.iconPath = new vscode.ThemeIcon('folder');
|
|
44
|
-
return item;
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (parent.contextValue === 'forge.pipelineProject' && parent.meta?.projectPath) {
|
|
49
|
-
const r = await this.client.getProjectPipelines(parent.meta.projectPath);
|
|
50
|
-
if (!r.ok) return [hint('⚠ ' + (r.error || 'failed to load'))];
|
|
51
|
-
const bindings: any[] = r.data?.bindings || [];
|
|
52
|
-
const runs: any[] = r.data?.runs || [];
|
|
53
|
-
const out: PItem[] = [];
|
|
54
|
-
|
|
55
|
-
// Bindings — workflows attached to this project.
|
|
56
|
-
if (bindings.length === 0) {
|
|
57
|
-
const empty = new PItem(
|
|
58
|
-
'+ No pipelines yet',
|
|
59
|
-
vscode.TreeItemCollapsibleState.None,
|
|
60
|
-
'forge.pipelineEmpty',
|
|
61
|
-
parent.meta,
|
|
62
|
-
'Click to add a pipeline workflow to this project',
|
|
63
|
-
);
|
|
64
|
-
empty.iconPath = new vscode.ThemeIcon('add', new vscode.ThemeColor('charts.gray'));
|
|
65
|
-
empty.command = {
|
|
66
|
-
command: 'forge.addPipeline',
|
|
67
|
-
title: 'Add Pipeline',
|
|
68
|
-
arguments: [parent.meta],
|
|
69
|
-
};
|
|
70
|
-
out.push(empty);
|
|
71
|
-
} else {
|
|
72
|
-
for (const b of bindings) {
|
|
73
|
-
const enabled = !!b.enabled;
|
|
74
|
-
const schedule = b.config?.schedule || b.config?.cron || (b.config?.trigger === 'manual' ? 'manual' : 'manual');
|
|
75
|
-
const item = new PItem(
|
|
76
|
-
b.workflowName,
|
|
77
|
-
vscode.TreeItemCollapsibleState.None,
|
|
78
|
-
enabled ? 'forge.pipelineBinding.enabled' : 'forge.pipelineBinding.disabled',
|
|
79
|
-
{ projectPath: parent.meta.projectPath, projectName: parent.meta.projectName, workflowName: b.workflowName, enabled },
|
|
80
|
-
`${b.workflowName}\n${enabled ? 'enabled' : 'disabled'} · ${schedule}${b.nextRunAt ? `\nnext run: ${new Date(b.nextRunAt).toLocaleString()}` : ''}`,
|
|
81
|
-
);
|
|
82
|
-
item.iconPath = new vscode.ThemeIcon(
|
|
83
|
-
enabled ? 'gear' : 'gear',
|
|
84
|
-
new vscode.ThemeColor(enabled ? 'charts.green' : 'charts.gray'),
|
|
85
|
-
);
|
|
86
|
-
item.description = `${enabled ? '' : 'disabled · '}${schedule}`;
|
|
87
|
-
// Click → trigger now (fast iteration use case).
|
|
88
|
-
item.command = {
|
|
89
|
-
command: 'forge.triggerPipeline',
|
|
90
|
-
title: 'Trigger Pipeline',
|
|
91
|
-
arguments: [{ projectPath: parent.meta.projectPath, projectName: parent.meta.projectName, workflowName: b.workflowName }],
|
|
92
|
-
};
|
|
93
|
-
out.push(item);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Recent Runs section.
|
|
98
|
-
if (runs.length > 0) {
|
|
99
|
-
const sep = new PItem('Recent Runs', vscode.TreeItemCollapsibleState.None, 'forge.pipelineRunsHeader');
|
|
100
|
-
sep.iconPath = new vscode.ThemeIcon('history');
|
|
101
|
-
out.push(sep);
|
|
102
|
-
// Sort by createdAt desc, last 10.
|
|
103
|
-
const sorted = [...runs].sort((a: any, b: any) => (b.createdAt || '').localeCompare(a.createdAt || ''));
|
|
104
|
-
for (const run of sorted.slice(0, 10)) {
|
|
105
|
-
const status = run.status || 'pending';
|
|
106
|
-
const created = run.createdAt ? new Date(run.createdAt).toLocaleString() : '';
|
|
107
|
-
// Two ids: run.id (the project-pipelines bookkeeping row) and
|
|
108
|
-
// run.pipelineId (the actual /api/pipelines/<id> document). Use
|
|
109
|
-
// pipelineId for fetching detail; fall back to id for very old rows.
|
|
110
|
-
const detailId = run.pipelineId || run.id;
|
|
111
|
-
const item = new PItem(
|
|
112
|
-
`${statusEmoji(status)} ${run.workflowName || detailId?.slice(0, 8)}`,
|
|
113
|
-
vscode.TreeItemCollapsibleState.Collapsed,
|
|
114
|
-
'forge.pipelineRun',
|
|
115
|
-
{ runId: detailId, status, view: 'tasks' },
|
|
116
|
-
`${run.workflowName}\nstatus: ${status}\ncreated: ${created}`,
|
|
117
|
-
);
|
|
118
|
-
item.iconPath = statusIcon(status);
|
|
119
|
-
item.description = `${status} · ${created}`;
|
|
120
|
-
out.push(item);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return out;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Run expanded → show nodes with status + error.
|
|
128
|
-
if (parent.contextValue === 'forge.pipelineRun' && parent.meta?.runId) {
|
|
129
|
-
const r = await this.client.getPipeline(parent.meta.runId);
|
|
130
|
-
if (!r.ok || !r.data) return [hint(`⚠ ${r.error || 'failed to load'}`)];
|
|
131
|
-
const nodes: Record<string, any> = r.data.nodes || {};
|
|
132
|
-
const order: string[] = r.data.nodeOrder || Object.keys(nodes);
|
|
133
|
-
if (order.length === 0) return [hint('No nodes')];
|
|
134
|
-
return order.map((name) => {
|
|
135
|
-
const n = nodes[name] || {};
|
|
136
|
-
const status = n.status || 'pending';
|
|
137
|
-
const item = new PItem(
|
|
138
|
-
`${statusEmoji(status)} ${name}`,
|
|
139
|
-
vscode.TreeItemCollapsibleState.None,
|
|
140
|
-
'forge.pipelineNode',
|
|
141
|
-
{
|
|
142
|
-
runId: parent.meta.runId,
|
|
143
|
-
nodeName: name,
|
|
144
|
-
status,
|
|
145
|
-
error: n.error || '',
|
|
146
|
-
taskId: n.taskId,
|
|
147
|
-
outputs: n.outputs || {},
|
|
148
|
-
startedAt: n.startedAt,
|
|
149
|
-
completedAt: n.completedAt,
|
|
150
|
-
},
|
|
151
|
-
`${name}\nstatus: ${status}${n.error ? `\n\nerror:\n${n.error}` : ''}${n.taskId ? `\ntaskId: ${n.taskId}` : ''}`,
|
|
152
|
-
);
|
|
153
|
-
item.iconPath = statusIcon(status);
|
|
154
|
-
// Description shows error preview for failed nodes, status for others.
|
|
155
|
-
if (status === 'failed' && n.error) {
|
|
156
|
-
item.description = '✕ ' + String(n.error).split('\n')[0].slice(0, 60);
|
|
157
|
-
} else if (n.startedAt) {
|
|
158
|
-
const dur = n.completedAt
|
|
159
|
-
? `${Math.round((new Date(n.completedAt).getTime() - new Date(n.startedAt).getTime()) / 1000)}s`
|
|
160
|
-
: '';
|
|
161
|
-
item.description = dur ? `${status} · ${dur}` : status;
|
|
162
|
-
}
|
|
163
|
-
// Always clickable — pending nodes still show status/duration (helpful)
|
|
164
|
-
// and once they run we get richer detail.
|
|
165
|
-
item.command = {
|
|
166
|
-
command: 'forge.showPipelineNodeResult',
|
|
167
|
-
title: 'Show Node Result',
|
|
168
|
-
arguments: [{ runId: parent.meta.runId, nodeName: name, status, error: n.error, taskId: n.taskId, outputs: n.outputs, startedAt: n.startedAt, completedAt: n.completedAt }],
|
|
169
|
-
};
|
|
170
|
-
return item;
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return [];
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
class PItem extends vscode.TreeItem {
|
|
179
|
-
constructor(
|
|
180
|
-
label: string,
|
|
181
|
-
collapsible: vscode.TreeItemCollapsibleState,
|
|
182
|
-
contextValue: string,
|
|
183
|
-
public meta: any = {},
|
|
184
|
-
tooltip?: string,
|
|
185
|
-
) {
|
|
186
|
-
super(label, collapsible);
|
|
187
|
-
this.contextValue = contextValue;
|
|
188
|
-
if (tooltip) this.tooltip = tooltip;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function statusEmoji(s: string): string {
|
|
193
|
-
switch (s) {
|
|
194
|
-
case 'running': return '▶';
|
|
195
|
-
case 'pending': return '⌛';
|
|
196
|
-
case 'done': return '✓';
|
|
197
|
-
case 'failed': return '✕';
|
|
198
|
-
case 'cancelled': return '⊘';
|
|
199
|
-
default: return '·';
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function statusIcon(s: string): vscode.ThemeIcon {
|
|
204
|
-
switch (s) {
|
|
205
|
-
case 'running': return new vscode.ThemeIcon('loading~spin', new vscode.ThemeColor('charts.green'));
|
|
206
|
-
case 'pending': return new vscode.ThemeIcon('clock', new vscode.ThemeColor('charts.gray'));
|
|
207
|
-
case 'done': return new vscode.ThemeIcon('check', new vscode.ThemeColor('charts.green'));
|
|
208
|
-
case 'failed': return new vscode.ThemeIcon('error', new vscode.ThemeColor('charts.red'));
|
|
209
|
-
case 'cancelled': return new vscode.ThemeIcon('stop-circle', new vscode.ThemeColor('charts.gray'));
|
|
210
|
-
default: return new vscode.ThemeIcon('circle-outline');
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function hint(msg: string): PItem {
|
|
215
|
-
return new PItem(msg, vscode.TreeItemCollapsibleState.None, 'forge.hint');
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
function loginPrompt(): PItem {
|
|
219
|
-
const item = new PItem('🔑 Click to login', vscode.TreeItemCollapsibleState.None, 'forge.login');
|
|
220
|
-
item.command = { command: 'forge.login', title: 'Login' };
|
|
221
|
-
return item;
|
|
222
|
-
}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import * as vscode from 'vscode';
|
|
2
|
-
import { ForgeClient } from '../api/client';
|
|
3
|
-
|
|
4
|
-
export class TerminalsProvider implements vscode.TreeDataProvider<TermItem> {
|
|
5
|
-
private _onDidChange = new vscode.EventEmitter<TermItem | undefined | void>();
|
|
6
|
-
onDidChangeTreeData = this._onDidChange.event;
|
|
7
|
-
|
|
8
|
-
constructor(private client: ForgeClient) {}
|
|
9
|
-
|
|
10
|
-
refresh(): void {
|
|
11
|
-
this._onDidChange.fire();
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
getTreeItem(el: TermItem): vscode.TreeItem {
|
|
15
|
-
return el;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
async getChildren(): Promise<TermItem[]> {
|
|
19
|
-
const r = await this.client.listTerminals();
|
|
20
|
-
if (r.status === 401 || r.status === 403) {
|
|
21
|
-
const item = new TermItem('🔑 Click to login', vscode.TreeItemCollapsibleState.None, 'forge.login');
|
|
22
|
-
item.command = { command: 'forge.login', title: 'Login' };
|
|
23
|
-
return [item];
|
|
24
|
-
}
|
|
25
|
-
if (!r.ok || !r.data) return [hint('⚠ ' + (r.error || 'Not connected'))];
|
|
26
|
-
|
|
27
|
-
// /api/terminal-state returns { sessions: [{name, projectPath, ...}], ... }
|
|
28
|
-
const sessions: any[] = Array.isArray(r.data) ? r.data : (r.data.sessions || []);
|
|
29
|
-
|
|
30
|
-
const items: TermItem[] = sessions.map((s: any) => {
|
|
31
|
-
const name = s.name || s.sessionName || 'unknown';
|
|
32
|
-
const proj = s.projectPath ? s.projectPath.split('/').pop() : '';
|
|
33
|
-
const item = new TermItem(
|
|
34
|
-
`${name}${proj ? ' · ' + proj : ''}`,
|
|
35
|
-
vscode.TreeItemCollapsibleState.None,
|
|
36
|
-
'forge.terminal',
|
|
37
|
-
{ sessionName: name },
|
|
38
|
-
s.projectPath || name,
|
|
39
|
-
);
|
|
40
|
-
item.iconPath = new vscode.ThemeIcon('terminal');
|
|
41
|
-
item.command = {
|
|
42
|
-
command: 'forge.attachTerminal',
|
|
43
|
-
title: 'Attach',
|
|
44
|
-
arguments: [{ sessionName: name }],
|
|
45
|
-
};
|
|
46
|
-
return item;
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
// Always offer "Open <project>…" entries below — clicking opens the
|
|
50
|
-
// session picker (Current / New / Other) like the forge web UI.
|
|
51
|
-
const projRes = await this.client.listProjects();
|
|
52
|
-
const projects: any[] = projRes.ok && Array.isArray(projRes.data) ? projRes.data : [];
|
|
53
|
-
for (const p of projects) {
|
|
54
|
-
const item = new TermItem(
|
|
55
|
-
`Open ${p.name}…`,
|
|
56
|
-
vscode.TreeItemCollapsibleState.None,
|
|
57
|
-
'forge.openSession',
|
|
58
|
-
{ projectPath: p.path },
|
|
59
|
-
`Open a session in ${p.path}`,
|
|
60
|
-
);
|
|
61
|
-
item.iconPath = new vscode.ThemeIcon('repo');
|
|
62
|
-
item.command = {
|
|
63
|
-
command: 'forge.openSession',
|
|
64
|
-
title: 'Open Session',
|
|
65
|
-
arguments: [{ projectPath: p.path, projectName: p.name }],
|
|
66
|
-
};
|
|
67
|
-
items.push(item);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (items.length === 0) return [hint('No active terminals — add a project root in Settings')];
|
|
71
|
-
return items;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
class TermItem extends vscode.TreeItem {
|
|
76
|
-
constructor(
|
|
77
|
-
label: string,
|
|
78
|
-
collapsible: vscode.TreeItemCollapsibleState,
|
|
79
|
-
contextValue: string,
|
|
80
|
-
public meta: any = {},
|
|
81
|
-
tooltip?: string,
|
|
82
|
-
) {
|
|
83
|
-
super(label, collapsible);
|
|
84
|
-
this.contextValue = contextValue;
|
|
85
|
-
if (tooltip) this.tooltip = tooltip;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function hint(msg: string): TermItem {
|
|
90
|
-
return new TermItem(msg, vscode.TreeItemCollapsibleState.None, 'forge.hint');
|
|
91
|
-
}
|